Board   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 265
Duplicated Lines 3.02 %

Coupling/Cohesion

Components 3
Dependencies 1

Importance

Changes 0
Metric Value
wmc 47
lcom 3
cbo 1
dl 8
loc 265
rs 8.64
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A addActions() 0 6 2
A addAction() 0 4 1
A getAction() 0 8 2
A addFieldTypes() 0 6 2
A hasAction() 0 4 1
A addFieldType() 0 4 1
A loadFromString() 0 13 3
A addActor() 0 10 1
A addExit() 0 7 1
A addFunctions() 0 6 2
A addFunction() 0 4 1
A isActorAtExit() 0 7 2
A runActor() 0 4 1
A runActorProgram() 0 6 2
A runActorOperation() 0 4 1
A getActorNextMove() 0 7 1
A setVariable() 0 4 1
A getVariable() 0 4 1
A getFunction() 0 4 1
A moveActor() 0 23 2
A debug() 0 3 1
A setField() 0 4 1
A getField() 0 4 1
A getActorDirection() 0 4 1
A setActorDirection() 0 4 1
A getActorPick() 0 4 1
A setActorPick() 0 4 1
A getActorPosition() 0 4 1
B renderBoard() 8 28 7
A getWidth() 0 4 1
A getHeight() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Board 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Board, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
namespace Thunder\Logeek;
4
5
final class Board
6
{
7
    private $width;
8
    private $height;
9
    private $fieldTypes = [];
10
    private $fields = [];
11
    private $actors = [];
12
    private $exits = [];
13
    private $functions = [];
14
    private $variables = [];
15
    private $actions = [];
16
17
    private static $moveMap = [
18
        'left' => [0, -1],
19
        'right' => [0, 1],
20
        'up' => [-1, 0],
21
        'down' => [1, 0],
22
    ];
23
24
    public function __construct(int $width, int $height)
25
    {
26
        $this->width = $width;
27
        $this->height = $height;
28
    }
29
30
    /* --- METHODS --- */
31
32
    /**
33
     * @param ActionInterface[] $actions
34
     */
35
    public function addActions(array $actions)
36
    {
37
        foreach($actions as $action) {
38
            $this->addAction($action);
39
        }
40
    }
41
42
    public function addAction(ActionInterface $action)
43
    {
44
        $this->actions[$action->getAlias()] = $action;
45
    }
46
47
    public function getAction(string $alias): ActionInterface
48
    {
49
        if(!$this->hasAction($alias)) {
50
            throw new \RuntimeException(sprintf('Action %s does not exist!', $alias));
51
        }
52
53
        return $this->actions[$alias];
54
    }
55
56
    public function addFieldTypes(array $types)
57
    {
58
        foreach($types as $alias => $symbol) {
59
            $this->addFieldType($alias, $symbol);
60
        }
61
    }
62
63
    public function hasAction($alias): bool
64
    {
65
        return array_key_exists($alias, $this->actions);
66
    }
67
68
    public function addFieldType($alias, $symbol)
69
    {
70
        $this->fieldTypes[$alias] = $symbol;
71
    }
72
73
    public function loadFromString($string)
74
    {
75
        $types = array_flip($this->fieldTypes);
76
        $lines = explode("\n", trim($string));
77
        $height = \count($lines);
78
        $width = \strlen(trim($lines[0]));
79
        for($i = 0; $i < $height; $i++) {
80
            $line = trim($lines[$i]);
81
            for($j = 0; $j < $width; $j++) {
82
                $this->fields[$i][$j] = $types[$line[$j]];
83
            }
84
        }
85
    }
86
87
    public function addActor($alias, $x, $y, $direction)
88
    {
89
        $this->actors[$alias] = [
90
            'x' => $x,
91
            'y' => $y,
92
            'direction' => $direction,
93
            'pick' => null,
94
        ];
95
        $this->debug('Actor Alias[%s] Direction[%s] Position[%s:%s]', $alias, $direction, $y, $x);
96
    }
97
98
    public function addExit($alias, $x, $y)
99
    {
100
        $this->exits[$alias] = [
101
            'x' => $y,
102
            'y' => $x,
103
        ];
104
    }
105
106
    public function addFunctions(array $functions)
107
    {
108
        foreach($functions as $name => $program) {
109
            $this->functions[$name] = $program;
110
        }
111
    }
112
113
    public function addFunction($name, array $program)
114
    {
115
        $this->functions[$name] = $program;
116
    }
117
118
    public function isActorAtExit($actor, $exit): bool
119
    {
120
        $actor = $this->actors[$actor];
121
        $exit = $this->exits[$exit];
122
123
        return $actor['x'] === $exit['x'] && $actor['y'] === $exit['y'];
124
    }
125
126
    public function runActor($alias)
127
    {
128
        $this->runActorProgram($alias, $this->functions['main']);
129
    }
130
131
    public function runActorProgram($alias, array $program)
132
    {
133
        foreach($program as $operation) {
134
            $this->runActorOperation($alias, $operation);
135
        }
136
    }
137
138
    private function runActorOperation($alias, array $operation)
139
    {
140
        $this->getAction($operation['action'])->execute($this, $alias, $operation);
141
    }
142
143
    public function getActorNextMove($alias): array
144
    {
145
        list($x, $y) = $this->getActorPosition($alias);
146
        list($diffX, $diffY) = static::$moveMap[$this->getActorDirection($alias)];
0 ignored issues
show
Comprehensibility introduced by
Since Thunder\Logeek\Board is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
147
148
        return [$x + $diffX, $y + $diffY];
149
    }
150
151
    public function setVariable($name, $value)
152
    {
153
        $this->variables[$name] = $value;
154
    }
155
156
    public function getVariable($name)
157
    {
158
        return $this->variables[$name];
159
    }
160
161
    public function getFunction($name)
162
    {
163
        return $this->functions[$name];
164
    }
165
166
    public function moveActor($alias)
167
    {
168
        $moveMap = [
169
            'left' => [0, -1],
170
            'right' => [0, 1],
171
            'up' => [-1, 0],
172
            'down' => [1, 0],
173
        ];
174
175
        $actor = $this->actors[$alias];
176
        list($x, $y) = $moveMap[$actor['direction']];
177
        $newX = $actor['x'] + $x;
178
        $newY = $actor['y'] + $y;
179
180
        $this->debug('Move [%s:%s] -> [%s:%s]', $actor['y'], $actor['x'], $newY, $newX);
181
        if('wall' === $this->fields[$newX][$newY]) {
182
            throw new \RuntimeException(sprintf('Wall at [%s, %s]', $newX, $newY));
183
        }
184
185
        $actor['x'] = $newX;
186
        $actor['y'] = $newY;
187
        $this->actors[$alias] = $actor;
188
    }
189
190
    public function debug(...$args)
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
191
    {
192
    }
193
194
    public function setField($x, $y, $field)
195
    {
196
        $this->fields[$x][$y] = $field;
197
    }
198
199
    public function getField($x, $y)
200
    {
201
        return $this->fields[$x][$y];
202
    }
203
204
    public function getActorDirection($alias)
205
    {
206
        return $this->actors[$alias]['direction'];
207
    }
208
209
    public function setActorDirection($alias, $direction)
210
    {
211
        $this->actors[$alias]['direction'] = $direction;
212
    }
213
214
    public function getActorPick($alias)
215
    {
216
        return $this->actors[$alias]['pick'];
217
    }
218
219
    public function setActorPick($alias, $pick)
220
    {
221
        $this->actors[$alias]['pick'] = $pick;
222
    }
223
224
    public function getActorPosition($alias): array
225
    {
226
        return [$this->actors[$alias]['x'], $this->actors[$alias]['y']];
227
    }
228
229
    public function renderBoard(): string
230
    {
231
        $return = '';
232
        $return .= "\n";
233
        for($i = 0; $i < $this->height; $i++) {
234
            for($j = 0; $j < $this->width; $j++) {
235
                $symbol = $this->fieldTypes[$this->fields[$i][$j]];
236 View Code Duplication
                $isActor = array_reduce($this->actors, function($state, array $item) use ($i, $j) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237
                    $state += $item['x'] === $i && $item['y'] === $j;
238
                    return $state;
239
                }, 0);
240 View Code Duplication
                $isExit = array_reduce($this->exits, function($state, array $item) use ($i, $j) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
                    $state += $item['x'] === $i && $item['y'] === $j;
242
                    return $state;
243
                }, 0);
244
                if($isExit) {
245
                    $symbol = 'E';
246
                }
247
                if($isActor) {
248
                    $symbol = 'A';
249
                }
250
                $return .= $symbol;
251
            }
252
            $return .= "\n";
253
        }
254
255
        return $return;
256
    }
257
258
    /* --- GETTERS --- */
259
260
    public function getWidth(): int
261
    {
262
        return $this->width;
263
    }
264
265
    public function getHeight(): int
266
    {
267
        return $this->height;
268
    }
269
}
270