diff --git a/88 3-D Tic-Tac-Toe/python/qubit.py b/88 3-D Tic-Tac-Toe/python/qubit.py new file mode 100644 index 00000000..e8840a87 --- /dev/null +++ b/88 3-D Tic-Tac-Toe/python/qubit.py @@ -0,0 +1,406 @@ +#!/usr/bin/python3 + +# Ported from the BASIC source for 3D Tic Tac Toe +# in BASIC Computer Games, by David H. Ahl +# The code originated from Dartmouth College + +from enum import Enum + + +class Move(Enum): + """ Game status and types of machine move """ + HUMAN_WIN = 0 + MACHINE_WIN = 1 + DRAW = 2 + MOVES = 3 + LIKES = 4 + TAKES = 5 + GET_OUT = 6 + YOU_FOX = 7 + NICE_TRY = 8 + CONCEDES = 9 + +class Player(Enum): + EMPTY = 0 + HUMAN = 1 + MACHINE = 2 + +class TicTacToe3D: + """ The game logic for 3D Tic Tac Toe and the machine opponent """ + + def __init__(self): + + # 4x4x4 board keeps track of which player occupies each place + # and used by machine to work out its strategy + self.board = [0] * 64 + + # starting move + self.corners = [0, 48, 51, 3, 12, 60, 63, 15, + 21, 38, 22, 37, 25, 41, 26, 42] + + # lines to check for end game + self.lines = [[0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9, 10, 11], + [12, 13, 14, 15], + [16, 17, 18, 19], + [20, 21, 22, 23], + [24, 25, 26, 27], + [28, 29, 30, 31], + [32, 33, 34, 35], + [36, 37, 38, 39], + [40, 41, 42, 43], + [44, 45, 46, 47], + [48, 49, 50, 51], + [52, 53, 54, 55], + [56, 57, 58, 59], + [60, 61, 62, 63], + [0, 16, 32, 48], + [4, 20, 36, 52], + [8, 24, 40, 56], + [12, 28, 44, 60], + [1, 17, 33, 49], + [5, 21, 37, 53], + [9, 25, 41, 57], + [13, 29, 45, 61], + [2, 18, 34, 50], + [6, 22, 38, 54], + [10, 26, 42, 58], + [14, 30, 46, 62], + [3, 19, 35, 51], + [7, 23, 39, 55], + [11, 27, 43, 59], + [15, 31, 47, 63], + [0, 4, 8, 12], + [16, 20, 24, 28], + [32, 36, 40, 44], + [48, 52, 56, 60], + [1, 5, 9, 13], + [17, 21, 25, 29], + [33, 37, 41, 45], + [49, 53, 57, 61], + [2, 6, 10, 14], + [18, 22, 26, 30], + [34, 38, 42, 46], + [50, 54, 58, 62], + [3, 7, 11, 15], + [19, 23, 27, 31], + [35, 39, 43, 47], + [51, 55, 59, 63], + [0, 5, 10, 15], + [16, 21, 26, 31], + [32, 37, 42, 47], + [48, 53, 58, 63], + [12, 9, 6, 3], + [28, 25, 22, 19], + [44, 41, 38, 35], + [60, 57, 54, 51], + [0, 20, 40, 60], + [1, 21, 41, 61], + [2, 22, 42, 62], + [3, 23, 43, 63], + [48, 36, 24, 12], + [49, 37, 25, 13], + [50, 38, 26, 14], + [51, 39, 27, 15], + [0, 17, 34, 51], + [4, 21, 38, 55], + [8, 25, 42, 59], + [12, 29, 46, 63], + [48, 33, 18, 3], + [52, 37, 22, 7], + [56, 41, 26, 11], + [60, 45, 30, 15], + [0, 21, 42, 63], + [15, 26, 37, 48], + [3, 22, 41, 60], + [12, 25, 38, 51]] + + def get(self, x, y, z): + m = self.board[4*(4*z + y) + x] + if m == 40: + return Player.MACHINE + elif m == 8: + return Player.HUMAN + else: + return Player.EMPTY + + def move3D(self, x, y, z, player): + m = 4*(4*z + y) + x + return self.move(m, player) + + def move(self, m, player): + if self.board[m] > 1: + return False + + if player == Player.MACHINE: + self.board[m] = 40 + else: + self.board[m] = 8 + return True + + def get3DPosition(self, m): + x = m % 4 + y = (m // 4) % 4 + z = m // 16 + return x, y, z + + def evaluateLines(self): + self.lineValues = [0] * 76 + for j in range(76): + value = 0 + for k in range(4): + value += self.board[self.lines[j][k]] + self.lineValues[j] = value + + def strategyMarkLine(self, i): + for j in range(4): + m = self.lines[i][j] + if self.board[m] == 0: + self.board[m] = 1 + + def clearStrategyMarks(self): + for i in range(64): + if self.board[i] == 1: + self.board[i] = 0 + + def markAndMove(self, vlow, vhigh, vmove): + """ + mark lines that can potentially win the game for the human + or the machine and choose best place to play + """ + for i in range(76): + value = 0 + for j in range(4): + value += self.board[self.lines[i][j]] + self.lineValues[i] = value + if vlow <= value < vhigh: + if value > vlow: + return self.moveTriple(i) + self.strategyMarkLine(i) + self.evaluateLines() + + for i in range(76): + value = self.lineValues[i] + if value == 4 or value == vmove: + return self.moveDiagonals(i, 1) + return None + + def machineMove(self): + """ machine works out what move to play """ + self.clearStrategyMarks() + + self.evaluateLines() + for value, event in [(32, self.humanWin), + (120, self.machineWin), + (24, self.blockHumanWin)]: + for i in range(76): + if self.lineValues[i] == value: + return event(i) + + m = self.markAndMove(80, 88, 43) + if m != None: + return m + + self.clearStrategyMarks() + + m = self.markAndMove(16, 24, 11) + if m != None: + return m + + for k in range(18): + value = 0 + for i in range(4*k, 4*k+4): + for j in range(4): + value += self.board[self.lines[i][j]] + if (32 <= value < 40) or (72 <= value < 80): + for s in [1, 0]: + for i in range(4*k, 4*k+4): + m = self.moveDiagonals(i, s) + if m != None: + return m + + self.clearStrategyMarks() + + for y in self.corners: + if self.board[y] == 0: + return (Move.MOVES, y) + + for i in range(64): + if self.board[i] == 0: + return (Move.LIKES, i) + + return (Move.DRAW, -1) + + def humanWin(self, i): + return (Move.HUMAN_WIN, -1, i) + + def machineWin(self, i): + for j in range(4): + m = self.lines[i][j] + if self.board[m] == 0: + return (Move.MACHINE_WIN, m, i) + return None + + def blockHumanWin(self, i): + for j in range(4): + m = self.lines[i][j] + if self.board[m] == 0: + return (Move.NICE_TRY, m) + return None + + + def moveTriple(self, i): + """ make two lines-of-3 or prevent human from doing this """ + for j in range(4): + m = self.lines[i][j] + if self.board[m] == 1: + if self.lineValues[i] < 40: + return (Move.YOU_FOX, m) + else: + return (Move.GET_OUT, m) + return (Move.CONCEDES, -1) + + # choose move in corners or center boxes of square 4x4 + def moveDiagonals(self, i, s): + if 0 < (i % 4) < 3: + jrange = [1, 2] + else: + jrange = [0, 3] + for j in jrange: + m = self.lines[i][j] + if self.board[m] == s: + return (Move.TAKES, m) + return None + +class Qubit: + + def moveCode(self, board, m): + x, y, z = board.get3DPosition(m) + return "{:d}{:d}{:d}".format(z+1, y+1, x+1) + + def showWin(self, board, i): + for m in board.lines[i]: + print(self.moveCode(board, m)) + + def showBoard(self, board): + c = " YM" + for z in range(4): + for y in range(4): + print(" " * y, end="") + for x in range(4): + p = board.get(x, y, z) + print("({}) ".format(c[p.value]), end="") + print("\n") + print("\n") + + def humanMove(self, board): + print("") + c = "1234" + while True: + h = input("Your move?\n") + if h == "1": + return False + if h == "0": + self.showBoard(board) + continue + if (len(h) == 3) and (h[0] in c) and (h[1] in c) and (h[2] in c): + x = c.find(h[2]) + y = c.find(h[1]) + z = c.find(h[0]) + if board.move3D(x, y, z, Player.HUMAN): + break + + print("That square is used. Try again.") + else: + print("Incorrect move. Retype it--") + + return True + + def play(self): + print("Qubic\n") + print("Create Computing Morristown, New Jersey\n\n\n") + while True: + c = input("Do you want instructions?\n") + if len(c) >= 1 and (c[0] in "ynYN"): + break + print("Incorrect answer. Please type 'yes' or 'no.") + + c = c.lower() + if c[0] == 'y': + print("The game is Tic-Tac-Toe in a 4 x 4 x 4 cube.") + print("Each move is indicated by a 3 digit number, with each") + print("digit between 1 and 4 inclusive. The digits indicate the") + print("level, row, and column, respectively, of the occupied") + print("place.\n") + + print("To print the playing board, type 0 (zero) as your move.") + print("The program will print the board with your moves indicated") + print("with a (Y), the machine's moves with an (M), and") + print("unused squares with a ( ).\n") + + print("To stop the program run, type 1 as your move.\n\n") + + + + play_again = True + while play_again: + board = TicTacToe3D() + + while True: + s = input("Do you want to move first?\n") + if len(s) >= 1 and (s[0] in "ynYN"): + break + print("Incorrect answer. Please type 'yes' or 'no'.") + + skipHuman = s[0] in "nN" + + move_text = ["Machine moves to", + "Machine likes", + "Machine takes", + "Let's see you get out of this: Machine moves to", + "You fox. Just in the nick of time, machine moves to", + "Nice try. Machine moves to"] + + while True: + if not skipHuman: + if not self.humanMove(board): + break + skipHuman = False + + m = board.machineMove() + if m[0] == Move.HUMAN_WIN: + print("You win as follows,") + self.showWin(board, m[2]) + break + elif m[0] == Move.MACHINE_WIN: + print("Machine moves to {}, and wins as follows".format(self.moveCode(board, m[1]))) + self.showWin(board, m[2]) + break + elif m[0] == Move.DRAW: + print("The game is a draw.") + break + elif m[0] == Move.CONCEDES: + print("Machine concedes this game.") + break + else: + print(move_text[m[0].value - Move.MOVES.value]) + print(self.moveCode(board, m[1])) + board.move(m[1], Player.MACHINE) + + self.showBoard(board) + + print(" ") + while True: + x = input("Do you want to try another game\n") + if len(x) >= 1 and x[0] in "ynYN": + break + print("Incorrect answer. Please Type 'yes' or 'no'.") + + play_again = x[0] in "yY" + + + +if __name__ == "__main__": + game = Qubit() + game.play()