encode.gno
6.75 Kb · 236 lines
1package engine
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/cards/v0"
8 "gno.land/p/g1th0masp67zfs9lg6d34qhghldhg6kjcqjuv7c2/gnoslinger/hex/v0"
9)
10
11// Compact text encodings of the engine's values, for what a realm keeps
12// per turn: a struct stored on chain costs every field its type and
13// value, so a turn's events as structs weigh over ten kilobytes, as one
14// string a tenth of that. Fields are joined with '|', records with ';',
15// gun slots with ',' and gun fields with ':'; none of the values (codes,
16// hex ids, numbers) contain those characters. Decoding is lenient: a
17// malformed number reads as 0, so a damaged record never breaks a view.
18
19// EncodeEvents packs events into one string; DecodeEvents unpacks it.
20func EncodeEvents(evs []Event) string {
21 var b strings.Builder
22 for i, e := range evs {
23 if i > 0 {
24 b.WriteByte(';')
25 }
26 b.WriteString(strings.Join([]string{
27 strconv.Itoa(e.Seg), strconv.Itoa(e.Player), e.Kind, string(e.Action), e.From, e.To,
28 strconv.Itoa(int(e.Facing)), boolStr(e.Down), strconv.Itoa(e.N), strconv.Itoa(e.Delay),
29 e.Reason, strconv.Itoa(e.Card), e.Result, strconv.Itoa(e.Endurance), strconv.Itoa(e.Target),
30 e.Hit, strconv.Itoa(e.Range), string(e.Gun), strconv.Itoa(e.GunID), strconv.Itoa(e.Step),
31 e.Chart, e.Status,
32 }, "|"))
33 }
34 return b.String()
35}
36
37// DecodeEvents unpacks what EncodeEvents produced.
38func DecodeEvents(s string) []Event {
39 if s == "" {
40 return nil
41 }
42 recs := strings.Split(s, ";")
43 evs := make([]Event, 0, len(recs))
44 for _, r := range recs {
45 f := strings.Split(r, "|")
46 if len(f) != 22 {
47 continue
48 }
49 evs = append(evs, Event{
50 Seg: atoi(f[0]), Player: atoi(f[1]), Kind: f[2], Action: cards.Code(f[3]), From: f[4], To: f[5],
51 Facing: hex.Dir(atoi(f[6])), Down: f[7] == "1", N: atoi(f[8]), Delay: atoi(f[9]),
52 Reason: f[10], Card: atoi(f[11]), Result: f[12], Endurance: atoi(f[13]), Target: atoi(f[14]),
53 Hit: f[15], Range: atoi(f[16]), Gun: GunType(f[17]), GunID: atoi(f[18]), Step: atoi(f[19]),
54 Chart: f[20], Status: f[21],
55 })
56 }
57 return evs
58}
59
60// EncodeCharacter packs a character, guns included; DecodeCharacter unpacks it.
61func EncodeCharacter(c Character) string {
62 guns := make([]string, 0, MaxGuns)
63 for _, g := range c.Guns {
64 if g.Type != NoGun {
65 guns = append(guns, encodeGun(g))
66 }
67 }
68 var killers [MaxPlayers]int
69 for q, k := range c.Killers {
70 if k {
71 killers[q] = 1
72 }
73 }
74 return strings.Join([]string{
75 c.Hex, strconv.Itoa(int(c.Facing)), boolStr(c.Down), strconv.Itoa(c.Delay), strconv.Itoa(int(c.Status)),
76 boolStr(c.PlayedFoot), boolStr(c.PlayedRun), boolStr(c.RanLastTurn), boolStr(c.Dropped),
77 strconv.Itoa(c.Aim), c.AimHex, strconv.Itoa(c.Endurance), strconv.Itoa(c.Exhausted),
78 strconv.Itoa(c.Serious), strconv.Itoa(c.GunArm), strconv.Itoa(c.OtherArm), strconv.Itoa(c.Leg),
79 strings.Join(guns, ","),
80 strconv.Itoa(c.AimAt), perSeat(killers), perSeat(c.SeriousFrom), perSeat(c.LegFrom),
81 perSeat(c.Hurt),
82 }, "|")
83}
84
85// perSeat packs one number per seat, "/"-separated.
86func perSeat(v [MaxPlayers]int) string {
87 s := make([]string, len(v))
88 for i, n := range v {
89 s[i] = strconv.Itoa(n)
90 }
91 return strings.Join(s, "/")
92}
93
94// seatValues unpacks what perSeat packed.
95func seatValues(f string) (out [MaxPlayers]int) {
96 if f == "" {
97 return out
98 }
99 for i, n := range strings.Split(f, "/") {
100 if i < MaxPlayers {
101 out[i] = atoi(n)
102 }
103 }
104 return out
105}
106
107// DecodeCharacter unpacks what EncodeCharacter produced.
108func DecodeCharacter(s string) Character {
109 f := strings.Split(s, "|")
110 if len(f) != 23 {
111 return Character{}
112 }
113 c := Character{
114 Hex: f[0], Facing: hex.Dir(atoi(f[1])), Down: f[2] == "1", Delay: atoi(f[3]), Status: Status(atoi(f[4])),
115 PlayedFoot: f[5] == "1", PlayedRun: f[6] == "1", RanLastTurn: f[7] == "1", Dropped: f[8] == "1",
116 Aim: atoi(f[9]), AimHex: f[10], Endurance: atoi(f[11]), Exhausted: atoi(f[12]),
117 Serious: atoi(f[13]), GunArm: atoi(f[14]), OtherArm: atoi(f[15]), Leg: atoi(f[16]),
118 AimAt: atoi(f[18]),
119 }
120 for q, k := range seatValues(f[19]) {
121 c.Killers[q] = k != 0
122 }
123 c.SeriousFrom = seatValues(f[20])
124 c.LegFrom = seatValues(f[21])
125 c.Hurt = seatValues(f[22])
126 if f[17] != "" {
127 for i, rec := range strings.Split(f[17], ",") {
128 g, ok := decodeGun(rec)
129 if i >= MaxGuns || !ok {
130 break
131 }
132 c.Guns[i] = g
133 }
134 }
135 return c
136}
137
138// EncodeChars packs the characters of a state (a turn's starting
139// snapshot), seats in order; DecodeChars unpacks them.
140func EncodeChars(cs []Character) string {
141 parts := make([]string, len(cs))
142 for i, c := range cs {
143 parts[i] = EncodeCharacter(c)
144 }
145 return strings.Join(parts, ";")
146}
147
148// DecodeChars unpacks what EncodeChars produced.
149func DecodeChars(s string) []Character {
150 if s == "" {
151 return nil
152 }
153 parts := strings.Split(s, ";")
154 cs := make([]Character, len(parts))
155 for i, part := range parts {
156 cs[i] = DecodeCharacter(part)
157 }
158 return cs
159}
160
161// encodeGun packs a gun's fields, ':'-separated; decodeGun unpacks them.
162func encodeGun(g Gun) string {
163 return strings.Join([]string{
164 strconv.Itoa(g.ID), string(g.Type), strconv.Itoa(int(g.Location)), boolStr(g.Cocked),
165 strconv.Itoa(g.Shells), boolStr(g.Jammed), boolStr(g.Exploded),
166 }, ":")
167}
168
169func decodeGun(s string) (Gun, bool) {
170 g := strings.Split(s, ":")
171 if len(g) != 7 {
172 return Gun{}, false
173 }
174 return Gun{
175 ID: atoi(g[0]), Type: GunType(g[1]), Location: GunLocation(atoi(g[2])), Cocked: g[3] == "1",
176 Shells: atoi(g[4]), Jammed: g[5] == "1", Exploded: g[6] == "1",
177 }, true
178}
179
180// EncodeGround packs the weapons lying on the board (a turn's ground at its
181// start), ';'-separated records of id, hex and gun ("1|A-G6|1:colt45:0:0:6:0:0");
182// DecodeGround unpacks them, skipping a malformed record.
183func EncodeGround(ground []GroundGun) string {
184 recs := make([]string, len(ground))
185 for i, gg := range ground {
186 recs[i] = strconv.Itoa(gg.ID) + "|" + gg.Hex + "|" + encodeGun(gg.Gun)
187 }
188 return strings.Join(recs, ";")
189}
190
191// DecodeGround unpacks what EncodeGround produced.
192func DecodeGround(s string) []GroundGun {
193 if s == "" {
194 return nil
195 }
196 var out []GroundGun
197 for _, rec := range strings.Split(s, ";") {
198 f := strings.SplitN(rec, "|", 3)
199 if len(f) != 3 {
200 continue
201 }
202 if g, ok := decodeGun(f[2]); ok {
203 out = append(out, GroundGun{ID: atoi(f[0]), Hex: f[1], Gun: g})
204 }
205 }
206 return out
207}
208
209// EncodeInts packs numbers (the cards drawn in a turn); DecodeInts unpacks them.
210func EncodeInts(ns []int) string {
211 parts := make([]string, len(ns))
212 for i, n := range ns {
213 parts[i] = strconv.Itoa(n)
214 }
215 return strings.Join(parts, ",")
216}
217
218// DecodeInts unpacks what EncodeInts produced.
219func DecodeInts(s string) []int {
220 if s == "" {
221 return nil
222 }
223 parts := strings.Split(s, ",")
224 ns := make([]int, len(parts))
225 for i, p := range parts {
226 ns[i] = atoi(p)
227 }
228 return ns
229}
230
231func boolStr(b bool) string {
232 if b {
233 return "1"
234 }
235 return "0"
236}