Handle game state immutably

This commit is contained in:
Luke Meyers 2020-02-06 18:57:43 -08:00
parent 54398b4e17
commit f62e9833db
5 changed files with 104 additions and 45 deletions

View File

@ -2,8 +2,9 @@ package game
import "log" import "log"
func chooseCommand(t Team, s State) command { func chooseCommand(s State, teamID int) command {
h := t.Baton.Holder t := s.Teams[teamID]
h := t.BatonHolder()
if collide(h.Pos+1, h.Lane, s) { if collide(h.Pos+1, h.Lane, s) {
if h.Lane <= t.Lane { if h.Lane <= t.Lane {
return left return left
@ -13,7 +14,7 @@ func chooseCommand(t Team, s State) command {
var nextBot *Bot var nextBot *Bot
for i, b := range t.Bots { for i, b := range t.Bots {
if b.id == h.id+1 { if b.ID == h.ID+1 {
nextBot = &t.Bots[i] nextBot = &t.Bots[i]
break break
} }

View File

@ -1,24 +1,8 @@
package game package game
import "math/rand" import (
"math/rand"
func doCommand(cmd command, b *Bot) { )
da := 1
da += rand.Intn(3) - 1
switch cmd {
case speedUp:
b.a += da
accelerate(b)
case slowDown:
b.a -= da
accelerate(b)
case left:
b.Lane++
case right:
b.Lane--
}
}
type command int type command int
@ -28,3 +12,28 @@ const (
left left
right right
) )
func doCommand(cmd command, s State, teamID int) State {
da := 1
da += rand.Intn(3) - 1
b := activeBot(s.Teams[teamID])
if b == nil {
return s
}
switch cmd {
case speedUp:
b.a += da
*b = accelerate(*b)
case slowDown:
b.a -= da
*b = accelerate(*b)
case left:
b.Lane++
case right:
b.Lane--
}
return updateBot(s, teamID, *b)
}

View File

@ -16,7 +16,7 @@ func NewState() State {
var bots []Bot var bots []Bot
for j := 0; j < numBots; j++ { for j := 0; j < numBots; j++ {
b := Bot{ b := Bot{
id: i*NumTeams + j, ID: i*NumTeams + j,
Lane: i, Lane: i,
Pos: j * (Steps / numBots), Pos: j * (Steps / numBots),
} }
@ -25,7 +25,7 @@ func NewState() State {
teams = append(teams, Team{ teams = append(teams, Team{
id: i, id: i,
Bots: bots, Bots: bots,
Baton: Baton{Holder: &bots[0]}, Baton: Baton{HolderID: i * NumTeams},
Lane: i, Lane: i,
}) })
} }
@ -61,8 +61,17 @@ type Team struct {
Lane int Lane int
} }
func (t Team) BatonHolder() *Bot {
for _, b := range t.Bots {
if b.ID == t.Baton.HolderID {
return &b
}
}
return nil
}
type Bot struct { type Bot struct {
id int ID int
Lane int Lane int
Pos int Pos int
v int v int
@ -70,7 +79,7 @@ type Bot struct {
} }
type Baton struct { type Baton struct {
Holder *Bot HolderID int
} }
type Obstacle struct { type Obstacle struct {
@ -78,17 +87,17 @@ type Obstacle struct {
Pos int Pos int
} }
func UpdateState(sOld State) State { func UpdateState(s State) State {
s := sOld
for i, t := range s.Teams { for i, t := range s.Teams {
doCommand(chooseCommand(t, sOld), t.Baton.Holder) s = doCommand(chooseCommand(s, i), s, i)
moveBot(t.Baton.Holder, sOld) if b := activeBot(t); b != nil {
maybePassBaton(&s.Teams[i]) s = moveBot(s, i, *b)
}
s = maybePassBaton(s, i)
} }
for _, t := range s.Teams { for _, t := range s.Teams {
if won(*t.Baton.Holder, s) { if b := activeBot(t); b != nil && won(*b, s) {
s.GameOver = true s.GameOver = true
} }
} }
@ -96,21 +105,57 @@ func UpdateState(sOld State) State {
return s return s
} }
func maybePassBaton(t *Team) { func maybePassBaton(s State, teamID int) State {
t := s.Teams[teamID]
h := activeBot(t)
if h == nil {
return s
}
for i, b := range t.Bots { for i, b := range t.Bots {
h := t.Baton.Holder if h.ID >= b.ID || h.Lane != b.Lane {
if h.id >= b.id || h.Lane != b.Lane {
continue continue
} }
if abs(b.Pos-h.Pos) <= passDistance { if abs(b.Pos-h.Pos) <= passDistance {
log.Printf("team %v pass from %v to %v!", t.id, h.id, b.id) log.Printf("team %v pass from %v to %v!", teamID, h.ID, b.ID)
t.Baton.Holder.v = 0 h.v = 0
t.Baton.Holder.a = 0 h.a = 0
t.Baton.Holder = &t.Bots[i] s = updateBot(s, teamID, *h)
t.Bots[i].a = baseAccel newH := t.Bots[i]
return newH.a = baseAccel
t.Baton.HolderID = newH.ID
s = updateTeam(s, t)
return updateBot(s, teamID, newH)
} }
} }
return s
}
func activeBot(t Team) *Bot {
for _, b := range t.Bots {
if b.ID == t.Baton.HolderID {
return &b
}
}
return nil
}
func updateBot(s State, teamID int, b Bot) State {
t := s.Teams[teamID]
for i, bb := range t.Bots {
if bb.ID == b.ID {
t.Bots = append(t.Bots[:i], append([]Bot{b}, t.Bots[i+1:]...)...)
break
}
}
return updateTeam(s, t)
}
func updateTeam(s State, t Team) State {
s.Teams = append(s.Teams[:t.id], append([]Team{t}, s.Teams[t.id+1:]...)...)
return s
} }
func won(b Bot, s State) bool { func won(b Bot, s State) bool {

View File

@ -1,6 +1,6 @@
package game package game
func accelerate(b *Bot) { func accelerate(b Bot) Bot {
if b.a < -maxA { if b.a < -maxA {
b.a = -maxA b.a = -maxA
} }
@ -15,14 +15,18 @@ func accelerate(b *Bot) {
if b.v < -maxV { if b.v < -maxV {
b.v = -maxV b.v = -maxV
} }
return b
} }
func moveBot(b *Bot, s State) { func moveBot(s State, teamID int, b Bot) State {
for i := 0; i < b.v; i++ { for i := 0; i < b.v; i++ {
if !collide(b.Pos+1, b.Lane, s) { if !collide(b.Pos+1, b.Lane, s) {
b.Pos++ b.Pos++
} }
} }
return updateBot(s, teamID, b)
} }
func collide(pos, lane int, s State) bool { func collide(pos, lane int, s State) bool {

View File

@ -50,7 +50,7 @@ func renderBots(s game.State, w *pixelgl.Window, d time.Duration, colors map[*ga
im.Circle(botWidth, 0) im.Circle(botWidth, 0)
im.Draw(w) im.Draw(w)
if &t.Bots[j] == t.Baton.Holder { if t.Bots[j].ID == t.Baton.HolderID {
renderBaton(pos, w) renderBaton(pos, w)
} }
} }