Completed
Push — master ( c94f97...665cde )
by Thijs
03:59
created

DecisionPoint::considerNextMove()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 2
1
<?php
2
3
namespace lucidtaz\minimax\engine;
4
5
use Closure;
6
use lucidtaz\minimax\game\Decision;
7
use lucidtaz\minimax\game\GameState;
8
use lucidtaz\minimax\game\Player;
9
10
class DecisionPoint
11
{
12
    private $objectivePlayer;
13
    private $state;
14
    private $depthLeft;
15
    private $ideal;
16
17
    /**
18
     * @param Player $objectivePlayer The Player to optimize for
19
     * @param GameState $state Current GameState to base decisions on
20
     * @param int $depthLeft Recursion limiter
21
     * @param Closure $ideal Function that takes two DecisionWithScore objects
22
     * and returns the ideal one. In some situations this is the best, in others
23
     * it is the worst.
24
     */
25 12
    public function __construct(Player $objectivePlayer, GameState $state, int $depthLeft, Closure $ideal)
26
    {
27 12
        $this->objectivePlayer = $objectivePlayer;
28 12
        $this->state = $state;
29 12
        $this->depthLeft = $depthLeft;
30 12
        $this->ideal = $ideal;
31 12
    }
32
33 12
    public function decide(): DecisionWithScore
34
    {
35 12
        if ($this->depthLeft == 0) {
36 10
            return $this->makeLeafResult();
37
        }
38
39
        /* @var $possibleMoves Decision[] */
40 12
        $possibleMoves = $this->state->getDecisions();
41 12
        if (empty($possibleMoves)) {
42 10
            return $this->makeLeafResult();
43
        }
44
45 11
        $bestDecisionWithScore = null;
46 11
        foreach ($possibleMoves as $move) {
47 11
            $bestDecisionWithScore = $this->considerMove($move, $bestDecisionWithScore);
48
        }
49
50 11
        return $bestDecisionWithScore;
51
    }
52
53 12
    private function makeLeafResult(): DecisionWithScore
54
    {
55 12
        $result = new DecisionWithScore;
56 12
        $result->age = $this->depthLeft;
57 12
        $result->score = $this->state->evaluateScore($this->objectivePlayer);
58 12
        return $result;
59
    }
60
61 11
    private function considerMove(Decision $move, DecisionWithScore $bestDecisionWithScoreSoFar = null): DecisionWithScore
62
    {
63 11
        $newState = $move->apply($this->state);
64
65 11
        $nextDecisionWithScore = $this->considerNextMove($newState);
66
67 11
        $replaced = false;
68 11
        $bestDecisionWithScore = $this->replaceIfBetter(
69
            $nextDecisionWithScore,
70
            $bestDecisionWithScoreSoFar,
71
            $replaced
72
        );
73 11
        if ($replaced) {
74 11
            $bestDecisionWithScore->decision = $move;
75
        }
76
77 11
        return $bestDecisionWithScore;
78
    }
79
80 11
    private function considerNextMove(GameState $newState): DecisionWithScore
81
    {
82 11
        $nextPlayerIsFriendly = $newState->getNextPlayer()->isFriendsWith($this->objectivePlayer);
83 11
        $comparator = $nextPlayerIsFriendly ? DecisionWithScore::getBestComparator() : DecisionWithScore::getWorstComparator();
84 11
        $nextDecisionPoint = new static($this->objectivePlayer, $newState, $this->depthLeft - 1, $comparator);
85 11
        return $nextDecisionPoint->decide();
86
    }
87
88 11
    private function replaceIfBetter(DecisionWithScore $new, DecisionWithScore $current = null, &$replaced = false): DecisionWithScore
89
    {
90 11
        if ($current === null) {
91 11
            $replaced = true;
92 11
            return $new;
93
        }
94
95 10
        $ideal = $this->ideal;
96 10
        $idealDecisionWithScore = $ideal($new, $current);
97 10
        if ($idealDecisionWithScore === $new) {
98 8
            $replaced = true;
99 8
            return $new;
100
        }
101
102 10
        $replaced = false;
103 10
        return $current;
104
    }
105
}
106