Merged master into game

This commit is contained in:
Nicolas Margulies 2020-11-06 16:49:39 +01:00
commit 065f648118
13 changed files with 322 additions and 69 deletions

3
.gitignore vendored
View File

@ -8,3 +8,6 @@ venv/
.pytest_cache/
__pycache__
# Don't commit settings
settings.json

View File

@ -1,33 +1,36 @@
from ..interfaces import Entity, FightingEntity
from ..interfaces import Entity, FightingEntity, Map
class Item(Entity):
held: bool
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
super().__init__(*args, **kwargs)
self.held = False
def drop(self, x:int, y:int):
def drop(self, y: int, x: int) -> None:
self.held = False
self.move(x, y)
self.move(y, x)
def hold(self):
def hold(self) -> None:
self.held = True
class Bomb(Item):
damage: int = 5
exploding: bool
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
super().__init__(*args, **kwargs)
self.exploding = False
def drop(self, x:int, y:int):
super.drop(self, x, y)
def drop(self, x: int, y: int) -> None:
super().drop(x, y)
self.exploding = True
def act(self, map):
def act(self, m: Map) -> None:
if self.exploding:
for e in map.entities:
if abs (e.x - self.x) + abs (e.y - self.y) <= 1 and isinstance(e,FightingEntity):
for e in m.entities:
if abs(e.x - self.x) + abs(e.y - self.y) <= 1 and \
isinstance(e, FightingEntity):
e.take_damage(self, self.damage)

View File

@ -1,9 +1,11 @@
from ..interfaces import FightingEntity
from ..interfaces import FightingEntity, Map
class Monster(FightingEntity):
def act(self, map):
def act(self, m: Map) -> None:
pass
class Squirrel(Monster):
maxhealth = 10
strength = 3

View File

@ -1,5 +1,6 @@
from ..interfaces import FightingEntity
class Player(FightingEntity):
maxhealth = 20
strength = 5

View File

@ -1,5 +1,8 @@
from typing import Any
from .interfaces import Map
from .mapdisplay import MapDisplay
from .settings import Settings
from .term_manager import TermManager
@ -8,7 +11,9 @@ class Game:
def init(self) -> None:
Game.INSTANCE = self
self.key_handler = self.player_move
self.settings = Settings()
self.settings.load_settings()
self.settings.write_settings()
def new_game(self):
# TODO generate a new map procedurally
@ -29,9 +34,9 @@ class Game:
screen.refresh()
self.d.display(self.player.getPosY(), self.player.getPosX())
key = screen.getkey()
self.key_handler(key)
self.handle_key_pressed(key)
def player_move(self, key):
def handle_key_pressed(self, key: str) -> None:
# TODO load keys from settings
if key == 'z' or key == 'KEY_UP':
self.player.move_up()
@ -48,20 +53,14 @@ class Player:
y: int = 0
x: int = 0
def move_up(self):
def move_up(self) -> None:
self.y -= 1
def move_down(self):
def move_down(self) -> None:
self.y += 1
def move_left(self):
def move_left(self) -> None:
self.x -= 1
def move_right(self):
def move_right(self) -> None:
self.x += 1
def getPosX(self):
return self.x
def getPosY(self):
return self.y

View File

@ -11,11 +11,12 @@ class Map:
height: int
tiles: list
def __init__(self, width: int, height: int, tiles: list, entities = []):
def __init__(self, width: int, height: int, tiles: list,
entities: list = None):
self.width = width
self.height = height
self.tiles = tiles
self.entities = entities
self.entities = entities or []
@staticmethod
def load(filename: str):
@ -40,7 +41,6 @@ class Map:
return Map(width, height, tiles, [])
def draw_string(self) -> str:
"""
Draw the current map as a string object that can be rendered
@ -66,31 +66,43 @@ class Tile(Enum):
class Entity:
x: int
y: int
x: int
def move(self, x: int, y: int) -> None:
self.x = x
def __init__(self):
self.y = 0
self.x = 0
def move(self, y: int, x: int) -> None:
self.y = y
self.x = x
def act(self, m:Map):
def act(self, m: Map) -> None:
"""
Define the action of the entity that is ran each tick.
By default, does nothing.
"""
pass
class FightingEntity(Entity):
maxhealth: int
health: int
strength: int
dead: bool
def __init__(self):
super().__init__()
self.health = self.maxhealth
self.dead = False
def hit(self, opponent) -> None:
def hit(self, opponent: "FightingEntity") -> None:
opponent.take_damage(self, self.strength)
def take_damage(self, attacker, amount:int) -> None:
def take_damage(self, attacker: "Entity", amount: int) -> None:
self.health -= amount
if self.health <= 0:
self.die()
def die(self) -> None:
pass
self.dead = True

View File

@ -1,14 +0,0 @@
import unittest
from dungeonbattle.interfaces import Map
class TestInterfaces(unittest.TestCase):
def test_map(self) -> None:
"""
Create a map and check that it is well parsed.
"""
m = Map.load_from_string(".█\n█.\n")
self.assertEqual(m.width, 2)
self.assertEqual(m.height, 2)
self.assertEqual(m.draw_string(), ".█\n█.")

View File

@ -1,19 +1,29 @@
#!/usr/bin/env python
import curses
from typing import Any
from dungeonbattle.interfaces import Map
class MapDisplay:
<<<<<<< HEAD
def __init__(self, m: Map):
self.map = m
self.pad = curses.newpad(m.height, m.width+1)
=======
class MapDisplay:
def __init__(self, m: Map, player: Any):
# TODO Type the player field with the good type
self.map = m
self.pad = curses.newpad(m.height, m.width + 1)
self.player = player
>>>>>>> master
def update_pad(self):
def update_pad(self) -> None:
self.pad.addstr(0, 0, self.map.draw_string())
for e in self.map.entities:
self.pad.addch(e.y, e.x, e.img)
def display(self, y, x):
def display(self, y: int, x: int) -> None:
deltay, deltax = (curses.LINES // 2) + 1, (curses.COLS // 2) + 1
pminrow, pmincol = y - deltay, x - deltax
sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)

91
dungeonbattle/settings.py Normal file
View File

@ -0,0 +1,91 @@
import json
import os
from typing import Any, Generator
class Settings:
"""
This class stores the settings of the game.
Settings can be get by using for example settings.TEXTURE_PACK directly.
The comment can be get by using settings.get_comment('TEXTURE_PACK').
We can define the setting by simply use settings.TEXTURE_PACK = 'new_key'
"""
def __init__(self):
self.KEY_UP_PRIMARY = \
['z', 'Touche principale pour aller vers le haut']
self.KEY_UP_SECONDARY = \
['KEY_UP', 'Touche secondaire pour aller vers le haut']
self.KEY_DOWN_PRIMARY = \
['s', 'Touche principale pour aller vers le bas']
self.KEY_DOWN_SECONDARY = \
['KEY_DOWN', 'Touche secondaire pour aller vers le bas']
self.KEY_LEFT_PRIMARY = \
['q', 'Touche principale pour aller vers la gauche']
self.KEY_LEFT_SECONDARY = \
['KEY_LEFT', 'Touche secondaire pour aller vers la gauche']
self.KEY_RIGHT_PRIMARY = \
['d', 'Touche principale pour aller vers la droite']
self.KEY_RIGHT_SECONDARY = \
['KEY_RIGHT', 'Touche secondaire pour aller vers la droite']
self.TEXTURE_PACK = ['ASCII', 'Pack de textures utilisé']
def __getattribute__(self, item: str) -> Any:
superattribute = super().__getattribute__(item)
if item.isupper() and item in self.settings_keys:
return superattribute[0]
return superattribute
def __setattr__(self, name: str, value: Any) -> None:
if name in self.settings_keys:
object.__getattribute__(self, name)[0] = value
return
return super().__setattr__(name, value)
def get_comment(self, item: str) -> str:
"""
Retrieve the comment of a setting.
"""
if item in self.settings_keys:
return object.__getattribute__(self, item)[1]
for key in self.settings_keys:
if getattr(self, key) == item:
return object.__getattribute__(self, key)[1]
@property
def settings_keys(self) -> Generator[str, Any, None]:
"""
Get the list of all parameters.
"""
return (key for key in self.__dict__)
def loads_from_string(self, json_str: str) -> None:
"""
Dump settings
"""
d = json.loads(json_str)
for key in d:
setattr(self, key, d[key])
def dumps_to_string(self) -> str:
"""
Dump settings
"""
d = dict()
for key in self.settings_keys:
d[key] = getattr(self, key)
return json.dumps(d, indent=4)
def load_settings(self) -> None:
"""
Loads the settings from a file
"""
if os.path.isfile("settings.json"):
with open("settings.json", "r") as f:
self.loads_from_string(f.read())
def write_settings(self) -> None:
"""
Dumps the settings into a file
"""
with open("settings.json", "w") as f:
f.write(self.dumps_to_string())

View File

View File

@ -0,0 +1,81 @@
import unittest
from dungeonbattle.entities.items import Bomb, Item
from dungeonbattle.entities.monsters import Squirrel
from dungeonbattle.entities.player import Player
from dungeonbattle.interfaces import Entity, Map
class TestEntities(unittest.TestCase):
def setUp(self) -> None:
"""
Load example map that can be used in tests.
"""
self.map = Map.load("example_map.txt")
def test_basic_entities(self) -> None:
"""
Test some random stuff with basic entities.
"""
entity = Entity()
entity.move(42, 64)
self.assertEqual(entity.y, 42)
self.assertEqual(entity.x, 64)
self.assertIsNone(entity.act(self.map))
def test_fighting_entities(self) -> None:
"""
Test some random stuff with fighting entities.
"""
entity = Squirrel()
self.assertIsNone(entity.act(self.map))
self.assertEqual(entity.maxhealth, 10)
self.assertEqual(entity.maxhealth, entity.health)
self.assertEqual(entity.strength, 3)
self.assertIsNone(entity.hit(entity))
self.assertFalse(entity.dead)
self.assertIsNone(entity.hit(entity))
self.assertFalse(entity.dead)
self.assertIsNone(entity.hit(entity))
self.assertFalse(entity.dead)
self.assertIsNone(entity.hit(entity))
self.assertTrue(entity.dead)
def test_items(self) -> None:
"""
Test some random stuff with items.
"""
item = Item()
self.assertFalse(item.held)
item.hold()
self.assertTrue(item.held)
item.drop(42, 42)
self.assertEqual(item.y, 42)
self.assertEqual(item.x, 42)
def test_bombs(self) -> None:
"""
Test some random stuff with bombs.
"""
item = Bomb()
squirrel = Squirrel()
self.map.entities.append(item)
self.map.entities.append(squirrel)
squirrel.health = 2
squirrel.move(41, 42)
item.act(self.map)
self.assertFalse(squirrel.dead)
item.drop(42, 42)
self.assertEqual(item.y, 42)
self.assertEqual(item.x, 42)
item.act(self.map)
self.assertTrue(squirrel.dead)
def test_players(self) -> None:
"""
Test some random stuff with players.
"""
player = Player()
self.assertEqual(player.strength, 5)
self.assertEqual(player.health, player.maxhealth)
self.assertEqual(player.maxhealth, 20)

View File

@ -0,0 +1,33 @@
import unittest
from dungeonbattle.interfaces import Map, Tile
class TestInterfaces(unittest.TestCase):
def test_map(self) -> None:
"""
Create a map and check that it is well parsed.
"""
m = Map.load_from_string(".█\n█.\n")
self.assertEqual(m.width, 2)
self.assertEqual(m.height, 2)
self.assertEqual(m.draw_string(), ".█\n█.")
def test_load_map(self) -> None:
"""
Try to load a map from a file.
"""
m = Map.load("example_map.txt")
self.assertEqual(m.width, 52)
self.assertEqual(m.height, 17)
def test_tiles(self) -> None:
"""
Test some things about tiles.
"""
self.assertFalse(Tile.FLOOR.is_wall())
self.assertTrue(Tile.WALL.is_wall())
self.assertFalse(Tile.EMPTY.is_wall())
self.assertTrue(Tile.FLOOR.can_walk())
self.assertFalse(Tile.WALL.can_walk())
self.assertTrue(Tile.EMPTY.can_walk())

View File

@ -0,0 +1,32 @@
import unittest
from dungeonbattle.settings import Settings
class TestSettings(unittest.TestCase):
def test_settings(self) -> None:
"""
Ensure that settings are well loaded.
"""
settings = Settings()
self.assertEqual(settings.KEY_UP_PRIMARY, 'z')
self.assertEqual(settings.KEY_DOWN_PRIMARY, 's')
self.assertEqual(settings.KEY_LEFT_PRIMARY, 'q')
self.assertEqual(settings.KEY_RIGHT_PRIMARY, 'd')
self.assertEqual(settings.KEY_UP_SECONDARY, 'KEY_UP')
self.assertEqual(settings.KEY_DOWN_SECONDARY, 'KEY_DOWN')
self.assertEqual(settings.KEY_LEFT_SECONDARY, 'KEY_LEFT')
self.assertEqual(settings.KEY_RIGHT_SECONDARY, 'KEY_RIGHT')
self.assertEqual(settings.TEXTURE_PACK, 'ASCII')
self.assertEqual(settings.get_comment(settings.TEXTURE_PACK),
settings.get_comment('TEXTURE_PACK'))
self.assertEqual(settings.get_comment(settings.TEXTURE_PACK),
'Pack de textures utilisé')
settings.TEXTURE_PACK = 'UNICODE'
self.assertEqual(settings.TEXTURE_PACK, 'UNICODE')
settings.write_settings()
settings.load_settings()
self.assertEqual(settings.TEXTURE_PACK, 'UNICODE')