Passed
Push — master ( 2a68f1...702eef )
by Craig
02:58
created

Grid   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 341
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 175
c 1
b 0
f 0
dl 0
loc 341
rs 4.08
wmc 59

20 Methods

Rating   Name   Duplication   Size   Complexity  
A setWordsCollection() 0 7 2
A setGridSize() 0 13 3
A __construct() 0 8 1
A getRandomWordLength() 0 17 4
A initGrid() 0 9 2
A generate() 0 16 3
A markWordUsed() 0 4 1
A placeWordHorizontally() 0 11 2
A placeWord() 0 21 5
A getWordLike() 0 15 2
A getGrid() 0 15 3
A placeWordDiagonallyLtr() 0 15 3
A placeWordDiagonallyRtl() 0 15 3
A getRandomWord() 0 9 1
B addWord() 0 37 10
A placeWordVertical() 0 11 2
A getColumnDefault() 0 3 1
A getTextGrid() 0 10 3
A addPlacedWord() 0 28 6
A getPuzzleWords() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Grid 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 Grid, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace CustomD\WordFinder;
4
5
use Illuminate\Support\Collection;
6
use RuntimeException;
7
8
class Grid
9
{
10
11
     /**
12
     * minimum word length for the puzzle
13
     */
14
    protected int $minWordLen;
15
16
    /**
17
     * maximium word length for the puzzle
18
     */
19
    protected int $maxWordLen;
20
21
    /**
22
     * grid side length
23
     */
24
    protected int $gridSize;
25
26
27
    protected Collection $wordsCollection; // base de données de mots SQLite
28
29
    const RENDER_HTML = 0; // afficher la grille en HTML
30
    const RENDER_TEXT = 1; // afficher la grille en mode texte
31
32
    protected $cells; // (tableau de size*size éléments String) cellules de la grille, chacune contenant une lettre
33
    protected $wordsList = []; // (tableau d'objets Word) : liste des mots à trouver
34
    protected $columnArray = []; // tableau (Int) des numéros des colonnes d'après les index des cellules
35
36
    public function __construct(int $gridSize, int $minWordLen, int $maxWordLen, Collection $wordsCollection)
37
    {
38
        $this->minWordLen = $minWordLen;
39
        $this->maxWordLen = $maxWordLen;
40
41
        $this->setWordsCollection($wordsCollection)
42
            ->setGridsize($gridSize)
43
            ->initGrid();
44
    }
45
46
    protected function setGridSize(int $gridSize): self
47
    {
48
        if ($gridSize < $this->minWordLen) {
49
            throw new RuntimeException('size must be greater than '.$this->minWordLen);
50
        }
51
52
        if ($gridSize < $this->maxWordLen) {
53
            $this->maxWordLen = $gridSize;
54
        }
55
56
        $this->gridSize = $gridSize;
57
58
        return $this;
59
    }
60
61
    protected function setWordsCollection(Collection $wordsCollection): self
62
    {
63
        $this->wordsCollection = $wordsCollection->filter(function ($word) {
64
            return strlen($word) >= $this->minWordLen && strlen($word) <= $this->maxWordLen;
65
        })->map(fn($word) => strtoupper($word));
66
67
        return $this;
68
    }
69
70
    protected function initGrid(): self
71
    {
72
        $this->cells = array_fill(0, $this->gridSize * $this->gridSize, null);
73
74
        for ($i = 0; $i < (2 * $this->gridSize * $this->gridSize); $i++) {
75
            $this->columnArray[$i]=$this->getColumnDefault($i);
76
        }
77
78
        return $this;
79
    }
80
81
    public function generate(): self
82
    {
83
        $blocks = $this->gridSize * $this->gridSize;
84
        $i=rand(0, $blocks-1);
85
86
        $complete=0;
87
        while ($complete < $blocks) {
88
            $this->placeWord($i);
89
            $complete++;
90
            $i++;
91
            if ($i==$blocks) {
92
                $i=0;
93
            }
94
        }
95
96
        return $this;
97
    }
98
99
    protected function getRandomWordLength(array $exclude = []): int
100
    {
101
        if (count($exclude) >= ($this->maxWordLen - $this->minWordLen)) {
102
            throw new RuntimeException("Failed to generate a starting word . please add some additional words to your system");
103
        }
104
105
        do {
106
            $len = rand($this->minWordLen, $this->gridSize);
107
        } while (in_array($len, $exclude));
108
109
        $available = $this->wordsCollection->filter(function ($word) use ($len) {
110
            return strlen($word) === $len;
111
        })->count();
112
113
        $exclude[] = $len;
114
115
        return $available > 0 ? $len : $this->getRandomWordLength($exclude);
116
    }
117
118
    protected function addPlacedWord(Word $word, int $increment, int $len): void
119
    {
120
        $string = '';
121
        $flag=false;
122
123
        for ($i=$word->getStart(); $i<=$word->getEnd(); $i+=$increment) {
124
            if ($this->cells[$i]=='') {
125
                $string .= '_';
126
            } else {
127
                $string .= $this->cells[$i];
128
                $flag=true;
129
            }
130
        }
131
132
        if (! $flag) {
0 ignored issues
show
introduced by
The condition $flag is always false.
Loading history...
133
            $randomWord = $this->getRandomWord($len);
134
            $word->setLabel($word->getInversed() ? strrev($randomWord) : $randomWord);
135
            $this->addWord($word);
136
            return;
137
        }
138
139
        if (strpos($string, '_')===false) {
140
            return;
141
        }
142
143
        $word->setLabel($this->getWordLike($string));
144
        $word->setInversed(false);
145
        $this->addWord($word);
146
    }
147
148
    protected function placeWordHorizontally(Word $word, int $len): void
149
    {
150
        $inc = 1;
151
        $word->setEnd($word->getStart()+$len-1);
152
                 // si mot placé sur 2 lignes on décale à gauche
153
        while ($this->columnArray[$word->getEnd()] < $this->columnArray[$word->getStart()]) {
154
            $word->setStart($word->getStart()-1);
155
            $word->setEnd($word->getStart()+$len-1);
156
        }
157
158
        $this->addPlacedWord($word, $inc, $len);
159
    }
160
161
    protected function placeWordVertical(Word $word, int $len): void
162
    {
163
        $inc=$this->gridSize;
164
        $word->setEnd($word->getStart()+($len*$this->gridSize)-$this->gridSize);
165
                // si le mot dépasse la grille en bas, on décale vers le haut
166
        while ($word->getEnd()>($this->gridSize*$this->gridSize)-1) {
167
            $word->setStart($word->getStart()-$this->gridSize);
168
            $word->setEnd($word->getStart()+($len*$this->gridSize)-$this->gridSize);
169
        }
170
171
        $this->addPlacedWord($word, $inc, $len);
172
    }
173
174
    protected function placeWordDiagonallyLtr(Word $word, int $len): void
175
    {
176
        $inc=$this->gridSize+1;
177
        $word->setEnd($word->getStart()+($len*($this->gridSize+1))-($this->gridSize+1));
178
                // si le mot dépasse la grille à droite, on décale à gauche
179
        while ($this->columnArray[$word->getEnd()] < $this->columnArray[$word->getStart()]) {
180
            $word->setStart($word->getStart()-1);
181
            $word->setEnd($word->getStart()+($len*($this->gridSize+1))-($this->gridSize+1));
182
        }
183
                // si le mot dépasse la grille en bas, on décale vers le haut
184
        while ($word->getEnd()>($this->gridSize*$this->gridSize)-1) {
185
            $word->setStart($word->getStart()-$this->gridSize);
186
            $word->setEnd($word->getStart()+($len*($this->gridSize+1))-($this->gridSize+1));
187
        }
188
        $this->addPlacedWord($word, $inc, $len);
189
    }
190
191
    protected function placeWordDiagonallyRtl(Word $word, int $len): void
192
    {
193
        $inc=$this->gridSize-1;
194
        $word->setEnd($word->getStart()+(($len-1)*($this->gridSize-1)));
195
                // si le mot sort de la grille à gauche, on décale à droite
196
        while ($this->columnArray[$word->getEnd()] > $this->columnArray[$word->getStart()]) {
197
            $word->setStart($word->getStart()+1);
198
            $word->setEnd($word->getStart()+(($len-1)*($this->gridSize-1)));
199
        }
200
                // si le mot dépasse la grille en bas, on décale vers le haut
201
        while ($word->getEnd()>($this->gridSize*$this->gridSize)-1) {
202
            $word->setStart($word->getStart()-$this->gridSize);
203
            $word->setEnd($word->getStart()+(($len-1)*($this->gridSize-1)));
204
        }
205
        $this->addPlacedWord($word, $inc, $len);
206
    }
207
208
    protected function placeWord($start): void
209
    {
210
        $len = $this->getRandomWordLength();
211
        $word = Word::createRandom($start);
212
213
        switch ($word->getOrientation()) {
214
            case Word::HORIZONTAL:
215
                $this->placeWordHorizontally($word, $len);
216
                return;
217
218
            case Word::VERTICAL:
219
                $this->placeWordVertical($word, $len);
220
                return;
221
222
            case Word::DIAGONAL_LEFT_TO_RIGHT:
223
                $this->placeWordDiagonallyLtr($word, $len);
224
                return;
225
226
            case Word::DIAGONAL_RIGHT_TO_LEFT:
227
                $this->placeWordDiagonallyRtl($word, $len);
228
                return;
229
        }
230
    }
231
232
    protected function getColumnDefault(int $x): int
233
    {
234
        return ($x % $this->gridSize)+1;
235
    }
236
237
    protected function markWordUsed($word): void
238
    {
239
        $this->wordsCollection = $this->wordsCollection->reject(function ($current) use ($word) {
240
            return $current === $word;
241
        });
242
    }
243
244
    protected function getRandomWord(int $len): string
245
    {
246
        $word = $this->wordsCollection->filter(function ($word) use ($len) {
247
            return strlen($word) === $len;
248
        })->random(1)->first();
249
250
        $this->markWordUsed($word);
251
252
        return $word;
253
    }
254
255
    protected function getWordLike(string $pattern): ?string
256
    {
257
        $pattern = str_replace("_", ".", $pattern);
258
        $words = $this->wordsCollection->filter(function ($word) use ($pattern) {
259
            return preg_match("/^$pattern\$/i", $word);
260
        });
261
262
        if ($words->count() === 0) {
263
            return null;
264
        }
265
266
        $word = $words->random(1)->first();
267
268
        $this->markWordUsed($word);
269
        return $word;
270
    }
271
272
273
    protected function addWord(Word $word): void
274
    {
275
        if ($word->getLabel() === null) {
276
            return;
277
        }
278
279
        $j=0;
280
        switch ($word->getOrientation()) {
281
            case Word::HORIZONTAL:
282
                for ($i=$word->getStart(); $j<strlen($word->getLabel()); $i++) {
283
                    $this->cells[$i]=substr($word->getLabel(), $j, 1);
284
                    $j++;
285
                }
286
                break;
287
288
            case Word::VERTICAL:
289
                for ($i=$word->getStart(); $j<strlen($word->getLabel()); $i+=$this->gridSize) {
290
                    $this->cells[$i]=substr($word->getLabel(), $j, 1);
291
                    $j++;
292
                }
293
                break;
294
295
            case Word::DIAGONAL_LEFT_TO_RIGHT:
296
                for ($i=$word->getStart(); $j<strlen($word->getLabel()); $i+=$this->gridSize+1) {
297
                    $this->cells[$i]=substr($word->getLabel(), $j, 1);
298
                    $j++;
299
                }
300
                break;
301
302
            case Word::DIAGONAL_RIGHT_TO_LEFT:
303
                for ($i=$word->getStart(); $j<strlen($word->getLabel()); $i+=$this->gridSize-1) {
304
                    $this->cells[$i]=substr($word->getLabel(), $j, 1);
305
                    $j++;
306
                }
307
                break;
308
        }
309
        $this->wordsList[]=$word;
310
    }
311
312
    public function getTextGrid()
313
    {
314
        $r = '';
315
        foreach ($this->getGrid() as $idx => $row) {
316
            if ($idx > 0) {
317
                $r .= "\n";
318
            }
319
            $r .= implode(" ", $row);
320
        }
321
        return $r;
322
    }
323
324
    public function getGrid()
325
    {
326
        $return = [];
327
        $column = 0;
328
        $row = 0;
329
        foreach ($this->cells as $letter) {
330
            $cell = $letter ?? chr(rand(65, 90));
331
            $return[$row][$column] = $cell;
332
            $column++;
333
            if ($column === $this->gridSize) {
334
                $row++;
335
                $column = 0;
336
            }
337
        }
338
        return $return;
339
    }
340
341
    public function getPuzzleWords()
342
    {
343
        return collect($this->wordsList)->map(function (Word $word) {
344
            $label = $word->getLabel();
345
            if ($word->getInversed()) {
346
                $label = strrev(/** @scrutinizer ignore-type */$label);
347
            }
348
            return $label;
349
        });
350
    }
351
}
352