Results 1 to 8 of 8

Thread: [RESOLVED] Determining En Passant

  1. #1

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Posts
    12,370

    Resolved [RESOLVED] Determining En Passant

    It's been a while since I've built any games and so I figured I would set out to create a chess application.

    I have the concept of Squares, Moves, Pieces (so far only started on Pawn), and Boards. This is the relevant code:

    Chess Board
    Keeps track of the squares and pieces
    Code:
    Namespace Board
    
        Public Class ChessBoard
    
            ' private variables
            Private ReadOnly _darkPieces(15) As ChessPiece
            Private ReadOnly _lightPieces(15) As ChessPiece
            Private ReadOnly _squares(63) As Square
    
            ' private methods
            Private Sub InitializeSquares()
                Dim counter As Integer = 0
                For rank As Byte = 0 To Square.MAX_RANK
                    For file As Integer = 0 To Square.FILE_NAMES.Length - 1
                        _squares(counter) = New Square(rank, Convert.ToByte(file))
                        counter += 1
                    Next
                Next
            End Sub
    
            Private Sub InitializePawns()
                Const darkRank As Integer = 6
                Const lightRank As Integer = 1
    
                Dim index As Integer = 0
                For fileIndex As Byte = 0 To Convert.ToByte(Square.FILE_NAMES.Length - 1)
                    _darkPieces(index) = New Pawn(Shades.Dark, New Square(darkRank, fileIndex))
                    _lightPieces(index) = New Pawn(Shades.Dark, New Square(lightRank, fileIndex))
                Next
            End Sub
    
            ' properties
            Public ReadOnly Property Squares As IReadOnlyList(Of Square)
                Get
                    Return _squares
                End Get
            End Property
    
            ' public methods
            Public Function GetSquare(file As Byte, rank As Byte) As Square
                If (file > Square.FILE_NAMES.Length - 1) Then
                    Throw New ArgumentOutOfRangeException(NameOf(file))
                End If
                If (rank > Square.MAX_RANK) Then
                    Throw New ArgumentOutOfRangeException(NameOf(rank))
                End If
    
                Return _squares.Single(Function(square) square.File.Equals(file) AndAlso square.Rank.Equals(rank))
            End Function
    
            Public Function HasEnemyPiece(square As Square, shade As Shades) As Boolean
                Dim opposingPieces As ChessPiece() = If(shade = Shades.Dark, _lightPieces, _darkPieces)
    
                Return opposingPieces.Any(Function(piece) piece IsNot Nothing AndAlso piece.Square.Equals(square))
            End Function
    
            Public Function HasFriendlyPiece(square As Square, shade As Shades) As Boolean
                Dim friendlyPieces As ChessPiece() = If(shade = Shades.Dark, _darkPieces, _lightPieces)
    
                Return friendlyPieces.Any(Function(piece) piece IsNot Nothing AndAlso piece.Square.Equals(square))
            End Function
    
            Public Function IsEmpty(square As Square) As Boolean
                Return _
                    _darkPieces.All(Function(piece) piece Is Nothing OrElse Not piece.Square.Equals(square)) AndAlso
                    _lightPieces.All(Function(piece) piece Is Nothing OrElse Not piece.Square.Equals(square))
            End Function
    
            Public Sub Reset()
                ' WIP
                InitializePawns()
            End Sub
    
            ' constructor
            Public Sub New()
                InitializeSquares()
            End Sub
    
        End Class
    
    End Namespace
    Square
    Represents a physical space on the chess board
    Code:
    Namespace Board
    
        Public Class Square
            Implements IEquatable(Of Square)
    
            ' private variables/constants
            Friend Const FILE_NAMES As String = "ABCDEFGH"
    
            Friend Const MAX_RANK As Integer = 7
    
            Private ReadOnly _file As Byte = 0
            Private ReadOnly _rank As Byte = 0
    
            ' properties
            Public ReadOnly Property Shade As Shades
                Get
                    Dim evenRank As Boolean = _rank Mod 2 = 0
                    Dim evenFile As Boolean = _file Mod 2 = 0
                    Return If(
                        (evenRank AndAlso evenFile) OrElse (Not evenRank AndAlso Not evenFile),
                        Shades.Dark,
                        Shades.Light
                    )
                End Get
            End Property
    
            Public ReadOnly Property File As Byte
                Get
                    Return _file
                End Get
            End Property
    
            Public ReadOnly Property FileName As String
                Get
                    Return FILE_NAMES(_file).ToString()
                End Get
            End Property
    
            Public ReadOnly Property Name As String
                Get
                    Return String.Concat(FileName, RankName)
                End Get
            End Property
    
            Public ReadOnly Property Rank As Byte
                Get
                    Return _rank
                End Get
            End Property
    
            Public ReadOnly Property RankName As String
                Get
                    Return (_rank + 1).ToString()
                End Get
            End Property
    
            ' public methods
            Public Overloads Function Equals(other As Square) As Boolean Implements IEquatable(Of Square).Equals
                If (other Is Nothing) Then
                    Return False
                End If
                If (ReferenceEquals(Me, other)) Then
                    Return True
                End If
                Return File = other.File AndAlso Rank = other.Rank
            End Function
    
            Public Overrides Function Equals(obj As Object) As Boolean
                Return Equals(TryCast(obj, Square))
            End Function
    
            Public Overrides Function GetHashCode() As Integer
                Return HashCode.Combine(File, Rank)
            End Function
    
            Public Shared Operator =(square1 As Square, square2 As Square) As Boolean
                If (square1 Is Nothing) Then
                    Return square2 Is Nothing
                End If
                Return square1.Equals(square2)
            End Operator
    
            Public Shared Operator <>(square1 As Square, square2 As Square) As Boolean
                If (square1 Is Nothing) Then
                    Return square2 IsNot Nothing
                End If
                Return Not square1.Equals(square2)
            End Operator
    
            Public Overrides Function ToString() As String
                Return Name
            End Function
    
            Public Sub New(rank As Byte, file As Byte)
                _rank = rank
                _file = file
            End Sub
    
        End Class
    
    End Namespace
    ChessPiece
    Parent class representing a chess piece; must be inherited
    Code:
    Namespace Pieces
    
        Public MustInherit Class ChessPiece
    
            Public MustOverride ReadOnly Property PieceType As PieceTypes
            Public ReadOnly Property Shade As Shades
            Public Property Square As Square
    
            Public MustOverride Function GetMoves(board As ChessBoard) As IEnumerable(Of Move)
    
            Public Sub New(shade As Shades)
                _Shade = shade
            End Sub
    
            Public Sub New(shade As Shades, square As Square)
                _Shade = shade
                _Square = square
            End Sub
    
        End Class
    
    End Namespace
    Pawn
    Child chess piece for a pawn
    Code:
    Namespace Pieces
    
        Public Class Pawn
            Inherits ChessPiece
    
            ' private variables
            Private Shared ReadOnly PromotionPieceTypes As PieceTypes() = {
                PieceTypes.Queen,
                PieceTypes.Rook,
                PieceTypes.Bishop,
                PieceTypes.Knight
            }
    
            ' private methods
            Private Sub AddForwardMoves(board As ChessBoard, moves As List(Of Move), targetRank As Byte)
                Dim targetSquare As Square = board.GetSquare(Square.File, targetRank)
    
                If (Not board.IsEmpty(targetSquare)) Then
                    Return
                End If
    
                If (IsPromotionRank(targetRank)) Then
                    AddPromotionMoves(moves, Square, targetSquare, MoveTypes.Promotion)
                Else
                    Dim nextMove As New Move(Square, targetSquare, MoveTypes.Normal)
                    moves.Add(nextMove)
    
                    If (IsStartingRank(Square.Rank)) Then
                        Dim skipRankNumber As Byte = Convert.ToByte(Square.Rank + If(Shade.Equals(Shades.Dark), 2, -2))
                        Dim skipSquare As Square = board.GetSquare(Square.File, skipRankNumber)
    
                        If (board.IsEmpty(skipSquare)) Then
                            moves.Add(New Move(Square, skipSquare, MoveTypes.Normal))
                        End If
                    End If
                End If
            End Sub
    
            Private Sub AddPawnCaptureMove(board As ChessBoard, moves As List(Of Move), targetFile As Byte, targetRank As Byte)
                Dim targetSquare As Square = board.GetSquare(targetFile, targetRank)
                If (Not board.HasEnemyPiece(targetSquare, Shade)) Then
                    Return
                End If
    
                If IsPromotionRank(targetRank) Then
                    AddPromotionMoves(moves, Square, targetSquare, MoveTypes.PromotionCapture)
                Else
                    moves.Add(New Move(Square, targetSquare, MoveTypes.Capture))
                End If
            End Sub
    
            Private Shared Sub AddPromotionMoves(moves As List(Of Move), fromSquare As Square, toSquare As Square, moveType As MoveTypes)
                For Each promotionPieceType As PieceTypes In PromotionPieceTypes
                    moves.Add(New Move(fromSquare, toSquare, moveType, promotionPieceType))
                Next
            End Sub
    
            Private Function IsStartingRank(rank As Byte) As Boolean
                Return If(Shade.Equals(Shades.Dark), rank = 1, rank = 6)
            End Function
    
            Private Function IsPromotionRank(rank As Byte) As Boolean
                Return If(Shade.Equals(Shades.Dark), rank = 7, rank = 0)
            End Function
    
            ' properties
            Public Overrides ReadOnly Property PieceType As PieceTypes = PieceTypes.Pawn
    
            ' public methods
            Public Overrides Function GetMoves(board As ChessBoard) As IEnumerable(Of Move)
                If (Square Is Nothing) Then
                    Return Enumerable.Empty(Of Move)()
                End If
    
                Dim nextRankNumber As Byte = Convert.ToByte(Square.Rank + If(Shade.Equals(Shades.Dark), -1, 1))
                Dim moves As New List(Of Move)
    
                AddForwardMoves(board, moves, nextRankNumber)
                If (Square.File > 0) Then
                    AddPawnCaptureMove(board, moves, CByte(Square.File - 1), nextRankNumber)
                End If
                If (Square.File < 7) Then
                    AddPawnCaptureMove(board, moves, CByte(Square.File + 1), nextRankNumber)
                End If
    
                Return moves
            End Function
    
            ' constructor
            Public Sub New(shade As Shades)
                MyBase.New(shade)
            End Sub
    
            Public Sub New(shade As Shades, square As Square)
                MyBase.New(shade, square)
            End Sub
    
        End Class
    
    End Namespace
    The pawn works for the most part, except I cannot figure out how to handle en passant.

    The rules for en passant are that the capturing pawn can take an opponent's pawn if:
    1. The opponent's previously moved piece was a pawn
    2. The opponent's pawn moved forward 2 spaces (which can only happen the first time its moved)
    3. The opponent's pawn falls on the same rank as the capturing pawn


    This is essentially game logic, but as you can tell from my setup, I'm trying to lay out all the potential moves of a given piece inside the Pawn class. My issue is that I'm not quite sure how to square those two concepts.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | HtmlLessons | CssLessons | Code Tags | Sword of Fury - Jameram

  2. #2
    PowerPoster
    Join Date
    Nov 2017
    Posts
    3,630

    Re: Determining En Passant

    After thinking about this for a good 15 seconds, this would be my idea:

    Add a new boolean property to the Pawn class called something like EnPassantAvailable. Any time a pawn is moved forward 2 spaces, check if an opponent Pawn is adjacent to the moved Pawn. If it is, set that property to True on the adjacent opponent pawn(s). Then, during the opponent's next turn, your code would need to be modified to allow an En Passant move for any Pawn where that property is True. At the end of each turn, set that property to False for all Pawns for the player that just completed their turn.

  3. #3
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    6,167

    Re: Determining En Passant

    When pawns are allowed to move two squares on initial move they did it so piece development in game happens faster. Think of this double move as two consecutive single moves. The en “peasant” can happen because the pawn is temporary on an attacked square. The inbetween capture at first probably could happen with other pieces too but I digress.

  4. #4
    PowerPoster Zvoni's Avatar
    Join Date
    Sep 2012
    Location
    To the moon and then left
    Posts
    5,261

    Re: Determining En Passant

    You've forgotten two rules
    The rules for en passant are that the capturing pawn can take an opponent's pawn if:

    1. The opponent's previously moved piece was a pawn
    2. The opponent's pawn moved forward 2 spaces (which can only happen the first time its moved)
    3. The opponent's pawn falls on the same rank as the capturing pawn


    4. Opponent's pawn lands on a tile adjacent to your pawn
    5. You can only capture en passant on the very next turn. If you make any other move on the board, you lose the opportunity

    @1 It actually has to be the pawn you want to beat "en passant" (see rule 5)
    @3 i actually had to look up "rank" in this context. "Row" might be a better term for the un-initiated

    In a nutshell: "en passant" is taking the opponents pawn "as if" he had moved only one tile forward,
    Because then you would be in a "taking" position by moving one tile diagonally,
    With "en passant" you end up on the same "target"-tile
    Last edited by Zvoni; Tomorrow at 31:69 PM.
    ----------------------------------------------------------------------------------------

    One System to rule them all, One Code to find them,
    One IDE to bring them all, and to the Framework bind them,
    in the Land of Redmond, where the Windows lie
    ---------------------------------------------------------------------------------
    People call me crazy because i'm jumping out of perfectly fine airplanes.
    ---------------------------------------------------------------------------------
    Code is like a joke: If you have to explain it, it's bad

  5. #5

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Posts
    12,370

    Re: Determining En Passant

    I didn't do the best job writing down the rules of en passant, but I assure you that I'm very familiar with them.

    This was more along the lines of how I implement the business logic in the context of having separation of logic between my various classes.

    I like OptionBase1's idea of adding a Boolean flag and flipping it in my engine. I'll need to modify my Pawn.AddPawnCaptureMove method to account for the flag and then I'll need modify my ChessBoard.HasEnemyPiece to return the piece (or create a ChessBoard.TryGetPiece method).
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | HtmlLessons | CssLessons | Code Tags | Sword of Fury - Jameram

  6. #6

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Posts
    12,370

    Re: Determining En Passant

    This is what I did, and I'm happy with marking the thread as resolved:

    ChessBoard
    Created a GetAllPieces private function and TryGetPiece public function:
    Code:
    Private Function GetAllPieces() As IEnumerable(Of ChessPiece)
        Return _darkPieces.Concat(_lightPieces)
    End Function
    Public Function TryGetPiece(square As Square) As ChessPiece
        Dim matchingPiece As ChessPiece = GetAllPieces().SingleOrDefault(
            Function(piece)
                Return piece IsNot Nothing AndAlso
                       piece.Square IsNot Nothing AndAlso
                       piece.Square.Equals(square)
            End Function)
        Return matchingPiece
    End Function
    ChessPiece
    Implemented IEquatable and created an InternalId:
    Code:
    Public MustInherit Class ChessPiece
        Implements IEquatable(Of ChessPiece)
    
        ' public properties
        Public ReadOnly Property InternalId As Guid
        Public MustOverride ReadOnly Property PieceType As PieceTypes
        Public ReadOnly Property Shade As Shades
        Public Property Square As Square
    
        ' public methods
        Public MustOverride Function GetMoves(board As ChessBoard) As IEnumerable(Of Move)
    
        Public Overloads Function Equals(other As ChessPiece) As Boolean Implements IEquatable(Of ChessPiece).Equals
            If (other Is Nothing) Then
                Return False
            End If
            If (ReferenceEquals(Me, other)) Then
                Return True
            End If
            Return InternalId = other.InternalId
        End Function
    
        Public Overrides Function Equals(obj As Object) As Boolean
            Return Equals(TryCast(obj, ChessPiece))
        End Function
    
        Public Overrides Function GetHashCode() As Integer
            Return HashCode.Combine(InternalId)
        End Function
    
        Public Shared Operator =(chessPiece1 As ChessPiece, chessPiece2 As ChessPiece) As Boolean
            If (chessPiece1 Is Nothing) Then
                Return chessPiece2 Is Nothing
            End If
            Return chessPiece1.Equals(chessPiece2)
        End Operator
    
        Public Shared Operator <>(chessPiece1 As ChessPiece, chessPiece2 As ChessPiece) As Boolean
            If (chessPiece1 Is Nothing) Then
                Return chessPiece2 IsNot Nothing
            End If
            Return Not chessPiece1.Equals(chessPiece2)
        End Operator
    
        ' constructor
        Public Sub New(shade As Shades)
            _InternalId = Guid.NewGuid()
            _Shade = shade
        End Sub
    
        Public Sub New(shade As Shades, square As Square)
            _InternalId = Guid.NewGuid()
            _Shade = shade
            _Square = square
        End Sub
    
    End Class
    Pawn
    Created an EnPassantCapture property (type of Pawn), created two new private functions AddEnPassantMove and IsEnPassantRank, and updated GetMoves to call AddEnPassantMove:
    Code:
    Private Sub AddEnPassantMove(board As ChessBoard, moves As List(Of Move), capturedPawnFile As Byte, capturedPawnRank As Byte, landingRank As Byte)
        If (EnPassantCapture Is Nothing) Then
            Return
        End If
        If (Not IsEnPassantRank(capturedPawnRank)) Then
            Return
        End If
    
        Dim landingSquare As Square = board.GetSquare(capturedPawnFile, landingRank)
        If (Not board.IsEmpty(landingSquare)) Then
            Return
        End If
    
        Dim capturedPawnSquare As Square = board.GetSquare(capturedPawnFile, capturedPawnRank)
        Dim adjacentPiece As ChessPiece = board.TryGetPiece(capturedPawnSquare)
        If (adjacentPiece Is Nothing OrElse adjacentPiece <> EnPassantCapture) Then
            Return
        End If
    
        moves.Add(New Move(Square, landingSquare, MoveTypes.EnPassant))
    End Sub
    
    Private Function IsEnPassantRank(rank As Byte) As Boolean
        Return If(Shade.Equals(Shades.Dark), rank = 3, rank = 4)
    End Function
    
    Public Property EnPassantCapture As Pawn
    
    Public Overrides Function GetMoves(board As ChessBoard) As IEnumerable(Of Move)
        If (Square Is Nothing) Then
            Return Enumerable.Empty(Of Move)()
        End If
    
        Dim nextRankNumber As Byte = Convert.ToByte(Square.Rank + If(Shade.Equals(Shades.Dark), -1, 1))
        Dim moves As New List(Of Move)
    
        AddForwardMoves(board, moves, nextRankNumber)
        If (Square.File > 0) Then
            AddPawnCaptureMove(board, moves, CByte(Square.File - 1), nextRankNumber)
            AddEnPassantMove(board, moves, CByte(Square.File - 1), Square.Rank, nextRankNumber) ' this
        End If
        If (Square.File < 7) Then
            AddPawnCaptureMove(board, moves, CByte(Square.File + 1), nextRankNumber)
            AddEnPassantMove(board, moves, CByte(Square.File + 1), Square.Rank, nextRankNumber) ' and this
        End If
    
        Return moves
    End Function
    So rather than doing a Boolean check, I'm doing a null and equality check against the pawn the triggered the en passant situation.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | HtmlLessons | CssLessons | Code Tags | Sword of Fury - Jameram

  7. #7
    Addicted Member
    Join Date
    Jun 2010
    Posts
    186

    Re: [RESOLVED] Determining En Passant

    A chess player here and I wrote a similar program years ago. What I did was to give every square a number starting at zero from the square a1. For example 0 for a1, 1 for b1, 2 for c1, etc. all the way to 63. Then I used these numbers in the squares to determine if En Passant was possible.

  8. #8
    PowerPoster Zvoni's Avatar
    Join Date
    Sep 2012
    Location
    To the moon and then left
    Posts
    5,261

    Re: [RESOLVED] Determining En Passant

    Quote Originally Posted by VB-MCU-User View Post
    A chess player here and I wrote a similar program years ago. What I did was to give every square a number starting at zero from the square a1. For example 0 for a1, 1 for b1, 2 for c1, etc. all the way to 63. Then I used these numbers in the squares to determine if En Passant was possible.
    Well, considering, there are only 16 possible tiles an "en passant" can occur that narrows it down significiantly
    Last edited by Zvoni; Tomorrow at 31:69 PM.
    ----------------------------------------------------------------------------------------

    One System to rule them all, One Code to find them,
    One IDE to bring them all, and to the Framework bind them,
    in the Land of Redmond, where the Windows lie
    ---------------------------------------------------------------------------------
    People call me crazy because i'm jumping out of perfectly fine airplanes.
    ---------------------------------------------------------------------------------
    Code is like a joke: If you have to explain it, it's bad

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width