Completed
Push — master ( c0475c...73004b )
by Thijs
04:33
created

Engine   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 112
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%
Metric Value
wmc 19
lcom 1
cbo 4
dl 0
loc 112
ccs 55
cts 55
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A decide() 0 11 3
A decideMax() 0 6 2
A decideMin() 0 6 2
B decideVar() 0 25 4
A considerMove() 0 19 2
A considerNextMove() 0 7 2
A replaceIfBetter() 0 16 3
1
<?php
2
3
namespace lucidtaz\minimax;
4
5
use BadMethodCallException;
6
use Closure;
7
use RuntimeException;
8
9
class Engine
10
{
11
    private $objectivePlayer;
12
13
    private $maxDepth;
14
15 12
    public function __construct(Player $objectivePlayer, int $maxDepth = 3)
16
    {
17 12
        $this->objectivePlayer = $objectivePlayer;
18 12
        $this->maxDepth = $maxDepth;
19 12
    }
20
21
    /**
22
     * Evaluate possible decisions and take the best one
23
     */
24 12
    public function decide(GameState $state): Decision
25
    {
26 12
        if (!$state->getNextPlayer()->equals($this->objectivePlayer)) {
27 1
            throw new BadMethodCallException('It is not this players turn');
28
        }
29 11
        $decisionWithScore = $this->decideMax($state, $this->maxDepth);
30 11
        if ($decisionWithScore->decision === null) {
31 1
            throw new RuntimeException('There are no possible moves');
32
        }
33 10
        return $decisionWithScore->decision;
34
    }
35
36 11
    private function decideMax(GameState $state, int $depthLeft)
37
    {
38
        return $this->decideVar($state, $depthLeft, function (DecisionWithScore $a, DecisionWithScore $b) {
39 9
            return $a->isBetterThan($b) ? $a : $b;
40 11
        });
41
    }
42
43
    private function decideMin(GameState $state, int $depthLeft)
44
    {
45 10
        return $this->decideVar($state, $depthLeft, function (DecisionWithScore $a, DecisionWithScore $b) {
46 9
            return $b->isBetterThan($a) ? $a : $b;
47 10
        });
48
    }
49
50 11
    private function decideVar(GameState $state, int $depthLeft, Closure $ideal)
51
    {
52 11
        if ($depthLeft == 0) {
53 9
            $result = new DecisionWithScore;
54 9
            $result->age = $depthLeft;
55 9
            $result->score = $state->evaluateScore($this->objectivePlayer);
56 9
            return $result;
57
        }
58
59
        /* @var $possibleMoves Decision[] */
60 11
        $possibleMoves = $state->getDecisions();
61 11
        if (empty($possibleMoves)) {
62 9
            $result = new DecisionWithScore;
63 9
            $result->age = $depthLeft;
64 9
            $result->score = $state->evaluateScore($this->objectivePlayer);
65 9
            return $result;
66
        }
67
68 10
        $bestDecisionWithScore = null;
69 10
        foreach ($possibleMoves as $move) {
70 10
            $bestDecisionWithScore = $this->considerMove($move, $state, $depthLeft, $ideal, $bestDecisionWithScore);
71
        }
72
73 10
        return $bestDecisionWithScore;
74
    }
75
76 10
    private function considerMove(Decision $move, GameState $state, int $depthLeft, Closure $ideal, DecisionWithScore $bestDecisionWithScoreSoFar = null): DecisionWithScore
77
    {
78 10
        $newState = $move->apply($state);
79
80 10
        $nextDecisionWithScore = $this->considerNextMove($newState, $depthLeft - 1);
81
82 10
        $replaced = false;
83 10
        $bestDecisionWithScore = $this->replaceIfBetter(
84
            $ideal,
85
            $nextDecisionWithScore,
86
            $bestDecisionWithScoreSoFar,
87
            $replaced
88
        );
89 10
        if ($replaced) {
90 10
            $bestDecisionWithScore->decision = $move;
91
        }
92
93 10
        return $bestDecisionWithScore;
94
    }
95
96 10
    private function considerNextMove(GameState $state, $depthLeft): DecisionWithScore
97
    {
98 10
        if ($state->getNextPlayer()->isFriendsWith($this->objectivePlayer)) {
99 9
            return $this->decideMax($state, $depthLeft);
100
        }
101 10
        return $this->decideMin($state, $depthLeft);
102
    }
103
104 10
    private function replaceIfBetter(Closure $ideal, DecisionWithScore $new, DecisionWithScore $current = null, &$replaced = false): DecisionWithScore
105
    {
106 10
        if ($current === null) {
107 10
            $replaced = true;
108 10
            return $new;
109
        }
110
111 9
        $idealDecisionWithScore = $ideal($new, $current);
112 9
        if ($idealDecisionWithScore === $new) {
113 7
            $replaced = true;
114 7
            return $new;
115
        }
116
117 9
        $replaced = false;
118 9
        return $current;
119
    }
120
}
121