Add tabletop components 486-500 and ten game icons
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.
This commit is contained in:
32 files changed
+2781
-1
No files matched your search
@@ -0,0 +1,127 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/tabletop"
|
||||
)
|
||||
|
||||
var parlorEntries = []Entry{
|
||||
{494, "Domino", "多米诺", "tabletop", "Two halves, zero to six pips. Lay them flat or on end.", demoDomino},
|
||||
{495, "Roulette", "轮盘", "tabletop", "SPIN and the wheel runs a lap before it stops. The house is green.", demoRoulette},
|
||||
{496, "CoinFlip", "抛硬币", "tabletop", "Heads or tails, turned in the air frame by frame.", demoCoinFlip},
|
||||
{497, "BingoCard", "宾果卡", "tabletop", "Mark your numbers; five in a line shouts BINGO!", demoBingoCard},
|
||||
{498, "TicTacToe", "井字棋", "tabletop", "You are X. The computer is a decent O, not a genius.", demoTicTacToe},
|
||||
{499, "ScoreTrack", "计分轨道", "tabletop", "A winding track of spaces; each player's meeple marks the score.", demoScoreTrack},
|
||||
{500, "Meeple", "棋子", "tabletop", "A player's token in its slot. Number five hundred: thank you for scrolling!", demoMeeple},
|
||||
}
|
||||
|
||||
type parlorDemo struct {
|
||||
spins, result, flips int
|
||||
heads bool
|
||||
marked [25]bool
|
||||
scores []int
|
||||
turn int
|
||||
}
|
||||
|
||||
// prizes ring the demo wheel; the house takes the first.
|
||||
var prizes = []string{"HOUSE", "50", "100", "1UP", "200", "LOSE", "500", "x2", "300", "BOMB", "1000", "?"}
|
||||
|
||||
func parlorState(c *ui.Context) *parlorDemo {
|
||||
return ui.Local(c.Root(), demoKey("parlor"), func() parlorDemo {
|
||||
return parlorDemo{result: 3, heads: true, scores: []int{12, 7, 12}}
|
||||
})
|
||||
}
|
||||
|
||||
func demoDomino(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
ui.Row(c).Gap(k.D(6)).AlignItems(ui.Center).Children(func() {
|
||||
tabletop.Domino(c, 6, 6, tabletop.DominoOptions{})
|
||||
tabletop.Domino(c, 3, 5, tabletop.DominoOptions{})
|
||||
tabletop.Domino(c, 0, 1, tabletop.DominoOptions{Vertical: true})
|
||||
tabletop.Domino(c, 2, 4, tabletop.DominoOptions{Vertical: true})
|
||||
})
|
||||
}
|
||||
|
||||
func demoRoulette(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := parlorState(c)
|
||||
rng := tableState(c).rng
|
||||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
|
||||
tabletop.Roulette(c, "Wheel", prizes, s.result, tabletop.RouletteOptions{Cue: s.spins})
|
||||
if input.Button(c, "SPIN", input.ButtonOptions{Size: input.Small}).Clicked {
|
||||
s.result, s.spins = rng.IntN(len(prizes)), s.spins+1
|
||||
log("landed on " + prizes[s.result])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoCoinFlip(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := parlorState(c)
|
||||
rng := tableState(c).rng
|
||||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
|
||||
tabletop.CoinFlip(c, "Coin", s.heads, tabletop.CoinFlipOptions{Cue: s.flips})
|
||||
if input.Button(c, "FLIP", input.ButtonOptions{Size: input.Small}).Clicked {
|
||||
s.heads, s.flips = rng.IntN(2) == 0, s.flips+1
|
||||
log(fmt.Sprintf("heads %v", s.heads))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoBingoCard(c *ui.Context, log func(string)) {
|
||||
s := parlorState(c)
|
||||
numbers := [25]int{}
|
||||
for i := range numbers {
|
||||
numbers[i] = i%5*15 + 1 + (i/5*7)%15
|
||||
}
|
||||
r := tabletop.BingoCard(c, "Card", numbers, &s.marked, tabletop.BingoCardOptions{})
|
||||
if r.Changed {
|
||||
log("marked")
|
||||
}
|
||||
if r.Bingo {
|
||||
log("BINGO!")
|
||||
}
|
||||
}
|
||||
|
||||
func demoTicTacToe(c *ui.Context, log func(string)) {
|
||||
r := tabletop.TicTacToe(c, "Grid", tabletop.TicTacToeOptions{})
|
||||
if r.Played >= 0 {
|
||||
log(fmt.Sprintf("X takes %d", r.Played))
|
||||
}
|
||||
if r.Over {
|
||||
log([]string{"a draw", "you won", "the computer won"}[r.Winner])
|
||||
}
|
||||
if r.Restarted {
|
||||
log("a new game")
|
||||
}
|
||||
}
|
||||
|
||||
func demoScoreTrack(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := parlorState(c)
|
||||
rng := tableState(c).rng
|
||||
ui.Column(c).Gap(k.D(6)).Children(func() {
|
||||
tabletop.ScoreTrack(c, "Scores", s.scores, tabletop.ScoreTrackOptions{})
|
||||
if input.Button(c, "TAKE A TURN", input.ButtonOptions{Size: input.Small}).Clicked {
|
||||
gain := rng.IntN(6) + 1
|
||||
s.scores[s.turn] += gain
|
||||
log(fmt.Sprintf("player %d +%d", s.turn+1, gain))
|
||||
s.turn = (s.turn + 1) % len(s.scores)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoMeeple(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
ui.Row(c).Gap(k.D(4)).Children(func() {
|
||||
for i := range 6 {
|
||||
tabletop.Meeple(c, fmt.Sprintf("Player %d", i+1), tabletop.MeepleOptions{Player: i})
|
||||
}
|
||||
tabletop.Meeple(c, "Open seat", tabletop.MeepleOptions{Empty: true})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/tabletop"
|
||||
)
|
||||
|
||||
var tabletopEntries = []Entry{
|
||||
{486, "Dice", "骰子", "tabletop", "A d6 with pips. ROLL and it tumbles before it lands.", demoDice},
|
||||
{487, "DiceTray", "骰盘", "tabletop", "Several dice on felt, and what they add up to.", demoDiceTray},
|
||||
{488, "PlayingCard", "扑克牌", "tabletop", "Rank and suit, court cards with their pieces, or the back.", demoPlayingCard},
|
||||
{489, "CardHand", "手牌", "tabletop", "A fanned hand. The card you point at or choose lifts; a click plays it.", demoCardHand},
|
||||
{490, "Deck", "牌堆", "tabletop", "A face-down pile with its count. Click to draw.", demoDeck},
|
||||
{491, "Chessboard", "棋盘", "tabletop", "Click a piece, then a square. The rules are on the honor system.", demoChessboard},
|
||||
{492, "GoBoard", "围棋盘", "tabletop", "Black, then White, a stone a click. 9, 13 or 19 lines.", demoGoBoard},
|
||||
{493, "MahjongTile", "麻将牌", "tabletop", "Dots, bamboo, characters, the four winds and three dragons.", demoMahjongTile},
|
||||
}
|
||||
|
||||
type tableDemo struct {
|
||||
rng *rand.Rand
|
||||
die, rolls int
|
||||
tray []int
|
||||
hand []tabletop.Card
|
||||
left int
|
||||
drawn tabletop.Card
|
||||
board [64]tabletop.Piece
|
||||
stones []tabletop.Stone
|
||||
}
|
||||
|
||||
func tableState(c *ui.Context) *tableDemo {
|
||||
return ui.Local(c.Root(), demoKey("tabletop"), func() tableDemo {
|
||||
return tableDemo{rng: rand.New(rand.NewPCG(8, 16)), die: 6, tray: []int{3, 5, 2}, hand: deal(), left: 52,
|
||||
board: chessStart(), stones: make([]tabletop.Stone, 81)}
|
||||
})
|
||||
}
|
||||
|
||||
// deal is the demo's five-card hand.
|
||||
func deal() []tabletop.Card {
|
||||
return []tabletop.Card{{Rank: 1, Suit: tabletop.Spades}, {Rank: 10, Suit: tabletop.Hearts}, {Rank: 7, Suit: tabletop.Clubs},
|
||||
{Rank: 12, Suit: tabletop.Diamonds}, {Rank: 13, Suit: tabletop.Hearts}}
|
||||
}
|
||||
|
||||
// chessStart is the opening position, Black at the top.
|
||||
func chessStart() (b [64]tabletop.Piece) {
|
||||
back := []tabletop.Kind{tabletop.Rook, tabletop.Knight, tabletop.Bishop, tabletop.Queen, tabletop.King, tabletop.Bishop, tabletop.Knight, tabletop.Rook}
|
||||
for i, kind := range back {
|
||||
b[i], b[56+i] = tabletop.Piece{Kind: kind}, tabletop.Piece{Kind: kind, White: true}
|
||||
b[8+i], b[48+i] = tabletop.Piece{Kind: tabletop.Pawn}, tabletop.Piece{Kind: tabletop.Pawn, White: true}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func demoDice(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := tableState(c)
|
||||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
|
||||
tabletop.Dice(c, "Die", s.die, tabletop.DiceOptions{Cue: s.rolls})
|
||||
if input.Button(c, "ROLL", input.ButtonOptions{Size: input.Small}).Clicked {
|
||||
s.die, s.rolls = s.rng.IntN(6)+1, s.rolls+1
|
||||
log(fmt.Sprintf("rolled a %d", s.die))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoDiceTray(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := tableState(c)
|
||||
ui.Column(c).Gap(k.D(6)).Children(func() {
|
||||
tabletop.DiceTray(c, "Dice", s.tray, tabletop.DiceTrayOptions{Cue: s.rolls})
|
||||
if input.Button(c, "ROLL ALL", input.ButtonOptions{Size: input.Small}).Clicked {
|
||||
total := 0
|
||||
for i := range s.tray {
|
||||
s.tray[i] = s.rng.IntN(6) + 1
|
||||
total += s.tray[i]
|
||||
}
|
||||
s.rolls++
|
||||
log(fmt.Sprintf("rolled %d", total))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoPlayingCard(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
ui.Row(c).Gap(k.D(4)).Children(func() {
|
||||
for _, cd := range deal()[:4] {
|
||||
tabletop.PlayingCard(c, cd, tabletop.PlayingCardOptions{})
|
||||
}
|
||||
tabletop.PlayingCard(c, tabletop.Card{Rank: 1}, tabletop.PlayingCardOptions{Back: true})
|
||||
})
|
||||
}
|
||||
|
||||
func demoCardHand(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := tableState(c)
|
||||
ui.Column(c).Gap(k.D(6)).Children(func() {
|
||||
if i := tabletop.CardHand(c, "Hand", s.hand, tabletop.CardHandOptions{}).Played; i >= 0 {
|
||||
log(fmt.Sprintf("played the %d of suit %d", s.hand[i].Rank, s.hand[i].Suit))
|
||||
s.hand = append(s.hand[:i:i], s.hand[i+1:]...)
|
||||
}
|
||||
if input.Button(c, "DEAL", input.ButtonOptions{Size: input.Small}).Clicked {
|
||||
s.hand = deal()
|
||||
log("a fresh hand")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoDeck(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := tableState(c)
|
||||
ui.Row(c).Gap(k.D(8)).AlignItems(ui.End).Children(func() {
|
||||
if tabletop.Deck(c, "Draw pile", s.left, tabletop.DeckOptions{}).Drawn {
|
||||
s.left--
|
||||
s.drawn = tabletop.Card{Rank: s.rng.IntN(13) + 1, Suit: tabletop.Suit(s.rng.IntN(4))}
|
||||
log(fmt.Sprintf("drew a card, %d left", s.left))
|
||||
}
|
||||
if s.drawn.Rank > 0 {
|
||||
tabletop.PlayingCard(c, s.drawn, tabletop.PlayingCardOptions{})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoChessboard(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
s := tableState(c)
|
||||
ui.Column(c).Gap(k.D(6)).Children(func() {
|
||||
r := tabletop.Chessboard(c, "Board", &s.board, tabletop.ChessboardOptions{})
|
||||
if r.Changed {
|
||||
log(fmt.Sprintf("holding square %d", r.Held))
|
||||
}
|
||||
if r.Moved {
|
||||
log(fmt.Sprintf("moved from square %d to %d", r.From, r.To))
|
||||
}
|
||||
if input.Button(c, "NEW GAME", input.ButtonOptions{Size: input.Small}).Clicked {
|
||||
s.board = chessStart()
|
||||
log("pieces reset")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func demoGoBoard(c *ui.Context, log func(string)) {
|
||||
s := tableState(c)
|
||||
if i := tabletop.GoBoard(c, "Goban", s.stones, tabletop.GoBoardOptions{}).Placed; i >= 0 {
|
||||
log(fmt.Sprintf("stone at %d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func demoMahjongTile(c *ui.Context, log func(string)) {
|
||||
k := pixel.K(c)
|
||||
tiles := []tabletop.Tile{
|
||||
{Family: tabletop.Dots, Rank: 1}, {Family: tabletop.Dots, Rank: 5}, {Family: tabletop.Dots, Rank: 9},
|
||||
{Family: tabletop.Bamboo, Rank: 3}, {Family: tabletop.Bamboo, Rank: 8}, {Family: tabletop.Characters, Rank: 7},
|
||||
{Family: tabletop.Winds, Rank: 1}, {Family: tabletop.Winds, Rank: 4}, {Family: tabletop.Dragons, Rank: 1},
|
||||
{Family: tabletop.Dragons, Rank: 2}, {Family: tabletop.Dragons, Rank: 3},
|
||||
}
|
||||
ui.Row(c).Gap(k.D(2)).Wrap().Width(k.D(300)).Children(func() {
|
||||
for _, t := range tiles {
|
||||
tabletop.MahjongTile(c, t, tabletop.MahjongTileOptions{})
|
||||
}
|
||||
tabletop.MahjongTile(c, tiles[0], tabletop.MahjongTileOptions{Back: true})
|
||||
})
|
||||
}
|
||||
+1
-1
@@ -56,7 +56,7 @@ var Categories = []Category{
|
||||
{"tabletop", "Tabletop", "桌游", icon.Crown},
|
||||
}
|
||||
|
||||
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries, chatEntries, mailEntries, mediaEntries, studioEntries, codeEntries, debugEntries, accountEntries, playerEntries, filesEntries, storageEntries, projectEntries, teamEntries, shopEntries, marketEntries, agentEntries, guildEntries, deskEntries, retroEntries)
|
||||
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries, chatEntries, mailEntries, mediaEntries, studioEntries, codeEntries, debugEntries, accountEntries, playerEntries, filesEntries, storageEntries, projectEntries, teamEntries, shopEntries, marketEntries, agentEntries, guildEntries, deskEntries, retroEntries, tabletopEntries, parlorEntries)
|
||||
|
||||
func Entries() []Entry { return entries }
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTabletopDemosLogTheirActions(t *testing.T) {
|
||||
// Focusing, or a move that ends nothing, logs nothing.
|
||||
quiet := map[string]bool{"key:Right": true}
|
||||
for _, tc := range []struct {
|
||||
path string
|
||||
steps []string
|
||||
}{
|
||||
{"/c/486-dice", []string{"ROLL"}}, {"/c/487-dicetray", []string{"ROLL ALL"}},
|
||||
{"/c/489-cardhand", []string{"Hand", "DEAL"}}, {"/c/490-deck", []string{"Draw pile"}},
|
||||
{"/c/491-chessboard", []string{"square:52", "NEW GAME"}}, {"/c/492-goboard", []string{"Goban"}},
|
||||
{"/c/495-roulette", []string{"SPIN"}}, {"/c/496-coinflip", []string{"FLIP"}},
|
||||
{"/c/497-bingocard", []string{"Card", "key:Right", "key:Enter"}}, {"/c/498-tictactoe", []string{"Grid"}},
|
||||
{"/c/499-scoretrack", []string{"TAKE A TURN"}},
|
||||
} {
|
||||
a, tt := open(tc.path)
|
||||
slug := tc.path[3:]
|
||||
for _, step := range tc.steps {
|
||||
before := len(a.logs[slug])
|
||||
if k, ok := strings.CutPrefix(step, "key:"); ok {
|
||||
tt.Key(0, letters[k])
|
||||
} else if sq, ok := strings.CutPrefix(step, "square:"); ok {
|
||||
// A chess square: 16 dots, two pixels a dot.
|
||||
r, ok := tt.Find("CHESSBOARD")
|
||||
i, err := strconv.Atoi(sq)
|
||||
if !ok || err != nil {
|
||||
t.Fatalf("no board, or square %q: %v", sq, err)
|
||||
}
|
||||
tt.ClickAt(r.X+float32(32*(i%8)+16), r.Y+float32(32*(i/8)+16))
|
||||
} else {
|
||||
click(t, tt, step)
|
||||
}
|
||||
if len(a.logs[slug]) <= before && !quiet[step] && step != "Card" {
|
||||
t.Errorf("%s: %s logged nothing: %q", slug, step, a.logs[slug])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user