update port HEXAPAWN to Python

mostly working, did major refactoring for clarity and understanding what all of the pieces are doing. Still another round of refactoring to go to make it clearer what's going on, but I think almost all of the logic is correct.

There's what looks like a bug where the human wins by advancing to the back row, and the computer incorrectly says that it has lost because it has no moves. I suspect this is a bug in my logic, but I'll have to trace through the BASIC to verify.

The game is playable and somewhat fun in its current state.
This commit is contained in:
Dave LeCompte
2021-03-02 09:05:13 -08:00
parent 3d6267433a
commit 3a9e19197d

View File

@@ -14,8 +14,17 @@ Conversion to MITS BASIC by Steve North
Port to Python by Dave LeCompte
"""
import collections
import random
PAGE_WIDTH = 64
HUMAN_PIECE = 1
EMPTY_SPACE = 0
COMPUTER_PIECE = -1
ComputerMove = collections.namedtuple('ComputerMove', ['x', 'y', 'm1', 'm2'])
def print_centered(msg):
spaces = " " * ((PAGE_WIDTH - len(msg)) // 2)
print(spaces + msg)
@@ -70,21 +79,20 @@ def prompt_yes_no(msg):
elif response[0] == "N":
return False
def fnr(x):
score = {1: -3,
2: -2,
3: -1,
4: -6,
5: -5,
6: -4,
7: -9,
8: -8,
9: -7}
def reverse_board_position(x):
assert(x >= 1 and x < 10)
score = {1: 3,
2: 2,
3: 1,
4: 6,
5: 5,
6: 4,
7: 9,
8: 8,
9: 7}
return score[x]
def fnm(y):
return y % 10
def get_b(x, y):
data = [[-1, -1, -1, 1, 0, 0, 0, 1, 1],
[-1, -1, -1, 0, 1, 0, 1, 0, 1],
@@ -106,36 +114,49 @@ def get_b(x, y):
[ 0, -1, 0, 1, -1, 0, 0, 0, 0],
[-1, 0, 0, -1, 1, 0, 0, 0, 0]]
return data[x+1][y+1]
assert(x >= 1 and x < 20)
assert(y >= 1 and y < 10)
return data[x-1][y-1]
m_data = [[24, 25, 36, 0],
[14, 15, 36, 0],
[15, 35, 36, 47],
[36, 58, 59, 0],
[15, 35, 36, 0],
[24, 25, 26, 0],
[26, 57, 58, 0],
[26, 35, 0, 0],
[47, 48, 0, 0],
[35, 36, 0, 0],
[35, 36, 0, 0],
[36, 0, 0, 0],
[47, 58, 0, 0],
[15, 0, 0, 0],
[26, 47, 0, 0],
[47, 58, 0, 0],
[35, 36, 47, 0],
[28, 58, 0, 0],
[15, 47, 0, 0]]
def get_m(x, y):
data = [[24, 25,36,0],
[14,15,36,0],
[15,35,36,47],
[36,58,59,0],
[15,35,36,0],
[24,25,26,0],
[26,57,58,0],
[26,35,0,0],
[47,48,0,0],
[35,36,0,0],
[35,36,0,0],
[36,0,0,0],
[47,58,0,0],
[15,0,0,0],
[26,47,0,0],
[47,58,0,0],
[35,36,47,0],
[28,58,0,0],
[15,47,0,0]]
assert(x >= 1 and x < 20)
assert(y >= 1 and y < 5)
return data[x+1][y+1]
return m_data[x-1][y-1]
def set_m(x, y, value):
m_data[x-1][y-1] = value
def init_board():
return [-1] * 3 + [0] * 3 + [1] * 3
return ([COMPUTER_PIECE] * 3 +
[EMPTY_SPACE] * 3 +
[HUMAN_PIECE] * 3)
def print_board(board):
pieces = "X.O"
piece_dict = {COMPUTER_PIECE: 'X',
EMPTY_SPACE: '.',
HUMAN_PIECE: 'O'}
space = " "*10
print()
@@ -145,7 +166,7 @@ def print_board(board):
line += space
space_number = i * 3 + j
space_contents = board[space_number]
line += pieces[space_contents + 1]
line += piece_dict[space_contents]
print(line)
print()
@@ -157,7 +178,7 @@ def get_coordinates():
m1, m2 = [int(c) for c in response.split(',')]
return m1, m2
except ValueError as ve:
print("ILLEGAL MOVE.")
print_illegal()
def print_illegal():
print("ILLEGAL MOVE.")
@@ -168,78 +189,329 @@ def board_contents(board, space_number):
def set_board(board, space_number, new_value):
board[space_number - 1] = new_value
def is_legal_move(board, m1, m2):
if board_contents(board, m1) != HUMAN_PIECE:
# Start space doesn't contain player's piece
return False
if board_contents(board, m2) == HUMAN_PIECE:
# Destination space contains player's piece (can't capture your own piece)
return False
# line 160
is_capture = (m2-m1 != -3)
if is_capture and board_contents(board, m2) != COMPUTER_PIECE:
# Destination does not contain computer piece
return False
# line 170
if m2 > m1:
# can't move backwards
return False
# line 180
if (not is_capture) and board_contents(board, m2) != EMPTY_SPACE:
# Destination is not open
return False
# line 185
if m2-m1 < -4:
# too far
return False
# line 186
if m1 == 7 and m2 == 3:
# can't jump corner to corner (wrapping around the board)
return False
return True
def player_piece_on_back_row(board):
for space in range(1,4):
if board_contents(board, space) == HUMAN_PIECE:
return True
return False
def computer_piece_on_front_row(board):
for space in range(7, 10):
if board_contents(board, space) == COMPUTER_PIECE:
return True
return False
def all_human_pieces_captured(board):
return len(list(get_human_spaces(board))) == 0
def all_computer_pieces_captured(board):
return len(list(get_computer_spaces(board))) == 0
def human_win(last_computer_move):
print("YOU WIN")
set_m(last_computer_move.x, last_computer_move.y, 0)
global l
l += 1
def computer_win(has_moves):
if has_moves:
msg = "YOU CAN'T MOVE, SO "
else:
msg = ""
msg += "I WIN"
print(msg)
global w
w += 1
def show_scores():
print(f"I HAVE WON {w} AND YOU {l} OUT OF {w+l} GAMES.")
print()
def human_has_move(board):
# line 690
for i in get_human_spaces(board):
if board_contents(board, i-3) == EMPTY_SPACE:
# can move piece forward
return True
elif reverse_board_position(i) == i:
# line 780
# can capture from center
if ((board_contents(board, i-2) == COMPUTER_PIECE) or
(board_contents(board, i-4) == COMPUTER_PIECE)):
return True
else:
continue
elif i < 7:
# Line 760
assert((i == 4) or (i == 6))
# can capture computer piece at 2
if board_contents(board, 2) == COMPUTER_PIECE:
return True
else:
continue
elif board_contents(board, 5) == COMPUTER_PIECE:
assert((i == 7) or (i == 9))
# can capture computer piece at 5
return True
else:
continue
return False
def get_board_spaces():
yield from range(1, 10)
def get_board_spaces_with(board, val):
for i in get_board_spaces():
if board_contents(board, i) == val:
yield i
def get_human_spaces(board):
yield from get_board_spaces_with(board, HUMAN_PIECE)
def get_empty_spaces(board):
yield from get_board_spaces_with(board, EMPTY_SPACE)
def get_computer_spaces(board):
yield from get_board_spaces_with(board, COMPUTER_PIECE)
def has_computer_move(board):
for i in get_computer_spaces(board):
found_move = False
if board_contents(board, i+3) == EMPTY_SPACE:
# can move forward (down)
return True
# line 260
if reverse_board_position(i) == i:
# i is in the middle column
if ((board_contents(board, i + 2) == HUMAN_PIECE) or
(board_contents(board, i + 4) == HUMAN_PIECE)):
return True
else:
# line 270
if i > 3:
# beyond the first row
if board_contents(board, 8) == HUMAN_PIECE:
# can capture on 8
return True
else:
continue
else:
# line 280
if board_contents(board, 5) == HUMAN_PIECE:
# can capture on 5
return True
else:
continue
return False
def get_flipped_table(b_line): # TODO remove table altogether
t = {}
# line 360
for row in range(1, 4):
for column in range(1, 4):
# line 380
flipped_column = 4 - column
# fill out t to represent the data from b flipped left to right
space = (row-1) * 3 + column
flipped_space = (row - 1) * 3 + flipped_column
t[space] = get_b(b_line, flipped_space)
return t
def board_matches_b(b_line, board):
for s in get_board_spaces():
if get_b(b_line, s) != board_contents(board, s):
return False
return True
def board_matches_flipped_b(b_line, board):
flipped_table = get_flipped_table(b_line)
for s in get_board_spaces():
if flipped_table[s] != board_contents(board, s):
return False
return True
def does_b_line_match(b_line, board):
if board_matches_b(b_line, board):
return True, False
elif board_matches_flipped_b(b_line, board):
return True, True
else:
return False, None
def has_any_m_table(x):
for i in range(1,5):
if get_m(x, i) != 0:
return True
return False
def pick_from_m_table(x):
valid_y_list = [y for y in range(1,5) if get_m(x, y) != 0]
assert(len(valid_y_list) > 0)
return random.choice(valid_y_list)
def get_move_for_b_line(b_line, reverse_board):
# line 540
x = b_line
if not has_any_m_table(x):
return None
# line 600
y = pick_from_m_table(x)
# line 610
mxy = get_m(x, y)
m1 = mxy // 10
m2 = mxy % 10
if reverse_board:
m1 = reverse_board_position(m1)
m2 = reverse_board_position(m2)
return ComputerMove(x, y, m1, m2)
def find_b_line_that_matches_board(board):
for b_line in range(1,20):
matches, reverse_board = does_b_line_match(b_line, board)
if matches:
return b_line, reverse_board
# THE TERMINATION OF THIS LOOP IS IMPOSSIBLE
print("ILLEGAL BOARD PATTERN.")
assert(False)
def pick_computer_move(board):
if not has_computer_move(board):
# Line 340
return None
# line 350
b_line, reverse_board = find_b_line_that_matches_board(board)
m = get_move_for_b_line(b_line, reverse_board)
if m == None:
print("I RESIGN")
return None
return m
def play_game():
last_computer_move = None
board = init_board()
while True:
print_board(board)
has_legal_move = False
while not has_legal_move:
m1, m2 = get_coordinates()
if not is_legal_move(board, m1, m2):
print_illegal()
else:
# otherwise, acceptable move
has_legal_move = True
set_board(board, m1, 0)
set_board(board, m2, 1)
# line 205
print_board(board)
if (player_piece_on_back_row(board) or
all_computer_pieces_captured(board)):
human_win(last_computer_move)
return
# line 230
computer_move = pick_computer_move(board)
if computer_move is None:
human_win(last_computer_move)
return
last_computer_move = computer_move
m1, m2 = last_computer_move.m1, last_computer_move.m2
print(f"I MOVE FROM {m1} TO {m2}")
set_board(board, m1, 0)
set_board(board, m2, -1)
# line 640
print_board(board)
if (computer_piece_on_front_row(board) or
all_human_pieces_captured(board)):
computer_win(True)
return
elif not human_has_move(board):
computer_win(False)
return
def main():
print_header("HEXAPAWN")
if prompt_yes_no("INSTRUCTIONS (Y-N)?"):
print_instructions()
global w, l
w = 0
l = 0
x = 0
y = 0
board = init_board()
print_board(board)
while True:
m1, m2 = get_coordinates()
if board_contents(board, m1) != 1:
# Start space doesn't contain player's piece
print_illegal()
continue
if board_contents(board, m2) == 1:
# Destination space contains player's piece (can't capture your own piece)
print_illegal()
continue
# line 160
is_capture = (m2-m1 != -3)
if is_capture and board_contents(board, m2) != -1:
# Destination does not contain computer piece
print_illegal()
continue
# line 170
if m2 > m1:
# can't move backwards
print_illegal()
continue
# line 180
if (not is_capture) and board_contents(board, m2) != 0:
# Destination is not open
print_illegal()
continue
# line 185
if m2-m1 < -4:
# too far
print_illegal()
continue
# line 186
if m1 == 7 and m2 == 3:
# can't jump corner to corner ?!
print_illegal()
continue
# otherwise, acceptable move
break
play_game()
show_scores()
set_board(board, m1, 0)
set_board(board, m2, 1)
# line 205
print_board(board)
if __name__ == "__main__":
main()
"""
210 IF S(1)=1 OR S(2)=1 OR S(3)=1 THEN 820
220 FOR I=1 TO 9
221 IF S(I)=-1 THEN 230
222 NEXT I
223 GOTO 820
230 FOR I=1 TO 9
240 IF S(I)<>-1 THEN 330
250 IF S(I+3)=0 THEN 350