|
-
Jun 1st, 2026, 01:55 PM
#1
[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:
- The opponent's previously moved piece was a pawn
- The opponent's pawn moved forward 2 spaces (which can only happen the first time its moved)
- 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.
-
Jun 1st, 2026, 03:46 PM
#2
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.
-
Jun 2nd, 2026, 12:27 AM
#3
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.
-
Jun 2nd, 2026, 01:16 AM
#4
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:
- The opponent's previously moved piece was a pawn
- The opponent's pawn moved forward 2 spaces (which can only happen the first time its moved)
- 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
-
Jun 2nd, 2026, 08:17 AM
#5
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).
-
Jun 2nd, 2026, 08:52 AM
#6
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.
-
Jun 2nd, 2026, 12:54 PM
#7
Addicted Member
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.
-
Re: [RESOLVED] Determining En Passant
 Originally Posted by VB-MCU-User
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|