Created
October 23, 2020 19:49
-
-
Save masouduut94/c9f9ba4b76cc796594255cb5c5049145 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from tkinter import (Frame, Canvas, ttk, HORIZONTAL, VERTICAL, IntVar, Scale, Button, Label, PhotoImage, BOTH, LEFT, Y, | |
X, TOP, messagebox) | |
from numpy import int_ | |
from gamestate import GameState | |
from meta import GameMeta | |
from uct_mcstsagent import UctMctsAgent | |
class Gui: | |
""" | |
This class is built to let the user have a better interaction with | |
game. | |
inputs => | |
root = Tk() => an object which inherits the traits of Tkinter class | |
agent = an object which inherit the traits of mctsagent class. | |
""" | |
agent_type = {1: "UCT"} | |
AGENTS = {"UCT": UctMctsAgent} | |
def __init__(self, root, agent_name='UCT'): | |
self.root = root | |
self.root.geometry('1366x690+0+0') | |
self.agent_name = agent_name | |
try: | |
self.agent = self.AGENTS[agent_name]() | |
except KeyError: | |
print("Unknown agent defaulting to basic") | |
self.agent_name = "uct" | |
self.agent = self.AGENTS[agent_name]() | |
self.game = GameState(8) | |
self.agent.set_gamestate(self.game) | |
self.time = 1 | |
self.root.configure(bg='#363636') | |
self.colors = {'white': '#ffffff', | |
'milk': '#e9e5e5', | |
'red': '#9c0101', | |
'orange': '#ee7600', | |
'yellow': '#f4da03', | |
'green': '#00ee76', | |
'cyan': '#02adfd', | |
'blue': '#0261fd', | |
'purple': '#9c02fd', | |
'gray1': '#958989', | |
'gray2': '#3e3e3e', | |
'black': '#000000'} | |
global BG | |
BG = self.colors['gray2'] | |
self.last_move = None | |
self.frame_board = Frame(self.root) # main frame for the play board | |
self.canvas = Canvas(self.frame_board, bg=BG) | |
self.scroll_y = ttk.Scrollbar(self.frame_board, orient=VERTICAL) | |
self.scroll_x = ttk.Scrollbar(self.frame_board, orient=HORIZONTAL) | |
# the notebook frame which holds the left panel frames | |
self.notebook = ttk.Notebook(self.frame_board, width=350) | |
self.panel_game = Frame(self.notebook, highlightbackground=self.colors['white']) | |
self.developers = Frame(self.notebook, highlightbackground=self.colors['white']) | |
# Registering variables for: | |
self.game_size_value = IntVar() # size of the board | |
self.game_time_value = IntVar() # time of CPU player | |
self.game_turn_value = IntVar() # defines whose turn is it | |
self.switch_agent_value = IntVar() # defines which agent to play against | |
self.switch_agent_value.set(1) | |
self.game_turn_value.set(1) | |
self.turn = {1: 'white', 2: 'black'} | |
self.game_size = Scale(self.panel_game) | |
self.game_time = Scale(self.panel_game) | |
self.game_turn = Scale(self.panel_game) | |
self.generate = Button(self.panel_game) | |
self.reset_board = Button(self.panel_game) | |
self.switch_agent = Scale(self.panel_game) | |
self.agent_show = Label(self.panel_game, font=('Calibri', 14, 'bold'), fg='white', justify=LEFT, | |
bg=BG, text='Agent Policy: ' + self.agent_name + '\n') | |
self.hex_board = [] | |
# Holds the IDs of hexagons in the main board for implementing the click and play functions | |
self.game_size_value.set(8) | |
self.game_time_value.set(1) | |
self.size = self.game_size_value.get() | |
self.time = self.game_time_value.get() | |
self.board = self.game.board | |
self.board = int_(self.board).tolist() | |
self.gameboard2hexagons(self.board) # building the game board | |
self.logo = PhotoImage(file='image/hex.png') | |
self.uut_logo = PhotoImage(file='image/uut_2.png') | |
self.generate_black_edge() | |
self.generate_white_edge() | |
# Frame_content | |
self.frame_board.configure(bg=BG, width=1366, height=760) | |
self.frame_board.pack(fill=BOTH) | |
self.notebook.add(self.panel_game, text=' Game ') | |
self.notebook.add(self.developers, text=' Developers ') | |
self.notebook.pack(side=LEFT, fill=Y) | |
self.canvas.configure(width=980, bg=BG, cursor='hand2') | |
self.canvas.pack(side=LEFT, fill=Y) | |
self.canvas.configure(yscrollcommand=self.scroll_y.set) | |
self.scroll_y.configure(command=self.canvas.yview) | |
self.scroll_x.configure(command=self.canvas.xview) | |
self.scroll_y.place(x=387, y=482) | |
self.scroll_x.place(x=370, y=500) | |
# Frame_left_panel | |
""" | |
the left panel notebook ----> Game | |
""" | |
self.panel_game.configure(bg=BG) | |
Label(self.panel_game, text='Board size', | |
font=('Calibri', 14, 'bold'), | |
foreground='white', bg=BG, pady=10).pack(fill=X, side=TOP) # label ---> Board size | |
self.game_size.configure(from_=3, to=20, tickinterval=1, bg=BG, fg='white', | |
orient=HORIZONTAL, variable=self.game_size_value) | |
self.game_size.pack(side=TOP, fill=X) | |
Label(self.panel_game, text='Time', | |
font=('Calibri', 14, 'bold'), | |
foreground='white', bg=BG, pady=10).pack(side=TOP, fill=X) # label ---> Time | |
self.game_time.configure(from_=1, to=20, tickinterval=1, bg=BG, fg='white', | |
orient=HORIZONTAL, variable=self.game_time_value) | |
self.game_time.pack(side=TOP, fill=X) | |
Label(self.panel_game, text='Player', | |
font=('Calibri', 14, 'bold'), | |
foreground='white', bg=BG, pady=10).pack(side=TOP, fill=X) # label ---> Turn | |
self.game_turn.configure(from_=1, to=2, tickinterval=1, bg=BG, fg='white', | |
orient=HORIZONTAL, variable=self.game_turn_value) | |
self.game_turn.pack(side=TOP) | |
Label(self.panel_game, text=' ', | |
font=('Calibri', 14, 'bold'), | |
foreground='white', bg=BG).pack(side=TOP, fill=X) | |
# ################################## AGENT CONTROLS ############################# | |
self.agent_show.pack(fill=X, side=TOP) | |
self.switch_agent.configure(from_=1, to=len(self.agent_type), tickinterval=1, bg=BG, fg='white', | |
orient=HORIZONTAL, variable=self.switch_agent_value, ) | |
self.switch_agent.pack(side=TOP, fill=X) | |
# ################################## MOVE LABELS ################################ | |
self.move_label = Label(self.panel_game, font=('Calibri', 15, 'bold'), height=5, fg='white', justify=LEFT, | |
bg=BG, text='PLAY : CLICK A CELL ON GAME BOARD \nMCTS BOT: CLICK GENERATE') | |
self.move_label.pack(side=TOP, fill=X) | |
self.reset_board.configure(text='Reset Board', pady=10, | |
cursor='hand2', width=22, | |
font=('Calibri', 12, 'bold')) | |
self.reset_board.pack(side=LEFT) | |
self.generate.configure(text='Generate', pady=10, | |
cursor='hand2', width=22, | |
font=('Calibri', 12, 'bold')) | |
self.generate.pack(side=LEFT) | |
""" | |
the left panel notebook ---> Developers | |
""" | |
self.developers.configure(bg=BG) | |
Label(self.developers, | |
text='HEXPY', | |
font=('Calibri', 18, 'bold'), | |
foreground='white', bg=BG, pady=5).pack(side=TOP, fill=X) | |
Label(self.developers, | |
text='DEVELOPED BY:\n' | |
+ 'Masoud Masoumi Moghadam\n\n' | |
+ 'SUPERVISED BY:\n' | |
+ 'Dr.Pourmahmoud Aghababa\n' | |
+ 'Dr.Bagherzadeh\n\n' | |
+ 'SPECIAL THANKS TO:\n' | |
+ 'Nemat Rahmani\n', | |
font=('Calibri', 16, 'bold'), justify=LEFT, | |
foreground='white', bg=BG, pady=10).pack(side=TOP, fill=X) | |
Label(self.developers, image=self.uut_logo, bg=BG).pack(side=TOP, fill=X) | |
Label(self.developers, text='Summer 2016', | |
font=('Calibri', 17, 'bold'), wraplength=350, justify=LEFT, | |
foreground='white', bg=BG, pady=30).pack(side=TOP, fill=X) | |
# Binding Actions | |
""" | |
Binding triggers for the actions defined in the class. | |
""" | |
self.canvas.bind('<1>', self.click2play) | |
self.game_size.bind('<ButtonRelease>', self.set_size) | |
self.game_time.bind('<ButtonRelease>', self.set_time) | |
self.generate.bind('<ButtonRelease>', self.click_to_bot_play) | |
self.reset_board.bind('<ButtonRelease>', self.reset) | |
self.switch_agent.bind('<ButtonRelease>', self.set_agent) | |
@staticmethod | |
def top_left_hexagon(): | |
""" | |
Returns the points which the first hexagon has to be created based on. | |
""" | |
return [[85, 50], [105, 65], [105, 90], [85, 105], [65, 90], [65, 65]] | |
def hexagon(self, points, color): | |
""" | |
Creates a hexagon by getting a list of points and their assigned colors | |
according to the game board | |
""" | |
if color == 0: | |
hx = self.canvas.create_polygon(points[0], points[1], points[2], | |
points[3], points[4], points[5], | |
fill=self.colors['gray1'], outline='black', width=2, activefill='cyan') | |
elif color == 1: | |
hx = self.canvas.create_polygon(points[0], points[1], points[2], | |
points[3], points[4], points[5], | |
fill=self.colors['yellow'], outline='black', width=2, activefill='cyan') | |
elif color == 2: | |
hx = self.canvas.create_polygon(points[0], points[1], points[2], | |
points[3], points[4], points[5], | |
fill=self.colors['red'], outline='black', width=2, activefill='cyan') | |
elif color == 3: | |
hx = self.canvas.create_polygon(points[0], points[1], points[2], | |
points[3], points[4], points[5], | |
fill=self.colors['black'], outline='black', width=2) | |
else: | |
hx = self.canvas.create_polygon(points[0], points[1], points[2], | |
points[3], points[4], points[5], | |
fill=self.colors['white'], outline='black', width=2) | |
return hx | |
def generate_row(self, points, colors): | |
""" | |
By getting a list of points as the starting point of each row and a list of | |
colors as the dedicated color for each item in row, it generates a row of | |
hexagons by calling hexagon functions multiple times. | |
""" | |
x_offset = 40 | |
row = [] | |
temp_array = [] | |
for i in range(len(colors)): | |
for point in points: | |
temp_points_x = point[0] + x_offset * i | |
temp_points_y = point[1] | |
temp_array.append([temp_points_x, temp_points_y]) | |
if colors[i] == 0: | |
hx = self.hexagon(temp_array, 0) | |
elif colors[i] == 1: | |
hx = self.hexagon(temp_array, 4) | |
else: | |
hx = self.hexagon(temp_array, 3) | |
row.append(hx) | |
temp_array = [] | |
return row | |
def gameboard2hexagons(self, array): | |
""" | |
Simply gets the game_board and generates the hexagons by their dedicated colors. | |
""" | |
initial_offset = 20 | |
y_offset = 40 | |
temp = [] | |
for i in range(len(array)): | |
points = self.top_left_hexagon() | |
for point in points: | |
point[0] += initial_offset * i | |
point[1] += y_offset * i | |
temp.append([point[0], point[1]]) | |
row = self.generate_row(temp, self.board[i]) | |
temp.clear() | |
self.hex_board.append(row) | |
def generate_white_edge(self): | |
""" | |
Generates the white zones in the left and right of the board. | |
""" | |
init_points = self.top_left_hexagon() | |
for pt in init_points: | |
pt[0] -= 40 | |
for pt in init_points: | |
pt[0] -= 20 | |
pt[1] -= 40 | |
label_x, label_y = 0, 0 | |
init_offset = 20 | |
y_offset = 40 | |
temp_list = [] | |
for i in range(len(self.board)): | |
for pt in range(len(init_points)): | |
init_points[pt][0] += init_offset | |
init_points[pt][1] += y_offset | |
label_x += init_points[pt][0] | |
label_y += init_points[pt][1] | |
label_x /= 6 | |
label_y /= 6 | |
self.hexagon(init_points, 4) | |
self.canvas.create_text(label_x, label_y, fill=self.colors['black'], font="Times 20 bold", | |
text=chr(ord('A') + i)) | |
label_x, label_y = 0, 0 | |
for j in init_points: | |
temp_list.append([j[0] + (len(self.board) + 1) * 40, j[1]]) | |
self.hexagon(temp_list, 4) | |
temp_list.clear() | |
def generate_black_edge(self): | |
""" | |
Generates the black zones in the top and bottom of the board. | |
""" | |
init_points = self.top_left_hexagon() | |
label_x, label_y = 0, 0 | |
temp_list = [] | |
for pt in init_points: | |
pt[0] -= 60 | |
pt[1] -= 40 | |
for t in range(len(init_points)): | |
init_points[t][0] += 40 | |
label_x += init_points[t][0] | |
label_y += init_points[t][1] | |
label_x /= 6 | |
label_y /= 6 | |
for i in range(len(self.board)): | |
self.hexagon(init_points, 3) | |
self.canvas.create_text(label_x, label_y, fill=self.colors['white'], font="Times 20 bold", text=i + 1) | |
label_x, label_y = 0, 0 | |
for pt in init_points: | |
temp_list.append([pt[0] + (len(self.board) + 1) * 20, pt[1] + (len(self.board) + 1) * 40]) | |
self.hexagon(temp_list, 3) | |
temp_list.clear() | |
for j in range(len(init_points)): | |
init_points[j][0] += 40 | |
label_x += init_points[j][0] | |
label_y += init_points[j][1] | |
label_x /= 6 | |
label_y /= 6 | |
def click2play(self, event): | |
""" | |
Whenever any of the hexagons in the board is clicked, depending | |
on the player turns, it changes the color of hexagon to the player | |
assigned color. | |
""" | |
if self.winner() == 'none': | |
x = self.canvas.canvasx(event.x) | |
y = self.canvas.canvasy(event.y) | |
idd = self.canvas.find_overlapping(x, y, x, y) | |
idd = list(idd) | |
if len(idd) != 0: | |
clicked_cell = idd[0] | |
if any([clicked_cell in x for x in self.hex_board]): | |
coordinated_cell = clicked_cell - self.hex_board[0][0] | |
col = (coordinated_cell % self.size) | |
turn = self.turn[self.game_turn_value.get()] | |
if coordinated_cell % self.size == 0: | |
row = int(coordinated_cell / self.size) | |
else: | |
row = int(coordinated_cell / self.size) | |
cell = str(chr(65 + row)) + str(col + 1) | |
self.move_label.configure(text=str(turn) + ' played ' + cell, justify=LEFT, height=5) | |
if self.board[row][col] == 0: | |
self.board[row][col] = self.game_turn_value.get() | |
if self.game_turn_value.get() == 1: | |
self.game_turn_value.set(2) | |
else: | |
self.game_turn_value.set(1) | |
self.refresh() | |
y = row | |
x = col | |
if turn[0].lower() == 'w': | |
self.last_move = (x, y) | |
if self.game.turn() == GameMeta.PLAYERS["white"]: | |
self.game.play((x, y)) | |
self.agent.move((x, y)) | |
if self.winner() != 'none': | |
messagebox.showinfo(" GAME OVER", " Wow, You won! \n Winner is %s" % self.winner()) | |
return | |
else: | |
self.game.place_white((x, y)) | |
self.agent.set_gamestate(self.game) | |
if self.winner() != 'none': | |
messagebox.showinfo(" GAME OVER", " Wow, You won! \n Winner is %s" % self.winner()) | |
return | |
elif turn[0].lower() == 'b': | |
self.last_move = (x, y) | |
if self.game.turn() == GameMeta.PLAYERS["black"]: | |
self.game.play((x, y)) | |
self.agent.move((x, y)) | |
if self.winner() != 'none': | |
messagebox.showinfo(" GAME OVER", " Wow, You won! \n Winner is %s" % self.winner()) | |
return | |
else: | |
self.game.place_black((x, y)) | |
self.agent.set_gamestate(self.game) | |
if self.winner() != 'none': | |
messagebox.showinfo(" GAME OVER", " Wow, You won! \n Winner is %s" % self.winner()) | |
return | |
else: | |
messagebox.showinfo(" GAME OVER ", " The game is already over! Winner is %s" % self.winner()) | |
def set_size(self, event): | |
""" | |
It changes the board size and reset the whole game. | |
""" | |
self.canvas.delete('all') | |
self.size = self.game_size_value.get() | |
self.game = GameState(self.size) | |
self.agent.set_gamestate(self.game) | |
self.board = self.game.board | |
self.board = int_(self.board).tolist() | |
self.last_move = None | |
self.move_label.config(text='PLAY : CLICK A CELL ON GAME BOARD \nMCTS BOT: CLICK GENERATE', justify='left', | |
height=5) | |
self.refresh() | |
def set_time(self, event) -> None: | |
""" | |
It changes the time for CPU player to think and generate a move. | |
""" | |
self.time = self.game_time_value.get() | |
print('The CPU time = ', self.time, ' seconds') | |
def set_agent(self, event) -> None: | |
""" | |
It changes the time for CPU player to think and generate a move. | |
""" | |
agent_num = self.switch_agent_value.get() | |
self.agent_name = self.agent_type[agent_num] | |
self.agent = self.AGENTS[self.agent_name](self.game) | |
self.agent_show.config(font=('Calibri', 14, 'bold'), justify=LEFT, | |
text='Agent Policy: ' + self.agent_name + '\n') | |
def winner(self) -> str: | |
""" | |
Return the winner of the current game (black or white), none if undecided. | |
""" | |
if self.game.winner == GameMeta.PLAYERS["white"]: | |
return "white" | |
elif self.game.winner == GameMeta.PLAYERS["black"]: | |
return "black" | |
else: | |
return "none" | |
def click_to_bot_play(self, event): | |
""" | |
By pushing the generate button, It produces an appropriate move | |
by using monte carlo tree search algorithm for the player which | |
turn is his/hers! . | |
""" | |
if self.winner() == 'none': | |
self.agent.search(self.time) | |
num_rollouts, node_count, run_time = self.agent.statistics() | |
move = self.agent.best_move() # the move is tuple like (3, 1) | |
self.game.play(move) | |
self.agent.move(move) | |
row, col = move # Relating the 'move' tuple with index of self.board | |
self.board[col][row] = self.game_turn_value.get() | |
if self.game_turn_value.get() == 1: # change the turn of players | |
self.game_turn_value.set(2) | |
else: | |
self.game_turn_value.set(1) | |
self.refresh() | |
player = self.turn[self.game_turn_value.get()] | |
cell = chr(ord('A') + move[1]) + str(move[0] + 1) | |
self.move_label.config(font=('Calibri', 15, 'bold'), justify='left', | |
text=str(num_rollouts) + ' Game Simulations ' + '\n' | |
+ 'In ' + str(run_time) + ' seconds ' + '\n' | |
+ 'Node Count : ' + str(node_count) + '\n' | |
+ player + ' played at ' + cell, height=5) | |
print('move = ', cell) | |
if self.winner() != 'none': | |
messagebox.showinfo(" GAME OVER", " Oops!\n You lost! \n Winner is %s" % self.winner()) | |
else: | |
messagebox.showinfo(" GAME OVER", " The game is already over! Winner is %s" % self.winner()) | |
def refresh(self): | |
""" | |
Delete the whole world and recreate it again | |
""" | |
self.canvas.delete('all') | |
self.hex_board.clear() | |
self.gameboard2hexagons(self.board) | |
self.generate_black_edge() | |
self.generate_white_edge() | |
def reset(self, event): | |
""" | |
By clicking on the Reset button game board would be cleared | |
for a new game | |
""" | |
self.game = GameState(self.game.size) | |
self.agent.set_gamestate(self.game) | |
self.set_size(event) | |
self.last_move = None | |
self.game_turn_value.set(1) | |
self.move_label.config(text='PLAY : CLICK A CELL ON GAME BOARD \nMCTS BOT: CLICK GENERATE', justify='left', | |
height=5) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment