shootout.gno
2.48 Kb · 104 lines
1package penalty
2
3// Side is who takes a kick.
4type Side uint8
5
6const (
7 SidePlayer Side = iota + 1
8 SideChain
9)
10
11func (s Side) String() string {
12 switch s {
13 case SidePlayer:
14 return "player"
15 case SideChain:
16 return "chain"
17 }
18 return "none"
19}
20
21// Other returns the opposing side.
22func (s Side) Other() Side {
23 if s == SidePlayer {
24 return SideChain
25 }
26 return SidePlayer
27}
28
29// RegulationKicks is how many kicks each side takes before sudden death.
30const RegulationKicks = 5
31
32// Shootout is the score of a penalty shootout in which the player always
33// kicks first in each round:
34//
35// - each side takes RegulationKicks kicks, alternating;
36// - it ends early as soon as one side cannot be caught with the kicks the
37// other has left;
38// - level after regulation, it goes to sudden death: rounds of one kick
39// each, decided when one side scores and the other does not.
40type Shootout struct {
41 PlayerKicks int
42 PlayerGoals int
43 ChainKicks int
44 ChainGoals int
45}
46
47// Next returns who kicks next (SidePlayer at the start of every round).
48func (s Shootout) Next() Side {
49 if s.PlayerKicks == s.ChainKicks {
50 return SidePlayer
51 }
52 return SideChain
53}
54
55// Round is the 1-based number of the round in progress.
56func (s Shootout) Round() int { return s.ChainKicks + 1 }
57
58// SuddenDeath reports whether regulation is over.
59func (s Shootout) SuddenDeath() bool { return s.ChainKicks >= RegulationKicks }
60
61// Record adds a kick for side. It panics if side is out of turn or the
62// shootout is already decided.
63func (s *Shootout) Record(side Side, scored bool) {
64 if s.Winner() != 0 {
65 panic("penalty: the shootout is already decided")
66 }
67 if side != s.Next() {
68 panic("penalty: " + side.String() + " is kicking out of turn")
69 }
70 g := 0
71 if scored {
72 g = 1
73 }
74 if side == SidePlayer {
75 s.PlayerKicks++
76 s.PlayerGoals += g
77 } else {
78 s.ChainKicks++
79 s.ChainGoals += g
80 }
81}
82
83// Winner returns the side that has won, or 0 while undecided.
84func (s Shootout) Winner() Side {
85 if s.PlayerKicks <= RegulationKicks && s.ChainKicks <= RegulationKicks {
86 playerLeft := RegulationKicks - s.PlayerKicks
87 chainLeft := RegulationKicks - s.ChainKicks
88 if s.PlayerGoals > s.ChainGoals+chainLeft {
89 return SidePlayer
90 }
91 if s.ChainGoals > s.PlayerGoals+playerLeft {
92 return SideChain
93 }
94 return 0
95 }
96 // Sudden death: decided only once both have kicked in the round.
97 if s.PlayerKicks == s.ChainKicks && s.PlayerGoals != s.ChainGoals {
98 if s.PlayerGoals > s.ChainGoals {
99 return SidePlayer
100 }
101 return SideChain
102 }
103 return 0
104}