Initial Commit
This commit is contained in:
commit
ddf09fe00c
113 changed files with 187148 additions and 0 deletions
10
app/Game/Exceptions/Exception.php
Normal file
10
app/Game/Exceptions/Exception.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Game\Exceptions;
|
||||
|
||||
use Exception as BaseException;
|
||||
|
||||
class Exception extends BaseException
|
||||
{
|
||||
//
|
||||
}
|
||||
8
app/Game/Exceptions/NotEnoughCardsException.php
Normal file
8
app/Game/Exceptions/NotEnoughCardsException.php
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Game\Exceptions;
|
||||
|
||||
class NotEnoughCardsException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
||||
93
app/Game/GameBoard.php
Normal file
93
app/Game/GameBoard.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
namespace App\Game;
|
||||
|
||||
use App\Models\Card;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Game\Exceptions\NotEnoughCardsException;
|
||||
|
||||
class GameBoard
|
||||
{
|
||||
/**
|
||||
* Collection of the cards on the board.
|
||||
*/
|
||||
protected Collection $cards;
|
||||
|
||||
/**
|
||||
* State of the cards on the board.
|
||||
*/
|
||||
protected GameBoardState $state;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new board.
|
||||
*/
|
||||
public function __construct(GameBoardState $state)
|
||||
{
|
||||
$this->state = $state;
|
||||
$this->regenerate(new GameSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cards.
|
||||
*/
|
||||
public function getCards()
|
||||
{
|
||||
return $this->cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the state
|
||||
*/
|
||||
public function getState() : GameBoardState
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the board.
|
||||
*/
|
||||
public function getSize() : int
|
||||
{
|
||||
return $this->state->getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the game board.
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->state->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the board is in an ended state.
|
||||
* E.g: all cards is in a winning state.
|
||||
*/
|
||||
public function hasGameEnded() : bool
|
||||
{
|
||||
return $this->state->isFull(GameBoardState::STATE_WIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the board with new cards.
|
||||
*/
|
||||
public function regenerate(GameSettings $settings) : self
|
||||
{
|
||||
$num_cards = $this->getSize();
|
||||
$cards = Card::getBySettings($settings, $num_cards);
|
||||
|
||||
if (count($cards) < $num_cards) {
|
||||
$message = sprintf("Impossible to generate cards: requested %d but only %d returned", $num_cards, count($cards));
|
||||
throw new NotEnoughCardsException($message);
|
||||
}
|
||||
|
||||
$this->cards = $cards;
|
||||
|
||||
// Reset the state if we set new cards.
|
||||
$this->clear();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
241
app/Game/GameBoardState.php
Normal file
241
app/Game/GameBoardState.php
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
|
||||
namespace App\Game;
|
||||
|
||||
class GameBoardState
|
||||
{
|
||||
/**
|
||||
* Cell states. (unset is default state)
|
||||
*/
|
||||
const STATE_PRESSED = 0;
|
||||
const STATE_WIN = 1;
|
||||
|
||||
/**
|
||||
* Width of the board
|
||||
*/
|
||||
protected int $width;
|
||||
|
||||
/**
|
||||
* Height of the board
|
||||
*/
|
||||
protected int $height;
|
||||
|
||||
/**
|
||||
* State of the cells.
|
||||
*/
|
||||
protected array $state = [];
|
||||
|
||||
/**
|
||||
* If the board is in a winning state or not.
|
||||
*/
|
||||
protected int $winning = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($width, $height)
|
||||
{
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of a cell.
|
||||
*/
|
||||
public function set(int $pos, bool $pressed = true) : self
|
||||
{
|
||||
if ($this->hasGameEnded()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($pressed) {
|
||||
$this->state[$pos] = self::STATE_PRESSED;
|
||||
} else {
|
||||
unset($this->state[$pos]);
|
||||
}
|
||||
|
||||
$this->update();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle pressed state of a cell.
|
||||
*/
|
||||
public function toggle(int $pos) : self
|
||||
{
|
||||
return $this->set($pos, !$this->isPressed($pos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cell is pressed or not.
|
||||
*/
|
||||
public function isPressed(int $pos) : bool
|
||||
{
|
||||
return isset($this->state[$pos]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cell is part of a winning row/column.
|
||||
*/
|
||||
public function isWin(int $pos) : bool
|
||||
{
|
||||
return isset($this->state[$pos]) && $this->state[$pos] == self::STATE_WIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sate of the cells.
|
||||
*/
|
||||
public function getState() : array
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the board.
|
||||
*/
|
||||
public function getSize() : int
|
||||
{
|
||||
return $this->width * $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the board.
|
||||
*/
|
||||
public function clear() : self
|
||||
{
|
||||
$this->state = [];
|
||||
$this->winning = 0;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// TODO: Rename
|
||||
public function getNumWinRows() : int
|
||||
{
|
||||
return $this->winning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the board only has winning cards
|
||||
*/
|
||||
public function hasGameEnded() : bool
|
||||
{
|
||||
if (count($this->state) < $this->width * $this->height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($this->state as $pos) {
|
||||
if ($pos !== self::STATE_WIN) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the board is full and all positions is in a given state.
|
||||
*/
|
||||
public function isFull($state) : bool
|
||||
{
|
||||
// Board not filled, impossible to have all positions set.
|
||||
if (count($this->state) < $this->width * $this->height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($this->state as $pos) {
|
||||
if ($pos !== $state) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function update()
|
||||
{
|
||||
foreach($this->state as $pos => $_) {
|
||||
$this->state[$pos] = self::STATE_PRESSED;
|
||||
}
|
||||
|
||||
$patterns = $this->checkWinningState();
|
||||
foreach($patterns as $cards) {
|
||||
foreach($cards as $pos) {
|
||||
$this->state[$pos] = self::STATE_WIN;
|
||||
}
|
||||
}
|
||||
$this->winning = count($patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the board is in a winning state.
|
||||
*/
|
||||
protected function checkWinningState() : array
|
||||
{
|
||||
$win = [];
|
||||
|
||||
// Rows
|
||||
for($y=0; $y < $this->height; $y++) {
|
||||
$c = $this->checkRow($y);
|
||||
if (count($c)) {
|
||||
$win[] = $c;
|
||||
}
|
||||
}
|
||||
|
||||
// Columns
|
||||
for($x=0; $x < $this->width; $x++) {
|
||||
$c = $this->checkCol($x);
|
||||
if (count($c)) {
|
||||
$win[] = $c;
|
||||
}
|
||||
}
|
||||
|
||||
return $win;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a row is in a winning state (all cells pressed)
|
||||
*/
|
||||
protected function checkRow($y) : array
|
||||
{
|
||||
$cards = [];
|
||||
|
||||
$y = $y * $this->width;
|
||||
for($x = 0; $x < $this->width; $x++) {
|
||||
$pos = $x + $y;
|
||||
if (!$this->isPressed($pos)) {
|
||||
return [];
|
||||
}
|
||||
/*
|
||||
if ($this->state[$pos] == self::STATE_WIN) {
|
||||
continue;
|
||||
} */
|
||||
$cards[] = $pos;
|
||||
}
|
||||
|
||||
return $cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a column is in a winning state (all cells pressed)
|
||||
*/
|
||||
protected function checkCol($x) : array
|
||||
{
|
||||
$cards = [];
|
||||
|
||||
for($y = 0; $y < $this->height; $y++) {
|
||||
$pos = $x + ($y * $this->width);
|
||||
if (!$this->isPressed($pos)) {
|
||||
return [];
|
||||
}
|
||||
/*
|
||||
if ($this->state[$pos] == self::STATE_WIN) {
|
||||
continue;
|
||||
} */
|
||||
$cards[] = $pos;
|
||||
}
|
||||
|
||||
return $cards;
|
||||
}
|
||||
}
|
||||
36
app/Game/GameService.php
Normal file
36
app/Game/GameService.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Game;
|
||||
|
||||
class GameService
|
||||
{
|
||||
protected GameSession $session;
|
||||
|
||||
public function __construct(GameSession $session)
|
||||
{
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function session() : GameSession
|
||||
{
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new Game session
|
||||
*/
|
||||
public function newSession(?GameSettings $settings = null) : self
|
||||
{
|
||||
// Generate new cards
|
||||
if (!$settings) {
|
||||
$settings = $this->session()->getSettings();
|
||||
}
|
||||
|
||||
$this->session->getBoard()->regenerate($settings);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
69
app/Game/GameSession.php
Normal file
69
app/Game/GameSession.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Game;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class GameSession
|
||||
{
|
||||
/**
|
||||
* The Game Board
|
||||
*/
|
||||
protected GameBoard $board;
|
||||
|
||||
/**
|
||||
* Game settings for this session.
|
||||
*/
|
||||
protected GameSettings $settings;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
//protected $cards;
|
||||
|
||||
public function __construct(GameBoard $board, GameSettings $settings)
|
||||
{
|
||||
$this->board = $board;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getBoard() : GameBoard
|
||||
{
|
||||
return $this->board;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getBoardState() : GameBoardState
|
||||
{
|
||||
return $this->getBoard()->getState();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getCards() : Collection
|
||||
{
|
||||
return $this->getBoard()->getCards();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getSettings() : GameSettings
|
||||
{
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function setSettings(GameSettings $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
}
|
||||
66
app/Game/GameSettings.php
Normal file
66
app/Game/GameSettings.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Game;
|
||||
|
||||
use App\Support\Set;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class GameSettings
|
||||
{
|
||||
public Set $classes;
|
||||
|
||||
public Set $roles;
|
||||
|
||||
public Set $characters;
|
||||
|
||||
public Set $raids;
|
||||
|
||||
public function __construct($settings = [])
|
||||
{
|
||||
$this->classes = new Set();
|
||||
$this->characters = new Set();
|
||||
$this->raids = new Set();
|
||||
$this->roles = new Set();
|
||||
$this->fromArray($settings);
|
||||
}
|
||||
|
||||
public function fromArray(array $array)
|
||||
{
|
||||
if (isset($array['classes'])) {
|
||||
$this->classes->fill($array['classes']);
|
||||
}
|
||||
|
||||
if (isset($array['raids'])) {
|
||||
$this->raids->fill($array['raids']);
|
||||
}
|
||||
|
||||
if (isset($array['characters'])) {
|
||||
$this->characters->fill($array['characters']);
|
||||
}
|
||||
|
||||
if (isset($array['roles'])) {
|
||||
$this->roles->fill($array['roles']);
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray() : array
|
||||
{
|
||||
return [
|
||||
'classes' => $this->classes->toArray(),
|
||||
'roles' => $this->roles->toArray(),
|
||||
'raids' => $this->raids->toArray(),
|
||||
'characters' => $this->characters->toArray()
|
||||
];
|
||||
}
|
||||
|
||||
public function all() : Collection
|
||||
{
|
||||
return collect([
|
||||
'classes' => $this->classes->all()->toArray(),
|
||||
'roles' => $this->roles->all()->toArray(),
|
||||
'raids' => $this->raids->all()->toArray(),
|
||||
'characters' => $this->characters->all()->toArray()
|
||||
])->filter();
|
||||
}
|
||||
}
|
||||
Reference in a new issue