Created
August 24, 2018 09:34
-
-
Save vpArth/d8f2775f2cfdde9ebb5cb1a24ac3b345 to your computer and use it in GitHub Desktop.
TicTacToe implementation (variable map size, win condition, players set)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class TicTacToeGame | |
{ | |
/** @var int */ | |
private $width; | |
/** @var int */ | |
private $height; | |
/** @var int */ | |
private $winCond; | |
/** @var array */ | |
private $players; | |
private $currentPlayer = 0; | |
private $turnsLeft; | |
private $map = []; | |
private $winner = null; | |
private $isOver = false; | |
public function __construct($width = 3, $height = 3, $winCond = 3, $players = ['X', 'O']) | |
{ | |
if ($winCond > min($width, $height)) throw new InvalidArgumentException('$win can not be greater than size'); | |
$this->width = $width; | |
$this->height = $height; | |
$this->winCond = $winCond; | |
$this->players = $players; | |
$this->start(); | |
} | |
public function turn($i, $j) | |
{ | |
if ($i < 0 or $i >= $this->width) throw new InvalidArgumentException('Invalid $i arg'); | |
if ($j < 0 or $j >= $this->height) throw new InvalidArgumentException('Invalid $j arg'); | |
if (!is_null($this->map[$j][$i])) throw new InvalidArgumentException('Field is busy!');; | |
$this->map[$j][$i] = $this->currentPlayer; | |
$this->turnsLeft--; | |
if ($this->checkWin($i, $j)) { | |
$this->winner = $this->currentPlayer; | |
$this->isOver = true; | |
} elseif ($this->turnsLeft <= 0) { | |
// Draw | |
$this->isOver = true; | |
} else { | |
$this->nextPlayer(); | |
} | |
} | |
private function nextPlayer() | |
{ | |
$this->currentPlayer = ($this->currentPlayer + 1) % count($this->players); | |
} | |
public function getPlayer($player = null) | |
{ | |
return $this->players[$player ?? $this->currentPlayer]; | |
} | |
private function checkWin($i, $j) | |
{ | |
$player = $this->map[$j][$i]; | |
// '—' direction | |
$count = 1; | |
for ($ci = $i - 1; $this->map[$j][$ci] === $player; $ci--) $count++; | |
for ($ci = $i + 1; $this->map[$j][$ci] === $player; $ci++) $count++; | |
if ($count >= $this->winCond) return true; | |
// '|' direction | |
$count = 1; | |
for ($cj = $j - 1; $this->map[$cj][$i] === $player; $cj--) $count++; | |
for ($cj = $j + 1; $this->map[$cj][$i] === $player; $cj++) $count++; | |
if ($count >= $this->winCond) return true; | |
// '\' direction | |
$count = 1; | |
for ($ci = $i - 1, $cj = $j - 1; $this->map[$cj][$ci] === $player; $ci--, $cj--) $count++; | |
for ($ci = $i + 1, $cj = $j + 1; $this->map[$cj][$ci] === $player; $ci++, $cj++) $count++; | |
if ($count >= $this->winCond) return true; | |
// '/' direction | |
$count = 1; | |
for ($ci = $i - 1, $cj = $j + 1; $this->map[$cj][$ci] === $player; $ci--, $cj++) $count++; | |
for ($ci = $i + 1, $cj = $j - 1; $this->map[$cj][$ci] === $player; $ci++, $cj--) $count++; | |
if ($count >= $this->winCond) return true; | |
return false; | |
} | |
public function render() | |
{ | |
echo '<table border="1">'; | |
foreach ($this->map as $j => $row) { | |
echo '<tr>'; | |
foreach ($row as $i => $cell) { | |
echo "<td width='30' onclick='location=\"?i={$i}&j={$j}\"'>"; | |
echo is_null($cell) ? ' ' : $this->players[$cell]; | |
echo "</td>"; | |
} | |
echo '</tr>'; | |
} | |
echo '</table>'; | |
echo '<hr>'; | |
?> | |
<form action=""> | |
<input type="text" name="width" title="Width" placeholder="width" value="<?= $this->width ?>"> | |
<input type="text" name="height" title="Height" placeholder="height" value="<?= $this->height ?>"> | |
<input type="text" name="winCond" title="WinCond" placeholder="winCond" value="<?= $this->winCond ?>"> | |
<input type="text" name="players" title="Players" placeholder="players" | |
value="<?= implode(',', $this->players) ?>"> | |
<input type="submit" name="reset"> | |
</form> | |
<?php | |
echo 'Turn of ' . $this->getPlayer(); | |
} | |
public function getWinner() | |
{ | |
return $this->winner; | |
} | |
public function isOver() | |
{ | |
return $this->isOver; | |
} | |
public function start() | |
{ | |
$this->isOver = false; | |
$this->winner = null; | |
$this->currentPlayer = 0; | |
$this->turnsLeft = $this->width * $this->height; | |
for ($j = 0; $j < $this->height; $j++) { | |
$this->map[$j] = []; | |
for ($i = 0; $i < $this->width; $i++) { | |
$this->map[$j][$i] = null; | |
} | |
} | |
} | |
} | |
session_start(); | |
if (empty($_SESSION['game']) || array_key_exists('reset', $_GET)) { | |
$width = $_GET['width'] ?? 3; | |
$height = $_GET['height'] ?? 3; | |
$cond = $_GET['winCond'] ?? 3; | |
$players = isset($_GET['players']) ? explode(',', $_GET['players']) : ['X', 'O']; | |
// start game | |
$game = new TicTacToeGame($width, $height, $cond, $players); | |
$_SESSION['game'] = $game; | |
echo 'Game started<br>'; | |
} else { | |
/** @var TicTacToeGame $game */ | |
$game = &$_SESSION['game']; | |
if ($game->isOver()) { | |
$game->start(); | |
} | |
if (!isset($_GET['i'], $_GET['j'])) { | |
echo 'Pass coords: i, j!<br>'; | |
} else { | |
// turn | |
try { | |
$game->turn($_GET['i'], $_GET['j']); | |
if ($game->isOver()) { | |
echo "Game is over!<br/>"; | |
$winner = $game->getWinner(); | |
if (is_null($winner)) { | |
echo "Draw!<br/>"; | |
} else { | |
echo $game->getPlayer($winner) . " Win!<br/>"; | |
} | |
} | |
} catch (InvalidArgumentException $e) { | |
echo $e->getMessage(), '<br/>'; | |
} | |
} | |
} | |
$game->render(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment