Engine::decide()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4

Importance

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