1 | <?php |
||
2 | |||
3 | namespace MidoriKocak\GameOfLife; |
||
4 | |||
5 | /** |
||
6 | * Class GameOfLife |
||
7 | * |
||
8 | * A simple standalone OOP, Connway's gameoflife implementation with PHP. |
||
9 | * API of Program |
||
10 | * |
||
11 | * @package MidoriKocak\GameOfLife |
||
12 | */ |
||
13 | class GameOfLife |
||
14 | { |
||
15 | /** |
||
16 | * @var \DOMDocument |
||
17 | */ |
||
18 | private $xml; |
||
19 | |||
20 | /** |
||
21 | * @var Life |
||
22 | */ |
||
23 | public $life; |
||
24 | |||
25 | /** |
||
26 | * @var World |
||
27 | */ |
||
28 | private $world; |
||
29 | |||
30 | /** |
||
31 | * @var Organisms |
||
32 | */ |
||
33 | private $organisms; |
||
34 | |||
35 | /** |
||
36 | * @var int |
||
37 | */ |
||
38 | private $size; |
||
39 | |||
40 | /** |
||
41 | * @var int |
||
42 | */ |
||
43 | private $iterations; |
||
44 | |||
45 | /** |
||
46 | * @var int |
||
47 | */ |
||
48 | private $species; |
||
49 | |||
50 | /** |
||
51 | * @var string |
||
52 | */ |
||
53 | private $outputFilename = "out.xml"; |
||
54 | |||
55 | /** |
||
56 | * GameOfLife constructor. |
||
57 | * |
||
58 | * @param string $filename |
||
59 | * @param string|null $outputFilename |
||
60 | */ |
||
61 | 5 | public function __construct(string $filename, string $outputFilename = null) |
|
62 | { |
||
63 | 5 | $this->loadXML($filename); |
|
64 | |||
65 | 5 | if ($outputFilename !== null) { |
|
66 | $this->outputFilename = $outputFilename; |
||
67 | } |
||
68 | |||
69 | 5 | $world = $this->xml->getElementsByTagName('world')->item(0); |
|
70 | 5 | $this->size = $world->getElementsByTagName('cells')->item(0)->nodeValue; |
|
71 | 5 | $this->iterations = $world->getElementsByTagName('iterations')->item(0)->nodeValue; |
|
72 | 5 | $this->species = $world->getElementsByTagName('species')->item(0)->nodeValue; |
|
73 | |||
74 | 5 | $this->createWorld(); |
|
75 | 5 | $this->createOrganisms(); |
|
76 | 5 | $this->createLife(); |
|
77 | 5 | } |
|
78 | |||
79 | /** |
||
80 | * Loads the inital XML file. Always runs. |
||
81 | * |
||
82 | * @param $filename |
||
83 | * @return bool |
||
84 | * @throws \Exception |
||
85 | */ |
||
86 | 5 | private function loadXML($filename) |
|
87 | { |
||
88 | 5 | if (!file_exists($filename)) { |
|
89 | throw new \Exception('File not found.'); |
||
90 | } |
||
91 | |||
92 | 5 | $handler = fopen($filename, "r"); |
|
93 | |||
94 | 5 | if (!$handler) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
95 | throw new \Exception('File open failed.'); |
||
96 | } |
||
97 | |||
98 | 5 | $xmlString = stream_get_contents($handler); |
|
99 | 5 | $this->xml = new \DOMDocument(); |
|
100 | 5 | $this->xml->loadXML($xmlString); |
|
101 | 5 | fclose($handler); |
|
102 | |||
103 | 5 | return true; |
|
104 | } |
||
105 | |||
106 | /** |
||
107 | * Starts the game |
||
108 | * |
||
109 | * @param bool $verbose |
||
110 | * @return bool |
||
111 | * @throws \Exception |
||
112 | */ |
||
113 | 1 | public function start($verbose = true) |
|
114 | { |
||
115 | 1 | if ($this->organisms == null || $this->species == null || $this->iterations == null) { |
|
116 | throw new \Exception('Not ready yet'); |
||
117 | } |
||
118 | |||
119 | 1 | $this->life->start($verbose); |
|
120 | |||
121 | 1 | if (!$this->life->isEnded()) { |
|
122 | throw new \Exception('You cannot generate output while life continues'); |
||
123 | } |
||
124 | |||
125 | 1 | if ($this->life->isEnded()) { |
|
126 | 1 | self::createXMLfromCells("data/" . $this->outputFilename, $this->organisms->getCells(), $this->species, $this->iterations); |
|
127 | } |
||
128 | |||
129 | 1 | return true; |
|
130 | } |
||
131 | |||
132 | /** |
||
133 | * Creates the life of game. |
||
134 | * |
||
135 | * @throws \Exception |
||
136 | */ |
||
137 | 5 | private function createLife() |
|
138 | { |
||
139 | 5 | if ($this->organisms !== null && $this->world !== null) { |
|
140 | 5 | $this->life = new Life($this->world, $this->organisms); |
|
141 | } else { |
||
142 | throw new \Exception('Not ready yet'); |
||
143 | } |
||
144 | 5 | } |
|
145 | |||
146 | /** |
||
147 | * Creates the World of game. |
||
148 | * |
||
149 | * @throws \Exception |
||
150 | */ |
||
151 | 5 | private function createWorld() |
|
152 | { |
||
153 | 5 | if ($this->size == null || $this->species == null || $this->iterations == null) { |
|
154 | throw new \Exception('Not ready yet'); |
||
155 | } |
||
156 | |||
157 | 5 | if ($this->size < 0 || $this->species < 0 || $this->iterations < 0) { |
|
158 | throw new \InvalidArgumentException("World arguments should be positive."); |
||
159 | } |
||
160 | |||
161 | 5 | $this->world = new World($this->size, $this->species, $this->iterations); |
|
162 | 5 | } |
|
163 | |||
164 | /** |
||
165 | * Creates organisms of game. |
||
166 | * |
||
167 | * @throws \Exception |
||
168 | */ |
||
169 | 5 | private function createOrganisms() |
|
170 | { |
||
171 | 5 | if ($this->size == null || $this->species == null) { |
|
172 | throw new \Exception('Not ready yet'); |
||
173 | } |
||
174 | |||
175 | 5 | if ($this->size < 0 || $this->species < 0) { |
|
176 | throw new \InvalidArgumentException("Organisms arguments should be positive."); |
||
177 | } |
||
178 | |||
179 | 5 | $cellsArray = self::createCellsFromXML($this->xml); |
|
180 | 5 | $this->checkCells($cellsArray, $this->species); |
|
181 | |||
182 | 5 | $this->organisms = new Organisms($cellsArray); |
|
183 | 5 | } |
|
184 | |||
185 | /** |
||
186 | * Helper function to generate array of cells from XML DomDocument. |
||
187 | * |
||
188 | * @param \DOMDocument $xml |
||
189 | * @return array |
||
190 | */ |
||
191 | 5 | public static function createCellsFromXML(\DOMDocument $xml) |
|
192 | { |
||
193 | 5 | $world = $xml->getElementsByTagName('world')->item(0); |
|
194 | 5 | $size = $world->getElementsByTagName('cells')->item(0)->nodeValue; |
|
195 | |||
196 | 5 | $cellsArray = self::createSquareMatrixWithZeors($size); |
|
197 | 5 | $organisms = $xml->getElementsByTagName('organism'); |
|
198 | |||
199 | /** |
||
200 | * @var \DOMElement $organism |
||
201 | */ |
||
202 | 5 | foreach ($organisms as $organism) { |
|
203 | 5 | $j = $organism->getElementsByTagName('x_pos')->item(0)->nodeValue; |
|
204 | 5 | $i = $organism->getElementsByTagName('y_pos')->item(0)->nodeValue; |
|
205 | 5 | $value = $organism->getElementsByTagName('species')->item(0)->nodeValue; |
|
206 | |||
207 | 5 | $cellsArray[$i][$j] = $value; |
|
208 | } |
||
209 | |||
210 | 5 | return $cellsArray; |
|
211 | } |
||
212 | |||
213 | /** |
||
214 | * Creates XML file from matrix |
||
215 | * |
||
216 | * @param $filename |
||
217 | * @param array $matrix |
||
218 | * @param int $species |
||
219 | * @param int $iterations |
||
220 | */ |
||
221 | 2 | public static function createXMLfromCells($filename, array $matrix, int $species, int $iterations) |
|
222 | { |
||
223 | 2 | $domtree = new \DOMDocument('1.0', 'UTF-8'); |
|
224 | |||
225 | 2 | $xmlRoot = $domtree->createElement("life"); |
|
226 | |||
227 | 2 | $xmlRoot = $domtree->appendChild($xmlRoot); |
|
228 | |||
229 | |||
230 | 2 | $world = $domtree->createElement("world"); |
|
231 | 2 | $xmlRoot->appendChild($world); |
|
232 | |||
233 | 2 | $world->appendChild($domtree->createElement('cells', sizeof($matrix))); |
|
234 | 2 | $world->appendChild($domtree->createElement('species', $species)); |
|
235 | 2 | $world->appendChild($domtree->createElement('iterations', $iterations)); |
|
236 | |||
237 | 2 | $organisms = $domtree->createElement("organisms"); |
|
238 | |||
239 | |||
240 | 2 | $xmlRoot->appendChild($organisms); |
|
241 | 2 | $rows = sizeof($matrix); |
|
242 | 2 | $columns = sizeof($matrix[0]); |
|
243 | |||
244 | 2 | for ($i = 0; $i < $rows; $i++) { |
|
245 | 2 | for ($j = 0; $j < $columns; $j++) { |
|
246 | 2 | if ($matrix[$i][$j] !== 0) { |
|
247 | 2 | $organism = $domtree->createElement('organism'); |
|
248 | 2 | $organism->appendChild($domtree->createElement('x_pos', $j)); |
|
249 | 2 | $organism->appendChild($domtree->createElement('y_pos', $i)); |
|
250 | 2 | $organism->appendChild($domtree->createElement('species', $matrix[$i][$j])); |
|
251 | 2 | $organisms->appendChild($organism); |
|
252 | } |
||
253 | } |
||
254 | } |
||
255 | |||
256 | 2 | file_put_contents($filename, $domtree->saveXML()); |
|
257 | 2 | } |
|
258 | |||
259 | /** |
||
260 | * Initializes a multidimensional array of zeros |
||
261 | * |
||
262 | * @param int $size |
||
263 | * @return array |
||
264 | */ |
||
265 | 5 | public static function createSquareMatrixWithZeors(int $size) |
|
266 | { |
||
267 | |||
268 | 5 | $matrix = array_fill(0, $size, array_fill(0, $size, 0)); |
|
269 | 5 | return $matrix; |
|
270 | } |
||
271 | |||
272 | /** |
||
273 | * Creates a random array with integers up to $max |
||
274 | * |
||
275 | * @param int $rows |
||
276 | * @param int $columns |
||
277 | * @param int $max |
||
278 | * |
||
279 | * @return array |
||
280 | */ |
||
281 | 1 | public static function createRandomMatrix(int $rows, int $columns, int $max) |
|
282 | { |
||
283 | 1 | $matrix = array_fill(0, $rows, array_fill(0, $columns, 0)); |
|
284 | |||
285 | 1 | for ($i = 0; $i < $rows; $i++) { |
|
286 | 1 | for ($j = 0; $j < $columns; $j++) { |
|
287 | 1 | $matrix[$i][$j] = rand(0, $max); |
|
288 | } |
||
289 | } |
||
290 | |||
291 | 1 | return $matrix; |
|
292 | } |
||
293 | |||
294 | /** |
||
295 | * Checks if array cells are valid. |
||
296 | * |
||
297 | * @param int[][] $cells |
||
298 | * @param int $species |
||
299 | */ |
||
300 | 5 | private function checkCells(array $cells, int $species) |
|
301 | { |
||
302 | 5 | if (empty($cells) || empty($cells[0])) { |
|
303 | throw new \InvalidArgumentException("Cells can't be empty"); |
||
304 | } |
||
305 | |||
306 | 5 | $rows = sizeof($cells); |
|
307 | 5 | $columns = sizeof($cells[0]); |
|
308 | |||
309 | 5 | for ($i = 0; $i < $rows; $i++) { |
|
310 | 5 | for ($j = 0; $j < $columns; $j++) { |
|
311 | 5 | if ($cells[$i][$j] < 0 || $cells[$i][$j] > ($species + 1)) { |
|
312 | throw new \InvalidArgumentException("Cells can't be empty"); |
||
313 | } |
||
314 | } |
||
315 | } |
||
316 | 5 | } |
|
317 | |||
318 | } |