fire.gno
11.76 Kb · 267 lines
1package engine
2
3import (
4 "strconv"
5 "strings"
6)
7
8// HitLoc is a hit location on a FIRE card's hit chart (rule 13.5).
9type HitLoc string
10
11const (
12 Miss HitLoc = "-"
13 Bullseye HitLoc = "BE" // the shooter picks the location (13.7)
14 LegHit HitLoc = "LEG"
15 GunHit HitLoc = "GUN" // the gun arm
16 ArmHit HitLoc = "ARM" // the other arm
17 SideHit HitLoc = "SIDE" // a flesh wound
18 BodyHit HitLoc = "BODY"
19 CritHit HitLoc = "CRIT"
20 VitalHit HitLoc = "VITAL"
21 HeadHit HitLoc = "HEAD"
22)
23
24// Target statuses a FIRE card may list (rule 13.6), as the letters of a
25// card's status string.
26const (
27 StatusMove = 'M' // played a foot action or dropped this turn (13.61)
28 StatusRun = 'R' // played a Run this turn (13.62)
29 StatusDown = 'D' // is DOWN (13.63)
30 StatusHead = 'H' // head counter shot at (13.64, not played)
31)
32
33// Band is an inclusive range of aim times or ranges; Hi < 0 is open-ended.
34type Band struct{ Lo, Hi int }
35
36// Contains reports whether n falls in the band.
37func (b Band) Contains(n int) bool { return n >= b.Lo && (b.Hi < 0 || n <= b.Hi) }
38
39// Chart is the hit chart of a FIRE card: a hit location per aim-time row
40// and range column.
41type Chart struct {
42 Cols []Band
43 Rows []ChartRow
44}
45
46// ChartRow is one aim-time row of a Chart.
47type ChartRow struct {
48 Aim Band
49 Cells []HitLoc
50}
51
52// Lookup is the preliminary hit location for an aim time and a range; off
53// the chart is a miss (rule 13.5).
54func (c *Chart) Lookup(aim, rng int) HitLoc {
55 col := -1
56 for i, b := range c.Cols {
57 if b.Contains(rng) {
58 col = i
59 break
60 }
61 }
62 if col < 0 {
63 return Miss
64 }
65 for _, r := range c.Rows {
66 if r.Aim.Contains(aim) {
67 return r.Cells[col]
68 }
69 }
70 return Miss
71}
72
73// Lesser is the hit the card's Target Status entries name: the chart's hit
74// location other than a bullseye (every card has exactly one).
75func (c *Chart) Lesser() HitLoc {
76 for _, r := range c.Rows {
77 for _, h := range r.Cells {
78 if h != Miss && h != Bullseye {
79 return h
80 }
81 }
82 }
83 return Miss
84}
85
86// Malfunction is the upper block of cards 101-108 (rule 13.3): whether the
87// shot misfires per shell type, and the gun's fate on a second malfunction
88// drawn after a misfire (13.31).
89type Malfunction struct {
90 Storebought bool // misfire with storebought shells
91 Handloaded bool // misfire with handloaded shells (the Colt 45's)
92 CapAndBall bool // misfire with cap-and-ball shells
93 Second string
94}
95
96// Second-draw penalties.
97const (
98 GunJams = "jams" // all shells crossed off; cannot fire until reloaded
99 GunExplodes = "explodes" // cannot fire for the rest of the game
100)
101
102// fireCard is the transcription of a FIRE card's upper block: the hit chart
103// as "<range bands>|<aim band>:<cell per column>|..." (bands "n", "n-m",
104// "n+" or "n-m+") and the Target Status letters (M move, R run, D down,
105// H head), each of which turns the card's lesser hit into a miss and, for
106// M and R, a bullseye into the lesser hit.
107type fireCard struct {
108 chart, status string
109}
110
111// fireCards are cards 1 to 100, from the card images (assets/result_card_NNN.png).
112var fireCards = [100]fireCard{
113 {"0,1-5,6-12+|2-5:BE,-,-|6-8:BE,SIDE,-|9+:BE,BE,SIDE", "H"}, // 1
114 {"0,1-5,6-9|3-6:BE,-,-|7:BE,SIDE,-|8+:BE,BE,SIDE", "M"}, // 2
115 {"0-1,2-4|1-3:SIDE,-|4+:BE,SIDE", "H"}, // 3
116 {"0-2,3-7|1-6:SIDE,-|7+:BE,SIDE", "H"}, // 4
117 {"0-5,6-8|2-4:SIDE,-|5+:BE,SIDE", "D"}, // 5
118 {"0,1|3-4:BE,-|5-7:BE,SIDE|8+:BE,BE", "H"}, // 6
119 {"0,1-3|1-4:SIDE,-|5:BE,SIDE|6+:BE,BE", "H"}, // 7
120 {"0,1-4|2-6:BE,-|7+:BE,SIDE", ""}, // 8
121 {"0-1,2-6|1-5:SIDE,-|6+:BE,SIDE", "D"}, // 9
122 {"0-3,4-5|2-6:SIDE,-|7-8:BE,-|9+:BE,SIDE", "H"}, // 10
123 {"0-2,3-6|4-8:SIDE,-|9+:BE,BE", ""}, // 11
124 {"0-1,2-3,4-5|3-4:SIDE,SIDE,-|5-8:BE,SIDE,SIDE|9+:BE,BE,SIDE", "D"}, // 12
125 {"0,1-3,4-7|6-8:BE,SIDE,-|9+:BE,BE,SIDE", ""}, // 13
126 {"0-7|4-6:SIDE|7+:BE", "M"}, // 14
127 {"0,1,2-5|1-2:BE,-,-|3-7:BE,BE,-|8-9:BE,BE,SIDE", "D"}, // 15
128 {"0-4|1-3:SIDE|4+:BE", "D"}, // 16
129 {"0-5,6-11|8:SIDE,-|9+:BE,BE", ""}, // 17
130 {"0-6|3-5:SIDE|6+:BE", ""}, // 18
131 {"0,1|3-4:BE,-|5-7:BE,BODY|8+:BE,BE", ""}, // 19
132 {"0,1-5,6-12+|2-5:BE,-,-|6-8:BE,BODY,-|9+:BE,BE,BODY", ""}, // 20
133 {"0-1,2,3|1-2:BODY,-,-|3-8:BE,BODY,-|9+:BE,BE,BODY", ""}, // 21
134 {"0,1-3|2-6:BODY,-|7+:BE,BE", ""}, // 22
135 {"0-3,4-7|2-7:BODY,-|8+:BE,BODY", ""}, // 23
136 {"0-4,5-10|4-6:BODY,-|7:BE,BODY|8+:BE,BE", ""}, // 24
137 {"0-5,6-8|2-4:BODY,-|5+:BE,BODY", "D"}, // 25
138 {"0-6|3-5:BODY|6+:BE", ""}, // 26
139 {"0-1,2,3-5|1-2:BE,BODY,BODY|3-4:BE,BE,BODY|5+:BE,BE,BE", "R"}, // 27
140 {"0-2,3-9|2-8:BODY,-|9+:BE,BE", ""}, // 28
141 {"0,1-4|2-6:BE,-|7+:BE,BODY", "H"}, // 29
142 {"0-1,2,3|8:BE,BODY,-|9+:BE,BE,BODY", ""}, // 30
143 {"0,1-3|1-4:BODY,-|5:BE,BODY|6+:BE,BE", "H"}, // 31
144 {"0-5,6-11|8:BODY,-|9+:BE,BE", ""}, // 32
145 {"0-1,2-3,4|2-7:BE,-,-|8:BE,BODY,-|9+:BE,BE,BODY", "H"}, // 33
146 {"0,1-2,3-6|1-2:BODY,-,-|3-7:BE,BODY,-|8+:BE,BE,BODY", "H"}, // 34
147 {"0,1-4,5-8|1-5:BE,-,-|6-8:BE,BODY,-|9+:BE,BE,BODY", "H"}, // 35
148 {"0,1-5,6-9|3-6:BE,-,-|7:BE,BODY,-|8+:BE,BE,BODY", "H"}, // 36
149 {"0,1-2,3-4|1-4:BODY,-,-|5+:BE,BE,BODY", "DH"}, // 37
150 {"0-4|3-7:BODY|8+:BE", "D"}, // 38
151 {"0-1,2-3,4-5|3-4:BODY,BODY,-|5-8:BE,BODY,BODY|9+:BE,BE,BODY", "D"}, // 39
152 {"0-2,3-7|1-6:BODY,-|7+:BE,BODY", "MH"}, // 40
153 {"0-1,2-6|1-5:BODY,-|6+:BE,BODY", "H"}, // 41
154 {"0-1,2,3|8:BE,BODY,-|9+:BE,BE,BODY", ""}, // 42
155 {"0-2,3-4|5+:BE,BODY", "H"}, // 43
156 {"0,1-4|2-6:BE,-|7+:BE,CRIT", "R"}, // 44
157 {"0-3,4-9|1-5:CRIT,-|6+:BE,CRIT", "DH"}, // 45
158 {"0,1-2,3-6|1-2:CRIT,-,-|3-7:BE,CRIT,-|8+:BE,BE,CRIT", "DH"}, // 46
159 {"0-5,6-11|2-7:CRIT,-|8+:BE,CRIT", ""}, // 47
160 {"0-1,2-3|4-7:CRIT,-|8+:BE,BE", "M"}, // 48
161 {"0,1,2-4|1-2:BE,-,-|3+:BE,BE,CRIT", "D"}, // 49
162 {"0-1,2-5|3-7:CRIT,-|8+:BE,CRIT", "H"}, // 50
163 {"0-3,4-6|5+:BE,CRIT", "MD"}, // 51
164 {"0-1,2-3,4|2-3:BE,-,-|4-8:BE,CRIT,-|9+:BE,BE,CRIT", "D"}, // 52
165 {"0-3,4-5|2-6:CRIT,-|7-8:BE,-|9+:BE,CRIT", "H"}, // 53
166 {"0,1-3|2-6:CRIT,-|7+:BE,BE", "M"}, // 54
167 {"0-1,2,3|1-2:CRIT,-,-|3-8:BE,CRIT,-|9+:BE,BE,CRIT", "M"}, // 55
168 {"0,1-3,4-7|6-8:BE,CRIT,-|9+:BE,BE,CRIT", "R"}, // 56
169 {"0-2,3-9|2-8:CRIT,-|9+:BE,BE", "M"}, // 57
170 {"0-7|4-6:CRIT|7+:BE", "H"}, // 58
171 {"0,1-2,3-6|1-4:VITAL,-,-|5-8:BE,BE,-|9+:BE,BE,BE", "M"}, // 59
172 {"0-2,3,4-7|2-5:BE,VITAL,-|6-8:BE,VITAL,VITAL|9+:BE,BE,BE", "R"}, // 60
173 {"0-1,2,3-4|1-2:BE,VITAL,-|3+:BE,VITAL,VITAL", "M"}, // 61
174 {"0-1,2,3-5|3:VITAL,-,-|4-8:BE,VITAL,-|9+:BE,BE,VITAL", "DH"}, // 62
175 {"0-2,3-4|2-4:VITAL,-|5-6:VITAL,VITAL|7+:BE,VITAL", "DH"}, // 63
176 {"0-1,2-3,4-7|1-5:VITAL,-,-|6-7:VITAL,VITAL,-|8+:BE,BE,VITAL", "H"}, // 64
177 {"0-1,2,3-6|4-6:VITAL,-,-|7-8:BE,VITAL,VITAL|9+:BE,BE,VITAL", "M"}, // 65
178 {"0-1,2-5|1-5:BE,-|6-8:BE,HEAD|9+:BE,BE", "M"}, // 66
179 {"0,1-4,5-8|1-5:BE,-,-|6-8:BE,HEAD,-|9+:BE,BE,HEAD", ""}, // 67
180 {"0-1,2-6|3:HEAD,-|4-6:BE,-|7+:BE,HEAD", "R"}, // 68
181 {"0-2,3-5|1-6:HEAD,-|7-8:BE,-|9+:BE,HEAD", "R"}, // 69
182 {"0-3|4-7:HEAD|8+:BE", ""}, // 70
183 {"0,1-4|2-6:BE,-|7+:BE,HEAD", ""}, // 71
184 {"0-1,2-4|1-3:HEAD,-|4+:BE,HEAD", "M"}, // 72
185 {"0-2,3-5,6+|1-4:BE,LEG,LEG|5+:BE,BE,LEG", "H"}, // 73
186 {"0-5,6-11|2-7:LEG,-|8+:BE,LEG", "RH"}, // 74
187 {"0-3,4-6|5+:BE,LEG", "MD"}, // 75
188 {"0-3,4-9|1-5:LEG,-|6+:BE,LEG", "MH"}, // 76
189 {"0-2,3-4|2:BE,-|3+:BE,LEG", "MH"}, // 77
190 {"0-2,3-4|5+:BE,LEG", "H"}, // 78
191 {"0-4|1-3:LEG|4+:BE", "DH"}, // 79
192 {"0-4,5-10|4-6:LEG,-|7:BE,LEG|8+:BE,BE", "DH"}, // 80
193 {"0,1-2|1-3:GUN,-|4-7:BE,GUN|8+:BE,BE", ""}, // 81
194 {"0-3,4-5,6-7|3-8:BE,GUN,-|9+:BE,BE,BE", "M"}, // 82
195 {"0-1,2-8|2-7:GUN,-|8+:BE,BE", "R"}, // 83
196 {"0-1|3-8:GUN|9+:BE", ""}, // 84
197 {"0-3,4-5|5-8:GUN,-|9+:BE,GUN", ""}, // 85
198 {"0-3,4-5|1-6:GUN,-|7+:BE,GUN", "MD"}, // 86
199 {"0-2,3-6|1-5:GUN,-|6-7:BE,-|8+:BE,GUN", "R"}, // 87
200 {"0-1,2-3|5-6:BE,-|7+:BE,GUN", ""}, // 88
201 {"0-4|2-8:GUN|9+:BE", "R"}, // 89
202 {"0-2,3-6|4+:BE,GUN", "RD"}, // 90
203 {"0,1-2|1-3:ARM,-|4-7:BE,ARM|8+:BE,BE", "H"}, // 91
204 {"0-3,4-5,6-7|3-8:BE,ARM,-|9+:BE,BE,BE", "MH"}, // 92
205 {"0-1,2-8|2-7:ARM,-|8+:BE,BE", "RH"}, // 93
206 {"0-1|3-8:ARM|9+:BE", "H"}, // 94
207 {"0-3,4-5|5-8:ARM,-|9+:BE,ARM", "H"}, // 95
208 {"0-3,4-7|1-6:ARM,-|8+:BE,ARM", "MDH"}, // 96
209 {"0-2,3-6|1-5:ARM,-|6-7:BE,-|8+:BE,ARM", "RH"}, // 97
210 {"0-1,2-3|5-6:BE,-|7+:BE,ARM", "H"}, // 98
211 {"0-4|2-8:ARM|9+:BE", "RH"}, // 99
212 {"0-2,3-6|4+:BE,ARM", "RDH"}, // 100
213}
214
215// malfunctions are the blocks of cards 101 to 108.
216var malfunctions = [8]Malfunction{
217 {false, false, true, GunJams}, // 101
218 {false, false, true, GunJams}, // 102
219 {false, false, true, GunJams}, // 103
220 {true, true, true, GunJams}, // 104
221 {true, true, true, GunJams}, // 105
222 {true, true, true, GunJams}, // 106
223 {false, true, true, GunExplodes}, // 107
224 {false, true, true, GunExplodes}, // 108
225}
226
227func parseChart(s string) *Chart {
228 parts := strings.Split(s, "|")
229 c := &Chart{}
230 for _, b := range strings.Split(parts[0], ",") {
231 c.Cols = append(c.Cols, parseBand(b))
232 }
233 for _, row := range parts[1:] {
234 i := strings.IndexByte(row, ':')
235 r := ChartRow{Aim: parseBand(row[:i])}
236 for _, cell := range strings.Split(row[i+1:], ",") {
237 r.Cells = append(r.Cells, HitLoc(cell))
238 }
239 if len(r.Cells) != len(c.Cols) {
240 panic("fire chart row/column mismatch: " + s)
241 }
242 c.Rows = append(c.Rows, r)
243 }
244 return c
245}
246
247func parseBand(s string) Band {
248 open := strings.HasSuffix(s, "+")
249 s = strings.TrimSuffix(s, "+")
250 lo, hi := s, s
251 if i := strings.IndexByte(s, '-'); i >= 0 {
252 lo, hi = s[:i], s[i+1:]
253 }
254 b := Band{Lo: atoi(lo), Hi: atoi(hi)}
255 if open {
256 b.Hi = -1
257 }
258 return b
259}
260
261func atoi(s string) int {
262 n, err := strconv.Atoi(s)
263 if err != nil {
264 panic("fire chart: bad number " + strconv.Quote(s))
265 }
266 return n
267}