Merge branch 'mouse_interaction' into 'master'
Mouse interaction Closes #40 See merge request ynerant/squirrel-battle!45
This commit is contained in:
commit
81de0d8eb0
|
@ -5,6 +5,7 @@ import curses
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from squirrelbattle.display.texturepack import TexturePack
|
from squirrelbattle.display.texturepack import TexturePack
|
||||||
|
from squirrelbattle.game import Game
|
||||||
from squirrelbattle.tests.screen import FakePad
|
from squirrelbattle.tests.screen import FakePad
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,6 +87,13 @@ class Display:
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||||
|
"""
|
||||||
|
A mouse click was performed on the coordinates (y, x) of the pad.
|
||||||
|
Maybe it can do something.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rows(self) -> int:
|
def rows(self) -> int:
|
||||||
return curses.LINES if self.screen else 42
|
return curses.LINES if self.screen else 42
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit
|
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit, \
|
||||||
|
Display
|
||||||
from squirrelbattle.display.mapdisplay import MapDisplay
|
from squirrelbattle.display.mapdisplay import MapDisplay
|
||||||
from squirrelbattle.display.messagedisplay import MessageDisplay
|
from squirrelbattle.display.messagedisplay import MessageDisplay
|
||||||
from squirrelbattle.display.statsdisplay import StatsDisplay
|
from squirrelbattle.display.statsdisplay import StatsDisplay
|
||||||
|
@ -10,7 +11,7 @@ from squirrelbattle.display.menudisplay import MainMenuDisplay, \
|
||||||
PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay
|
PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay
|
||||||
from squirrelbattle.display.logsdisplay import LogsDisplay
|
from squirrelbattle.display.logsdisplay import LogsDisplay
|
||||||
from squirrelbattle.display.texturepack import TexturePack
|
from squirrelbattle.display.texturepack import TexturePack
|
||||||
from typing import Any
|
from typing import Any, List
|
||||||
from squirrelbattle.game import Game, GameMode
|
from squirrelbattle.game import Game, GameMode
|
||||||
from squirrelbattle.enums import DisplayActions
|
from squirrelbattle.enums import DisplayActions
|
||||||
|
|
||||||
|
@ -39,11 +40,13 @@ class DisplayManager:
|
||||||
self.storeinventorydisplay]
|
self.storeinventorydisplay]
|
||||||
self.update_game_components()
|
self.update_game_components()
|
||||||
|
|
||||||
def handle_display_action(self, action: DisplayActions) -> None:
|
def handle_display_action(self, action: DisplayActions, *params) -> None:
|
||||||
if action == DisplayActions.REFRESH:
|
if action == DisplayActions.REFRESH:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
elif action == DisplayActions.UPDATE:
|
elif action == DisplayActions.UPDATE:
|
||||||
self.update_game_components()
|
self.update_game_components()
|
||||||
|
elif action == DisplayActions.MOUSE:
|
||||||
|
self.handle_mouse_click(*params)
|
||||||
|
|
||||||
def update_game_components(self) -> None:
|
def update_game_components(self) -> None:
|
||||||
for d in self.displays:
|
for d in self.displays:
|
||||||
|
@ -58,7 +61,21 @@ class DisplayManager:
|
||||||
self.logsdisplay.update_logs(self.game.logs)
|
self.logsdisplay.update_logs(self.game.logs)
|
||||||
self.messagedisplay.update_message(self.game.message)
|
self.messagedisplay.update_message(self.game.message)
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def handle_mouse_click(self, y: int, x: int) -> None:
|
||||||
|
displays = self.refresh()
|
||||||
|
display = None
|
||||||
|
for d in displays:
|
||||||
|
top_y, top_x, height, width = d.y, d.x, d.height, d.width
|
||||||
|
if top_y <= y < top_y + height and top_x <= x < top_x + width:
|
||||||
|
# The click coordinates correspond to the coordinates
|
||||||
|
# of that display
|
||||||
|
display = d
|
||||||
|
if display:
|
||||||
|
display.handle_click(y - display.y, x - display.x, self.game)
|
||||||
|
|
||||||
|
def refresh(self) -> List[Display]:
|
||||||
|
displays = []
|
||||||
|
|
||||||
if self.game.state == GameMode.PLAY \
|
if self.game.state == GameMode.PLAY \
|
||||||
or self.game.state == GameMode.INVENTORY \
|
or self.game.state == GameMode.INVENTORY \
|
||||||
or self.game.state == GameMode.STORE:
|
or self.game.state == GameMode.STORE:
|
||||||
|
@ -74,18 +91,26 @@ class DisplayManager:
|
||||||
self.rows // 5 - 1, self.cols * 4 // 5)
|
self.rows // 5 - 1, self.cols * 4 // 5)
|
||||||
self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5)
|
self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5)
|
||||||
self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1)
|
self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1)
|
||||||
|
|
||||||
|
displays += [self.mapdisplay, self.statsdisplay, self.logsdisplay,
|
||||||
|
self.hbar, self.vbar]
|
||||||
|
|
||||||
if self.game.state == GameMode.INVENTORY:
|
if self.game.state == GameMode.INVENTORY:
|
||||||
self.playerinventorydisplay.refresh(
|
self.playerinventorydisplay.refresh(
|
||||||
self.rows // 10, self.cols // 2,
|
self.rows // 10, self.cols // 2,
|
||||||
8 * self.rows // 10, 2 * self.cols // 5)
|
8 * self.rows // 10, 2 * self.cols // 5)
|
||||||
|
displays.append(self.playerinventorydisplay)
|
||||||
elif self.game.state == GameMode.STORE:
|
elif self.game.state == GameMode.STORE:
|
||||||
self.storeinventorydisplay.refresh(
|
self.storeinventorydisplay.refresh(
|
||||||
self.rows // 10, self.cols // 2,
|
self.rows // 10, self.cols // 2,
|
||||||
8 * self.rows // 10, 2 * self.cols // 5)
|
8 * self.rows // 10, 2 * self.cols // 5)
|
||||||
|
displays.append(self.storeinventorydisplay)
|
||||||
elif self.game.state == GameMode.MAINMENU:
|
elif self.game.state == GameMode.MAINMENU:
|
||||||
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||||
|
displays.append(self.mainmenudisplay)
|
||||||
elif self.game.state == GameMode.SETTINGS:
|
elif self.game.state == GameMode.SETTINGS:
|
||||||
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
|
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||||
|
displays.append(self.settingsmenudisplay)
|
||||||
|
|
||||||
if self.game.message:
|
if self.game.message:
|
||||||
height, width = 0, 0
|
height, width = 0, 0
|
||||||
|
@ -94,9 +119,12 @@ class DisplayManager:
|
||||||
width = max(width, len(line))
|
width = max(width, len(line))
|
||||||
y, x = (self.rows - height) // 2, (self.cols - width) // 2
|
y, x = (self.rows - height) // 2, (self.cols - width) // 2
|
||||||
self.messagedisplay.refresh(y, x, height, width)
|
self.messagedisplay.refresh(y, x, height, width)
|
||||||
|
displays.append(self.messagedisplay)
|
||||||
|
|
||||||
self.resize_window()
|
self.resize_window()
|
||||||
|
|
||||||
|
return displays
|
||||||
|
|
||||||
def resize_window(self) -> bool:
|
def resize_window(self) -> bool:
|
||||||
"""
|
"""
|
||||||
If the window got resized, ensure that the screen size got updated.
|
If the window got resized, ensure that the screen size got updated.
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from squirrelbattle.menus import Menu, MainMenu
|
from squirrelbattle.menus import Menu, MainMenu
|
||||||
from .display import Display, Box
|
from .display import Display, Box
|
||||||
|
from ..enums import KeyValues
|
||||||
|
from ..game import Game
|
||||||
from ..resources import ResourceManager
|
from ..resources import ResourceManager
|
||||||
from ..translations import gettext as _
|
from ..translations import gettext as _
|
||||||
|
|
||||||
|
@ -44,6 +47,13 @@ class MenuDisplay(Display):
|
||||||
self.height - 2 + self.y,
|
self.height - 2 + self.y,
|
||||||
self.width - 2 + self.x)
|
self.width - 2 + self.x)
|
||||||
|
|
||||||
|
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||||
|
"""
|
||||||
|
We can select a menu item with the mouse.
|
||||||
|
"""
|
||||||
|
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 1))
|
||||||
|
game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def truewidth(self) -> int:
|
def truewidth(self) -> int:
|
||||||
return max([len(str(a)) for a in self.values])
|
return max([len(str(a)) for a in self.values])
|
||||||
|
@ -108,6 +118,14 @@ class MainMenuDisplay(Display):
|
||||||
menuy, menux, min(self.menudisplay.preferred_height,
|
menuy, menux, min(self.menudisplay.preferred_height,
|
||||||
self.height - menuy), menuwidth)
|
self.height - menuy), menuwidth)
|
||||||
|
|
||||||
|
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||||
|
menuwidth = min(self.menudisplay.preferred_width, self.width)
|
||||||
|
menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1
|
||||||
|
menuheight = min(self.menudisplay.preferred_height, self.height - menuy)
|
||||||
|
|
||||||
|
if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth:
|
||||||
|
self.menudisplay.handle_click(y - menuy, x - menux, game)
|
||||||
|
|
||||||
|
|
||||||
class PlayerInventoryDisplay(MenuDisplay):
|
class PlayerInventoryDisplay(MenuDisplay):
|
||||||
message = _("== INVENTORY ==")
|
message = _("== INVENTORY ==")
|
||||||
|
@ -129,6 +147,13 @@ class PlayerInventoryDisplay(MenuDisplay):
|
||||||
def trueheight(self) -> int:
|
def trueheight(self) -> int:
|
||||||
return 2 + super().trueheight
|
return 2 + super().trueheight
|
||||||
|
|
||||||
|
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||||
|
"""
|
||||||
|
We can select a menu item with the mouse.
|
||||||
|
"""
|
||||||
|
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3))
|
||||||
|
game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
|
||||||
|
|
||||||
class StoreInventoryDisplay(MenuDisplay):
|
class StoreInventoryDisplay(MenuDisplay):
|
||||||
message = _("== STALL ==")
|
message = _("== STALL ==")
|
||||||
|
@ -150,3 +175,10 @@ class StoreInventoryDisplay(MenuDisplay):
|
||||||
@property
|
@property
|
||||||
def trueheight(self) -> int:
|
def trueheight(self) -> int:
|
||||||
return 2 + super().trueheight
|
return 2 + super().trueheight
|
||||||
|
|
||||||
|
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||||
|
"""
|
||||||
|
We can select a menu item with the mouse.
|
||||||
|
"""
|
||||||
|
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3))
|
||||||
|
game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
|
|
@ -31,7 +31,7 @@ class Item(Entity):
|
||||||
"""
|
"""
|
||||||
if self.held:
|
if self.held:
|
||||||
self.held_by.inventory.remove(self)
|
self.held_by.inventory.remove(self)
|
||||||
self.map.add_entity(self)
|
self.held_by.map.add_entity(self)
|
||||||
self.move(self.held_by.y, self.held_by.x)
|
self.move(self.held_by.y, self.held_by.x)
|
||||||
self.held = False
|
self.held = False
|
||||||
self.held_by = None
|
self.held_by = None
|
||||||
|
|
|
@ -16,6 +16,7 @@ class DisplayActions(Enum):
|
||||||
"""
|
"""
|
||||||
REFRESH = auto()
|
REFRESH = auto()
|
||||||
UPDATE = auto()
|
UPDATE = auto()
|
||||||
|
MOUSE = auto()
|
||||||
|
|
||||||
|
|
||||||
class GameMode(Enum):
|
class GameMode(Enum):
|
||||||
|
@ -33,6 +34,7 @@ class KeyValues(Enum):
|
||||||
"""
|
"""
|
||||||
Key values options used in the game
|
Key values options used in the game
|
||||||
"""
|
"""
|
||||||
|
MOUSE = auto()
|
||||||
UP = auto()
|
UP = auto()
|
||||||
DOWN = auto()
|
DOWN = auto()
|
||||||
LEFT = auto()
|
LEFT = auto()
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from random import randint
|
from random import randint
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
import curses
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -15,7 +16,6 @@ from .resources import ResourceManager
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
from . import menus
|
from . import menus
|
||||||
from .translations import gettext as _, Translator
|
from .translations import gettext as _, Translator
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
|
@ -26,7 +26,7 @@ class Game:
|
||||||
player: Player
|
player: Player
|
||||||
screen: Any
|
screen: Any
|
||||||
# display_actions is a display interface set by the bootstrapper
|
# display_actions is a display interface set by the bootstrapper
|
||||||
display_actions: Callable[[DisplayActions], None]
|
display_actions: callable
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -71,8 +71,12 @@ class Game:
|
||||||
screen.refresh()
|
screen.refresh()
|
||||||
self.display_actions(DisplayActions.REFRESH)
|
self.display_actions(DisplayActions.REFRESH)
|
||||||
key = screen.getkey()
|
key = screen.getkey()
|
||||||
self.handle_key_pressed(
|
if key == "KEY_MOUSE":
|
||||||
KeyValues.translate_key(key, self.settings), key)
|
_ignored1, x, y, _ignored2, _ignored3 = curses.getmouse()
|
||||||
|
self.display_actions(DisplayActions.MOUSE, y, x)
|
||||||
|
else:
|
||||||
|
self.handle_key_pressed(
|
||||||
|
KeyValues.translate_key(key, self.settings), key)
|
||||||
|
|
||||||
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\
|
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\
|
||||||
-> None:
|
-> None:
|
||||||
|
|
|
@ -20,6 +20,8 @@ class TermManager: # pragma: no cover
|
||||||
curses.cbreak()
|
curses.cbreak()
|
||||||
# make cursor invisible
|
# make cursor invisible
|
||||||
curses.curs_set(False)
|
curses.curs_set(False)
|
||||||
|
# Catch mouse events
|
||||||
|
curses.mousemask(True)
|
||||||
# Enable colors
|
# Enable colors
|
||||||
curses.start_color()
|
curses.start_color()
|
||||||
|
|
||||||
|
|
|
@ -230,6 +230,33 @@ class TestGame(unittest.TestCase):
|
||||||
self.game.handle_key_pressed(KeyValues.SPACE)
|
self.game.handle_key_pressed(KeyValues.SPACE)
|
||||||
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
||||||
|
|
||||||
|
def test_mouse_click(self) -> None:
|
||||||
|
"""
|
||||||
|
Simulate mouse clicks.
|
||||||
|
"""
|
||||||
|
self.game.state = GameMode.MAINMENU
|
||||||
|
|
||||||
|
# Settings menu
|
||||||
|
self.game.display_actions(DisplayActions.MOUSE, 25, 21)
|
||||||
|
self.assertEqual(self.game.main_menu.position, 4)
|
||||||
|
self.assertEqual(self.game.state, GameMode.SETTINGS)
|
||||||
|
|
||||||
|
bomb = Bomb()
|
||||||
|
bomb.hold(self.game.player)
|
||||||
|
bomb2 = Bomb()
|
||||||
|
bomb2.hold(self.game.player)
|
||||||
|
|
||||||
|
self.game.state = GameMode.INVENTORY
|
||||||
|
|
||||||
|
# Click nowhere
|
||||||
|
self.game.display_actions(DisplayActions.MOUSE, 0, 0)
|
||||||
|
self.assertEqual(self.game.state, GameMode.INVENTORY)
|
||||||
|
|
||||||
|
# Click on the second item
|
||||||
|
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
|
||||||
|
self.assertEqual(self.game.state, GameMode.INVENTORY)
|
||||||
|
self.assertEqual(self.game.inventory_menu.position, 1)
|
||||||
|
|
||||||
def test_new_game(self) -> None:
|
def test_new_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure that the start button starts a new game.
|
Ensure that the start button starts a new game.
|
||||||
|
@ -512,9 +539,10 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
# The second item is not a heart
|
# The second item is not a heart
|
||||||
merchant.inventory[1] = Sword()
|
merchant.inventory[1] = Sword()
|
||||||
# Buy the second item
|
# Buy the second item by clicking on it
|
||||||
item = self.game.store_menu.validate()
|
item = self.game.store_menu.validate()
|
||||||
self.assertIn(item, merchant.inventory)
|
self.assertIn(item, merchant.inventory)
|
||||||
|
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
|
||||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
self.assertIn(item, self.game.player.inventory)
|
self.assertIn(item, self.game.player.inventory)
|
||||||
self.assertNotIn(item, merchant.inventory)
|
self.assertNotIn(item, merchant.inventory)
|
||||||
|
|
Loading…
Reference in New Issue