Code? Cool? Well OK, hang on.
The first online app I ever wrote was a chess/checkers game called Acies. It didn’t have AI, but it used raw TCP to keep the two game boards in sync, and allow players to chat and send ‘nudges’.
I was hunting around for some other code this morning, and I found a draft of the code used to enforce chess/checkers rules. It sets up a board, keeps track of the game, and tells you which moves are legal, and when you’re in check, checkmate or stalemate (and whatever they call it when you win checkers).
Slightly more interesting is that I wrote it in VB5 when I was 14. It seems I was smarter then than I am now, because although it’s somewhat unstructured and undisciplined, it’s more creatively crafted than anything I’d write today. This is the proof I need that alcohol kills brain cells!
Don’t ask me for the source for the rest of the game, let alone the game itself; no way am I looking that far back!
Option Explicit
Public Enum ePieceColor
Black = 0
White = 10
End Enum
Public Enum ePieceType
None = 0
Pawn = 1
Knight = 2
Bishop = 3
Rook = 4
Queen = 5
King = 6
Checker = 7
CheckerKing = 8
End Enum
Public Enum eGameType
Chess = 0
Checkers = 1
End Enum
Public Enum eGameState
NotStarted = 0
InProgress = 1
Check = 2
CheckMate = 3
stalemate = 4
End Enum
Private Square(1 To 8, 1 To 8) As Byte
Private pGameType As eGameType
Private pGameState As eGameState
Private pWhoseMove As ePieceColor
Private pCanCastle(1 To 18) As Boolean
Private pCanBeTakenEnPassent(10, 1 To 8) As Boolean
Public Property Get MoveIsValid(ByVal StartX As Byte, ByVal StartY As Byte, ByVal TargetX As Byte, ByVal TargetY As Byte, ParamArray Additional() As Variant)
MoveIsValid = False
Dim MD As String, Ad As String, i As Integer
If Abs(UBound(Additional) Mod 2) <> 1 Then Exit Property
If UBound(Additional) >= 0 Then
For i = 0 To UBound(Additional) Step 2
Ad = Ad & Chr(Val(Additional(i)) + Val(Additional(i + 1)) * 16)
Next i
End If
MD = GetValidMD(StartX, StartY, TargetX, TargetY, Ad)
If MD = "" Then Exit Property
MoveIsValid = IsValid(MD)
End Property
Public Function MovePiece(ByVal StartX As Byte, ByVal StartY As Byte, ByVal TargetX As Byte, ByVal TargetY As Byte, ParamArray Additional() As Variant) As Boolean
MovePiece = False
Dim MD As String, i As Integer
If Abs(UBound(Additional) Mod 2) <> 1 Then Exit Function
If UBound(Additional) >= 0 Then
For i = 0 To UBound(Additional) Step 2
MD = MD & Chr(Val(Additional(i)) + Val(Additional(i + 1)) * 16)
Next i
End If
MD = GetValidMD(StartX, StartY, TargetX, TargetY, MD)
If MD = "" Then Exit Function
MovePiece = IsValid(MD)
If Not MovePiece Then Exit Function
If IsCheck(MD, 10 - pWhoseMove) Then pGameState = Check
pGameState = InProgress
ResetEnPassent pWhoseMove
If PT(StartX, StartY) = Pawn And Abs(StartY - TargetY) = 2 Then pCanBeTakenEnPassent(pWhoseMove, StartX) = True
If PT(StartX, StartY) = King Then
pCanCastle(pWhoseMove + 1) = False
pCanCastle(pWhoseMove + 8) = False
End If
If PT(StartX, StartY) = Rook Then pCanCastle(pWhoseMove + StartX) = False
ForceMove MD
pWhoseMove = 10 - pWhoseMove
If Not CanMove Then
If pGameState = Check Then pGameState = CheckMate Else pGameState = stalemate
End If
End Function
Public Property Get WhoseMove() As ePieceColor
WhoseMove = pWhoseMove
End Property
Public Property Let WhoseMove(vData As ePieceColor)
pWhoseMove = vData
ResetEnPassent vData
End Property
Public Property Get GameType() As eGameType
GameType = pGameType
End Property
Public Property Get GameState() As eGameState
GameState = pGameState
End Property
Public Property Get Free(ByVal XIndex As Byte, ByVal YIndex As Byte) As Boolean
Free = (PieceType(XIndex, YIndex) = None)
End Property
Public Property Get PieceColor(ByVal XIndex As Byte, ByVal YIndex As Byte) As ePieceColor
PieceColor = PC(XIndex, YIndex)
End Property
Public Property Let PieceColor(ByVal XIndex As Byte, ByVal YIndex As Byte, ByVal vData As ePieceColor)
Square(XIndex, YIndex) = PT(XIndex, YIndex) + vData
End Property
Public Property Get PieceType(ByVal XIndex As Byte, ByVal YIndex As Byte) As ePieceType
PieceType = PT(XIndex, YIndex)
End Property
Public Property Let PieceType(ByVal XIndex As Byte, ByVal YIndex As Byte, ByVal vData As ePieceType)
Square(XIndex, YIndex) = PC(XIndex, YIndex) + vData
End Property
Public Sub NewGame(NewGameType As eGameType, Optional WhoStarts As ePieceColor = -1)
Dim i As Integer
If WhoStarts = -1 Then
If NewGameType = Chess Then WhoStarts = White Else WhoStarts = Black
End If
pWhoseMove = WhoStarts
pGameType = NewGameType
pGameState = NotStarted
pCanCastle(White + 3) = True
pCanCastle(Black + 3) = True
pCanCastle(White + 7) = True
pCanCastle(Black + 7) = True
ResetEnPassent Black
ResetEnPassent White
Dim x, y, pCol As Integer
If pGameType = Chess Then
For x = 1 To 8
For y = 1 To 8
If y < 4 Then pCol = White Else pCol = Black
Select Case y
Case 3, 4, 5, 6: Square(x, y) = 0
Case 2, 7: Square(x, y) = pCol + Pawn
Case 1, 8
Select Case x
Case 1, 8: Square(x, y) = pCol + Rook
Case 2, 7: Square(x, y) = pCol + Knight
Case 3, 6: Square(x, y) = pCol + Bishop
Case 4: Square(x, y) = pCol + Queen
Case 5: Square(x, y) = pCol + King
End Select
End Select
Next y
Next x
Else
For x = 1 To 8
For y = 1 To 8
If y < 4 Then pCol = White Else pCol = Black
If y <> 4 And y <> 5 And (x + y) Mod 2 = 0 Then
Square(x, y) = Checker + pCol
Else
Square(x, y) = 0
End If
Next y
Next x
End If
End Sub
Private Function IsValid(MoveDescription As String) As Boolean
IsValid = False
If pGameState = CheckMate Or pGameState = stalemate Then Exit Function
Dim pCol As ePieceColor
pCol = PC(Asc(Left(MoveDescription, 1)) And 15, (Asc(Left(MoveDescription, 1)) And 240) / 16)
If Not pCol = pWhoseMove Then Exit Function
If Not CanGoToSquare(MoveDescription) Then Exit Function
If IsCheck(MoveDescription, pCol) Then Exit Function
IsValid = True
End Function
Private Function CanMove() As Boolean
CanMove = False
If pGameType = Checkers Then Exit Function
Dim i, j, k, l As Byte, MD As String
For i = 1 To 8
For j = 1 To 8
If Not Free(i, j) And PieceColor(i, j) = WhoseMove Then
For k = 1 To 8
For l = 1 To 8
If Free(k, l) And IsValid(GetValidMD(i, j, k, l)) Then
CanMove = True
End If
Next l
Next k
End If
Next j
Next i
End Function
Private Function IsCheck(MoveDescription As String, CheckToWho As ePieceColor) As Boolean
IsCheck = False
If pGameType = Checkers Then Exit Function
Dim i, j As Byte, CheckMove As String
Dim OldSquare(1 To 8, 1 To 8) As Integer
If MoveDescription <> "" Then
For i = 1 To 8
For j = 1 To 8
OldSquare(i, j) = Square(i, j)
Next j
Next i
ForceMove MoveDescription
End If
For i = 1 To 8
For j = 1 To 8
If Square(i, j) = CheckToWho + King Then
CheckMove = Chr(i + j * 16)
End If
Next j
Next i
For i = 1 To 8
For j = 1 To 8
If Not Free(i, j) And PC(i, j) = 10 - CheckToWho Then
IsCheck = IsCheck Or CanGoToSquare(Chr(i + j * 16) & CheckMove)
End If
Next j
Next i
If MoveDescription <> "" Then
For i = 1 To 8
For j = 1 To 8
Square(i, j) = OldSquare(i, j)
Next j
Next i
End If
End Function
Private Sub ForceMove(MoveDescription As String)
Dim i As Integer
If pGameType = Checkers Then
Dim x(255) As Byte
Dim y(255) As Byte
For i = 0 To Len(MoveDescription) - 1
x(i) = Asc(Mid(MoveDescription, i + 1, 1)) And 15
y(i) = (Asc(Mid(MoveDescription, i + 1, 1)) And 240) / 16
Next i
For i = 0 To Len(MoveDescription) - 2
Square(x(i + 1), y(i + 1)) = Square(x(i), y(i))
Square(x(i), y(i)) = None
If Abs(x(i) - x(i + 1)) = 2 Then Square((x(i) + x(i + 1)) / 2, (y(i) + y(i + 1)) / 2) = None
Next i
Else
Dim X1, X2, Y1, Y2 As Integer
X1 = Asc(Left(MoveDescription, 1)) And 15
Y1 = (Asc(Left(MoveDescription, 1)) And 240) / 16
X2 = Asc(Right(MoveDescription, 1)) And 15
Y2 = (Asc(Right(MoveDescription, 1)) And 240) / 16
If PT(X1, Y1) = King And Abs(X1 - X2) = 2 Then
If X2 = 3 Then
Square(1, Y1) = None
Square(4, Y1) = Rook + PC(X1, Y1)
Else
Square(8, Y1) = None
Square(6, Y1) = Rook + PC(X1, Y1)
End If
End If
If IsEnPassent(MoveDescription) Then Square(X2, Y1) = None
Square(X2, Y2) = Square(X1, Y1)
Square(X1, Y1) = None
End If
End Sub
Private Function PT(ByVal XIndex As Byte, ByVal YIndex As Byte) As ePieceType
PT = Square(XIndex, YIndex) Mod 10
End Function
Private Function PC(ByVal XIndex As Byte, ByVal YIndex As Byte) As ePieceColor
PC = Int(Square(XIndex, YIndex) / 10) * 10
End Function
Private Function CanGoToSquare(MoveDescription As String) As Boolean
CanGoToSquare = False
Dim X1, X2, Y1, Y2, i, StepX, StepY As Integer, pCol As ePieceColor, pType As ePieceType
X1 = Asc(Left(MoveDescription, 1)) And 15
Y1 = (Asc(Left(MoveDescription, 1)) And 240) / 16
X2 = Asc(Right(MoveDescription, 1)) And 15
Y2 = (Asc(Right(MoveDescription, 1)) And 240) / 16
pCol = PC(X1, Y1)
pType = PT(X1, Y1)
If PC(X2, Y2) = PC(X1, Y1) And Not Free(X2, Y2) Then Exit Function
If X1 = X2 And Y1 = Y2 Then Exit Function
If pGameType = Checkers And Not Free(X2, Y2) Then Exit Function
Select Case pType
Case Checker, CheckerKing
If pType = Checker Then
If Y2 < Y1 And pCol = White Then Exit Function
If Y2 > Y1 And pCol = Black Then Exit Function
End If
If Abs(X1 - X2) = 1 And Abs(Y1 - Y2) = 1 Then CanGoToSquare = True
If Abs(X1 - X2) = 2 And Abs(Y1 - Y2) = 2 And PC((X1 + X2) / 2, (Y1 + Y2) / 2) <> pCol Then CanGoToSquare = True
Exit Function
Case Knight
If X1 = X2 Or Y1 = Y2 Then Exit Function
If Abs(X1 - X2) + Abs(Y1 - Y2) <> 3 Then Exit Function
Case Bishop
If Abs(X1 - X2) <> Abs(Y1 - Y2) Then Exit Function
If X1 < X2 Then StepX = 1 Else StepX = -1
If Y1 < Y2 Then StepY = 1 Else StepY = -1
For i = X1 + StepX To X2 - StepX Step StepX
If Not Free(i, Y1 + (StepY * i)) Then Exit Function
Next i
Case Queen
For i = 3 To 4
PieceType(X1, Y1) = i
If CanGoToSquare(Chr(X1 + Y1 * 16) & Chr(X2 + Y2 * 16)) Then CanGoToSquare = True
Next i
PieceType(X1, Y1) = Queen
If Not CanGoToSquare Then Exit Function
Case King
If pGameState <> Check And pCanCastle(pCol + X2) And Y1 = Y2 And Abs(X1 - X2) = 2 And X1 = 5 Then
If X2 = 3 Then
StepX = 2: StepY = 4: i = 1
Else
StepX = 6: StepY = 7: i = 8
End If
If Square(i, Y1) <> Rook + pCol Then Exit Function
For i = StepX To StepY
If Not Free(i, Y1) Then Exit Function
Next i
End If
If Abs(X1 - X2) > 1 Or Abs(Y1 - Y2) > 1 Then Exit Function
Case Rook
If X1 <> X2 And Y1 <> Y2 Then Exit Function
If Abs(X1 - X2) = 1 Or Abs(Y1 - Y2) = 1 Then
CanGoToSquare = True
Exit Function
Else
If Y1 = Y2 Then
If X1 < X2 Then StepX = 1 Else StepX = -1
For i = X1 + StepX To X2 - StepX Step StepX
If Not Free(i, Y1) Then Exit Function
Next i
Else
If Y1 < Y2 Then StepY = 1 Else StepY = -1
For i = Y1 + StepY To Y2 - StepY Step StepY
If Not Free(X1, i) Then Exit Function
Next i
End If
End If
Case Pawn
Dim pDir As Integer: pDir = ((pCol / 5) - 1)
If Y2 = Y1 + pDir And X1 = X2 And Free(X2, Y2) Then CanGoToSquare = True
If ((Y1 = 2 And pCol = White) Or (Y1 = 7 And pCol = Black)) And Y2 = Y1 + 2 * pDir And X1 = X2 And Free(X2, Y2) Then CanGoToSquare = True
If Y2 = Y1 + pDir And Abs(X1 - X2) = 1 And PC(X2, Y2) <> pCol And Not Free(X2, Y2) Then CanGoToSquare = True
CanGoToSquare = CanGoToSquare Or IsEnPassent(MoveDescription)
Exit Function
End Select
CanGoToSquare = True
End Function
Private Function IsEnPassent(MoveDescription As String) As Boolean
IsEnPassent = False
Dim X1, X2, Y1, Y2, pDir As Integer, pCol As ePieceColor, pType As ePieceType
X1 = Asc(Left(MoveDescription, 1)) And 15
Y1 = (Asc(Left(MoveDescription, 1)) And 240) / 16
X2 = Asc(Right(MoveDescription, 1)) And 15
Y2 = (Asc(Right(MoveDescription, 1)) And 240) / 16
pCol = PC(X1, Y1)
pType = PT(X1, Y1)
pDir = ((pCol / 5) - 1) * -1
If Y2 <> Y1 + pDir Then Exit Function
If Abs(X1 - X2) <> 1 Then Exit Function
If Square(X2, Y1) <> Pawn + (10 - pCol) Then Exit Function
If Not Free(X2, Y2) Then Exit Function
If Not pCanBeTakenEnPassent(10 - pCol, X2) Then Exit Function
IsEnPassent = True
End Function
Private Sub ResetEnPassent(WhichColor As ePieceColor)
Dim i As Integer
For i = 1 To 8
pCanBeTakenEnPassent(WhichColor, i) = False
Next i
End Sub
Private Function GetValidMD(ByVal StartX As Byte, ByVal StartY As Byte, ByVal TargetX As Byte, ByVal TargetY As Byte, Optional Additional As String = "") As String
GetValidMD = ""
Dim i As Integer, MD As String
If Val(StartX) > 8 Or Val(StartX) < 1 Then Exit Function
If Val(StartY) > 8 Or Val(StartY) < 1 Then Exit Function
If Val(TargetX) > 8 Or Val(TargetX) < 1 Then Exit Function
If Val(TargetY) > 8 Or Val(TargetY) < 1 Then Exit Function
If Additional <> "" Then
If pGameType = Chess Then Exit Function
For i = 1 To Len(Additional)
MD = CStr(Asc(Mid(Additional, i, 1)) And 15)
If Val(MD) < 1 Or Val(MD) > 8 Then Exit Function
MD = CStr((Asc(Mid(Additional, i, 1)) And 240) / 16)
If Val(MD) < 1 Or Val(MD) > 8 Then Exit Function
Next i
End If
MD = Chr(StartX + StartY * 16) & Chr(TargetX + TargetY * 16) & Additional
GetValidMD = MD
End Function