victory.gno
4.73 Kb · 151 lines
1package engine
2
3// Victory point awards (rule 15), for showdowns where each character is a
4// side of its own: there are no allies (no "Ally killed"), and no special
5// rule captures or kills anyone.
6const (
7 VPSurvive = "survive" // +2: conscious and unsurrendered at the end (15.62)
8 VPHoldField = "hold_field" // +2: the only side with survivors (15.63)
9 VPSurrender = "surrender" // -2 (15.41)
10 VPCaptured = "captured" // -4: passed out or surrendered when the enemy holds the field (15.63, 15.7)
11 VPKilled = "killed" // -8 (15.2)
12 VPKill = "kill_enemy" // +2: the enemy who killed him (15.2)
13 VPKnockOut = "knock_out_enemy" // +1: the enemy who crossed off most of his endurance boxes (15.3, 15.41)
14 VPRunAway = "run_away" // -2: moved off the map (15.5); no end-of-showdown points (15.6)
15)
16
17// VPPoints is what each award is worth (the VICTORY POINTS schedule, 15.11).
18// Passing out is worth 0 and makes no award.
19var VPPoints = map[string]int{
20 VPSurvive: 2,
21 VPHoldField: 2,
22 VPSurrender: -2,
23 VPCaptured: -4,
24 VPKilled: -8,
25 VPKill: 2,
26 VPKnockOut: 1,
27 VPRunAway: -2,
28}
29
30// VPSharedKill is what each enemy gets when several KILLs land in the same
31// segment: 15.2 shares the "Kill enemy" points, kept whole here.
32const VPSharedKill = 1
33
34// Award is victory points a character earns (Points > 0) or loses.
35type Award struct {
36 Player int
37 Reason string // one of the VP* names
38 Points int
39}
40
41// TurnVictoryPoints scores what a turn did, from the characters before and
42// after it: a character killed loses the "Killed" points and the enemy
43// whose hit killed it gets "Kill enemy" (15.2, Character.Killers), or,
44// when several enemies share the kill, 1 point each (kept whole); one
45// who passed out gets nothing and the enemy who crossed off most of its
46// endurance boxes "Knock out enemy" (15.3, Character.Hurt). A character
47// who is not up cannot be hit, so a knocked-out one is never killed
48// afterwards.
49func TurnVictoryPoints(before, after State) []Award {
50 var out []Award
51 for p, c := range after.Chars {
52 if p >= len(before.Chars) || before.Chars[p].Status != Alive {
53 continue
54 }
55 switch c.Status {
56 case Killed:
57 out = append(out, award(p, VPKilled))
58 var killers []int
59 for q, k := range c.Killers {
60 if k && q != p && q < len(after.Chars) {
61 killers = append(killers, q)
62 }
63 }
64 for _, q := range killers {
65 if len(killers) == 1 {
66 out = append(out, award(q, VPKill))
67 } else {
68 out = append(out, Award{Player: q, Reason: VPKill, Points: VPSharedKill})
69 }
70 }
71 case PassedOut:
72 if k := knockedOutBy(c); k >= 0 {
73 out = append(out, award(k, VPKnockOut))
74 }
75 case RanAway:
76 out = append(out, award(p, VPRunAway))
77 }
78 }
79 return out
80}
81
82// knockedOutBy is the enemy who crossed off most of the character's
83// endurance boxes (15.3): the first in seat order when two did as much,
84// NoSeat when nobody did.
85func knockedOutBy(c Character) int {
86 best, most := NoSeat, 0
87 for q, n := range c.Hurt {
88 if n > most {
89 best, most = q, n
90 }
91 }
92 return best
93}
94
95// EndVictoryPoints scores the end of the showdown (15.6): a character still
96// up survives, and holds the field when it is the only one (15.62, 15.63);
97// Out stands for a surrendered character, who loses the "Surrender" points
98// and, like passing out, gives "Knock out enemy" to the enemy who crossed
99// off most of its endurance boxes (15.41); passed out or surrendered, a
100// character is captured when an enemy holds the field (15.63, 15.7).
101func EndVictoryPoints(s State) []Award {
102 var out []Award
103 survivors := AliveCount(s)
104 for p, c := range s.Chars {
105 holds := survivors == 1 && c.Status != Alive // someone else holds the field
106 switch c.Status {
107 case Alive:
108 out = append(out, award(p, VPSurvive))
109 if survivors == 1 {
110 out = append(out, award(p, VPHoldField))
111 }
112 case Out:
113 out = append(out, award(p, VPSurrender))
114 if k := knockedOutBy(c); k >= 0 {
115 out = append(out, award(k, VPKnockOut))
116 }
117 if holds {
118 out = append(out, award(p, VPCaptured))
119 }
120 case PassedOut:
121 if holds {
122 out = append(out, award(p, VPCaptured))
123 }
124 }
125 }
126 return out
127}
128
129func award(p int, reason string) Award {
130 return Award{Player: p, Reason: reason, Points: VPPoints[reason]}
131}
132
133// VPEvents are the awards as end-of-turn events of the turn's log.
134func VPEvents(awards []Award) []Event {
135 evs := make([]Event, 0, len(awards))
136 for _, a := range awards {
137 evs = append(evs, Event{Seg: EndOfTurn, Player: a.Player, Kind: EvVP, N: a.Points, Reason: a.Reason})
138 }
139 return evs
140}
141
142// VPTotals adds the awards up per seat, for n seats.
143func VPTotals(awards []Award, n int) []int {
144 t := make([]int, n)
145 for _, a := range awards {
146 if a.Player >= 0 && a.Player < n {
147 t[a.Player] += a.Points
148 }
149 }
150 return t
151}