Completed
Push — master ( 0c19f1...225a83 )
by Thijs
04:18
created

Engine::decide()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 15
cts 15
cp 1
rs 8.6845
c 0
b 0
f 0
cc 4
eloc 16
nc 4
nop 1
crap 4
1
<?php
2
3
namespace lucidtaz\minimax\engine;
4
5
use BadMethodCallException;
6
use LogicException;
7
use lucidtaz\minimax\game\GameState;
8
use lucidtaz\minimax\game\Player;
9
use RuntimeException;
10
11
/**
12
 * MiniMax game engine
13
 *
14
 * Construct an object of this class, give it the player to optimize for, and
15
 * call decide() when it is time for the player to make a move, in order to get
16
 * the Decision that the engine has taken for the player.
17
 */
18
class Engine
19
{
20
    private $objectivePlayer;
21
22
    private $maxDepth;
23
24
    /**
25
     * @var ?Analytics Only available after run, contains statistics about the
26
     * execution.
27
     */
28
    private $analytics;
29
30
    /**
31
     * @param Player $objectivePlayer The player to play as
32
     * @param int $maxDepth How far ahead should the engine look?
33
     */
34 16
    public function __construct(Player $objectivePlayer, int $maxDepth = 3)
35
    {
36 16
        $this->objectivePlayer = $objectivePlayer;
37 16
        $this->maxDepth = $maxDepth;
38 16
    }
39
40
    /**
41
     * Evaluate possible decisions and take the best one
42
     * @param GameState $state Current state of the game for which there needs
43
     * to be made a decision. This implicitly means that the objective player
44
     * currently must have its turn in the GameState.
45
     * @return GameState The state resulting after the engine made its decision.
46
     */
47 16
    public function decide(GameState $state): GameState
48
    {
49 16
        if (!$state->getNextPlayer()->equals($this->objectivePlayer)) {
50 1
            throw new BadMethodCallException('It is not this players turn');
51
        }
52 15
        if (empty($state->getPossibleMoves())) {
53 1
            throw new RuntimeException('There are no possible moves');
54
        }
55
56 14
        $rootNode = new DecisionNode(
57 14
            $this->objectivePlayer,
58
            $state,
59 14
            $this->maxDepth,
60 14
            NodeType::MAX(),
61 14
            AlphaBeta::initial()
62
        );
63
64 14
        $moveWithEvaluation = $rootNode->traverseGameTree();
65 14
        $this->analytics = $moveWithEvaluation->analytics;
66 14
        if ($moveWithEvaluation->move === null) {
67 1
            throw new LogicException('Could not find move even though there are moves. Is the maxdepth parameter correct?');
68
        }
69 13
        return $moveWithEvaluation->move;
70
    }
71
72 1
    public function getAnalytics(): Analytics
73
    {
74 1
        if ($this->analytics === null) {
75
            throw new BadMethodCallException('Please run decide() first.');
76
        }
77 1
        return $this->analytics;
78
    }
79
}
80