ruleset.gno
6.05 Kb · 152 lines
1package engine
2
3// Clan is a named pool of special cards.
4type Clan struct {
5 Name string
6 Pool uint64 // bit i set: Ruleset.Cards[i] is in the pool
7}
8
9// Ruleset holds every card and number the rules use, so the solver can sweep them.
10type Ruleset struct {
11 Cards []Card
12 Clans []Clan
13 HP int // starting HP of both gnomes
14 BlockCap int
15 CounterCap int
16 VulnerableCap int
17 StrengthCap int
18 // VulnerableDoublesOneHit: each hit against a Vulnerable gnome deals double and uses a
19 // token. Otherwise the next card to hit deals double on every hit and uses one token.
20 VulnerableDoublesOneHit bool
21 // StrengthLastsTheRound: Strength ends with the round, like Block and Counter. Otherwise
22 // it lasts the match.
23 StrengthLastsTheRound bool
24 // VulnerableLastsTheRound: unused Vulnerable ends with the round. Otherwise it lasts
25 // until hits use it.
26 VulnerableLastsTheRound bool
27}
28
29// Gnome is one side's state between rounds; Block and Counter end with each round.
30type Gnome struct {
31 HP, Vulnerable, Strength int
32 Spent uint64 // special cards played, by CardID bit
33}
34
35// State is the match between rounds. Round is the round about to be played, 1 to 3.
36type State struct {
37 Round int
38 Gnomes [2]Gnome
39}
40
41// Start is the state before round 1.
42func (rs *Ruleset) Start() State {
43 return State{Round: 1, Gnomes: [2]Gnome{{HP: rs.HP}, {HP: rs.HP}}}
44}
45
46func (rs *Ruleset) CardByName(name string) (CardID, bool) {
47 for i := range rs.Cards {
48 if rs.Cards[i].Name == name {
49 return CardID(i), true
50 }
51 }
52 return 0, false
53}
54
55// PlanOf looks each card up by name. It reports false when a name matches no card.
56func (rs *Ruleset) PlanOf(names ...string) (Plan, bool) {
57 plan := make(Plan, 0, len(names))
58 for _, n := range names {
59 id, ok := rs.CardByName(n)
60 if !ok {
61 return nil, false
62 }
63 plan = append(plan, id)
64 }
65 return plan, true
66}
67
68func (rs *Ruleset) ClanByName(name string) (Clan, bool) {
69 for _, c := range rs.Clans {
70 if c.Name == name {
71 return c, true
72 }
73 }
74 return Clan{}, false
75}
76
77func hits(n, damage int) []Effect {
78 out := make([]Effect, n)
79 for i := range out {
80 out[i] = Effect{Hit, damage}
81 }
82 return out
83}
84
85// ProofOfConcept is the proof-of-concept ruleset: the two basics, the Pickers and
86// Ironhide 10-card starter packs of the duel spec (candidate AS2-HB5-F4), and the Delvers
87// pack, a draft not yet in the spec.
88func ProofOfConcept() *Ruleset {
89 cards := []Card{
90 {Name: "Pick", Cost: 1, Tier: Normal, Damage: hits(1, 1)},
91 {Name: "Brace", Cost: 1, Tier: Normal, Block: []Effect{{GainBlock, 1}}},
92
93 {Name: "Pickaxe Flurry", Cost: 1, Tier: Quick, Special: true, Damage: hits(2, 1)},
94 {Name: "Kneecap", Cost: 1, Tier: Quick, Special: true, Interrupt: true, Damage: hits(1, 1)},
95 {Name: "Sling Stone", Cost: 1, Tier: Normal, Special: true, Damage: hits(1, 2)},
96 {Name: "Dodge Roll", Cost: 2, Tier: Normal, Special: true, Parry: 1},
97 {Name: "Chalk Dust", Cost: 1, Tier: Quick, Special: true, Damage: []Effect{{ApplyVulnerable, 1}}},
98 {Name: "Double Tap", Cost: 2, Tier: Normal, Special: true, Damage: hits(3, 1)},
99 {Name: "Momentum", Cost: 1, Tier: Normal, Special: true, Damage: []Effect{{GainStrength, 1}}},
100 {Name: "Undermine", Cost: 1, Tier: Quick, Special: true, Rare: true, Interrupt: true, Damage: []Effect{{VulnerableIfCancelled, 2}}},
101 {Name: "Hunker Down", Cost: 3, Tier: Normal, Special: true, Parry: 2},
102 {Name: "Boot and Heel", Cost: 2, Tier: Slow, Special: true, Damage: hits(2, 2)},
103
104 {Name: "Stone Guard", Cost: 3, Tier: Normal, Special: true, Parry: 2},
105 {Name: "Bulwark", Cost: 2, Tier: Normal, Special: true, Block: []Effect{{GainBlock, 5}}},
106 {Name: "Hammer Blow", Cost: 2, Tier: Slow, Special: true, Damage: hits(1, 5)},
107 {Name: "Cobble Throw", Cost: 1, Tier: Slow, Special: true, Damage: hits(1, 2)},
108 {Name: "Spiked Shield", Cost: 1, Tier: Normal, Special: true, Block: []Effect{{GainBlock, 1}, {GainCounter, 1}}},
109 {Name: "Anvil Stomp", Cost: 2, Tier: Slow, Special: true, Damage: []Effect{{Hit, 2}, {ApplyVulnerable, 1}}},
110 {Name: "Feint", Cost: 2, Tier: Normal, Special: true, Damage: hits(1, 4), OnCancel: []Effect{{GainBlock, 4}}},
111 {Name: "Avalanche", Cost: 1, Tier: Slow, Special: true, Rare: true, Damage: []Effect{{HitBlock, 0}}},
112 {Name: "Shield Catch", Cost: 2, Tier: Normal, Special: true, Parry: 1},
113 {Name: "Loose Timbers", Cost: 2, Tier: Normal, Special: true, Block: []Effect{{GainBlock, 3}}, CancelDamage: hits(1, 4)},
114
115 {Name: "Kickback", Cost: 1, Tier: Slow, Special: true, Rare: true, Damage: []Effect{{HitHardest, 5}}},
116 {Name: "Overdig", Cost: 2, Tier: Normal, Special: true, Damage: []Effect{{Hit, 6}, {GainVulnerable, 1}}},
117 {Name: "Ambush", Cost: 1, Tier: Normal, Special: true, Damage: []Effect{{GainVulnerable, 1}, {HitIfNotAttacked, 6}}},
118 {Name: "Open Guard", Cost: 1, Tier: Normal, Special: true, Block: []Effect{{GainBlock, 4}}, Damage: []Effect{{GainVulnerable, 1}}},
119 {Name: "Chisel Jab", Cost: 1, Tier: Normal, Special: true, Damage: hits(1, 2)},
120 {Name: "Rockfall", Cost: 2, Tier: Slow, Special: true, Damage: hits(1, 5)},
121 {Name: "Cribbing", Cost: 1, Tier: Normal, Special: true, Block: []Effect{{BlockPerVulnerable, 1}}},
122 {Name: "Deadfall", Cost: 2, Tier: Normal, Special: true, Parry: 1, Damage: []Effect{{VulnerableIfCancelled, 1}}},
123 {Name: "Blowback", Cost: 2, Tier: Normal, Special: true, Block: []Effect{{MoveVulnerable, 0}}},
124 {Name: "Cave-In", Cost: 3, Tier: Slow, Special: true, Damage: []Effect{{StripBlock, 0}, {Hit, 5}}},
125 }
126 const firstPickers, firstIronhide, firstDelvers, end = 2, 12, 22, 32
127 return &Ruleset{
128 Cards: cards,
129 Clans: []Clan{
130 {Name: "Pickers", Pool: bitRange(firstPickers, firstIronhide)},
131 {Name: "Ironhide", Pool: bitRange(firstIronhide, firstDelvers)},
132 {Name: "Delvers", Pool: bitRange(firstDelvers, end)},
133 },
134 HP: 15,
135 BlockCap: 20,
136 CounterCap: 3,
137 VulnerableCap: 3,
138 StrengthCap: 8,
139
140 VulnerableDoublesOneHit: true,
141 StrengthLastsTheRound: true,
142 }
143}
144
145// bitRange sets bits from, from+1, ..., to-1.
146func bitRange(from, to int) uint64 {
147 var bits uint64
148 for i := from; i < to; i++ {
149 bits |= uint64(1) << uint(i)
150 }
151 return bits
152}