464 lines
12 KiB
Go
464 lines
12 KiB
Go
package game
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.sunturtle.xyz/studio/shotgun/player"
|
|
"git.sunturtle.xyz/studio/shotgun/serve"
|
|
)
|
|
|
|
func TestNewGame(t *testing.T) {
|
|
t.Parallel()
|
|
dealer := player.ID{1}
|
|
chall := player.ID{2}
|
|
for i := 0; i < 10000; i++ {
|
|
g := New(dealer, chall)
|
|
checks := []struct {
|
|
failed bool
|
|
msg string
|
|
args []any
|
|
}{
|
|
{g.players[0].id != dealer, "dealer isn't in first position", nil},
|
|
{g.players[0].hp != g.hp, "dealer hp is %d, want %d", []any{g.players[0].hp, g.hp}},
|
|
{g.players[1].id != chall, "challenger isn't in second position", nil},
|
|
{g.players[1].hp != g.hp, "challenger hp is %d, want %d", []any{g.players[1].hp, g.hp}},
|
|
{len(g.shells) < 2 || len(g.shells) > 8, "bad shells count %d, want 2-8", []any{len(g.shells)}},
|
|
{&g.shells[0] != &g.shellArray[0], "shells[0] is %p, want %p", []any{&g.shells[0], &g.shellArray[0]}},
|
|
{g.round != 1, "first round is %d, want 1", []any{g.round}},
|
|
{g.group != 1, "first group is %d, want 1", []any{g.group}},
|
|
{g.turn != 1, "first turn is %d, want 1", []any{g.turn}},
|
|
{g.hp < 2 || g.hp > 4, "hp is %d, want 2-4", []any{g.hp}},
|
|
{g.damage != 1, "damage is %d, want 1", []any{g.damage}},
|
|
{g.reveal, "revealed at start", nil},
|
|
{g.prev != nil, "already discharged", nil},
|
|
}
|
|
for _, c := range checks {
|
|
if c.failed {
|
|
t.Errorf(c.msg, c.args...)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGameStartRound(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(player.ID{1}, player.ID{2})
|
|
for i := uint(1); i < 10000; i++ {
|
|
checks := []struct {
|
|
failed bool
|
|
msg string
|
|
args []any
|
|
}{
|
|
{g.players[0].hp != g.hp, "dealer hp is %d, want %d", []any{g.players[0].hp, g.hp}},
|
|
{g.players[1].hp != g.hp, "challenger hp is %d, want %d", []any{g.players[1].hp, g.hp}},
|
|
{len(g.shells) < 2 || len(g.shells) > 8, "bad shells count %d, want 2-8", []any{len(g.shells)}},
|
|
{&g.shells[0] != &g.shellArray[0], "shells[0] is %p, want %p", []any{&g.shells[0], &g.shellArray[0]}},
|
|
{g.round != i, "round is %d, want %d", []any{g.round, i}},
|
|
{g.group != 1, "group is %d, want 1", []any{g.group}},
|
|
{g.turn != 1, "turn is %d, want 1", []any{g.turn}},
|
|
{g.hp < 2 || g.hp > 4, "hp is %d, want 2-4", []any{g.hp}},
|
|
{g.damage != 1, "damage is %d, want 1", []any{g.damage}},
|
|
{g.reveal, "revealed at start", nil},
|
|
{g.prev != nil, "already discharged", nil},
|
|
}
|
|
for _, c := range checks {
|
|
if c.failed {
|
|
t.Errorf(c.msg, c.args...)
|
|
}
|
|
}
|
|
// H*ck with the game state as if a round is played.
|
|
g.players[0].hp = 0
|
|
g.PopShell()
|
|
g.PopShell()
|
|
g.group = 2
|
|
g.turn = 3
|
|
g.damage = 2
|
|
// Start the next round and check again.
|
|
g.StartRound()
|
|
}
|
|
}
|
|
|
|
func TestGameStartGroup(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(player.ID{1}, player.ID{2})
|
|
for i := uint(1); i < 10000; i++ {
|
|
counts := g.ShellCounts()
|
|
checks := []struct {
|
|
failed bool
|
|
msg string
|
|
args []any
|
|
}{
|
|
{g.players[0].items[0] == ItemNone, "dealer has no first item: %v", []any{g.players[0].items}},
|
|
{g.players[1].items[0] == ItemNone, "challenger has no first item: %v", []any{g.players[1].items}},
|
|
{len(g.shells) < 2 || len(g.shells) > 8, "bad shells count %d, want 2-8", []any{len(g.shells)}},
|
|
{&g.shells[0] != &g.shellArray[0], "shells[0] is %p, want %p", []any{&g.shells[0], &g.shellArray[0]}},
|
|
{counts.Live != counts.Blank && counts.Live+1 != counts.Blank, "imbalanced live/blank %d/%d", []any{counts.Live, counts.Blank}},
|
|
{g.round != 1, "round is %d, want 1", []any{g.round}},
|
|
{g.group != i, "group is %d, want %d", []any{g.group, i}},
|
|
{g.turn != 1, "turn is %d, want 1", []any{g.turn}},
|
|
{g.damage != 1, "damage is %d, want 1", []any{g.damage}},
|
|
{g.reveal, "revealed at start", nil},
|
|
{g.prev != nil, "already discharged", nil},
|
|
}
|
|
for _, c := range checks {
|
|
if c.failed {
|
|
t.Errorf(c.msg, c.args...)
|
|
}
|
|
}
|
|
// H*ck with the game state.
|
|
g.players[0].items[0] = ItemNone
|
|
g.players[1].items[0] = ItemNone
|
|
g.PopShell()
|
|
g.PopShell()
|
|
g.turn = 3
|
|
g.damage = 2
|
|
g.reveal = true
|
|
// Now advance the group.
|
|
g.StartGroup()
|
|
}
|
|
}
|
|
|
|
func TestGameNextTurn(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(player.ID{1}, player.ID{2})
|
|
for i := uint(1); i < 10000; i++ {
|
|
checks := []struct {
|
|
failed bool
|
|
msg string
|
|
args []any
|
|
}{
|
|
{g.round != 1, "round is %d, want 1", []any{g.round}},
|
|
{g.group != 1, "group is %d, want 1", []any{g.group}},
|
|
{g.turn != i, "turn is %d, want %d", []any{g.turn, i}},
|
|
{g.damage != 1, "damage is %d, want 1", []any{g.damage}},
|
|
{g.reveal, "revealed at start", nil},
|
|
{g.CurrentPlayer().cuffs != Uncuffed, "cuffs is %d, want %d", []any{g.CurrentPlayer().cuffs, Uncuffed}},
|
|
}
|
|
for _, c := range checks {
|
|
if c.failed {
|
|
t.Errorf(c.msg, c.args...)
|
|
}
|
|
}
|
|
g.damage = 2
|
|
g.reveal = true
|
|
g.Opponent().cuffs = Cuffed
|
|
g.NextTurn()
|
|
}
|
|
}
|
|
|
|
// TODO(zeph): test next turn when the opponent has just been cuffed
|
|
|
|
func TestGamePlayers(t *testing.T) {
|
|
t.Parallel()
|
|
dealer := player.ID{1}
|
|
chall := player.ID{2}
|
|
g := New(dealer, chall)
|
|
if g.CurrentPlayer().id != chall {
|
|
t.Errorf("challenger isn't current player at start")
|
|
}
|
|
if g.Opponent().id != dealer {
|
|
t.Errorf("dealer isn't opponent at start")
|
|
}
|
|
g.NextTurn()
|
|
if g.CurrentPlayer().id != dealer {
|
|
t.Errorf("dealer isn't current player after turn")
|
|
}
|
|
if g.Opponent().id != chall {
|
|
t.Errorf("challenger isn't opponent after turn")
|
|
}
|
|
g.NextTurn()
|
|
if g.CurrentPlayer().id != chall {
|
|
t.Errorf("challenger isn't current player after two turns")
|
|
}
|
|
if g.Opponent().id != dealer {
|
|
t.Errorf("dealer isn't opponent after two turns")
|
|
}
|
|
me, you := g.CurrentPlayer(), g.Opponent()
|
|
for i := 3; i < 1000; i++ {
|
|
g.NextTurn()
|
|
if g.CurrentPlayer() != you {
|
|
t.Errorf("wrong player after %d turns", i)
|
|
}
|
|
if g.Opponent() != me {
|
|
t.Errorf("wrong opponent after %d turns", i)
|
|
}
|
|
me, you = you, me
|
|
}
|
|
}
|
|
|
|
// TODO(zeph): test using items
|
|
|
|
func TestGamePopShell(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(player.ID{1}, player.ID{2})
|
|
if live := g.PopShell(); live != g.shellArray[0] {
|
|
t.Errorf("first pop %t, wanted %t", live, g.shellArray[0])
|
|
}
|
|
if g.prev != &g.shellArray[0] {
|
|
t.Errorf("first prev is %p, wanted %p", g.prev, &g.shellArray[0])
|
|
}
|
|
if live := g.PopShell(); live != g.shellArray[1] {
|
|
t.Errorf("second pop %t, wanted %t", live, g.shellArray[1])
|
|
}
|
|
if g.prev != &g.shellArray[1] {
|
|
t.Errorf("second prev is %p, wanted %p", g.prev, &g.shellArray[1])
|
|
}
|
|
for range g.shellArray {
|
|
if g.Empty() {
|
|
return
|
|
}
|
|
g.PopShell()
|
|
}
|
|
if !g.Empty() {
|
|
t.Errorf("popped too many shells")
|
|
}
|
|
}
|
|
|
|
func TestGamePeek(t *testing.T) {
|
|
dealer := player.ID{1}
|
|
chall := player.ID{2}
|
|
t.Run("empty", func(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(dealer, chall)
|
|
for !g.Empty() {
|
|
g.PopShell()
|
|
}
|
|
if g.Peek(dealer) != nil || g.Peek(chall) != nil {
|
|
t.Errorf("peeked a shell when empty")
|
|
}
|
|
})
|
|
t.Run("unrevealed", func(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(dealer, chall)
|
|
if g.Peek(dealer) != nil || g.Peek(chall) != nil {
|
|
t.Errorf("peeked a shell when not revealed")
|
|
}
|
|
})
|
|
t.Run("turn", func(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(dealer, chall)
|
|
g.reveal = true
|
|
if g.Peek(dealer) != nil {
|
|
t.Errorf("dealer peeked on challenger's turn")
|
|
}
|
|
if g.Peek(chall) != &g.shellArray[0] {
|
|
t.Errorf("challenger couldn't peek on own turn")
|
|
}
|
|
g.NextTurn()
|
|
g.reveal = true
|
|
if g.Peek(dealer) != &g.shellArray[0] {
|
|
t.Errorf("dealer couldn't peek on own turn")
|
|
}
|
|
if g.Peek(chall) != nil {
|
|
t.Errorf("challenger peeked on dealer's turn")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGameEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
shells []bool
|
|
want bool
|
|
}{
|
|
{nil, true},
|
|
{[]bool{}, true},
|
|
{[]bool{true}, false},
|
|
{[]bool{false}, false},
|
|
}
|
|
for _, c := range cases {
|
|
// We don't care about anything but the shells list.
|
|
g := Game{shells: c.shells}
|
|
if got := g.Empty(); got != c.want {
|
|
t.Errorf("empty reported %t, wanted %t", got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGameWinner(t *testing.T) {
|
|
t.Parallel()
|
|
dealer := player.ID{1}
|
|
chall := player.ID{2}
|
|
cases := []struct {
|
|
name string
|
|
p0, p1 int8
|
|
want player.ID
|
|
}{
|
|
{
|
|
name: "none",
|
|
p0: 1,
|
|
p1: 1,
|
|
want: player.ID{},
|
|
},
|
|
{
|
|
name: "dealer",
|
|
p0: 1,
|
|
p1: 0,
|
|
want: dealer,
|
|
},
|
|
{
|
|
name: "challenger",
|
|
p0: 0,
|
|
p1: 1,
|
|
want: chall,
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
c := c
|
|
t.Run(c.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(dealer, chall)
|
|
g.players[0].hp = c.p0
|
|
g.players[1].hp = c.p1
|
|
if got := g.Winner(); deref(got).id != c.want {
|
|
t.Errorf("wrong winner: %#v doesn't have id %#v", got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGameShoot(t *testing.T) {
|
|
dealer := player.ID{1}
|
|
chall := player.ID{2}
|
|
cases := []struct {
|
|
name string
|
|
live bool
|
|
self bool
|
|
shooter player.ID
|
|
p0l, p1l int8
|
|
turn bool
|
|
err error
|
|
}{
|
|
{
|
|
name: "challenger shoots dealer live",
|
|
live: true,
|
|
self: false,
|
|
shooter: chall,
|
|
p0l: 1,
|
|
p1l: 0,
|
|
turn: true,
|
|
},
|
|
{
|
|
name: "challenger shoots dealer blank",
|
|
live: false,
|
|
self: false,
|
|
shooter: chall,
|
|
p0l: 0,
|
|
p1l: 0,
|
|
turn: true,
|
|
},
|
|
{
|
|
name: "challenger shoots self live",
|
|
live: true,
|
|
self: true,
|
|
shooter: chall,
|
|
p0l: 0,
|
|
p1l: 1,
|
|
turn: true,
|
|
},
|
|
{
|
|
name: "challenger shoots self blank",
|
|
live: false,
|
|
self: true,
|
|
shooter: chall,
|
|
p0l: 0,
|
|
p1l: 0,
|
|
turn: false,
|
|
},
|
|
{
|
|
name: "dealer shoots",
|
|
live: true,
|
|
self: false,
|
|
shooter: dealer,
|
|
err: ErrWrongTurn,
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
c := c
|
|
t.Run(c.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(dealer, chall)
|
|
g.shellArray[0] = c.live
|
|
err := g.Shoot(c.shooter, c.self)
|
|
if err != c.err {
|
|
t.Errorf("wrong error: %v, wanted %v", err, c.err)
|
|
}
|
|
if err == nil && &g.shells[0] != &g.shellArray[1] {
|
|
t.Errorf("no error but shells didn't move")
|
|
}
|
|
if err != nil && &g.shells[0] != &g.shellArray[0] {
|
|
t.Errorf("error but shells moved")
|
|
}
|
|
if l := g.hp - g.players[0].hp; l != c.p0l {
|
|
t.Errorf("wrong hp loss for dealer: got %d, wanted %d", l, c.p0l)
|
|
}
|
|
if l := g.hp - g.players[1].hp; l != c.p1l {
|
|
t.Errorf("wrong hp loss for challenger: got %d, wanted %d", l, c.p1l)
|
|
}
|
|
if c.turn && g.CurrentPlayer().id != dealer {
|
|
t.Errorf("shot should have advanced turn but didn't")
|
|
}
|
|
if !c.turn && g.CurrentPlayer().id != chall {
|
|
t.Errorf("shot should not have advanced turn but did")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGameShellCounts(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
shells []bool
|
|
want serve.ShellCounts
|
|
}{
|
|
{
|
|
name: "empty",
|
|
shells: nil,
|
|
want: serve.ShellCounts{Live: 0, Blank: 0},
|
|
},
|
|
{
|
|
name: "one-live",
|
|
shells: []bool{true},
|
|
want: serve.ShellCounts{Live: 1, Blank: 0},
|
|
},
|
|
{
|
|
name: "one-blank",
|
|
shells: []bool{false},
|
|
want: serve.ShellCounts{Live: 0, Blank: 1},
|
|
},
|
|
{
|
|
name: "two",
|
|
shells: []bool{true, false},
|
|
want: serve.ShellCounts{Live: 1, Blank: 1},
|
|
},
|
|
{
|
|
name: "three",
|
|
shells: []bool{true, false, true},
|
|
want: serve.ShellCounts{Live: 2, Blank: 1},
|
|
},
|
|
{
|
|
name: "four",
|
|
shells: []bool{true, false, true, false},
|
|
want: serve.ShellCounts{Live: 2, Blank: 2},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
c := c
|
|
t.Run(c.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
g := New(player.ID{1}, player.ID{2})
|
|
g.shells = c.shells
|
|
if got := g.ShellCounts(); got != c.want {
|
|
t.Errorf("wrong shell counts: got %#v, wanted %#v", got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func deref[T any, P ~*T](p P) (x T) {
|
|
if p != nil {
|
|
x = *p
|
|
}
|
|
return
|
|
}
|