pokecli/main.go

393 lines
7.4 KiB
Go
Raw Normal View History

2024-08-28 12:36:14 +01:00
package main
import (
"bufio"
"errors"
2024-08-28 12:36:14 +01:00
"fmt"
"maps"
"math/rand/v2"
2024-08-28 12:36:14 +01:00
"os"
"slices"
"strings"
"time"
2024-08-28 12:36:14 +01:00
"codeflow.dananglin.me.uk/apollo/pokedex/internal/api/pokeapi"
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient"
2024-08-28 12:36:14 +01:00
)
type State struct {
Previous *string
Next *string
}
type command struct {
name string
description string
callback callbackFunc
}
type callbackFunc func(args []string) error
type pokedex map[string]pokeapi.Pokemon
var dexter = make(pokedex)
2024-08-28 12:36:14 +01:00
func main() {
run()
}
func run() {
client := pokeclient.NewClient(
5*time.Minute,
10*time.Second,
)
var state State
commandMap := map[string]command{
"exit": {
name: "exit",
description: "Exit the Pokedex",
callback: exitFunc,
},
"help": {
name: "help",
description: "Displays a help message",
callback: nil,
},
"map": {
name: "map",
description: "Displays the next 20 locations in the Pokemon world",
callback: mapFunc(client, &state),
},
"mapb": {
name: "map back",
description: "Displays the previous 20 locations in the Pokemon world",
callback: mapBFunc(client, &state),
},
"explore": {
name: "explore",
description: "Lists all the Pokemon in a given area",
callback: exploreFunc(client),
},
"catch": {
name: "catch",
description: "Catches a Pokemon and adds them to your Pokedex",
callback: catchFunc(client),
},
"inspect": {
name: "inspect",
description: "Inspects a Pokemon from your Pokedex",
callback: inspectFunc(),
},
"pokedex": {
name: "pokedex",
description: "Lists the names of all the Pokemon in your Pokedex",
callback: pokedexFunc(),
},
}
summaries := summaryMap(commandMap)
commandMap["help"] = command{
name: "help",
description: "Displays a help message",
callback: helpFunc(summaries),
}
fmt.Printf("\nWelcome to the Pokedex!\n")
fmt.Print("\npokedex > ")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input := scanner.Text()
command, args := parseArgs(input)
cmd, ok := commandMap[command]
if !ok {
fmt.Println("ERROR: Unrecognised command.")
fmt.Print("\npokedex > ")
continue
}
if cmd.callback == nil {
fmt.Println("ERROR: This command is defined but does not have a callback function.")
fmt.Print("\npokedex > ")
continue
}
if err := commandMap[command].callback(args); err != nil {
fmt.Printf("ERROR: %v.\n", err)
}
fmt.Print("pokedex > ")
}
}
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, state *State) callbackFunc {
return func(_ []string) error {
url := state.Next
if url == nil {
url = new(string)
*url = pokeclient.LocationAreaPath
}
return printResourceList(client, *url, state)
}
}
func mapBFunc(client *pokeclient.Client, state *State) callbackFunc {
return func(_ []string) error {
url := state.Previous
if url == nil {
return fmt.Errorf("no previous locations available")
}
return printResourceList(client, *url, state)
}
}
func exploreFunc(client *pokeclient.Client) callbackFunc {
return func(args []string) error {
if args == nil {
return errors.New("the location has not been specified")
}
if len(args) != 1 {
return fmt.Errorf(
"unexpected number of locations: want 1; got %d",
len(args),
)
}
location := args[0]
fmt.Println("Exploring", location)
locationArea, err := client.GetLocationArea(location)
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 catchFunc(client *pokeclient.Client) 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]
fmt.Printf("Throwing a Pokeball at %s...\n", pokemonName)
pokemon, err := client.GetPokemon(pokemonName)
if err != nil {
return fmt.Errorf(
"unable to get the information on %s: %w",
pokemonName,
err,
)
}
chance := 50
if caught := catchPokemon(chance); caught {
dexter[pokemonName] = pokemon
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() 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 := dexter[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() callbackFunc {
return func(_ []string) error {
if len(dexter) == 0 {
fmt.Println("You have no Pokemon in your Pokedex")
return nil
}
fmt.Println("Your Pokedex:")
for name := range maps.All(dexter) {
fmt.Println(" -", name)
}
return nil
}
}
func printResourceList(client *pokeclient.Client, url string, state *State) error {
list, err := client.GetNamedAPIResourceList(url)
if err != nil {
return fmt.Errorf("unable to get the list of resources: %w", err)
}
state.Next = list.Next
state.Previous = list.Previous
for _, location := range slices.All(list.Results) {
fmt.Println(location.Name)
}
2024-08-28 12:36:14 +01:00
return nil
}
func summaryMap(commandMap map[string]command) map[string]string {
summaries := make(map[string]string)
for key, value := range maps.All(commandMap) {
summaries[key] = value.description
}
return summaries
}
func parseArgs(input string) (string, []string) {
split := strings.Split(input, " ")
if len(split) == 0 {
return "", nil
}
if len(split) == 1 {
return split[0], nil
}
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
}