Removed spaces from top-level directory names.

Spaces tend to cause annoyances in a Unix-style shell environment.
This change fixes that.
This commit is contained in:
Chris Reuter
2021-11-21 18:30:21 -05:00
parent df2e7426eb
commit d26dbf036a
1725 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Python](https://www.python.org/about/)

View File

@@ -0,0 +1,516 @@
"""
HEXAPAWN
A machine learning game, an interpretation of HEXAPAWN game as
presented in Martin Gardner's "The Unexpected Hanging and Other
Mathematical Diversions", Chapter Eight: A Matchbox Game-Learning
Machine.
Original version for H-P timeshare system by R.A. Kaapke 5/5/76
Instructions by Jeff Dalton
Conversion to MITS BASIC by Steve North
Port to Python by Dave LeCompte
"""
"""
PORTING NOTES:
I printed out the BASIC code and hand-annotated what each little block
of code did, which feels amazingly retro.
I encourage other porters that have a complex knot of GOTOs and
semi-nested subroutines to do hard-copy hacking, it might be a
different perspective that helps.
A spoiler - the objective of the game is not documented, ostensibly to
give the human player a challenge. If a player (human or computer)
advances a pawn across the board to the far row, that player wins. If
a player has no legal moves (either by being blocked, or all their
pieces having been captured), that player loses.
The original BASIC had 2 2-dimensional tables stored in DATA at the
end of the program. This encoded all 19 different board configurations
(Hexapawn is a small game), with reflections in one table, and then in
a parallel table, for each of the 19 rows, a list of legal moves was
encoded by turning them into 2-digit decimal numbers. As gameplay
continued, the AI would overwrite losing moves with 0 in the second
array.
My port takes this "parallel array" structure and turns that
information into a small Python class, BoardLayout. BoardLayout stores
the board description and legal moves, but stores the moves as (row,
column) 2-tuples, which is easier to read. The logic for checking if a
BoardLayout matches the current board, as well as removing losing move
have been moved into methods of this class.
"""
import collections
import random
PAGE_WIDTH = 64
HUMAN_PIECE = 1
EMPTY_SPACE = 0
COMPUTER_PIECE = -1
ComputerMove = collections.namedtuple(
"ComputerMove", ["board_index", "move_index", "m1", "m2"]
)
wins = 0
losses = 0
def print_centered(msg):
spaces = " " * ((PAGE_WIDTH - len(msg)) // 2)
print(spaces + msg)
def print_header(title):
print_centered(title)
print_centered("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
print()
print()
print()
def print_instructions():
print(
"""
THIS PROGRAM PLAYS THE GAME OF HEXAPAWN.
HEXAPAWN IS PLAYED WITH CHESS PAWNS ON A 3 BY 3 BOARD.
THE PAWNS ARE MOVED AS IN CHESS - ONE SPACE FORWARD TO
AN EMPTY SPACE OR ONE SPACE FORWARD AND DIAGONALLY TO
CAPTURE AN OPPOSING MAN. ON THE BOARD, YOUR PAWNS
ARE 'O', THE COMPUTER'S PAWNS ARE 'X', AND EMPTY
SQUARES ARE '.'. TO ENTER A MOVE, TYPE THE NUMBER OF
THE SQUARE YOU ARE MOVING FROM, FOLLOWED BY THE NUMBER
OF THE SQUARE YOU WILL MOVE TO. THE NUMBERS MUST BE
SEPERATED BY A COMMA.
THE COMPUTER STARTS A SERIES OF GAMES KNOWING ONLY WHEN
THE GAME IS WON (A DRAW IS IMPOSSIBLE) AND HOW TO MOVE.
IT HAS NO STRATEGY AT FIRST AND JUST MOVES RANDOMLY.
HOWEVER, IT LEARNS FROM EACH GAME. THUS, WINNING BECOMES
MORE AND MORE DIFFICULT. ALSO, TO HELP OFFSET YOUR
INITIAL ADVANTAGE, YOU WILL NOT BE TOLD HOW TO WIN THE
GAME BUT MUST LEARN THIS BY PLAYING.
THE NUMBERING OF THE BOARD IS AS FOLLOWS:
123
456
789
FOR EXAMPLE, TO MOVE YOUR RIGHTMOST PAWN FORWARD,
YOU WOULD TYPE 9,6 IN RESPONSE TO THE QUESTION
'YOUR MOVE ?'. SINCE I'M A GOOD SPORT, YOU'LL ALWAYS
GO FIRST.
"""
)
def prompt_yes_no(msg):
while True:
print(msg)
response = input().upper()
if response[0] == "Y":
return True
elif response[0] == "N":
return False
def reverse_space_name(space_name):
# reverse a space name in the range 1-9 left to right
assert 1 <= space_name <= 9
reflections = {1: 3, 2: 2, 3: 1, 4: 6, 5: 5, 6: 4, 7: 9, 8: 8, 9: 7}
return reflections[space_name]
def is_space_in_center_column(space_name):
return reverse_space_name(space_name) == space_name
class BoardLayout:
def __init__(self, cells, move_list):
self.cells = cells
self.moves = move_list
def _check_match_no_mirror(self, cell_list):
for space_index, board_contents in enumerate(self.cells):
if board_contents != cell_list[space_index]:
return False
return True
def _check_match_with_mirror(self, cell_list):
for space_index, board_contents in enumerate(self.cells):
reversed_space_index = reverse_space_name(space_index + 1) - 1
if board_contents != cell_list[reversed_space_index]:
return False
return True
def check_match(self, cell_list):
if self._check_match_with_mirror(cell_list):
return True, True
elif self._check_match_no_mirror(cell_list):
return True, False
return False, None
def get_random_move(self, reverse_board):
if not self.moves:
return None
move_index = random.randrange(len(self.moves))
m1, m2 = self.moves[move_index]
if reverse_board:
m1 = reverse_space_name(m1)
m2 = reverse_space_name(m2)
return move_index, m1, m2
boards = [
BoardLayout([-1, -1, -1, 1, 0, 0, 0, 1, 1], [(2, 4), (2, 5), (3, 6)]),
BoardLayout([-1, -1, -1, 0, 1, 0, 1, 0, 1], [(1, 4), (1, 5), (3, 6)]),
BoardLayout([-1, 0, -1, -1, 1, 0, 0, 0, 1], [(1, 5), (3, 5), (3, 6), (4, 7)]),
BoardLayout([0, -1, -1, 1, -1, 0, 0, 0, 1], [(3, 6), (5, 8), (5, 9)]),
BoardLayout([-1, 0, -1, 1, 1, 0, 0, 1, 0], [(1, 5), (3, 5), (3, 6)]),
BoardLayout([-1, -1, 0, 1, 0, 1, 0, 0, 1], [(2, 4), (2, 5), (2, 6)]),
BoardLayout([0, -1, -1, 0, -1, 1, 1, 0, 0], [(2, 6), (5, 7), (5, 8)]),
BoardLayout([0, -1, -1, -1, 1, 1, 1, 0, 0], [(2, 6), (3, 5)]),
BoardLayout([-1, 0, -1, -1, 0, 1, 0, 1, 0], [(4, 7), (4, 8)]),
BoardLayout([0, -1, -1, 0, 1, 0, 0, 0, 1], [(3, 5), (3, 6)]),
BoardLayout([0, -1, -1, 0, 1, 0, 1, 0, 0], [(3, 5), (3, 6)]),
BoardLayout([-1, 0, -1, 1, 0, 0, 0, 0, 1], [(3, 6)]),
BoardLayout([0, 0, -1, -1, -1, 1, 0, 0, 0], [(4, 7), (5, 8)]),
BoardLayout([-1, 0, 0, 1, 1, 1, 0, 0, 0], [(1, 5)]),
BoardLayout([0, -1, 0, -1, 1, 1, 0, 0, 0], [(2, 6), (4, 7)]),
BoardLayout([-1, 0, 0, -1, -1, 1, 0, 0, 0], [(4, 7), (5, 8)]),
BoardLayout([0, 0, -1, -1, 1, 0, 0, 0, 0], [(3, 5), (3, 6), (4, 7)]),
BoardLayout([0, -1, 0, 1, -1, 0, 0, 0, 0], [(2, 8), (5, 8)]),
BoardLayout([-1, 0, 0, -1, 1, 0, 0, 0, 0], [(1, 5), (4, 7)]),
]
def get_move(board_index, move_index):
assert board_index >= 0 and board_index < len(boards)
board = boards[board_index]
assert move_index >= 0 and move_index < len(board.moves)
return board.moves[move_index]
def remove_move(board_index, move_index):
assert board_index >= 0 and board_index < len(boards)
board = boards[board_index]
assert move_index >= 0 and move_index < len(board.moves)
del board.moves[move_index]
def init_board():
return [COMPUTER_PIECE] * 3 + [EMPTY_SPACE] * 3 + [HUMAN_PIECE] * 3
def print_board(board):
piece_dict = {COMPUTER_PIECE: "X", EMPTY_SPACE: ".", HUMAN_PIECE: "O"}
space = " " * 10
print()
for row in range(3):
line = ""
for column in range(3):
line += space
space_number = row * 3 + column
space_contents = board[space_number]
line += piece_dict[space_contents]
print(line)
print()
def get_coordinates():
while True:
try:
print("YOUR MOVE?")
response = input()
m1, m2 = [int(c) for c in response.split(",")]
return m1, m2
except ValueError as ve:
print_illegal()
def print_illegal():
print("ILLEGAL MOVE.")
def board_contents(board, space_number):
return board[space_number - 1]
def set_board(board, space_number, new_value):
board[space_number - 1] = new_value
def is_legal_human_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
is_capture = m2 - m1 != -3
if is_capture and board_contents(board, m2) != COMPUTER_PIECE:
# Destination does not contain computer piece
return False
if m2 > m1:
# can't move backwards
return False
if (not is_capture) and board_contents(board, m2) != EMPTY_SPACE:
# Destination is not open
return False
if m2 - m1 < -4:
# too far
return False
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")
remove_move(last_computer_move.board_index, last_computer_move.move_index)
global losses
losses += 1
def computer_win(has_moves):
if not has_moves:
msg = "YOU CAN'T MOVE, SO "
else:
msg = ""
msg += "I WIN"
print(msg)
global wins
wins += 1
def show_scores():
print(f"I HAVE WON {wins} AND YOU {losses} OUT OF {wins + losses} GAMES.")
print()
def human_has_move(board):
for i in get_human_spaces(board):
if board_contents(board, i - 3) == EMPTY_SPACE:
# can move piece forward
return True
elif is_space_in_center_column(i):
if (board_contents(board, i - 2) == COMPUTER_PIECE) or (
board_contents(board, i - 4) == COMPUTER_PIECE
):
# can capture from center
return True
else:
continue
elif i < 7:
assert (i == 4) or (i == 6)
if board_contents(board, 2) == COMPUTER_PIECE:
# can capture computer piece at 2
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():
""" generates the space names (1-9)"""
yield from range(1, 10)
def get_board_spaces_with(board, val):
""" generates spaces containing pieces of type 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
if is_space_in_center_column(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:
if i > 3:
# beyond the first row
if board_contents(board, 8) == HUMAN_PIECE:
# can capture on 8
return True
else:
continue
else:
if board_contents(board, 5) == HUMAN_PIECE:
# can capture on 5
return True
else:
continue
return False
def find_board_index_that_matches_board(board):
for board_index, board_layout in enumerate(boards):
matches, is_reversed = board_layout.check_match(board)
if matches:
return board_index, is_reversed
# THE TERMINATION OF THIS LOOP IS IMPOSSIBLE
print("ILLEGAL BOARD PATTERN.")
assert False
def pick_computer_move(board):
if not has_computer_move(board):
return None
board_index, reverse_board = find_board_index_that_matches_board(board)
m = boards[board_index].get_random_move(reverse_board)
if m is None:
print("I RESIGN")
return None
move_index, m1, m2 = m
return ComputerMove(board_index, move_index, m1, m2)
def get_human_move(board):
while True:
m1, m2 = get_coordinates()
if not is_legal_human_move(board, m1, m2):
print_illegal()
else:
return m1, m2
def apply_move(board, m1, m2, piece_value):
set_board(board, m1, EMPTY_SPACE)
set_board(board, m2, piece_value)
def play_game():
last_computer_move = None
board = init_board()
while True:
print_board(board)
m1, m2 = get_human_move(board)
apply_move(board, m1, m2, HUMAN_PIECE)
print_board(board)
if player_piece_on_back_row(board) or all_computer_pieces_captured(board):
human_win(last_computer_move)
return
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}")
apply_move(board, m1, m2, COMPUTER_PIECE)
print_board(board)
if computer_piece_on_front_row(board):
computer_win(True)
return
elif (not human_has_move(board)) or (all_human_pieces_captured(board)):
computer_win(False)
return
def main():
print_header("HEXAPAWN")
if prompt_yes_no("INSTRUCTIONS (Y-N)?"):
print_instructions()
global wins, losses
wins = 0
losses = 0
while True:
play_game()
show_scores()
if __name__ == "__main__":
main()