public function getPiecePossibleMoves(Piece $piece) { $player = $piece->getPlayer(); $isKingAttacked = $this->isKingAttacked($player); $allOpponentPieces = PieceFilter::filterNotClass(PieceFilter::filterAlive($player->getOpponent()->getPieces()), 'King'); $king = $player->getKing(); $kingSquareKey = $king->getSquareKey(); $pieceOriginalX = $piece->getX(); $pieceOriginalY = $piece->getY(); $opponentPieces = PieceFilter::filterProjection($allOpponentPieces); //if we are not moving the king, and the king is not attacked, don't check pawns nor knights if (!$piece instanceof King && !$isKingAttacked) { $opponentPieces = PieceFilter::filterProjection($opponentPieces); } $squares = $this->board->keysToSquares($piece->getBasicTargetKeys()); if ($piece instanceof King && !$isKingAttacked && !$piece->hasMoved()) { $squares = $this->addCastlingSquares($piece, $squares); } foreach ($squares as $it => $square) { // kings move to its target so we update its position if ($piece instanceof King) { $kingSquareKey = $square->getKey(); } // kill opponent piece if ($killedPiece = $square->getPiece()) { $killedPiece->setIsDead(true); } $this->board->move($piece, $square->getX(), $square->getY()); foreach ($opponentPieces as $opponentPiece) { if (null !== $killedPiece && $opponentPiece->getIsDead()) { continue; } // if our king gets attacked if (in_array($kingSquareKey, $this->getPieceControlledKeys($opponentPiece, $piece instanceof King))) { // can't go here unset($squares[$it]); break; } } $this->board->move($piece, $pieceOriginalX, $pieceOriginalY); // if a piece has been killed, bring it back to life if ($killedPiece) { $killedPiece->setIsDead(false); $this->board->add($killedPiece); } } return $this->board->squaresToKeys($squares); }
/** * Handle castling **/ protected function castling(King $king, Square $to) { if (7 === $to->getX()) { $rookSquare = $to->getSquareByRelativePos(1, 0); $newRookSquare = $to->getSquareByRelativePos(-1, 0); } else { $rookSquare = $to->getSquareByRelativePos(-2, 0); $newRookSquare = $to->getSquareByRelativePos(1, 0); } $rook = $rookSquare->getPiece(); $this->board->move($rook, $newRookSquare->getX(), $newRookSquare->getY()); $rook->setFirstMove($this->game->getTurns()); if ($this->stack) { $this->stack->add(array('type' => 'castling', 'from' => $rookSquare->getKey(), 'to' => $newRookSquare->getKey())); } }
/** * 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; }
/** * @return array key => array keys */ public function getPlayerPossibleMoves(Player $player, $isKingAttacked = null) { $possibleMoves = array(); $isKingAttacked = null === $isKingAttacked ? $this->isKingAttacked($player) : $isKingAttacked; $allOpponentPieces = PieceFilter::filterAlive($player->getOpponent()->getPieces()); $attackOpponentPieces = PieceFilter::filterNotClass($allOpponentPieces, 'King'); $projectionOpponentPieces = PieceFilter::filterProjection($allOpponentPieces); $king = $player->getKing(); foreach (PieceFilter::filterAlive($player->getPieces()) as $piece) { $kingSquareKey = $king->getSquareKey(); $pieceOriginalX = $piece->getX(); $pieceOriginalY = $piece->getY(); $pieceClass = $piece->getClass(); //if we are not moving the king, and the king is not attacked, don't check pawns nor knights if ('King' === $pieceClass) { $opponentPieces = $allOpponentPieces; } elseif ($isKingAttacked) { $opponentPieces = $attackOpponentPieces; } else { $opponentPieces = $projectionOpponentPieces; } $squares = $this->board->keysToSquares($piece->getBasicTargetKeys()); foreach ($squares as $it => $square) { // king move to its target so we update its position if ('King' === $pieceClass) { $kingSquareKey = $square->getKey(); } // kill opponent piece if ($killedPiece = $square->getPiece()) { $killedPiece->setIsDead(true); } elseif ('Pawn' === $pieceClass && $square->getX() !== $pieceOriginalX) { $killedPiece = $square->getSquareByRelativePos(0, $player->isWhite() ? -1 : 1)->getPiece(); $killedPiece->setIsDead(true); } $this->board->move($piece, $square->getX(), $square->getY()); foreach ($opponentPieces as $opponentPiece) { if (null !== $killedPiece && $opponentPiece->getIsDead()) { continue; } // if our king gets attacked if (in_array($kingSquareKey, $opponentPiece->getAttackTargetKeys())) { // can't go here unset($squares[$it]); break; } } $this->board->move($piece, $pieceOriginalX, $pieceOriginalY); // if a piece has been killed, bring it back to life if ($killedPiece) { $killedPiece->setIsDead(false); $this->board->add($killedPiece); } } if ('King' === $pieceClass && !$isKingAttacked && !$piece->hasMoved()) { $squares = $this->addCastlingSquares($piece, $squares); } if (!empty($squares)) { $possibleMoves[$piece->getSquareKey()] = $this->board->squaresToKeys($squares); } } return $possibleMoves; }