Merge branch 'error-messages' into 'master'
Error messages Closes #24 et #17 See merge request ynerant/squirrel-battle!29
This commit is contained in:
commit
ec4ac13231
|
@ -139,16 +139,22 @@ class HorizontalSplit(Display):
|
||||||
|
|
||||||
class Box(Display):
|
class Box(Display):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, fg_border_color: Optional[int] = None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.pad = self.newpad(self.rows, self.cols)
|
self.pad = self.newpad(self.rows, self.cols)
|
||||||
|
self.fg_border_color = fg_border_color or curses.COLOR_WHITE
|
||||||
|
|
||||||
|
pair_number = 4 + self.fg_border_color
|
||||||
|
self.init_pair(pair_number, self.fg_border_color, curses.COLOR_BLACK)
|
||||||
|
self.pair = self.color_pair(pair_number)
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓")
|
self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓",
|
||||||
|
self.pair)
|
||||||
for i in range(1, self.height - 1):
|
for i in range(1, self.height - 1):
|
||||||
self.addstr(self.pad, i, 0, "┃")
|
self.addstr(self.pad, i, 0, "┃", self.pair)
|
||||||
self.addstr(self.pad, i, self.width - 1, "┃")
|
self.addstr(self.pad, i, self.width - 1, "┃", self.pair)
|
||||||
self.addstr(self.pad, self.height - 1, 0,
|
self.addstr(self.pad, self.height - 1, 0,
|
||||||
"┗" + "━" * (self.width - 2) + "┛")
|
"┗" + "━" * (self.width - 2) + "┛", self.pair)
|
||||||
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
||||||
self.y + self.height - 1, self.x + self.width - 1)
|
self.y + self.height - 1, self.x + self.width - 1)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import curses
|
import curses
|
||||||
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit
|
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit
|
||||||
from squirrelbattle.display.mapdisplay import MapDisplay
|
from squirrelbattle.display.mapdisplay import MapDisplay
|
||||||
|
from squirrelbattle.display.messagedisplay import MessageDisplay
|
||||||
from squirrelbattle.display.statsdisplay import StatsDisplay
|
from squirrelbattle.display.statsdisplay import StatsDisplay
|
||||||
from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \
|
from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \
|
||||||
MainMenuDisplay
|
MainMenuDisplay
|
||||||
|
@ -26,11 +27,12 @@ class DisplayManager:
|
||||||
screen, pack)
|
screen, pack)
|
||||||
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
|
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
|
||||||
self.logsdisplay = LogsDisplay(screen, pack)
|
self.logsdisplay = LogsDisplay(screen, pack)
|
||||||
|
self.messagedisplay = MessageDisplay(screen=screen, pack=None)
|
||||||
self.hbar = HorizontalSplit(screen, pack)
|
self.hbar = HorizontalSplit(screen, pack)
|
||||||
self.vbar = VerticalSplit(screen, pack)
|
self.vbar = VerticalSplit(screen, pack)
|
||||||
self.displays = [self.statsdisplay, self.mapdisplay,
|
self.displays = [self.statsdisplay, self.mapdisplay,
|
||||||
self.mainmenudisplay, self.settingsmenudisplay,
|
self.mainmenudisplay, self.settingsmenudisplay,
|
||||||
self.logsdisplay]
|
self.logsdisplay, self.messagedisplay]
|
||||||
self.update_game_components()
|
self.update_game_components()
|
||||||
|
|
||||||
def handle_display_action(self, action: DisplayActions) -> None:
|
def handle_display_action(self, action: DisplayActions) -> None:
|
||||||
|
@ -46,6 +48,7 @@ class DisplayManager:
|
||||||
self.statsdisplay.update_player(self.game.player)
|
self.statsdisplay.update_player(self.game.player)
|
||||||
self.settingsmenudisplay.update_menu(self.game.settings_menu)
|
self.settingsmenudisplay.update_menu(self.game.settings_menu)
|
||||||
self.logsdisplay.update_logs(self.game.logs)
|
self.logsdisplay.update_logs(self.game.logs)
|
||||||
|
self.messagedisplay.update_message(self.game.message)
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
if self.game.state == GameMode.PLAY:
|
if self.game.state == GameMode.PLAY:
|
||||||
|
@ -65,6 +68,15 @@ class DisplayManager:
|
||||||
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||||
if self.game.state == GameMode.SETTINGS:
|
if self.game.state == GameMode.SETTINGS:
|
||||||
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols - 1)
|
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols - 1)
|
||||||
|
|
||||||
|
if self.game.message:
|
||||||
|
height, width = 0, 0
|
||||||
|
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
|
||||||
|
self.messagedisplay.refresh(y, x, height, width)
|
||||||
|
|
||||||
self.resize_window()
|
self.resize_window()
|
||||||
|
|
||||||
def resize_window(self) -> bool:
|
def resize_window(self) -> bool:
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
import curses
|
||||||
|
|
||||||
|
from squirrelbattle.display.display import Box, Display
|
||||||
|
|
||||||
|
|
||||||
|
class MessageDisplay(Display):
|
||||||
|
"""
|
||||||
|
Display a message in a popup.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.box = Box(fg_border_color=curses.COLOR_RED, *args, **kwargs)
|
||||||
|
self.message = ""
|
||||||
|
self.pad = self.newpad(1, 1)
|
||||||
|
|
||||||
|
def update_message(self, msg: str) -> None:
|
||||||
|
self.message = msg
|
||||||
|
|
||||||
|
def display(self) -> None:
|
||||||
|
self.box.refresh(self.y - 1, self.x - 2,
|
||||||
|
self.height + 2, self.width + 4)
|
||||||
|
self.box.display()
|
||||||
|
self.pad.erase()
|
||||||
|
self.addstr(self.pad, 0, 0, self.message, curses.A_BOLD)
|
||||||
|
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
||||||
|
self.height + self.y - 1,
|
||||||
|
self.width + self.x - 1)
|
|
@ -1,6 +1,6 @@
|
||||||
# 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
|
||||||
|
from json import JSONDecodeError
|
||||||
from random import randint
|
from random import randint
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
import json
|
import json
|
||||||
|
@ -37,6 +37,7 @@ class Game:
|
||||||
self.settings.write_settings()
|
self.settings.write_settings()
|
||||||
self.settings_menu.update_values(self.settings)
|
self.settings_menu.update_values(self.settings)
|
||||||
self.logs = Logs()
|
self.logs = Logs()
|
||||||
|
self.message = None
|
||||||
|
|
||||||
def new_game(self) -> None:
|
def new_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -71,6 +72,11 @@ class Game:
|
||||||
Indicates what should be done when the given key is pressed,
|
Indicates what should be done when the given key is pressed,
|
||||||
according to the current game state.
|
according to the current game state.
|
||||||
"""
|
"""
|
||||||
|
if self.message:
|
||||||
|
self.message = None
|
||||||
|
self.display_actions(DisplayActions.REFRESH)
|
||||||
|
return
|
||||||
|
|
||||||
if self.state == GameMode.PLAY:
|
if self.state == GameMode.PLAY:
|
||||||
self.handle_key_pressed_play(key)
|
self.handle_key_pressed_play(key)
|
||||||
elif self.state == GameMode.MAINMENU:
|
elif self.state == GameMode.MAINMENU:
|
||||||
|
@ -133,9 +139,24 @@ class Game:
|
||||||
"""
|
"""
|
||||||
Loads the game from a dictionary
|
Loads the game from a dictionary
|
||||||
"""
|
"""
|
||||||
self.map.load_state(d)
|
try:
|
||||||
# noinspection PyTypeChecker
|
self.map.load_state(d)
|
||||||
self.player = self.map.find_entities(Player)[0]
|
except KeyError:
|
||||||
|
self.message = "Some keys are missing in your save file.\n" \
|
||||||
|
"Your save seems to be corrupt. It got deleted."
|
||||||
|
os.unlink(ResourceManager.get_config_path("save.json"))
|
||||||
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
return
|
||||||
|
|
||||||
|
players = self.map.find_entities(Player)
|
||||||
|
if not players:
|
||||||
|
self.message = "No player was found on this map!\n" \
|
||||||
|
"Maybe you died?"
|
||||||
|
self.player.health = 0
|
||||||
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.player = players[0]
|
||||||
self.display_actions(DisplayActions.UPDATE)
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
|
||||||
def load_game(self) -> None:
|
def load_game(self) -> None:
|
||||||
|
@ -145,7 +166,14 @@ class Game:
|
||||||
file_path = ResourceManager.get_config_path("save.json")
|
file_path = ResourceManager.get_config_path("save.json")
|
||||||
if os.path.isfile(file_path):
|
if os.path.isfile(file_path):
|
||||||
with open(file_path, "r") as f:
|
with open(file_path, "r") as f:
|
||||||
self.load_state(json.loads(f.read()))
|
try:
|
||||||
|
state = json.loads(f.read())
|
||||||
|
self.load_state(state)
|
||||||
|
except JSONDecodeError:
|
||||||
|
self.message = "The JSON file is not correct.\n" \
|
||||||
|
"Your save seems corrupted. It got deleted."
|
||||||
|
os.unlink(file_path)
|
||||||
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
|
||||||
def save_game(self) -> None:
|
def save_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from squirrelbattle.resources import ResourceManager
|
||||||
|
|
||||||
|
from squirrelbattle.enums import DisplayActions
|
||||||
|
|
||||||
from squirrelbattle.bootstrap import Bootstrap
|
from squirrelbattle.bootstrap import Bootstrap
|
||||||
from squirrelbattle.display.display import Display
|
from squirrelbattle.display.display import Display
|
||||||
from squirrelbattle.display.display_manager import DisplayManager
|
from squirrelbattle.display.display_manager import DisplayManager
|
||||||
|
@ -41,6 +45,27 @@ class TestGame(unittest.TestCase):
|
||||||
new_state = self.game.save_state()
|
new_state = self.game.save_state()
|
||||||
self.assertEqual(old_state, new_state)
|
self.assertEqual(old_state, new_state)
|
||||||
|
|
||||||
|
# Error on loading save
|
||||||
|
with open(ResourceManager.get_config_path("save.json"), "w") as f:
|
||||||
|
f.write("I am not a JSON file")
|
||||||
|
self.assertIsNone(self.game.message)
|
||||||
|
self.game.load_game()
|
||||||
|
self.assertIsNotNone(self.game.message)
|
||||||
|
self.game.message = None
|
||||||
|
|
||||||
|
with open(ResourceManager.get_config_path("save.json"), "w") as f:
|
||||||
|
f.write("{}")
|
||||||
|
self.assertIsNone(self.game.message)
|
||||||
|
self.game.load_game()
|
||||||
|
self.assertIsNotNone(self.game.message)
|
||||||
|
self.game.message = None
|
||||||
|
|
||||||
|
# Load game with a dead player
|
||||||
|
self.game.map.remove_entity(self.game.player)
|
||||||
|
self.game.save_game()
|
||||||
|
self.game.load_game()
|
||||||
|
self.assertIsNotNone(self.game.message)
|
||||||
|
|
||||||
def test_bootstrap_fail(self) -> None:
|
def test_bootstrap_fail(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure that the test can't play the game,
|
Ensure that the test can't play the game,
|
||||||
|
@ -292,3 +317,13 @@ class TestGame(unittest.TestCase):
|
||||||
Check that some functions are not implemented, only for coverage.
|
Check that some functions are not implemented, only for coverage.
|
||||||
"""
|
"""
|
||||||
self.assertRaises(NotImplementedError, Display.display, None)
|
self.assertRaises(NotImplementedError, Display.display, None)
|
||||||
|
|
||||||
|
def test_messages(self) -> None:
|
||||||
|
"""
|
||||||
|
Display error messages.
|
||||||
|
"""
|
||||||
|
self.game.message = "I am an error"
|
||||||
|
self.game.display_actions(DisplayActions.UPDATE)
|
||||||
|
self.game.display_actions(DisplayActions.REFRESH)
|
||||||
|
self.game.handle_key_pressed(None, "random key")
|
||||||
|
self.assertIsNone(self.game.message)
|
||||||
|
|
Loading…
Reference in New Issue