Skip to content

Instantly share code, notes, and snippets.

@flyingL123
Last active August 31, 2015 02:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flyingL123/15dc3c829e98a46f58ac to your computer and use it in GitHub Desktop.
Save flyingL123/15dc3c829e98a46f58ac to your computer and use it in GitHub Desktop.
Tic Tac Toe
<?php
class Game {
/**
* Array to hold the state of the cells in the game.
*/
private $cells;
/**
* Integer representing the square size
* of the game. For example, 3, for a 3x3 game.
*/
private $size;
/**
* The currently active player.
*/
private $active;
/**
* Total number of moves made.
*/
private $moves;
/**
* The letter value X or O of the computer player.
*/
private $compPlayer;
/**
* The character to place in an empty cell.
*/
const EMPTY_CELL = ' ';
/**
* Create a new Tic Tac Toe game of a certain size.
*/
public function __construct($size = 3) {
$this->size = $size;
echo "To make a move, enter the row,column of the cell you want to mark.".PHP_EOL;
$this->newGame();
}
private function newGame() {
$this->active = 'X';
$this->compPlayer = 'O';
$this->moves = 0;
$this->cells = array_fill(1, $this->size, array_fill(1, $this->size, self::EMPTY_CELL));
$this->printBoard();
}
public function play() {
$this->nextStep();
}
private function nextStep() {
if (!$this->isOver()) {
if ($this->active != $this->compPlayer) {
$coords = $this->promptForMove();
if ($coords) {
$valid = $this->validateMove($coords);
if ($valid) {
$this->makeMove($coords);
}
}
$this->nextStep();
}
else {
$this->computerPlay();
}
}
else {
$this->endGame();
}
}
private function computerPlay() {
echo "Computer player is playing".PHP_EOL;
$x = rand(1, $this->size);
$y = rand(1, $this->size);
$coords = [rand(1, $this->size), rand(1, $this->size)];
$valid = $this->validateMove($coords);
if ($valid) {
echo "Computer moved".PHP_EOL;
$this->makeMove($coords);
$this->nextStep();
}
else {
$this->computerPlay();
}
}
/**
* Make a move
*/
private function makeMove($coords) {
$this->updateCell($coords);
$this->moves++;
$this->printBoard();
$this->switchPlayer();
}
/**
* End the game.
*/
private function endGame() {
echo "The game is over".PHP_EOL;
$winner = $this->getWinner();
if ($winner) {
echo "The winner is {$winner}".PHP_EOL;
}
else {
echo "The game ended in a tie".PHP_EOL;
}
$input = readline("Start a new game? y/n: ");
if ($input == 'y') {
$this->newGame();
$this->play();
}
}
/**
* Prompt for a move.
*/
private function promptForMove() {
$input = readline("{$this->active}, please enter your move: ");
$coords = $this->coordsFromInput($input);
if ($coords) {
return $coords;
}
else {
echo "Invalid input, please try again".PHP_EOL;
return false;
}
}
/**
* Get [x,y] coordinates from user input
*/
private function coordsFromInput($input) {
$input = explode(',', $input);
if (count($input) !== 2) {
return false;
}
$x = trim($input[0]);
$y = trim($input[1]);
if (ctype_digit($x) && ctype_digit($y)) {
return [$x, $y];
}
return false;
}
/**
* Determine if a move is valid.
*/
private function validateMove($coords) {
$x = $coords[0];
$y = $coords[1];
if ($x < 1 || $x > $this->size || $y < 1 || $y > $this->size) {
echo "Please try again. The cell does not exist.".PHP_EOL;
return false;
}
if ($this->cells[$x][$y] !== self::EMPTY_CELL) {
echo "Please try again. The cell is already full.".PHP_EOL;
return false;
}
return true;
}
/**
* Update a cell.
*/
private function updateCell($coords) {
$this->cells[$coords[0]][$coords[1]] = $this->active;
}
private function isOver() {
if ($this->moves == $this->size * $this->size || $this->getWinner()) {
return true;
}
}
private function getWinner() {
$cells = $this->cells;
// check for winner by row
foreach ($cells as $x => $row) {
$val = $row[1];
foreach ($row as $y => $cell) {
if ($cell == self::EMPTY_CELL || $val != $cell) {
break;
}
if ($y == $this->size) {
return $val;
}
}
}
// check for winner by column
$cols = [];
foreach ($cells as $i => $row) {
$cols[$i] = array_column($cells, $i);
}
foreach ($cols as $y => $col) {
$val = $col[0];
foreach ($col as $x => $cell) {
if ($cell == self::EMPTY_CELL || $val != $cell) {
break;
}
// need size - 1 here because column
// arrays are indexed from 0 by array_column
if ($x == $this->size - 1) {
return $val;
}
}
}
// check for winner on diagonal: top-left to bottom-right
$val = $cells[1][1];
if ($val != self::EMPTY_CELL) {
for ($i = 1; $i <= $this->size; $i++) {
if ($cells[$i][$i] != $val) {
break;
}
if ($i == $this->size) {
return $val;
}
}
}
//check for winner on diagonal: top-right to bottom-left
$val = $cells[$this->size][1];
if ($val != self::EMPTY_CELL) {
for ($x = $this->size, $y = 1; $x >= 1; $x--, $y++) {
if ($cells[$x][$y] != $val) {
break;
}
if ($x == 1) {
return $val;
}
}
}
return false;
}
public function switchPlayer() {
if ($this->active == 'X') {
$this->active = 'O';
}
else {
$this->active = 'X';
}
}
public function printBoard() {
$rowStrings = [];
foreach ($this->cells as $row) {
$rowStrings[] = implode('|', $row).PHP_EOL;
}
$str = '';
for ($i = 0; $i < (2 * $this->size - 1); $i++) {
$str .= '-';
}
echo PHP_EOL.implode($str.PHP_EOL, $rowStrings).PHP_EOL;
}
}
$game = new Game;
$game->play();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment