Skip to content

Commit

Permalink
Big commit to enable saving mouse events
Browse files Browse the repository at this point in the history
  • Loading branch information
LewisGaul committed Jun 20, 2020
1 parent f369e1a commit 17c9e85
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 44 deletions.
6 changes: 3 additions & 3 deletions minegauler/frontend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys

from PyQt5.QtCore import QTimer, pyqtRemoveInputHook
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QApplication, QWidget

from . import state
from .main_window import MinegaulerGUI
Expand All @@ -28,12 +28,12 @@ def init_app() -> None:
pyqtRemoveInputHook()


def run_app(gui: MinegaulerGUI) -> int:
def run_app(gui: QWidget) -> int:
"""
Run the GUI application.
:param gui:
The main PyQt GUI object.
The main GUI widget.
:return:
Exit code.
"""
Expand Down
18 changes: 13 additions & 5 deletions minegauler/frontend/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

__all__ = ("MinegaulerGUI",)

import json
import logging
import os
import textwrap
Expand Down Expand Up @@ -46,7 +47,7 @@
from ..core import api
from ..shared.highscores import HighscoreSettingsStruct, HighscoreStruct
from ..shared.types import (
CellContents_T,
CellContents,
CellImageType,
Coord_T,
Difficulty,
Expand All @@ -56,7 +57,7 @@
)
from ..shared.utils import GUIOptsStruct
from . import highscores, minefield, panel, state, utils
from .utils import FILES_DIR
from .utils import FILES_DIR, HIGHSCORES_DIR, save_highscore


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -250,15 +251,14 @@ def set_mines(self, mines: int) -> None:
self._state.mines = mines
self._diff_menu_actions[self._state.difficulty].setChecked(True)

def update_cells(self, cell_updates: Mapping[Coord_T, CellContents_T]) -> None:
def update_cells(self, cell_updates: Mapping[Coord_T, CellContents]) -> None:
"""
Called to indicate some cells have changed state.
:param cell_updates:
A mapping of cell coordinates to their new state.
"""
for c, state in cell_updates.items():
self._mf_widget.set_cell_image(c, state)
self._mf_widget.update_cells(cell_updates)

def update_game_state(self, game_state: GameState) -> None:
"""
Expand Down Expand Up @@ -591,6 +591,10 @@ def _handle_finished_game(self) -> None:
else:
if new_best:
self.open_highscores_window(highscore, new_best)
save_highscore(
self._state.current_game_state,
self._mf_widget.get_mouse_events(),
)

def _open_save_board_modal(self) -> None:
if not (
Expand Down Expand Up @@ -663,6 +667,10 @@ def _open_text_popup(self, title: str, file: PathLike):
win.show()
self._open_subwindows[title] = win

def _open_play_highscore_modal(self):
with open(HIGHSCORES_DIR / "B_3_True.mgh") as f:
data = json.load(f)

def get_gui_opts(self) -> GUIOptsStruct:
return GUIOptsStruct.from_structs(self._state, self._state.pending_game_state)

Expand Down
91 changes: 68 additions & 23 deletions minegauler/frontend/minefield.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
__all__ = ("MinefieldWidget",)

import logging
from typing import Dict, Optional, Set
import time
from typing import Dict, List, Mapping, Optional, Set

from PyQt5.QtCore import QSize, Qt, pyqtSignal
from PyQt5.QtGui import QImage, QMouseEvent, QPainter, QPixmap
Expand All @@ -22,7 +23,7 @@
from ..core import Board, api
from ..shared.types import CellContents, CellImageType, Coord_T
from .state import State
from .utils import IMG_DIR
from .utils import IMG_DIR, ClickEvent, MouseEvent, MouseMove


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -147,9 +148,16 @@ def __init__(
self._await_release_all_buttons = False
self._was_double_left_click = False
self._unflag_on_right_drag = False

# Set of coords for cells which are sunken.
self._sunken_cells: Set = set()

# Mouse tracking info, for simulating a played game.
self._enable_mouse_tracking: bool = True
self._mouse_tracking: List[MouseMove] = []
self._mouse_events: List[MouseEvent] = []
self._first_click_time: Optional[int] = None

self.reset()

@property
Expand Down Expand Up @@ -188,6 +196,11 @@ def mousePressEvent(self, event: QMouseEvent):
return

self._mouse_coord = coord = self._coord_from_event(event)
if not self._first_click_time:
self._first_click_time = time.time()
assert len(self._mouse_tracking) == 0
assert len(self._mouse_events) == 0
self._mouse_tracking.append(MouseMove(0, (event.x(), event.y())))

## Bothclick
if event.buttons() & Qt.LeftButton and event.buttons() & Qt.RightButton:
Expand Down Expand Up @@ -285,6 +298,7 @@ def left_button_down(self, coord: Coord_T) -> None:
Left mouse button was pressed (single click). Change display and call
callback functions as appropriate.
"""
self._track_mouse_event(ClickEvent.LEFT_DOWN, coord)
if self._state.drag_select:
self.at_risk_signal.emit()
self._ctrlr.select_cell(coord)
Expand All @@ -296,6 +310,7 @@ def left_button_double_down(self, coord: Coord_T) -> None:
Left button was double clicked. Call callback to remove any flags that
were on the cell.
"""
self._track_mouse_event(ClickEvent.DOUBLE_LEFT_DOWN, coord)
if type(self._board[coord]) is CellContents.Flag:
self._ctrlr.remove_cell_flags(coord)
else:
Expand All @@ -307,6 +322,7 @@ def left_button_move(self, coord: Coord_T) -> None:
Left mouse button was moved after a single click. Change display as
appropriate.
"""
self._track_mouse_event(ClickEvent.LEFT_MOVE, coord)
self._raise_all_sunken_cells()
self.no_risk_signal.emit()
if coord is not None:
Expand All @@ -316,6 +332,7 @@ def left_button_double_move(self, coord: Coord_T) -> None:
"""
Left mouse button moved after a double click.
"""
self._track_mouse_event(ClickEvent.DOUBLE_LEFT_MOVE, coord)
if self._state.drag_select:
self._ctrlr.remove_cell_flags(coord)

Expand All @@ -324,6 +341,7 @@ def left_button_release(self, coord: Coord_T) -> None:
Left mouse button was released. Change display and call callback
functions as appropriate.
"""
self._track_mouse_event(ClickEvent.LEFT_UP, coord)
self._raise_all_sunken_cells()
self.no_risk_signal.emit()
if not self._state.drag_select and coord is not None:
Expand All @@ -334,6 +352,7 @@ def right_button_down(self, coord: Coord_T) -> None:
Right mouse button was pressed. Change display and call callback
functions as appropriate.
"""
self._track_mouse_event(ClickEvent.RIGHT_DOWN, coord)
self._ctrlr.flag_cell(coord)
if self._board[coord] is CellContents.Unclicked:
self._unflag_on_right_drag = True
Expand All @@ -344,6 +363,7 @@ def right_button_move(self, coord: Coord_T) -> None:
"""
Right mouse button was moved. Change display as appropriate.
"""
self._track_mouse_event(ClickEvent.RIGHT_MOVE, coord)
if self._state.drag_select and coord is not None:
if self._unflag_on_right_drag:
self._ctrlr.remove_cell_flags(coord)
Expand All @@ -355,6 +375,7 @@ def both_buttons_down(self, coord: Coord_T) -> None:
Both left and right mouse buttons were pressed. Change display and call
callback functions as appropriate.
"""
self._track_mouse_event(ClickEvent.BOTH_DOWN, coord)
if not self._board[coord].is_mine_type():
for c in self._board.get_nbrs(coord, include_origin=True):
self._sink_unclicked_cell(c)
Expand All @@ -367,6 +388,7 @@ def both_buttons_move(self, coord: Coord_T) -> None:
Both left and right mouse buttons were moved. Change display as
appropriate.
"""
self._track_mouse_event(ClickEvent.BOTH_MOVE, coord)
self._raise_all_sunken_cells()
if not self._state.drag_select:
self.no_risk_signal.emit()
Expand All @@ -378,6 +400,7 @@ def first_of_both_buttons_release(self, coord: Coord_T) -> None:
One of the mouse buttons was released after both were pressed. Change
display and call callback functions as appropriate.
"""
self._track_mouse_event(ClickEvent.FIRST_OF_BOTH_UP, coord)
self._raise_all_sunken_cells()
if not self._state.drag_select:
self.no_risk_signal.emit()
Expand Down Expand Up @@ -417,7 +440,7 @@ def _sink_unclicked_cell(self, coord: Coord_T) -> None:
if self._state.game_status.finished():
return
if self._board[coord] is CellContents.Unclicked:
self.set_cell_image(coord, _SUNKEN_CELL)
self._set_cell_image(coord, _SUNKEN_CELL)
self._sunken_cells.add(coord)
if self._sunken_cells:
self.at_risk_signal.emit()
Expand All @@ -429,18 +452,9 @@ def _raise_all_sunken_cells(self) -> None:
while self._sunken_cells:
coord = self._sunken_cells.pop()
if self._board[coord] is CellContents.Unclicked:
self.set_cell_image(coord, _RAISED_CELL)

def reset(self) -> None:
"""Reset all cell images and other state for a new game."""
logger.info("Resetting minefield widget")
self._mouse_coord = None
self._both_mouse_buttons_pressed = False
self._await_release_all_buttons = True
for c in self._board.all_coords:
self.set_cell_image(c, CellContents.Unclicked)
self._set_cell_image(coord, _RAISED_CELL)

def set_cell_image(self, coord: Coord_T, state: CellContents) -> None:
def _set_cell_image(self, coord: Coord_T, state: CellContents) -> None:
"""
Set the image of a cell.
Expand All @@ -457,11 +471,46 @@ def set_cell_image(self, coord: Coord_T, state: CellContents) -> None:
b = self._scene.addPixmap(self._cell_images[state])
b.setPos(x * self.btn_size, y * self.btn_size)

def _track_mouse_event(self, event: ClickEvent, coord: Coord_T):
if self._enable_mouse_tracking:
self._mouse_events.append(
MouseEvent(time.time() - self._first_click_time, event, coord)
)

def _update_size(self) -> None:
self.setMaximumSize(self.sizeHint())
self.setSceneRect(
0, 0, self.x_size * self.btn_size, self.y_size * self.btn_size
)
self.size_changed.emit()

def reset(self) -> None:
"""Reset all cell images and other state for a new game."""
logger.info("Resetting minefield widget")
self._mouse_coord = None
self._both_mouse_buttons_pressed = False
self._await_release_all_buttons = True
self._mouse_tracking = []
self._mouse_events = []
self._first_click_time = None
for c in self._board.all_coords:
self._set_cell_image(c, CellContents.Unclicked)

def update_cells(self, cell_updates: Mapping[Coord_T, CellContents]) -> None:
"""
Called to indicate some cells have changed state.
:param cell_updates:
A mapping of cell coordinates to their new state.
"""
for c, state in cell_updates.items():
self._set_cell_image(c, state)

def reshape(self, x_size: int, y_size: int) -> None:
logger.info("Resizing minefield to %sx%s", x_size, y_size)
self._update_size()
for c in [(i, j) for i in range(self.x_size) for j in range(self.y_size)]:
self.set_cell_image(c, CellContents.Unclicked)
self._set_cell_image(c, CellContents.Unclicked)

def update_style(self, img_type: CellImageType, style: str) -> None:
"""Update the cell images."""
Expand All @@ -470,19 +519,15 @@ def update_style(self, img_type: CellImageType, style: str) -> None:
self._cell_images, self.btn_size, self._state.styles, img_type
)
for coord in self._board.all_coords:
self.set_cell_image(coord, self._board[coord])
self._set_cell_image(coord, self._board[coord])

def update_btn_size(self, size: int) -> None:
"""Update the size of the cells."""
assert size == self._state.btn_size
_update_cell_images(self._cell_images, self.btn_size, self._state.styles)
for coord in self._board.all_coords:
self.set_cell_image(coord, self._board[coord])
self._set_cell_image(coord, self._board[coord])
self._update_size()

def _update_size(self) -> None:
self.setMaximumSize(self.sizeHint())
self.setSceneRect(
0, 0, self.x_size * self.btn_size, self.y_size * self.btn_size
)
self.size_changed.emit()
def get_mouse_events(self) -> List[MouseEvent]:
return self._mouse_events
Loading

0 comments on commit 17c9e85

Please sign in to comment.