feat: add initial project code

Add the initial code for the PokeDex project. For now the code can list
the next and previous 20 location areas within the Pokemon world.
This commit is contained in:
Dan Anglin 2024-09-17 18:43:21 +01:00
parent c29412e612
commit 4152a9d14f
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
9 changed files with 242 additions and 24 deletions

View file

@ -22,5 +22,5 @@ jobs:
with:
target: test
env:
PROJECT_TEST_VERBOSE: "1"
PROJECT_TEST_COVER: "1"
POKEDEX_TEST_VERBOSE: "1"
POKEDEX_TEST_COVER: "1"

3
.gitignore vendored
View file

@ -1,2 +1 @@
/__build/*
!__build/.gitkeep
pokedex

View file

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module codeflow.dananglin.me.uk/apollo/pokedex
go 1.23.0

5
magefiles/go.mod Normal file
View file

@ -0,0 +1,5 @@
module codeflow.dananglin.me.uk/apollo/pokedex/magefiles
go 1.23.0
require github.com/magefile/mage v1.15.0

2
magefiles/go.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=

View file

@ -14,23 +14,23 @@ import (
)
const (
app = "binary"
app = "pokedex"
defaultInstallPrefix = "/usr/local"
envInstallPrefix = "PROJECT_INSTALL_PREFIX"
envTestVerbose = "PROJECT_TEST_VERBOSE"
envTestCover = "PROJECT_TEST_COVER"
envBuildRebuildAll = "PROJECT_BUILD_REBUILD_ALL"
envBuildVerbose = "PROJECT_BUILD_VERBOSE"
envInstallPrefix = "POKEDEX_INSTALL_PREFIX"
envTestVerbose = "POKEDEX_TEST_VERBOSE"
envTestCover = "POKEDEX_TEST_COVER"
envBuildRebuildAll = "POKEDEX_BUILD_REBUILD_ALL"
envBuildVerbose = "POKEDEX_BUILD_VERBOSE"
)
var (
Default = Build
binary = "./__build/" + app
binary = app
)
// Test run the go tests.
// To enable verbose mode set PROJECT_TEST_VERBOSE=1.
// To enable coverage mode set PROJECT_TEST_COVER=1.
// To enable verbose mode set POKEDEX_TEST_VERBOSE=1.
// To enable coverage mode set POKEDEX_TEST_COVER=1.
func Test() error {
goTest := sh.RunCmd("go", "test")
@ -53,8 +53,8 @@ func Lint() error {
}
// Build build the executable.
// To rebuild packages that are already up-to-date set PROJECT_BUILD_REBUILD_ALL=1
// To enable verbose mode set PROJECT_BUILD_VERBOSE=1
// To rebuild packages that are already up-to-date set POKEDEX_BUILD_REBUILD_ALL=1
// To enable verbose mode set POKEDEX_BUILD_VERBOSE=1
func Build() error {
main := "main.go"
flags := ldflags()

166
main.go
View file

@ -1,24 +1,174 @@
package main
import (
"bufio"
"context"
"encoding/json"
"fmt"
"maps"
"net/http"
"os"
"slices"
"time"
)
var (
binaryVersion string
buildTime string
goVersion string
gitCommit string
const (
baseURL string = "https://pokeapi.co/api/v2"
locationAreaEndpoint string = "/location-area"
)
type State struct {
Previous *string
Next *string
}
var state State
type command struct {
name string
description string
callback func() error
}
func main() {
if err := run(); err != nil {
run()
}
func run() {
fmt.Print("pokedex > ")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
command := scanner.Text()
cmdMap := getCommandMap()
if _, ok := cmdMap[command]; !ok {
fmt.Println("ERROR: Unrecognised command.")
fmt.Print("\npokedex > ")
continue
}
if err := cmdMap[command].callback(); err != nil {
fmt.Printf("ERROR: %v.\n", err)
os.Exit(1)
}
fmt.Print("pokedex > ")
}
}
func run() error {
func getCommandMap() map[string]command {
return map[string]command{
"exit": {
name: "exit",
description: "Exit the Pokedex",
callback: commandExit,
},
"help": {
name: "help",
description: "Displays a help message",
callback: commandHelp,
},
"map": {
name: "map",
description: "Displays the next 20 locations in the Pokemon world",
callback: commandMap,
},
"mapb": {
name: "map back",
description: "Displays the previous 20 locations in the Pokemon world",
callback: commandMapB,
},
}
}
func commandHelp() error {
cmdMap := getCommandMap()
keys := []string{}
for key := range maps.All(cmdMap) {
keys = append(keys, key)
}
slices.Sort(keys)
fmt.Printf("\nWelcome to the Pokedex!\nUsage:\n")
for _, key := range slices.All(keys) {
fmt.Printf("\n%s: %s", key, cmdMap[key].description)
}
fmt.Println("\n")
return nil
}
func commandExit() error {
os.Exit(0)
return nil
}
func commandMap() error {
url := state.Next
if url == nil {
url = new(string)
*url = baseURL + locationAreaEndpoint
}
return printMap(*url)
}
func commandMapB() error {
url := state.Previous
if url == nil {
return fmt.Errorf("no previous locations available")
}
return printMap(*url)
}
func printMap(url string) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("error creating the HTTP request: %w", err)
}
client := http.Client{}
resp, err := client.Do(request)
if err != nil {
return fmt.Errorf("error getting the response from the server: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf(
"received a bad status from %s: (%d) %s",
url,
resp.StatusCode,
resp.Status,
)
}
var result NamedAPIResourceList
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("unable to decode the JSON response: %w", err)
}
state.Next = result.Next
state.Previous = result.Previous
for _, location := range slices.All(result.Results) {
fmt.Println(location.Name)
}
return nil
}

59
types.go Normal file
View file

@ -0,0 +1,59 @@
package main
// LocationArea is a section of areas such as floors in a building or a cave.
type LocationArea struct {
ID int `json:"id"`
Name string `json:"name"`
GameIndex int `json:"game_index"`
EncounterMethodRates []EncounterMethodRate `json:"encounter_method_rates"`
Location NamedAPIResource `json:"location"`
Names []Name `json:"names"`
PokemonEncounters []PokemonEncounter `json:"pokemon_encounters"`
}
type EncounterMethodRate struct {
EncounterMethod NamedAPIResource `json:"encounter_method"`
VersionDetails EncounterVersionDetails `json:"version_details"`
}
type EncounterVersionDetails struct {
Rate int `json:"rate"`
Version NamedAPIResource `json:"version"`
}
type Name struct {
Name string `json:"name"`
Language NamedAPIResource `json:"language"`
}
// PokemonEncounter is details of a possible Pokemon encounter.
type PokemonEncounter struct {
Pokemon NamedAPIResource `json:"pokemon"`
VersionDetails []VersionEncounterDetails `json:"version_details"`
}
type VersionEncounterDetails struct {
Version NamedAPIResource `json:"version"`
MaxChance int `json:"max_chance"`
EncounterDetails []Encounter `json:"encounter_details"`
}
type Encounter struct {
MinLevel int `json:"min_level"`
MaxLevel int `json:"max_level"`
ConditionValues []NamedAPIResource `json:"condition_values"`
Chance int `json:"chance"`
Method NamedAPIResource `json:"method"`
}
type NamedAPIResourceList struct {
Count int `json:"count"`
Next *string `json:"next"`
Previous *string `json:"previous"`
Results []NamedAPIResource `json:"results"`
}
type NamedAPIResource struct {
Name string `json:"name"`
URL string `json:"url"`
}