deck.gno
5.34 Kb · 190 lines
1package engine
2
3import (
4 "crypto/sha256"
5 "encoding/binary"
6 "math/rand"
7 "strings"
8
9 "gno.land/p/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/hex/v0"
10)
11
12// Result is one line of a result card: a number of points, or a named
13// penalty (rules 14.1, 14.21). Points are 0 or more; penalties are negative.
14type Result int
15
16const (
17 // LoseAim: the character loses its aim (no aim is tracked yet).
18 LoseAim Result = -1
19 // WildShot: every cocked gun goes off without hitting anything, is
20 // uncocked and loses a shell; the aim is lost.
21 WildShot Result = -2
22 // Drop: an upright character turns DOWN, draws 3 delay cards and loses
23 // its aim; no effect when already DOWN.
24 Drop Result = -3
25)
26
27// IsPoints reports whether the line is a number of points.
28func (r Result) IsPoints() bool { return r >= 0 }
29
30// String returns the penalty name used in events and JSON, or "" for points.
31func (r Result) String() string {
32 switch r {
33 case LoseAim:
34 return "lose_aim"
35 case WildShot:
36 return "wild_shot"
37 case Drop:
38 return "drop"
39 }
40 return ""
41}
42
43// ResultCard is one of the 108 result cards (rule 2.4): N is its number on
44// the card. Delay and Wound are the DELAY and WOUND lines and Hex the HEX
45// line (direction cards, 14.3: a relative direction, hex.Same or hex.Long).
46// The upper block is either a FIRE hit chart with its Target Status letters
47// (cards 1-100, see fire.gno) or a MALFUNCTION block (cards 101-108). The
48// TAC line (optional rule 9) is not used by this game.
49type ResultCard struct {
50 N int
51 Delay Result
52 Wound Result
53 Hex hex.Rel
54 Fire *Chart // the hit chart of a FIRE card, nil on a MALFUNCTION
55 Status string // Target Status letters of a FIRE card (StatusMove...)
56 Malf *Malfunction // the block of a MALFUNCTION card, nil on a FIRE card
57}
58
59// IsFire reports whether the card is a FIRE card (13.2).
60func (c ResultCard) IsFire() bool { return c.Malf == nil }
61
62// DeckSize is the number of result cards.
63const DeckSize = 108
64
65// Cards is the result deck in card order, index 0 = card 1.
66var Cards = buildCards()
67
68func buildCards() (cards [DeckSize]ResultCard) {
69 // Each row is a run of consecutive cards sharing the same three lines,
70 // transcribed from the card images (assets/result_card_NNN.png).
71 runs := []struct {
72 from, to int
73 delay, wound Result
74 hex hex.Rel
75 }{
76 {1, 18, 0, 0, hex.Same},
77 {19, 36, 0, 1, hex.Long},
78 {37, 54, 1, 2, hex.Long},
79 {55, 60, 1, 3, hex.AheadLeft},
80 {61, 69, 2, 3, hex.Same},
81 {70, 78, 2, 4, hex.Long},
82 {79, 84, 2, 5, hex.AheadRight},
83 {85, 87, 2, 6, hex.Ahead},
84 {88, 93, LoseAim, 1, hex.BackRight},
85 {94, 99, LoseAim, WildShot, hex.BackLeft},
86 {100, 100, WildShot, Drop, hex.Back},
87 {101, 103, WildShot, Drop, hex.Back},
88 {104, 106, 1, Drop, hex.Ahead},
89 {107, 108, Drop, Drop, hex.Back},
90 }
91 n := 0
92 for _, r := range runs {
93 for i := r.from; i <= r.to; i++ {
94 c := ResultCard{N: i, Delay: r.delay, Wound: r.wound, Hex: r.hex}
95 if i <= len(fireCards) {
96 c.Fire = parseChart(fireCards[i-1].chart)
97 c.Status = fireCards[i-1].status
98 } else {
99 c.Malf = &malfunctions[i-1-len(fireCards)]
100 }
101 cards[i-1] = c
102 n++
103 }
104 }
105 if n != DeckSize {
106 panic("result deck: wrong card count")
107 }
108 return cards
109}
110
111// Card returns result card n (1 to 108).
112func Card(n int) ResultCard { return Cards[n-1] }
113
114// Drawer hands out result cards to Resolve.
115type Drawer interface {
116 // Draw returns the next result card.
117 Draw() ResultCard
118}
119
120// Seed hashes the given parts into shuffle seed material.
121func Seed(parts ...string) [32]byte {
122 return sha256.Sum256([]byte(strings.Join(parts, "|")))
123}
124
125// Deck is the result deck shuffled once from a seed and dealt in order
126// (rule 5.7: the deck is reshuffled when exhausted). The same seed always
127// deals the same cards, so a turn can be replayed from its stored seed.
128type Deck struct {
129 r *rand.Rand
130 order [DeckSize]int // card numbers in deal order
131 next int
132 drawn []int // every card dealt so far, in order
133}
134
135// NewDeck shuffles the 108 cards with the seed.
136func NewDeck(seed [32]byte) *Deck {
137 s1 := binary.BigEndian.Uint64(seed[0:8])
138 s2 := binary.BigEndian.Uint64(seed[8:16])
139 d := &Deck{r: rand.New(rand.NewPCG(s1, s2))}
140 d.shuffle()
141 return d
142}
143
144func (d *Deck) shuffle() {
145 for i := range d.order {
146 d.order[i] = i + 1
147 }
148 d.r.Shuffle(DeckSize, func(i, j int) { d.order[i], d.order[j] = d.order[j], d.order[i] })
149 d.next = 0
150}
151
152// Draw implements Drawer: the next card of the shuffle, reshuffling the
153// whole deck once every card has been dealt.
154func (d *Deck) Draw() ResultCard {
155 if d.next >= DeckSize {
156 d.shuffle()
157 }
158 c := Cards[d.order[d.next]-1]
159 d.next++
160 d.drawn = append(d.drawn, c.N)
161 return c
162}
163
164// Order returns the card numbers in the order the deck deals them.
165func (d *Deck) Order() [DeckSize]int { return d.order }
166
167// Drawn returns the numbers of every card dealt so far, in order: the trace
168// of a turn's draws.
169func (d *Deck) Drawn() []int { return d.drawn }
170
171// Fixed is a Drawer that always deals the same card; tests use Fixed(37)
172// (DELAY 1) to match the BGA reference engine's stub.
173type Fixed int
174
175// Draw implements Drawer.
176func (f Fixed) Draw() ResultCard { return Card(int(f)) }
177
178// Sequence is a Drawer dealing the given card numbers in order, then the
179// last one forever; for tests.
180type Sequence []int
181
182// Draw implements Drawer.
183func (s *Sequence) Draw() ResultCard {
184 q := *s
185 n := q[0]
186 if len(q) > 1 {
187 *s = q[1:]
188 }
189 return Card(n)
190}