Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update move.go, case consistency #3

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.log
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# learn-pub-sub-starter
# learn-pub-sub-starter (Peril)

This is the starter code used in Boot.dev's [Learn Pub/Sub](https://learn.boot.dev/learn-pub-sub) course.
7 changes: 7 additions & 0 deletions cmd/client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "fmt"

func main() {
fmt.Println("Starting Peril client...")
}
7 changes: 7 additions & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "fmt"

func main() {
fmt.Println("Starting Peril server...")
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/bootdotdev/learn-pub-sub-starter

go 1.22.1
Empty file added go.sum
Empty file.
52 changes: 52 additions & 0 deletions internal/gamelogic/gamedata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package gamelogic

type Player struct {
Username string
Units map[int]Unit
}

type UnitRank string

const (
RankInfantry = "infantry"
RankCavalry = "cavalry"
RankArtillery = "artillery"
)

type Unit struct {
ID int
Rank UnitRank
Location Location
}

type ArmyMove struct {
Player Player
Units []Unit
ToLocation Location
}

type RecognitionOfWar struct {
Attacker Player
Defender Player
}

type Location string

func getAllRanks() map[UnitRank]struct{} {
return map[UnitRank]struct{}{
RankInfantry: {},
RankCavalry: {},
RankArtillery: {},
}
}

func getAllLocations() map[Location]struct{} {
return map[Location]struct{}{
"americas": {},
"europe": {},
"africa": {},
"asia": {},
"australia": {},
"antarctica": {},
}
}
92 changes: 92 additions & 0 deletions internal/gamelogic/gamelogic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package gamelogic

import (
"bufio"
"errors"
"fmt"
"math/rand"
"os"
"strings"
)

func PrintClientHelp() {
fmt.Println("Possible commands:")
fmt.Println("* move <location> <unitID> <unitID> <unitID>...")
fmt.Println(" example:")
fmt.Println(" move asia 1")
fmt.Println("* spawn <location> <rank>")
fmt.Println(" example:")
fmt.Println(" spawn europe infantry")
fmt.Println("* status")
fmt.Println("* spam <n>")
fmt.Println(" example:")
fmt.Println(" spam 5")
fmt.Println("* quit")
fmt.Println("* help")
}

func ClientWelcome() (string, error) {
fmt.Println("Welcome to the Peril client!")
fmt.Println("Please enter your username:")
words := GetInput()
if len(words) == 0 {
return "", errors.New("you must enter a username. goodbye")
}
username := words[0]
fmt.Printf("Welcome, %s!\n", username)
PrintClientHelp()
return username, nil
}

func PrintServerHelp() {
fmt.Println("Possible commands:")
fmt.Println("* pause")
fmt.Println("* resume")
fmt.Println("* quit")
fmt.Println("* help")
}

func GetInput() []string {
fmt.Print("> ")
scanner := bufio.NewScanner(os.Stdin)
scanned := scanner.Scan()
if !scanned {
return nil
}
line := scanner.Text()
line = strings.TrimSpace(line)
return strings.Fields(line)
}

func GetMaliciousLog() string {
possibleLogs := []string{
"Never interrupt your enemy when he is making a mistake.",
"The hardest thing of all for a soldier is to retreat.",
"A soldier will fight long and hard for a bit of colored ribbon.",
"It is well that war is so terrible, otherwise we should grow too fond of it.",
"The art of war is simple enough. Find out where your enemy is. Get at him as soon as you can. Strike him as hard as you can, and keep moving on.",
"All warfare is based on deception.",
}
randomIndex := rand.Intn(len(possibleLogs))
msg := possibleLogs[randomIndex]
return msg
}

func PrintQuit() {
fmt.Println("I hate this game! (╯°□°)╯︵ ┻━┻")
}

func (gs *GameState) CommandStatus() {
if gs.isPaused() {
fmt.Println("The game is paused.")
return
} else {
fmt.Println("The game is not paused.")
}

p := gs.GetPlayerSnap()
fmt.Printf("You are %s, and you have %d units.\n", p.Username, len(p.Units))
for _, unit := range p.Units {
fmt.Printf("* %v: %v, %v\n", unit.ID, unit.Location, unit.Rank)
}
}
96 changes: 96 additions & 0 deletions internal/gamelogic/gamestate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package gamelogic

import (
"sync"
)

type GameState struct {
Player Player
Paused bool
mu *sync.RWMutex
}

func NewGameState(username string) *GameState {
return &GameState{
Player: Player{
Username: username,
Units: map[int]Unit{},
},
Paused: false,
mu: &sync.RWMutex{},
}
}

func (gs *GameState) resumeGame() {
gs.mu.Lock()
defer gs.mu.Unlock()
gs.Paused = false
}

func (gs *GameState) pauseGame() {
gs.mu.Lock()
defer gs.mu.Unlock()
gs.Paused = true
}

func (gs *GameState) isPaused() bool {
gs.mu.RLock()
defer gs.mu.RUnlock()
return gs.Paused
}

func (gs *GameState) addUnit(u Unit) {
gs.mu.Lock()
defer gs.mu.Unlock()
gs.Player.Units[u.ID] = u
}

func (gs *GameState) removeUnitsInLocation(loc Location) {
gs.mu.Lock()
defer gs.mu.Unlock()
for k, v := range gs.Player.Units {
if v.Location == loc {
delete(gs.Player.Units, k)
}
}
}

func (gs *GameState) UpdateUnit(u Unit) {
gs.mu.Lock()
defer gs.mu.Unlock()
gs.Player.Units[u.ID] = u
}

func (gs *GameState) GetUsername() string {
return gs.Player.Username
}

func (gs *GameState) getUnitsSnap() []Unit {
gs.mu.RLock()
defer gs.mu.RUnlock()
Units := []Unit{}
for _, v := range gs.Player.Units {
Units = append(Units, v)
}
return Units
}

func (gs *GameState) GetUnit(id int) (Unit, bool) {
gs.mu.RLock()
defer gs.mu.RUnlock()
u, ok := gs.Player.Units[id]
return u, ok
}

func (gs *GameState) GetPlayerSnap() Player {
gs.mu.RLock()
defer gs.mu.RUnlock()
Units := map[int]Unit{}
for k, v := range gs.Player.Units {
Units[k] = v
}
return Player{
Username: gs.Player.Username,
Units: Units,
}
}
32 changes: 32 additions & 0 deletions internal/gamelogic/logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gamelogic

import (
"fmt"
"log"
"os"
"time"

"github.com/bootdotdev/learn-pub-sub-starter/internal/routing"
)

const logsFile = "game.log"

const writeToDiskSleep = 1 * time.Second

func WriteLog(gamelog routing.GameLog) error {
log.Printf("received game log...")
time.Sleep(writeToDiskSleep)

f, err := os.OpenFile(logsFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("could not open logs file: %v", err)
}
defer f.Close()

str := fmt.Sprintf("%v %v: %v\n", gamelog.CurrentTime.Format(time.RFC3339), gamelog.Username, gamelog.Message)
_, err = f.WriteString(str)
if err != nil {
return fmt.Errorf("could not write to logs file: %v", err)
}
return nil
}
90 changes: 90 additions & 0 deletions internal/gamelogic/move.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package gamelogic

import (
"errors"
"fmt"
"strconv"
)

type MoveOutcome int

const (
MoveOutcomeSamePlayer MoveOutcome = iota
MoveOutcomeSafe
MoveOutcomeMakeWar
)

func (gs *GameState) HandleMove(move ArmyMove) MoveOutcome {
defer fmt.Println("------------------------")
player := gs.GetPlayerSnap()

fmt.Println()
fmt.Println("==== Move Detected ====")
fmt.Printf("%s is moving %v unit(s) to %s\n", move.Player.Username, len(move.Units), move.ToLocation)
for _, unit := range move.Units {
fmt.Printf("* %v\n", unit.Rank)
}

if player.Username == move.Player.Username {
return MoveOutcomeSamePlayer
}

overlappingLocation := getOverlappingLocation(player, move.Player)
if overlappingLocation != "" {
fmt.Printf("You have units in %s! You are at war with %s!\n", overlappingLocation, move.Player.Username)
return MoveOutcomeMakeWar
}
fmt.Printf("You are safe from %s's units.\n", move.Player.Username)
return MoveOutcomeSafe
}

func getOverlappingLocation(p1 Player, p2 Player) Location {
for _, u1 := range p1.Units {
for _, u2 := range p2.Units {
if u1.Location == u2.Location {
return u1.Location
}
}
}
return ""
}

func (gs *GameState) CommandMove(words []string) (ArmyMove, error) {
if gs.isPaused() {
return ArmyMove{}, errors.New("the game is paused, you can not move units")
}
if len(words) < 3 {
return ArmyMove{}, errors.New("usage: move <location> <unitID> <unitID> <unitID> etc")
}
newLocation := Location(words[1])
locations := getAllLocations()
if _, ok := locations[newLocation]; !ok {
return ArmyMove{}, fmt.Errorf("error: %s is not a valid location", newLocation)
}
unitIDs := []int{}
for _, word := range words[2:] {
id := word
unitID, err := strconv.Atoi(id)
if err != nil {
return ArmyMove{}, fmt.Errorf("error: %s is not a valid unit ID", id)
}
unitIDs = append(unitIDs, unitID)
}

for _, unitID := range unitIDs {
unit, ok := gs.GetUnit(unitID)
if !ok {
return ArmyMove{}, fmt.Errorf("error: unit with ID %v not found", unitID)
}
unit.Location = newLocation
gs.UpdateUnit(unit)
}

mv := ArmyMove{
ToLocation: newLocation,
Units: gs.getUnitsSnap(),
Player: gs.GetPlayerSnap(),
}
fmt.Printf("Moved %v units to %s\n", len(mv.Units), mv.ToLocation)
return mv, nil
}
Loading