From 3a9e19197dd69d5787134df0e5b553d9af36eab0 Mon Sep 17 00:00:00 2001 From: Dave LeCompte Date: Tue, 2 Mar 2021 09:05:13 -0800 Subject: [PATCH] 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. --- 46 Hexapawn/python/hexapawn.py | 464 ++++++++++++++++++++++++++------- 1 file changed, 368 insertions(+), 96 deletions(-) diff --git a/46 Hexapawn/python/hexapawn.py b/46 Hexapawn/python/hexapawn.py index 232bb782..50f2abc8 100644 --- a/46 Hexapawn/python/hexapawn.py +++ b/46 Hexapawn/python/hexapawn.py @@ -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