Merge branch 'game-logs' into 'master'

Game logs, see #12

See merge request ynerant/squirrel-battle!18
This commit is contained in:
ynerant 2020-11-19 22:48:39 +01:00
commit b7207bb738
9 changed files with 86 additions and 12 deletions

View File

@ -33,7 +33,7 @@ class Display:
self.width = width self.width = width
self.height = height self.height = height
if hasattr(self, "pad") and resize_pad: if hasattr(self, "pad") and resize_pad:
self.pad.resize(self.height - 1, self.width - 1) self.pad.resize(self.height, self.width)
def refresh(self, *args, resize_pad: bool = True) -> None: def refresh(self, *args, resize_pad: bool = True) -> None:
if len(args) == 4: if len(args) == 4:

View File

@ -3,6 +3,7 @@ from squirrelbattle.display.mapdisplay import MapDisplay
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
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
from squirrelbattle.game import Game, GameMode from squirrelbattle.game import Game, GameMode
@ -20,8 +21,10 @@ class DisplayManager:
self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
screen, pack) screen, pack)
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
self.logsdisplay = LogsDisplay(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.update_game_components() self.update_game_components()
def handle_display_action(self, action: DisplayActions) -> None: def handle_display_action(self, action: DisplayActions) -> None:
@ -36,6 +39,7 @@ class DisplayManager:
self.mapdisplay.update_map(self.game.map) self.mapdisplay.update_map(self.game.map)
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)
def refresh(self) -> None: def refresh(self) -> None:
if self.game.state == GameMode.PLAY: if self.game.state == GameMode.PLAY:
@ -43,7 +47,9 @@ class DisplayManager:
self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.cols, self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.cols,
resize_pad=False) resize_pad=False)
self.statsdisplay.refresh(self.rows * 4 // 5, 0, self.statsdisplay.refresh(self.rows * 4 // 5, 0,
self.rows // 5, self.cols) self.rows // 10, self.cols)
self.logsdisplay.refresh(self.rows * 9 // 10, 0,
self.rows // 10, self.cols)
if self.game.state == GameMode.MAINMENU: if self.game.state == GameMode.MAINMENU:
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:

View File

@ -0,0 +1,23 @@
from squirrelbattle.display.display import Display
from squirrelbattle.interfaces import Logs
class LogsDisplay(Display):
def __init__(self, *args) -> None:
super().__init__(*args)
self.pad = self.newpad(self.rows, self.cols)
def update_logs(self, logs: Logs) -> None:
self.logs = logs
def display(self) -> None:
print(type(self.logs.messages), flush=True)
messages = self.logs.messages[-self.height:]
messages = messages[::-1]
self.pad.clear()
for i in range(min(self.height, len(messages))):
self.pad.addstr(self.height - i - 1, self.x,
messages[i][:self.width])
self.pad.refresh(0, 0, self.y, self.x, self.y + self.height,
self.x + self.width)

View File

@ -44,7 +44,7 @@ class Monster(FightingEntity):
next_y, next_x = target.paths[(self.y, self.x)] next_y, next_x = target.paths[(self.y, self.x)]
moved = self.check_move(next_y, next_x, True) moved = self.check_move(next_y, next_x, True)
if not moved and self.distance_squared(target) <= 1: if not moved and self.distance_squared(target) <= 1:
self.hit(target) self.map.logs.add_message(self.hit(target))
else: else:
for _ in range(100): for _ in range(100):
if choice([self.move_up, self.move_down, if choice([self.move_up, self.move_down,

View File

@ -72,7 +72,7 @@ class Player(FightingEntity):
for entity in self.map.entities: for entity in self.map.entities:
if entity.y == y and entity.x == x: if entity.y == y and entity.x == x:
if entity.is_fighting_entity(): if entity.is_fighting_entity():
self.hit(entity) self.map.logs.add_message(self.hit(entity))
if entity.dead: if entity.dead:
self.add_xp(randint(3, 7)) self.add_xp(randint(3, 7))
return True return True

View File

@ -6,7 +6,7 @@ import sys
from .entities.player import Player from .entities.player import Player
from .enums import GameMode, KeyValues, DisplayActions from .enums import GameMode, KeyValues, DisplayActions
from .interfaces import Map from .interfaces import Map, Logs
from .resources import ResourceManager from .resources import ResourceManager
from .settings import Settings from .settings import Settings
from . import menus from . import menus
@ -33,6 +33,7 @@ class Game:
self.settings.load_settings() self.settings.load_settings()
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()
def new_game(self) -> None: def new_game(self) -> None:
""" """
@ -40,6 +41,8 @@ class Game:
""" """
# TODO generate a new map procedurally # TODO generate a new map procedurally
self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt"))
self.map.logs = self.logs
self.logs.clear()
self.player = Player() self.player = Player()
self.map.add_entity(self.player) self.map.add_entity(self.player)
self.player.move(self.map.start_y, self.map.start_x) self.player.move(self.map.start_y, self.map.start_x)

View File

@ -7,6 +7,26 @@ from typing import List, Optional
from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.display.texturepack import TexturePack
class Logs:
"""
The logs object stores the messages to display. It is encapsulating a list
of such messages, to allow multiple pointers to keep track of it even if
the list was to be reassigned.
"""
def __init__(self) -> None:
self.messages = []
def add_message(self, msg: str) -> None:
self.messages.append(msg)
def add_messages(self, msg: List[str]) -> None:
self.messages += msg
def clear(self) -> None:
self.messages = []
class Map: class Map:
""" """
Object that represents a Map with its width, height Object that represents a Map with its width, height
@ -18,6 +38,7 @@ class Map:
start_x: int start_x: int
tiles: List[List["Tile"]] tiles: List[List["Tile"]]
entities: List["Entity"] entities: List["Entity"]
logs: Logs
# coordinates of the point that should be # coordinates of the point that should be
# on the topleft corner of the screen # on the topleft corner of the screen
currentx: int currentx: int
@ -31,6 +52,7 @@ class Map:
self.start_x = start_x self.start_x = start_x
self.tiles = tiles self.tiles = tiles
self.entities = [] self.entities = []
self.logs = Logs()
def add_entity(self, entity: "Entity") -> None: def add_entity(self, entity: "Entity") -> None:
""" """
@ -362,19 +384,22 @@ class FightingEntity(Entity):
def dead(self) -> bool: def dead(self) -> bool:
return self.health <= 0 return self.health <= 0
def hit(self, opponent: "FightingEntity") -> None: def hit(self, opponent: "FightingEntity") -> str:
""" """
Deals damage to the opponent, based on the stats Deals damage to the opponent, based on the stats
""" """
opponent.take_damage(self, self.strength) return f"{self.name} hits {opponent.name}. "\
+ opponent.take_damage(self, self.strength)
def take_damage(self, attacker: "Entity", amount: int) -> None: def take_damage(self, attacker: "Entity", amount: int) -> str:
""" """
Take damage from the attacker, based on the stats Take damage from the attacker, based on the stats
""" """
self.health -= amount self.health -= amount
if self.health <= 0: if self.health <= 0:
self.die() self.die()
return f"{self.name} takes {amount} damage."\
+ (f" {self.name} dies." if self.health <= 0 else "")
def die(self) -> None: def die(self) -> None:
""" """

View File

@ -42,9 +42,11 @@ class TestEntities(unittest.TestCase):
self.assertEqual(entity.maxhealth, entity.health) self.assertEqual(entity.maxhealth, entity.health)
self.assertEqual(entity.strength, 2) self.assertEqual(entity.strength, 2)
for _ in range(9): for _ in range(9):
self.assertIsNone(entity.hit(entity)) self.assertEqual(entity.hit(entity),
"beaver hits beaver. beaver takes 2 damage.")
self.assertFalse(entity.dead) self.assertFalse(entity.dead)
self.assertIsNone(entity.hit(entity)) self.assertEqual(entity.hit(entity), "beaver hits beaver. "
+ "beaver takes 2 damage. beaver dies.")
self.assertTrue(entity.dead) self.assertTrue(entity.dead)
entity = Rabbit() entity = Rabbit()
@ -64,6 +66,9 @@ class TestEntities(unittest.TestCase):
self.map.tick() self.map.tick()
self.assertTrue(entity.y == 2 and entity.x == 6) self.assertTrue(entity.y == 2 and entity.x == 6)
self.assertEqual(old_health - entity.strength, self.player.health) self.assertEqual(old_health - entity.strength, self.player.health)
self.assertEqual(self.map.logs.messages[-1],
f"{entity.name} hits {self.player.name}. \
{self.player.name} takes {entity.strength} damage.")
# Fight the rabbit # Fight the rabbit
old_health = entity.health old_health = entity.health
@ -156,7 +161,7 @@ class TestEntities(unittest.TestCase):
self.assertFalse(player.move_up()) self.assertFalse(player.move_up())
self.assertTrue(player.move_left()) self.assertTrue(player.move_left())
self.assertFalse(player.move_left()) self.assertFalse(player.move_left())
for i in range(8): for _ in range(8):
self.assertTrue(player.move_down()) self.assertTrue(player.move_down())
self.assertFalse(player.move_down()) self.assertFalse(player.move_down())
self.assertTrue(player.move_right()) self.assertTrue(player.move_right())

View File

@ -17,6 +17,7 @@ class TestGame(unittest.TestCase):
""" """
self.game = Game() self.game = Game()
self.game.new_game() self.game.new_game()
self.game.logs.add_message("Hello World !")
display = DisplayManager(None, self.game) display = DisplayManager(None, self.game)
self.game.display_actions = display.handle_display_action self.game.display_actions = display.handle_display_action
@ -256,6 +257,17 @@ class TestGame(unittest.TestCase):
self.game.handle_key_pressed(KeyValues.ENTER) self.game.handle_key_pressed(KeyValues.ENTER)
self.assertEqual(self.game.state, GameMode.MAINMENU) self.assertEqual(self.game.state, GameMode.MAINMENU)
def test_logs(self) -> None:
"""
Tests the use of logs
"""
self.assertEqual(self.game.logs.messages, ["Hello World !"])
self.game.logs.add_messages(["Hello", "World"])
self.assertEqual(self.game.logs.messages, ["Hello World !",
"Hello", "World"])
self.game.logs.clear()
self.assertEqual(self.game.logs.messages, [])
def test_dead_screen(self) -> None: def test_dead_screen(self) -> None:
""" """
Kill player and render dead screen. Kill player and render dead screen.