backgammon/board.py

175 lines
6.3 KiB
Python
Raw Normal View History

2018-02-05 21:31:34 +00:00
import numpy as np
2018-02-13 13:38:49 +00:00
import itertools
2018-02-05 21:31:34 +00:00
class Board:
2018-02-13 13:38:49 +00:00
initial_state = [ 0,
2, 0, 0, 0, 0, -5,
0, -3, 0, 0, 0, 5,
-5, 0, 0, 0, 3, 0,
5, 0, 0, 0, 0, -2,
0 ]
2018-02-06 22:46:15 +00:00
# TODO: Remember to handle pushing other pieces to home
# TODO: Also remember that a player can't move backwards and the one
# player goes from 1-47 while the other goes from 47-1
2018-02-05 21:31:34 +00:00
2018-02-06 22:46:15 +00:00
# Remember to handle edge case when we're on the last moves and you may go
# from position 22 -> 24 on a 6, if you have no pieces behind 22. Simply
# check if any are behind if you're circle or if any are higher if you are
# X, then it can be allowed.
2018-02-05 21:31:34 +00:00
2018-02-06 22:46:15 +00:00
# Also, the check_move will also fail when you're attempting to leave a
2018-02-07 15:09:09 +00:00
# bar. A fix of this is of course to check if the from_idx = bar and if so,
2018-02-06 22:46:15 +00:00
# allow some extra stuff!
2018-02-07 15:09:09 +00:00
# TODO: write tests
# FIXME: make sure to disallow backwards movement
# TODO: implement double roll feature (4 dice if dice are equal)
# TODO: implement moving checkers into home (bearing off)
# TODO: handle barring in a more elengant way
# TODO: allow bearing off with non-exact die roll if this is only possible
# move
2018-02-13 13:38:49 +00:00
# TODO: Allow not doing anything if and only if no alternatives
@staticmethod
def is_move_valid(board, player, face_value, move):
board = list(board)
from_idx = move[0]
to_idx = move[1]
to_state = board[to_idx]
from_state = board[from_idx]
2018-02-07 15:09:09 +00:00
2018-02-06 22:46:15 +00:00
if from_idx == 26:
from_idx = 1
2018-02-13 13:38:49 +00:00
face_value -= 1
2018-02-06 22:46:15 +00:00
elif from_idx == 27:
from_idx = 24
2018-02-13 13:38:49 +00:00
face_value -= 1
2018-02-07 15:09:09 +00:00
if not (1 <= from_idx <= 24 and
2018-02-13 13:38:49 +00:00
1 <= to_idx <= 24):
2018-02-05 21:31:34 +00:00
return False
2018-02-13 13:38:49 +00:00
elif ( abs( from_idx - to_idx ) != face_value ):
2018-02-05 21:31:34 +00:00
return False
2018-02-07 15:09:09 +00:00
elif (from_state * player >= 1 and # Is moving player's own checker?
(to_state * player >= 0 or # Is 'to' empty or has player's own checkers?
to_state * -player == 1)): # Can opponent checker be hit?
2018-02-05 21:31:34 +00:00
return True
2018-02-13 13:38:49 +00:00
@staticmethod
def is_winner(board, player):
for i in range(1,25):
2018-02-13 13:38:49 +00:00
if player * board[i] > 0:
return False
return True
2018-02-13 13:38:49 +00:00
@staticmethod
def calculate_legal_states(board, player, roll):
2018-02-05 22:36:32 +00:00
# Find all pips with things on them belonging to the player
# Iterate through each index and check if it's a possible move given the roll
# If player is O, then check for idx + roll
# If player is X, then check for idx - roll
2018-02-13 13:38:49 +00:00
print("Find legal moves: ",roll,"-"*20)
def idxs_with_checkers_of_current_player(board):
idxs = []
for idx, checker_count in enumerate(board):
if checker_count * player >= 1:
idxs.append(idx)
return idxs
2018-02-05 21:31:34 +00:00
2018-02-13 13:38:49 +00:00
def calc_moves(board, face_value):
idxs_with_checkers = idxs_with_checkers_of_current_player(board)
boards = [(do_move(board,
player,
(idx, idx + (face_value * player))) if
is_move_valid(board,
player,
face_value,
(idx, idx + (face_value * player))) else None)
for idx
in idxs_with_checkers]
2018-02-13 13:38:49 +00:00
# If no move can be made, make sure to include current board
if is_move_valid(board, player, face_value, None):
boards.append(board)
return list(filter(None, boards)) # Remove None-values
# ------------------
# 1. Determine if dice have identical face value
# 2. Iterate through remaining dice
legal_moves = set()
dice_permutations = list(itertools.permutations(roll)) if roll[0] != roll[1] else [[roll[0]]*4]
for pair in dice_permutations:
# Calculate boards resulting from first move
boards = calc_moves(board, pair[0])
for x in dice_permutations[1:None]:
# Calculate boards resulting from second move
nested_boards = [calc_moves(board, x) for board in boards]
boards = [board for boards in nested_boards for board in boards]
# Add resulting unique boards to set of legal boards resulting from roll
legal_moves = legal_moves | set(boards)
2018-02-05 22:36:32 +00:00
return legal_moves
2018-02-13 13:38:49 +00:00
@staticmethod
def pretty(board):
temp = []
2018-02-13 13:38:49 +00:00
for x in board:
if x >= 0:
temp.append(" {}".format(x))
else:
temp.append("{}".format(x))
2018-02-07 15:27:03 +00:00
2018-02-05 21:31:34 +00:00
return """
2018-02-07 23:00:18 +00:00
13 14 15 16 17 18 19 20 21 22 23 24
--------------------------------------------------------------------------
2018-02-13 13:38:49 +00:00
| {11}| {10}| {9}| {8}| {7}| {6}| bar -1: {24} | {5}| {4}| {3}| {2}| {1}| {0}| end -1: TODO|
2018-02-07 23:00:18 +00:00
|---|---|---|---|---|---|-----------|---|---|---|---|---|---|
2018-02-13 13:38:49 +00:00
| {12}| {13}| {14}| {15}| {16}| {17}| bar 1: {25} | {18}| {19}| {20}| {21}| {22}| {23}| end 1: TODO|
2018-02-07 23:00:18 +00:00
--------------------------------------------------------------------------
12 11 10 9 8 7 6 5 4 3 2 1
""".format(*temp)
2018-02-07 15:09:09 +00:00
2018-02-13 13:38:49 +00:00
@staticmethod
def do_move(board, player, move):
# Implies that move is valid; make sure to check move validity before calling do_move(...)
def move_to_bar(board, to_idx):
board = list(board)
if player == 1:
board[0] += player
else:
board[25] += player
board[to_idx] = 0
return board
2018-02-07 15:09:09 +00:00
2018-02-13 13:38:49 +00:00
# TODO: Moving in from bar is handled by the representation
# TODONE: Handle bearing off
2018-02-05 21:31:34 +00:00
2018-02-13 13:38:49 +00:00
from_idx = move[0]
to_idx = move[1]
board = list(board) # Clone board
# 'Lift' checker
board[from_idx] -= player
# Handle bearing off
if to_idx < 1 or to_idx > 24:
return board
# Handle hitting checkers
if board[to_idx] * player == -1:
board = move_to_bar(board, to_idx)
# Put down checker
board[to_idx] += player
return board