Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}