Home > Mobile >  Game of the nim, minimax, game tree, data structure
Game of the nim, minimax, game tree, data structure

Time:04-10

There are 3 piles (1 pile - 7 matches, 2 pile - 5 matches, 3 pile - 3 matches) you can take any number of matches, but only from one pile, the one who takes the last match loses. The number of moves in this game is not so big, so I think it is possible to build the whole game tree at once, but how to properly implement minimax for the game and how to store the game tree? https://en.wikipedia.org/wiki/Nim

It turns out that if the initial state is [7,5,3] and if the computer moves first, it can take 1 match from any pile, 2 matches from any pile, 3 matches from any pile, 4 matches from either 1 or 2 piles, 5 matches from 1 or 2 piles, 6 matches from 1 pile or 7 matches from 1 pile. (there are 15 choices for the first move), 13 choices for the second move, etc. How do I generate and store these choices in the program?

from tkinter import Tk, Canvas, Frame, Button, LEFT, RIGHT, messagebox, CURRENT
from random import randint

def on_click(event): #this deals with actions from clicks based on the name of the button clicked on
    if canvas.find_withtag(CURRENT):
        global last_piece, piece_name

        piece_name = canvas.gettags(CURRENT)[0]
        group_name = piece_name[0]
        if pieces == [7,5,3]:
            last_piece = None

        try:
            if piece_name == "AI_first":
                AI_to_play()
            elif group_name != last_piece and last_piece != None and piece_name != 'DONE':
                # display ILLEGAL MOVE in the game canvas for 1.5 seconds if the user tries to pick pieces from different piles on the same turn
                canvas.create_text(355, 40, text="ILLEGAL MOVE", font="Purisa", tags="ILLEGAL_WARNING", fill="black",command=None)
                canvas.update_idletasks()
                canvas.after(1500)
                canvas.delete("ILLEGAL_WARNING")
            else:
                if piece_name == 'DONE' and last_piece != 'DONE' and last_piece != None:
                    last_piece = None
                    AI_to_play() # this is for the computer's turn when the user has clicked the Done button
                elif piece_name == 'DONE' and last_piece == None:
                    # do not let the user click the done button more than once in a row. Displays the message for 1.5 seconds
                    canvas.create_text(355, 40, text="YOU HAVE NOT MADE ANY MOVES", font="Purisa", tags="DOUBLE_DONE",fill="black", command=None)
                    canvas.update_idletasks()
                    canvas.after(1500)
                    canvas.delete("DOUBLE_DONE")
                elif piece_name == 'WON_BUTTON':
                    pass
                else:
                    canvas.delete('AI_first')
                    update_board(piece_name)
                    canvas.delete(piece_name)
                    last_piece = piece_name[0]
                    if sum(pieces) == 0:
                        game_over('computer')
        except NameError:
            last_piece = group_name
            if piece_name == 'DONE':
                AI_to_play()  # this is the computer's turn when the user has clicked the Done button
            elif piece_name == 'WON_BUTTON':
                pass
            else:
                update_board(piece_name)
                canvas.delete(piece_name)
                # this should only happen on first go and is to catch the case where the last button press is not defined
                last_piece = piece_name[0]
                if sum(pieces) == 0:
                    game_over('computer')

def create_pieces():

    # ititialise the mathematical representations of the board and declare as global variable so it can easily be updated within the update function
    global pieces, board, piece_name
    pieces = [7, 5, 3]
    board = [[1,1,1,1,1,1,1],[1,1,1,1,1],[1,1,1]]
    piece_name = 'NEW_GAME'

    circle_size = 50
    linecolour = "black"
    fillcolour = "blue"

    # Group A is on the left. It is a group of 7
    A1x = 50
    A1y = 50
    A2x = 110
    A2y = 50
    A3x = 20
    A3y = 105
    A4x = 80
    A4y = 105
    A5x = 140
    A5y = 105
    A6x = 50
    A6y = 160
    A7x = 110
    A7y = 160
    canvas.create_oval(A1x, A1y, A1x circle_size, A1y circle_size, outline=linecolour,fill=fillcolour,tags="A1")
    canvas.create_oval(A2x, A2y, A2x circle_size, A2y circle_size, outline=linecolour,fill=fillcolour,tags="A2")
    canvas.create_oval(A3x, A3y, A3x circle_size, A3y circle_size, outline=linecolour,fill=fillcolour,tags="A3")
    canvas.create_oval(A4x, A4y, A4x circle_size, A4y circle_size, outline=linecolour,fill=fillcolour,tags="A4")
    canvas.create_oval(A5x, A5y, A5x circle_size, A5y circle_size, outline=linecolour,fill=fillcolour,tags="A5")
    canvas.create_oval(A6x, A6y, A6x circle_size, A6y circle_size, outline=linecolour,fill=fillcolour,tags="A6")
    canvas.create_oval(A7x, A7y, A7x circle_size, A7y circle_size, outline=linecolour,fill=fillcolour,tags="A7")

    # Group B is in the centre. It is a group of 5
    B1x = 330
    B1y = 65
    B2x = 280
    B2y = 105
    B3x = 380
    B3y = 105
    B4x = 300
    B4y = 160
    B5x = 360
    B5y = 160
    canvas.create_oval(B1x, B1y, B1x circle_size, B1y circle_size, outline=linecolour,fill=fillcolour,tags="B1")
    canvas.create_oval(B2x, B2y, B2x circle_size, B2y circle_size, outline=linecolour,fill=fillcolour,tags="B2")
    canvas.create_oval(B3x, B3y, B3x circle_size, B3y circle_size, outline=linecolour,fill=fillcolour,tags="B3")
    canvas.create_oval(B4x, B4y, B4x circle_size, B4y circle_size, outline=linecolour,fill=fillcolour,tags="B4")
    canvas.create_oval(B5x, B5y, B5x circle_size, B5y circle_size, outline=linecolour,fill=fillcolour,tags="B5")

    # Group C is on the right. It is a group of 3
    C1x = 570
    C1y = 105
    C2x = 540
    C2y = 160
    C3x = 600
    C3y = 160
    canvas.create_oval(C1x, C1y, C1x circle_size, C1y circle_size, outline=linecolour,fill=fillcolour,tags="C1")
    canvas.create_oval(C2x, C2y, C2x circle_size, C2y circle_size, outline=linecolour,fill=fillcolour,tags="C2")
    canvas.create_oval(C3x, C3y, C3x circle_size, C3y circle_size, outline=linecolour,fill=fillcolour,tags="C3")

def create_DONE_button():
    canvas.create_rectangle(580,2,680,45,outline="black",fill="gray80",tags="DONE")
    canvas.create_text(630,23,text="I'm done with\n     my turn",font="Purisa",tags="DONE")

def create_computer_go_first_button():
    canvas.create_rectangle(580,48,680,91,outline="black",fill="gray80",tags="AI_first")
    canvas.create_text(630,70,text="The computer\n  can go first",font="Purisa",tags="AI_first")

def create_operation_buttons():
    # create the buttons to start the game and show the rules
    operation_frame = Frame()
    operation_frame.pack(fill="both", expand=True)
    Start = Button(operation_frame, text='Click here to start a new game', height=2, command=start_game, bg='white',fg='navy')
    Start.pack(fill="both", expand=True, side=LEFT)
    Rules = Button(operation_frame, text='Click here to see the rules', command=show_rules, height=2, bg='navy',fg='white')
    Rules.pack(fill="both", expand=True, side=RIGHT)

def start_game():
    # this turns all the ovals into buttons to be activated by a mouse click. Function then jumps to on_click.
    create_pieces()
    create_DONE_button()
    create_computer_go_first_button()
    canvas.delete('WON_BUTTON')
    canvas.bind("<Button-1>",func=on_click)

def show_rules():
    rules_intro = 'Welcome to Nim, a game with more strategy than may first appear!\n'
    game_play = 'Playing Nim involves each player taking pieces from the game screen in turns.\n'
    rule1 = 'Your goal is to leave your opponent with the last piece remaining on the screen.\n'
    rule2 = 'You may only take from one pile each turn.\n'
    rule3 = 'You can take as many pieces as you want each turn.\n\n'
    operation = 'When you are done with your turn, please click the button in the top right corner to let the computer know that it can play.'
    rule_message = rules_intro game_play rule1 rule2 rule3 operation
    messagebox.showinfo("How to play Nim", rule_message)

def update_board(piece_names):
    # the board had two representations, the "pieces" representation is [7,5,3].
    # the "board" representation has all the pieces and looks like [[1,1,1,1,1,1,1],[1,1,1,1,1],[1,1,1]].
    if type(piece_names) == str:    #need to tell if there is a single or multiple updates to be done
        update_board_pieces(piece_names)
    else:
        for item in range(0, len(piece_names)): # multiple updates are required when the AI makes its moves
            piece_name = piece_names[item]
            update_board_pieces(piece_name)

def update_board_pieces(piece_name):
    # this part adjusts the mathematical representations of the board, being the [7,5,3] and [[1,1,1,1,1,1,1],[1,1,1,1,1],[1,1,1]] arrays.
    # these arrays are global variables defined when the board is created
    group_name = piece_name[0]
    if group_name == 'A':
        pieces[0] -= 1
    elif group_name == 'B':
        pieces[1] -= 1
    elif group_name == 'C':
        pieces[2] -= 1

    if piece_name == 'A1':
        board[0][0] = 0
    elif piece_name == 'A2':
        board[0][1] = 0
    elif piece_name == 'A3':
        board[0][2] = 0
    elif piece_name == 'A4':
        board[0][3] = 0
    elif piece_name == 'A5':
        board[0][4] = 0
    elif piece_name == 'A6':
        board[0][5] = 0
    elif piece_name == 'A7':
        board[0][6] = 0
    elif piece_name == 'B1':
        board[1][0] = 0
    elif piece_name == 'B2':
        board[1][1] = 0
    elif piece_name == 'B3':
        board[1][2] = 0
    elif piece_name == 'B4':
        board[1][3] = 0
    elif piece_name == 'B5':
        board[1][4] = 0
    elif piece_name == 'C1':
        board[2][0] = 0
    elif piece_name == 'C2':
        board[2][1] = 0
    elif piece_name == 'C3':
        board[2][2] = 0

def AI_to_play():
    # this finds the computer's action using the strategies defined
    next_move = find_next_move()
    canvas.delete("AI_first")

    # this applies the strategy and consist of the computer building a list of board moves it must make to apply the strategy has determined is best
    if finish == False:
        # delta is the difference is the current state and the future state of the board
        # delta should only every have 1 number of the 3 that is greater than zero. eg. [0,3,0] tells the program to remove 3 pieced from pile B
        delta = [pieces[0] - next_move[0], pieces[1] - next_move[1], pieces[2] - next_move[2]]
        pieces_to_take = []

        if delta[0] > 0:
            # take from A
            piece_index = 0
            number_of_pieces_to_take = delta[0]
            while number_of_pieces_to_take > 0:
                if board[0][piece_index] == 1:
                    board[0][piece_index] = 0
                    pieces[0] -= 1
                    piece_index  = 1
                    number_of_pieces_to_take -= 1
                    pieces_to_take.append('A'   str(piece_index))
                else:
                    piece_index  = 1
        elif delta[1] > 0:
            # take from B
            piece_index = 0
            number_of_pieces_to_take = delta[1]
            while number_of_pieces_to_take > 0:
                if board[1][piece_index] == 1:
                    board[1][piece_index] = 0
                    pieces[1] -= 1
                    piece_index  = 1
                    number_of_pieces_to_take -= 1
                    pieces_to_take.append('B'   str(piece_index))
                else:
                    piece_index  = 1
        elif delta[2] > 0:
            # take from C
            piece_index = 0
            number_of_pieces_to_take = delta[2]
            while number_of_pieces_to_take > 0:
                if board[2][piece_index] == 1:
                    board[2][piece_index] = 0
                    pieces[2] -= 1
                    piece_index  = 1
                    number_of_pieces_to_take -= 1
                    pieces_to_take.append('C'   str(piece_index))
                else:
                    piece_index  = 1

    # this is the computer actually making the moves iteratively for each move it has decided
    if finish == False:
        for piece in pieces_to_take:
            canvas.itemconfig(piece, fill="yellow") # change the colour of the pieces the AI selects to yellow before deleting them
            canvas.update_idletasks()
            canvas.after(500) #500ms delay to allow the user to see the pieces change colour before the AI deletes them
            canvas.delete(piece) # delete each piece the AI selects
            if sum(pieces) == 0: #check if the user has won
                game_over('user')

def game_over(who_won):  # displays the final message of who won

    button_width = 190
    button_height = 40
    BX = 260
    BY = 110
    canvas.delete('DONE')
    canvas.create_rectangle(BX, BY, BX   button_width, BY   button_height, outline="black", fill="grey80",
                            tags="WON_BUTTON", command=None)
    if who_won == 'user':
        canvas.create_text(BX   95, BY   20, text="!!! YOU WON !!!", font="Purisa", tags="WON_BUTTON",
                           fill="black", command=None)
    elif who_won == 'computer':
        canvas.create_text(BX   95, BY   20, text="THE COMPUTER WON", font="Purisa", tags="WON_BUTTON",
                           fill="red", command=None)

# this is a very prescriptive strategy where each move has been typed explicitly.
# anywhere there is a choice (randint) is where the computer does not have a clear strategy and may try and trick the user into making a mistake
def find_next_move():
    global finish
    finish = False
    next_move = []
    choice = randint(1,3)
    choice1 = randint(1,2)
    if pieces == [7, 5, 3]: #this is the opening move if you ask the AI to play first. It will pick one of two options
        if choice1 < 2:
            next_move = [7, 4, 3]
        else:
            next_move = [7, 5, 2]
    elif pieces == [7, 5, 2]: #every other choice is a matter of 3 options
        if choice == 1:
            next_move = [6, 5, 2]
        elif choice == 2:
            next_move = [4, 5, 2]
        else:
            next_move = [7, 4, 2]
    elif pieces == [7, 5, 1]: # if there is a clear best move then the computer will always play that
        next_move = [4, 5, 1]
    elif pieces == [7, 5, 0]:
        next_move = [5, 5, 0]
    elif pieces == [7, 4, 3]:
        if choice == 1:
            next_move = [6, 4, 3]
        elif choice == 2:
            next_move = [7, 4, 2]
        else:
            next_move = [7, 4, 1]
    elif pieces == [7, 4, 2]:
        next_move = [6, 4, 2]
    elif pieces == [7, 4, 1]:
        next_move = [5, 4, 1]
    elif pieces == [7, 4, 0]:
        next_move = [4, 4, 0]
    elif pieces == [7, 3, 3]:
        next_move = [0, 3, 3]
    elif pieces == [7, 3, 2]:
        next_move = [1, 3, 2]
    elif pieces == [7, 3, 1]:
        next_move = [2, 3, 1]
    elif pieces == [7, 3, 0]:
        next_move = [3, 3, 0]
    elif pieces == [7, 2, 3]:
        next_move = [1, 2, 3]
    elif pieces == [7, 2, 2]:
        next_move = [2, 2, 2]
    elif pieces == [7, 2, 1]:
        next_move = [3, 2, 1]
    elif pieces == [7, 2, 0]:
        next_move = [2, 2, 0]
    elif pieces == [7, 1, 3]:
        next_move = [2, 1, 3]
    elif pieces == [7, 1, 2]:
        next_move = [3, 1, 2]
    elif pieces == [7, 1, 1]:
        next_move = [1, 1, 1]
    elif pieces == [7, 1, 0]:
        next_move = [0, 1, 0]
    elif pieces == [7, 0, 3]:
        next_move = [3, 0, 3]
    elif pieces == [7, 0, 2]:
        next_move = [2, 0, 2]
    elif pieces == [7, 0, 1]:
        next_move = [0, 0, 1]
    elif pieces == [7, 0, 0]:
        next_move = [1, 0, 0]
    elif pieces == [6, 5, 3]:
        if choice == 1:
            next_move = [6, 4, 3]
        elif choice == 2:
            next_move = [6, 5, 2]
        else:
            next_move = [4, 5, 3]
    elif pieces == [6, 5, 2]:
        next_move = [6, 4, 2]
    elif pieces == [6, 5, 1]:
        next_move = [4, 5, 1]
    elif pieces == [6, 5, 0]:
        next_move = [5, 5, 0]
    elif pieces == [6, 4, 3]:
        next_move = [6, 4, 2]
    elif pieces == [6, 4, 2]:
        if choice == 1:
            next_move = [6, 4, 1]
        elif choice == 2:
            next_move = [5, 4, 2]
        else:
            next_move = [3, 4, 2]
    elif pieces == [6, 4, 1]:
        next_move = [5, 4, 1]
    elif pieces == [6, 4, 0]:
        next_move = [4, 4, 0]
    elif pieces == [6, 3, 3]:
        next_move = [0, 3, 3]
    elif pieces == [6, 3, 2]:
        next_move = [1, 3, 2]
    elif pieces == [6, 3, 1]:
        next_move = [2, 3, 1]
    elif pieces == [6, 3, 0]:
        next_move = [3, 3, 0]
    elif pieces == [6, 2, 3]:
        next_move = [1, 2, 3]
    elif pieces == [6, 2, 2]:
        next_move = [0, 2, 2]
    elif pieces == [6, 2, 1]:
        next_move = [3, 2, 1]
    elif pieces == [6, 2, 0]:
        next_move = [2, 2, 0]
    elif pieces == [6, 1, 3]:
        next_move = [2, 1, 3]
    elif pieces == [6, 1, 2]:
        next_move = [3, 1, 2]
    elif pieces == [6, 1, 1]:
        next_move = [1, 1, 1]
    elif pieces == [6, 1, 0]:
        next_move = [0, 1, 0]
    elif pieces == [6, 0, 3]:
        next_move = [3, 0, 3]
    elif pieces == [6, 0, 2]:
        next_move = [2, 0, 2]
    elif pieces == [6, 0, 1]:
        next_move = [0, 0, 1]
    elif pieces == [6, 0, 0]:
        next_move = [1, 0, 0]
    elif pieces == [5, 5, 3]:
        next_move = [5, 5, 0]
    elif pieces == [5, 5, 2]:
        next_move = [5, 5, 0]
    elif pieces == [5, 5, 1]:
        next_move = [5, 5, 0]
    elif pieces == [5, 5, 0]:
        if choice == 1:
            next_move = [5, 2, 0]
        elif choice == 2:
            next_move = [3, 5, 0]
        else:
            next_move = [2, 5, 0]
    elif pieces == [5, 4, 3]:
        next_move = [5, 4, 1]
    elif pieces == [5, 4, 2]:
        next_move = [5, 4, 1]
    elif pieces == [5, 4, 1]:
        if choice == 1:
            next_move = [5, 3, 1]
        elif choice == 2:
            next_move = [3, 4, 1]
        else:
            next_move = [4, 4, 1]
    elif pieces == [5, 4, 0]:
        next_move = [4, 4, 0]
    elif pieces == [5, 3, 3]:
        next_move = [0, 3, 3]
    elif pieces == [5, 3, 2]:
        next_move = [1, 3, 2]
    elif pieces == [5, 3, 1]:
        next_move = [2, 3, 1]
    elif pieces == [5, 3, 0]:
        next_move = [3, 3, 0]
    elif pieces == [5, 2, 3]:
        next_move = [1, 2, 3]
    elif pieces == [5, 2, 2]:
        next_move = [0, 2, 2]
    elif pieces == [5, 2, 1]:
        next_move = [3, 2, 1]
    elif pieces == [5, 2, 0]:
        next_move = [2, 2, 0]
    elif pieces == [5, 1, 3]:
        next_move = [2, 1, 3]
    elif pieces == [5, 1, 2]:
        next_move = [3, 1, 2]
    elif pieces == [5, 1, 1]:
        next_move = [1, 1, 1]
    elif pieces == [5, 1, 0]:
        next_move = [0, 1, 0]
    elif pieces == [5, 0, 3]:
        next_move = [3, 0, 3]
    elif pieces == [5, 0, 2]:
        next_move = [2, 0, 2]
    elif pieces == [5, 0, 1]:
        next_move = [0, 0, 1]
    elif pieces == [5, 0, 0]:
        next_move = [1, 0, 0]
    elif pieces == [4, 5, 3]:
        next_move = [4, 5, 1]
    elif pieces == [4, 5, 2]:
        next_move = [4, 5, 1]
    elif pieces == [4, 5, 1]:
        if choice == 1:
            next_move = [3, 5, 1]
        elif choice == 2:
            next_move = [2, 5, 1]
        else:
            next_move = [4, 3, 1]
    elif pieces == [4, 5, 0]:
        next_move = [4, 4, 0]
    elif pieces == [4, 4, 3]:
        next_move = [4, 4, 0]
    elif pieces == [4, 4, 2]:
        next_move = [4, 4, 0]
    elif pieces == [4, 4, 1]:
        next_move = [4, 4, 0]
    elif pieces == [4, 4, 0]:
        if choice == 1:
            next_move = [4, 2, 0]
        elif choice == 2:
            next_move = [3, 4, 0]
        else:
            next_move = [4, 1, 0]
    elif pieces == [4, 3, 3]:
        next_move = [0, 3, 3]
    elif pieces == [4, 3, 2]:
        next_move = [1, 3, 2]
    elif pieces == [4, 3, 1]:
        next_move = [2, 3, 1]
    elif pieces == [4, 3, 0]:
        next_move = [3, 3, 0]
    elif pieces == [4, 2, 3]:
        next_move = [1, 2, 3]
    elif pieces == [4, 2, 2]:
        next_move = [0, 2, 2]
    elif pieces == [4, 2, 1]:
        next_move = [3, 2, 1]
    elif pieces == [4, 2, 0]:
        next_move = [2, 2, 0]
    elif pieces == [4, 1, 3]:
        next_move = [2, 1, 3]
    elif pieces == [4, 1, 2]:
        next_move = [3, 1, 2]
    elif pieces == [4, 1, 1]:
        next_move = [1, 1, 1]
    elif pieces == [4, 1, 0]:
        next_move = [0, 1, 0]
    elif pieces == [4, 0, 3]:
        next_move = [3, 0, 3]
    elif pieces == [4, 0, 2]:
        next_move = [2, 0, 2]
    elif pieces == [4, 0, 1]:
        next_move = [0, 0, 1]
    elif pieces == [4, 0, 0]:
        next_move = [1, 0, 0]
    elif pieces == [3, 5, 3]:
        next_move = [3, 0, 3]
    elif pieces == [3, 5, 2]:
        next_move = [3, 1, 2]
    elif pieces == [3, 5, 1]:
        next_move = [3, 2, 1]
    elif pieces == [3, 5, 0]:
        next_move = [3, 3, 0]
    elif pieces == [3, 4, 3]:
        next_move = [3, 0, 3]
    elif pieces == [3, 4, 2]:
        next_move = [3, 1, 2]
    elif pieces == [3, 4, 1]:
        next_move = [3, 2, 1]
    elif pieces == [3, 4, 0]:
        next_move = [3, 3, 0]
    elif pieces == [3, 3, 3]:
        next_move = [3, 0, 3]
    elif pieces == [3, 3, 2]:
        next_move = [3, 3, 0]
    elif pieces == [3, 3, 1]:
        next_move = [3, 3, 0]
    elif pieces == [3, 3, 0]:
        if choice == 1:
            next_move = [3, 2, 0]
        elif choice == 2:
            next_move = [1, 3, 0]
        else:
            next_move = [3, 1, 0]
    elif pieces == [3, 2, 3]:
        next_move = [3, 0, 3]
    elif pieces == [3, 2, 2]:
        next_move = [0, 2, 2]
    elif pieces == [3, 2, 1]:
        if choice == 1:
            next_move = [1, 2, 1]
        elif choice == 2:
            next_move = [3, 0, 1]
        else:
            next_move = [2, 2, 1]
    elif pieces == [3, 2, 0]:
        next_move = [2, 2, 0]
    elif pieces == [3, 1, 3]:
        next_move = [3, 0, 3]
    elif pieces == [3, 1, 2]:
        if choice == 1:
            next_move = [1, 1, 2]
        elif choice == 2:
            next_move = [3, 0, 2]
        else:
            next_move = [2, 1, 2]
    elif pieces == [3, 1, 1]:
        next_move = [1, 1, 1]
    elif pieces == [3, 1, 0]:
        next_move = [0, 1, 0]
    elif pieces == [3, 0, 3]:
        if choice == 1:
            next_move = [2, 0, 3]
        elif choice == 2:
            next_move = [3, 0, 2]
        else:
            next_move = [1, 0, 3]
    elif pieces == [3, 0, 2]:
        next_move = [2, 0, 2]
    elif pieces == [3, 0, 1]:
        next_move = [0, 0, 1]
    elif pieces == [3, 0, 0]:
        next_move = [1, 0, 0]
    elif pieces == [2, 5, 3]:
        next_move = [2, 1, 3]
    elif pieces == [2, 5, 2]:
        next_move = [2, 0, 2]
    elif pieces == [2, 5, 1]:
        next_move = [2, 3, 1]
    elif pieces == [2, 5, 0]:
        next_move = [2, 2, 0]
    elif pieces == [2, 4, 3]:
        next_move = [2, 1, 3]
    elif pieces == [2, 4, 2]:
        next_move = [2, 0, 2]
    elif pieces == [2, 4, 1]:
        next_move = [2, 3, 1]
    elif pieces == [2, 4, 0]:
        next_move = [2, 2, 0]
    elif pieces == [2, 3, 3]:
        next_move = [0, 3, 3]
    elif pieces == [2, 3, 2]:
        next_move = [2, 0, 2]
    elif pieces == [2, 3, 1]:
        if choice == 1:
            next_move = [2, 2, 1]
        elif choice == 2:
            next_move = [0, 3, 1]
        else:
            next_move = [2, 1, 1]
    elif pieces == [2, 3, 0]:
        next_move = [2, 2, 0]
    elif pieces == [2, 2, 3]:
        next_move = [2, 2, 0]
    elif pieces == [2, 2, 2]:
        next_move = [2, 0, 2]
    elif pieces == [2, 2, 1]:
        next_move = [2, 2, 0]
    elif pieces == [2, 2, 0]:
        if choice == 1:
            next_move = [2, 0, 0]
        elif choice == 2:
            next_move = [1, 2, 0]
        else:
            next_move = [2, 1, 0]
    elif pieces == [2, 1, 3]:
        if choice == 1:
            next_move = [2, 1, 2]
        elif choice == 2:
            next_move = [2, 1, 1]
        else:
            next_move = [1, 1, 3]
    elif pieces == [2, 1, 2]:
        next_move = [2, 0, 2]
    elif pieces == [2, 1, 1]:
        next_move = [1, 1, 1]
    elif pieces == [2, 1, 0]:
        next_move = [0, 1, 0]
    elif pieces == [2, 0, 3]:
        next_move = [2, 0, 2]
    elif pieces == [2, 0, 2]:
        if choice == 1:
            next_move = [2, 0, 1]
        elif choice == 2:
            next_move = [1, 0, 2]
        else:
            next_move = [2, 0, 0]
    elif pieces == [2, 0, 1]:
        next_move = [0, 0, 1]
    elif pieces == [2, 0, 0]:
        next_move = [1, 0, 0]
    elif pieces == [1, 5, 3]:
        next_move = [1, 2, 3]
    elif pieces == [1, 5, 2]:
        next_move = [1, 3, 2]
    elif pieces == [1, 5, 1]:
        next_move = [1, 1, 1]
    elif pieces == [1, 5, 0]:
        next_move = [1, 0, 0]
    elif pieces == [1, 4, 3]:
        next_move = [1, 2, 3]
    elif pieces == [1, 4, 2]:
        next_move = [1, 3, 2]
    elif pieces == [1, 4, 1]:
        next_move = [1, 1, 1]
    elif pieces == [1, 4, 0]:
        next_move = [1, 0, 0]
    elif pieces == [1, 3, 3]:
        next_move = [0, 3, 3]
    elif pieces == [1, 3, 2]:
        if choice == 1:
            next_move = [1, 2, 2]
        elif choice == 2:
            next_move = [1, 3, 1]
        else:
            next_move = [1, 1, 2]
    elif pieces == [1, 3, 1]:
        next_move = [1, 1, 1]
    elif pieces == [1, 3, 0]:
        next_move = [1, 0, 0]
    elif pieces == [1, 2, 3]:
        if choice == 1:
            next_move = [1, 2, 2]
        elif choice == 2:
            next_move = [1, 2, 1]
        else:
            next_move = [1, 1, 3]
    elif pieces == [1, 2, 2]:
        next_move = [0, 2, 2]
    elif pieces == [1, 2, 1]:
        next_move = [1, 1, 1]
    elif pieces == [1, 2, 0]:
        next_move = [1, 0, 0]
    elif pieces == [1, 1, 3]:
        next_move = [1, 1, 1]
    elif pieces == [1, 1, 2]:
        next_move = [1, 1, 1]
    elif pieces == [1, 1, 1]:
        if choice == 1:
            next_move = [1, 0, 1]
        elif choice == 2:
            next_move = [1, 1, 0]
        else:
            next_move = [0, 1, 1]
    elif pieces == [1, 1, 0]:
        next_move = [1, 0, 0]
    elif pieces == [1, 0, 3]:
        next_move = [1, 0, 0]
    elif pieces == [1, 0, 2]:
        next_move = [1, 0, 0]
    elif pieces == [1, 0, 1]:
        next_move = [1, 0, 0]
    elif pieces == [1, 0, 0]:
        next_move = [0, 0, 0]
    elif pieces == [0, 5, 3]:
        next_move = [0, 3, 3]
    elif pieces == [0, 5, 2]:
        next_move = [0, 2, 2]
    elif pieces == [0, 5, 1]:
        next_move = [0, 0, 1]
    elif pieces == [0, 5, 0]:
        next_move = [0, 1, 0]
    elif pieces == [0, 4, 3]:
        next_move = [0, 3, 3]
    elif pieces == [0, 4, 2]:
        next_move = [0, 2, 2]
    elif pieces == [0, 4, 1]:
        next_move = [0, 0, 1]
    elif pieces == [0, 4, 0]:
        next_move = [0, 1, 0]
    elif pieces == [0, 3, 3]:
        if choice == 1:
            next_move = [0, 3, 2]
        elif choice == 2:
            next_move = [0, 2, 3]
        else:
            next_move = [0, 3, 1]
    elif pieces == [0, 3, 2]:
        next_move = [0, 2, 2]
    elif pieces == [0, 3, 1]:
        next_move = [0, 0, 1]
    elif pieces == [0, 3, 0]:
        next_move = [0, 1, 0]
    elif pieces == [0, 2, 3]:
        next_move = [0, 2, 1]
    elif pieces == [0, 2, 2]:
        if choice == 1:
            next_move = [0, 1, 2]
        elif choice == 2:
            next_move = [0, 2, 1]
        else:
            next_move = [0, 2, 0]
    elif pieces == [0, 2, 1]:
        next_move = [0, 0, 1]
    elif pieces == [0, 2, 0]:
        next_move = [0, 1, 0]
    elif pieces == [0, 1, 3]:
        next_move = [0, 1, 0]
    elif pieces == [0, 1, 2]:
        next_move = [0, 1, 0]
    elif pieces == [0, 1, 1]:
        next_move = [0, 1, 0]
    elif pieces == [0, 1, 0]:
        next_move = [0, 0, 0]
    elif pieces == [0, 0, 3]:
        next_move = [0, 0, 1]
    elif pieces == [0, 0, 2]:
        next_move = [0, 0, 1]
    elif pieces == [0, 0, 1]:
        next_move = [0, 0, 0]
    elif pieces == [0, 0, 0]:
        finish = True
    return next_move

#this is the main program
root = Tk()
root.title('Nim')
canvas = Canvas(root, width=680, height=250)
canvas.pack()
create_pieces()
create_operation_buttons()
root.mainloop()

CodePudding user response:

You can collect the results of the minimax search in a dictionary. We could associate each possible state with a list of winning moves. If that list is empty, it means the state is losing. The zero-state, where all piles are empty, is winning for the player that would normally have to play the next move, so for that state you'd store something else than an empty list, as that would count as losing.

Here is the code to build the dictionary (called memo):

def movelist(state):
    for i in range(state[0]):
        yield (i, state[1], state[2])
    for i in range(state[1]):
        yield (state[0], i, state[2])
    for i in range(state[2]):
        yield (state[0], state[1], i)

def winnningmoves(initialstate):
    memo = {}
    memo[(0,0,0)] = True # Won game for player that would play next    

    def recur(state):
        if state not in memo:
            # Collect states that are losing (for the opponent)
            memo[state] = [move for move in movelist(state) if not recur(move)]
        return memo[state]

    recur(initialstate)
    return memo

So you would build that dictionary with a call to winningmoves:

initialstate = (7,5,3)
memo = winnningmoves(initialstate)  # Collect winning moves for each state

Here is a simple loop to actually play a computer-against-human game (without any input validation):

import random

def inputmove(state):
    pile = int(input("Which pile you want to draw from? [1,2,3]: ")) - 1
    take = int(input(f"How many to draw from that pile? [1..{state[pile]}]: "))
    if pile == 0:
        return (state[0] - take, state[1], state[2])
    if pile == 1:
        return (state[0], state[1] - take, state[2])
    return (state[0], state[1], state[2] - take)
        
state = initialstate
print("Game starts with", state)
while state != (0,0,0):
    state = random.choice(memo[state])  # Select winning move
    print("Computer moved to", state)
    if state == (0,0,0):
        print("You won!")
        break
    state = inputmove(state)
    print("You moved to", state)
else:
    print("You lost")

This example code assumes that the "computer" can win, i.e. that there is always a move to choose from. If ever it would start with a state that is losing, this code will fail. You could adapt to make the "computer" choose any random move in that case, or one where the opponent will only have one narrow winning path.

  • Related