patternpythonMinor
Unicode Chess PvP with Move Validation
Viewed 0 times
withvalidationunicodechessmovepvp
Problem
Main Purpose
This script allows two players to play chess on a virtual chessboard
printed on the screen by making use of the Unicode chess characters.
Visual appearence
User interaction
Moves are performed by typing in the start position of
the piece in chess notation, [ENTER], and the end position.
For example (starting from the start position):
Results in:
Legality checks
This programme also performs many checks to ensure that moves
are legal.
It checks:
AI extension possibility
The main loop loops between function objects to allow a possibility
of extension, introducing AI should be as easy as
replacing a
Board data storage
The board is represented as a dict {Point : piece}, the code
Empty squares are not even present in the dictionary as keys.
```
"""
-- Main Purpose
This script allows two players to play chess on a virtual chessboard
printed on the screen by making use of the Unicode chess characters.
-- Visual appearence
The chessboard looks like this:
8 ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6
5
4
3
2 ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 ♖ ♘ ♗ ♕ ♔ ♗
This script allows two players to play chess on a virtual chessboard
printed on the screen by making use of the Unicode chess characters.
Visual appearence
The chessboard looks like this:
8 ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6
5
4
3
2 ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
a b c d e f g hUser interaction
Moves are performed by typing in the start position of
the piece in chess notation, [ENTER], and the end position.
For example (starting from the start position):
Start? e2
End? e4Results in:
8 ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6
5
4 ♙
3
2 ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
a b c d e f g hLegality checks
This programme also performs many checks to ensure that moves
are legal.
It checks:
- If start and end position are both inside the board.
- If a player tries to move an opponents piece.
- If at the given start there is no piece.
- If a piece is being moved unlawfully. # TO DO support castling and en-passant
- If the end location is already occupied by a same colour piece.
- #TO DO: Limit options if the king is in check
AI extension possibility
The main loop loops between function objects to allow a possibility
of extension, introducing AI should be as easy as
replacing a
player_turn with an ai_turnBoard data storage
The board is represented as a dict {Point : piece}, the code
board[Point(x, y)] returns the piece at position (x, y).Empty squares are not even present in the dictionary as keys.
```
"""
-- Main Purpose
This script allows two players to play chess on a virtual chessboard
printed on the screen by making use of the Unicode chess characters.
-- Visual appearence
The chessboard looks like this:
8 ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7 ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6
5
4
3
2 ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1 ♖ ♘ ♗ ♕ ♔ ♗
Solution
Repeated logic
You have an
Recursion
Unpacking
It is my personal taste, but I find unpacking sexier than indexing. You can use it in various places:
-
-
-
You get the point.
sign
Can help you simplify some "delta" generation:
(I also changed the
By the way, your function had a bug. Try printing the list of
TODO list
You should add pawn promotion to your list, probably before castling or en-passant, but after limiting moves for checks (because you need that to check for end of game).
Given the amount of functions that take the
to easily restart games.
You have an
is_empty helper function that you don't use in make_move validation. You also check that the user input fits into the board both in ask_chess_coordinate and in make_move. You can keep the validation in ask_chess_coordinate an remove it from make_move since it makes more sense to warn about such error this early.Recursion
ask_chess_coordinate and human_player both use recursion to handle illegal moves/positions. But I don't see an interest to that as you’re not modifying their parameters. Using an explicit loop feels better here:def ask_chess_coordinate(prompt):
"""
Prompts the user for a square in chess coordinates and
returns a `Point` object indicating such square.
"""
while True:
given = input(prompt)
if not (given[0] in ALPHABET and given[1] in "12345678"):
print("Invalid coordinates, [ex: b4, e6, a1, h8 ...]. Try again.")
else:
return Point(ALPHABET.index(given[0]), 8 - int(given[1]))
def human_player(board, turn):
"""
Prompts a human player to make a move.
Also shows him the board to inform him about the
current game state and validates the move as
detailed in the main __doc__ section `Legality checks`
"""
while True:
print("{}'s Turn.\n".format("White" if turn else "Black"))
print_board(board)
start = ask_chess_coordinate("Start? ")
end = ask_chess_coordinate("End? ")
print("\n\n")
try:
make_move(board, start, end, turn)
except ValueError as e:
print("Invalid move: {}".format(e))
else:
breakUnpacking
It is my personal taste, but I find unpacking sexier than indexing. You can use it in various places:
-
ask_chess_coordinates (even though it makes it a bit more verbose :/)def ask_chess_coordinate(prompt):
"""
Prompts the user for a square in chess coordinates and
returns a `Point` object indicating such square.
"""
while True:
try:
x, y = input(prompt)
y = 8 - int(y)
except ValueError:
print("Invalid format. Expecting a letter and a digit [ex: b4, e6, a1, h8 ...].")
else:
if x not in ALPHABET and y not in range(BOARD_SIZE):
print("Coordinates out of bounds. Try again.")
else:
return Point(ALPHABET.index(x), y)-
bishop_move:intermediates = list(takewhile(lambda x: x != end, (Point(start.x + x, start.y + y) for x, y in ps)))-
legal_by_delta:return end in (Point(start.x + x, start.y + y) for x, y in deltas)You get the point.
sign
import math
def sign(x):
return int(math.copysign(1, x))Can help you simplify some "delta" generation:
def bishop_move(start, end, board):
"""
Can a bishop move from start to end?
"""
delta_x = sign(end.x - start.x)
delta_y = sign(end.y - start.y)
ps = ((delta_x * i, delta_y * i) for i in range(1, BOARD_SIZE))
intermediates = takewhile(end.__ne__, (Point(start.x + x, start.y + y) for x, y in ps))
return bishop_move_ignoring_obstruction(start, end) and all(is_empty(s, board) for s in intermediates)(I also changed the
lambda to propose an alternative and removed converting intermediates to a list as you don't need it.)def rook_move(start, end, board):
"""
Can a rook move from start to end?
Also checks if a piece blocks the path.
"""
def r(a, b):
direction = sign(b - a)
return range(a + direction, b, direction)
if start.x == end.x:
intermediates = (Point(start.x, y) for y in r(start.y, end.y))
if start.y == end.y:
intermediates = (Point(x, start.y) for x in r(start.x, end.x))
return rook_move_ignoring_obstruction(start, end) and all(is_empty(s, board) for s in intermediates)By the way, your function had a bug. Try printing the list of
intermediates positions instead of returning something and call it with rook_move(Point(3,4), Point(3, 1), None) ;)TODO list
You should add pawn promotion to your list, probably before castling or en-passant, but after limiting moves for checks (because you need that to check for end of game).
Given the amount of functions that take the
board as parameter, you may want to define a class instead. Or at least:if __name__ == "__main__":
interact_with_board(board.copy())to easily restart games.
Code Snippets
def ask_chess_coordinate(prompt):
"""
Prompts the user for a square in chess coordinates and
returns a `Point` object indicating such square.
"""
while True:
given = input(prompt)
if not (given[0] in ALPHABET and given[1] in "12345678"):
print("Invalid coordinates, [ex: b4, e6, a1, h8 ...]. Try again.")
else:
return Point(ALPHABET.index(given[0]), 8 - int(given[1]))
def human_player(board, turn):
"""
Prompts a human player to make a move.
Also shows him the board to inform him about the
current game state and validates the move as
detailed in the main __doc__ section `Legality checks`
"""
while True:
print("{}'s Turn.\n".format("White" if turn else "Black"))
print_board(board)
start = ask_chess_coordinate("Start? ")
end = ask_chess_coordinate("End? ")
print("\n\n")
try:
make_move(board, start, end, turn)
except ValueError as e:
print("Invalid move: {}".format(e))
else:
breakdef ask_chess_coordinate(prompt):
"""
Prompts the user for a square in chess coordinates and
returns a `Point` object indicating such square.
"""
while True:
try:
x, y = input(prompt)
y = 8 - int(y)
except ValueError:
print("Invalid format. Expecting a letter and a digit [ex: b4, e6, a1, h8 ...].")
else:
if x not in ALPHABET and y not in range(BOARD_SIZE):
print("Coordinates out of bounds. Try again.")
else:
return Point(ALPHABET.index(x), y)intermediates = list(takewhile(lambda x: x != end, (Point(start.x + x, start.y + y) for x, y in ps)))return end in (Point(start.x + x, start.y + y) for x, y in deltas)import math
def sign(x):
return int(math.copysign(1, x))Context
StackExchange Code Review Q#132582, answer score: 4
Revisions (0)
No revisions yet.