Completed
Push — master ( ec639f...3272c5 )
by Guillermo
8s
created

SecretSanta::addExclusivePlayers()   B

Complexity

Conditions 5
Paths 11

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 14
cts 14
cp 1
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 14
nc 11
nop 1
crap 5
1
<?php
2
3
namespace SecretSanta;
4
5
use SecretSanta\Exceptions\PlayerException;
6
use SecretSanta\Exceptions\SecretSantaException;
7
8
/**
9
 * Class SecretSanta
10
 * @package SecretSanta
11
 */
12
class SecretSanta
13
{
14
    /** @var PlayersCollection */
15
    private $players;
16
    /** @var  array */
17
    private $combination;
18
19
    /**
20
     * SecretSanta constructor.
21
     */
22 19
    public function __construct()
23
    {
24 19
        $this->players = new PlayersCollection();
25 19
    }
26
27
    /**
28
     * Add single player to the game
29
     * @param string $name
30
     * @param string $email
31
     * @return SecretSanta
32
     */
33 7
    public function addPlayer($name, $email)
34
    {
35 7
        $this->players->addPlayer(Player::create($name, $email));
36
37 7
        return $this;
38
    }
39
40
    /**
41
     * Add two excluded players to the game
42
     * @param string $name
43
     * @param string $email
44
     * @param string $coupleName
45
     * @param string $coupleEmail
46
     * @return SecretSanta
47
     */
48 12
    public function addCouple($name, $email, $coupleName, $coupleEmail)
49
    {
50 12
        $this->players->addCouple(
51 12
            Player::create($name, $email),
52 12
            Player::create($coupleName, $coupleEmail)
53 12
        );
54
55 11
        return $this;
56
    }
57
58
    /**
59
     * Add multiple exclusive players to the game
60
     * @param array $playersData
61
     * @return $this
62
     * @throws SecretSantaException
63
     */
64 7
    public function addExclusivePlayers(...$playersData)
65
    {
66 7
        if (count($playersData) < 2) {
67 2
            throw new SecretSantaException('Number of players must be grater or equal than two.');
68
        }
69
70
        try {
71 5
            $players = [];
72 5
            foreach ($playersData as $playerData) {
73 5
                list($name, $email) = array_values($playerData);
74 5
                $players[] = Player::create($name, $email);
75 4
            }
76
77 4
            $this->players->addExclusivePlayers($players);
78 5
        } catch (PlayerException $exception) {
79 1
            throw new SecretSantaException('One o more players are invalids.', $exception);
80 1
        } catch (\Exception $exception) {
81 1
            throw new SecretSantaException('An error has occurred.', $exception);
82
        }
83
84 3
        return $this;
85
    }
86
87
    /**
88
     * @return Player[]
89
     * @throws SecretSantaException
90
     */
91 12
    public function play()
92
    {
93
        try {
94 12
            $this->combinePlayers();
95
96 10
            return $this->associatePlayers();
97 2
        } catch (SecretSantaException $exception) {
98 2
            throw  $exception;
99
        } catch (\Exception $exception) {
100
            throw new SecretSantaException('Error during play, impossible to find secret santa, try again', $exception);
101
        }
102
    }
103
104
    /**
105
     * @throws SecretSantaException
106
     */
107 12
    private function combinePlayers()
108
    {
109 12
        if (count($this->players) < 4) {
110 2
            throw new SecretSantaException("Not enough players to play, at least 4 players are required");
111
        }
112
113 10
        $retry = count($this->players) + $this->players->countExclusivePlayers();
114
115 10
        while (!$this->tryMatchSecretSantaPlayers() && $retry > 0 ) {
116
            $retry--;
117
        }
118
119 10
        if (!$this->isValidCombination()) {
120
            throw new SecretSantaException("Not enough players to play");
121
        }
122 10
    }
123
124
    /**
125
     * @return bool
126
     */
127 10
    private function tryMatchSecretSantaPlayers()
128
    {
129 10
        $this->combination = [];
130 10
        $secretPlayers = $this->players->shufflePlayers();
131 10
        foreach ($this->players->players() as $playerId => $player) {
132 10
            foreach ($secretPlayers as $secretPlayer) {
133 10
                if ($this->isValidSecretSanta($player, $secretPlayer)) {
134 10
                    $this->combination[$player->id()] = $secretPlayer->id();
135 10
                    unset ($secretPlayers[$secretPlayer->id()]);
136 10
                    break;
137
                }
138 10
            }
139 10
        }
140
141 10
        return $this->isValidCombination();
142
    }
143
144
    /**
145
     * @param Player $player
146
     * @param Player $secretPlayer
147
     * @return bool
148
     */
149 10
    private function isValidSecretSanta($player, $secretPlayer)
150
    {
151 10
        if ($player->id() != $secretPlayer->id() && !$this->players->areExclusive($player, $secretPlayer)) {
152 10
            if (!in_array($secretPlayer->id(), $this->combination)) {
153 10
                return true;
154
            }
155
        }
156
157 9
        return false;
158
    }
159
160
    /**
161
     * @return bool
162
     */
163 10
    private function isValidCombination()
164
    {
165 10
        return count($this->combination) > 0 && count($this->combination) == count($this->players);
166
    }
167
168 10
    private function associatePlayers()
169
    {
170 10
        foreach ($this->combination as $playerId => $secretSantaId) {
171 10
            $this->players->player($playerId)->setSecretSanta(
172 10
                $this->players->player($secretSantaId)
173 10
            );
174 10
        }
175
176 10
        return $this->players->players();
177
    }
178
}
179