From a5890a341d87734f50c7542a598b6aef2e3dc2df Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 17 Dec 2020 23:46:20 +0100 Subject: [PATCH 1/8] Display inventory menu next to the merchant menu --- squirrelbattle/display/display_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index f9b3f01..4e2381a 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -104,7 +104,11 @@ class DisplayManager: self.storeinventorydisplay.refresh( self.rows // 10, self.cols // 2, 8 * self.rows // 10, 2 * self.cols // 5) + self.playerinventorydisplay.refresh( + self.rows // 10, self.cols // 10, + 8 * self.rows // 10, 2 * self.cols // 5) displays.append(self.storeinventorydisplay) + displays.append(self.playerinventorydisplay) elif self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) displays.append(self.mainmenudisplay) From 9a556ba669d24027c2ebd381efbbbfd3e3370388 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 01:05:50 +0100 Subject: [PATCH 2/8] We can now buy items to merchants, closes #47 --- squirrelbattle/game.py | 28 +++++++++++++------ .../locale/de/LC_MESSAGES/squirrelbattle.po | 4 +-- .../locale/es/LC_MESSAGES/squirrelbattle.po | 4 +-- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 4 +-- squirrelbattle/tests/game_test.py | 3 +- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ed3b60f..5fc130b 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -34,6 +34,7 @@ class Game: """ self.state = GameMode.MAINMENU self.waiting_for_friendly_key = False + self.is_in_store_menu = True self.settings = Settings() self.settings.load_settings() self.settings.write_settings() @@ -191,22 +192,31 @@ class Game: """ In a store menu, we can buy items or close the menu. """ - if key == KeyValues.SPACE: + menu = self.store_menu if self.is_in_store_menu else self.inventory_menu + + if key == KeyValues.SPACE or key == KeyValues.INVENTORY: self.state = GameMode.PLAY elif key == KeyValues.UP: - self.store_menu.go_up() + menu.go_up() elif key == KeyValues.DOWN: - self.store_menu.go_down() - if self.store_menu.values and not self.player.dead: + menu.go_down() + elif key == KeyValues.LEFT: + self.is_in_store_menu = False + elif key == KeyValues.RIGHT: + self.is_in_store_menu = True + if menu.values and not self.player.dead: if key == KeyValues.ENTER: - item = self.store_menu.validate() - flag = item.be_sold(self.player, self.store_menu.merchant) + item = menu.validate() + owner = self.store_menu.merchant if self.is_in_store_menu \ + else self.player + buyer = self.player if self.is_in_store_menu \ + else self.store_menu.merchant + flag = item.be_sold(buyer, owner) if not flag: - self.message = _("You do not have enough money") + self.message = _("The buyer does not have enough money") self.display_actions(DisplayActions.UPDATE) # Ensure that the cursor has a good position - self.store_menu.position = min(self.store_menu.position, - len(self.store_menu.values) - 1) + menu.position = min(menu.position, len(menu.values) - 1) def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 39cfeec..29bf10e 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -59,8 +59,8 @@ msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." #: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 -msgid "You do not have enough money" -msgstr "Sie haben nicht genug Geld" +msgid "The buyer does not have enough money" +msgstr "Der Kaufer hat nicht genug Geld" #: squirrelbattle/game.py:249 msgid "" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index f4d1c3c..8a7bc3e 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -58,8 +58,8 @@ msgid "{player} exchanged its body with {entity}." msgstr "{player} intercambió su cuerpo con {entity}." #: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 -msgid "You do not have enough money" -msgstr "No tienes suficiente dinero" +msgid "The buyer does not have enough money" +msgstr "El comprador no tiene suficiente dinero" #: squirrelbattle/game.py:249 msgid "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 8b1c4db..ffbfdce 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -59,8 +59,8 @@ msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." #: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 -msgid "You do not have enough money" -msgstr "Vous n'avez pas assez d'argent" +msgid "The buyer does not have enough money" +msgstr "L'acheteur n'a pas assez d'argent" #: squirrelbattle/game.py:249 msgid "" diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 52aeeaf..c70ca8f 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -579,7 +579,8 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.ENTER) self.assertNotIn(item, self.game.player.inventory) self.assertIn(item, merchant.inventory) - self.assertEqual(self.game.message, _("You do not have enough money")) + self.assertEqual(self.game.message, + _("The buyer does not have enough money")) self.game.handle_key_pressed(KeyValues.ENTER) # Exit the menu From c55a7451e78dca7b2e705500179e01d5d9abbc02 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 01:50:11 +0100 Subject: [PATCH 3/8] Display more precisely where we are in the store menu --- squirrelbattle/display/menudisplay.py | 16 ++++++++++++---- squirrelbattle/display/texturepack.py | 2 +- squirrelbattle/game.py | 8 +++++++- squirrelbattle/tests/game_test.py | 4 ++++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index a00d0fe..f9a5e09 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -7,7 +7,7 @@ from typing import List from squirrelbattle.menus import Menu, MainMenu from .display import Box, Display -from ..enums import KeyValues +from ..enums import KeyValues, GameMode from ..game import Game from ..resources import ResourceManager from ..translations import gettext as _ @@ -139,9 +139,14 @@ class PlayerInventoryDisplay(MenuDisplay): self.menubox.update_title(_("INVENTORY")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] - selection = f"[{rep}]" if i == self.menu.position else f" {rep} " + selection = f"[{rep}]" if i == self.menu.position \ + and (Game.INSTANCE.state == GameMode.INVENTORY + or Game.INSTANCE.state == GameMode.STORE + and not Game.INSTANCE.is_in_store_menu) else f" {rep} " self.addstr(self.pad, i + 1, 0, selection - + " " + item.translated_name.capitalize()) + + " " + item.translated_name.capitalize() + + (": " + str(item.price) + " Hazels" + if Game.INSTANCE.state == GameMode.STORE else "")) @property def truewidth(self) -> int: @@ -156,6 +161,7 @@ class PlayerInventoryDisplay(MenuDisplay): We can select a menu item with the mouse. """ self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) + game.is_in_store_menu = False game.handle_key_pressed(KeyValues.ENTER) @@ -164,7 +170,8 @@ class StoreInventoryDisplay(MenuDisplay): self.menubox.update_title(_("STALL")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] - selection = f"[{rep}]" if i == self.menu.position else f" {rep} " + selection = f"[{rep}]" if i == self.menu.position \ + and Game.INSTANCE.is_in_store_menu else f" {rep} " self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + ": " + str(item.price) + " Hazels") @@ -182,4 +189,5 @@ class StoreInventoryDisplay(MenuDisplay): We can select a menu item with the mouse. """ self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) + game.is_in_store_menu = True game.handle_key_pressed(KeyValues.ENTER) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index f72cd97..1f6dc76 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -97,7 +97,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( MERCHANT='🦜', RABBIT='🐇', SUNFLOWER='🌻', - SWORD='🗡️', + SWORD='🗡️ ', TEDDY_BEAR='🧸', TIGER='🐅', WALL='🧱', diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 5fc130b..2204508 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -22,6 +22,9 @@ class Game: """ The game object controls all actions in the game. """ + # Global instance of the game + INSTANCE: "Game" + map: Map player: Player screen: Any @@ -32,6 +35,8 @@ class Game: """ Init the game. """ + Game.INSTANCE = self + self.state = GameMode.MAINMENU self.waiting_for_friendly_key = False self.is_in_store_menu = True @@ -52,7 +57,7 @@ class Game: Create a new game on the screen. """ # TODO generate a new map procedurally - self.map = Map.load(ResourceManager.get_asset_path("example_map.txt")) + self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) self.map.logs = self.logs self.logs.clear() self.player = Player() @@ -163,6 +168,7 @@ class Game: self.logs.add_message(msg) if entity.is_merchant(): self.state = GameMode.STORE + self.is_in_store_menu = True self.store_menu.update_merchant(entity) def handle_key_pressed_inventory(self, key: KeyValues) -> None: diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index c70ca8f..2b604be 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -546,6 +546,10 @@ class TestGame(unittest.TestCase): # Navigate in the menu self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.LEFT) + self.assertFalse(self.game.is_in_store_menu) + self.game.handle_key_pressed(KeyValues.RIGHT) + self.assertTrue(self.game.is_in_store_menu) self.game.handle_key_pressed(KeyValues.UP) self.assertEqual(self.game.store_menu.position, 1) From b8d32b29c8b3d500b696a2d77f123756e7a1d9e8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 02:17:06 +0100 Subject: [PATCH 4/8] Test selling items --- squirrelbattle/display/display_manager.py | 1 - squirrelbattle/menus.py | 4 ++-- squirrelbattle/tests/game_test.py | 22 ++++++++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 4e2381a..83df543 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -54,7 +54,6 @@ class DisplayManager: self.mapdisplay.update_map(self.game.map) self.statsdisplay.update_player(self.game.player) self.game.inventory_menu.update_player(self.game.player) - self.game.store_menu.update_merchant(self.game.player) self.playerinventorydisplay.update_menu(self.game.inventory_menu) self.storeinventorydisplay.update_menu(self.game.store_menu) self.settingsmenudisplay.update_menu(self.game.settings_menu) diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index e0087a3..458c7b4 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -132,11 +132,11 @@ class InventoryMenu(Menu): class StoreMenu(Menu): - merchant: Merchant + merchant: Merchant = None def update_merchant(self, merchant: Merchant) -> None: self.merchant = merchant @property def values(self) -> list: - return self.merchant.inventory + return self.merchant.inventory if self.merchant else [] diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 2b604be..89d6ed6 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -12,6 +12,7 @@ from ..entities.items import Bomb, Heart, Sword, Explosion from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode +from ..interfaces import Map from ..menus import MainMenuValues from ..resources import ResourceManager from ..settings import Settings @@ -25,6 +26,10 @@ class TestGame(unittest.TestCase): """ self.game = Game() self.game.new_game() + self.game.map = Map.load( + ResourceManager.get_asset_path("example_map.txt")) + self.game.map.add_entity(self.game.player) + self.game.player.move(self.game.map.start_y, self.game.map.start_x) self.game.logs.add_message("Hello World !") display = DisplayManager(None, self.game) self.game.display_actions = display.handle_display_action @@ -556,12 +561,11 @@ class TestGame(unittest.TestCase): self.game.player.hazel = 0x7ffff42ff # The second item is not a heart - merchant.inventory[1] = Sword() + merchant.inventory[1] = sword = Sword() # Buy the second item by clicking on it item = self.game.store_menu.validate() self.assertIn(item, merchant.inventory) self.game.display_actions(DisplayActions.MOUSE, 7, 25) - self.game.handle_key_pressed(KeyValues.ENTER) self.assertIn(item, self.game.player.inventory) self.assertNotIn(item, merchant.inventory) @@ -587,6 +591,20 @@ class TestGame(unittest.TestCase): _("The buyer does not have enough money")) self.game.handle_key_pressed(KeyValues.ENTER) + # Sell an item + self.game.inventory_menu.position = len(self.game.player.inventory) - 1 + self.game.handle_key_pressed(KeyValues.LEFT) + self.assertFalse(self.game.is_in_store_menu) + self.assertIn(sword, self.game.player.inventory) + self.assertEqual(self.game.inventory_menu.validate(), sword) + old_player_money, old_merchant_money = self.game.player.hazel,\ + merchant.hazel + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertNotIn(sword, self.game.player.inventory) + self.assertIn(sword, merchant.inventory) + self.assertEqual(self.game.player.hazel, old_player_money + sword.price) + self.assertEqual(merchant.hazel, old_merchant_money - sword.price) + # Exit the menu self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.PLAY) From 85870494a077190d49529d061f55b3aae5f3378b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 15:07:09 +0100 Subject: [PATCH 5/8] More generic display update --- squirrelbattle/display/display.py | 10 ++++++++++ squirrelbattle/display/display_manager.py | 13 +++++-------- squirrelbattle/display/logsdisplay.py | 5 +++-- squirrelbattle/display/mapdisplay.py | 8 +++++--- squirrelbattle/display/menudisplay.py | 17 ++++++++++++++++- squirrelbattle/display/messagedisplay.py | 5 +++-- squirrelbattle/display/statsdisplay.py | 5 +++-- squirrelbattle/tests/game_test.py | 1 + 8 files changed, 46 insertions(+), 18 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 29295de..023ad6e 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -162,6 +162,16 @@ class Display: pad.refresh(top_y, top_x, window_y, window_x, last_y, last_x) def display(self) -> None: + """ + Draw the content of the display and refresh pads. + """ + raise NotImplementedError + + def update(self, game: Game) -> None: + """ + The game state was updated. + Indicate what to do with the new state. + """ raise NotImplementedError def handle_click(self, y: int, x: int, game: Game) -> None: diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 83df543..3481348 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -49,16 +49,13 @@ class DisplayManager: self.handle_mouse_click(*params) def update_game_components(self) -> None: + """ + The game state was updated. + Trigger all displays of these modifications. + """ for d in self.displays: d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) - self.mapdisplay.update_map(self.game.map) - self.statsdisplay.update_player(self.game.player) - self.game.inventory_menu.update_player(self.game.player) - self.playerinventorydisplay.update_menu(self.game.inventory_menu) - self.storeinventorydisplay.update_menu(self.game.store_menu) - self.settingsmenudisplay.update_menu(self.game.settings_menu) - self.logsdisplay.update_logs(self.game.logs) - self.messagedisplay.update_message(self.game.message) + d.update(self.game) def handle_mouse_click(self, y: int, x: int) -> None: displays = self.refresh() diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index b768a0e..b21273d 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from squirrelbattle.display.display import Display +from squirrelbattle.game import Game from squirrelbattle.interfaces import Logs @@ -11,8 +12,8 @@ class LogsDisplay(Display): super().__init__(*args) self.pad = self.newpad(self.rows, self.cols) - def update_logs(self, logs: Logs) -> None: - self.logs = logs + def update(self, game: Game) -> None: + self.logs = game.logs def display(self) -> None: messages = self.logs.messages[-self.height:] diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 54d9432..72e339c 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -3,6 +3,7 @@ from squirrelbattle.interfaces import Map from .display import Display +from ..game import Game class MapDisplay(Display): @@ -10,9 +11,10 @@ class MapDisplay(Display): def __init__(self, *args): super().__init__(*args) - def update_map(self, m: Map) -> None: - self.map = m - self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) + def update(self, game: Game) -> None: + self.map = game.map + self.pad = self.newpad(self.map.height, + self.pack.tile_width * self.map.width + 1) def update_pad(self) -> None: self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index f9a5e09..ed10fec 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -5,7 +5,7 @@ import curses from random import randint from typing import List -from squirrelbattle.menus import Menu, MainMenu +from squirrelbattle.menus import Menu, MainMenu, SettingsMenu from .display import Box, Display from ..enums import KeyValues, GameMode from ..game import Game @@ -17,6 +17,7 @@ class MenuDisplay(Display): """ A class to display the menu objects """ + menu: Menu position: int def __init__(self, *args, **kwargs): @@ -80,6 +81,11 @@ class SettingsMenuDisplay(MenuDisplay): """ A class to display specifically a settingsmenu object """ + menu: SettingsMenu + + def update(self, game: Game) -> None: + self.update_menu(game.settings_menu) + @property def values(self) -> List[str]: return [_(a[1][1]) + (" : " @@ -122,6 +128,9 @@ class MainMenuDisplay(Display): menuy, menux, min(self.menudisplay.preferred_height, self.height - menuy), menuwidth) + def update(self, game: Game) -> None: + self.menudisplay.update_menu(game.main_menu) + 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 @@ -135,6 +144,9 @@ class MainMenuDisplay(Display): class PlayerInventoryDisplay(MenuDisplay): + def update(self, game: Game) -> None: + self.update_menu(game.inventory_menu) + def update_pad(self) -> None: self.menubox.update_title(_("INVENTORY")) for i, item in enumerate(self.menu.values): @@ -166,6 +178,9 @@ class PlayerInventoryDisplay(MenuDisplay): class StoreInventoryDisplay(MenuDisplay): + def update(self, game: Game) -> None: + self.update_menu(game.store_menu) + def update_pad(self) -> None: self.menubox.update_title(_("STALL")) for i, item in enumerate(self.menu.values): diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py index 32f7139..271055a 100644 --- a/squirrelbattle/display/messagedisplay.py +++ b/squirrelbattle/display/messagedisplay.py @@ -3,6 +3,7 @@ import curses from squirrelbattle.display.display import Box, Display +from squirrelbattle.game import Game class MessageDisplay(Display): @@ -17,8 +18,8 @@ class MessageDisplay(Display): self.message = "" self.pad = self.newpad(1, 1) - def update_message(self, msg: str) -> None: - self.message = msg + def update(self, game: Game) -> None: + self.message = game.message def display(self) -> None: self.box.refresh(self.y - 1, self.x - 2, diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 9937c3e..ef358bb 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -4,6 +4,7 @@ import curses from ..entities.player import Player +from ..game import Game from ..translations import gettext as _ from .display import Display @@ -15,8 +16,8 @@ class StatsDisplay(Display): super().__init__(*args, **kwargs) self.pad = self.newpad(self.rows, self.cols) - def update_player(self, p: Player) -> None: - self.player = p + def update(self, game: Game) -> None: + self.player = game.player def update_pad(self) -> None: string2 = "Player -- LVL {}\nEXP {}/{}\nHP {}/{}"\ diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 89d6ed6..6751016 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -404,6 +404,7 @@ class TestGame(unittest.TestCase): Check that some functions are not implemented, only for coverage. """ self.assertRaises(NotImplementedError, Display.display, None) + self.assertRaises(NotImplementedError, Display.update, None, self.game) def test_messages(self) -> None: """ From 46ce7c33bf3dcabc5465c89c89490ed69bbc0056 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 15:15:47 +0100 Subject: [PATCH 6/8] Merchant menu is updated through its update function, and does not access globally to the Game instance --- squirrelbattle/display/logsdisplay.py | 1 + squirrelbattle/display/mapdisplay.py | 1 + squirrelbattle/display/menudisplay.py | 17 ++++++++++++----- squirrelbattle/game.py | 10 ++++------ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index b21273d..bb53e48 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -7,6 +7,7 @@ from squirrelbattle.interfaces import Logs class LogsDisplay(Display): + logs: Logs def __init__(self, *args) -> None: super().__init__(*args) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 72e339c..b10619e 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -7,6 +7,7 @@ from ..game import Game class MapDisplay(Display): + map: Map def __init__(self, *args): super().__init__(*args) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index ed10fec..c5dfda5 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -144,21 +144,25 @@ class MainMenuDisplay(Display): class PlayerInventoryDisplay(MenuDisplay): + selected: bool = True + store_mode: bool = False + def update(self, game: Game) -> None: self.update_menu(game.inventory_menu) + self.store_mode = game.state == GameMode.STORE + self.selected = game.state == GameMode.INVENTORY \ + or self.store_mode and not game.is_in_store_menu def update_pad(self) -> None: self.menubox.update_title(_("INVENTORY")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position \ - and (Game.INSTANCE.state == GameMode.INVENTORY - or Game.INSTANCE.state == GameMode.STORE - and not Game.INSTANCE.is_in_store_menu) else f" {rep} " + and self.selected else f" {rep} " self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + (": " + str(item.price) + " Hazels" - if Game.INSTANCE.state == GameMode.STORE else "")) + if self.store_mode else "")) @property def truewidth(self) -> int: @@ -178,15 +182,18 @@ class PlayerInventoryDisplay(MenuDisplay): class StoreInventoryDisplay(MenuDisplay): + selected: bool = False + def update(self, game: Game) -> None: self.update_menu(game.store_menu) + self.selected = game.is_in_store_menu def update_pad(self) -> None: self.menubox.update_title(_("STALL")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position \ - and Game.INSTANCE.is_in_store_menu else f" {rep} " + and self.selected else f" {rep} " self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + ": " + str(item.price) + " Hazels") diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 2204508..b0a65f4 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -22,9 +22,6 @@ class Game: """ The game object controls all actions in the game. """ - # Global instance of the game - INSTANCE: "Game" - map: Map player: Player screen: Any @@ -35,8 +32,6 @@ class Game: """ Init the game. """ - Game.INSTANCE = self - self.state = GameMode.MAINMENU self.waiting_for_friendly_key = False self.is_in_store_menu = True @@ -170,6 +165,7 @@ class Game: self.state = GameMode.STORE self.is_in_store_menu = True self.store_menu.update_merchant(entity) + self.display_actions(DisplayActions.UPDATE) def handle_key_pressed_inventory(self, key: KeyValues) -> None: """ @@ -208,8 +204,10 @@ class Game: menu.go_down() elif key == KeyValues.LEFT: self.is_in_store_menu = False + self.display_actions(DisplayActions.UPDATE) elif key == KeyValues.RIGHT: self.is_in_store_menu = True + self.display_actions(DisplayActions.UPDATE) if menu.values and not self.player.dead: if key == KeyValues.ENTER: item = menu.validate() @@ -220,7 +218,7 @@ class Game: flag = item.be_sold(buyer, owner) if not flag: self.message = _("The buyer does not have enough money") - self.display_actions(DisplayActions.UPDATE) + self.display_actions(DisplayActions.UPDATE) # Ensure that the cursor has a good position menu.position = min(menu.position, len(menu.values) - 1) From 5ae49e71ff0d14e9890802e1cc2762dc465e5b9b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 15:36:25 +0100 Subject: [PATCH 7/8] Display the amount of hazels in the store menu, closes #49 --- squirrelbattle/display/menudisplay.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index c5dfda5..9140ca7 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -5,8 +5,9 @@ import curses from random import randint from typing import List -from squirrelbattle.menus import Menu, MainMenu, SettingsMenu +from squirrelbattle.menus import Menu, MainMenu, SettingsMenu, StoreMenu from .display import Box, Display +from ..entities.player import Player from ..enums import KeyValues, GameMode from ..game import Game from ..resources import ResourceManager @@ -144,10 +145,12 @@ class MainMenuDisplay(Display): class PlayerInventoryDisplay(MenuDisplay): + player: Player = None selected: bool = True store_mode: bool = False def update(self, game: Game) -> None: + self.player = game.player self.update_menu(game.inventory_menu) self.store_mode = game.state == GameMode.STORE self.selected = game.state == GameMode.INVENTORY \ @@ -164,6 +167,12 @@ class PlayerInventoryDisplay(MenuDisplay): + (": " + str(item.price) + " Hazels" if self.store_mode else "")) + if self.store_mode: + price = f"{self.pack.HAZELNUT} {self.player.hazel} Hazels" + width = len(price) + (self.pack.tile_width - 1) + self.addstr(self.pad, self.height - 3, self.width - width - 2, + price, italic=True) + @property def truewidth(self) -> int: return max(1, self.height if hasattr(self, "height") else 10) @@ -182,6 +191,7 @@ class PlayerInventoryDisplay(MenuDisplay): class StoreInventoryDisplay(MenuDisplay): + menu: StoreMenu selected: bool = False def update(self, game: Game) -> None: @@ -198,6 +208,11 @@ class StoreInventoryDisplay(MenuDisplay): + " " + item.translated_name.capitalize() + ": " + str(item.price) + " Hazels") + price = f"{self.pack.HAZELNUT} {self.menu.merchant.hazel} Hazels" + width = len(price) + (self.pack.tile_width - 1) + self.addstr(self.pad, self.height - 3, self.width - width - 2, price, + italic=True) + @property def truewidth(self) -> int: return max(1, self.height if hasattr(self, "height") else 10) From 77f52b627614577f4c581d291e29398f541c601a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 16:40:52 +0100 Subject: [PATCH 8/8] Screen is refreshed only when pads are all refreshed, fixes #50 --- squirrelbattle/display/display.py | 2 +- squirrelbattle/display/display_manager.py | 22 +++++++++++++++------- squirrelbattle/display/mapdisplay.py | 1 + squirrelbattle/display/menudisplay.py | 2 +- squirrelbattle/game.py | 8 +++++--- squirrelbattle/tests/screen.py | 4 ++-- 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 023ad6e..6257427 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -159,7 +159,7 @@ class Display: if last_y >= window_y and last_x >= window_x: # Refresh the pad only if coordinates are valid - pad.refresh(top_y, top_x, window_y, window_x, last_y, last_x) + pad.noutrefresh(top_y, top_x, window_y, window_x, last_y, last_x) def display(self) -> None: """ diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 3481348..70879b1 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -71,6 +71,7 @@ class DisplayManager: def refresh(self) -> List[Display]: displays = [] + pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) if self.game.state == GameMode.PLAY \ or self.game.state == GameMode.INVENTORY \ @@ -93,16 +94,22 @@ class DisplayManager: if self.game.state == GameMode.INVENTORY: self.playerinventorydisplay.refresh( - self.rows // 10, self.cols // 2, - 8 * self.rows // 10, 2 * self.cols // 5) + self.rows // 10, + pack.tile_width * (self.cols // (2 * pack.tile_width)), + 8 * self.rows // 10, + pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) displays.append(self.playerinventorydisplay) elif self.game.state == GameMode.STORE: self.storeinventorydisplay.refresh( - self.rows // 10, self.cols // 2, - 8 * self.rows // 10, 2 * self.cols // 5) + self.rows // 10, + pack.tile_width * (self.cols // (2 * pack.tile_width)), + 8 * self.rows // 10, + pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) self.playerinventorydisplay.refresh( - self.rows // 10, self.cols // 10, - 8 * self.rows // 10, 2 * self.cols // 5) + self.rows // 10, + pack.tile_width * (self.cols // (10 * pack.tile_width)), + 8 * self.rows // 10, + pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) displays.append(self.storeinventorydisplay) displays.append(self.playerinventorydisplay) elif self.game.state == GameMode.MAINMENU: @@ -117,7 +124,8 @@ class DisplayManager: for line in self.game.message.split("\n"): height += 1 width = max(width, len(line)) - y, x = (self.rows - height) // 2, (self.cols - width) // 2 + y = pack.tile_width * (self.rows - height) // (2 * pack.tile_width) + x = pack.tile_width * ((self.cols - width) // (2 * pack.tile_width)) self.messagedisplay.refresh(y, x, height, width) displays.append(self.messagedisplay) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index b10619e..7e21adb 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -18,6 +18,7 @@ class MapDisplay(Display): self.pack.tile_width * self.map.width + 1) def update_pad(self) -> None: + self.pad.resize(500, 500) self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), self.pack.tile_fg_color, self.pack.tile_bg_color) for e in self.map.entities: diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 9140ca7..c71142e 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -154,7 +154,7 @@ class PlayerInventoryDisplay(MenuDisplay): self.update_menu(game.inventory_menu) self.store_mode = game.state == GameMode.STORE self.selected = game.state == GameMode.INVENTORY \ - or self.store_mode and not game.is_in_store_menu + or (self.store_mode and not game.is_in_store_menu) def update_pad(self) -> None: self.menubox.update_title(_("INVENTORY")) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index b0a65f4..0553d2e 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -61,16 +61,18 @@ class Game: self.map.spawn_random_entities(randint(3, 10)) self.inventory_menu.update_player(self.player) - def run(self, screen: Any) -> None: + def run(self, screen: Any) -> None: # pragma no cover """ Main infinite loop. We wait for the player's action, then we do what that should be done when the given key gets pressed. """ - while True: # pragma no cover + screen.refresh() + while True: screen.erase() - screen.refresh() + screen.noutrefresh() self.display_actions(DisplayActions.REFRESH) + curses.doupdate() key = screen.getkey() if key == "KEY_MOUSE": _ignored1, x, y, _ignored2, _ignored3 = curses.getmouse() diff --git a/squirrelbattle/tests/screen.py b/squirrelbattle/tests/screen.py index 9a8afe6..470aeef 100644 --- a/squirrelbattle/tests/screen.py +++ b/squirrelbattle/tests/screen.py @@ -12,8 +12,8 @@ class FakePad: def addstr(self, y: int, x: int, message: str, color: int = 0) -> None: pass - def refresh(self, pminrow: int, pmincol: int, sminrow: int, - smincol: int, smaxrow: int, smaxcol: int) -> None: + def noutrefresh(self, pminrow: int, pmincol: int, sminrow: int, + smincol: int, smaxrow: int, smaxcol: int) -> None: pass def erase(self) -> None: