shotgun/game/game.go

169 lines
3.5 KiB
Go
Raw Normal View History

2024-01-20 22:06:56 -06:00
package game
import (
"errors"
"git.sunturtle.xyz/studio/shotgun/player"
2024-01-21 00:56:45 -06:00
"git.sunturtle.xyz/studio/shotgun/serve"
)
2024-01-20 22:06:56 -06:00
type Game struct {
RNG RNG
PP [2]Player
Shells []bool
Round uint
Group uint
Turn uint
2024-01-21 00:13:23 -06:00
HP int8
2024-01-20 22:06:56 -06:00
Damage int8
Reveal bool
2024-01-21 00:28:29 -06:00
Prev *bool
2024-01-20 22:06:56 -06:00
// Backing storage for shells.
ShellArray [8]bool
}
func NewGame() *Game {
g := &Game{
2024-01-21 00:13:23 -06:00
RNG: NewRNG(),
}
g.StartRound()
return g
2024-01-20 22:06:56 -06:00
}
func (g *Game) StartRound() {
g.HP = int8(g.RNG.Intn(3) + 2)
g.PP[0].StartRound(g.HP)
g.PP[1].StartRound(g.HP)
2024-01-20 23:03:34 -06:00
g.Round++
2024-01-21 00:13:23 -06:00
g.Group = 0
g.StartGroup()
}
func (g *Game) StartGroup() {
2024-01-20 22:06:56 -06:00
items := g.RNG.Intn(4) + 1
g.PP[0].StartGroup(&g.RNG, items)
g.PP[1].StartGroup(&g.RNG, items)
2024-01-20 22:06:56 -06:00
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++
2024-01-20 23:03:34 -06:00
g.Turn = 0
2024-01-21 00:28:29 -06:00
g.Prev = nil
2024-01-21 00:13:23 -06:00
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
}
2024-01-20 23:02:15 -06:00
if item < 0 || item >= len(cur.Items) {
return errors.New("item index out of bounds")
2024-01-20 23:02:15 -06:00
}
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.
2024-01-20 23:52:58 -06:00
func (g *Game) PopShell() bool {
2024-01-21 00:28:29 -06:00
g.Prev = &g.Shells[0]
g.Shells = g.Shells[1:]
2024-01-21 00:28:29 -06:00
return *g.Prev
}
2024-01-21 00:33:39 -06:00
// Peek returns the current turn's shell if it is revealed for the player with
// the given ID, or nil otherwise.
func (g *Game) Peek(id player.ID) *bool {
2024-01-21 00:56:45 -06:00
if len(g.Shells) == 0 || id != g.CurrentPlayer().ID || !g.Reveal {
2024-01-21 00:33:39 -06:00
return nil
}
return &g.Shells[0]
}
// Empty returns whether the shotgun is empty.
func (g *Game) Empty() bool {
return len(g.Shells) == 0
2024-01-20 22:06:56 -06:00
}
2024-01-20 23:52:58 -06:00
// 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
}
2024-01-20 23:52:58 -06:00
target := g.Opponent()
if self {
target = g.CurrentPlayer()
}
live := g.PopShell()
if live {
target.HP -= g.Damage
2024-01-21 00:13:23 -06:00
g.NextTurn()
2024-01-20 23:52:58 -06:00
} else if !self {
2024-01-21 00:13:23 -06:00
g.NextTurn()
2024-01-20 23:52:58 -06:00
}
return nil
2024-01-20 23:52:58 -06:00
}
2024-01-21 00:56:45 -06:00
// DTO returns the current game state as viewed by the given player.
func (g *Game) DTO(id player.ID) serve.Game {
return serve.Game{
Players: [2]serve.Player{
g.PP[0].DTO(),
g.PP[1].DTO(),
},
Round: g.Round,
Damage: g.Damage,
Shell: g.Peek(id),
Previous: g.Prev,
}
}
var ErrWrongTurn = errors.New("not your turn")