/** * Add castling moves if available * * @return array the squares where the king can go **/ protected function addCastlingSquares(King $king, array $squares) { $player = $king->getPlayer(); $rooks = PieceFilter::filterNotMoved(PieceFilter::filterClass(PieceFilter::filterAlive($player->getPieces()), 'Rook')); if (empty($rooks)) { return $squares; } $opponentControlledKeys = $this->getPlayerControlledKeys($player->getOpponent(), true); foreach ($rooks as $rook) { $kingX = $king->getX(); $kingY = $king->getY(); $dx = $kingX > $rook->getX() ? -1 : 1; $possible = true; foreach (array($kingX + $dx, $kingX + 2 * $dx) as $_x) { $key = Board::postoKey($_x, $kingY); if ($this->board->hasPieceByKey($key) || in_array($key, $opponentControlledKeys)) { $possible = false; break; } } if ($possible) { if (-1 === $dx && $this->board->hasPieceByKey(Board::postoKey($kingX - 3, $kingY))) { } else { $squares[] = $this->board->getSquareByKey($key); } } } return $squares; }
/** * Move a piece on the board * Performs several validation before applying the move * * @param mixed $notation Valid algebraic notation (e.g. "a2 a4") * @return void */ public function move($notation, array $options = array()) { list($from, $to) = explode(' ', $notation); if (!($from = $this->board->getSquareByKey($from))) { throw new \InvalidArgumentException('Square ' . $from . ' does not exist'); } if (!($to = $this->board->getSquareByKey($to))) { throw new \InvalidArgumentException('Square ' . $to . ' does not exist'); } if (!($piece = $from->getPiece())) { throw new \InvalidArgumentException('No piece on ' . $from); } if (!$piece->getPlayer()->isMyTurn()) { throw new \LogicException('Can not play ' . $from . ' ' . $to . ' - Not ' . $piece->getColor() . ' player turn'); } $possibleMoves = $this->analyser->getPiecePossibleMoves($piece); if (!$possibleMoves) { throw new \LogicException($piece . ' can not move'); } if (!in_array($to->getKey(), $possibleMoves)) { throw new \LogicException($piece . ' can not go to ' . $to . ' (' . implode(',', $possibleMoves) . ')'); } if ($killed = $to->getPiece()) { $killed->setIsDead(true); $this->board->remove($killed); } $this->board->move($piece, $to->getX(), $to->getY()); if ($this->stack) { $this->stack->add(array('type' => 'move', 'from' => $from->getKey(), 'to' => $to->getKey())); } if (null === $piece->getFirstMove()) { $piece->setFirstMove($this->game->getTurns()); } // casting? if ($piece instanceof King && 2 === abs($from->getX() - $to->getX())) { $this->castling($piece, $to); } // promotion? if ($piece instanceof Pawn && $to->getY() === ($piece->getPlayer()->isWhite() ? 8 : 1)) { $this->promotion($piece, $options); } // enpassant? if ($piece instanceof Pawn && $to->getX() !== $from->getX() && !$killed) { $this->enpassant($piece, $to); } }
public function getSquare() { return $this->board->getSquareByKey(Board::posToKey($this->x, $this->y)); }
/** * @depends testBoardCreation */ public function testGetSquareByPos(Board $board) { $square = $board->getSquareByPos(1, 1); $this->assertSame($board->getSquareByKey('a1'), $square); }
/** * Move a piece on the board * Performs several validation before applying the move * * @param mixed $notation Valid algebraic notation (e.g. "a2 a4") * @return string PGN notation of the move */ public function move($notation, array $options = array()) { list($fromKey, $toKey) = explode(' ', $notation); if (!$fromKey || !$toKey) { throw new \InvalidArgumentException(sprintf('Manipulator:move game:%s, Invalid internal move notation "%s"', $this->game->getId(), $notation)); } if (!($from = $this->board->getSquareByKey($fromKey))) { throw new \InvalidArgumentException(sprintf('Manipulator:move game:%s, Square ' . $fromKey . ' does not exist', $this->game->getId())); } if (!($to = $this->board->getSquareByKey($toKey))) { throw new \InvalidArgumentException(sprintf('Manipulator:move game:%s, Square ' . $toKey . ' does not exist', $this->game->getId())); } if (!($piece = $from->getPiece())) { throw new \InvalidArgumentException(sprintf('Manipulator:move game:%s, No piece on ' . $from, $this->game->getId())); } $player = $piece->getPlayer(); if (!$player->isMyTurn()) { throw new \LogicException(sprintf('Manipulator:move game:%s, Can not play ' . $from . ' ' . $to . ' - Not ' . $piece->getColor() . ' player turn', $this->game->getId())); } $pieceClass = $piece->getClass(); $isPlayerKingAttacked = $this->analyser->isKingAttacked($player); $playerPossibleMoves = $this->analyser->getPlayerPossibleMoves($player, $isPlayerKingAttacked); $possibleMoves = isset($playerPossibleMoves[$fromKey]) ? $playerPossibleMoves[$fromKey] : false; if (!$possibleMoves) { throw new \LogicException(sprintf('Manipulator:move game:%s, %s can not move', $this->game->getId(), $piece)); } if (!in_array($toKey, $possibleMoves)) { throw new \LogicException(sprintf('Manipulator:move game:%s, %s can not go to ' . $to . ' (' . implode(',', $possibleMoves) . ')', $this->game->getId(), $piece)); } // killed? $killed = $to->getPiece(); // castling? $isCastling = false; if ('King' === $pieceClass) { // standard castling if (1 < abs($from->getX() - $to->getX())) { $isCastling = true; } elseif ($killed && $killed->getColor() === $piece->getColor()) { $isCastling = true; $killed = null; } } // promotion? if ('Pawn' === $pieceClass && $to->getY() === ($player->isWhite() ? 8 : 1)) { $isPromotion = true; $promotionClass = isset($options['promotion']) ? ucfirst($options['promotion']) : 'Queen'; if (!in_array($promotionClass, array('Queen', 'Knight', 'Bishop', 'Rook'))) { throw new \InvalidArgumentException(sprintf('Manipulator:move game:%s, Bad promotion class: ' . $promotionClass, $this->game->getId())); } $options['promotion'] = $promotionClass; } else { $isPromotion = false; } // enpassant? $isEnPassant = 'Pawn' === $pieceClass && $to->getX() !== $from->getX() && !$killed; $pgnDumper = new PgnDumper(); $pgn = $pgnDumper->dumpMove($this->game, $piece, $from, $to, $playerPossibleMoves, $killed, $isCastling, $isPromotion, $isEnPassant, $options); $this->stack->addEvent(array('type' => 'move', 'from' => $from->getKey(), 'to' => $to->getKey(), 'color' => $piece->getColor())); if ($isCastling) { $this->castle($piece, $to); } else { if ($killed) { $killed->setIsDead(true); $this->board->remove($killed); } $this->board->move($piece, $to->getX(), $to->getY()); if (null === $piece->getFirstMove()) { $piece->setFirstMove($this->game->getTurns()); } } if ($isPromotion) { $this->promotion($piece, $options['promotion']); } if ($isEnPassant) { $this->enpassant($piece, $to); } // When an irreversible event happens, // we can safely clear the game position hashes if ($killed || $isPromotion || $isCastling || 'Pawn' === $pieceClass) { $this->game->clearPositionHashes(); } return $pgn; }