131 lines
2.7 KiB
Go
131 lines
2.7 KiB
Go
package game
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"git.sunturtle.xyz/studio/shotgun/player"
|
|
)
|
|
|
|
type Game struct {
|
|
RNG RNG
|
|
PP [2]Player
|
|
Shells []bool
|
|
HP int8
|
|
Round uint
|
|
Group uint
|
|
Turn uint
|
|
Damage int8
|
|
Reveal bool
|
|
|
|
// Backing storage for shells.
|
|
ShellArray [8]bool
|
|
}
|
|
|
|
func NewGame() *Game {
|
|
return &Game{
|
|
RNG: NewRNG(),
|
|
Damage: 1,
|
|
}
|
|
}
|
|
|
|
func (g *Game) StartRound() {
|
|
g.PP[0].StartRound()
|
|
g.PP[1].StartRound()
|
|
g.Round++
|
|
}
|
|
|
|
func (g *Game) StartGroup() {
|
|
items := g.RNG.Intn(4) + 1
|
|
g.HP = int8(g.RNG.Intn(3) + 2)
|
|
g.PP[0].StartGroup(&g.RNG, g.HP, items)
|
|
g.PP[1].StartGroup(&g.RNG, g.HP, 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
|
|
}
|
|
|
|
// 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 {
|
|
r := g.Shells[0]
|
|
g.Shells = g.Shells[1:]
|
|
return r
|
|
}
|
|
|
|
// 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.Turn++
|
|
} else if !self {
|
|
g.Turn++
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var ErrWrongTurn = errors.New("not your turn")
|