# Written by Eric Martin for COMP9021

from tkinter import *
import tkinter.messagebox
from functools import reduce


DIM = 9
GAP = 30
PEG_COLOUR = '#A0522D'
SELECTION_COLOUR = '#00FFFF'
BOARD_COLOUR = '#DCDCDC'
DELAY = 1000


class Solitaire(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.title('Solitaire')
        menubar = Menu()
        help_menu = Menu(menubar)
        menubar.add_cascade(label = 'Solitaire Help', menu = help_menu)
        help_menu.add_command(label = 'Rules of the game',
                              command = self.rules_help)
        help_menu.add_command(label = 'Defined problems',
                              command = self.defined_problems_help)
        help_menu.add_command(label = 'Reaction to clicks',
                              command = self.reaction_to_clicks_help)
        self.config(menu = menubar)

        buttons = Frame(bd = 20)        
        self.defined_problems_button = Menubutton(buttons,
                                                  text = 'Defined problems',
                                                  state = DISABLED)
        self.defined_problems_button.pack(padx = 30, side = LEFT)
        problems = Menu(self.defined_problems_button)
        problems.add_command(label = 'Six marble cross',
                             command = lambda p = 0: self.select_problem(p))
        problems.add_command(label = 'Nine marble cross',
                             command = lambda p = 1: self.select_problem(p))
        problems.add_command(label = 'Triangle',
                             command = lambda p = 2: self.select_problem(p))
        problems.add_command(label = 'Fireplace',
                             command = lambda p = 3: self.select_problem(p))
        problems.add_command(label = 'Calvary',
                             command = lambda p = 4: self.select_problem(p))
        problems.add_command(label = 'Pyramid',
                             command = lambda p = 5: self.select_problem(p))
        problems.add_command(label = 'Double cross',
                             command = lambda p = 6: self.select_problem(p))
        problems.add_command(label = 'Interlaced five crosses',
                             command = lambda p = 7: self.select_problem(p))
        problems.add_command(label = 'Pentagon',
                             command = lambda p = 8: self.select_problem(p))
        problems.add_command(label = 'Inclined square',
                             command = lambda p = 9: self.select_problem(p))
        problems.add_command(label = 'Octogon',
                             command = lambda p = 10: self.select_problem(p))
        problems.add_command(label = 'Triple cross',
                             command = lambda p = 11: self.select_problem(p))
        self.defined_problems_button.config(menu = problems)
        self.select_or_create_problem_button =\
                            Button(buttons,
                                   text = 'Select defined problem',
                                   width = 16,
                                   command = self.select_or_create_problem)
        self.select_or_create_problem_button.pack(padx = 30, side = LEFT)
        self.prepare_to_play_or_play_button =\
                            Button(buttons,
                                   text = 'Start playing',
                                   command = self.prepare_to_play_or_play)
        self.prepare_to_play_or_play_button.pack(padx = 30, side = LEFT)
        self.clear_or_demonstrate_button =\
                            Button(buttons,
                                   text = 'Clear',
                                   width = 16,
                                   command = self.clear_or_demonstrate)
        self.clear_or_demonstrate_button.pack(padx = 30)
        buttons.pack()
        self.board = Board()        
        self.board.pack()
        self.selecting_problem = False
        self.selected_problem = None
        self.playing = False
        self.demonstrating = False
        self.demonstrating_further = False

    def rules_help(self):
        tkinter.messagebox.showinfo('Rules of the game',
            'A peg can jump over another peg, "take it" '
            '(so the "taken" peg is removed) '
            'and fill a hole, moving either horizontall or vertically.\n\n'
            'The aim is that only one peg be left.')

    def defined_problems_help(self):
        tkinter.messagebox.showinfo('Defined problems',
            'Twelve defined problems are provided.\n\n'
            'When "Select defined problem" is visible '
            'on a button, that button can be clicked to '
            'allow one of those problems to be selected '
            'from the "Defined problems" dropdown menu. '
            'There are then two options:\n'
            ' - Click on the button that displays "Demonstrate solution". '
            'This will start the animation of a solution that can be paused '
            'and resumed at will by clicking on the rightmost button, '
            'and which will have to eventually be stopped by clicking '
            'on the button that displays "Stop demonstration" '
            'as it starts from the initial configuration again after only '
            'one peg is left and the game is over.\n'
            ' - Click on the button that displays "Create own problem" '
            'to try and solve the problem manually, possibly after having '
            'modified it, or by setting a new problem by first clicking '
            'on the button which displays "Clear".')

    def reaction_to_clicks_help(self):
        tkinter.messagebox.showinfo('Reaction to clicks',
            'Clicking has not effect:\n'
            ' - after the button that displays "Select defined problem" '
            'has been clicked;\n'
            ' - after one the defined problems has been selected;\n'
            ' - after the button that displays "Demonstrate solution" '
            'has been clicked and for as long as the button that displays '
            '"Stop demonstration" will not have been clicked.\n\n'
            'If the third button displays "Start playing", then clicking '
            'on the board allows one to insert a new peg (when clicking '
            'on a hole) or remove an existing peg.\n\n'
            'If the third button displays "Stop playing", then clicking '
            'on the board allows one to:\n'
            ' - select a peg if none is being selected;\n'
            ' - unselect the selected peg, if any;\n'
            ' - jump over and take an adjacent peg by clicking on the hole '
            'where the selected peg will end up, if that move is possible.')

    def select_or_create_problem(self):
        if self.demonstrating or self.selecting_problem:
            self.selecting_problem = False
            self.selected_problem = None
            self.board.selected_problem = None
            self.defined_problems_button.config(state = DISABLED)
            self.select_or_create_problem_button.config(
                                              text = 'Select defined problem')
            self.prepare_to_play_or_play_button.config(state = NORMAL)
            self.clear_or_demonstrate_button.config(text = 'Clear',
                                                    state = NORMAL)
        else:
            self.selecting_problem = True
            self.board.selected_problem = True
            self.defined_problems_button.config(state = NORMAL)
            self.select_or_create_problem_button.config(
                                                  text = 'Create own problem')
            self.prepare_to_play_or_play_button.config(state = DISABLED)
            self.clear_or_demonstrate_button.config(state = DISABLED)
        if self.demonstrating:
            self.demonstrating = False
            self.demonstrating_further = False
            self.board.selected_problem = None
            self.board.selected_solution = None

    def select_problem(self, p):
        self.selected_problem = p
        self.board.selected_problem = p
        self.board.represent_problem(p)
        self.clear_or_demonstrate_button.config(text = 'Demonstrate solution',
                                                state = NORMAL)

    def prepare_to_play_or_play(self):
        self.defined_problems_button.config(state = DISABLED)
        self.select_or_create_problem_button.config(state = DISABLED)
        self.clear_or_demonstrate_button.config(text = 'Clear')
        if self.playing:
            self.playing = False
            self.board.playing = False
            self.select_or_create_problem_button.config(state = NORMAL)
            self.prepare_to_play_or_play_button.config(text = 'Start playing')
            self.clear_or_demonstrate_button.config(state = NORMAL)
            self.board.configuration.positions = self.initial_configuration
            self.board.selected_peg = None
            self.board.represent_configuration()
        else:                
            self.playing = True
            self.board.playing = True
            self.initial_configuration =\
                                       dict(self.board.configuration.positions)
            self.select_or_create_problem_button.config(state = DISABLED)
            self.prepare_to_play_or_play_button.config(text = 'Stop playing')
            self.clear_or_demonstrate_button.config(state = DISABLED)

    def clear_or_demonstrate(self):
        if self.selected_problem != None:
            if self.demonstrating:
                self.demonstrating_further = not self.demonstrating_further
                self.board.demonstrating_further = self.demonstrating_further
                if self.demonstrating_further:
                    self.clear_or_demonstrate_button.config(
                                                  text = 'Pause demonstration')
                    self.board.demonstrate()
                else:
                    self.clear_or_demonstrate_button.config(
                                                 text = 'Resume demonstration')
            else:
                self.demonstrating = True
                self.demonstrating_further = True
                self.board.demonstrating_further = True
                self.board.configuration.selected_solution =\
                             list(self.board.configuration.problem_solutions[
                                                        self.selected_problem])
                self.board.configuration.selected_solution.reverse()
                self.defined_problems_button.config(state = DISABLED)
                self.select_or_create_problem_button.config(
                                                   text = 'Stop demonstration')
                self.clear_or_demonstrate_button.config(
                                                  text = 'Pause demonstration')
                self.board.demonstrate()
        else:
            self.board.clear_board()


# We represent the board using the following coding:
#       37 47 57
#    26 36 46 56 66
# 15 25 35 45 55 65 75
# 14 24 34 44 54 64 74
# 13 23 33 43 53 63 73
#    22 32 42 52 62
#       31 41 51
class Configuration:
    def __init__(self):
        locations = reduce(lambda x, y: x + y,
                           map(list, [range(37, 58, 10),
                                      range(26, 67, 10),
                                      range(15, 76, 10),
                                      range(14, 75, 10),
                                      range(13, 74, 10),
                                      range(22, 63, 10),
                                      range(31, 52, 10)]))
        self.positions = dict.fromkeys(locations, False)
        self.problems = [[35, 43, 44, 45, 46, 55],
                         [24, 34, 42, 43, 44, 45, 46, 54, 64],
                         [23, 33, 34, 43, 44, 45, 53, 54, 63],
                         [34, 35, 36, 37, 45, 46, 47, 54, 55, 56, 57],
                         [25, 31, 32, 35, 41, 42, 43, 44, 45, 46, 47,
                          51, 52, 55, 65],
                         [14, 24, 25, 34, 35, 36, 44, 45, 46, 47, 54,
                          55, 56, 64, 65, 74],
                         [14, 22, 24, 26, 33, 34, 35, 41, 42, 43, 44,
                          45, 46, 47, 53, 54,
                          55, 62, 64, 66, 74],
                         [14, 23, 24, 25, 32, 34, 36, 41, 42, 43, 44,
                          45, 46, 47, 52, 54,
                          56, 63, 64, 65, 74],
                         [14, 23, 24, 25, 32, 33, 34, 35, 36, 42, 43,
                          44, 45, 46, 47, 52,
                          53, 54, 55, 56, 63, 64, 65, 74],
                         [14, 23, 24, 25, 32, 33, 34, 35, 36, 41, 42,
                          43, 45, 46, 47, 52,
                          53, 54, 55, 56, 63, 64, 65, 74],
                         list(set(self.positions) -
                                      set([13, 15, 31, 37, 51, 57, 73, 75])),
                         list(set(self.positions) -
                                      set([22, 26, 44, 62, 66]))]
        self.problem_solutions = [[[45, 65], [43, 45], [35, 55], [65, 45],
                                   [46, 44]],
                                  [[43, 41], [45, 43], [24, 44], [44, 42],
                                   [64, 44], [41, 43], [43, 45], [46, 44]],
                                  [[53, 55], [55, 35], [33, 53], [63, 43],
                                   [44, 42], [35, 33], [23, 43], [42, 44]],
                                  [[45, 25], [37, 35], [57, 37], [34, 36],
                                   [37, 35], [25, 45], [46, 66], [54, 56],
                                   [66, 46], [46, 44]],
                                  [[31, 33], [51, 53], [43, 63], [41, 43],
                                   [33, 53], [63, 43], [44, 42], [46, 44],
                                   [25, 45], [45, 43], [65, 45], [42, 44],
                                   [44, 46], [47, 45]],
                                  [[55, 53], [74, 54], [53, 55], [55, 57],
                                   [57, 37], [35, 33], [14, 34], [33, 35],
                                   [36, 56], [44, 46], [56, 36], [25, 45],
                                   [37, 35], [35, 55], [65, 45]],
                                  [[54, 52], [52, 32], [22, 42], [33, 53],
                                   [41, 43], [43, 63], [74, 54], [62, 64],
                                   [45, 65], [54, 74], [66, 64], [74, 54],
                                   [35, 33], [54, 34], [33, 35], [47, 45],
                                   [45, 25], [14, 34], [26, 24], [24, 44]],
                                  [[64, 62], [44, 64], [74, 54], [46, 66],
                                   [66, 64], [64, 44], [44, 46], [47, 45],
                                   [24, 26], [26, 46], [46, 44], [44, 24],
                                   [14, 34], [42, 22], [22, 24], [24, 44],
                                   [44, 42], [41, 43], [62, 42], [42, 44]],
                                  [[53, 51], [32, 52], [51, 53], [44, 42],
                                   [23, 43], [42, 44], [63, 43], [25, 23],
                                   [45, 25], [43, 45], [55, 35], [35, 33],
                                   [33, 13], [13, 15], [15, 35], [35, 37],
                                   [37, 57], [57, 55], [55, 53], [74, 54],
                                   [53, 55], [65, 45], [46, 44]],
                                  [[53, 51], [51, 31], [55, 75], [75, 73],
                                   [73, 53], [64, 44], [35, 37], [37, 57],
                                   [57, 55], [55, 35], [34, 36], [46, 26],
                                   [14, 34], [26, 24], [23, 25], [44, 24],
                                   [25, 23], [32, 34], [53, 33], [34, 32],
                                   [31, 33], [23, 43], [42, 44]],
                                  [[53, 51], [32, 52], [51, 53], [54, 52],
                                   [74, 54], [44, 42], [52, 32], [22, 42],
                                   [41, 43], [24, 22], [43, 23], [22, 24],
                                   [62, 64], [64, 44], [34, 54], [14, 34],
                                   [66, 64], [64, 44], [56, 54], [35, 33],
                                   [54, 34], [33, 35], [35, 55], [47, 45],
                                   [55, 35], [25, 45], [26, 46], [46, 44]],
                                  [[42, 62], [54, 52], [51, 53], [74, 54],
                                   [54, 52], [62, 42], [73, 53], [32, 52],
                                   [31, 51], [43, 63], [51, 53], [63, 43],
                                   [56, 54], [75, 55], [54, 56], [35, 55],
                                   [47, 45], [45, 65], [57, 55], [65, 45],
                                   [37, 35], [34, 32], [13, 33], [15, 13],
                                   [43, 23], [13, 33], [32, 34], [24, 26],
                                   [34, 36], [26, 46], [46, 44]]]

    
class Board(Frame):
    def __init__(self):
        Frame.__init__(self, pady = 40)
        self.configuration = Configuration()
        self.drawn_configuration = dict(self.configuration.positions)
        self.board = Canvas(self, width = DIM * GAP, height = DIM * GAP)
        self.board.bind('<1>', self.act_on_click)
        self.draw_board()
        self.board.pack()
        self.selected_problem = None
        self.selected_solution = None
        self.playing = False
        self.selected_peg = None
        self.demonstrating_further = False
        
    def draw_board(self):
        self.board.create_polygon(3 * GAP, 2 * GAP - 5,
                                  5 * GAP + 10, 2 * GAP - 5,
                                  7 * GAP + 15, 4 * GAP,
                                  7 * GAP + 15, 6 * GAP + 10,
                                  5 * GAP + 10, 8 * GAP + 15,
                                  3 * GAP, 8 * GAP + 15,
                                  GAP - 5, 6 * GAP + 10,
                                  GAP - 5, 4 * GAP,
                                  fill = BOARD_COLOUR,
                                  outline = 'black',
                                  width = 2)
        for p in self.configuration.positions:
            x = p // 10 * GAP
            y = (DIM - p) % 10 * GAP
            self.drawn_configuration[p] =\
                            self.board.create_oval(x + 1, y + 1,
                                                   x + 9, y + 9,
                                                   fill = 'white',
                                                   outline = 'white')

    def represent_configuration(self):
        for p in self.configuration.positions:
            if self.configuration.positions[p]:
                self.board.itemconfig(self.drawn_configuration[p],
                                      fill = PEG_COLOUR,
                                      outline = PEG_COLOUR)
                self.configuration.positions[p] = True
            else:
                self.board.itemconfig(self.drawn_configuration[p],
                                      fill = 'white',
                                      outline = 'white')
                self.configuration.positions[p] = False

    def represent_problem(self, i):
        self.clear_board()
        for p in self.configuration.problems[i]:
            self.board.itemconfig(self.drawn_configuration[p],
                                  fill = PEG_COLOUR,
                                  outline = PEG_COLOUR)
            self.configuration.positions[p] = True

    def clear_board(self):
        for p in self.drawn_configuration:
            self.board.itemconfig(self.drawn_configuration[p],
                                  fill = 'white',
                                  outline = 'white')
            self.configuration.positions[p] = False

    def position_where_clicked(self, event):
        x = round(self.board.canvasx(event.x))
        y = round(self.board.canvasx(event.y))
        x_last_digit = x % 10
        y_last_digit = y % 10
        if not (1 <= x_last_digit <= 9 and 1 <= y_last_digit <= 9):
            return None
        x -= x_last_digit
        y -= y_last_digit
        if x % GAP or y % GAP:
            return None
        x //= GAP
        y = DIM - y // GAP
        p = x * 10 + y
        return p

    def act_on_click(self, event):
        if self.selected_problem != None:
            return
        if not self.playing:
            self.insert_or_remove_peg(event)
        else:
            self.select_peg_or_jump_and_take(event)

    def insert_or_remove_peg(self, event):
        p = self.position_where_clicked(event)
        if p:
            if self.configuration.positions[p]:
                self.board.itemconfig(self.drawn_configuration[p],
                                      fill = 'white',
                                      outline = 'white')
            else:
                 self.board.itemconfig(self.drawn_configuration[p],
                                       fill = PEG_COLOUR,
                                       outline = PEG_COLOUR)
            self.configuration.positions[p] =\
                                            not self.configuration.positions[p]

    def select_peg_or_jump_and_take(self, event):
        p = self.position_where_clicked(event)
        if not p:
            return
        x = p // 10
        y = p % 10
        if self.selected_peg:
            if self.selected_peg == p:
                self.board.itemconfig(self.drawn_configuration[p],
                                      fill = PEG_COLOUR,
                                      outline = PEG_COLOUR)
                self.selected_peg = None
            elif not self.configuration.positions[p]:
                x_diff = self.selected_peg // 10 - x
                y_diff = self.selected_peg % 10 - y
                if (x_diff == 0 or y_diff == 0) and (abs(x_diff) == 2\
                                                         or abs(y_diff) == 2):
                    self.try_and_take_peg(p, x_diff // 2, y_diff // 2)
        elif self.configuration.positions[p]:
            self.board.itemconfig(self.drawn_configuration[p],
                                  fill = SELECTION_COLOUR,
                                  outline = SELECTION_COLOUR)
            self.selected_peg = p
                    
    def try_and_take_peg(self, p, x_half_diff, y_half_diff):
        if y_half_diff:
            position_of_peg_to_take = p + y_half_diff
        else:
            position_of_peg_to_take = p + x_half_diff * 10
        if not self.configuration.positions[position_of_peg_to_take]:
            return
        if y_half_diff:
            position_of_taking_peg = p + 2 * y_half_diff
        else:
            position_of_taking_peg = p + 2 * x_half_diff * 10
        self.board.itemconfig(self.drawn_configuration[position_of_taking_peg],
                              fill = 'white',
                              outline = 'white')
        self.configuration.positions[position_of_taking_peg] = False
        self.board.itemconfig(self.drawn_configuration[position_of_peg_to_take],
                              fill = 'white',
                              outline = 'white')
        self.configuration.positions[position_of_peg_to_take] = False
        self.board.itemconfig(self.drawn_configuration[p],
                              fill = PEG_COLOUR,
                              outline = PEG_COLOUR)
        self.configuration.positions[p] = True
        self.selected_peg = False

    def demonstrate(self):
        if self.selected_peg:               
            self.selected_peg = False
            self.board.itemconfig(self.drawn_configuration[self.from_position],
                                  fill = 'white',
                                  outline = 'white')
            self.configuration.positions[self.from_position] = False
            taken_position = (self.from_position // 10 +\
                              self.to_position // 10) //\
                              2 * 10 + (self.from_position % 10 +
                                        self.to_position % 10) // 2
            self.board.itemconfig(self.drawn_configuration[taken_position],
                                  fill = 'white',
                                  outline = 'white')
            self.configuration.positions[taken_position] = False
            self.board.itemconfig(self.drawn_configuration[self.to_position],
                                  fill = PEG_COLOUR,
                                  outline = PEG_COLOUR)
            self.configuration.positions[self.to_position] = True
            self.after(DELAY, self.demonstrate)
        elif self.selected_problem == None:
            return
        elif self.demonstrating_further:
            if not self.selected_solution:
                self.represent_problem(self.selected_problem)
                self.selected_solution =\
                            list(self.configuration.problem_solutions[
                                                        self.selected_problem])
                self.selected_solution.reverse()
                self.after(DELAY, self.demonstrate)
            else:
                self.from_position, self.to_position =\
                                                   self.selected_solution.pop()
                self.board.itemconfig(self.drawn_configuration[
                                                       self.from_position],
                                      fill = SELECTION_COLOUR,
                                      outline = SELECTION_COLOUR)
                self.selected_peg = True
                self.after(DELAY, self.demonstrate)



if __name__ == '__main__':
    Solitaire().mainloop()

Resource created Wednesday 09 September 2015, 12:15:42 PM.

file: solitaire.py


Back to top

COMP9021 15s2 (Principles of Programming) is powered by WebCMS3
CRICOS Provider No. 00098G