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:
ZacharyZhang-NY committed 2026-10-12 14:03:58 -04:00
1 parent 7800003418
commit 81003b21ee
32 files changed
+2781 -1

No files matched your search

+123
View File
@@ -0,0 +1,123 @@
package tabletop
import (
"fmt"
"log/slog"
"strconv"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
type BingoCardOptions struct {
Disabled bool
}
type BingoCardResult struct {
Element *ui.Element
// Bingo is the first full line, this frame.
Changed, Bingo bool
}
type bingoKey struct{}
// card is the cursor, and if a line was full.
type card struct {
cur int
line bool
}
const bingoW, bingoH = 18, 16
// BingoCard marks a 5×5 card; a full line is BINGO!
func BingoCard(c *ui.Context, text string, numbers [25]int, marked *[25]bool, o BingoCardOptions) BingoCardResult {
k := pixel.K(c)
// Cells print three digits at most; the middle is FREE.
for i, n := range numbers {
if i != 12 && (n < 1 || n > 999) {
panic(fmt.Sprintf("tabletop: BingoCard number %d at %d", n, i))
}
}
col := ui.Column(c).Gap(k.D(2)).AlignItems(ui.Center).AlignSelf(ui.Start)
st := ui.Local(col, bingoKey{}, func() card { return card{cur: 12} })
// The middle is FREE: always marked.
marked[12] = true
res := BingoCardResult{}
res.Element = col.Children(func() {
ui.Box(c).Size(k.D(5*bingoW), k.D(7)).Role(ui.RoleNone).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
for i, l := range "BINGO" {
g.Micro(i*bingoW+7, 1, string(l), pixel.Accent, 1)
}
})
shell, base, ps, picked := pixel.Pad(c, text, 5, 25, bingoW, bingoH, &st.cur, o.Disabled)
if picked >= 0 && picked != 12 {
marked[picked], res.Changed = !marked[picked], true
}
say := k.Msg("free_space")
if st.cur != 12 {
say = strconv.Itoa(numbers[st.cur])
}
if marked[st.cur] {
say += ", " + k.Msg("marked")
}
base.Description(fmt.Sprintf("%c %s", "BINGO"[st.cur%5], say))
at, shown := st.cur, *marked
shell.Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
for i := range 25 {
x, y := i%5*bingoW, i/5*bingoH
// Cells share their edges; the last ones end inside.
w, h := bingoW+1, bingoH+1
if i%5 == 4 {
w = bingoW
}
if i/5 == 4 {
h = bingoH
}
g.Frame(x, y, w, h, pixel.Flat, pixel.Lit)
if i == at && ps.Focus {
g.Cursor(x+1, y+1, bingoW-1, bingoH-1)
}
if shown[i] && i != 12 {
// A dauber's blot, dithered so the number reads.
g.Disc(x+9, y+8, 6, pixel.Magic)
g.Veil(x+3, y+2, 13, 13, pixel.Lit, 6)
}
// FREE is a star; it fits in any tongue.
if i == 12 {
g.Sprite(x+3, y+2, icon.Star, 0, pixel.Accent, 1)
continue
}
s := strconv.Itoa(numbers[i])
g.Micro(x+(bingoW+1-pixel.MicroWidth(s))/2, y+6, s, pixel.Ink, 1)
}
g.Finish(ps)
})
line := full(marked)
res.Bingo, st.line = line && !st.line, line
if line {
pixel.Text(c, k.Msg("bingo"), pixel.TextOptions{Slot: pixel.Good, Scale: 2})
}
})
if res.Changed || res.Bingo {
slog.Debug("8bit: bingo card", "label", text, "cell", st.cur, "bingo", res.Bingo)
}
return res
}
// full reports a whole row, column or diagonal marked.
func full(m *[25]bool) bool {
lines := [][5]int{{0, 6, 12, 18, 24}, {4, 8, 12, 16, 20}}
for i := range 5 {
lines = append(lines, [5]int{5 * i, 5*i + 1, 5*i + 2, 5*i + 3, 5*i + 4}, [5]int{i, i + 5, i + 10, i + 15, i + 20})
}
for _, l := range lines {
if m[l[0]] && m[l[1]] && m[l[2]] && m[l[3]] && m[l[4]] {
return true
}
}
return false
}