GameOfLife   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 300
Duplicated Lines 0 %

Test Coverage

Coverage 88.57%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 99
dl 0
loc 300
ccs 93
cts 105
cp 0.8857
rs 8.96
c 1
b 0
f 0
wmc 43

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 2
A start() 0 17 6
A createCellsFromXML() 0 20 2
B createWorld() 0 11 7
A createXMLfromCells() 0 36 4
B checkCells() 0 13 7
A loadXML() 0 18 3
A createOrganisms() 0 14 5
A createRandomMatrix() 0 11 3
A createSquareMatrixWithZeors() 0 5 1
A createLife() 0 6 3

How to fix   Complexity   

Complex Class

Complex classes like GameOfLife often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GameOfLife, and based on these observations, apply Extract Interface, too.

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
$handler is of type false|resource, thus it always evaluated to false.
Loading history...
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
}