Completed
Push — master ( 73004b...66a620 )
by Thijs
02:37
created

DecisionPoint::replaceIfBetter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

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