-
Notifications
You must be signed in to change notification settings - Fork 0
/
choose_difficulty_ui.py
188 lines (158 loc) · 6.89 KB
/
choose_difficulty_ui.py
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
from tkinter import Toplevel, Grid, Label, Button, Canvas, Scale, Event
from tkinter import HORIZONTAL, N, E, S, W
from typing import TYPE_CHECKING, Callable
from hexgrid import HexGrid
from hexgrid_ui_utilities import HexGridUIUtilities
if TYPE_CHECKING:
from game_ui import GameUI # pylint: disable=cyclic-import
class ChooseDifficultyView:
def __init__(
self,
update_slider_range: Callable[[Event], None],
draw_field: Callable[[Event], None]) -> None:
# programs can only have one window, but can
# create multiple "Toplevel"s (effectively new windows)
self.window = Toplevel()
self.window.title('HexSweeper - Choose Difficulty')
self.window.geometry('400x473')
self.window.bind('<Configure>', draw_field)
# these statements allow the hexgrid (in col 1, row 3)
# to stretch as the window is resized
# stretch the second column horizontally:
Grid.columnconfigure(self.window, 1, weight=1)
# stretch the fourth row vertically:
Grid.rowconfigure(self.window, 3, weight=1)
Label(self.window, text='Board Size:').grid(
row=0, column=0, sticky=W)
# TkInter "Scale" objects are sliders
# Default slider resolution/accuracy is 1
self.game_size_slider = Scale(
self.window,
# from_ because from is a Python keyword
from_=2,
to=15,
orient=HORIZONTAL,
command=update_slider_range
)
self.game_size_slider.grid(row=0, column=1, sticky=E + W)
Label(self.window, text='Number of mines:') \
.grid(row=1, column=0, sticky=W)
self.mine_count_slider = Scale(
self.window,
from_=1,
to=315,
orient=HORIZONTAL,
command=draw_field
)
self.mine_count_slider.grid(row=1, column=1, sticky=E + W)
self.canvas = Canvas(self.window, bg='white')
self.canvas.grid(
row=3,
column=0,
# span columns 0 and 1
columnspan=2,
# resize with the window
sticky=E + N + W + S)
def close_window(self):
self.window.destroy()
def set_mine_count_slider_upper_bound(self, upper_bound):
self.mine_count_slider.config(to=upper_bound)
class ChooseDifficultyUI:
def __init__(self, game_ui: 'GameUI') -> None:
self.view = ChooseDifficultyView(
lambda event: self.update_slider_range(),
lambda event: self.draw_field())
self.canvas = self.view.canvas
self.game_ui = game_ui
# initialise these to arbitrary values, as these are
# overwritten before being used anyway
self.apothem: float = 0
self.hshift: float = 0
self.view.window.protocol("WM_DELETE_WINDOW", self.on_window_close)
# set default slider values to values from the previous game
# this makes it easier for the user to make small adjustments
# without having to remember previous game values
self.view.game_size_slider.set(self.game_ui.hex_grid.size)
self.last_size = self.game_ui.hex_grid.size
self.view.set_mine_count_slider_upper_bound(
HexGrid.highest_possible_mine_count_for_size(
self.game_ui.hex_grid.size
)
)
self.view.mine_count_slider.set(self.game_ui.hex_grid.mine_count)
Button(
self.view.window,
text='Select this difficulty',
command=self.select_difficulty_clicked
).grid(row=2, column=0, columnspan=2)
self.draw_field()
# put self.window in the foreground and
# make self.game_ui.window (the root window) inaccessible
self.view.window.transient(self.game_ui.window)
self.view.window.grab_set()
self.game_ui.window.wait_window(self.view.window)
@staticmethod
def border() -> int:
return 20
def update_slider_range(self) -> None:
new_size = self.view.game_size_slider.get()
last_mine_count = self.view.mine_count_slider.get()
if new_size == self.last_size:
# unchanged, no need to make adjustments
return
# start by setting up some variables for later
last_max_mine_count = \
HexGrid.highest_possible_mine_count_for_size(
self.last_size)
new_max_mine_count = \
HexGrid.highest_possible_mine_count_for_size(new_size)
# update mine count slider upper bound
self.view.set_mine_count_slider_upper_bound(new_max_mine_count)
# Calculate new suggested mine count using proportion
# of mines to total mine count (here max_mine_count, which
# is basically equal to total tile count).
# Slider.set() is used to update current slider value.
new_suggested_mine_count = round(
last_mine_count / last_max_mine_count * new_max_mine_count
)
self.view.mine_count_slider.set(new_suggested_mine_count)
# set self.last_size so it can be used next time when
# the slider is updated and this event handler is called
self.last_size = self.view.game_size_slider.get()
# the field preview also needs to be
# redrawn after all these adjustments
self.draw_field()
def draw_field(self) -> None:
"""
Redraw the preview field. Includes generating a new HexGrid.
"""
size = self.view.game_size_slider.get()
mine_count = self.view.mine_count_slider.get()
# create a new, random HexGrid on every redraw
# this only causes slight lag with huge field sizes,
# so it is not a major issue
self.hex_grid = HexGrid(size, mine_count)
# As demonstrated in hexgrid.py (at the top),
# (size - 1, size - 1) always refers to the centre tile.
# Using the center tile as a guaranteed empty
# tile looks nice and symmetrical.
self.hex_grid.try_generate_mines(size - 1, size - 1)
# hidden tiles (just like in an actual game) would
# make the preview worthless, so all tiles (including
# mines) need to be revealed
for pos in self.hex_grid.all_valid_coords():
self.hex_grid[pos].reveal()
# finally draw the field, just like in the
# actual game (see game_ui.py)
HexGridUIUtilities.draw_field(self, self.border())
def select_difficulty_clicked(self) -> None:
size = self.view.game_size_slider.get()
mine_count = self.view.mine_count_slider.get()
self.game_ui.hex_grid.size = size
self.game_ui.hex_grid.mine_count = mine_count
self.on_window_close()
def on_window_close(self) -> None:
self.game_ui.hex_grid.restart_game()
self.game_ui.window.focus_set()
self.game_ui.draw_field()
self.view.close_window()