Exemple #1
0
def main(input_file):

    data = []
    floor = HexGrid('W')
    dir_pat = re.compile(r"e|se|sw|w|nw|ne")

    with open(input_file, "r") as fp:
        data = fp.read().split('\n')[:-1]

    for t in data:

        print(t)
        dirs = dir_pat.findall(t)

        cur = (0, 0, 0)

        for d in dirs:
            cur = floor.mv_adj(cur, d)
            #print(f"{d}: {cur}")

        flip = floor.flip(cur, 'W', 'B')

        print(f"Flipped {cur} to {flip}")

    print(f"The number of black tiles in the floor is {floor.count('B')}")
Exemple #2
0
    def __init__(self, difficulty: Difficulty) -> None:
        self.window = Tk() # create application window
        self.window.title('HexSweeper')
        # default width & height
        self.window.geometry(f'{800}x{615}')
        self.canvas = Canvas(self.window, bg='white')
        # fill entire window with canvas
        # "fill='both'" allows the canvas to stretch
        # in both x and y direction
        self.canvas.pack(expand=1, fill='both')

        # initialized with irrelevant values before
        # it is properly initialised in the start_new_game
        # method, which calls .restart_game() with correct size
        # and mine count
        self.hex_grid = HexGrid(2, 1)

        # these instance variables are initialised properly in
        # HexGridUIUtilities.draw_field
        self.apothem: float = 0
        self.hshift: float = 0
        self.start_new_game(difficulty)

        self.ui_elements: List[Widget] = [self.canvas]

        self.canvas.bind('<Button-1>', self.on_click)
        # both <Button-2> and <Button-3> need to be bound for OS X and
        # Linux support (due to Tkinter compatibility issues)
        self.canvas.bind('<Button-2>', self.on_secondary_click)
        self.canvas.bind('<Button-3>', self.on_secondary_click)
        self.window.bind('<Configure>', self.on_window_resize)

        self.init_menubar()

        self.window.mainloop()
Exemple #3
0
    def test_error_check_index(self):
        height = 3
        int_cases = list(range(0, height))
        {HexGrid._error_check_index(index, height) for index in int_cases}
        slice_cases = list()
        for start in list(range(0, height)) + [None]:
            slice_cases.append(slice(start))
            for stop in list(range(start or 0, height)) + [None]:
                for step in [None, 1]:
                    slice_cases.append(slice(start, stop, step))
        {HexGrid._error_check_index(index, height) for index in slice_cases}

        index_error_cases = [
            (-1, height),
            (3, height),
            (slice(4), height),
            (slice(-1, 2), height),
            (slice(1, 4), height),
            (slice(1, 2, 2), height),
        ]
        type_error_cases = ["1", 1.5, (1, 2)]

        def assertRaises(index, height, error):
            with self.assertRaises(
                    error,
                    msg=f"{error} not raised index={index}, height={height}"):
                HexGrid._error_check_index(index, height)

        {
            assertRaises(index, height, IndexError)
            for index, height in index_error_cases
        }
        {assertRaises(index, 10, TypeError) for index in index_error_cases}
    def __init__(self, difficulty):
        self.window = Tk() # create application window
        self.window.title('HexSweeper')
        self.window.geometry('{}x{}'.format(800, 615)) # default width & height
        self.canvas = Canvas(self.window, bg = 'white')
        # fill entire window with canvas
        # fill = 'both' allows canvas to stretch in both x and y direction
        self.canvas.pack(expand = 1, fill = 'both')

        self.border = 20

        # initialized with irrelevant values before
        # it is properly initialised in the start_new_game
        # method, which calls .restart_game() with correct size
        # and mine count
        self.hex_grid = HexGrid(2, 1)
        self.start_new_game(difficulty)

        self.canvas.bind('<Button-1>', self.on_click)
        # both <Button-2> and <Button-3> need to be bound for OS X and
        # Linux support (due to Tkinter compatibility issues)
        self.canvas.bind('<Button-2>', self.on_secondary_click)
        self.canvas.bind('<Button-3>', self.on_secondary_click)
        self.window.bind('<Configure>', self.on_window_resize)

        self.init_menubar()

        self.window.mainloop()
    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()
Exemple #6
0
 def test_from_order(self):
     self.assertGridAndListListEqual(HexGrid.from_order(1), expected=[[0]])
     self.assertGridAndListListEqual(HexGrid.from_order(2),
                                     expected=[[0, 0], [0, 0, 0], [0, 0]])
     self.assertGridAndListListEqual(HexGrid.from_order(3),
                                     expected=[[0, 0, 0], [0, 0, 0, 0],
                                               [0, 0, 0, 0, 0],
                                               [0, 0, 0, 0], [0, 0, 0]])
Exemple #7
0
    def test_getitem(self):
        grid = [
            [4, 3, 2],
            [2, 1, 3, 1],
            [3, 1, 5, 2, 4],
            [2, 1, 4, 3],
            [2, 3, 1],
        ]
        hexgrid = HexGrid.from_grid(grid)

        actual = hexgrid[0]
        expected = grid[0]
        self.assertListEqual(actual, expected)

        actual = hexgrid[0:2]
        expected = grid[0:2]
        self.assertListEqual(actual, expected)

        actual = hexgrid[0, 0]
        expected = grid[0][0]
        self.assertEqual(actual, expected)

        actual = hexgrid[0, 0:2]
        expected = grid[0][0:2]
        self.assertListEqual(actual, expected)

        actual = hexgrid[0:2, 0:2]
        expected = [[4, 3], [2, 1]]
        self.assertListEqual(actual, expected)

        actual = hexgrid["list", 2, 2]
        expected = [
            [1, 3],
            [1, 5, 2],
            [1, 4],
        ]
        self.assertListEqual(actual, expected)

        actual = hexgrid["list", 2, 2, 2]
        expected = grid
        self.assertListEqual(actual, expected)

        actual = hexgrid["grid", 2, 2]
        self.assertIsGrid(actual)
        expected_grid = [
            [1, 3],
            [1, 5, 2],
            [1, 4],
        ]
        expected = HexGrid.from_grid(expected_grid)
        self.assertGridEqual(actual, expected)

        actual = hexgrid["grid", 2, 2, 2]
        self.assertIsGrid(actual)
        expected = HexGrid.from_grid(grid)
        self.assertGridEqual(actual, expected)
Exemple #8
0
def iterate(grid):
    newgrid = HexGrid()
    for c, v in grid.grid.items():
        x, y = c

        # check black tiles
        if v == "b":
            n = grid.neighbors(x, y, "w")
            if n.get("b", 0) in [0, 3, 4, 5, 6]:
                newgrid.set(x, y, "w")
            else:
                newgrid.set(x, y, "b")

        # check white tiles. we have to check the neighbors of all of the
        # black tiles, because some white tiles won't be explicitly stored in
        # the HexGrid
        for dx, dy in HexGrid.deltas:
            nx = x + dx
            ny = y + dy
            # we always set the tile, so if it's set then it's already done
            if grid.get(nx, ny, "w") == "w" and not newgrid.get(nx, ny):
                n = grid.neighbors(nx, ny, "w")
                newgrid.set(nx, ny, "b" if n.get("b") == 2 else "w")

    return newgrid
def main():

    grid = HexGrid(7, lambda: set(string.ascii_uppercase))

    regexes = []

    print("Compiling regex objects...")
    for i, regexstr in enumerate(definitions[:13]):
        print("{0:25}".format(regexstr), end="")
        cells = list(grid.traverse_l2r(i))
        regex = NFSM(regexstr, len(cells), string.ascii_uppercase)
        regexes.append((regexstr, regex, cells))
        print("  ...done")
    for i, regexstr in enumerate(definitions[13:26]):
        print("{0:25}".format(regexstr), end="")
        cells = list(grid.traverse_ur2ll(i))
        regex = NFSM(regexstr, len(cells), string.ascii_uppercase)
        regexes.append((regexstr, regex, cells))
        print("  ...done")
    for i, regexstr in enumerate(definitions[26:]):
        print("{0:25}".format(regexstr), end="")
        cells = list(grid.traverse_lr2ul(i))
        regex = NFSM(regexstr, len(cells), string.ascii_uppercase)
        regexes.append((regexstr, regex, cells))
        print("  ...done")

    finished = False
    iteration = 0
    while not finished:
        # Step 1: go and apply board constraints to the regexes
        for _, r, cells in regexes:
            for i, cell in enumerate(cells):
                r.constrain_slot(i, cell)

        # Step 2: go and apply regex constraints to the board
        # finished will be set back to false if at least one cell changed
        finished = True
        for regexstr, r, cells in regexes:
            for i, cell in enumerate(cells):
                oldcell = cell.copy()
                cell &= r.peek_slot(i)
                if oldcell != cell:
                    finished = False

        # Step 3: print progress
        iteration += 1
        print("\nIteration {0}".format(iteration))
        for regexstr, _, cells in regexes:
            print("{0:25} {1}".format(regexstr, "".join(("".join(c) if len(c)==1 else "_") for c in cells)))
Exemple #10
0
 def testHexGrid(self):
     hg = HexGrid(0.001)
     head, tail = os.path.split(os.path.abspath(__file__))
     head, tail = os.path.split(head)
     filename = os.path.join(head, 'resources', 'hex.csv')
     with open(filename, 'rb') as csvfile:
         reader = csv.reader(csvfile)
         for row in reader:
             px = float(row[0])
             py = float(row[1])
             er = long(row[2])
             ec = long(row[3])
             rr, rc = hg.xy2rc(px, py)
             self.assertEquals(er, rr)
             self.assertEquals(ec, rc)
    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)
Exemple #12
0
    def test_error_check_getitem(self):
        hexgrid = HexGrid.from_order(3)
        correct_cases = [
            (1, ("index", 1)),
            (slice(0, 2), ("index", slice(0, 2))),
            ((1, ), ("index", 1)),
            ((slice(0, 2), 0), ("indices", slice(0, 2), 0)),
            ((1, slice(0, 2)), ("indices", 1, slice(0, 2))),
            ((slice(0, 2), slice(0, 2)), ("indices", slice(0, 2), slice(0,
                                                                        2))),
            (("list", 0, 2), ("list", 0, 2, 1)),
            (("list", 0, 2, 1), ("list", 0, 2, 1)),
            (("LIST", 0, 2), ("list", 0, 2, 1)),
            (("grid", 0, 2), ("grid", 0, 2, 1)),
            (("grid", 0, 2, 1), ("grid", 0, 2, 1)),
            (("GRID", 0, 2), ("grid", 0, 2, 1)),
        ]
        for index, expected in correct_cases:
            actual = hexgrid._error_check_getitem(index)
            self.assertTupleEqual(actual, expected)

        incorrect_cases = [
            ((1, 0, 2), TypeError),
            (("lists", 0, 2), ValueError),
            (("list", 0, 2, 1, 2), TypeError),
        ]

        def assertRaises(index, error):
            with self.assertRaises(error,
                                   msg=f"{error} not raised index={index}"):
                hexgrid._error_check_getitem(index)

        {assertRaises(index, error) for index, error in incorrect_cases}
Exemple #13
0
    def __init__(self, difficulty):
        self.window = Tk() # create application window
        self.window.title('HexSweeper')
        # default width & height
        self.window.geometry('{}x{}'.format(800, 615))
        self.canvas = Canvas(self.window, bg='white')
        # fill entire window with canvas
        # "fill='both'" allows the canvas to stretch
        # in both x and y direction
        self.canvas.pack(expand=1, fill='both')

        # initialized with irrelevant values before
        # it is properly initialised in the start_new_game
        # method, which calls .restart_game() with correct size
        # and mine count
        self.hex_grid = HexGrid(2, 1)

        # these instance variables are initialised properly in
        # HexGridUIUtilities.draw_field
        self.apothem = 0
        self.hshift = 0
        self.start_new_game(difficulty)

        self.ui_elements = [self.canvas]

        self.canvas.bind('<Button-1>', self.on_click)
        # both <Button-2> and <Button-3> need to be bound for OS X and
        # Linux support (due to Tkinter compatibility issues)
        self.canvas.bind('<Button-2>', self.on_secondary_click)
        self.canvas.bind('<Button-3>', self.on_secondary_click)
        self.window.bind('<Configure>', self.on_window_resize)

        self.init_menubar()

        self.window.mainloop()
Exemple #14
0
 def test_copy(self):
     expected = HexGrid.from_order(3)
     with self.assertRaises(AssertionError):
         self.assertGridIsNotGrid(expected, expected)
     actual = expected.copy()
     self.assertGridEqual(actual, expected)
     self.assertGridIsNotGrid(actual, expected)
Exemple #15
0
 def draw_field(self):
     size = self.game_size_slider.get()
     mine_count = self.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)
Exemple #16
0
    def update_slider_range(self, event):
        """
        When the board size slider is adjusted, the mine count slider
        is readjusted to maintain the same ratio of empty tiles
        to tiles with a mine. As this the ratio involved the total
        tile count instead of the board size, it looks like the slider
        value shifts along slightly (try it out to see this), but that
        is only due to the quadratic relationship between board size
        and tile count (specifically, tile count = 3s^2 - 3s + 1).
        The maximum value for the mine count slider is also adjusted
        based on HexGrid.highest_possible_mine_count_for_size().
        """
        new_size = self.game_size_slider.get()
        last_mine_count = self.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.mine_count_slider.config(to=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.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.game_size_slider.get()

        # the field preview also needs to be
        # redrawn after all these adjustments
        self.draw_field()
Exemple #17
0
 def test_iter(self):
     expected = [
         ((0, 0), 0),
         ((0, 1), 0),
         ((1, 0), 0),
         ((1, 1), 0),
         ((1, 2), 0),
         ((2, 0), 0),
         ((2, 1), 0),
     ]
     actual = list(HexGrid.from_order(2))
     self.assertListEqual(actual, expected)
Exemple #18
0
 def test_index_generator(self):
     expected = [
         (0, 0),
         (0, 1),
         (1, 0),
         (1, 1),
         (1, 2),
         (2, 0),
         (2, 1),
     ]
     actual = list(HexGrid.from_order(2).index_generator())
     self.assertListEqual(actual, expected)
Exemple #19
0
    def test_str_from_index_or_slice(self):
        test_cases = [
            (HexGrid._str_from_index_or_slice(1), "1"),
            (HexGrid._str_from_index_or_slice(slice(1, None)), "1::"),
            (HexGrid._str_from_index_or_slice(slice(3)), ":3:"),
            (HexGrid._str_from_index_or_slice(slice(0, 3)), "0:3:"),
            (HexGrid._str_from_index_or_slice(slice(1, 3, 1)), "1:3:1"),
            (HexGrid._str_from_index_or_slice(slice(None, None, 2)), "::2"),
        ]
        for actual, expected in test_cases:
            self.assertIsInstance(actual, str)
            self.assertEqual(actual, expected)

        with self.assertRaises(TypeError):
            HexGrid._str_from_index_or_slice("1")
Exemple #20
0
def main(input_file):

	data = []
	floor = HexGrid('W')
	dir_pat = re.compile(r"e|se|sw|w|nw|ne")

	with open(input_file, "r") as fp:
		data = fp.read().split('\n')[:-1]

	#Initial floor pattern
	for t in data:

		dirs = dir_pat.findall(t)
		cur = (0,0,0)

		for d in dirs:
			cur = floor.mv_adj(cur,d)

		flip = floor.flip(cur, 'W', 'B')

	for i in range( 100):

		print(f"Iteration {i}: {floor.count('B')} black tiles")
		flippers = []

		for h in sorted(floor):

			count = floor.count_adj(h, 'B')

			if   (floor[h] == 'B' and count not in (1,2)) or (floor[h] == 'W' and count == 2):
				flippers.append(h)

			elif floor[h] != 'B' and count == 0:
				floor.pop(h)


		for f in flippers:
			floor.flip(f, 'B', 'W')
	
	print(f"After 100 days there are {floor.count('B')} black tiles")
Exemple #21
0
    def draw_field(ui):
        """
        Draw a complete hexagonal grid.
        ui: as for HexGridUIUtilities.draw_hexagon.
            This class has all members required for drawing a hexgrid,
            so no additional parameters are required
        """
        # start by clearing any previously drawn hexgrid, which would
        # otherwise contribute to a deccrease in performance
        ui.canvas.delete('all')

        # in case the window was resized, ui.apothem and ui.hshift
        # are invalidated each time the hexgrid is drawn.
        # this calculation is quite fast, so this is not a performance issue.
        (ui.apothem, ui.hshift) = HexGrid.apothem_and_hshift_for_size(
            ui.canvas.winfo_width(), ui.canvas.winfo_height(), ui.border,
            ui.hex_grid.size)

        # now draw every hexagonal tile individually
        for pos in ui.hex_grid.all_valid_coords():
            # field x, field y (in the grid coordinate system)
            (fx, fy) = pos
            # now converted to screen x, screen y
            # (in the screen coordinate system)
            (sx, sy) = ui.hex_grid.game_position_to_screen_coordinates(
                fx, fy, ui.apothem)
            # correct for borders and centering in
            # the canvas as calculated above
            sx += ui.border + ui.hshift
            sy += ui.border
            # the HexGrid class implements Python's array subscripting operator
            # for (x, y) tuples.
            tile = ui.hex_grid[pos]
            # the nested brackets for sx and sy are
            # necessary to wrap them in a tuple,
            # empty colour string is transparent (outline)
            HexGridUIUtilities.draw_hexagon(ui, (sx, sy), (fx, fy),
                                            tile.color())
            # tile.text() returns None if the tile should not contain any text,
            # which evaluates to False in a boolean expression
            if tile.text():
                # fill is the text colour; the background
                # colour is transparent by default
                ui.canvas.create_text(sx, sy, text=tile.text(), fill="black")
 def setUp(self):
     self.g = HexGrid(7, list)
Exemple #23
0
class GameUI:
    """
    This class is in charge of the main Minesweeper window.
    It owns a HexGrid instance that manages game state,
    it initialises Tkinter and it forwards mouse
    events to the HexGrid instance.
    """
    def __init__(self, difficulty):
        self.window = Tk() # create application window
        self.window.title('HexSweeper')
        # default width & height
        self.window.geometry('{}x{}'.format(800, 615))
        self.canvas = Canvas(self.window, bg='white')
        # fill entire window with canvas
        # "fill='both'" allows the canvas to stretch
        # in both x and y direction
        self.canvas.pack(expand=1, fill='both')

        # initialized with irrelevant values before
        # it is properly initialised in the start_new_game
        # method, which calls .restart_game() with correct size
        # and mine count
        self.hex_grid = HexGrid(2, 1)

        # these instance variables are initialised properly in
        # HexGridUIUtilities.draw_field
        self.apothem = 0
        self.hshift = 0
        self.start_new_game(difficulty)

        self.ui_elements = [self.canvas]

        self.canvas.bind('<Button-1>', self.on_click)
        # both <Button-2> and <Button-3> need to be bound for OS X and
        # Linux support (due to Tkinter compatibility issues)
        self.canvas.bind('<Button-2>', self.on_secondary_click)
        self.canvas.bind('<Button-3>', self.on_secondary_click)
        self.window.bind('<Configure>', self.on_window_resize)

        self.init_menubar()

        self.window.mainloop()

    def init_menubar(self):
        """
        Creates the menubar. Called once on startup. The rendering
        of the menubar is operating-system dependent (Windows
        renders it inside the app window, OS X renders it in
        the OS menubar).
        """
        menubar = Menu(self.window)

        game_menu = Menu(menubar, tearoff=0)
        game_menu.add_command(
            label='New (Easy)',
            command=lambda: self.start_new_game('easy'))
        game_menu.add_command(
            label='New (Intermediate)',
            command=lambda: self.start_new_game('intermediate'))
        game_menu.add_command(
            label='New (Advanced)',
            command=lambda: self.start_new_game('advanced'))
        game_menu.add_command(
            label='New (Custom)',
            command=lambda: self.start_new_game('custom'))
        menubar.add_cascade(label='Game', menu=game_menu)

        # finally add the menubar to the root window
        self.window.config(menu=menubar)

        self.ui_elements += [menubar, game_menu]

    def start_new_game(self, difficulty):
        """ Start a new game with the selected difficulty """
        if difficulty == 'easy':
            self.hex_grid.restart_game(5, 8)
            self.draw_field()
        elif difficulty == 'intermediate':
            self.hex_grid.restart_game(10, 45)
            self.draw_field()
        elif difficulty == 'advanced':
            self.hex_grid.restart_game(13, 80)
            self.draw_field()
        else: # custom difficulty
            # This call takes care of restarting the game
            # with selected board size and mine count,
            # and also redraws the Game UI after difficulty
            # has been set.
            ChooseDifficultyUI(self)

    @staticmethod
    def border():
        """ Return fixed border size (on all sides) """
        return 20

    def draw_field(self):
        """ Redraw the game on the Canvas element """
        HexGridUIUtilities.draw_field(self, self.border())

    def on_click(self, event):
        """ Primary click event handler """
        self.hex_grid.primary_click(
            (event.x - self.border() - self.hshift,
             event.y - self.border()),
            self.apothem,
            self.draw_field,
            messagebox.showinfo)

    def on_secondary_click(self, event):
        """ Secondary click event handler """
        self.hex_grid.secondary_click(
            (event.x - self.border() - self.hshift,
             event.y - self.border()),
            self.apothem,
            self.draw_field,
            messagebox.showinfo)

    def on_window_resize(self, _):
        """ Handler for window resize. Redraws the board. """
        # recalculates apothem and hshift values automatically
        # for the new window size
        self.draw_field()
class TestHexGrid(unittest.TestCase):
    def setUp(self):
        self.g = HexGrid(7, list)

    def test_sidelen(self):
        self.assertEqual(13, len(self.g.leftedges))
        self.assertEqual(13, len(self.g.uredges))
        self.assertEqual(13, len(self.g.lredges))

    def test_traversal_len(self):
        lengths = [7,8,9,10,11,12,13,12,11,10,9,8,7]

        for i in range(13):
            self.assertEqual(lengths[i],
                    len(list(self.g.traverse_l2r(i))))
            self.assertEqual(lengths[i],
                    len(list(self.g.traverse_lr2ul(i))))
            self.assertEqual(lengths[i],
                    len(list(self.g.traverse_ur2ll(i))))

    def _fill_by_row(self):
        for i in range(13):
            for cell in self.g.traverse_l2r(i):
                cell.append(i)
    def _fill_by_diag(self):
        for i in range(13):
            for cell in self.g.traverse_ur2ll(i):
                cell.append(i)

    def test_row_storage(self):
        # Fill each row with its index
        self._fill_by_row()

        # Now traverse ur2ll and make sure the order goes descending from
        # 12 to 0
        for i in range(7):
            for cell, expected in zip(self.g.traverse_ur2ll(i),
                    range(12,-1,-1)):
                self.assertEqual([expected], cell)

        # From 7-12 the starting number is reduced
        for i in range(7,13):
            for cell, expected in zip(self.g.traverse_ur2ll(i),
                    range(12-(i-6), -1, -1)):
                self.assertEqual([expected], cell)

        # Now traverse lr2ul and make sure the values increase. For the
        # first 7 we expect it to start not at 0
        for i in range(0,7):
            for cell, expected in zip(self.g.traverse_lr2ul(i),
                    range(6-i, 13)):
                self.assertEqual([expected], cell)

        for i in range(7,13):
            for cell, expected in zip(self.g.traverse_lr2ul(i),
                    range(0, 13)):
                self.assertEqual([expected], cell)

    def test_total_cells(self):
        """Tests that exactly the expected number of cells exist and are
        reachable with the three directions of traversal"""
        all_items = set()
        for i in range(13):
            for cell in self.g.traverse_l2r(i):
                all_items.add(id(cell))
        self.assertEqual(127, len(all_items))

        all_items = set()
        for i in range(13):
            for cell in self.g.traverse_lr2ul(i):
                all_items.add(id(cell))
        self.assertEqual(127, len(all_items))

        all_items = set()
        for i in range(13):
            for cell in self.g.traverse_ur2ll(i):
                all_items.add(id(cell))

        self.assertEqual(127, len(all_items))

    def _get_all_cells(self):
        all_cells = []
        for cell in self.g.leftedges:
            while cell is not None:
                all_cells.append(cell)
                cell = cell.right
        return all_cells

    def test_node_single_links(self):
        """Test and make sure each of the 6 links, if they go to a node,
        the appropriate link from that node go back.

        """
        # First get us a list of every cell object (not cell value)
        all_cells = self._get_all_cells()

        self._fill_by_row() # To help in debugging

        # Now do the test
        for cell in all_cells:
            if cell.right:
                self.assertIs(cell, cell.right.left)
            if cell.left:
                self.assertIs(cell, cell.left.right)
            if cell.ul:
                self.assertIs(cell, cell.ul.lr)
            if cell.ur:
                self.assertIs(cell, cell.ur.ll)
            if cell.lr:
                self.assertIs(cell, cell.lr.ul)
            if cell.ll:
                self.assertIs(cell, cell.ll.ur)

    def test_node_link_circle(self):
        self._fill_by_row() # To help in debugging
        self._fill_by_diag()
        for cell in self._get_all_cells():

            # ur → lr → left
            if cell.ur and cell.ur.lr:
                self.assertIs(cell, cell.ur.lr.left)

            # right → ll → ul
            if cell.right and cell.right.ll:
                self.assertIs(cell, cell.right.ll.ul)

            # ll → ul → right
            if cell.ll and cell.ll.ul:
                self.assertIs(cell, cell.ll.ul.right)

            # left → ur → lr
            if cell.left and cell.left.ur:
                self.assertIs(cell, cell.left.ur.lr)

            # ul → right → ll
            if cell.ul and cell.ul.right:
                self.assertIs(cell, cell.ul.right.ll)

            # Now for counterclockwise circles

            # right → ul → ll
            if cell.right and cell.right.ul:
                self.assertIs(cell, cell.right.ul.ll)

            # ur → left → lr
            if cell.ur and cell.ur.left:
                self.assertIs(cell, cell.ur.left.lr)

            # ul → ll → right
            if cell.ul and cell.ul.ll:
                self.assertIs(cell, cell.ul.ll.right)

            # left → lr → ur
            if cell.left and cell.left.lr:
                self.assertIs(cell, cell.left.lr.ur)

            # ll → right → ul
            if cell.ll and cell.ll.right:
                self.assertIs(cell, cell.ll.right.ul)

            # lr → ur → left
            if cell.lr and cell.lr.ur:
                self.assertIs(cell, cell.lr.ur.left)
Exemple #25
0
 def test_index_generator(self):
     expected = [0] * 7
     actual = list(HexGrid.from_order(2).value_generator())
     self.assertListEqual(actual, expected)
Exemple #26
0
class ChooseDifficultyUI:
    def __init__(self, game_ui):
        self.game_ui = game_ui
        # 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')  # size maximises space in the window
        self.window.bind('<Configure>', lambda event: self.draw_field())
        self.window.protocol("WM_DELETE_WINDOW", self.on_window_close)

        # 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
        self.game_size_slider = Scale(
            self.window,
            # from_ because from is a Python keyword
            from_=2,
            to=15,
            orient=HORIZONTAL,
            command=lambda event: self.update_slider_range(event)
        )  # default slider resolution/accuracy is 1
        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=lambda event: self.draw_field())
        self.mine_count_slider.grid(row=1, column=1, sticky=E + W)

        # 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.game_size_slider.set(self.game_ui.hex_grid.size)
        self.last_size = self.game_ui.hex_grid.size
        self.mine_count_slider.config(
            to=HexGrid.highest_possible_mine_count_for_size(
                self.game_ui.hex_grid.size))
        self.mine_count_slider.set(self.game_ui.hex_grid.mine_count)

        Button(self.window,
               text='Select difficulty',
               command=self.select_difficulty_clicked).grid(row=2,
                                                            column=0,
                                                            columnspan=2)

        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)

        self.border = 5
        self.draw_field()

        # put self.window in the foreground and
        # make self.game_ui.window (the root window) inaccessible
        self.window.transient(self.game_ui.window)
        self.window.grab_set()
        self.game_ui.window.wait_window(self.window)

    def update_slider_range(self, event):
        """
        When the board size slider is adjusted, the mine count slider
        is readjusted to maintain the same ratio of empty tiles
        to tiles with a mine. As this the ratio involved the total
        tile count instead of the board size, it looks like the slider
        value shifts along slightly (try it out to see this), but that
        is only due to the quadratic relationship between board size
        and tile count (specifically, tile count = 3s^2 - 3s + 1).
        The maximum value for the mine count slider is also adjusted
        based on HexGrid.highest_possible_mine_count_for_size().
        """
        new_size = self.game_size_slider.get()
        last_mine_count = self.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.mine_count_slider.config(to=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.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.game_size_slider.get()

        # the field preview also needs to be
        # redrawn after all these adjustments
        self.draw_field()

    def draw_field(self):
        size = self.game_size_slider.get()
        mine_count = self.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)

    def select_difficulty_clicked(self):
        """
        Called when the user clicks on the "Select difficulty"
        button. Selected board size and mine count are saved
        in the game_ui.hex_grid object and the window on_close event
        handler is called, which takes care of closing the window,
        restoring focus to the actual game and redrawing the game.
        """
        size = self.game_size_slider.get()
        mine_count = self.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):
        """
        Called when the user either closes the window using the
        red 'X' or when the user clicks on the "Select difficulty"
        button. Because we don't know how the user got to this
        method, we can't save the board size/mine count options
        (if required, they will have already been saved). So we
        just restart and redraw the main game and close the window.
        """
        self.game_ui.hex_grid.restart_game()
        self.game_ui.window.focus_set()
        self.game_ui.draw_field()
        self.window.destroy()
Exemple #27
0
        hexagon = grid.ret_hex_cube(gon[0], gon[1], gon[2])
        hexagon.set_color('#000000')


# init tk
root = Tk()

# create canvas
myCanvas = Canvas(root, bg="white", height=canvas_height, width=canvas_width)

# create frame to put control buttons onto
frame = Frame(root, bg='grey', width=canvas_width, height=canvas_height / 5)
frame.pack(fill='x')

# draw grid
grid = HexGrid(myCanvas, 6, 'pointy', 15)
grid.draw_grid()

myCanvas.pack()

# create variables for hex distance
hex_dist_a = grid.ret_hex_cube(0, 0, 0)
hex_dist_b = grid.ret_hex_cube(0, 0, 0)

# create frame to put control buttons onto
frame = Frame(root, bg='grey', width=canvas_width, height=canvas_height / 5)
frame.pack(fill='x')
rot_button = Button(frame, text="orientation", command=grid.change_orientation)
rot_button.pack(side='bottom', padx=10)
button = Button(frame, text="Select color", command=choose_color)
button.pack()
Exemple #28
0
    def __init__(self, game_ui):
        self.game_ui = game_ui
        # 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')  # size maximises space in the window
        self.window.bind('<Configure>', lambda event: self.draw_field())
        self.window.protocol("WM_DELETE_WINDOW", self.on_window_close)

        # 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
        self.game_size_slider = Scale(
            self.window,
            # from_ because from is a Python keyword
            from_=2,
            to=15,
            orient=HORIZONTAL,
            command=lambda event: self.update_slider_range(event)
        )  # default slider resolution/accuracy is 1
        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=lambda event: self.draw_field())
        self.mine_count_slider.grid(row=1, column=1, sticky=E + W)

        # 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.game_size_slider.set(self.game_ui.hex_grid.size)
        self.last_size = self.game_ui.hex_grid.size
        self.mine_count_slider.config(
            to=HexGrid.highest_possible_mine_count_for_size(
                self.game_ui.hex_grid.size))
        self.mine_count_slider.set(self.game_ui.hex_grid.mine_count)

        Button(self.window,
               text='Select difficulty',
               command=self.select_difficulty_clicked).grid(row=2,
                                                            column=0,
                                                            columnspan=2)

        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)

        self.border = 5
        self.draw_field()

        # put self.window in the foreground and
        # make self.game_ui.window (the root window) inaccessible
        self.window.transient(self.game_ui.window)
        self.window.grab_set()
        self.game_ui.window.wait_window(self.window)
Exemple #29
0
 def test_eq(self):
     actual = HexGrid.from_order(0)
     expected = HexGrid.from_order(0)
     self.assertEqual(actual, expected)
Exemple #30
0
 def test_from_grid(self):
     actual = HexGrid.from_grid([[0]])
     expected = HexGrid.from_order(1)
     self.assertGridEqual(actual, expected)
Exemple #31
0
class GameUI:
 
    def __init__(self, difficulty: Difficulty) -> None:
        self.window = Tk() # create application window
        self.window.title('HexSweeper')
        # default width & height
        self.window.geometry(f'{800}x{615}')
        self.canvas = Canvas(self.window, bg='white')
        # fill entire window with canvas
        # "fill='both'" allows the canvas to stretch
        # in both x and y direction
        self.canvas.pack(expand=1, fill='both')

        # initialized with irrelevant values before
        # it is properly initialised in the start_new_game
        # method, which calls .restart_game() with correct size
        # and mine count
        self.hex_grid = HexGrid(2, 1)

        # these instance variables are initialised properly in
        # HexGridUIUtilities.draw_field
        self.apothem: float = 0
        self.hshift: float = 0
        self.start_new_game(difficulty)

        self.ui_elements: List[Widget] = [self.canvas]

        self.canvas.bind('<Button-1>', self.on_click)
        # both <Button-2> and <Button-3> need to be bound for OS X and
        # Linux support (due to Tkinter compatibility issues)
        self.canvas.bind('<Button-2>', self.on_secondary_click)
        self.canvas.bind('<Button-3>', self.on_secondary_click)
        self.window.bind('<Configure>', self.on_window_resize)

        self.init_menubar()

        self.window.mainloop()

    def init_menubar(self) -> None:
       
        menubar = Menu(self.window)

        game_menu = Menu(menubar, tearoff=0)
        game_menu.add_command(
            label='New (Easy)',
            command=lambda: self.start_new_game(Difficulty.EASY))
        game_menu.add_command(
            label='New (Intermediate)',
            command=lambda: self.start_new_game(
                Difficulty.INTERMEDIATE))
        game_menu.add_command(
            label='New (Advanced)',
            command=lambda: self.start_new_game(Difficulty.ADVANCED))
        game_menu.add_command(
            label='New (Custom)',
            command=lambda: self.start_new_game(Difficulty.CUSTOM))
        menubar.add_cascade(label='Game', menu=game_menu)

        # finally add the menubar to the root window
        self.window.config(menu=menubar)

        self.ui_elements += [menubar, game_menu]

    def start_new_game(self, difficulty: Difficulty) -> None:
        if difficulty == Difficulty.EASY:
            self.hex_grid.restart_game(5, 8)
            self.draw_field()
        elif difficulty == Difficulty.INTERMEDIATE:
            self.hex_grid.restart_game(10, 45)
            self.draw_field()
        elif difficulty == Difficulty.ADVANCED:
            self.hex_grid.restart_game(13, 80)
            self.draw_field()
        else: # custom difficulty
            # This call takes care of restarting the game
            # with selected board size and mine count,
            # and also redraws the Game UI after difficulty
            # has been set.
            ChooseDifficultyUI(self)

    @staticmethod
    def border() -> float:
        return 20

    def draw_field(self) -> None:
        HexGridUIUtilities.draw_field(self, self.border())

    @staticmethod
    def show_alert(title: str, msg: str) -> None:
        messagebox.showinfo(title, msg)

    def on_click(self, event: Any) -> None:
        self.hex_grid.primary_click(
            (event.x - self.border() - self.hshift,
             event.y - self.border()),
            self.apothem,
            self.draw_field,
            self.show_alert)

    def on_secondary_click(self, event: Any) -> None:
        self.hex_grid.secondary_click(
            (event.x - self.border() - self.hshift,
             event.y - self.border()),
            self.apothem,
            self.draw_field,
            self.show_alert)

    def on_window_resize(self, _: Event) -> None:
        # recalculates apothem and hshift values automatically
        # for the new window size
        self.draw_field()
class GameUI:
    """
    This class is in charge of the main Minesweeper window.
    It owns a HexGrid instance that manages game state,
    it initialises Tkinter and it forwards mouse
    events to the HexGrid instance.
    """
    def __init__(self, difficulty):
        self.window = Tk() # create application window
        self.window.title('HexSweeper')
        self.window.geometry('{}x{}'.format(800, 615)) # default width & height
        self.canvas = Canvas(self.window, bg = 'white')
        # fill entire window with canvas
        # fill = 'both' allows canvas to stretch in both x and y direction
        self.canvas.pack(expand = 1, fill = 'both')

        self.border = 20

        # initialized with irrelevant values before
        # it is properly initialised in the start_new_game
        # method, which calls .restart_game() with correct size
        # and mine count
        self.hex_grid = HexGrid(2, 1)
        self.start_new_game(difficulty)

        self.canvas.bind('<Button-1>', self.on_click)
        # both <Button-2> and <Button-3> need to be bound for OS X and
        # Linux support (due to Tkinter compatibility issues)
        self.canvas.bind('<Button-2>', self.on_secondary_click)
        self.canvas.bind('<Button-3>', self.on_secondary_click)
        self.window.bind('<Configure>', self.on_window_resize)

        self.init_menubar()

        self.window.mainloop()

    def init_menubar(self):
        """
        Creates the menubar. Called once on startup. The rendering
        of the menubar is operating-system dependent (Windows
        renders it inside the app window, OS X renders it in
        the OS menubar).
        """
        self.menubar = Menu(self.window)

        self.game_menu = Menu(self.menubar, tearoff = 0)
        self.game_menu.add_command(
            label = 'New (Easy)',
            command = lambda: self.start_new_game('easy'))
        self.game_menu.add_command(
            label = 'New (Intermediate)',
            command = lambda: self.start_new_game('intermediate'))
        self.game_menu.add_command(
            label = 'New (Advanced)',
            command = lambda: self.start_new_game('advanced'))
        self.game_menu.add_command(
            label = 'New (Custom)',
            command = lambda: self.start_new_game('custom'))
        self.menubar.add_cascade(label = 'Game', menu = self.game_menu)

        # finally add the menubar to the root window
        self.window.config(menu = self.menubar)

    def start_new_game(self, difficulty):
        if difficulty == 'easy':
            self.hex_grid.restart_game(5, 8)
            self.draw_field()
        elif difficulty == 'intermediate':
            self.hex_grid.restart_game(10, 45)
            self.draw_field()
        elif difficulty == 'advanced':
            self.hex_grid.restart_game(13, 80)
            self.draw_field()
        else: # custom difficulty
            # This call takes care of restarting the game
            # with selected board size and mine count,
            # and also redraws the Game UI after difficulty
            # has been set.
            ChooseDifficultyUI(self)

    def draw_field(self):
        HexGridUIUtilities.draw_field(self)

    def on_click(self, event):
        self.hex_grid.primary_click(
            event.x - self.border - self.hshift,
            event.y - self.border,
            self.apothem,
            self.draw_field,
            messagebox.showinfo)

    def on_secondary_click(self, event):
        self.hex_grid.secondary_click(
            event.x - self.border - self.hshift,
            event.y - self.border,
            self.apothem,
            self.draw_field,
            messagebox.showinfo)

    def on_window_resize(self, event):
        # recalculates apothem and hshift values automatically
        # for the new window size
        self.draw_field()
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()
Exemple #34
0
 def assertRaises(index, height, error):
     with self.assertRaises(
             error,
             msg=f"{error} not raised index={index}, height={height}"):
         HexGrid._error_check_index(index, height)
Exemple #35
0
 def test_len(self):
     actual = len(HexGrid.from_order(3))
     expected = 19
     self.assertEqual(actual, expected)