-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgameoflife.go
177 lines (155 loc) · 3.71 KB
/
gameoflife.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Package implements Conway's Game of life with possibility to travel
// into specific state or listen to changes
package gameoflife
import (
"context"
"errors"
"time"
)
type Config struct {
// TicksPerSecond considered by GetStateAfterSeconds and ListenState
TicksPerSecond uint8
}
type State []StateRow
// Array of bools represent dead and alive cells
type StateRow []bool
type Engine struct {
Config Config
state State
lifeIsDead bool
listeners map[context.Context]chan State
}
// Get new object with it's own state and listeners
func NewEngine() *Engine {
return &Engine{
Config: Config{
TicksPerSecond: 10,
},
listeners: make(map[context.Context]chan State),
}
}
// The only ways to set state, changing not allowed
func (en *Engine) SetInitialState(state State) error {
if len(en.state) > 0 {
return errors.New("state changing not allowed")
}
en.state = state
return nil
}
// Calculates future state of life based on Config.TicksPerSecond
// Not allowed to call with listeners connected
// Throwns error when called on empty state
func (en *Engine) GetStateAfterSeconds(seconds uint) (State, error) {
return en.GetStateAfterTicks(seconds * uint(en.Config.TicksPerSecond))
}
// Calculates future state of life
// Not allowed to call with listeners connected
// Throwns error when called on empty state
func (en *Engine) GetStateAfterTicks(ticks uint) (State, error) {
if len(en.listeners) > 0 {
return nil, errors.New("can't change state with active listeners")
}
if len(en.state) == 0 {
return nil, errors.New("GetStateAfterTicks called on empty state")
}
var i uint
for i = 0; i < ticks; i++ {
en.doTick()
}
return en.state, nil
}
// Returns a channel for state updates, frequency based on Config.TicksPerSecond
// Throwns error when called on empty state
func (en *Engine) ListenState(ctx context.Context) (chan State, error) {
if len(en.state) == 0 {
return nil, errors.New("ListenState called on empty state")
}
if len(en.listeners) == 0 {
go func() {
ticker := time.NewTicker(time.Duration(1 / float64(en.Config.TicksPerSecond) * float64(time.Second)))
for range ticker.C {
select {
case <-ctx.Done():
close(en.listeners[ctx])
delete(en.listeners, ctx)
default:
en.doTick()
if len(en.listeners) == 0 {
return
}
for _, ch := range en.listeners {
ch <- en.state
}
}
}
}()
}
out := make(chan State)
en.listeners[ctx] = out
return out, nil
}
func (en *Engine) doTick() {
if en.lifeIsDead {
return
}
var countLiveNeighbours = func(state State, row int, col int) int {
alive := 0
for i := row - 1; i <= row+1; i++ {
for j := col - 1; j <= col+1; j++ {
var x int
var y int
if i == row && j == col {
continue
}
if i < 0 {
x = len(state) - 1
} else if i >= len(state) {
x = 0
} else {
x = i
}
if j < 0 {
y = len(state[x]) - 1
} else if j >= len(state[x]) {
y = 0
} else {
y = j
}
if state[x][y] {
alive++
}
}
}
return alive
}
totalAliveCells := 0
nextState := make(State, len(en.state))
copyState(&nextState, en.state)
for i := 0; i < len(en.state); i++ {
for j := 0; j < len(en.state[i]); j++ {
aliveNeighbours := countLiveNeighbours(en.state, i, j)
if en.state[i][j] {
totalAliveCells++
if aliveNeighbours < 2 || aliveNeighbours > 3 {
nextState[i][j] = false
}
} else {
if aliveNeighbours == 3 {
nextState[i][j] = true
}
}
}
}
en.state = nextState
if totalAliveCells < 3 {
en.lifeIsDead = true
}
}
func copyState(dest *State, src State) {
for i, row := range src {
(*dest)[i] = make(StateRow, len(row))
for j, cell := range row {
(*dest)[i][j] = cell
}
}
}