Files
8bitMyGoUI/tabletop/bingocard.go
T
ZacharyZhang-NY 81003b21ee 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.
2026-10-12 14:03:58 -04:00

124 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}