package game import ( "errors" "git.sunturtle.xyz/studio/shotgun/player" ) type Game struct { RNG RNG PP [2]Player Shells []bool Round uint Group uint Turn uint HP int8 Damage int8 Reveal bool Prev *bool // Backing storage for shells. ShellArray [8]bool } func NewGame() *Game { return &Game{ RNG: NewRNG(), } } func (g *Game) StartRound() { g.HP = int8(g.RNG.Intn(3) + 2) g.PP[0].StartRound(g.HP) g.PP[1].StartRound(g.HP) g.Round++ g.Group = 0 g.StartGroup() } func (g *Game) StartGroup() { items := g.RNG.Intn(4) + 1 g.PP[0].StartGroup(&g.RNG, items) g.PP[1].StartGroup(&g.RNG, items) shells := g.RNG.Intn(6) + 2 for i := 0; i < shells/2; i++ { g.ShellArray[i] = true } for i := shells / 2; i < shells; i++ { g.ShellArray[i] = false } g.Shells = g.ShellArray[:shells] ShuffleSlice(&g.RNG, g.Shells) g.Group++ g.Turn = 0 g.Prev = nil g.NextTurn() } func (g *Game) NextTurn() { g.Turn++ g.Damage = 1 g.Reveal = false cur := g.CurrentPlayer() cur.Cuffs = cur.Cuffs.NextState() } // CurrentPlayer gets the index of the current player, either 0 or 1. func (g *Game) CurrentPlayer() *Player { return &g.PP[g.Turn%2] } // Opponent returns the player who is not the current player. func (g *Game) Opponent() *Player { return &g.PP[g.Turn%2^1] } // Apply uses an item by index for the current player. // This may cause the group to end. // Returns ErrWrongTurn if id does not correspond to the current player. func (g *Game) Apply(id player.ID, item int) error { cur := g.CurrentPlayer() if cur.ID != id { return ErrWrongTurn } if item < 0 || item >= len(cur.Items) { return errors.New("item index out of bounds") } if cur.Items[item].Apply(g) { cur.Items[item] = ItemNone } return nil } // PopShell removes a shell from the shotgun. // This may cause the shotgun to be empty, but does not start the next group // if so. func (g *Game) PopShell() bool { g.Prev = &g.Shells[0] g.Shells = g.Shells[1:] return *g.Prev } // Empty returns whether the shotgun is empty. func (g *Game) Empty() bool { return len(g.Shells) == 0 } // Winner returns the player who won, or nil if the round is not over. func (g *Game) Winner() *Player { if g.PP[0].HP <= 0 { return &g.PP[1] } if g.PP[1].HP <= 0 { return &g.PP[0] } return nil } // Shoot fires the shotgun, at the opponent if self is false and at the current // player if self is true. Afterward, the round may be over, or the shotgun may // be empty. // Returns ErrWrongTurn if id does not correspond to the current player. func (g *Game) Shoot(id player.ID, self bool) error { cur := g.CurrentPlayer() if cur.ID != id { return ErrWrongTurn } target := g.Opponent() if self { target = g.CurrentPlayer() } live := g.PopShell() if live { target.HP -= g.Damage g.NextTurn() } else if !self { g.NextTurn() } return nil } var ErrWrongTurn = errors.New("not your turn")