refactor: move command funcs to internal package

Move the command functions to the new internal commands package
This commit is contained in:
Dan Anglin 2024-09-21 16:53:11 +01:00
parent 171b728686
commit 8b03ba7be1
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
10 changed files with 367 additions and 319 deletions

117
internal/commands/catch.go Normal file
View file

@ -0,0 +1,117 @@
package commands
import (
"errors"
"fmt"
"math/rand/v2"
"slices"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/poketrainer"
)
func CatchFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) CommandFunc {
return func(args []string) error {
if args == nil {
return errors.New("the name of the Pokemon has not been specified")
}
if len(args) != 1 {
return fmt.Errorf(
"unexpected number of Pokemon names: want 1; got %d",
len(args),
)
}
pokemonName := args[0]
if _, caught := trainer.GetPokemonFromPokedex(pokemonName); caught {
return fmt.Errorf(
"you've already caught a %s",
pokemonName,
)
}
pokemonDetails, err := client.GetPokemon(pokemonName)
if err != nil {
return fmt.Errorf(
"unable to get the information on %s: %w",
pokemonName,
err,
)
}
encountersPath := pokemonDetails.LocationAreaEncounters
encounterAreas, err := client.GetPokemonLocationAreas(encountersPath)
if err != nil {
return fmt.Errorf(
"unable to get the Pokemon's possible encounter areas: %w",
err,
)
}
validLocationArea := false
currentLocation := trainer.CurrentLocationAreaName()
for _, area := range slices.All(encounterAreas) {
if currentLocation == area.LocationArea.Name {
validLocationArea = true
break
}
}
if !validLocationArea {
return fmt.Errorf(
"%s cannot be found in %s",
pokemonName,
currentLocation,
)
}
chance := 50
fmt.Printf("Throwing a Pokeball at %s...\n", pokemonName)
if caught := success(chance); caught {
trainer.AddPokemonToPokedex(pokemonName, pokemonDetails)
fmt.Printf("%s was caught!\nYou may now inspect it with the inspect command.\n", pokemonName)
} else {
fmt.Printf("%s escaped!\n", pokemonName)
}
return nil
}
}
func success(chance int) bool {
if chance >= 100 {
return true
}
if chance <= 0 {
return false
}
maxInt := 100
numGenerator := rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64()))
luckyNumberSet := make(map[int]struct{})
for len(luckyNumberSet) < chance {
num := numGenerator.IntN(maxInt)
if _, ok := luckyNumberSet[num]; !ok {
luckyNumberSet[num] = struct{}{}
}
}
roller := rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64()))
got := roller.IntN(maxInt)
_, ok := luckyNumberSet[got]
return ok
}

View file

@ -0,0 +1,3 @@
package commands
type CommandFunc func(args []string) error

View file

@ -0,0 +1,9 @@
package commands
import "os"
func ExitProgram(_ []string) error {
os.Exit(0)
return nil
}

View file

@ -0,0 +1,33 @@
package commands
import (
"fmt"
"slices"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/poketrainer"
)
func ExploreFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) CommandFunc {
return func(_ []string) error {
locationAreaName := trainer.CurrentLocationAreaName()
fmt.Printf("Exploring %s...\n", locationAreaName)
locationArea, err := client.GetLocationArea(locationAreaName)
if err != nil {
return fmt.Errorf(
"unable to get the location area: %w",
err,
)
}
fmt.Println("Found Pokemon:")
for _, encounter := range slices.All(locationArea.PokemonEncounters) {
fmt.Printf("- %s\n", encounter.Pokemon.Name)
}
return nil
}
}

29
internal/commands/help.go Normal file
View file

@ -0,0 +1,29 @@
package commands
import (
"fmt"
"maps"
"slices"
)
func HelpFunc(summaries map[string]string) CommandFunc {
return func(_ []string) error {
keys := []string{}
for key := range maps.All(summaries) {
keys = append(keys, key)
}
slices.Sort(keys)
fmt.Printf("\nCommands:\n")
for _, key := range slices.All(keys) {
fmt.Printf("\n%s: %s", key, summaries[key])
}
fmt.Printf("\n\n")
return nil
}
}

View file

@ -0,0 +1,56 @@
package commands
import (
"errors"
"fmt"
"slices"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/poketrainer"
)
func InspectFunc(trainer *poketrainer.Trainer) CommandFunc {
return func(args []string) error {
if args == nil {
return errors.New("the name of the Pokemon has not been specified")
}
if len(args) != 1 {
return fmt.Errorf(
"unexpected number of Pokemon names: want 1; got %d",
len(args),
)
}
pokemonName := args[0]
pokemon, ok := trainer.GetPokemonFromPokedex(pokemonName)
if !ok {
return fmt.Errorf("you have not caught %s", pokemonName)
}
info := fmt.Sprintf(
"Name: %s\nHeight: %d\nWeight: %d\nStats:",
pokemon.Name,
pokemon.Height,
pokemon.Weight,
)
for _, stat := range slices.All(pokemon.Stats) {
info += fmt.Sprintf(
"\n - %s: %d",
stat.Stat.Name,
stat.BaseStat,
)
}
info += "\nTypes:"
for _, pType := range slices.All(pokemon.Types) {
info += "\n - " + pType.Type.Name
}
fmt.Println(info)
return nil
}
}

53
internal/commands/map.go Normal file
View file

@ -0,0 +1,53 @@
package commands
import (
"fmt"
"slices"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/poketrainer"
)
func MapFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) CommandFunc {
return func(_ []string) error {
url := trainer.NextLocationArea()
if url == nil {
url = new(string)
*url = pokeclient.LocationAreaPath
}
return printResourceList(client, *url, trainer.UpdateLocationAreas)
}
}
func MapBFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) CommandFunc {
return func(_ []string) error {
url := trainer.PreviousLocationArea()
if url == nil {
return fmt.Errorf("no previous locations available")
}
return printResourceList(client, *url, trainer.UpdateLocationAreas)
}
}
func printResourceList(
client *pokeclient.Client,
url string,
updateStateFunc func(previous *string, next *string),
) error {
list, err := client.GetNamedAPIResourceList(url)
if err != nil {
return fmt.Errorf("unable to get the list of resources: %w", err)
}
if updateStateFunc != nil {
updateStateFunc(list.Previous, list.Next)
}
for _, location := range slices.All(list.Results) {
fmt.Println(location.Name)
}
return nil
}

View file

@ -0,0 +1,11 @@
package commands
import "codeflow.dananglin.me.uk/apollo/pokedex/internal/poketrainer"
func PokedexFunc(trainer *poketrainer.Trainer) CommandFunc {
return func(_ []string) error {
trainer.ListAllPokemonFromPokedex()
return nil
}
}

View file

@ -0,0 +1,40 @@
package commands
import (
"errors"
"fmt"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/poketrainer"
)
func VisitFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) CommandFunc {
return func(args []string) error {
if args == nil {
return errors.New("the location area has not been specified")
}
if len(args) != 1 {
return fmt.Errorf(
"unexpected number of location areas: want 1; got %d",
len(args),
)
}
locationAreaName := args[0]
locationArea, err := client.GetLocationArea(locationAreaName)
if err != nil {
return fmt.Errorf(
"unable to get the location area: %w",
err,
)
}
trainer.UpdateCurrentLocationAreaName(locationArea.Name)
fmt.Println("You are now visiting", locationArea.Name)
return nil
}
}

335
main.go
View file

@ -2,25 +2,20 @@ package main
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"maps" "maps"
"math/rand/v2"
"os" "os"
"slices"
"strings" "strings"
"time" "time"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/commands"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient" "codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/poketrainer" "codeflow.dananglin.me.uk/apollo/pokedex/internal/poketrainer"
) )
type callbackFunc func(args []string) error
type command struct { type command struct {
name string
description string description string
callback callbackFunc callback commands.CommandFunc
} }
func main() { func main() {
@ -28,67 +23,57 @@ func main() {
} }
func run() { func run() {
client := pokeclient.NewClient( var (
5*time.Minute, cacheCleanupInterval = 30 * time.Minute
10*time.Second, httpTimeout = 10 * time.Second
client = pokeclient.NewClient(cacheCleanupInterval, httpTimeout)
trainer = poketrainer.NewTrainer()
) )
trainer := poketrainer.NewTrainer()
commandMap := map[string]command{ commandMap := map[string]command{
"catch": { "catch": {
name: "catch",
description: "Catch a Pokemon and add it to your Pokedex", description: "Catch a Pokemon and add it to your Pokedex",
callback: catchFunc(client, trainer), callback: commands.CatchFunc(client, trainer),
}, },
"exit": { "exit": {
name: "exit",
description: "Exit the Pokedex", description: "Exit the Pokedex",
callback: exitFunc, callback: commands.ExitProgram,
}, },
"explore": { "explore": {
name: "explore",
description: "List all the Pokemon in a given area", description: "List all the Pokemon in a given area",
callback: exploreFunc(client, trainer), callback: commands.ExploreFunc(client, trainer),
}, },
"help": { "help": {
name: "help",
description: "Display the help message", description: "Display the help message",
callback: nil, callback: nil,
}, },
"inspect": { "inspect": {
name: "inspect",
description: "Inspect a Pokemon from your Pokedex", description: "Inspect a Pokemon from your Pokedex",
callback: inspectFunc(trainer), callback: commands.InspectFunc(trainer),
}, },
"map": { "map": {
name: "map",
description: "Display the next 20 locations in the Pokemon world", description: "Display the next 20 locations in the Pokemon world",
callback: mapFunc(client, trainer), callback: commands.MapFunc(client, trainer),
}, },
"mapb": { "mapb": {
name: "map back",
description: "Display the previous 20 locations in the Pokemon world", description: "Display the previous 20 locations in the Pokemon world",
callback: mapBFunc(client, trainer), callback: commands.MapBFunc(client, trainer),
}, },
"pokedex": { "pokedex": {
name: "pokedex",
description: "List the names of all the Pokemon in your Pokedex", description: "List the names of all the Pokemon in your Pokedex",
callback: pokedexFunc(trainer), callback: commands.PokedexFunc(trainer),
}, },
"visit": { "visit": {
name: "visit",
description: "Visit a location area", description: "Visit a location area",
callback: visitFunc(client, trainer), callback: commands.VisitFunc(client, trainer),
}, },
} }
summaries := summaryMap(commandMap) summaries := summaryMap(commandMap)
commandMap["help"] = command{ commandMap["help"] = command{
name: "help",
description: "Displays a help message", description: "Displays a help message",
callback: helpFunc(summaries), callback: commands.HelpFunc(summaries),
} }
fmt.Printf("\nWelcome to the Pokedex!\n") fmt.Printf("\nWelcome to the Pokedex!\n")
@ -126,263 +111,6 @@ func run() {
} }
} }
func helpFunc(summaries map[string]string) callbackFunc {
return func(_ []string) error {
keys := []string{}
for key := range maps.All(summaries) {
keys = append(keys, key)
}
slices.Sort(keys)
fmt.Printf("\nCommands:\n")
for _, key := range slices.All(keys) {
fmt.Printf("\n%s: %s", key, summaries[key])
}
fmt.Printf("\n\n")
return nil
}
}
func exitFunc(_ []string) error {
os.Exit(0)
return nil
}
func mapFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) callbackFunc {
return func(_ []string) error {
url := trainer.NextLocationArea()
if url == nil {
url = new(string)
*url = pokeclient.LocationAreaPath
}
return printResourceList(client, *url, trainer.UpdateLocationAreas)
}
}
func mapBFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) callbackFunc {
return func(_ []string) error {
url := trainer.PreviousLocationArea()
if url == nil {
return fmt.Errorf("no previous locations available")
}
return printResourceList(client, *url, trainer.UpdateLocationAreas)
}
}
func exploreFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) callbackFunc {
return func(_ []string) error {
locationAreaName := trainer.CurrentLocationAreaName()
fmt.Printf("Exploring %s...\n", locationAreaName)
locationArea, err := client.GetLocationArea(locationAreaName)
if err != nil {
return fmt.Errorf(
"unable to get the location area: %w",
err,
)
}
fmt.Println("Found Pokemon:")
for _, encounter := range slices.All(locationArea.PokemonEncounters) {
fmt.Printf("- %s\n", encounter.Pokemon.Name)
}
return nil
}
}
func visitFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) callbackFunc {
return func(args []string) error {
if args == nil {
return errors.New("the location area has not been specified")
}
if len(args) != 1 {
return fmt.Errorf(
"unexpected number of location areas: want 1; got %d",
len(args),
)
}
locationAreaName := args[0]
locationArea, err := client.GetLocationArea(locationAreaName)
if err != nil {
return fmt.Errorf(
"unable to get the location area: %w",
err,
)
}
trainer.UpdateCurrentLocationAreaName(locationArea.Name)
fmt.Println("You are now visiting", locationArea.Name)
return nil
}
}
func catchFunc(client *pokeclient.Client, trainer *poketrainer.Trainer) callbackFunc {
return func(args []string) error {
if args == nil {
return errors.New("the name of the Pokemon has not been specified")
}
if len(args) != 1 {
return fmt.Errorf(
"unexpected number of Pokemon names: want 1; got %d",
len(args),
)
}
pokemonName := args[0]
if _, caught := trainer.GetPokemonFromPokedex(pokemonName); caught {
return fmt.Errorf(
"you've already caught a %s",
pokemonName,
)
}
pokemonDetails, err := client.GetPokemon(pokemonName)
if err != nil {
return fmt.Errorf(
"unable to get the information on %s: %w",
pokemonName,
err,
)
}
encountersPath := pokemonDetails.LocationAreaEncounters
encounterAreas, err := client.GetPokemonLocationAreas(encountersPath)
if err != nil {
return fmt.Errorf(
"unable to get the Pokemon's possible encounter areas: %w",
err,
)
}
validLocationArea := false
currentLocation := trainer.CurrentLocationAreaName()
for _, area := range slices.All(encounterAreas) {
if currentLocation == area.LocationArea.Name {
validLocationArea = true
break
}
}
if !validLocationArea {
return fmt.Errorf(
"%s cannot be found in %s",
pokemonName,
currentLocation,
)
}
chance := 50
fmt.Printf("Throwing a Pokeball at %s...\n", pokemonName)
if caught := catchPokemon(chance); caught {
trainer.AddPokemonToPokedex(pokemonName, pokemonDetails)
fmt.Printf("%s was caught!\nYou may now inspect it with the inspect command.\n", pokemonName)
} else {
fmt.Printf("%s escaped!\n", pokemonName)
}
return nil
}
}
func inspectFunc(trainer *poketrainer.Trainer) callbackFunc {
return func(args []string) error {
if args == nil {
return errors.New("the name of the Pokemon has not been specified")
}
if len(args) != 1 {
return fmt.Errorf(
"unexpected number of Pokemon names: want 1; got %d",
len(args),
)
}
pokemonName := args[0]
pokemon, ok := trainer.GetPokemonFromPokedex(pokemonName)
if !ok {
return fmt.Errorf("you have not caught %s", pokemonName)
}
info := fmt.Sprintf(
"Name: %s\nHeight: %d\nWeight: %d\nStats:",
pokemon.Name,
pokemon.Height,
pokemon.Weight,
)
for _, stat := range slices.All(pokemon.Stats) {
info += fmt.Sprintf(
"\n - %s: %d",
stat.Stat.Name,
stat.BaseStat,
)
}
info += "\nTypes:"
for _, pType := range slices.All(pokemon.Types) {
info += "\n - " + pType.Type.Name
}
fmt.Println(info)
return nil
}
}
func pokedexFunc(trainer *poketrainer.Trainer) callbackFunc {
return func(_ []string) error {
trainer.ListAllPokemonFromPokedex()
return nil
}
}
func printResourceList(
client *pokeclient.Client,
url string,
updateStateFunc func(previous *string, next *string),
) error {
list, err := client.GetNamedAPIResourceList(url)
if err != nil {
return fmt.Errorf("unable to get the list of resources: %w", err)
}
if updateStateFunc != nil {
updateStateFunc(list.Previous, list.Next)
}
for _, location := range slices.All(list.Results) {
fmt.Println(location.Name)
}
return nil
}
func summaryMap(commandMap map[string]command) map[string]string { func summaryMap(commandMap map[string]command) map[string]string {
summaries := make(map[string]string) summaries := make(map[string]string)
@ -406,34 +134,3 @@ func parseArgs(input string) (string, []string) {
return split[0], split[1:] return split[0], split[1:]
} }
func catchPokemon(chance int) bool {
if chance >= 100 {
return true
}
if chance <= 0 {
return false
}
maxInt := 100
numGenerator := rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64()))
luckyNumberSet := make(map[int]struct{})
for len(luckyNumberSet) < chance {
num := numGenerator.IntN(maxInt)
if _, ok := luckyNumberSet[num]; !ok {
luckyNumberSet[num] = struct{}{}
}
}
roller := rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64()))
got := roller.IntN(maxInt)
_, ok := luckyNumberSet[got]
return ok
}