Completed
Branch master (2d3735)
by Midori
15:34 queued 05:33
created

GameOfLife::start()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 8
nc 2
nop 1
dl 0
loc 17
rs 8.8571
c 0
b 0
f 0
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
    public function __construct(string $filename, string $outputFilename = null)
62
    {
63
        $this->loadXML($filename);
64
65
        if ($outputFilename != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $outputFilename of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
66
            $this->outputFilename = $outputFilename;
67
        }
68
69
        $world = $this->xml->getElementsByTagName('world')->item(0);
70
        $this->size = $world->getElementsByTagName('cells')->item(0)->nodeValue;
71
        $this->iterations = $world->getElementsByTagName('iterations')->item(0)->nodeValue;
72
        $this->species = $world->getElementsByTagName('species')->item(0)->nodeValue;
73
74
        $this->createWorld();
75
        $this->createOrganisms();
76
        $this->createLife();
77
    }
78
79
    /**
80
     * Loads the inital XML file. Always runs.
81
     *
82
     * @param $filename
83
     * @return bool
84
     * @throws \Exception
85
     */
86
    private function loadXML($filename)
87
    {
88
        if (!file_exists($filename)) {
89
            throw new \Exception('File not found.');
90
        }
91
92
        $handler = fopen($filename, "r");
93
94
        if (!$handler) {
95
            throw new \Exception('File open failed.');
96
        }
97
98
        $xmlString = stream_get_contents($handler);
99
        $this->xml = new \DOMDocument();
100
        $this->xml->loadXML($xmlString);
101
        fclose($handler);
102
103
        return true;
104
    }
105
106
    /**
107
     * Starts the game
108
     *
109
     * @param bool $verbose
110
     * @return bool
111
     * @throws \Exception
112
     */
113
    public function start($verbose = true)
114
    {
115
        if ($this->organisms == null || $this->species == null || $this->iterations == null) {
116
            throw new \Exception('Not ready yet');
117
        }
118
119
        $this->life->start($verbose);
120
121
        if (!$this->life->isEnded()) {
122
            throw new \Exception('You cannot generate output while life continues');
123
        }
124
125
        if ($this->life->isEnded()) {
126
            self::createXMLfromCells("data/" . $this->outputFilename, $this->organisms->getCells(), $this->species, $this->iterations);
127
        }
128
129
        return true;
130
    }
131
132
    /**
133
     * Creates the life of game.
134
     *
135
     * @throws \Exception
136
     */
137
    private function createLife()
138
    {
139
        if ($this->organisms !== null && $this->world !== null) {
140
            $this->life = new Life($this->world, $this->organisms);
141
        } else {
142
            throw new \Exception('Not ready yet');
143
        }
144
    }
145
146
    /**
147
     * Creates the World of game.
148
     *
149
     * @throws \Exception
150
     */
151
    private function createWorld()
152
    {
153
        if ($this->size == null || $this->species == null || $this->iterations == null) {
154
            throw new \Exception('Not ready yet');
155
        }
156
157
        if ($this->size < 0 || $this->species < 0 || $this->iterations < 0) {
158
            throw new \InvalidArgumentException("World arguments should be positive.");
159
        }
160
161
        $this->world = new World($this->size, $this->species, $this->iterations);
162
    }
163
164
    /**
165
     * Creates organisms of game.
166
     *
167
     * @throws \Exception
168
     */
169
    private function createOrganisms()
170
    {
171
        if ($this->size == null || $this->species == null) {
172
            throw new \Exception('Not ready yet');
173
        }
174
175
        if ($this->size < 0 || $this->species < 0) {
176
            throw new \InvalidArgumentException("Organisms arguments should be positive.");
177
        }
178
179
        $cellsArray = self::createCellsFromXML($this->xml);
180
        $this->checkCells($cellsArray, $this->species);
181
182
        $this->organisms = new Organisms($cellsArray);
183
    }
184
185
    /**
186
     * Helper function to generate array of cells from XML DomDocument.
187
     *
188
     * @param \DOMDocument $xml
189
     * @return array
190
     */
191
    public static function createCellsFromXML(\DOMDocument $xml)
192
    {
193
        $world = $xml->getElementsByTagName('world')->item(0);
194
        $size = $world->getElementsByTagName('cells')->item(0)->nodeValue;
195
196
        $cellsArray = self::createSquareMatrixWithZeors($size);
197
        $organisms = $xml->getElementsByTagName('organism');
198
199
        /**
200
         * @var \DOMElement $organism
201
         */
202
        foreach ($organisms as $organism) {
203
            $j = $organism->getElementsByTagName('x_pos')->item(0)->nodeValue;
204
            $i = $organism->getElementsByTagName('y_pos')->item(0)->nodeValue;
205
            $value = $organism->getElementsByTagName('species')->item(0)->nodeValue;
206
207
            $cellsArray[$i][$j] = $value;
208
        }
209
210
        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
    public static function createXMLfromCells($filename, array $matrix, int $species, int $iterations)
222
    {
223
        $domtree = new \DOMDocument('1.0', 'UTF-8');
224
225
        $xmlRoot = $domtree->createElement("life");
226
227
        $xmlRoot = $domtree->appendChild($xmlRoot);
228
229
230
        $world = $domtree->createElement("world");
231
        $xmlRoot->appendChild($world);
232
233
        $world->appendChild($domtree->createElement('cells', sizeof($matrix)));
234
        $world->appendChild($domtree->createElement('species', $species));
235
        $world->appendChild($domtree->createElement('iterations', $iterations));
236
237
        $organisms = $domtree->createElement("organisms");
238
239
240
        $xmlRoot->appendChild($organisms);
241
242
        for ($i = 0; $i < sizeof($matrix); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
243
            for ($j = 0; $j < sizeof($matrix[0]); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
244
                if ($matrix[$i][$j] !== 0) {
245
                    $organism = $domtree->createElement('organism');
246
                    $organism->appendChild($domtree->createElement('x_pos', $j));
247
                    $organism->appendChild($domtree->createElement('y_pos', $i));
248
                    $organism->appendChild($domtree->createElement('species', $matrix[$i][$j]));
249
                    $organisms->appendChild($organism);
250
                }
251
            }
252
        }
253
254
        file_put_contents($filename, $domtree->saveXML());
255
    }
256
257
    /**
258
     * Initializes a multidimensional array of zeros
259
     *
260
     * @param int $size
261
     * @return array
262
     */
263
    public static function createSquareMatrixWithZeors(int $size)
264
    {
265
266
        $matrix = array_fill(0, $size, array_fill(0, $size, 0));
267
        return $matrix;
268
    }
269
270
    /**
271
     * Creates a random array with integers up to $max
272
     *
273
     * @param int $n
274
     * @param int $m
275
     * @param int $max
276
     *
277
     * @return array
278
     */
279
    public static function createRandomMatrix(int $n, int $m, int $max)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $n. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $m. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
280
    {
281
        $matrix = array_fill(0, $n, array_fill(0, $m, 0));
282
283
        for ($i = 0; $i < $n; $i++) {
284
            for ($j = 0; $j < $m; $j++) {
285
                $matrix[$i][$j] = rand(0, $max);
286
            }
287
        }
288
289
        return $matrix;
290
    }
291
292
    /**
293
     * Checks if array cells are valid.
294
     *
295
     * @param int[][] $cells
296
     * @param int $species
297
     */
298
    private function checkCells(array $cells, int $species)
299
    {
300
        if (empty($cells) || empty($cells[0])) {
301
            throw new \InvalidArgumentException("Cells can't be empty");
302
        }
303
        for ($i = 0; $i < sizeof($cells); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
304
            for ($j = 0; $j < sizeof($cells[0]); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
305
                if ($cells[$i][$j] < 0 || $cells[$i][$j] > ($species + 1)) {
306
                    throw new \InvalidArgumentException("Cells can't be empty");
307
                }
308
            }
309
        }
310
    }
311
312
}