Skip to content

Instantly share code, notes, and snippets.

@echosa
Created March 13, 2014 13:42
Show Gist options
  • Save echosa/9528712 to your computer and use it in GitHub Desktop.
Save echosa/9528712 to your computer and use it in GitHub Desktop.
<?php
/**
* For easy of posting to Gist, I have put everything in one file called
* knight.php. The getValidKnightSquares() function is first in the file.
*
* The require_once is there to load PHPUnit installed with Composer. There is a
* unit test class for the getValidKnightSquares() function at the end of this
* file.
*/
namespace Chess;
require_once __DIR__ . '/vendor/autoload.php';
/**
* @param String $strStartingSquare - The knight's current square in algebraic notation (e4)
* @return array - list of all valid squares the knight can move to in algebraic notation
*/
function getValidKnightSquares($strStartingSquare) {
$knight = new Knight(AbstractPiece::WHITE);
$position = new AlgebraicBoardPosition($strStartingSquare);
$position->setPiece($knight);
return $position->getValidMoves();
}
interface BoardPosition
{
public function getNotation();
public function parseNotation($stringNotation);
public function getPiece();
public function setPiece(Piece $piece);
public function getPositionUp($count);
public function getPositionDown($count);
public function getPositionLeft($count);
public function getPositionRight($count);
}
abstract class AbstractBoardPosition implements BoardPosition
{
protected $notation;
protected $piece;
public function __construct($notation)
{
$this->notation = $notation;
$this->parseNotation($notation);
}
public function getNotation()
{
return $this->notation;
}
public function getPiece()
{
return $this->piece;
}
public function setPiece(Piece $piece)
{
$this->piece = $piece;
}
}
class AlgebraicBoardPosition extends AbstractBoardPosition
{
private $_file;
private $_rank;
public function getFile()
{
return $this->_file;
}
public function getRank()
{
return $this->_rank;
}
public function parseNotation($stringNotation)
{
$this->_rank = $stringNotation[0];
$this->_file = $stringNotation[1];
}
public function getPositionUp($count = 1)
{
$newFile = $this->_file + $count;
if ($newFile > 8) {
throw new InvalidMoveException("Can't go up that far!");
}
return new AlgebraicBoardPosition($this->_rank . $newFile);
}
public function getPositionDown($count = 1)
{
$newFile = $this->_file - $count;
if ($newFile < 1) {
throw new InvalidMoveException("Can't go down that far!");
}
return new AlgebraicBoardPosition($this->_rank . $newFile);
}
public function getPositionRight($count = 1)
{
$newRank = chr(ord($this->_rank) + $count);
if (strcmp($newRank, 'h') > 0) {
throw new InvalidMoveException("Can't go right that far!");
}
return new AlgebraicBoardPosition($newRank . $this->_file);
}
public function getPositionLeft($count = 1)
{
$newRank = chr(ord($this->_rank) - $count);
if (strcmp($newRank, 'a') < 0) {
throw new InvalidMoveException("Can't go left that far!");
}
return new AlgebraicBoardPosition($newRank . $this->_file);
}
public function getValidMoves()
{
$moves = array();
foreach ($this->piece->getPotentialMoves($this) as $move) {
if (!$this->_positionHasFriendlyPiece($move)) {
$moves[] = $move->getNotation();
}
}
return $moves;
}
private function _positionHasFriendlyPiece(BoardPosition $position)
{
$peiceOnTarget = $position->getPiece();
return !is_null($peiceOnTarget)
&& $peiceOnTarget->getColor() == $this->piece->getColor();
}
}
interface Piece
{
public function getPotentialMoves(BoardPosition $start);
public function getColor();
}
abstract class AbstractPiece implements Piece
{
const WHITE = 'white';
const BLACK = 'black';
protected $color;
public function __construct($color)
{
$this->color = $color;
}
public function getColor()
{
return $this->color;
}
}
class Knight extends AbstractPiece
{
public function getPotentialMoves(BoardPosition $start)
{
$potentialMoves = array(
$this->_getRightAndUpMove($start),
$this->_getUpAndRightMove($start),
$this->_getLeftAndUpMove($start),
$this->_getUpAndLeftMove($start),
$this->_getRightAndDownMove($start),
$this->_getDownAndRightMove($start),
$this->_getLeftAndDownMove($start),
$this->_getDownAndLeftMove($start),
);
foreach ($potentialMoves as $key => $move) {
if (is_null($move)) {
unset($potentialMoves[$key]);
}
}
return $potentialMoves;
}
private function _getRightAndUpMove(BoardPosition $start)
{
try {
return $start->getPositionRight()->getPositionUp(2);
} catch (InvalidMoveException $e) {
return null;
}
}
private function _getUpAndRightMove(BoardPosition $start)
{
try {
return $start->getPositionUp()->getPositionRight(2);
} catch (InvalidMoveException $e) {
return null;
}
}
private function _getLeftAndUpMove(BoardPosition $start)
{
try {
return $start->getPositionLeft()->getPositionUp(2);
} catch (InvalidMoveException $e) {
return null;
}
}
private function _getUpAndLeftMove(BoardPosition $start)
{
try {
return $start->getPositionUp()->getPositionLeft(2);
} catch (InvalidMoveException $e) {
return null;
}
}
private function _getRightAndDownMove(BoardPosition $start)
{
try {
return $start->getPositionRight()->getPositionDown(2);
} catch (InvalidMoveException $e) {
return null;
}
}
private function _getDownAndRightMove(BoardPosition $start)
{
try {
return $start->getPositionDown()->getPositionRight(2);
} catch (InvalidMoveException $e) {
return null;
}
}
private function _getLeftAndDownMove(BoardPosition $start)
{
try {
return $start->getPositionLeft()->getPositionDown(2);
} catch (InvalidMoveException $e) {
return null;
}
}
private function _getDownAndLeftMove(BoardPosition $start)
{
try {
return $start->getPositionDown()->getPositionLeft(2);
} catch (InvalidMoveException $e) {
return null;
}
}
}
class InvalidMoveException extends \Exception
{
public function __construct($direction, $code = 0,
\Exception $previous = null
) {
$message = "Can't go that far " . $direction;
parent::__construct($message, $code, $previous);
}
}
class KnightTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider movesProvider
*/
public function testKnightMoves($start, $validMoves)
{
$moves = getValidKnightSquares($start);
$this->assertCount(count($validMoves), $moves);
foreach ($validMoves as $validMove) {
$this->assertContains($validMove, $moves);
}
}
public function movesProvider()
{
return array(
array('a1', array('b3', 'c2')),
array('h1', array('f2', 'g3')),
array('a8', array('b6', 'c7')),
array('h8', array('g6', 'f7')),
array('c1', array('a2', 'b3', 'd3', 'e2')),
array('h3', array('g1', 'f2', 'f4', 'g5')),
array('f8', array('d7', 'e6', 'g6', 'h7')),
array('a5', array('b3', 'c4', 'c6', 'b7')),
array('d4', array('b3', 'c2', 'e2', 'f3', 'f5', 'e6', 'c6', 'b5')),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment