Beispiel #1
0
 /**
  * @param MoveInterface $move
  *
  * @return string
  */
 public static function toNotation(MoveInterface $move)
 {
     if (null === $move->getPiece()) {
         return '';
     }
     $notation = '';
     $piece = $move->getPiece();
     $toLabel = strtolower($move->getToLabel());
     $isPawn = $piece->getType() === PieceInterface::TYPE_PAWN;
     $pieceLetter = PieceDecorator::toLetter($piece);
     if (null !== ($capture = $move->getCapture())) {
         if ($isPawn) {
             $column = BoardHelper::getColumnFromPosition($move->getFrom());
             $pieceLetter = BoardHelper::columnNumberToLetter($column);
         }
         $notation .= sprintf('%sx%s', $pieceLetter, $toLabel);
         if ($isPawn) {
             if ($move->getType() === MoveInterface::TYPE_CAPTURE_EN_PASSANT) {
                 $notation .= 'e.p.';
             }
         }
         return $notation;
     }
     if ($isPawn) {
         return $toLabel;
     }
     return sprintf('%s%s', $pieceLetter, $toLabel);
 }
 /**
  * @param string $asciiBoard
  * @param array  $expectedPossibleMovesByColor
  *
  * @dataProvider getBoardsAndPossibleMoves()
  */
 public function testPossibleMoves($asciiBoard, array $expectedPossibleMovesByColor)
 {
     if (empty($expectedPossibleMovesByColor)) {
         $this->markTestIncomplete('No possible moves provided by testing data');
     }
     $game = $this->createGameFromAsciiBoard($asciiBoard);
     foreach ($expectedPossibleMovesByColor as $colorToMove => $expectedPossibleMoves) {
         $game->setCurrentColor($colorToMove);
         $board = $game->getBoard();
         $actualMovesByColor = $this->getSimplifiedActualMoves($colorToMove, $board);
         foreach ($expectedPossibleMoves as $x => $expectedMove) {
             $found = false;
             $expectedFrom = $expectedMove[0];
             $expectedTo = $expectedMove[1];
             foreach ($actualMovesByColor as $y => $actualMove) {
                 $actualFrom = $actualMove[0];
                 $actualTo = $actualMove[1];
                 if ($expectedFrom == $actualFrom && $expectedTo == $actualTo) {
                     unset($expectedPossibleMoves[$x]);
                     unset($actualMovesByColor[$y]);
                     $found = true;
                 }
             }
             if (null === ($fromPiece = $board->getSquare($expectedFrom)->getPiece())) {
                 throw new \RuntimeException(sprintf('No piece found to move from %s to %s: %s', BoardHelper::positionToLabel($expectedFrom), BoardHelper::positionToLabel($expectedTo), BoardDecorator::toAscii($board)));
             }
             $this->assertTrue($found, sprintf('Expected move for %s (%s from %s to %s) is missing from actual moves', $colorToMove === Color::WHITE ? 'white' : 'black', $fromPiece->getTypeLabel(), BoardHelper::positionToLabel($expectedFrom), BoardHelper::positionToLabel($expectedTo)));
         }
         foreach ($actualMovesByColor as $actualMove) {
             $actualFrom = $actualMove[0];
             $actualTo = $actualMove[1];
             $this->assertTrue(false, sprintf('Actual move for %s (%s from %s to %s) is missing from expected moves', $colorToMove === Color::WHITE ? 'white' : 'black', $board->getSquare($actualFrom)->getPiece()->getTypeLabel(), BoardHelper::positionToLabel($actualFrom), BoardHelper::positionToLabel($actualTo)));
         }
     }
 }
Beispiel #3
0
 /**
  * @param BoardWalker   $walker
  * @param MoveInterface $lastMove
  */
 private function walkEnPassant(BoardWalker $walker, MoveInterface $lastMove = null)
 {
     $lastEnemyPiece = $lastMove ? $walker->getBoard()->getSquare($lastMove->getTo())->getPiece() : null;
     if ($lastMove === null || $lastEnemyPiece === null || $lastEnemyPiece->getType() !== PieceInterface::TYPE_PAWN) {
         return;
     }
     $leftToPosition = $walker->peek(BoardWalker::DIRECTION_LEFT, 1, null, true, false);
     $rightToPosition = $walker->peek(BoardWalker::DIRECTION_RIGHT, 1, null, true, false);
     if ($leftToPosition !== null) {
         if (abs(BoardHelper::getRowFromPosition($lastMove->getFrom()) - BoardHelper::getRowFromPosition($leftToPosition)) !== 2) {
             return;
         }
         if ($lastMove->getTo() === $leftToPosition) {
             // en passant left
             $walker->forwardLeft(1, false)->restart();
         }
     }
     if ($rightToPosition !== null) {
         if (abs(BoardHelper::getRowFromPosition($lastMove->getFrom()) - BoardHelper::getRowFromPosition($rightToPosition)) !== 2) {
             return;
         }
         if ($lastMove->getTo() === $rightToPosition) {
             // en passant left
             $walker->forwardRight(1, false)->restart();
         }
     }
 }
Beispiel #4
0
 /**
  * @return Board
  */
 public static function createEmpty()
 {
     $squares = [];
     foreach (BoardHelper::getAllPositions() as $position) {
         $squares[] = SquareFactory::create($position);
     }
     return new Board($squares);
 }
Beispiel #5
0
 /**
  * {@inheritdoc}
  */
 public function configureWalker(BoardWalker $walker, GameInterface $game)
 {
     $walker->omnidirectional(1);
     $kingPosition = BoardHelper::getKingPosition($game->getBoard(), $game->getCurrentColor());
     $kingColumn = BoardHelper::getColumnFromPosition($kingPosition);
     $kingRow = BoardHelper::getRowFromPosition($kingPosition);
     $board = $game->getBoard();
     $king = $board->getSquare($kingPosition)->getPiece();
     $track = $kingPosition === SquareInterface::POSITION_E1;
     // check for possible castle move
     if (empty($game->getMovesByPiece($king->getId()))) {
         // see if one of the rooks did not make any moves
         $rookCriteria = ['piece_type' => PieceInterface::TYPE_ROOK, 'piece_color' => $game->getCurrentColor()];
         foreach ($board->getPiecesBy($rookCriteria) as $rookPosition => $rook) {
             if (empty($game->getMovesByPiece($rook->getId()))) {
                 // see if there are no pieces between the king and this rook
                 $rookColumn = BoardHelper::getColumnFromPosition($rookPosition);
                 $columnDiff = abs($kingColumn - $rookColumn) - 1;
                 $track = $track === true && $columnDiff === 3;
                 if ($columnDiff > 1) {
                     $skip = false;
                     for ($x = 1; $x <= $columnDiff; $x++) {
                         if ($rookColumn > $kingColumn) {
                             $cursor = $rookColumn - $x . $kingRow;
                         } else {
                             $cursor = $rookColumn + $x . $kingRow;
                         }
                         if (null !== ($piece = $board->getSquare($cursor)->getPiece())) {
                             $skip = true;
                             break;
                         }
                     }
                     if ($skip) {
                         continue;
                     }
                     if ($kingColumn > $rookColumn) {
                         if ($game->getCurrentColor() === Color::WHITE) {
                             $walker->jump(BoardWalker::DIRECTION_LEFT, 2);
                         } else {
                             $walker->jump(BoardWalker::DIRECTION_RIGHT, 2);
                         }
                     } else {
                         if ($game->getCurrentColor() === Color::WHITE) {
                             $walker->jump(BoardWalker::DIRECTION_RIGHT, 2);
                         } else {
                             $walker->jump(BoardWalker::DIRECTION_LEFT, 2);
                         }
                     }
                 }
             }
         }
     }
 }
Beispiel #6
0
 /**
  * {@inheritdoc}
  */
 public function getToLabel()
 {
     return BoardHelper::positionToLabel($this->getTo());
 }
Beispiel #7
0
 /**
  * {@inheritdoc}
  */
 public function getIndexedSquares($indexedBy = 'row')
 {
     $squares = [];
     switch ($indexedBy) {
         case 'row':
             $squares = [];
             foreach ($this->getSquares() as $square) {
                 $row = BoardHelper::getRowFromPosition($square->getPosition());
                 if (!array_key_exists($row, $squares)) {
                     $squares[$row] = [];
                 }
                 $squares[$row][$square->getPosition()] = $square;
             }
             break;
         case 'column':
             $squares = [];
             foreach ($this->getSquares() as $square) {
                 $column = BoardHelper::getColumnFromPosition($square->getPosition());
                 if (!array_key_exists($column, $squares)) {
                     $squares[$column] = [];
                 }
                 $squares[$column][$square->getPosition()] = $square;
             }
             break;
     }
     ksort($squares);
     return $squares;
 }
Beispiel #8
0
 /**
  * @param int      $direction        The direction to step into
  * @param int      $jumps            The number of jumps to make in this direction
  * @param int|null $startingPosition
  * @param bool     $ignoreCapture
  *
  * @return int|null The expected position, or null if the move is not possible
  *
  * @throws \InvalidArgumentException
  */
 public function peek($direction, $jumps = 1, $startingPosition = null, $ignoreCapture = false, $requireAttack = false)
 {
     $newPosition = null;
     if ($startingPosition !== null) {
         $column = BoardHelper::getColumnFromPosition($startingPosition);
         $row = BoardHelper::getRowFromPosition($startingPosition);
     } else {
         $column = $this->getColumn();
         $row = $this->getRow();
     }
     switch ($direction) {
         case self::DIRECTION_FORWARD:
             if ($this->color === Color::WHITE) {
                 $newRow = $this->formatNumber($row + $jumps);
             } else {
                 $newRow = $this->formatNumber($row - $jumps);
             }
             if ($newRow !== null) {
                 $newPosition = intval($column . $newRow);
             }
             break;
         case self::DIRECTION_FORWARDRIGHT:
             if ($this->color === Color::WHITE) {
                 $newColumn = $this->formatNumber($column + $jumps);
                 $newRow = $this->formatNumber($row + $jumps);
             } else {
                 $newColumn = $this->formatNumber($column - $jumps);
                 $newRow = $this->formatNumber($row - $jumps);
             }
             if ($newColumn !== null && $newRow !== null) {
                 $newPosition = intval($newColumn . $newRow);
             }
             break;
         case self::DIRECTION_RIGHT:
             if ($this->color === Color::WHITE) {
                 $newColumn = $this->formatNumber($column + $jumps);
             } else {
                 $newColumn = $this->formatNumber($column - $jumps);
             }
             if ($newColumn !== null) {
                 $newPosition = intval($newColumn . $row);
             }
             break;
         case self::DIRECTION_BACKWARDRIGHT:
             if ($this->color === Color::WHITE) {
                 $newColumn = $this->formatNumber($column + $jumps);
                 $newRow = $this->formatNumber($row - $jumps);
             } else {
                 $newColumn = $this->formatNumber($column - $jumps);
                 $newRow = $this->formatNumber($row + $jumps);
             }
             if ($newColumn !== null && $newRow !== null) {
                 $newPosition = intval($newColumn . $newRow);
             }
             break;
         case self::DIRECTION_BACKWARD:
             if ($this->color === Color::WHITE) {
                 $newRow = $this->formatNumber($row - $jumps);
             } else {
                 $newRow = $this->formatNumber($row + $jumps);
             }
             if ($newRow !== null) {
                 $newPosition = intval($column . $newRow);
             }
             break;
         case self::DIRECTION_BACKWARDLEFT:
             if ($this->color === Color::WHITE) {
                 $newColumn = $this->formatNumber($column - $jumps);
                 $newRow = $this->formatNumber($row - $jumps);
             } else {
                 $newColumn = $this->formatNumber($column + $jumps);
                 $newRow = $this->formatNumber($row + $jumps);
             }
             if ($newColumn !== null && $newRow !== null) {
                 $newPosition = intval($newColumn . $newRow);
             }
             break;
         case self::DIRECTION_LEFT:
             if ($this->color === Color::WHITE) {
                 $newColumn = $this->formatNumber($column - $jumps);
             } else {
                 $newColumn = $this->formatNumber($column + $jumps);
             }
             if ($newColumn !== null) {
                 $newPosition = intval($newColumn . $row);
             }
             break;
         case self::DIRECTION_FORWARDLEFT:
             if ($this->color === Color::WHITE) {
                 $newColumn = $this->formatNumber($column - $jumps);
                 $newRow = $this->formatNumber($row + $jumps);
             } else {
                 $newColumn = $this->formatNumber($column + $jumps);
                 $newRow = $this->formatNumber($row - $jumps);
             }
             if ($newColumn !== null && $newRow !== null) {
                 $newPosition = intval($newColumn . $newRow);
             }
             break;
         default:
             throw new \InvalidArgumentException(sprintf('Unknown direction to calculate: %s', $direction));
     }
     if ($ignoreCapture !== true && $newPosition !== null) {
         $toPiece = $this->board->getSquare($newPosition)->getPiece();
         if (null !== $toPiece) {
             if ($toPiece->getColor() === $this->getColor()) {
                 // own piece, can't capture
                 return null;
             }
         } elseif ($requireAttack === true) {
             return null;
         }
     }
     return $newPosition;
 }
Beispiel #9
0
 /**
  * @param MoveInterface $move
  * @param GameInterface $game
  *
  * @return int|null
  */
 private static function determineEnPassantType(MoveInterface $move, GameInterface $game)
 {
     if (null !== ($lastMove = $game->getLastMove())) {
         if ($lastMove->getPiece()->getType() === PieceInterface::TYPE_PAWN) {
             if (abs(BoardHelper::getColumnFromPosition($lastMove->getTo()) - BoardHelper::getColumnFromPosition($move->getFrom())) === 1 && abs(BoardHelper::getRowFromPosition($lastMove->getTo()) - BoardHelper::getRowFromPosition($lastMove->getFrom())) === 2 && BoardHelper::getRowFromPosition($move->getFrom()) === BoardHelper::getRowFromPosition($lastMove->getTo())) {
                 $toPiece = $game->getBoard()->getSquare($move->getTo())->getPiece();
                 $rowDiff = abs(BoardHelper::getRowFromPosition($move->getFrom()) - BoardHelper::getRowFromPosition($move->getTo()));
                 $columnDiff = abs(BoardHelper::getColumnFromPosition($move->getFrom()) - BoardHelper::getColumnFromPosition($move->getTo()));
                 if ($rowDiff === 1 && $columnDiff === 1) {
                     if ($toPiece === null) {
                         // en passant capture
                         return MoveInterface::TYPE_CAPTURE_EN_PASSANT;
                     } else {
                         return MoveInterface::TYPE_CAPTURE;
                     }
                 }
             }
         }
     }
     return null;
 }
Beispiel #10
0
 /**
  * @param string        $notation
  * @param GameInterface $game
  * @param array         $parsed
  *
  * @return int
  *
  * @throws InvalidNotationException
  */
 private function parseFrom($notation, GameInterface $game, array $parsed)
 {
     $enPassant = false;
     $capture = false;
     switch ($parsed['piece_type']) {
         case PieceInterface::TYPE_PAWN:
             if (substr($notation, -4) === 'e.p.') {
                 $enPassant = true;
             } elseif (substr($notation, 1, 1) === 'x') {
                 // calculate 'from' for capture by pawn (diagonally)
                 $capture = true;
             } else {
                 // calculate 'from' for forward move by pawn
             }
             break;
         default:
             // non-pawn calculation, depends on type
             break;
     }
     $finalMove = null;
     $criteria = ['piece_color' => $parsed['color'], 'piece_type' => $parsed['piece_type']];
     if (strlen($notation) > 3 && in_array(substr($notation, 1, 1), ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])) {
         $criteria['column'] = BoardHelper::columnLetterToNumber(substr($notation, 1, 1));
     }
     $possiblePieceMoves = $this->moveCalculator->possibleMovesTo($parsed['to'], $game->getBoard(), $criteria);
     foreach ($possiblePieceMoves as $move) {
         MoveHelper::enrich($move, $game);
         if ($enPassant === true && $move->getType() !== MoveInterface::TYPE_CAPTURE_EN_PASSANT) {
             continue;
         } elseif ($capture === true && $move->getType() !== MoveInterface::TYPE_CAPTURE) {
             continue;
         }
         if ($finalMove !== null) {
             throw new InvalidNotationException(sprintf('Multiple moves found starting from %s and ending on %s, you should make your notation more specific (criteria: %s)', $finalMove->getFromLabel(), $finalMove->getToLabel(), json_encode($criteria)));
         }
         $finalMove = $move;
     }
     if ($finalMove === null) {
         throw new InvalidNotationException(sprintf('There are no moves to make to this position: %s (criteria: %s)', $parsed['to'], json_encode($criteria, true)));
     }
     return $finalMove->getFrom();
 }
Beispiel #11
0
 /**
  * {@inheritdoc}
  */
 public function __construct($position, PieceInterface $piece = null)
 {
     $this->position = $position;
     $this->color = BoardHelper::getSquareColorFromPosition($position);
     $this->piece = $piece;
 }
Beispiel #12
0
 /**
  * @param Move[]         $moves
  * @param int            $ownColor
  * @param BoardInterface $board
  *
  * @return Move[]
  */
 private function filterOwnKingChecks(array $moves, $ownColor, BoardInterface $board)
 {
     $opposingColor = $ownColor === Color::WHITE ? Color::BLACK : Color::WHITE;
     $safeMoves = [];
     foreach ($moves as $move) {
         $futureBoard = $this->mockMove($move, $board);
         $checksKing = false;
         $ownKingPosition = BoardHelper::getKingPosition($futureBoard, $ownColor);
         foreach ($board->getSquaresBy(['piece_color' => $opposingColor]) as $square) {
             foreach ($this->possibleMovesFrom($square->getPosition(), $futureBoard, false, true) as $miscMove) {
                 if ($miscMove->getTo() == $ownKingPosition) {
                     $checksKing = true;
                     break 2;
                 }
             }
         }
         if ($checksKing === false) {
             $safeMoves[] = $move;
         }
     }
     return $safeMoves;
 }