mchess-server/chess/pawn.go
Marco ff2ec599fe Introduce PGN helpers
In order to simplify special moves like en passant or castling for the
client, we want to deliver the board state after every move (and not
only start square and end square).

With PGN we can encode a chess position into a string.
This commit implies changes to logic of the pieces' shortnames. This
will break the client/server connection (at least for promotions).
2023-08-12 11:24:40 +02:00

192 lines
5.2 KiB
Go

package chess
import (
"mchess_server/types"
"github.com/samber/lo"
)
type Pawn struct {
Color types.ChessColor
}
func (p Pawn) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
attackingMoves := make([]types.Coordinate, 0, 2)
allMoves := p.GetAllNonBlockedMoves(board, fromSquare)
for _, move := range allMoves {
if move.Col != fromSquare.Col {
attackingMoves = append(attackingMoves, move)
}
}
return attackingMoves
}
func (p Pawn) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
theoreticalSquares := p.getAllMoves(fromSquare)
legalSquares := p.filterBlockedSquares(board, fromSquare, theoreticalSquares)
return legalSquares
}
func (p Pawn) GetColor() types.ChessColor {
return p.Color
}
func (p *Pawn) HandlePossiblePromotion(b *Board, move types.Move) (bool, error) {
var isPromotionMove bool
var promotionToPiece types.PieceShortName
//TODO(m): What if message does not contain a promotion, but should be because a pawn is moved to the end square
messageContainsPromotion := move.IsPromotionMove()
if messageContainsPromotion {
promotionToPiece = types.PieceShortName(*move.PromotionToPiece)
}
switch move.ColorMoved {
case types.White:
if move.StartSquare.Row == types.RangeLastValid-1 &&
move.EndSquare.Row == types.RangeLastValid {
isPromotionMove = true
}
case types.Black:
if move.StartSquare.Row == types.RangeFirstValid+1 &&
move.EndSquare.Row == types.RangeFirstValid {
isPromotionMove = true
}
}
if isPromotionMove {
delete(b.position, move.StartSquare)
b.position[move.EndSquare] = GetPieceForShortName(promotionToPiece)
}
return isPromotionMove, nil
}
func (p *Pawn) HandleEnPassant(b *Board, move, lastMove types.Move) (bool, error) {
var wasEnPassant bool
if lastMove.PieceMoved.ToCommon() != types.PawnShortName {
return false, nil
}
switch move.ColorMoved {
case types.White:
if lastMove.StartSquare.Row != 7 || lastMove.EndSquare.Row != 5 {
wasEnPassant = false
break
}
if move.StartSquare.Row != 5 {
wasEnPassant = false
break
}
if move.EndSquare.Row != 6 {
wasEnPassant = false
break
}
if move.StartSquare.Col == lastMove.EndSquare.Col+1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
break
}
if move.StartSquare.Col == lastMove.EndSquare.Col-1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
break
}
case types.Black:
if lastMove.StartSquare.Row != 2 || lastMove.EndSquare.Row != 4 {
wasEnPassant = false
break
}
if move.StartSquare.Row != 4 {
wasEnPassant = false
break
}
if move.EndSquare.Row != 3 {
wasEnPassant = false
break
}
if move.StartSquare.Col == lastMove.EndSquare.Col+1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
break
}
if move.StartSquare.Col == lastMove.EndSquare.Col-1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
break
}
}
if wasEnPassant { //play the move
delete(b.position, lastMove.EndSquare)
b.position[move.EndSquare] = GetPieceForShortName(move.PieceMoved)
}
return wasEnPassant, nil
}
func (p Pawn) getAllMoves(fromSquare types.Coordinate) []types.Coordinate {
theoreticalMoves := make([]types.Coordinate, 0, 4)
switch p.Color {
case types.Black:
firstMove := fromSquare.Row == types.RangeLastValid-1
if fromSquare.Down(1) != nil {
theoreticalMoves = append(theoreticalMoves, *fromSquare.Down(1))
}
if firstMove && fromSquare.Down(2) != nil {
theoreticalMoves = append(theoreticalMoves, *fromSquare.Down(2))
}
if lowerRight := fromSquare.Down(1).Right(1); lowerRight != nil {
theoreticalMoves = append(theoreticalMoves, *lowerRight)
}
if lowerLeft := fromSquare.Down(1).Left(1); lowerLeft != nil {
theoreticalMoves = append(theoreticalMoves, *lowerLeft)
}
case types.White:
firstMove := fromSquare.Row == types.RangeFirstValid+1
if fromSquare.Up(1) != nil {
theoreticalMoves = append(theoreticalMoves, *fromSquare.Up(1))
}
if firstMove && fromSquare.Up(2) != nil {
theoreticalMoves = append(theoreticalMoves, *fromSquare.Up(2))
}
if upperRight := fromSquare.Up(1).Right(1); upperRight != nil {
theoreticalMoves = append(theoreticalMoves, *upperRight)
}
if upperLeft := fromSquare.Up(1).Left(1); upperLeft != nil {
theoreticalMoves = append(theoreticalMoves, *upperLeft)
}
}
return theoreticalMoves
}
func (p Pawn) filterBlockedSquares(board Board, fromSquare types.Coordinate, squaresToBeFiltered []types.Coordinate) []types.Coordinate {
var nonBlockedSquares []types.Coordinate
//order of movesToBeFiltered is important here
for _, square := range squaresToBeFiltered {
pieceAtSquare := board.getPieceAt(square)
if square.Col == fromSquare.Col { // squares ahead
if pieceAtSquare == nil {
nonBlockedSquares = append(nonBlockedSquares, square)
}
} else { //squares that pawn attacks
if pieceAtSquare != nil && pieceAtSquare.GetColor() != p.Color {
nonBlockedSquares = append(nonBlockedSquares, square)
}
}
}
return lo.Intersect(nonBlockedSquares, squaresToBeFiltered)
}
func (p Pawn) AfterMoveAction(board *Board, fromSquare types.Coordinate) {}