generated from templates/go-generic
checkpoint: add explore command
Add the explore command to list the pokemon in a given location area.
This commit is contained in:
parent
b6088bfd88
commit
578c781907
3 changed files with 180 additions and 65 deletions
|
@ -1,50 +1,50 @@
|
||||||
package pokeapi
|
package pokeapi
|
||||||
|
|
||||||
// LocationArea is a section of areas such as floors in a building or a cave.
|
// LocationArea is a section of areas such as floors in a building or a cave.
|
||||||
//type LocationArea struct {
|
type LocationArea struct {
|
||||||
// ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
// Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// GameIndex int `json:"game_index"`
|
GameIndex int `json:"game_index"`
|
||||||
// EncounterMethodRates []EncounterMethodRate `json:"encounter_method_rates"`
|
EncounterMethodRates []EncounterMethodRate `json:"encounter_method_rates"`
|
||||||
// Location NamedAPIResource `json:"location"`
|
Location NamedAPIResource `json:"location"`
|
||||||
// Names []Name `json:"names"`
|
Names []Name `json:"names"`
|
||||||
// PokemonEncounters []PokemonEncounter `json:"pokemon_encounters"`
|
PokemonEncounters []PokemonEncounter `json:"pokemon_encounters"`
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//type EncounterMethodRate struct {
|
type EncounterMethodRate struct {
|
||||||
// EncounterMethod NamedAPIResource `json:"encounter_method"`
|
EncounterMethod NamedAPIResource `json:"encounter_method"`
|
||||||
// VersionDetails EncounterVersionDetails `json:"version_details"`
|
VersionDetails []EncounterVersionDetails `json:"version_details"`
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//type EncounterVersionDetails struct {
|
type EncounterVersionDetails struct {
|
||||||
// Rate int `json:"rate"`
|
Rate int `json:"rate"`
|
||||||
// Version NamedAPIResource `json:"version"`
|
Version NamedAPIResource `json:"version"`
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//type Name struct {
|
type Name struct {
|
||||||
// Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// Language NamedAPIResource `json:"language"`
|
Language NamedAPIResource `json:"language"`
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//// PokemonEncounter is details of a possible Pokemon encounter.
|
// PokemonEncounter is details of a possible Pokemon encounter.
|
||||||
//type PokemonEncounter struct {
|
type PokemonEncounter struct {
|
||||||
// Pokemon NamedAPIResource `json:"pokemon"`
|
Pokemon NamedAPIResource `json:"pokemon"`
|
||||||
// VersionDetails []VersionEncounterDetails `json:"version_details"`
|
VersionDetails []VersionEncounterDetails `json:"version_details"`
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//type VersionEncounterDetails struct {
|
type VersionEncounterDetails struct {
|
||||||
// Version NamedAPIResource `json:"version"`
|
Version NamedAPIResource `json:"version"`
|
||||||
// MaxChance int `json:"max_chance"`
|
MaxChance int `json:"max_chance"`
|
||||||
// EncounterDetails []Encounter `json:"encounter_details"`
|
EncounterDetails []Encounter `json:"encounter_details"`
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//type Encounter struct {
|
type Encounter struct {
|
||||||
// MinLevel int `json:"min_level"`
|
MinLevel int `json:"min_level"`
|
||||||
// MaxLevel int `json:"max_level"`
|
MaxLevel int `json:"max_level"`
|
||||||
// ConditionValues []NamedAPIResource `json:"condition_values"`
|
ConditionValues []NamedAPIResource `json:"condition_values"`
|
||||||
// Chance int `json:"chance"`
|
Chance int `json:"chance"`
|
||||||
// Method NamedAPIResource `json:"method"`
|
Method NamedAPIResource `json:"method"`
|
||||||
//}
|
}
|
||||||
|
|
||||||
type NamedAPIResourceList struct {
|
type NamedAPIResourceList struct {
|
||||||
Count int `json:"count"`
|
Count int `json:"count"`
|
||||||
|
|
|
@ -12,6 +12,12 @@ import (
|
||||||
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokecache"
|
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokecache"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseURL string = "https://pokeapi.co/api/v2"
|
||||||
|
|
||||||
|
LocationAreaEndpoint = baseURL + "/location-area"
|
||||||
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
httpClient http.Client
|
httpClient http.Client
|
||||||
cache *pokecache.Cache
|
cache *pokecache.Cache
|
||||||
|
@ -38,7 +44,7 @@ func (c *Client) GetNamedAPIResourceList(url string) (pokeapi.NamedAPIResourceLi
|
||||||
fmt.Println("Using data from cache.")
|
fmt.Println("Using data from cache.")
|
||||||
|
|
||||||
if err := decodeJSON(dataFromCache, &list); err != nil {
|
if err := decodeJSON(dataFromCache, &list); err != nil {
|
||||||
return pokeapi.NamedAPIResourceList{}, fmt.Errorf("unable to decode the data from cache: %w", err)
|
return pokeapi.NamedAPIResourceList{}, fmt.Errorf("unable to decode the data from the cache: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return list, nil
|
return list, nil
|
||||||
|
@ -84,6 +90,62 @@ func (c *Client) GetNamedAPIResourceList(url string) (pokeapi.NamedAPIResourceLi
|
||||||
return list, nil
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetLocationArea(location string) (pokeapi.LocationArea, error) {
|
||||||
|
var locationArea pokeapi.LocationArea
|
||||||
|
|
||||||
|
url := LocationAreaEndpoint + "/" + location + "/"
|
||||||
|
|
||||||
|
dataFromCache, exists := c.cache.Get(url)
|
||||||
|
if exists {
|
||||||
|
fmt.Println("Using data from cache.")
|
||||||
|
|
||||||
|
if err := decodeJSON(dataFromCache, &locationArea); err != nil {
|
||||||
|
return pokeapi.LocationArea{}, fmt.Errorf("unable to decode the data from the cache: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return locationArea, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return pokeapi.LocationArea{}, fmt.Errorf("error creating the HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return pokeapi.LocationArea{}, fmt.Errorf("error getting the response from the server: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
return pokeapi.LocationArea{}, fmt.Errorf(
|
||||||
|
"received a bad status from %s: (%d) %s",
|
||||||
|
url,
|
||||||
|
resp.StatusCode,
|
||||||
|
resp.Status,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return pokeapi.LocationArea{}, fmt.Errorf(
|
||||||
|
"unable to read the response from the server: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decodeJSON(body, &locationArea); err != nil {
|
||||||
|
return pokeapi.LocationArea{}, fmt.Errorf("unable to decode the data from the server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cache.Add(url, body)
|
||||||
|
|
||||||
|
return locationArea, nil
|
||||||
|
}
|
||||||
|
|
||||||
func decodeJSON(data []byte, value any) error {
|
func decodeJSON(data []byte, value any) error {
|
||||||
if err := json.Unmarshal(data, value); err != nil {
|
if err := json.Unmarshal(data, value); err != nil {
|
||||||
return fmt.Errorf("unable to decode the JSON data: %w", err)
|
return fmt.Errorf("unable to decode the JSON data: %w", err)
|
||||||
|
|
93
main.go
93
main.go
|
@ -2,20 +2,17 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient"
|
"codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
baseURL string = "https://pokeapi.co/api/v2"
|
|
||||||
locationAreaEndpoint string = "/location-area"
|
|
||||||
)
|
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
Previous *string
|
Previous *string
|
||||||
Next *string
|
Next *string
|
||||||
|
@ -29,7 +26,7 @@ type command struct {
|
||||||
callback callbackFunc
|
callback callbackFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type callbackFunc func() error
|
type callbackFunc func(args []string) error
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
run()
|
run()
|
||||||
|
@ -45,7 +42,7 @@ func run() {
|
||||||
"exit": {
|
"exit": {
|
||||||
name: "exit",
|
name: "exit",
|
||||||
description: "Exit the Pokedex",
|
description: "Exit the Pokedex",
|
||||||
callback: commandExit,
|
callback: exitFunc,
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
name: "help",
|
name: "help",
|
||||||
|
@ -55,12 +52,17 @@ func run() {
|
||||||
"map": {
|
"map": {
|
||||||
name: "map",
|
name: "map",
|
||||||
description: "Displays the next 20 locations in the Pokemon world",
|
description: "Displays the next 20 locations in the Pokemon world",
|
||||||
callback: commandMap(client),
|
callback: mapFunc(client),
|
||||||
},
|
},
|
||||||
"mapb": {
|
"mapb": {
|
||||||
name: "map back",
|
name: "map back",
|
||||||
description: "Displays the previous 20 locations in the Pokemon world",
|
description: "Displays the previous 20 locations in the Pokemon world",
|
||||||
callback: commandMapB(client),
|
callback: mapBFunc(client),
|
||||||
|
},
|
||||||
|
"explore": {
|
||||||
|
name: "explore",
|
||||||
|
description: "Lists all the pokemon in a given area",
|
||||||
|
callback: exploreFunc(client),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +71,7 @@ func run() {
|
||||||
commandMap["help"] = command{
|
commandMap["help"] = command{
|
||||||
name: "help",
|
name: "help",
|
||||||
description: "Displays a help message",
|
description: "Displays a help message",
|
||||||
callback: commandHelp(summaries),
|
callback: helpFunc(summaries),
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\nWelcome to the Pokedex!\n")
|
fmt.Printf("\nWelcome to the Pokedex!\n")
|
||||||
|
@ -78,7 +80,9 @@ func run() {
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
command := scanner.Text()
|
input := scanner.Text()
|
||||||
|
|
||||||
|
command, args := parseArgs(input)
|
||||||
|
|
||||||
cmd, ok := commandMap[command]
|
cmd, ok := commandMap[command]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -97,7 +101,7 @@ func run() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commandMap[command].callback(); err != nil {
|
if err := commandMap[command].callback(args); err != nil {
|
||||||
fmt.Printf("ERROR: %v.\n", err)
|
fmt.Printf("ERROR: %v.\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,8 +109,8 @@ func run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandHelp(summaries map[string]string) callbackFunc {
|
func helpFunc(summaries map[string]string) callbackFunc {
|
||||||
return func() error {
|
return func(_ []string) error {
|
||||||
keys := []string{}
|
keys := []string{}
|
||||||
|
|
||||||
for key := range maps.All(summaries) {
|
for key := range maps.All(summaries) {
|
||||||
|
@ -127,26 +131,26 @@ func commandHelp(summaries map[string]string) callbackFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandExit() error {
|
func exitFunc(_ []string) error {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandMap(client *pokeclient.Client) callbackFunc {
|
func mapFunc(client *pokeclient.Client) callbackFunc {
|
||||||
return func() error {
|
return func(_ []string) error {
|
||||||
url := state.Next
|
url := state.Next
|
||||||
if url == nil {
|
if url == nil {
|
||||||
url = new(string)
|
url = new(string)
|
||||||
*url = baseURL + locationAreaEndpoint
|
*url = pokeclient.LocationAreaEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
return printResourceList(client, *url)
|
return printResourceList(client, *url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandMapB(client *pokeclient.Client) callbackFunc {
|
func mapBFunc(client *pokeclient.Client) callbackFunc {
|
||||||
return func() error {
|
return func(_ []string) error {
|
||||||
url := state.Previous
|
url := state.Previous
|
||||||
if url == nil {
|
if url == nil {
|
||||||
return fmt.Errorf("no previous locations available")
|
return fmt.Errorf("no previous locations available")
|
||||||
|
@ -156,6 +160,41 @@ func commandMapB(client *pokeclient.Client) callbackFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 printResourceList(client *pokeclient.Client, url string) error {
|
func printResourceList(client *pokeclient.Client, url string) error {
|
||||||
list, err := client.GetNamedAPIResourceList(url)
|
list, err := client.GetNamedAPIResourceList(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -181,3 +220,17 @@ func summaryMap(commandMap map[string]command) map[string]string {
|
||||||
|
|
||||||
return summaries
|
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:]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue