Skip to content

Commit 4885f7a

Browse files
committed
starter repo
1 parent d4a1dc9 commit 4885f7a

File tree

18 files changed

+624
-1
lines changed

18 files changed

+624
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.log

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
# learn-pub-sub-starter
1+
# learn-pub-sub-starter (Peril)
2+
3+
This is the starter code used in Boot.dev's [Learn Pub/Sub](https://learn.boot.dev/learn-pub-sub) course.

cmd/client/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func main() {
6+
fmt.Println("Starting Peril client...")
7+
}

cmd/server/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func main() {
6+
fmt.Println("Starting Peril server...")
7+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/bootdotdev/learn-pub-sub-starter
2+
3+
go 1.22.1

go.sum

Whitespace-only changes.

internal/gamelogic/gamedata.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package gamelogic
2+
3+
type Player struct {
4+
Username string
5+
Units map[int]Unit
6+
}
7+
8+
type UnitRank string
9+
10+
const (
11+
RankInfantry = "infantry"
12+
RankCavalry = "cavalry"
13+
RankArtillery = "artillery"
14+
)
15+
16+
type Unit struct {
17+
ID int
18+
Rank UnitRank
19+
Location Location
20+
}
21+
22+
type ArmyMove struct {
23+
Player Player
24+
Units []Unit
25+
ToLocation Location
26+
}
27+
28+
type RecognitionOfWar struct {
29+
Attacker Player
30+
Defender Player
31+
}
32+
33+
type Location string
34+
35+
func getAllRanks() map[UnitRank]struct{} {
36+
return map[UnitRank]struct{}{
37+
RankInfantry: {},
38+
RankCavalry: {},
39+
RankArtillery: {},
40+
}
41+
}
42+
43+
func getAllLocations() map[Location]struct{} {
44+
return map[Location]struct{}{
45+
"americas": {},
46+
"europe": {},
47+
"africa": {},
48+
"asia": {},
49+
"australia": {},
50+
"antarctica": {},
51+
}
52+
}

internal/gamelogic/gamelogic.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package gamelogic
2+
3+
import (
4+
"bufio"
5+
"errors"
6+
"fmt"
7+
"math/rand"
8+
"os"
9+
"strings"
10+
)
11+
12+
func PrintClientHelp() {
13+
fmt.Println("Possible commands:")
14+
fmt.Println("* move <location> <unitID> <unitID> <unitID>...")
15+
fmt.Println(" example:")
16+
fmt.Println(" move asia 1")
17+
fmt.Println("* spawn <location> <rank>")
18+
fmt.Println(" example:")
19+
fmt.Println(" spawn europe infantry")
20+
fmt.Println("* status")
21+
fmt.Println("* spam <n>")
22+
fmt.Println(" example:")
23+
fmt.Println(" spam 5")
24+
fmt.Println("* quit")
25+
fmt.Println("* help")
26+
}
27+
28+
func ClientWelcome() (string, error) {
29+
fmt.Println("Welcome to the Peril client!")
30+
fmt.Println("Please enter your username:")
31+
words := GetInput()
32+
if len(words) == 0 {
33+
return "", errors.New("you must enter a username. goodbye")
34+
}
35+
username := words[0]
36+
fmt.Printf("Welcome, %s!\n", username)
37+
PrintClientHelp()
38+
return username, nil
39+
}
40+
41+
func PrintServerHelp() {
42+
fmt.Println("Possible commands:")
43+
fmt.Println("* pause")
44+
fmt.Println("* resume")
45+
fmt.Println("* quit")
46+
fmt.Println("* help")
47+
}
48+
49+
func GetInput() []string {
50+
fmt.Print("> ")
51+
scanner := bufio.NewScanner(os.Stdin)
52+
scanned := scanner.Scan()
53+
if !scanned {
54+
return nil
55+
}
56+
line := scanner.Text()
57+
line = strings.TrimSpace(line)
58+
return strings.Fields(line)
59+
}
60+
61+
func GetMaliciousLog() string {
62+
possibleLogs := []string{
63+
"Never interrupt your enemy when he is making a mistake.",
64+
"The hardest thing of all for a soldier is to retreat.",
65+
"A soldier will fight long and hard for a bit of colored ribbon.",
66+
"It is well that war is so terrible, otherwise we should grow too fond of it.",
67+
"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.",
68+
"All warfare is based on deception.",
69+
}
70+
randomIndex := rand.Intn(len(possibleLogs))
71+
msg := possibleLogs[randomIndex]
72+
return msg
73+
}
74+
75+
func PrintQuit() {
76+
fmt.Println("I hate this game! (╯°□°)╯︵ ┻━┻")
77+
}
78+
79+
func (gs *GameState) CommandStatus() {
80+
if gs.isPaused() {
81+
fmt.Println("The game is paused.")
82+
return
83+
} else {
84+
fmt.Println("The game is not paused.")
85+
}
86+
87+
p := gs.GetPlayerSnap()
88+
fmt.Printf("You are %s, and you have %d units.\n", p.Username, len(p.Units))
89+
for _, unit := range p.Units {
90+
fmt.Printf("* %v: %v, %v\n", unit.ID, unit.Location, unit.Rank)
91+
}
92+
}

internal/gamelogic/gamestate.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package gamelogic
2+
3+
import (
4+
"sync"
5+
)
6+
7+
type GameState struct {
8+
Player Player
9+
Paused bool
10+
mu *sync.RWMutex
11+
}
12+
13+
func NewGameState(username string) *GameState {
14+
return &GameState{
15+
Player: Player{
16+
Username: username,
17+
Units: map[int]Unit{},
18+
},
19+
Paused: false,
20+
mu: &sync.RWMutex{},
21+
}
22+
}
23+
24+
func (gs *GameState) resumeGame() {
25+
gs.mu.Lock()
26+
defer gs.mu.Unlock()
27+
gs.Paused = false
28+
}
29+
30+
func (gs *GameState) pauseGame() {
31+
gs.mu.Lock()
32+
defer gs.mu.Unlock()
33+
gs.Paused = true
34+
}
35+
36+
func (gs *GameState) isPaused() bool {
37+
gs.mu.RLock()
38+
defer gs.mu.RUnlock()
39+
return gs.Paused
40+
}
41+
42+
func (gs *GameState) addUnit(u Unit) {
43+
gs.mu.Lock()
44+
defer gs.mu.Unlock()
45+
gs.Player.Units[u.ID] = u
46+
}
47+
48+
func (gs *GameState) removeUnitsInLocation(loc Location) {
49+
gs.mu.Lock()
50+
defer gs.mu.Unlock()
51+
for k, v := range gs.Player.Units {
52+
if v.Location == loc {
53+
delete(gs.Player.Units, k)
54+
}
55+
}
56+
}
57+
58+
func (gs *GameState) UpdateUnit(u Unit) {
59+
gs.mu.Lock()
60+
defer gs.mu.Unlock()
61+
gs.Player.Units[u.ID] = u
62+
}
63+
64+
func (gs *GameState) GetUsername() string {
65+
return gs.Player.Username
66+
}
67+
68+
func (gs *GameState) getUnitsSnap() []Unit {
69+
gs.mu.RLock()
70+
defer gs.mu.RUnlock()
71+
Units := []Unit{}
72+
for _, v := range gs.Player.Units {
73+
Units = append(Units, v)
74+
}
75+
return Units
76+
}
77+
78+
func (gs *GameState) GetUnit(id int) (Unit, bool) {
79+
gs.mu.RLock()
80+
defer gs.mu.RUnlock()
81+
u, ok := gs.Player.Units[id]
82+
return u, ok
83+
}
84+
85+
func (gs *GameState) GetPlayerSnap() Player {
86+
gs.mu.RLock()
87+
defer gs.mu.RUnlock()
88+
Units := map[int]Unit{}
89+
for k, v := range gs.Player.Units {
90+
Units[k] = v
91+
}
92+
return Player{
93+
Username: gs.Player.Username,
94+
Units: Units,
95+
}
96+
}

internal/gamelogic/logs.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package gamelogic
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"time"
8+
9+
"github.com/bootdotdev/learn-pub-sub-starter/internal/routing"
10+
)
11+
12+
const logsFile = "game.log"
13+
14+
const writeToDiskSleep = 1 * time.Second
15+
16+
func WriteLog(gamelog routing.GameLog) error {
17+
log.Printf("received game log...")
18+
time.Sleep(writeToDiskSleep)
19+
20+
f, err := os.OpenFile(logsFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
21+
if err != nil {
22+
return fmt.Errorf("could not open logs file: %v", err)
23+
}
24+
defer f.Close()
25+
26+
str := fmt.Sprintf("%v %v: %v\n", gamelog.CurrentTime.Format(time.RFC3339), gamelog.Username, gamelog.Message)
27+
_, err = f.WriteString(str)
28+
if err != nil {
29+
return fmt.Errorf("could not write to logs file: %v", err)
30+
}
31+
return nil
32+
}

internal/gamelogic/move.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package gamelogic
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strconv"
7+
)
8+
9+
type MoveOutcome int
10+
11+
const (
12+
MoveOutcomeSamePlayer MoveOutcome = iota
13+
MoveOutComeSafe
14+
MoveOutcomeMakeWar
15+
)
16+
17+
func (gs *GameState) HandleMove(move ArmyMove) MoveOutcome {
18+
defer fmt.Println("------------------------")
19+
player := gs.GetPlayerSnap()
20+
21+
fmt.Println()
22+
fmt.Println("==== Move Detected ====")
23+
fmt.Printf("%s is moving %v unit(s) to %s\n", move.Player.Username, len(move.Units), move.ToLocation)
24+
for _, unit := range move.Units {
25+
fmt.Printf("* %v\n", unit.Rank)
26+
}
27+
28+
if player.Username == move.Player.Username {
29+
return MoveOutcomeSamePlayer
30+
}
31+
32+
overlappingLocation := getOverlappingLocation(player, move.Player)
33+
if overlappingLocation != "" {
34+
fmt.Printf("You have units in %s! You are at war with %s!\n", overlappingLocation, move.Player.Username)
35+
return MoveOutcomeMakeWar
36+
}
37+
fmt.Printf("You are safe from %s's units.\n", move.Player.Username)
38+
return MoveOutComeSafe
39+
}
40+
41+
func getOverlappingLocation(p1 Player, p2 Player) Location {
42+
for _, u1 := range p1.Units {
43+
for _, u2 := range p2.Units {
44+
if u1.Location == u2.Location {
45+
return u1.Location
46+
}
47+
}
48+
}
49+
return ""
50+
}
51+
52+
func (gs *GameState) CommandMove(words []string) (ArmyMove, error) {
53+
if gs.isPaused() {
54+
return ArmyMove{}, errors.New("the game is paused, you can not move units")
55+
}
56+
if len(words) < 3 {
57+
return ArmyMove{}, errors.New("usage: move <location> <unitID> <unitID> <unitID> etc")
58+
}
59+
newLocation := Location(words[1])
60+
locations := getAllLocations()
61+
if _, ok := locations[newLocation]; !ok {
62+
return ArmyMove{}, fmt.Errorf("error: %s is not a valid location", newLocation)
63+
}
64+
unitIDs := []int{}
65+
for _, word := range words[2:] {
66+
id := word
67+
unitID, err := strconv.Atoi(id)
68+
if err != nil {
69+
return ArmyMove{}, fmt.Errorf("error: %s is not a valid unit ID", id)
70+
}
71+
unitIDs = append(unitIDs, unitID)
72+
}
73+
74+
newUnits := []Unit{}
75+
for _, unitID := range unitIDs {
76+
unit, ok := gs.GetUnit(unitID)
77+
if !ok {
78+
return ArmyMove{}, fmt.Errorf("error: unit with ID %v not found", unitID)
79+
}
80+
unit.Location = newLocation
81+
gs.UpdateUnit(unit)
82+
newUnits = append(newUnits, unit)
83+
}
84+
85+
mv := ArmyMove{
86+
ToLocation: newLocation,
87+
Units: newUnits,
88+
Player: gs.GetPlayerSnap(),
89+
}
90+
fmt.Printf("Moved %v units to %s\n", len(mv.Units), mv.ToLocation)
91+
return mv, nil
92+
}

0 commit comments

Comments
 (0)