Fifteen pieces of a games night: Dice, DiceTray, PlayingCard, CardHand, Deck, Chessboard, GoBoard, MahjongTile, Domino, Roulette, CoinFlip, BingoCard, TicTacToe, ScoreTrack and Meeple. Component number 500 is a meeple, which seems right. Dice tumble, coins turn and wheels spin on a cue, a frame at a time, and land at once under Reduce Motion. Hands lift the card you point at and play the one drawn on top; pieces and stones pick the lighter of Lit and Ink for White, so they read in every palette; the computer plays a fair O; FREE is a star in every language. New 12x12 icons: Spade, Club, Diamond, Meeple, King, Queen, Rook, Bishop, Knight and Pawn.
195 lines
6.6 KiB
Go
195 lines
6.6 KiB
Go
package tabletop_test
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/tabletop"
|
||
)
|
||
|
||
func TestDiceTumbleThenLand(t *testing.T) {
|
||
for _, reduce := range []bool{false, true} {
|
||
wd := newWorld()
|
||
wd.reduce = reduce
|
||
value, cue := 3, 0
|
||
tt := view(200, 200, wd, func(c *ui.Context) { tabletop.Dice(c, "Die", value, tabletop.DiceOptions{Cue: cue}) })
|
||
landed := func(v int) []byte {
|
||
return view(200, 200, newWorld(), func(c *ui.Context) { tabletop.Dice(c, "Die", v, tabletop.DiceOptions{}) }).Image().Pix
|
||
}
|
||
if !bytes.Equal(tt.Image().Pix, landed(3)) {
|
||
t.Fatal("a die shown first tumbled")
|
||
}
|
||
value, cue = 5, 1
|
||
tt.Frame()
|
||
if tumbling := !bytes.Equal(tt.Image().Pix, landed(5)); tumbling == reduce {
|
||
t.Errorf("reduce %v: tumbling is %v", reduce, tumbling)
|
||
}
|
||
later(tt, wd, time.Second)
|
||
if !bytes.Equal(tt.Image().Pix, landed(5)) {
|
||
t.Errorf("reduce %v: it never landed on 5", reduce)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestDiceTrayAddsUp(t *testing.T) {
|
||
tt := view(400, 200, newWorld(), func(c *ui.Context) { tabletop.DiceTray(c, "Dice", []int{6, 6, 1}, tabletop.DiceTrayOptions{}) })
|
||
find(t, tt, "TOTAL 13")
|
||
find(t, tt, "Dice 3")
|
||
}
|
||
|
||
func TestCardsReadAndInk(t *testing.T) {
|
||
tt := view(400, 400, newWorld(), func(c *ui.Context) {
|
||
tabletop.PlayingCard(c, tabletop.Card{Rank: 1, Suit: tabletop.Spades}, tabletop.PlayingCardOptions{})
|
||
tabletop.PlayingCard(c, tabletop.Card{Rank: 12, Suit: tabletop.Hearts}, tabletop.PlayingCardOptions{})
|
||
tabletop.PlayingCard(c, tabletop.Card{Rank: 9, Suit: tabletop.Clubs}, tabletop.PlayingCardOptions{Back: true})
|
||
})
|
||
img := tt.Image()
|
||
x, y, w, h := dots(find(t, tt, "ACE OF SPADES"))
|
||
if count(img, x, y, w, h, pixel.Bad) > 0 {
|
||
t.Error("a spade is red")
|
||
}
|
||
x, y, w, h = dots(find(t, tt, "QUEEN OF HEARTS"))
|
||
if count(img, x, y, w, h, pixel.Bad) == 0 {
|
||
t.Error("a heart is not red")
|
||
}
|
||
x, y, w, h = dots(find(t, tt, "CARD BACK"))
|
||
if count(img, x, y, w, h, pixel.Accent) == 0 {
|
||
t.Error("the back shows no pattern")
|
||
}
|
||
}
|
||
|
||
func TestCardHandLiftsAndPlays(t *testing.T) {
|
||
cards := []tabletop.Card{{Rank: 2, Suit: tabletop.Clubs}, {Rank: 3, Suit: tabletop.Clubs}, {Rank: 4, Suit: tabletop.Clubs}}
|
||
wd, played, off := newWorld(), -1, false
|
||
tt := view(400, 300, wd, func(c *ui.Context) {
|
||
if i := tabletop.CardHand(c, "Hand", cards, tabletop.CardHandOptions{Disabled: off}).Played; i >= 0 {
|
||
played = i
|
||
}
|
||
})
|
||
x, y, _, _ := dots(find(t, tt, "Hand"))
|
||
// top is the first row of a card's outline.
|
||
top := func(i int) int {
|
||
for j := y; j < y+60; j++ {
|
||
if at(tt.Image(), x+i*18+4, j) == pixel.Edge {
|
||
return j - y
|
||
}
|
||
}
|
||
return -1
|
||
}
|
||
rest := top(1)
|
||
tt.Key(0, ui.KeyTab)
|
||
tt.Key(0, ui.KeyRight)
|
||
later(tt, wd, time.Second)
|
||
if top(1) != rest-6 || top(0) != rest {
|
||
t.Fatalf("the chosen card rose to %d from %d", top(1), rest)
|
||
}
|
||
tt.Key(0, ui.KeyEnter)
|
||
if played != 1 {
|
||
t.Fatalf("Enter played %d", played)
|
||
}
|
||
r := find(t, tt, "Hand")
|
||
tt.ClickAt(r.X+r.W-4, r.Y+r.H/2)
|
||
if played != 2 {
|
||
t.Fatalf("a click on the last card played %d", played)
|
||
}
|
||
off, played = true, -1
|
||
tt.Key(0, ui.KeyEnter)
|
||
if played != -1 {
|
||
t.Fatal("a disabled hand played")
|
||
}
|
||
}
|
||
|
||
func TestDeckDrawsUntilEmpty(t *testing.T) {
|
||
left, drawn := 2, 0
|
||
tt := view(300, 300, newWorld(), func(c *ui.Context) {
|
||
if tabletop.Deck(c, "Deck", left, tabletop.DeckOptions{}).Drawn {
|
||
left--
|
||
drawn++
|
||
}
|
||
})
|
||
// The count's badge is the only AccentInk on the pile.
|
||
x, y, w, h := dots(find(t, tt, "Deck"))
|
||
if count(tt.Image(), x, y, w, h, pixel.AccentInk) == 0 {
|
||
t.Fatal("no count badge")
|
||
}
|
||
click(t, tt, "Deck")
|
||
tt.Key(0, ui.KeyEnter)
|
||
tt.Key(0, ui.KeyEnter)
|
||
click(t, tt, "Deck")
|
||
if left != 0 || drawn != 2 {
|
||
t.Fatalf("drew %d, %d left", drawn, left)
|
||
}
|
||
}
|
||
|
||
func TestDominoAndMahjongRead(t *testing.T) {
|
||
tt := view(600, 600, newWorld(), func(c *ui.Context) {
|
||
tabletop.Domino(c, 3, 5, tabletop.DominoOptions{Vertical: true})
|
||
tabletop.MahjongTile(c, tabletop.Tile{Family: tabletop.Dots, Rank: 3}, tabletop.MahjongTileOptions{})
|
||
tabletop.MahjongTile(c, tabletop.Tile{Family: tabletop.Winds, Rank: 1}, tabletop.MahjongTileOptions{})
|
||
tabletop.MahjongTile(c, tabletop.Tile{Family: tabletop.Dragons, Rank: 1}, tabletop.MahjongTileOptions{})
|
||
tabletop.MahjongTile(c, tabletop.Tile{Family: tabletop.Bamboo, Rank: 2}, tabletop.MahjongTileOptions{Back: true})
|
||
})
|
||
if _, _, w, h := dots(find(t, tt, "3 | 5")); w != 20 || h != 39 {
|
||
t.Fatalf("a standing domino is %d×%d", w, h)
|
||
}
|
||
for _, label := range []string{"3 DOTS", "EAST WIND", "TILE BACK"} {
|
||
find(t, tt, label)
|
||
}
|
||
x, y, w, h := dots(find(t, tt, "RED DRAGON"))
|
||
if count(tt.Image(), x, y, w, h, pixel.Bad) == 0 {
|
||
t.Fatal("the red dragon is not red")
|
||
}
|
||
}
|
||
|
||
func TestDiceShowPips(t *testing.T) {
|
||
for v := 2; v <= 6; v++ {
|
||
tt := view(200, 200, newWorld(), func(c *ui.Context) { tabletop.Dice(c, "Die", v, tabletop.DiceOptions{}) })
|
||
x, y, w, h := dots(find(t, tt, "Die"))
|
||
// Each pip is two dots square, in Ink.
|
||
if n := count(tt.Image(), x, y, w, h, pixel.Ink); n != 4*v {
|
||
t.Errorf("a %d shows %d Ink dots", v, n)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestWhitePiecesLookLight(t *testing.T) {
|
||
var board [64]tabletop.Piece
|
||
board[1] = tabletop.Piece{Kind: tabletop.Pawn, White: true}
|
||
board[3] = tabletop.Piece{Kind: tabletop.Pawn}
|
||
tt := view(600, 400, newWorld(), func(c *ui.Context) { tabletop.Chessboard(c, "B", &board, tabletop.ChessboardOptions{}) })
|
||
x, y, _, _ := dots(find(t, tt, "CHESSBOARD"))
|
||
// On dark squares, a white pawn takes the lighter slot.
|
||
white, black := count(tt.Image(), x+16, y, 16, 16, pixel.Lit), count(tt.Image(), x+48, y, 16, 16, pixel.Lit)
|
||
if white <= black {
|
||
t.Fatalf("the white pawn has %d Lit dots, the black %d", white, black)
|
||
}
|
||
}
|
||
|
||
func TestEveryTileAndDominoLooksItsOwn(t *testing.T) {
|
||
seen := map[string]tabletop.Tile{}
|
||
for f, ranks := range []int{9, 9, 9, 4, 3} {
|
||
for r := 1; r <= ranks; r++ {
|
||
tile := tabletop.Tile{Family: tabletop.Family(f), Rank: r}
|
||
pix := string(view(100, 100, newWorld(), func(c *ui.Context) { tabletop.MahjongTile(c, tile, tabletop.MahjongTileOptions{}) }).Image().Pix)
|
||
if other, ok := seen[pix]; ok {
|
||
t.Errorf("%+v looks like %+v", tile, other)
|
||
}
|
||
seen[pix] = tile
|
||
}
|
||
}
|
||
ink := func(a, b int) int {
|
||
tt := view(200, 100, newWorld(), func(c *ui.Context) { tabletop.Domino(c, a, b, tabletop.DominoOptions{}) })
|
||
x, y, w, h := dots(find(t, tt, fmt.Sprintf("%d | %d", a, b)))
|
||
return count(tt.Image(), x, y, w, h, pixel.Ink)
|
||
}
|
||
// Two dots square a pip, as on dice.
|
||
if blank, two, six := ink(0, 0), ink(1, 1), ink(6, 6); blank != 0 || two != 8 || six != 48 {
|
||
t.Errorf("0|0, 1|1 and 6|6 show %d, %d and %d Ink dots", blank, two, six)
|
||
}
|
||
}
|