From 428bbae7360915b40e3e49bd04778c79c35f335b Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 4 Dec 2020 16:02:48 +0100 Subject: [PATCH 001/122] Added base files for map generation and main loop for random walk generation --- dungeonbattle/mapgeneration/__init__.py | 0 dungeonbattle/mapgeneration/randomwalk.py | 44 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 dungeonbattle/mapgeneration/__init__.py create mode 100644 dungeonbattle/mapgeneration/randomwalk.py diff --git a/dungeonbattle/mapgeneration/__init__.py b/dungeonbattle/mapgeneration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py new file mode 100644 index 0000000..c0c807d --- /dev/null +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -0,0 +1,44 @@ +from random import choice, random, randint +from dungeonbattle.interfaces import Map, Tile + + class Generator: + + def __init__(self, params): + self.params = params + + def run(self): + width, height = self.params["width"], self.params["height"] + walkers = [Walker(width//2, height//2)] + grid = [[Tile.WALL] * width] * height + count = 0 + while count < self.params["fill"] * width*height: + # because we can't add or remove walkers while looping over the pop + # we need lists to keep track of what will be the walkers for the + # next iteration of the main loop + next_walker_pop = [] + + for walker in walkers: + if grid[walker.y][walker.x] == Tile.WALL: + count += 1 + grid[walker.y][walker.x] = Tile.EMPTY + if random() < self.params["turn_chance"]: + walker.random_turn() + walker.move_in_bounds(width, height) + if random() > self.params["death_chance"]: + next_walker_pop.append(walker) + + # we use a second loop for spliting so we're not bothered by cases + # like a walker not spliting because we hit the population cap even + # though the next one would have died and freed a place + # not a big if it happened though + for walker in walkers: + if len(next_walker_pop) < self.params["max_walkers"]: + if random() < self.params["split_chance"]: + next_walker_pop.append(walker.split()) + walkers = next_walker_pop + + start_x, start_y = randint(0, width), randint(0, height) + while grid[start_y][start_x] != Tile.EMPTY: + start_x, start_y = randint(0, width), randint(0, height) + + return Map(width, height, grid, start_x, start_y) -- 2.39.5 From a5c53c898e90ae3215978f7cd3f2d75f115b529a Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 4 Dec 2020 18:01:54 +0100 Subject: [PATCH 002/122] Implemented walker class and methods random_turn, next_pos, move_in_bounds --- dungeonbattle/mapgeneration/randomwalk.py | 41 +++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index c0c807d..3c42559 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -1,9 +1,46 @@ +from enum import Enum from random import choice, random, randint from dungeonbattle.interfaces import Map, Tile - class Generator: - def __init__(self, params): + + +class Directions(Enum): + up = auto() + down = auto() + left = auto() + right = auto() + + +class Walker: + + def __init__(self, x, y): + self.x = x + self.y = y + self.dir = choice(list(Directions)) + + def random_turn(self): + self.dir = choice(list(Directions)) + + def next_pos(self): + if self.dir == Directions.up: + return self.x, self.y + 1 + elif self.dir == Directions.down: + return self.x, self.y - 1 + elif self.dir == Directions.right: + return self.x + 1, self.y + elif self.dir == Directions.left: + return self.x - 1, self.y + + def move_in_bounds(self, width, height): + nx, ny = self.next_pos() + if 0 < nx < width and 0 < ny < height: + self.x, self.y = nx, ny + + +class Generator: + + def __init__(self, params = DEFAULT_PARAMS): self.params = params def run(self): -- 2.39.5 From bc9c7cd7f7f30dcd19acc77953d156b3fbff0fc2 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 4 Dec 2020 18:03:41 +0100 Subject: [PATCH 003/122] Finalised implementation of the walker class with method split --- dungeonbattle/mapgeneration/randomwalk.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index 3c42559..f0527b5 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -37,6 +37,11 @@ class Walker: if 0 < nx < width and 0 < ny < height: self.x, self.y = nx, ny + def split(): + child = Walker(self.x, self.y) + child.dir = self.dir + return child + class Generator: -- 2.39.5 From 3717429549dc2e35bfbffb0ebe8bfb0a5b27d33e Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 4 Dec 2020 18:04:50 +0100 Subject: [PATCH 004/122] Added some test default parameters for the random walk generator --- dungeonbattle/mapgeneration/randomwalk.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index f0527b5..35cfa47 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -3,6 +3,13 @@ from random import choice, random, randint from dungeonbattle.interfaces import Map, Tile +DEFAULT_PARAMS = {"split_chance" : .15, + "turn_chance" : .5, + "death_chance" : .1, + "max_walkers" : 15, + "width" : 100, + "height" : 100, + "fill" : .4} class Directions(Enum): -- 2.39.5 From 32e6eab9438abcbb4f05d4440a462358cc5d85a3 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Sun, 6 Dec 2020 23:55:57 +0100 Subject: [PATCH 005/122] Added import enum.auto to mapgeneration.randomwalk --- dungeonbattle/mapgeneration/randomwalk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index 35cfa47..3dc3906 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import auto, Enum from random import choice, random, randint from dungeonbattle.interfaces import Map, Tile -- 2.39.5 From 2a1be4233b3308afb8a7c9103253c67dbbc28941 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Mon, 7 Dec 2020 00:18:32 +0100 Subject: [PATCH 006/122] Fixed syntax error in Walker.split --- dungeonbattle/mapgeneration/randomwalk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index 3dc3906..60baaba 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -44,7 +44,7 @@ class Walker: if 0 < nx < width and 0 < ny < height: self.x, self.y = nx, ny - def split(): + def split(self): child = Walker(self.x, self.y) child.dir = self.dir return child -- 2.39.5 From 7cfe55f42cd23c7fbb0d2254abceaaaf837e8ecd Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Mon, 7 Dec 2020 00:24:31 +0100 Subject: [PATCH 007/122] Added a failsafe for cases where the walker population randomly dies out --- dungeonbattle/mapgeneration/randomwalk.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index 60baaba..3aaad70 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -66,6 +66,7 @@ class Generator: # next iteration of the main loop next_walker_pop = [] + failsafe = choice(walkers) for walker in walkers: if grid[walker.y][walker.x] == Tile.WALL: count += 1 @@ -76,6 +77,10 @@ class Generator: if random() > self.params["death_chance"]: next_walker_pop.append(walker) + # we make sure to never kill all walkers + if next_walker_pop == []: + next_walker_pop.append(failsafe) + # we use a second loop for spliting so we're not bothered by cases # like a walker not spliting because we hit the population cap even # though the next one would have died and freed a place -- 2.39.5 From d40a61554ed5cfbeaff61075bd098e64a65abe57 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 01:04:30 +0100 Subject: [PATCH 008/122] Changing the way the tile matrix is declared so that every column is represented by a different list --- dungeonbattle/mapgeneration/randomwalk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index 3aaad70..de08e9c 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -58,7 +58,7 @@ class Generator: def run(self): width, height = self.params["width"], self.params["height"] walkers = [Walker(width//2, height//2)] - grid = [[Tile.WALL] * width] * height + grid = [[Tile.WALL for _ in range(width)] for _ in range(height)] count = 0 while count < self.params["fill"] * width*height: # because we can't add or remove walkers while looping over the pop -- 2.39.5 From 021731b740e57633654add07b0a7fc56b8f2db40 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 01:09:49 +0100 Subject: [PATCH 009/122] Switching up the tiles used during generation to the correct ones --- dungeonbattle/mapgeneration/randomwalk.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index de08e9c..c9234f9 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -58,7 +58,7 @@ class Generator: def run(self): width, height = self.params["width"], self.params["height"] walkers = [Walker(width//2, height//2)] - grid = [[Tile.WALL for _ in range(width)] for _ in range(height)] + grid = [[Tile.EMPTY for _ in range(width)] for _ in range(height)] count = 0 while count < self.params["fill"] * width*height: # because we can't add or remove walkers while looping over the pop @@ -68,9 +68,9 @@ class Generator: failsafe = choice(walkers) for walker in walkers: - if grid[walker.y][walker.x] == Tile.WALL: + if grid[walker.y][walker.x] == Tile.EMPTY: count += 1 - grid[walker.y][walker.x] = Tile.EMPTY + grid[walker.y][walker.x] = Tile.FLOOR if random() < self.params["turn_chance"]: walker.random_turn() walker.move_in_bounds(width, height) @@ -91,8 +91,9 @@ class Generator: next_walker_pop.append(walker.split()) walkers = next_walker_pop + start_x, start_y = randint(0, width), randint(0, height) - while grid[start_y][start_x] != Tile.EMPTY: + while grid[start_y][start_x] != Tile.FLOOR: start_x, start_y = randint(0, width), randint(0, height) return Map(width, height, grid, start_x, start_y) -- 2.39.5 From 302017222d16aad3280d1e89c3a1bcbdfc86c365 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 01:11:07 +0100 Subject: [PATCH 010/122] Fixing the sampling of the starting position that caused out of bounds error --- dungeonbattle/mapgeneration/randomwalk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index c9234f9..e93a4e6 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -92,8 +92,8 @@ class Generator: walkers = next_walker_pop - start_x, start_y = randint(0, width), randint(0, height) + start_x, start_y = randint(0, width-1), randint(0, height-1) while grid[start_y][start_x] != Tile.FLOOR: - start_x, start_y = randint(0, width), randint(0, height) + start_x, start_y = randint(0, width-1), randint(0, height-1) return Map(width, height, grid, start_x, start_y) -- 2.39.5 From 45120d0c2b48661b4658a1b43d0c95fed4df42db Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 01:13:00 +0100 Subject: [PATCH 011/122] Integrating procedural generation into the game --- dungeonbattle/game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 46dd0a5..f8a79e1 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -6,6 +6,7 @@ from .enums import GameMode, KeyValues, DisplayActions from .interfaces import Map from .settings import Settings from . import menus +from .mapgeneration import randomwalk from typing import Callable @@ -31,8 +32,7 @@ class Game: """ Create a new game on the screen. """ - # TODO generate a new map procedurally - self.map = Map.load("resources/example_map_2.txt") + self.map = randomwalk.Generator().run() self.player = Player() self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) -- 2.39.5 From 29798c135e7a411f111908df74ab853bc9a2ee5d Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 01:24:20 +0100 Subject: [PATCH 012/122] Syntax change for the failsafe --- dungeonbattle/mapgeneration/randomwalk.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dungeonbattle/mapgeneration/randomwalk.py b/dungeonbattle/mapgeneration/randomwalk.py index e93a4e6..e4a95c8 100644 --- a/dungeonbattle/mapgeneration/randomwalk.py +++ b/dungeonbattle/mapgeneration/randomwalk.py @@ -66,7 +66,6 @@ class Generator: # next iteration of the main loop next_walker_pop = [] - failsafe = choice(walkers) for walker in walkers: if grid[walker.y][walker.x] == Tile.EMPTY: count += 1 @@ -79,7 +78,7 @@ class Generator: # we make sure to never kill all walkers if next_walker_pop == []: - next_walker_pop.append(failsafe) + next_walker_pop.append(choice(walkers)) # we use a second loop for spliting so we're not bothered by cases # like a walker not spliting because we hit the population cap even -- 2.39.5 From 8751120fe1f9be5275ec08c6a74bb862a89ab5d8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 02:17:00 +0100 Subject: [PATCH 013/122] Merge master into map_generation, there were some commit behind --- squirrelbattle/game.py | 4 ++-- squirrelbattle/mapgeneration/randomwalk.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 6d9e9e7..3f94fd7 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -11,6 +11,7 @@ import sys from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions from .interfaces import Map, Logs +from .mapgeneration import randomwalk from .resources import ResourceManager from .settings import Settings from . import menus @@ -47,8 +48,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 = randomwalk.Generator().run() self.map.logs = self.logs self.logs.clear() self.player = Player() diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index e4a95c8..ef0997d 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -1,6 +1,6 @@ from enum import auto, Enum from random import choice, random, randint -from dungeonbattle.interfaces import Map, Tile +from ..interfaces import Map, Tile DEFAULT_PARAMS = {"split_chance" : .15, -- 2.39.5 From 3c614dcca9e9cbd1c3f71ad8832212cefcaeb5c7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 02:19:59 +0100 Subject: [PATCH 014/122] Linting --- squirrelbattle/mapgeneration/randomwalk.py | 52 ++++++++++++---------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index ef0997d..3595098 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -1,15 +1,22 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from enum import auto, Enum from random import choice, random, randint +from typing import Tuple + from ..interfaces import Map, Tile -DEFAULT_PARAMS = {"split_chance" : .15, - "turn_chance" : .5, - "death_chance" : .1, - "max_walkers" : 15, - "width" : 100, - "height" : 100, - "fill" : .4} +DEFAULT_PARAMS = { + "split_chance": .15, + "turn_chance": .5, + "death_chance": .1, + "max_walkers": 15, + "width": 100, + "height": 100, + "fill": .4, +} class Directions(Enum): @@ -20,16 +27,15 @@ class Directions(Enum): class Walker: - - def __init__(self, x, y): + def __init__(self, x: int, y: int): self.x = x self.y = y self.dir = choice(list(Directions)) - def random_turn(self): + def random_turn(self) -> None: self.dir = choice(list(Directions)) - def next_pos(self): + def next_pos(self) -> Tuple[int, int]: if self.dir == Directions.up: return self.x, self.y + 1 elif self.dir == Directions.down: @@ -39,28 +45,29 @@ class Walker: elif self.dir == Directions.left: return self.x - 1, self.y - def move_in_bounds(self, width, height): + def move_in_bounds(self, width: int, height: int) -> None: nx, ny = self.next_pos() if 0 < nx < width and 0 < ny < height: self.x, self.y = nx, ny - def split(self): + def split(self) -> "Walker": child = Walker(self.x, self.y) child.dir = self.dir return child class Generator: - - def __init__(self, params = DEFAULT_PARAMS): + def __init__(self, params: dict = None): + if params is None: + params = DEFAULT_PARAMS self.params = params - def run(self): + def run(self) -> Map: width, height = self.params["width"], self.params["height"] - walkers = [Walker(width//2, height//2)] + walkers = [Walker(width // 2, height // 2)] grid = [[Tile.EMPTY for _ in range(width)] for _ in range(height)] count = 0 - while count < self.params["fill"] * width*height: + while count < self.params["fill"] * width * height: # because we can't add or remove walkers while looping over the pop # we need lists to keep track of what will be the walkers for the # next iteration of the main loop @@ -77,10 +84,10 @@ class Generator: next_walker_pop.append(walker) # we make sure to never kill all walkers - if next_walker_pop == []: + if not next_walker_pop: next_walker_pop.append(choice(walkers)) - # we use a second loop for spliting so we're not bothered by cases + # we use a second loop for spliting so we're not bothered by cases # like a walker not spliting because we hit the population cap even # though the next one would have died and freed a place # not a big if it happened though @@ -90,9 +97,8 @@ class Generator: next_walker_pop.append(walker.split()) walkers = next_walker_pop - - start_x, start_y = randint(0, width-1), randint(0, height-1) + start_x, start_y = randint(0, width - 1), randint(0, height - 1) while grid[start_y][start_x] != Tile.FLOOR: - start_x, start_y = randint(0, width-1), randint(0, height-1) + start_x, start_y = randint(0, width - 1), randint(0, height - 1) return Map(width, height, grid, start_x, start_y) -- 2.39.5 From 7fb743eb72958b4686b079846541819a4471163e Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 17:02:22 +0100 Subject: [PATCH 015/122] Switching up start_x and start_y so the player spawn is correctly set --- squirrelbattle/mapgeneration/randomwalk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index 3595098..5e5dbff 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -101,4 +101,4 @@ class Generator: while grid[start_y][start_x] != Tile.FLOOR: start_x, start_y = randint(0, width - 1), randint(0, height - 1) - return Map(width, height, grid, start_x, start_y) + return Map(width, height, grid, start_y, start_x) -- 2.39.5 From 3a8549cfcc867f5216a0dcfbcf5efb454bede17c Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 17:09:27 +0100 Subject: [PATCH 016/122] Added a method to interfaces.Map to get the neighbours of a given tile --- squirrelbattle/interfaces.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 3567ea0..50ebb2e 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -5,6 +5,7 @@ from enum import Enum, auto from math import sqrt from random import choice, randint from typing import List, Optional +from itertools import product from .display.texturepack import TexturePack from .translations import gettext as _ @@ -180,6 +181,13 @@ class Map: for entisave in d["entities"]: self.add_entity(dictclasses[entisave["type"]](**entisave)) + def large_neighbourhood(self, y, x): + neighbours = [] + for dy, dx in product([-1, 0, 1], [-1, 0, 1]): + if 0 < y+dy < self.height and 0 < x+dx < self.width: + neighbours.append([y+dy, x+dx]) + return neighbours + class Tile(Enum): """ -- 2.39.5 From 6a4d13c7264758f24490ade84c2eafb6d2d1a7a6 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 17:09:59 +0100 Subject: [PATCH 017/122] Walls now generate around the floor --- squirrelbattle/mapgeneration/randomwalk.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index 5e5dbff..ca7814b 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -16,6 +16,7 @@ DEFAULT_PARAMS = { "width": 100, "height": 100, "fill": .4, + "no_lone_walls": False, } @@ -101,4 +102,15 @@ class Generator: while grid[start_y][start_x] != Tile.FLOOR: start_x, start_y = randint(0, width - 1), randint(0, height - 1) - return Map(width, height, grid, start_y, start_x) + result = Map(width, height, grid, start_y, start_x) + + # post-processing: add walls + for x in range(width): + for y in range(height): + c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.neighbours_large(y, x)]) + if c == 4 and self.params["no_lone_walls"]: + result.tiles[y][x] = Tile.FLOOR + elif c > 0: + result.tiles[y][x] = Tile.WALL + + return result -- 2.39.5 From 757a460a44082f16849ca897b4cd3573945bac3e Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 17:13:12 +0100 Subject: [PATCH 018/122] Fix typo --- squirrelbattle/mapgeneration/randomwalk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index ca7814b..3ef2b2d 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -107,7 +107,7 @@ class Generator: # post-processing: add walls for x in range(width): for y in range(height): - c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.neighbours_large(y, x)]) + c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.large_neighbourhood(y, x)]) if c == 4 and self.params["no_lone_walls"]: result.tiles[y][x] = Tile.FLOOR elif c > 0: -- 2.39.5 From c8b07b3bf59175e2285fe57cb487b254cfd55f9a Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 17:17:11 +0100 Subject: [PATCH 019/122] Only empty tiles should be changed to walls, obviously... --- squirrelbattle/mapgeneration/randomwalk.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index 3ef2b2d..b913d1c 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -107,10 +107,11 @@ class Generator: # post-processing: add walls for x in range(width): for y in range(height): - c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.large_neighbourhood(y, x)]) - if c == 4 and self.params["no_lone_walls"]: - result.tiles[y][x] = Tile.FLOOR - elif c > 0: - result.tiles[y][x] = Tile.WALL + if grid[y][x] == Tile.EMPTY: + c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.large_neighbourhood(y, x)]) + if c == 4 and self.params["no_lone_walls"]: + result.tiles[y][x] = Tile.FLOOR + elif c > 0: + result.tiles[y][x] = Tile.WALL return result -- 2.39.5 From d3c14a48ee4168c709c8941e92319477550abed3 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 17:46:49 +0100 Subject: [PATCH 020/122] Add docstring for Map.large_neighbourhood --- squirrelbattle/interfaces.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 50ebb2e..9d13046 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -182,6 +182,10 @@ class Map: self.add_entity(dictclasses[entisave["type"]](**entisave)) def large_neighbourhood(self, y, x): + """ + Returns up to 8 nearby coordinates, in a 3x3 square around the input coordinate. + Does not return coordinates if they are out of bounds. + """ neighbours = [] for dy, dx in product([-1, 0, 1], [-1, 0, 1]): if 0 < y+dy < self.height and 0 < x+dx < self.width: -- 2.39.5 From 7667079aa3c026d8fb067f8453cda69bc7c3f7ef Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 18:33:16 +0100 Subject: [PATCH 021/122] Changed Map.large_neighbourhood so we can also request only immediate neighbours, ignoring diagonals --- squirrelbattle/interfaces.py | 11 ++++++----- squirrelbattle/mapgeneration/randomwalk.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 9d13046..41b11f0 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -180,14 +180,15 @@ class Map: dictclasses = Entity.get_all_entity_classes_in_a_dict() for entisave in d["entities"]: self.add_entity(dictclasses[entisave["type"]](**entisave)) - - def large_neighbourhood(self, y, x): + def neighbourhood(self, y, x, large=False): """ - Returns up to 8 nearby coordinates, in a 3x3 square around the input coordinate. - Does not return coordinates if they are out of bounds. + Returns up to 8 nearby coordinates, in a 3x3 square around the input coordinate if large is + set to True, or in a 5-square cross by default. Does not return coordinates if they are out + of bounds. """ neighbours = [] - for dy, dx in product([-1, 0, 1], [-1, 0, 1]): + dyxs = product([-1, 0, 1], [-1, 0, 1]) if large else [[0, -1], [0, 1], [-1, 0], [1, 0]] + for dy, dx in dyxs: if 0 < y+dy < self.height and 0 < x+dx < self.width: neighbours.append([y+dy, x+dx]) return neighbours diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index b913d1c..bd3a20d 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -108,7 +108,7 @@ class Generator: for x in range(width): for y in range(height): if grid[y][x] == Tile.EMPTY: - c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.large_neighbourhood(y, x)]) + c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.neighbourhood(y, x, large=True]) if c == 4 and self.params["no_lone_walls"]: result.tiles[y][x] = Tile.FLOOR elif c > 0: -- 2.39.5 From 18ca083ba213142416f7f7d28458f8c0ee3144b2 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 18:59:07 +0100 Subject: [PATCH 022/122] Added a connexity test --- squirrelbattle/tests/mapgeneration_test.py | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 squirrelbattle/tests/mapgeneration_test.py diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py new file mode 100644 index 0000000..abaed1b --- /dev/null +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -0,0 +1,29 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + +import unittest + +from squirrelbattle.interfaces import Map, Tile +from squirrelbattle.mapgeneration import randomwalk + + +class TestRandomWalk(unittest.TestCase): + def setUp(self) -> None: + self.generator = randomwalk.Generator() + + def test_starting(self) -> None: + """ + Create a map and check that the whole map is accessible from the starting position using a + depth-first search + """ + m = self.generator.run() + self.assertTrue(m.tiles[m.start_y][m.start_x].can_walk()) + + #DFS + queue = m.neighbourhood(m.start_y, m.start_x) + while queue != []: + y, x = queue.pop() + if m.tiles[y][x].can_walk(): + m.tiles[y][x] = Tile.WALL + queue += m.neighbourhood(y, x) + self.assertFalse(any([any([t.can_walk() for t in l]) for l in m.tiles])) -- 2.39.5 From deb52d73502eb481ffd208ea7c0c26d2e90f05d5 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 19:05:26 +0100 Subject: [PATCH 023/122] Adding a missing parenthesis --- squirrelbattle/mapgeneration/randomwalk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index bd3a20d..b3287ea 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -108,7 +108,7 @@ class Generator: for x in range(width): for y in range(height): if grid[y][x] == Tile.EMPTY: - c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.neighbourhood(y, x, large=True]) + c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.neighbourhood(y, x, large=True)]) if c == 4 and self.params["no_lone_walls"]: result.tiles[y][x] = Tile.FLOOR elif c > 0: -- 2.39.5 From fe9dfdf242f32cd02b2041415306f11ae81d948d Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 19:13:15 +0100 Subject: [PATCH 024/122] Syntax change in randomwalk.Generator.__init__ --- squirrelbattle/mapgeneration/randomwalk.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index b3287ea..32ec0d6 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -58,9 +58,7 @@ class Walker: class Generator: - def __init__(self, params: dict = None): - if params is None: - params = DEFAULT_PARAMS + def __init__(self, params: dict = DEFAULT_PARAMS): self.params = params def run(self) -> Map: -- 2.39.5 From 3d7667573e158c454031e9e6633f898151bf49ca Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 11 Dec 2020 19:14:28 +0100 Subject: [PATCH 025/122] Add testing for the no_lone_walls option --- squirrelbattle/tests/mapgeneration_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index abaed1b..ed0951c 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -9,7 +9,10 @@ from squirrelbattle.mapgeneration import randomwalk class TestRandomWalk(unittest.TestCase): def setUp(self) -> None: - self.generator = randomwalk.Generator() + #we set no_lone_walls to true for 100% coverage + params = randomwalk.DEFAULT_PARAMS + params["no_lone_walls"] = True + self.generator = randomwalk.Generator(params = params) def test_starting(self) -> None: """ -- 2.39.5 From 895abe88ad3ec947a9b6a0911f650ab774135fa9 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 19:14:25 +0100 Subject: [PATCH 026/122] Ensure that the neighboorhood is walkable in movement tests --- squirrelbattle/tests/game_test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index bc3ce12..8203299 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -12,6 +12,7 @@ from ..entities.items import Bomb, Heart, Sword from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode +from ..interfaces import Tile from ..menus import MainMenuValues from ..resources import ResourceManager from ..settings import Settings @@ -204,6 +205,12 @@ class TestGame(unittest.TestCase): self.game.map.remove_entity(entity) y, x = self.game.player.y, self.game.player.x + + # Ensure that the neighborhood is walkable + for dx in [-1, 0, 1]: + for dy in [-1, 0, 1]: + self.game.map.tiles[y + dy][x + dx] = Tile.FLOOR + self.game.handle_key_pressed(KeyValues.DOWN) new_y, new_x = self.game.player.y, self.game.player.x self.assertEqual(new_y, y + 1) -- 2.39.5 From 209bde5b5c23bc6c278e3cb1d4bf49bdf8aa5b1c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 19:21:02 +0100 Subject: [PATCH 027/122] Fix sunflowers and merchants since the position of the player is no longer fixed --- squirrelbattle/tests/game_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 8203299..b21a753 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -473,7 +473,7 @@ class TestGame(unittest.TestCase): self.game.state = GameMode.PLAY sunflower = Sunflower() - sunflower.move(2, 6) + sunflower.move(self.game.player.y + 1, self.game.player.x) self.game.map.add_entity(sunflower) # Does nothing @@ -504,15 +504,15 @@ class TestGame(unittest.TestCase): Sunflower.dialogue_option) # Test all directions to detect the friendly entity - self.game.player.move(3, 6) + self.game.player.move(sunflower.y + 1, sunflower.x) self.game.handle_key_pressed(KeyValues.CHAT) self.game.handle_key_pressed(KeyValues.UP) self.assertEqual(len(self.game.logs.messages), 3) - self.game.player.move(2, 7) + self.game.player.move(sunflower.y, sunflower.x + 1) self.game.handle_key_pressed(KeyValues.CHAT) self.game.handle_key_pressed(KeyValues.LEFT) self.assertEqual(len(self.game.logs.messages), 4) - self.game.player.move(2, 5) + self.game.player.move(sunflower.y, sunflower.x - 1) self.game.handle_key_pressed(KeyValues.CHAT) self.game.handle_key_pressed(KeyValues.RIGHT) self.assertEqual(len(self.game.logs.messages), 5) @@ -524,7 +524,7 @@ class TestGame(unittest.TestCase): self.game.state = GameMode.PLAY merchant = Merchant() - merchant.move(2, 6) + merchant.move(self.game.player.y + 1, self.game.player.x) self.game.map.add_entity(merchant) # Does nothing -- 2.39.5 From fb926f8c844c2e08ba8a368c427b6b6ffc5aaa34 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 19:27:57 +0100 Subject: [PATCH 028/122] Always use predefined map in game unit tests --- squirrelbattle/interfaces.py | 2 ++ squirrelbattle/tests/game_test.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 3f72682..ef74614 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -54,6 +54,8 @@ class Map: self.height = height self.start_y = start_y self.start_x = start_x + self.currenty = start_y + self.currentx = start_x self.tiles = tiles self.entities = [] self.logs = Logs() diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index b21a753..e149444 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -12,7 +12,7 @@ from ..entities.items import Bomb, Heart, Sword from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode -from ..interfaces import Tile +from ..interfaces import Tile, Map from ..menus import MainMenuValues from ..resources import ResourceManager from ..settings import Settings @@ -26,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.player.move(self.game.map.start_y, self.game.map.start_x) + self.game.map.add_entity(self.game.player) self.game.logs.add_message("Hello World !") display = DisplayManager(None, self.game) self.game.display_actions = display.handle_display_action -- 2.39.5 From 5fbb9181320e3bf742a706639a303da2d8e6fc22 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 18 Dec 2020 17:05:50 +0100 Subject: [PATCH 029/122] Add walls even to map borders --- squirrelbattle/mapgeneration/randomwalk.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index 32ec0d6..3acb58a 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -111,5 +111,13 @@ class Generator: result.tiles[y][x] = Tile.FLOOR elif c > 0: result.tiles[y][x] = Tile.WALL + for x in range(width): + for y in [0, height-1]: + if grid[y][x] = Tile.FLOOR: + grid[y][x] = Tile.WALL + for y in range(height): + for y in [0, width-1]: + if grid[y][x] = Tile.FLOOR: + grid[y][x] = Tile.WALL return result -- 2.39.5 From ba3d979f9c6ca72ccefe98d12a11e53bcfb7077c Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 18 Dec 2020 18:10:52 +0100 Subject: [PATCH 030/122] Fix syntax error --- squirrelbattle/mapgeneration/randomwalk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index 3acb58a..d4669d4 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -113,11 +113,11 @@ class Generator: result.tiles[y][x] = Tile.WALL for x in range(width): for y in [0, height-1]: - if grid[y][x] = Tile.FLOOR: + if grid[y][x] == Tile.FLOOR: grid[y][x] = Tile.WALL for y in range(height): for y in [0, width-1]: - if grid[y][x] = Tile.FLOOR: + if grid[y][x] == Tile.FLOOR: grid[y][x] = Tile.WALL return result -- 2.39.5 From f5e5e365d47f0e49f98f02f5945c25487a24abe1 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 18 Dec 2020 20:02:37 +0100 Subject: [PATCH 031/122] Starting the implementation of the new map generator --- squirrelbattle/mapgeneration/broguelike.py | 51 ++++++++++++++++++++++ squirrelbattle/mapgeneration/randomwalk.py | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 squirrelbattle/mapgeneration/broguelike.py diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py new file mode 100644 index 0000000..7572b05 --- /dev/null +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -0,0 +1,51 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + +from enum import auto, Enum +from random import choice, random, randint + +from ..interfaces import Map, Tile + + +DEFAULT_PARAMS = { + "width" : 80, + "height" : 40, + "tries" : 600, + "max_rooms" : 99, + "cross_room" : 1, + "corridor" : .8, + "min_v_corridor" : + "max_v_corridor" : + "min_h_corridor" : + "max_h_corridor" : + "large_circular_room" : .10, + "circular_holes" : .5, +} + + +class Generator: + def __init__(self, params: dict = DEFAULT_PARAMS): + self.params = params + + def createCircularRoom(self): + if random() < self.params["large_circular_room"]: + r = randint(5, 10)**2 + else: + r = randint(2, 4)**2 + + room = [] + height = 2*r+2+self.params["max_h_corridor"] + width = 2*r+2+self.params["max_v_corridor"] + make_hole = random() < self.params["circular_holes"] + if make_hole: + r2 = randint(3, r-3) + for i in range(height): + room.append([]) + d = (i-height//2)**2 + for j in range(width): + if d + (j-width//2)**2 < r**2 and \ + (not(make_hole) or d + (j-width//2)**2 >= r2**2): + room[-1].append(Tile.FLOOR) + else: + room[-1].append(Tile.EMPTY) + return room diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index d4669d4..7cac8ff 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -116,7 +116,7 @@ class Generator: if grid[y][x] == Tile.FLOOR: grid[y][x] = Tile.WALL for y in range(height): - for y in [0, width-1]: + for x in [0, width-1]: if grid[y][x] == Tile.FLOOR: grid[y][x] = Tile.WALL -- 2.39.5 From 9fb366aaab6c599fa03b7f7ccbe8ca24de951941 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Thu, 7 Jan 2021 05:02:49 +0100 Subject: [PATCH 032/122] Make name follow style convention --- squirrelbattle/mapgeneration/broguelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 7572b05..b5ea122 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -27,7 +27,7 @@ class Generator: def __init__(self, params: dict = DEFAULT_PARAMS): self.params = params - def createCircularRoom(self): + def create_circular_room(self): if random() < self.params["large_circular_room"]: r = randint(5, 10)**2 else: -- 2.39.5 From 5579f5d7913c22b5aa0f5f9c8403ae0fbb44ea59 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Thu, 7 Jan 2021 07:06:08 +0100 Subject: [PATCH 033/122] Room now can now generate with a corridor; implemenent door placement finding --- squirrelbattle/mapgeneration/broguelike.py | 79 ++++++++++++++++++---- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index b5ea122..6b394d5 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from enum import auto, Enum -from random import choice, random, randint +from random import choice, random, randint, shuffle from ..interfaces import Map, Tile @@ -12,12 +12,13 @@ DEFAULT_PARAMS = { "height" : 40, "tries" : 600, "max_rooms" : 99, + "max_room_tries" : 15, "cross_room" : 1, - "corridor" : .8, - "min_v_corridor" : - "max_v_corridor" : - "min_h_corridor" : - "max_h_corridor" : + "corridor_chance" : .8, + "min_v_corr" : 2, + "max_v_corr" : 6, + "min_h_corr" : 4, + "max_h_corr" : 12, "large_circular_room" : .10, "circular_holes" : .5, } @@ -27,6 +28,49 @@ class Generator: def __init__(self, params: dict = DEFAULT_PARAMS): self.params = params + def corr_meta_info(self): + if random() < self.params["corridor_chance"]: + h_sup = randint(self.params["min_h_corr"], \ + self.params["max_h_corr"]) if random() < .5 else 0 + w_sup = 0 if h_sup else randint(self.params["min_w_corr"], \ + self.params["max_w_coor"]) + h_off = h_sup if random() < .5 else 0 + w_off = w_sup if random() < .5 else 0 + return h_sup, w_sup, h_off, w_off + return 0, 0, 0, 0 + + def attach_door(self, room, h_sup, w_sup, h_off, w_off): + l = h_sup + w_sup + dy, dx = 0, 0 + if l > 0: + if h_sup: + dy = -1 if h_off else 1 + else: + dx = -1 if w_off else 1 + else: + if random() < .5: + dy = -1 if random() < .5 else 1 + else: + dx = -1 if random() < .5 else 1 + + yxs = [i for i in range(len(room) * len(room[0]))] + shuffle(xys) + for POS in yxs: + y, x = POS // len(room), POS % len(room) + if room[y][x] == Tile.EMPTY: + if room[y-dy][x-dx] == Tile.FLOOR: + build_here = True + for i in range(l): + if room[y+i*dy][x+i*dx] != Tile.EMPTY: + build_here = False + break + if build_here: + for i in range(l): + room[y+i*dy][x+i*dx] == Tile.FLOOR + break + return y+l*dy, x+l*dx + + def create_circular_room(self): if random() < self.params["large_circular_room"]: r = randint(5, 10)**2 @@ -34,18 +78,25 @@ class Generator: r = randint(2, 4)**2 room = [] - height = 2*r+2+self.params["max_h_corridor"] - width = 2*r+2+self.params["max_v_corridor"] + + h_sup, w_sup, h_off, w_off = self.corr_meta_info() + + height = 2*r+2 + width = 2*r+2 make_hole = random() < self.params["circular_holes"] if make_hole: r2 = randint(3, r-3) - for i in range(height): + for i in range(height+h_sup): room.append([]) - d = (i-height//2)**2 - for j in range(width): - if d + (j-width//2)**2 < r**2 and \ - (not(make_hole) or d + (j-width//2)**2 >= r2**2): + d = (i-h_off-height//2)**2 + for j in range(width+w_sup): + if d + (j-w_off-width//2)**2 < r**2 and \ + (not(make_hole) or d + (j-w_off-width//2)**2 >= r2**2): room[-1].append(Tile.FLOOR) else: room[-1].append(Tile.EMPTY) - return room + + door_y, door_x = self.attach_doors(room, h_sup, w_sup, h_off, w_off) + + return room, doory, doorx + -- 2.39.5 From bb3422f7d85c48d2414241ea25a3c576e6345b01 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 03:19:59 +0100 Subject: [PATCH 034/122] Add main generation loop --- squirrelbattle/mapgeneration/broguelike.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 6b394d5..db81065 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -100,3 +100,28 @@ class Generator: return room, doory, doorx + def run(self): + height, width = self.params["height"], self.params["width"] + level = [[Tile.EMPTY for i in range(width)] for j in range(height)] + + # the starting room must have no corridor + mem, self.params["corridor"] = self.params["corridor"], 0 + starting_room, _, _, _, _ = self.create_random_room() + self.place_room(level, height//2, width//2, 0, 0, starting_room) + self.params["corridor"] = mem + + tries, rooms_built = 0, 0 + while tries < self.params["tries"] and rooms_built < self.params["max_rooms"]: + + room, door_y, door_x, dy, dx = self.create_random_room() + positions = [i for i in range()] + shuffle(positions) + for pos in positions: + y, x = pos // height, pos % width + if self.room_fits(level, y, x, room, door_y, door_x, dy, dx): + self.place_room(level, y, x, door_y, door_x, room) + + # post-processing + self.place_walls(level) + + return level -- 2.39.5 From 5cbf15bef5ffc698a33d44d7fccf31344847660f Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 03:37:10 +0100 Subject: [PATCH 035/122] Return value of Generator.run should be a Map --- squirrelbattle/mapgeneration/broguelike.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index db81065..f60d423 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -110,6 +110,12 @@ class Generator: self.place_room(level, height//2, width//2, 0, 0, starting_room) self.params["corridor"] = mem + # find a starting position + sy, sx = randint(0, height-1), randint(0, width-1) + while level[sy][sx] != Tile.FLOOR: + sy, sx = randint(0, height-1), randint(0, width-1) + + # now we loop until we've tried enough, or we've added enough rooms tries, rooms_built = 0, 0 while tries < self.params["tries"] and rooms_built < self.params["max_rooms"]: @@ -124,4 +130,4 @@ class Generator: # post-processing self.place_walls(level) - return level + return Map(width, height, level, sy, sx) -- 2.39.5 From ddbd0299a09110c6ed247cdac2be8dd247d0866b Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 03:38:37 +0100 Subject: [PATCH 036/122] Implement method room_fits --- squirrelbattle/mapgeneration/broguelike.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index f60d423..0a92180 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -28,6 +28,20 @@ class Generator: def __init__(self, params: dict = DEFAULT_PARAMS): self.params = params + @staticmethod + def room_fits(level, y, x, room, door_y, door_x, dy, dx): + if level[y][x] != Tile.EMPTY or level[y-dy][x-dx] != Tile.FLOOR: + return False + lh, lw = len(level), len(level[0]) + rh, rw = len(room), len(room[0]) + for ry in range(rh): + for rx in range(rw): + if room[y][x] == Tile.FLOOR: + ly, lx = ry - door_y, rx - door_x + if not(0 <= ly <= rh and 0 <= lx <= rw) or \ + level[ly][lx] == Tile.FLOOR: + return False + return True def corr_meta_info(self): if random() < self.params["corridor_chance"]: h_sup = randint(self.params["min_h_corr"], \ -- 2.39.5 From 42f0c195aa373b7a80f7206cf18c8201a0154dff Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 03:43:20 +0100 Subject: [PATCH 037/122] Add prototype for create_random_room; change return value of attach_doors and create_circular_room so we have info on door direction; minor syntax change --- squirrelbattle/mapgeneration/broguelike.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 0a92180..9dad093 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -69,8 +69,8 @@ class Generator: yxs = [i for i in range(len(room) * len(room[0]))] shuffle(xys) - for POS in yxs: - y, x = POS // len(room), POS % len(room) + for pos in yxs: + y, x = pos // len(room), pos % len(room) if room[y][x] == Tile.EMPTY: if room[y-dy][x-dx] == Tile.FLOOR: build_here = True @@ -82,7 +82,7 @@ class Generator: for i in range(l): room[y+i*dy][x+i*dx] == Tile.FLOOR break - return y+l*dy, x+l*dx + return y+l*dy, x+l*dx, dy, dx def create_circular_room(self): @@ -110,10 +110,13 @@ class Generator: else: room[-1].append(Tile.EMPTY) - door_y, door_x = self.attach_doors(room, h_sup, w_sup, h_off, w_off) + door_y, door_x, dy, dx = self.attach_doors(room, h_sup, w_sup, h_off, w_off) - return room, doory, doorx + return room, doory, doorx, dy, dx + def create_random_room(self): + return create_circular_room(self) + def run(self): height, width = self.params["height"], self.params["width"] level = [[Tile.EMPTY for i in range(width)] for j in range(height)] -- 2.39.5 From 3229eb8ea71dacaefc5a4c19f64a8cf8b741c52e Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 03:45:26 +0100 Subject: [PATCH 038/122] Implement place_room method --- squirrelbattle/mapgeneration/broguelike.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 9dad093..1a6f70c 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -42,6 +42,16 @@ class Generator: level[ly][lx] == Tile.FLOOR: return False return True + + @staticmethod + def place_room(level, y, x, door_y, door_x, room): + rh, rw = len(room), len(room[0]) + # maybe place Tile.DOOR here ? + level[door_y][door_x] = Tile.FLOOR + for ry in range(rh): + for rx in range(rw): + if room[y][x] == Tile.FLOOR: + level[y-door_y][y-door_x] = Tile.FLOOR def corr_meta_info(self): if random() < self.params["corridor_chance"]: h_sup = randint(self.params["min_h_corr"], \ -- 2.39.5 From ffa7641b215b5ec47650a81b6432bde850509f14 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 04:36:57 +0100 Subject: [PATCH 039/122] Made Map.neighbourhood a static method --- squirrelbattle/interfaces.py | 7 +++++-- squirrelbattle/mapgeneration/randomwalk.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index ef74614..2a08abc 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -192,16 +192,19 @@ class Map: dictclasses = Entity.get_all_entity_classes_in_a_dict() for entisave in d["entities"]: self.add_entity(dictclasses[entisave["type"]](**entisave)) - def neighbourhood(self, y, x, large=False): + + @staticmethod + def neighbourhood(grid, y, x, large=False): """ Returns up to 8 nearby coordinates, in a 3x3 square around the input coordinate if large is set to True, or in a 5-square cross by default. Does not return coordinates if they are out of bounds. """ + height, width = len(grid), len(grid[0]) neighbours = [] dyxs = product([-1, 0, 1], [-1, 0, 1]) if large else [[0, -1], [0, 1], [-1, 0], [1, 0]] for dy, dx in dyxs: - if 0 < y+dy < self.height and 0 < x+dx < self.width: + if 0 < y+dy < height and 0 < x+dx < width: neighbours.append([y+dy, x+dx]) return neighbours diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index 7cac8ff..913599f 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -106,7 +106,7 @@ class Generator: for x in range(width): for y in range(height): if grid[y][x] == Tile.EMPTY: - c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in result.neighbourhood(y, x, large=True)]) + c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in Map.neighbourhood(grid, y, x, large=True)]) if c == 4 and self.params["no_lone_walls"]: result.tiles[y][x] = Tile.FLOOR elif c > 0: -- 2.39.5 From 6fbc757f1e2b46e7dc4da95400dc26b1fbdeb81e Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 04:43:10 +0100 Subject: [PATCH 040/122] Implement method place_walls --- squirrelbattle/mapgeneration/broguelike.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 1a6f70c..6101ceb 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -52,6 +52,17 @@ class Generator: for rx in range(rw): if room[y][x] == Tile.FLOOR: level[y-door_y][y-door_x] = Tile.FLOOR + + @staticmethod + def place_walls(level): + h, w = len(level), len(level[0]) + for y in range(h): + for x in range(w): + if level[y][x] == Tile.FLOOR: + for dy, dx in Map.neighbourhood(level, y, x): + if level[y+dy][x+dx] == Tile.EMPTY: + level[y+dy][x+dx] = Tile.FLOOR + def corr_meta_info(self): if random() < self.params["corridor_chance"]: h_sup = randint(self.params["min_h_corr"], \ -- 2.39.5 From c6f66d95f2b2f683baef7fd9ce14c744034fc38b Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 04:48:32 +0100 Subject: [PATCH 041/122] Fix typos --- squirrelbattle/mapgeneration/broguelike.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 6101ceb..52ec246 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -136,17 +136,17 @@ class Generator: return room, doory, doorx, dy, dx def create_random_room(self): - return create_circular_room(self) + return self.create_circular_room() def run(self): height, width = self.params["height"], self.params["width"] level = [[Tile.EMPTY for i in range(width)] for j in range(height)] # the starting room must have no corridor - mem, self.params["corridor"] = self.params["corridor"], 0 + mem, self.params["corridor_chance"] = self.params["corridor_chance"], 0 starting_room, _, _, _, _ = self.create_random_room() self.place_room(level, height//2, width//2, 0, 0, starting_room) - self.params["corridor"] = mem + self.params["corridor_chance"] = mem # find a starting position sy, sx = randint(0, height-1), randint(0, width-1) -- 2.39.5 From 05ccd0e33976ec691e91ad86c5a36aac4d48353f Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 04:51:20 +0100 Subject: [PATCH 042/122] Circular rooms should not try to generate any holes if their radius isn't large enough --- squirrelbattle/mapgeneration/broguelike.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 52ec246..c208e91 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -108,9 +108,9 @@ class Generator: def create_circular_room(self): if random() < self.params["large_circular_room"]: - r = randint(5, 10)**2 + r = randint(5, 10) else: - r = randint(2, 4)**2 + r = randint(2, 4) room = [] @@ -118,7 +118,7 @@ class Generator: height = 2*r+2 width = 2*r+2 - make_hole = random() < self.params["circular_holes"] + make_hole = r > 6 and random() < self.params["circular_holes"] if make_hole: r2 = randint(3, r-3) for i in range(height+h_sup): -- 2.39.5 From abbad0f352553fdb989b48a96d1bb5a9032f4175 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 05:14:32 +0100 Subject: [PATCH 043/122] Fix formulas in place_room and room_fits --- squirrelbattle/mapgeneration/broguelike.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index c208e91..ea3fe46 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -36,8 +36,8 @@ class Generator: rh, rw = len(room), len(room[0]) for ry in range(rh): for rx in range(rw): - if room[y][x] == Tile.FLOOR: - ly, lx = ry - door_y, rx - door_x + if room[ry][rx] == Tile.FLOOR: + ly, lx = y + ry - door_y, x + rx - door_x if not(0 <= ly <= rh and 0 <= lx <= rw) or \ level[ly][lx] == Tile.FLOOR: return False @@ -50,8 +50,8 @@ class Generator: level[door_y][door_x] = Tile.FLOOR for ry in range(rh): for rx in range(rw): - if room[y][x] == Tile.FLOOR: - level[y-door_y][y-door_x] = Tile.FLOOR + if room[ry][rx] == Tile.FLOOR: + level[y-door_y+ry][y-door_x+rx] = Tile.FLOOR @staticmethod def place_walls(level): -- 2.39.5 From 49e261557cb7aac00f845da6ec81da49810e5f62 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 05:14:46 +0100 Subject: [PATCH 044/122] Fix typos --- squirrelbattle/mapgeneration/broguelike.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index ea3fe46..9d6af48 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -23,7 +23,6 @@ DEFAULT_PARAMS = { "circular_holes" : .5, } - class Generator: def __init__(self, params: dict = DEFAULT_PARAMS): self.params = params @@ -89,7 +88,7 @@ class Generator: dx = -1 if random() < .5 else 1 yxs = [i for i in range(len(room) * len(room[0]))] - shuffle(xys) + shuffle(yxs) for pos in yxs: y, x = pos // len(room), pos % len(room) if room[y][x] == Tile.EMPTY: @@ -131,9 +130,9 @@ class Generator: else: room[-1].append(Tile.EMPTY) - door_y, door_x, dy, dx = self.attach_doors(room, h_sup, w_sup, h_off, w_off) + door_y, door_x, dy, dx = self.attach_door(room, h_sup, w_sup, h_off, w_off) - return room, doory, doorx, dy, dx + return room, door_y, door_x, dy, dx def create_random_room(self): return self.create_circular_room() -- 2.39.5 From 20cbf546f9c80bcd357d65888b3a9cdd037895e6 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 05:21:31 +0100 Subject: [PATCH 045/122] Correct formulas for random enumeration of a grid --- squirrelbattle/mapgeneration/broguelike.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 9d6af48..72f773c 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -90,7 +90,7 @@ class Generator: yxs = [i for i in range(len(room) * len(room[0]))] shuffle(yxs) for pos in yxs: - y, x = pos // len(room), pos % len(room) + y, x = pos // len(room[0]), pos % len(room[0]) if room[y][x] == Tile.EMPTY: if room[y-dy][x-dx] == Tile.FLOOR: build_here = True @@ -160,7 +160,7 @@ class Generator: positions = [i for i in range()] shuffle(positions) for pos in positions: - y, x = pos // height, pos % width + y, x = pos // width, pos % width if self.room_fits(level, y, x, room, door_y, door_x, dy, dx): self.place_room(level, y, x, door_y, door_x, room) -- 2.39.5 From 8475e5228e603e23f7682460d0ac60247a24198b Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 05:41:16 +0100 Subject: [PATCH 046/122] Large neighbourhood shouldn't return the central cell --- squirrelbattle/interfaces.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 2a08abc..4e5f9ff 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -202,9 +202,13 @@ class Map: """ height, width = len(grid), len(grid[0]) neighbours = [] - dyxs = product([-1, 0, 1], [-1, 0, 1]) if large else [[0, -1], [0, 1], [-1, 0], [1, 0]] + if large: + dyxs = product([-1, 0, 1], [-1, 0, 1]) + dyxs = dyxs[:5] + dyxs[6:] + else: + dyxs = [[0, -1], [0, 1], [-1, 0], [1, 0]] for dy, dx in dyxs: - if 0 < y+dy < height and 0 < x+dx < width: + if 0 <= y+dy < height and 0 <= x+dx < width: neighbours.append([y+dy, x+dx]) return neighbours -- 2.39.5 From c959a9d865949848f0b2f86692fe0aee95a5052a Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 05:42:12 +0100 Subject: [PATCH 047/122] Update tests because Map.neighbourhood became a static method --- squirrelbattle/tests/mapgeneration_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index ed0951c..4fc6b28 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -23,7 +23,7 @@ class TestRandomWalk(unittest.TestCase): self.assertTrue(m.tiles[m.start_y][m.start_x].can_walk()) #DFS - queue = m.neighbourhood(m.start_y, m.start_x) + queue = Map.neighbourhood(m.tiles, m.start_y, m.start_x) while queue != []: y, x = queue.pop() if m.tiles[y][x].can_walk(): -- 2.39.5 From 9c252a2bbc135a8d97cf23a4970478640ac5870e Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 06:54:01 +0100 Subject: [PATCH 048/122] Correct out of bounds errors and add missing arguments to range call --- squirrelbattle/mapgeneration/broguelike.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 72f773c..d6da979 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -29,15 +29,17 @@ class Generator: @staticmethod def room_fits(level, y, x, room, door_y, door_x, dy, dx): - if level[y][x] != Tile.EMPTY or level[y-dy][x-dx] != Tile.FLOOR: - return False lh, lw = len(level), len(level[0]) rh, rw = len(room), len(room[0]) + if not(0 < y+dy < lh and 0 < x+dx < lw): + return False + if level[y][x] != Tile.EMPTY or level[y+dy][x+dx] != Tile.FLOOR: + return False for ry in range(rh): for rx in range(rw): if room[ry][rx] == Tile.FLOOR: ly, lx = y + ry - door_y, x + rx - door_x - if not(0 <= ly <= rh and 0 <= lx <= rw) or \ + if not(0 <= ly < lh and 0 <= lx < lw) or \ level[ly][lx] == Tile.FLOOR: return False return True -- 2.39.5 From d362bdc949179c1d6afe68feb9bf44eeb0199cd4 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 06:58:02 +0100 Subject: [PATCH 049/122] Fix place_room and add missing argument --- squirrelbattle/mapgeneration/broguelike.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index d6da979..40529e5 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -45,14 +45,14 @@ class Generator: return True @staticmethod - def place_room(level, y, x, door_y, door_x, room): + def place_room(level, y, x, room, door_y, door_x): rh, rw = len(room), len(room[0]) # maybe place Tile.DOOR here ? - level[door_y][door_x] = Tile.FLOOR + level[y][x] = Tile.FLOOR for ry in range(rh): for rx in range(rw): if room[ry][rx] == Tile.FLOOR: - level[y-door_y+ry][y-door_x+rx] = Tile.FLOOR + level[y-door_y+ry][x-door_x+rx] = Tile.FLOOR @staticmethod def place_walls(level): @@ -146,9 +146,9 @@ class Generator: # the starting room must have no corridor mem, self.params["corridor_chance"] = self.params["corridor_chance"], 0 starting_room, _, _, _, _ = self.create_random_room() - self.place_room(level, height//2, width//2, 0, 0, starting_room) + self.place_room(level, height//2, width//2, starting_room, 0, 0) self.params["corridor_chance"] = mem - + # find a starting position sy, sx = randint(0, height-1), randint(0, width-1) while level[sy][sx] != Tile.FLOOR: @@ -159,13 +159,13 @@ class Generator: while tries < self.params["tries"] and rooms_built < self.params["max_rooms"]: room, door_y, door_x, dy, dx = self.create_random_room() - positions = [i for i in range()] + positions = [i for i in range(height * width)] shuffle(positions) for pos in positions: y, x = pos // width, pos % width if self.room_fits(level, y, x, room, door_y, door_x, dy, dx): - self.place_room(level, y, x, door_y, door_x, room) - + self.place_room(level, y, x, room, door_y, door_x) + # post-processing self.place_walls(level) -- 2.39.5 From b0ac580677a5f46ec1b61412f3025d9a5d8ab283 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 07:03:49 +0100 Subject: [PATCH 050/122] Fix place_walls, that placed floors instead ... --- squirrelbattle/mapgeneration/broguelike.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 40529e5..dff9a7c 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -60,9 +60,9 @@ class Generator: for y in range(h): for x in range(w): if level[y][x] == Tile.FLOOR: - for dy, dx in Map.neighbourhood(level, y, x): - if level[y+dy][x+dx] == Tile.EMPTY: - level[y+dy][x+dx] = Tile.FLOOR + for ny, nx in Map.neighbourhood(level, y, x): + if level[ny][nx] == Tile.EMPTY: + level[ny][nx] = Tile.WALL def corr_meta_info(self): if random() < self.params["corridor_chance"]: -- 2.39.5 From e21d4d230c85aef5facca16e942057d2d0e51659 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 07:04:24 +0100 Subject: [PATCH 051/122] Add missing termination condition --- squirrelbattle/mapgeneration/broguelike.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index dff9a7c..e8b6fd1 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -165,6 +165,8 @@ class Generator: y, x = pos // width, pos % width if self.room_fits(level, y, x, room, door_y, door_x, dy, dx): self.place_room(level, y, x, room, door_y, door_x) + rooms_built += 1 + tries += 1 # post-processing self.place_walls(level) -- 2.39.5 From 5ba07afc9ffa2a6e948ff0673c6ee4c8d03867ee Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 07:05:02 +0100 Subject: [PATCH 052/122] Fix typo in parameter names --- squirrelbattle/mapgeneration/broguelike.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index e8b6fd1..143911c 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -66,10 +66,10 @@ class Generator: def corr_meta_info(self): if random() < self.params["corridor_chance"]: - h_sup = randint(self.params["min_h_corr"], \ - self.params["max_h_corr"]) if random() < .5 else 0 - w_sup = 0 if h_sup else randint(self.params["min_w_corr"], \ - self.params["max_w_coor"]) + h_sup = randint(self.params["min_v_corr"], \ + self.params["max_v_corr"]) if random() < .5 else 0 + w_sup = 0 if h_sup else randint(self.params["min_h_corr"], \ + self.params["max_h_corr"]) h_off = h_sup if random() < .5 else 0 w_off = w_sup if random() < .5 else 0 return h_sup, w_sup, h_off, w_off -- 2.39.5 From 605696dddd0888b960f6d81ef3ba54cf9cc91c23 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 07:36:31 +0100 Subject: [PATCH 053/122] Revamp door placing algorithm so that it generates cleaner doors; also remove lone starting room door from level --- squirrelbattle/mapgeneration/broguelike.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 143911c..b986b8f 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -89,18 +89,24 @@ class Generator: else: dx = -1 if random() < .5 else 1 - yxs = [i for i in range(len(room) * len(room[0]))] + rh, rw = len(room), len(room[0]) + yxs = [i for i in range(rh * rw)] shuffle(yxs) for pos in yxs: - y, x = pos // len(room[0]), pos % len(room[0]) + y, x = pos // rw, pos % rw if room[y][x] == Tile.EMPTY: - if room[y-dy][x-dx] == Tile.FLOOR: - build_here = True + # verify we are pointing away from a floor tile + if not(0 <= y-dy < rh and 0 <= x-dx < rw) or room[y-dy][x-dx] != Tile.FLOOR: + continue + # verify there's no other floor tile around us + for ny, nx in [[y+dy, x+dx], [y-dx, x-dy], [y+dx, x+dy]]: + if 0 <= ny < rh and 0 <= nx < rw and room[ny][nx] != Tile.EMPTY: + break + else: for i in range(l): if room[y+i*dy][x+i*dx] != Tile.EMPTY: - build_here = False break - if build_here: + else: for i in range(l): room[y+i*dy][x+i*dx] == Tile.FLOOR break @@ -147,6 +153,7 @@ class Generator: mem, self.params["corridor_chance"] = self.params["corridor_chance"], 0 starting_room, _, _, _, _ = self.create_random_room() self.place_room(level, height//2, width//2, starting_room, 0, 0) + level[0][0] = Tile.EMPTY self.params["corridor_chance"] = mem # find a starting position -- 2.39.5 From 641f5c7872134f585883f6b76eaa1be16ff9c4f3 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 07:38:47 +0100 Subject: [PATCH 054/122] Make generation more sparse by asking for extra space around rooms; also add out of bounds option to Map.neighbourhood --- squirrelbattle/interfaces.py | 6 +++--- squirrelbattle/mapgeneration/broguelike.py | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 4e5f9ff..ad0e3b3 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -194,7 +194,7 @@ class Map: self.add_entity(dictclasses[entisave["type"]](**entisave)) @staticmethod - def neighbourhood(grid, y, x, large=False): + def neighbourhood(grid, y, x, large=False, oob=False): """ Returns up to 8 nearby coordinates, in a 3x3 square around the input coordinate if large is set to True, or in a 5-square cross by default. Does not return coordinates if they are out @@ -203,12 +203,12 @@ class Map: height, width = len(grid), len(grid[0]) neighbours = [] if large: - dyxs = product([-1, 0, 1], [-1, 0, 1]) + dyxs = [[dy, dx] for dy, dx in product([-1, 0, 1], [-1, 0, 1])] dyxs = dyxs[:5] + dyxs[6:] else: dyxs = [[0, -1], [0, 1], [-1, 0], [1, 0]] for dy, dx in dyxs: - if 0 <= y+dy < height and 0 <= x+dx < width: + if oob or (0 <= y+dy < height and 0 <= x+dx < width): neighbours.append([y+dy, x+dx]) return neighbours diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index b986b8f..96c7153 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -39,9 +39,16 @@ class Generator: for rx in range(rw): if room[ry][rx] == Tile.FLOOR: ly, lx = y + ry - door_y, x + rx - door_x + # tile must be in bounds and empty if not(0 <= ly < lh and 0 <= lx < lw) or \ level[ly][lx] == Tile.FLOOR: return False + # so do all neighbouring tiles bc we may + # need to place walls there eventually + for ny, nx in Map.neighbourhood(level, ly, lx, large=True, oob=True): + if not(0 <= ny < lh and 0 <= nx < lw) or \ + level[ny][nx] != Tile.EMPTY: + return False return True @staticmethod -- 2.39.5 From c6947fab44b87f2f45b299d29bf7294b0bc704fb Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 07:39:52 +0100 Subject: [PATCH 055/122] Integrate the new map generation into the game ! Closes #5 --- squirrelbattle/game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 4e097b1..e4544b0 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -12,7 +12,7 @@ import sys from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions from .interfaces import Map, Logs -from .mapgeneration import randomwalk +from .mapgeneration import randomwalk, broguelike from .resources import ResourceManager from .settings import Settings from . import menus @@ -51,7 +51,7 @@ class Game: """ Create a new game on the screen. """ - self.map = randomwalk.Generator().run() + self.map = broguelike.Generator().run() self.map.logs = self.logs self.logs.clear() self.player = Player() -- 2.39.5 From c06f903a16d8a824b8e0d66f1d8f61eeaa43fef2 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 07:41:00 +0100 Subject: [PATCH 056/122] Fix a typo that made corridors unable to be built --- squirrelbattle/mapgeneration/broguelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 96c7153..44a5716 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -115,7 +115,7 @@ class Generator: break else: for i in range(l): - room[y+i*dy][x+i*dx] == Tile.FLOOR + room[y+i*dy][x+i*dx] = Tile.FLOOR break return y+l*dy, x+l*dx, dy, dx -- 2.39.5 From fab1bee8d82a5ac63bcb50bacabd68fe989f4edc Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 14:52:59 +0100 Subject: [PATCH 057/122] Force loop entrance to get coverage --- squirrelbattle/mapgeneration/randomwalk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index 913599f..688c92c 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -96,8 +96,8 @@ class Generator: next_walker_pop.append(walker.split()) walkers = next_walker_pop - start_x, start_y = randint(0, width - 1), randint(0, height - 1) - while grid[start_y][start_x] != Tile.FLOOR: + start_x, start_y = -1, -1 + while grid[start_y][start_x] != Tile.FLOOR or start_x == -1: start_x, start_y = randint(0, width - 1), randint(0, height - 1) result = Map(width, height, grid, start_y, start_x) -- 2.39.5 From 8d7e26438101da2db5f2b4cc8f9e5e0a86f13f6c Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 15:06:38 +0100 Subject: [PATCH 058/122] Fix a bug where the generator could crash by trying to place the starting room out of bounds; starting room position is now random --- squirrelbattle/mapgeneration/broguelike.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 44a5716..02fc736 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -159,7 +159,9 @@ class Generator: # the starting room must have no corridor mem, self.params["corridor_chance"] = self.params["corridor_chance"], 0 starting_room, _, _, _, _ = self.create_random_room() - self.place_room(level, height//2, width//2, starting_room, 0, 0) + dim_v, dim_h = len(starting_room), len(starting_room[0]) + pos_y, pos_x = randint(0, height-dim_v-1), randint(0, width-dim_h-1) + self.place_room(level, pos_y, pos_x, starting_room, 0, 0) level[0][0] = Tile.EMPTY self.params["corridor_chance"] = mem -- 2.39.5 From dab84738d91e5a6b20132d1ba00de6ba8ecc3d8a Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 15:18:13 +0100 Subject: [PATCH 059/122] Remove the starting room door only if it really shouldn't be here; also account for the new randomized placement in removing lone door tile --- squirrelbattle/mapgeneration/broguelike.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 02fc736..59cbaeb 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -162,7 +162,8 @@ class Generator: dim_v, dim_h = len(starting_room), len(starting_room[0]) pos_y, pos_x = randint(0, height-dim_v-1), randint(0, width-dim_h-1) self.place_room(level, pos_y, pos_x, starting_room, 0, 0) - level[0][0] = Tile.EMPTY + if starting_room[0][0] != Tile.FLOOR: + level[pos_y][pos_x] = Tile.EMPTY self.params["corridor_chance"] = mem # find a starting position -- 2.39.5 From 5424c7cd983314aeaff7ad477141de7433b1cfda Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 15:20:32 +0100 Subject: [PATCH 060/122] Nicer default parameters --- squirrelbattle/mapgeneration/broguelike.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 59cbaeb..191e757 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -8,13 +8,13 @@ from ..interfaces import Map, Tile DEFAULT_PARAMS = { - "width" : 80, - "height" : 40, - "tries" : 600, - "max_rooms" : 99, + "width" : 120, + "height" : 35, + "tries" : 300, + "max_rooms" : 20, "max_room_tries" : 15, "cross_room" : 1, - "corridor_chance" : .8, + "corridor_chance" : .6, "min_v_corr" : 2, "max_v_corr" : 6, "min_h_corr" : 4, -- 2.39.5 From f240cafa833aebe3ab3ed43730beb5335e65f096 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 15:55:26 +0100 Subject: [PATCH 061/122] Fixing syntax in tests --- squirrelbattle/tests/mapgeneration_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index 4fc6b28..394977b 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -28,5 +28,6 @@ class TestRandomWalk(unittest.TestCase): y, x = queue.pop() if m.tiles[y][x].can_walk(): m.tiles[y][x] = Tile.WALL - queue += m.neighbourhood(y, x) + queue += Map.neighbourhood(m.tiles, y, x) + self.assertFalse(any([any([t.can_walk() for t in l]) for l in m.tiles])) -- 2.39.5 From 785ac403e354424b00143d6b6bc25aded8fac891 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 15:56:30 +0100 Subject: [PATCH 062/122] Forbid walker from ever reaching the outer most edge of the map --- squirrelbattle/mapgeneration/randomwalk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py index 688c92c..419d342 100644 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ b/squirrelbattle/mapgeneration/randomwalk.py @@ -48,7 +48,7 @@ class Walker: def move_in_bounds(self, width: int, height: int) -> None: nx, ny = self.next_pos() - if 0 < nx < width and 0 < ny < height: + if 0 < nx < width-1 and 0 < ny < height-1: self.x, self.y = nx, ny def split(self) -> "Walker": @@ -106,7 +106,7 @@ class Generator: for x in range(width): for y in range(height): if grid[y][x] == Tile.EMPTY: - c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in Map.neighbourhood(grid, y, x, large=True)]) + c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in Map.neighbourhood(grid, y, x)]) if c == 4 and self.params["no_lone_walls"]: result.tiles[y][x] = Tile.FLOOR elif c > 0: -- 2.39.5 From 0aa4eb9c0b88369b3619bad368ebd27f042c6741 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 16:11:17 +0100 Subject: [PATCH 063/122] Refactoring in tests to allow for easy connexity verification --- squirrelbattle/tests/mapgeneration_test.py | 35 ++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index 394977b..f546669 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -4,7 +4,22 @@ import unittest from squirrelbattle.interfaces import Map, Tile -from squirrelbattle.mapgeneration import randomwalk +from squirrelbattle.mapgeneration import randomwalk, broguelike + +def is_connex(grid): + h, w = len(grid), len(grid[0]) + y, x = randint(0, h-1), randint(0, w-1) + while not(grid[y][x].can_walk()): + y, x = randint(0, h-1), randint(0, w-1) + queue = Map.neighbourhood(grid, y, x) + while queue != []: + y, x = queue.pop() + if m.tiles[y][x].can_walk(): + m.tiles[y][x] = Tile.WALL + queue += Map.neighbourhood(grid, y, x) + return not(any([any([t.can_walk() for t in l]) for l in m.tiles])) + + class TestRandomWalk(unittest.TestCase): @@ -22,12 +37,14 @@ class TestRandomWalk(unittest.TestCase): m = self.generator.run() self.assertTrue(m.tiles[m.start_y][m.start_x].can_walk()) - #DFS - queue = Map.neighbourhood(m.tiles, m.start_y, m.start_x) - while queue != []: - y, x = queue.pop() - if m.tiles[y][x].can_walk(): - m.tiles[y][x] = Tile.WALL - queue += Map.neighbourhood(m.tiles, y, x) + def test_connexity(self) -> None: + m = self.generator.run() + self.assertTrue(is_connex(m.tiles)) - self.assertFalse(any([any([t.can_walk() for t in l]) for l in m.tiles])) +class TestBroguelike(unittest.TestCase): + def setUp(self) -> None: + self.generator = broguelike.Generator() + + def test_connexity(self) -> None: + m = self.generator.run() + self.assertTrue(is_connex(m)) -- 2.39.5 From a390f4f5e9149273409594d3bb220ec9fa28764b Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 16:21:16 +0100 Subject: [PATCH 064/122] Fix is_connex tests --- squirrelbattle/tests/mapgeneration_test.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index f546669..85d1722 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import unittest +from random import randint from squirrelbattle.interfaces import Map, Tile from squirrelbattle.mapgeneration import randomwalk, broguelike @@ -14,13 +15,10 @@ def is_connex(grid): queue = Map.neighbourhood(grid, y, x) while queue != []: y, x = queue.pop() - if m.tiles[y][x].can_walk(): - m.tiles[y][x] = Tile.WALL + if grid[y][x].can_walk(): + grid[y][x] = Tile.WALL queue += Map.neighbourhood(grid, y, x) - return not(any([any([t.can_walk() for t in l]) for l in m.tiles])) - - - + return not(any([any([t.can_walk() for t in l]) for l in grid])) class TestRandomWalk(unittest.TestCase): def setUp(self) -> None: @@ -47,4 +45,4 @@ class TestBroguelike(unittest.TestCase): def test_connexity(self) -> None: m = self.generator.run() - self.assertTrue(is_connex(m)) + self.assertTrue(is_connex(m.tiles)) -- 2.39.5 From c216a6089e60fcfcd0b429f2fe8e4a8e066c1d4e Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 16:51:04 +0100 Subject: [PATCH 065/122] Add a break so that generated rooms arre only placed once --- squirrelbattle/mapgeneration/broguelike.py | 1 + 1 file changed, 1 insertion(+) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 191e757..9d4cfd0 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -183,6 +183,7 @@ class Generator: if self.room_fits(level, y, x, room, door_y, door_x, dy, dx): self.place_room(level, y, x, room, door_y, door_x) rooms_built += 1 + break tries += 1 # post-processing -- 2.39.5 From 9b853324adf91a6cce444a609110a9be53f9e9b0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 16:16:42 +0100 Subject: [PATCH 066/122] Drop first version of random walk --- squirrelbattle/game.py | 2 +- squirrelbattle/mapgeneration/randomwalk.py | 123 --------------------- squirrelbattle/tests/mapgeneration_test.py | 23 +--- 3 files changed, 3 insertions(+), 145 deletions(-) delete mode 100644 squirrelbattle/mapgeneration/randomwalk.py diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index e4544b0..bb917ec 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -12,7 +12,7 @@ import sys from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions from .interfaces import Map, Logs -from .mapgeneration import randomwalk, broguelike +from .mapgeneration import broguelike from .resources import ResourceManager from .settings import Settings from . import menus diff --git a/squirrelbattle/mapgeneration/randomwalk.py b/squirrelbattle/mapgeneration/randomwalk.py deleted file mode 100644 index 419d342..0000000 --- a/squirrelbattle/mapgeneration/randomwalk.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse -# SPDX-License-Identifier: GPL-3.0-or-later - -from enum import auto, Enum -from random import choice, random, randint -from typing import Tuple - -from ..interfaces import Map, Tile - - -DEFAULT_PARAMS = { - "split_chance": .15, - "turn_chance": .5, - "death_chance": .1, - "max_walkers": 15, - "width": 100, - "height": 100, - "fill": .4, - "no_lone_walls": False, -} - - -class Directions(Enum): - up = auto() - down = auto() - left = auto() - right = auto() - - -class Walker: - def __init__(self, x: int, y: int): - self.x = x - self.y = y - self.dir = choice(list(Directions)) - - def random_turn(self) -> None: - self.dir = choice(list(Directions)) - - def next_pos(self) -> Tuple[int, int]: - if self.dir == Directions.up: - return self.x, self.y + 1 - elif self.dir == Directions.down: - return self.x, self.y - 1 - elif self.dir == Directions.right: - return self.x + 1, self.y - elif self.dir == Directions.left: - return self.x - 1, self.y - - def move_in_bounds(self, width: int, height: int) -> None: - nx, ny = self.next_pos() - if 0 < nx < width-1 and 0 < ny < height-1: - self.x, self.y = nx, ny - - def split(self) -> "Walker": - child = Walker(self.x, self.y) - child.dir = self.dir - return child - - -class Generator: - def __init__(self, params: dict = DEFAULT_PARAMS): - self.params = params - - def run(self) -> Map: - width, height = self.params["width"], self.params["height"] - walkers = [Walker(width // 2, height // 2)] - grid = [[Tile.EMPTY for _ in range(width)] for _ in range(height)] - count = 0 - while count < self.params["fill"] * width * height: - # because we can't add or remove walkers while looping over the pop - # we need lists to keep track of what will be the walkers for the - # next iteration of the main loop - next_walker_pop = [] - - for walker in walkers: - if grid[walker.y][walker.x] == Tile.EMPTY: - count += 1 - grid[walker.y][walker.x] = Tile.FLOOR - if random() < self.params["turn_chance"]: - walker.random_turn() - walker.move_in_bounds(width, height) - if random() > self.params["death_chance"]: - next_walker_pop.append(walker) - - # we make sure to never kill all walkers - if not next_walker_pop: - next_walker_pop.append(choice(walkers)) - - # we use a second loop for spliting so we're not bothered by cases - # like a walker not spliting because we hit the population cap even - # though the next one would have died and freed a place - # not a big if it happened though - for walker in walkers: - if len(next_walker_pop) < self.params["max_walkers"]: - if random() < self.params["split_chance"]: - next_walker_pop.append(walker.split()) - walkers = next_walker_pop - - start_x, start_y = -1, -1 - while grid[start_y][start_x] != Tile.FLOOR or start_x == -1: - start_x, start_y = randint(0, width - 1), randint(0, height - 1) - - result = Map(width, height, grid, start_y, start_x) - - # post-processing: add walls - for x in range(width): - for y in range(height): - if grid[y][x] == Tile.EMPTY: - c = sum([1 if grid[j][i] == Tile.FLOOR else 0 for j, i in Map.neighbourhood(grid, y, x)]) - if c == 4 and self.params["no_lone_walls"]: - result.tiles[y][x] = Tile.FLOOR - elif c > 0: - result.tiles[y][x] = Tile.WALL - for x in range(width): - for y in [0, height-1]: - if grid[y][x] == Tile.FLOOR: - grid[y][x] = Tile.WALL - for y in range(height): - for x in [0, width-1]: - if grid[y][x] == Tile.FLOOR: - grid[y][x] = Tile.WALL - - return result diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index 85d1722..062b2e0 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -5,7 +5,7 @@ import unittest from random import randint from squirrelbattle.interfaces import Map, Tile -from squirrelbattle.mapgeneration import randomwalk, broguelike +from squirrelbattle.mapgeneration import broguelike def is_connex(grid): h, w = len(grid), len(grid[0]) @@ -20,29 +20,10 @@ def is_connex(grid): queue += Map.neighbourhood(grid, y, x) return not(any([any([t.can_walk() for t in l]) for l in grid])) -class TestRandomWalk(unittest.TestCase): - def setUp(self) -> None: - #we set no_lone_walls to true for 100% coverage - params = randomwalk.DEFAULT_PARAMS - params["no_lone_walls"] = True - self.generator = randomwalk.Generator(params = params) - - def test_starting(self) -> None: - """ - Create a map and check that the whole map is accessible from the starting position using a - depth-first search - """ - m = self.generator.run() - self.assertTrue(m.tiles[m.start_y][m.start_x].can_walk()) - - def test_connexity(self) -> None: - m = self.generator.run() - self.assertTrue(is_connex(m.tiles)) - class TestBroguelike(unittest.TestCase): def setUp(self) -> None: self.generator = broguelike.Generator() - + def test_connexity(self) -> None: m = self.generator.run() self.assertTrue(is_connex(m.tiles)) -- 2.39.5 From afaa9d17cdc8e0a8e50959d7951d55d8e2ec9a18 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 16:55:02 +0100 Subject: [PATCH 067/122] Linting --- squirrelbattle/interfaces.py | 14 +- squirrelbattle/mapgeneration/broguelike.py | 141 ++++++++++++--------- squirrelbattle/tests/mapgeneration_test.py | 28 ++-- 3 files changed, 101 insertions(+), 82 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index ad0e3b3..0802fb4 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -194,11 +194,13 @@ class Map: self.add_entity(dictclasses[entisave["type"]](**entisave)) @staticmethod - def neighbourhood(grid, y, x, large=False, oob=False): + def neighbourhood(grid: List[List["Tile"]], y: int, x: int, + large: bool = False, oob: bool = False) \ + -> List[List[int]]: """ - Returns up to 8 nearby coordinates, in a 3x3 square around the input coordinate if large is - set to True, or in a 5-square cross by default. Does not return coordinates if they are out - of bounds. + Returns up to 8 nearby coordinates, in a 3x3 square around the input + coordinate if large is set to True, or in a 5-square cross by default. + Does not return coordinates if they are out of bounds. """ height, width = len(grid), len(grid[0]) neighbours = [] @@ -208,8 +210,8 @@ class Map: else: dyxs = [[0, -1], [0, 1], [-1, 0], [1, 0]] for dy, dx in dyxs: - if oob or (0 <= y+dy < height and 0 <= x+dx < width): - neighbours.append([y+dy, x+dx]) + if oob or (0 <= y + dy < height and 0 <= x + dx < width): + neighbours.append([y + dy, x + dx]) return neighbours diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 9d4cfd0..b261f57 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -1,39 +1,42 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -from enum import auto, Enum -from random import choice, random, randint, shuffle +from random import random, randint, shuffle +from typing import List, Tuple from ..interfaces import Map, Tile DEFAULT_PARAMS = { - "width" : 120, - "height" : 35, - "tries" : 300, - "max_rooms" : 20, - "max_room_tries" : 15, - "cross_room" : 1, - "corridor_chance" : .6, - "min_v_corr" : 2, - "max_v_corr" : 6, - "min_h_corr" : 4, - "max_h_corr" : 12, - "large_circular_room" : .10, - "circular_holes" : .5, + "width": 120, + "height": 35, + "tries": 300, + "max_rooms": 20, + "max_room_tries": 15, + "cross_room": 1, + "corridor_chance": .6, + "min_v_corr": 2, + "max_v_corr": 6, + "min_h_corr": 4, + "max_h_corr": 12, + "large_circular_room": .10, + "circular_holes": .5, } + class Generator: - def __init__(self, params: dict = DEFAULT_PARAMS): - self.params = params + def __init__(self, params: dict = None): + self.params = params or DEFAULT_PARAMS @staticmethod - def room_fits(level, y, x, room, door_y, door_x, dy, dx): + def room_fits(level: List[List[Tile]], y: int, x: int, + room: List[List[Tile]], door_y: int, door_x: int, + dy: int, dx: int) -> bool: lh, lw = len(level), len(level[0]) rh, rw = len(room), len(room[0]) - if not(0 < y+dy < lh and 0 < x+dx < lw): + if not(0 < y + dy < lh and 0 < x + dx < lw): return False - if level[y][x] != Tile.EMPTY or level[y+dy][x+dx] != Tile.FLOOR: + if level[y][x] != Tile.EMPTY or level[y + dy][x + dx] != Tile.FLOOR: return False for ry in range(rh): for rx in range(rw): @@ -45,24 +48,26 @@ class Generator: return False # so do all neighbouring tiles bc we may # need to place walls there eventually - for ny, nx in Map.neighbourhood(level, ly, lx, large=True, oob=True): + for ny, nx in Map.neighbourhood(level, ly, lx, + large=True, oob=True): if not(0 <= ny < lh and 0 <= nx < lw) or \ level[ny][nx] != Tile.EMPTY: return False return True @staticmethod - def place_room(level, y, x, room, door_y, door_x): + def place_room(level: List[List[Tile]], y: int, x: int, + room: List[List[Tile]], door_y: int, door_x: int) -> None: rh, rw = len(room), len(room[0]) # maybe place Tile.DOOR here ? level[y][x] = Tile.FLOOR for ry in range(rh): for rx in range(rw): if room[ry][rx] == Tile.FLOOR: - level[y-door_y+ry][x-door_x+rx] = Tile.FLOOR + level[y - door_y + ry][x - door_x + rx] = Tile.FLOOR @staticmethod - def place_walls(level): + def place_walls(level: List[List[Tile]]) -> None: h, w = len(level), len(level[0]) for y in range(h): for x in range(w): @@ -70,22 +75,24 @@ class Generator: for ny, nx in Map.neighbourhood(level, y, x): if level[ny][nx] == Tile.EMPTY: level[ny][nx] = Tile.WALL - - def corr_meta_info(self): + + def corr_meta_info(self) -> Tuple[int, int, int, int]: if random() < self.params["corridor_chance"]: - h_sup = randint(self.params["min_v_corr"], \ - self.params["max_v_corr"]) if random() < .5 else 0 - w_sup = 0 if h_sup else randint(self.params["min_h_corr"], \ - self.params["max_h_corr"]) + h_sup = randint(self.params["min_v_corr"], + self.params["max_v_corr"]) if random() < .5 else 0 + w_sup = 0 if h_sup else randint(self.params["min_h_corr"], + self.params["max_h_corr"]) h_off = h_sup if random() < .5 else 0 w_off = w_sup if random() < .5 else 0 return h_sup, w_sup, h_off, w_off return 0, 0, 0, 0 - def attach_door(self, room, h_sup, w_sup, h_off, w_off): - l = h_sup + w_sup + def attach_door(self, room: List[List[Tile]], h_sup: int, w_sup: int, + h_off: int, w_off: int) \ + -> Tuple[int, int, int, int]: + length = h_sup + w_sup dy, dx = 0, 0 - if l > 0: + if length > 0: if h_sup: dy = -1 if h_off else 1 else: @@ -103,77 +110,85 @@ class Generator: y, x = pos // rw, pos % rw if room[y][x] == Tile.EMPTY: # verify we are pointing away from a floor tile - if not(0 <= y-dy < rh and 0 <= x-dx < rw) or room[y-dy][x-dx] != Tile.FLOOR: + if not(0 <= y - dy < rh and 0 <= x - dx < rw) \ + or room[y - dy][x - dx] != Tile.FLOOR: continue # verify there's no other floor tile around us - for ny, nx in [[y+dy, x+dx], [y-dx, x-dy], [y+dx, x+dy]]: - if 0 <= ny < rh and 0 <= nx < rw and room[ny][nx] != Tile.EMPTY: + for ny, nx in [[y + dy, x + dx], [y - dx, x - dy], + [y + dx, x + dy]]: + if 0 <= ny < rh and 0 <= nx < rw \ + and room[ny][nx] != Tile.EMPTY: break else: - for i in range(l): - if room[y+i*dy][x+i*dx] != Tile.EMPTY: + for i in range(length): + if room[y + i * dy][x + i * dx] != Tile.EMPTY: break else: - for i in range(l): - room[y+i*dy][x+i*dx] = Tile.FLOOR + for i in range(length): + room[y + i * dy][x + i * dx] = Tile.FLOOR break - return y+l*dy, x+l*dx, dy, dx + return y + length * dy, x + length * dx, dy, dx - - def create_circular_room(self): + def create_circular_room(self) -> Tuple[List[List[Tile]], int, int, + int, int]: if random() < self.params["large_circular_room"]: r = randint(5, 10) else: r = randint(2, 4) room = [] - + h_sup, w_sup, h_off, w_off = self.corr_meta_info() - height = 2*r+2 - width = 2*r+2 + height = 2 * r + 2 + width = 2 * r + 2 make_hole = r > 6 and random() < self.params["circular_holes"] + r2 = 0 if make_hole: - r2 = randint(3, r-3) - for i in range(height+h_sup): + r2 = randint(3, r - 3) + for i in range(height + h_sup): room.append([]) - d = (i-h_off-height//2)**2 - for j in range(width+w_sup): - if d + (j-w_off-width//2)**2 < r**2 and \ - (not(make_hole) or d + (j-w_off-width//2)**2 >= r2**2): + d = (i - h_off - height // 2) ** 2 + for j in range(width + w_sup): + if d + (j - w_off - width // 2) ** 2 < r ** 2 and \ + (not make_hole + or d + (j - w_off - width // 2) ** 2 >= r2 ** 2): room[-1].append(Tile.FLOOR) else: room[-1].append(Tile.EMPTY) - door_y, door_x, dy, dx = self.attach_door(room, h_sup, w_sup, h_off, w_off) + door_y, door_x, dy, dx = self.attach_door(room, h_sup, w_sup, + h_off, w_off) return room, door_y, door_x, dy, dx - def create_random_room(self): + def create_random_room(self) -> Tuple[List[list], int, int, int, int]: return self.create_circular_room() - - def run(self): + + def run(self) -> Map: height, width = self.params["height"], self.params["width"] - level = [[Tile.EMPTY for i in range(width)] for j in range(height)] + level = [width * [Tile.EMPTY] for _ignored in range(height)] # the starting room must have no corridor mem, self.params["corridor_chance"] = self.params["corridor_chance"], 0 starting_room, _, _, _, _ = self.create_random_room() dim_v, dim_h = len(starting_room), len(starting_room[0]) - pos_y, pos_x = randint(0, height-dim_v-1), randint(0, width-dim_h-1) + pos_y, pos_x = randint(0, height - dim_v - 1),\ + randint(0, width - dim_h - 1) self.place_room(level, pos_y, pos_x, starting_room, 0, 0) if starting_room[0][0] != Tile.FLOOR: level[pos_y][pos_x] = Tile.EMPTY self.params["corridor_chance"] = mem - + # find a starting position - sy, sx = randint(0, height-1), randint(0, width-1) + sy, sx = randint(0, height - 1), randint(0, width - 1) while level[sy][sx] != Tile.FLOOR: - sy, sx = randint(0, height-1), randint(0, width-1) + sy, sx = randint(0, height - 1), randint(0, width - 1) # now we loop until we've tried enough, or we've added enough rooms tries, rooms_built = 0, 0 - while tries < self.params["tries"] and rooms_built < self.params["max_rooms"]: + while tries < self.params["tries"] \ + and rooms_built < self.params["max_rooms"]: room, door_y, door_x, dy, dx = self.create_random_room() positions = [i for i in range(height * width)] @@ -185,7 +200,7 @@ class Generator: rooms_built += 1 break tries += 1 - + # post-processing self.place_walls(level) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index 062b2e0..fc1de8d 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -3,27 +3,29 @@ import unittest from random import randint +from typing import List from squirrelbattle.interfaces import Map, Tile from squirrelbattle.mapgeneration import broguelike -def is_connex(grid): - h, w = len(grid), len(grid[0]) - y, x = randint(0, h-1), randint(0, w-1) - while not(grid[y][x].can_walk()): - y, x = randint(0, h-1), randint(0, w-1) - queue = Map.neighbourhood(grid, y, x) - while queue != []: - y, x = queue.pop() - if grid[y][x].can_walk(): - grid[y][x] = Tile.WALL - queue += Map.neighbourhood(grid, y, x) - return not(any([any([t.can_walk() for t in l]) for l in grid])) class TestBroguelike(unittest.TestCase): def setUp(self) -> None: self.generator = broguelike.Generator() + def is_connex(self, grid: List[List[Tile]]) -> bool: + h, w = len(grid), len(grid[0]) + y, x = randint(0, h - 1), randint(0, w - 1) + while not (grid[y][x].can_walk()): + y, x = randint(0, h - 1), randint(0, w - 1) + queue = Map.neighbourhood(grid, y, x) + while queue: + y, x = queue.pop() + if grid[y][x].can_walk(): + grid[y][x] = Tile.WALL + queue += Map.neighbourhood(grid, y, x) + return not any([t.can_walk() for row in grid for t in row]) + def test_connexity(self) -> None: m = self.generator.run() - self.assertTrue(is_connex(m.tiles)) + self.assertTrue(self.is_connex(m.tiles)) -- 2.39.5 From 8e7029e34d1ff9b96856943227e6ba5c2d9ea741 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 17:10:30 +0100 Subject: [PATCH 068/122] Fix walls --- squirrelbattle/mapgeneration/broguelike.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index b261f57..ca830b8 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -71,8 +71,8 @@ class Generator: h, w = len(level), len(level[0]) for y in range(h): for x in range(w): - if level[y][x] == Tile.FLOOR: - for ny, nx in Map.neighbourhood(level, y, x): + if not level[y][x].is_wall(): + for ny, nx in Map.neighbourhood(level, y, x, large=True): if level[ny][nx] == Tile.EMPTY: level[ny][nx] = Tile.WALL -- 2.39.5 From df2c1a4b55232605fac936b4938fae3b2d724830 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 17:10:42 +0100 Subject: [PATCH 069/122] Add ladder on the start position --- squirrelbattle/mapgeneration/broguelike.py | 1 + 1 file changed, 1 insertion(+) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index ca830b8..38d462f 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -184,6 +184,7 @@ class Generator: sy, sx = randint(0, height - 1), randint(0, width - 1) while level[sy][sx] != Tile.FLOOR: sy, sx = randint(0, height - 1), randint(0, width - 1) + level[sy][sx] = Tile.LADDER # now we loop until we've tried enough, or we've added enough rooms tries, rooms_built = 0, 0 -- 2.39.5 From 7e14122b8c38793ea6036ceb1bf9028ed0be1a00 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 17:25:52 +0100 Subject: [PATCH 070/122] Randomly place exit ladder --- squirrelbattle/mapgeneration/broguelike.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 38d462f..65eb337 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -205,4 +205,11 @@ class Generator: # post-processing self.place_walls(level) + # place an exit ladder + y, x = randint(0, height - 1), randint(0, width - 1) + while level[y][x] != Tile.FLOOR or \ + sum([t.can_walk() for t in Map.neighbourhood(level, y, x, large=True)]) < 5: + y, x = randint(0, height - 1), randint(0, width - 1) + level[sy][sx] = Tile.LADDER + return Map(width, height, level, sy, sx) -- 2.39.5 From 9e099d071509378b0e276b31c5cffb6aa0c94586 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Fri, 8 Jan 2021 19:50:27 +0100 Subject: [PATCH 071/122] Ladders should spawn with no wall nearby --- squirrelbattle/mapgeneration/broguelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 65eb337..2425df7 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -208,7 +208,7 @@ class Generator: # place an exit ladder y, x = randint(0, height - 1), randint(0, width - 1) while level[y][x] != Tile.FLOOR or \ - sum([t.can_walk() for t in Map.neighbourhood(level, y, x, large=True)]) < 5: + any([t.is_wall() for t in Map.neighbourhood(level, y, x, large=True)]): y, x = randint(0, height - 1), randint(0, width - 1) level[sy][sx] = Tile.LADDER -- 2.39.5 From d8d0bc61908bf09d66028e147df3570445e0eab7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 19:20:56 +0100 Subject: [PATCH 072/122] Fix the end ladder --- squirrelbattle/mapgeneration/broguelike.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 2425df7..e3f86a7 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -208,8 +208,9 @@ class Generator: # place an exit ladder y, x = randint(0, height - 1), randint(0, width - 1) while level[y][x] != Tile.FLOOR or \ - any([t.is_wall() for t in Map.neighbourhood(level, y, x, large=True)]): + any([level[j][i].is_wall() for j, i + in Map.neighbourhood(level, y, x, large=True)]): y, x = randint(0, height - 1), randint(0, width - 1) - level[sy][sx] = Tile.LADDER + level[y][x] = Tile.LADDER return Map(width, height, level, sy, sx) -- 2.39.5 From 571857b06301c58a7afdf31fbdeaf5363dda4558 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 20:00:42 +0100 Subject: [PATCH 073/122] Generate a random map when changing floor --- squirrelbattle/game.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index d51a7fc..7e5874c 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -53,7 +53,6 @@ class Game: """ Creates a new game on the screen. """ - # TODO generate a new map procedurally self.maps = [] self.map_index = 0 self.map = broguelike.Generator().run() @@ -188,9 +187,7 @@ class Game: self.map_index = 0 return while self.map_index >= len(self.maps): - # TODO: generate a new map - self.maps.append(Map.load(ResourceManager.get_asset_path( - "example_map_2.txt"))) + self.maps.append(broguelike.Generator().run()) new_map = self.map new_map.floor = self.map_index old_map.remove_entity(self.player) -- 2.39.5 From 949555ffffb60ec70bf8b5889d12ec36c5ff8ef7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 20:06:32 +0100 Subject: [PATCH 074/122] Map at floor -1 is now not deterministic --- squirrelbattle/tests/game_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 2b8bbe4..ef81e44 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -745,8 +745,6 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.LADDER) self.assertEqual(self.game.map_index, 1) self.assertEqual(self.game.player.map.floor, 1) - self.assertEqual(self.game.player.y, 1) - self.assertEqual(self.game.player.x, 17) self.game.display_actions(DisplayActions.UPDATE) # Move up -- 2.39.5 From ad3cce116e470d20548fab41cbe4e4f59944ebaf Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 21:23:12 +0100 Subject: [PATCH 075/122] Load map floor index when loading a new game --- squirrelbattle/game.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 7e5874c..fb10404 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -343,6 +343,8 @@ class Game: try: self.map_index = d["map_index"] self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]] + for i, m in enumerate(self.maps): + m.floor = i except KeyError: self.message = _("Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted.") -- 2.39.5 From 2b63f8b3f154060de20c517d9b4d700a4a41260f Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 18:54:36 +0100 Subject: [PATCH 076/122] Added stats in doc. --- docs/entities/player.rst | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/entities/player.rst b/docs/entities/player.rst index fc0d12c..62f1e3c 100644 --- a/docs/entities/player.rst +++ b/docs/entities/player.rst @@ -5,6 +5,9 @@ Joueur .. _`paramètres`: ../settings.html .. _`pack de textures`: ../texture-pack.html .. _`objet`: items.html +.. _`parchemins`: items.html#Parchemin +.. _`batons` : items.html#Baton de boule de feu +.. _`arc` : items.html#Arc Le joueur est une `entité attaquante`_, contrôlée par l'utilisateur humain. @@ -50,6 +53,20 @@ de chances de rendre confus tous les ennemis à distance moins de 3. Un ennemi c ne peut pas attaquer. +Statistiques +------------ + +Le joueur possède plusieurs statistiques : + +* Niveau : son niveau, qui dépend de combien d'expérience il a accumulé +* Expérience : la quantité d'expérience accumulée par le joueur, qui dépend de combien d'entités il a tué. +* Force : indique combien de dommages le joueur inflige à ses ennemis +* Intelligence : joue sur l'effet des objets magiques, tels que les `parchemins`_ ou les `batons`_ +* Charisme : joue sur l'efficacité de la danse du joueur +* Dextérité : joue sur l'efficacité de l'`arc`_ +* Constitution : joue sur la quantité de dégâts que le joueur prend lorsqu'un monstre le frappe +* Taux de critique : la chance (en pourcentage) que le joueur a de faire un coup critique + Expérience ---------- @@ -58,6 +75,4 @@ Lorsque le joueur atteint la quantité d'expérience requise pour monter de nive le joueur gagne un niveau, regagne toute sa vie, consomme son expérience et la nouvelle quantité d'expérience requise est 10 fois le niveau actuel. De plus, entre 5 et 10 fois le niveau actuel entités apparaissent aléatoirement sur la -carte à la montée de niveau. Enfin, le joueur améliore ses statistiques en augmentant -de niveau. Toutes les caractéristiques ne sont pas incrémentées à chaque niveau -gagné. +carte à la montée de niveau. Enfin, le joueur améliore ses statistiques en augmentant de niveau. Toutes les caractéristiques ne sont pas incrémentées à chaque niveau gagné. -- 2.39.5 From cddff5c2d9943eab747666d1e8b4bd59eeb72f43 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 19:17:04 +0100 Subject: [PATCH 077/122] Fix linting --- squirrelbattle/enums.py | 4 ++-- squirrelbattle/tests/game_test.py | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 42bd643..b61f236 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -53,7 +53,8 @@ class KeyValues(Enum): DANCE = auto() @staticmethod - def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: + def translate_key(key: str, settings: Settings) \ + -> Optional["KeyValues"]: # noqa: C901 """ Translates the raw string key into an enum value that we can use. """ @@ -91,4 +92,3 @@ class KeyValues(Enum): return KeyValues.LAUNCH elif key == settings.KEY_DANCE: return KeyValues.DANCE - return None diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index a3c3a87..895af12 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -260,12 +260,10 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.DANCE) self.assertEqual(rabbit.confused, 1) string = rabbit.hit(self.game.player) - self.assertEqual(string, - "{name} is confused, it can not hit {opponent}." - .format(name=_(rabbit.translated_name.capitalize() - ), opponent=_( - self.game.player.translated_name - ))) + self.assertEqual( + string, _("{name} is confused, it can not hit {opponent}.") + .format(name=rabbit.translated_name.capitalize(), + opponent=self.game.player.translated_name)) rabbit.confused = 0 self.game.player.charisma = 0 self.game.handle_key_pressed(KeyValues.DANCE) -- 2.39.5 From 01ee49ddd44a12b0aa0bad39a6b01c76185b7b62 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 19:25:12 +0100 Subject: [PATCH 078/122] Entities are updated after a bow shot, fixes #70 --- squirrelbattle/entities/items.py | 11 ++++++----- squirrelbattle/game.py | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 182c86b..ac5f74d 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -47,7 +47,7 @@ class Item(Entity): Indicates what should be done when the item is used. """ - def throw(self, direction: int) -> None: + def throw(self, direction: int) -> Any: """ Indicates what should be done when the item is thrown. """ @@ -555,7 +555,7 @@ class LongRangeWeapon(Weapon): self.held_by, self.damage + getattr(self.held_by, self.stat)) self.held_by.map.logs.add_message(line) - return (to_kill.x, to_kill.y) if to_kill else None + return (to_kill.y, to_kill.x) if to_kill else None def equip(self) -> None: """ @@ -621,17 +621,18 @@ class FireBallStaff(LongRangeWeapon): def string(self) -> str: return " is shot by a fire ball." - def throw(self, direction: int) -> None: + def throw(self, direction: int) -> Any: """ Adds an explosion animation when killing something. """ coord = super().throw(direction) if coord: - x = coord[0] - y = coord[1] + y = coord[0] + x = coord[1] explosion = Explosion(y=y, x=x) self.held_by.map.add_entity(explosion) + return y, x class Monocle(Item): diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index bde825f..83da5f3 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -292,7 +292,8 @@ class Game: return if self.player.equipped_main: - self.player.equipped_main.throw(direction) + if self.player.equipped_main.throw(direction): + self.map.tick(self.player) def handle_key_pressed_inventory(self, key: KeyValues) -> None: """ -- 2.39.5 From 16ddbd31f305e897d60a81a4fc847738ed1dd766 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Sun, 10 Jan 2021 19:41:51 +0100 Subject: [PATCH 079/122] also prevent updating seen tiles when checking vision --- squirrelbattle/interfaces.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index bf8ddbe..330301c 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -197,9 +197,11 @@ class Map: def is_visible_from(self, starty: int, startx: int, desty: int, destx: int, max_range: int) -> bool: oldvisibility = deepcopy(self.visibility) + oldseen = deepcopy(self.seen_tiles) self.compute_visibility(starty, startx, max_range) result = self.visibility[desty][destx] self.visibility = oldvisibility + self.seen_tiles = oldseen return result def compute_visibility(self, y: int, x: int, max_range: int) -> None: -- 2.39.5 From 5694dd4dff20d5aa9da90344ebe2e4e49a38bed4 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 19:52:28 +0100 Subject: [PATCH 080/122] Update the player inventory when opening the menu/loading the game, fixes #72 --- squirrelbattle/display/menudisplay.py | 1 + squirrelbattle/game.py | 1 + 2 files changed, 2 insertions(+) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 54acaed..ccc3561 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -162,6 +162,7 @@ class PlayerInventoryDisplay(MenuDisplay): def update(self, game: Game) -> None: self.player = game.player self.update_menu(game.inventory_menu) + game.inventory_menu.update_player(self.player) self.store_mode = game.state == GameMode.STORE self.chest_mode = game.state == GameMode.CHEST self.selected = game.state == GameMode.INVENTORY \ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 83da5f3..9cfa721 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -435,6 +435,7 @@ class Game: return self.player = players[0] + self.inventory_menu.update_player(self.player) self.map.compute_visibility(self.player.y, self.player.x, self.player.vision) self.display_actions(DisplayActions.UPDATE) -- 2.39.5 From 3758cb13361682922eadfba3ab6be8b6c69889cf Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 20:20:12 +0100 Subject: [PATCH 081/122] Fix the GiantSeaEagle registration, fixes #76 --- squirrelbattle/game.py | 4 ++-- squirrelbattle/interfaces.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 9cfa721..f59f676 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -419,9 +419,9 @@ class Game: try: self.map_index = d["map_index"] self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]] - except KeyError: + except KeyError as error: self.message = _("Some keys are missing in your save file.\n" - "Your save seems to be corrupt. It got deleted.") + "Your save seems to be corrupt. It got deleted.") + f"\n{error}" os.unlink(ResourceManager.get_config_path("save.json")) self.display_actions(DisplayActions.UPDATE) return diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 330301c..14fcc6d 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -662,8 +662,8 @@ class Entity: "Bow": Bow, "Chest": Chest, "Chestplate": Chestplate, - "Eagle": GiantSeaEagle, "FireBallStaff": FireBallStaff, + "GiantSeaEagle": GiantSeaEagle, "Heart": Heart, "Hedgehog": Hedgehog, "Helmet": Helmet, -- 2.39.5 From 67a9bda6e180aa95907eefc0a406fc2bc6a4ab27 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 20:46:01 +0100 Subject: [PATCH 082/122] Don't fill the inventory of a chest or a Merchant if it is empty, fixes #73 --- squirrelbattle/entities/friendly.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 57506e9..1a92392 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -24,9 +24,11 @@ class Merchant(InventoryHolder, FriendlyEntity): def __init__(self, name: str = "merchant", inventory: list = None, hazel: int = 75, maxhealth: int = 8, *args, **kwargs): super().__init__(name=name, maxhealth=maxhealth, *args, **kwargs) - self.inventory = self.translate_inventory(inventory or []) + self.inventory = self.translate_inventory(inventory) \ + if inventory is not None else None self.hazel = hazel - if not self.inventory: + if self.inventory is None: + self.inventory = [] for i in range(5): self.inventory.append(choice(Item.get_all_items())()) @@ -54,9 +56,11 @@ class Chest(InventoryHolder, FriendlyEntity): hazel: int = 0, *args, **kwargs): super().__init__(name=name, *args, **kwargs) self.hazel = hazel - self.inventory = self.translate_inventory(inventory or []) + self.inventory = self.translate_inventory(inventory) \ + if inventory is not None else None self.annihilated = False - if not self.inventory: + if self.inventory is None: + self.inventory = [] for i in range(3): self.inventory.append(choice(Item.get_all_items())()) @@ -84,7 +88,6 @@ class Chest(InventoryHolder, FriendlyEntity): """ return self.annihilated - class Sunflower(FriendlyEntity): """ A friendly sunflower. -- 2.39.5 From d2af345c0ca4e8cce2182482d32ee357ba96f2f0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 21:01:43 +0100 Subject: [PATCH 083/122] Fix linting --- squirrelbattle/entities/friendly.py | 1 + squirrelbattle/game.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 1a92392..9b149de 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -88,6 +88,7 @@ class Chest(InventoryHolder, FriendlyEntity): """ return self.annihilated + class Sunflower(FriendlyEntity): """ A friendly sunflower. diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index f59f676..44c1f48 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -421,7 +421,8 @@ class Game: self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]] except KeyError as error: self.message = _("Some keys are missing in your save file.\n" - "Your save seems to be corrupt. It got deleted.") + f"\n{error}" + "Your save seems to be corrupt. It got deleted.")\ + + f"\n{error}" os.unlink(ResourceManager.get_config_path("save.json")) self.display_actions(DisplayActions.UPDATE) return -- 2.39.5 From a1d69203c9c5dbc4bec2954d57c5a656a0725659 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 21:15:30 +0100 Subject: [PATCH 084/122] Translations. --- .../locale/de/LC_MESSAGES/squirrelbattle.po | 197 ++++++++++------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 202 +++++++++++------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 154 ++++++++----- 3 files changed, 345 insertions(+), 208 deletions(-) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 2cea0a3..1aed270 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,3 +1,13 @@ +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." + +msgid "ring_of_more_experience" +msgstr "" + +msgid "ring_of_critical_damage" +msgstr "" + # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao # This file is distributed under the same license as the squirrelbattle package. @@ -8,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-08 15:15+0100\n" +"POT-Creation-Date: 2021-01-10 20:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,116 +27,146 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -msgid "ring_of_critical_damage" -msgstr "" - -msgid "ring_of_more_experience" -msgstr "" - -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." - -#: squirrelbattle/display/creditsdisplay.py:28 -#: squirrelbattle/display/menudisplay.py:123 -#: squirrelbattle/display/menudisplay.py:148 -msgid "Credits" -msgstr "" - -#: squirrelbattle/display/creditsdisplay.py:32 -msgid "Developers:" -msgstr "" - -#: squirrelbattle/display/creditsdisplay.py:38 -msgid "Translators:" -msgstr "" - -#: squirrelbattle/display/menudisplay.py:168 -msgid "INVENTORY" -msgstr "BESTAND" - -#: squirrelbattle/display/menudisplay.py:214 -msgid "STALL" -msgstr "STAND" - -#: squirrelbattle/display/statsdisplay.py:44 +#: squirrelbattle/display/gamedisplay.py:150 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/gamedisplay.py:167 msgid "Equipped main:" -msgstr "" +msgstr "Hauptausgestattete Ding" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/gamedisplay.py:171 msgid "Equipped secondary:" -msgstr "" +msgstr "zusätzlich Ausgestattete Ding" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/gamedisplay.py:176 msgid "Equipped chestplate:" -msgstr "" +msgstr "Ausgestattet Harnisch" -#: squirrelbattle/display/statsdisplay.py:74 +#: squirrelbattle/display/gamedisplay.py:180 msgid "Equipped helmet:" -msgstr "" +msgstr "Ausgestattet Helm" -#: squirrelbattle/display/statsdisplay.py:81 +#: squirrelbattle/display/gamedisplay.py:187 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" -#: squirrelbattle/display/statsdisplay.py:85 +#: squirrelbattle/display/gamedisplay.py:191 #, python-brace-format msgid "Use {key} to use the ladder" -msgstr "" +msgstr "Nutzen {key} um die Leiter zu nutzen" -#: squirrelbattle/display/statsdisplay.py:94 +#: squirrelbattle/display/gamedisplay.py:210 msgid "Move to the friendly entity to talk to it" -msgstr "" +msgstr "Ziehen Sie zu der freundlichen Einheit hin, um mit ihr zu sprechen" -#: squirrelbattle/display/statsdisplay.py:96 +#: squirrelbattle/display/gamedisplay.py:212 #, python-brace-format msgid "Use {key} then move to talk to the entity" -msgstr "" +msgstr "Verwenden Sie {key} dann bewegen Sie sich, um mit der Einheit zu sprechen" + +#: squirrelbattle/display/menudisplay.py:124 +#: squirrelbattle/display/menudisplay.py:149 +#: squirrelbattle/display/menudisplay.py:304 +msgid "Credits" +msgstr "Abspann" + +#: squirrelbattle/display/menudisplay.py:173 +msgid "INVENTORY" +msgstr "BESTAND" + +#: squirrelbattle/display/menudisplay.py:219 +msgid "STALL" +msgstr "STAND" + +#: squirrelbattle/display/menudisplay.py:263 +msgid "CHEST" +msgstr "KASTE" + +#: squirrelbattle/display/menudisplay.py:308 +msgid "Developers:" +msgstr "Entwickler:" + +#: squirrelbattle/display/menudisplay.py:314 +msgid "Translators:" +msgstr "Ubersetzer:" #. TODO -#: squirrelbattle/entities/friendly.py:33 +#: squirrelbattle/entities/friendly.py:38 msgid "I don't sell any squirrel" msgstr "Ich verkaufe keinen Eichhörnchen." -#: squirrelbattle/entities/friendly.py:55 +#: squirrelbattle/entities/friendly.py:68 +msgid "You have opened the chest" +msgstr "Sie haben der Kaste geöffnet" + +#: squirrelbattle/entities/friendly.py:77 +msgid "The chest exploded" +msgstr "" + +#: squirrelbattle/entities/friendly.py:78 +msgid "It's not really effective" +msgstr "Es ist nicht wirklich effektiv" + +#: squirrelbattle/entities/friendly.py:101 msgid "Flower power!!" msgstr "Blumenmacht!!" -#: squirrelbattle/entities/friendly.py:55 +#: squirrelbattle/entities/friendly.py:101 msgid "The sun is warm today" msgstr "Die Sonne ist warm heute" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:178 +#: squirrelbattle/entities/items.py:189 msgid "Bomb is exploding." msgstr "Die Bombe explodiert." -#: squirrelbattle/entities/items.py:365 +#: squirrelbattle/entities/items.py:385 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." -#: squirrelbattle/game.py:200 +#: squirrelbattle/entities/items.py:519 +msgid "" +"The ennemies have -{max(1, self.held_by.intelligence // 2)}strength for 3 " +"turns" +msgstr "Die Feinde haben 3 Runden lang - {max(1, self.held_by.intelligence // 2)} Stärke" +"" + +#: squirrelbattle/entities/items.py:552 +#, python-brace-format +msgid "{name}" +msgstr "{name}" + +#: squirrelbattle/entities/player.py:83 +msgid "It worked! Nearby ennemies will be confused for 3 turns." +msgstr "Es funktionierte! In der Nähe befindliche Feinde werden 3 Runden lang verwirrt." + +#: squirrelbattle/entities/player.py:86 +msgid "It worked, but there is no one nearby..." +msgstr "Es hat funktioniert, aber es ist niemand in der Nähe ..." + +#: squirrelbattle/entities/player.py:89 +msgid "The dance was not effective..." +msgstr "Der Tanz war nicht effektiv ..." + +#: squirrelbattle/game.py:214 #, python-brace-format msgid "The player climbs down to the floor {floor}." msgstr "Der Spieler klettert auf dem Stock {floor} hinunter." -#: squirrelbattle/game.py:213 +#: squirrelbattle/game.py:227 #, python-brace-format msgid "The player climbs up the floor {floor}." msgstr "Der Spieler klettert auf dem Stock {floor} hinoben." -#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603 +#: squirrelbattle/game.py:348 squirrelbattle/tests/game_test.py:631 msgid "The buyer does not have enough money" msgstr "Der Kaufer hat nicht genug Geld" -#: squirrelbattle/game.py:349 +#: squirrelbattle/game.py:423 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -134,7 +174,7 @@ msgstr "" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." -#: squirrelbattle/game.py:357 +#: squirrelbattle/game.py:431 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -142,7 +182,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:379 +#: squirrelbattle/game.py:454 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -150,26 +190,31 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:718 -msgid "It's a critical hit!" -msgstr "" +#: squirrelbattle/interfaces.py:758 squirrelbattle/tests/game_test.py:264 +#, python-brace-format +msgid "{name} is confused, it can not hit {opponent}." +msgstr "{name} ist verwirrt, es kann {opponent} nicht schlagen." -#: squirrelbattle/interfaces.py:719 +#: squirrelbattle/interfaces.py:766 +msgid "It's a critical hit!" +msgstr "Es ist ein kritischer Treffer!" + +#: squirrelbattle/interfaces.py:767 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:733 +#: squirrelbattle/interfaces.py:781 #, python-brace-format msgid "{name} takes {damage} damage." -msgstr "" +msgstr "{name} erleidet {damage} Schaden." -#: squirrelbattle/interfaces.py:735 +#: squirrelbattle/interfaces.py:783 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/interfaces.py:769 +#: squirrelbattle/interfaces.py:817 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} hat gesagt: {message}" @@ -178,8 +223,8 @@ msgstr "{entity} hat gesagt: {message}" msgid "Back" msgstr "Zurück" -#: squirrelbattle/tests/game_test.py:368 squirrelbattle/tests/game_test.py:371 -#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377 +#: squirrelbattle/tests/game_test.py:395 squirrelbattle/tests/game_test.py:398 +#: squirrelbattle/tests/game_test.py:401 squirrelbattle/tests/game_test.py:404 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" @@ -306,7 +351,7 @@ msgstr "Tiger" #: squirrelbattle/tests/translations_test.py:70 msgid "eagle" -msgstr "" +msgstr "Adler" #: squirrelbattle/tests/translations_test.py:72 msgid "body snatch potion" @@ -334,20 +379,20 @@ msgstr "" #: squirrelbattle/tests/translations_test.py:78 msgid "chestplate" -msgstr "" +msgstr "Brustpanzer" #: squirrelbattle/tests/translations_test.py:79 msgid "shield" -msgstr "" +msgstr "Schild" #: squirrelbattle/tests/translations_test.py:80 msgid "ring of critical damage" -msgstr "" +msgstr "Ring des kritischen Schadens" #: squirrelbattle/tests/translations_test.py:82 msgid "ring of more experience" -msgstr "" +msgstr "Ring der mehr Erfahrung" #: squirrelbattle/tests/translations_test.py:84 msgid "monocle" -msgstr "" +msgstr "Monokel" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 18868c3..7b19c4e 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -1,3 +1,13 @@ +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} recibe {amount} daño." + +msgid "ring_of_more_experience" +msgstr "ring_of_more_experience" + +msgid "ring_of_critical_damage" +msgstr "ring_of_critical_damage" + # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao # This file is distributed under the same license as the squirrelbattle package. @@ -8,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-08 15:15+0100\n" +"POT-Creation-Date: 2021-01-10 20:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,115 +27,146 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -msgid "ring_of_critical_damage" -msgstr "" - -msgid "ring_of_more_experience" -msgstr "" - -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} recibe {amount} daño." - -#: squirrelbattle/display/creditsdisplay.py:28 -#: squirrelbattle/display/menudisplay.py:123 -#: squirrelbattle/display/menudisplay.py:148 -msgid "Credits" -msgstr "" - -#: squirrelbattle/display/creditsdisplay.py:32 -msgid "Developers:" -msgstr "" - -#: squirrelbattle/display/creditsdisplay.py:38 -msgid "Translators:" -msgstr "" - -#: squirrelbattle/display/menudisplay.py:168 -msgid "INVENTORY" -msgstr "INVENTORIO" - -#: squirrelbattle/display/menudisplay.py:214 -msgid "STALL" -msgstr "PUESTO" - -#: squirrelbattle/display/statsdisplay.py:44 +#: squirrelbattle/display/gamedisplay.py:150 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/gamedisplay.py:167 msgid "Equipped main:" -msgstr "" +msgstr "Principal equipado:" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/gamedisplay.py:171 msgid "Equipped secondary:" -msgstr "" +msgstr "Equipado secundario:" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/gamedisplay.py:176 msgid "Equipped chestplate:" -msgstr "" +msgstr "Pechera equipada:" -#: squirrelbattle/display/statsdisplay.py:74 +#: squirrelbattle/display/gamedisplay.py:180 msgid "Equipped helmet:" -msgstr "" +msgstr "Casco equipado:" -#: squirrelbattle/display/statsdisplay.py:81 +#: squirrelbattle/display/gamedisplay.py:187 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" -#: squirrelbattle/display/statsdisplay.py:85 +#: squirrelbattle/display/gamedisplay.py:191 #, python-brace-format msgid "Use {key} to use the ladder" -msgstr "" +msgstr "Usa {key} para usar la escalera" -#: squirrelbattle/display/statsdisplay.py:94 +#: squirrelbattle/display/gamedisplay.py:210 msgid "Move to the friendly entity to talk to it" -msgstr "" +msgstr "Muévete hacia la entidad amiga para hablar con ella." -#: squirrelbattle/display/statsdisplay.py:96 +#: squirrelbattle/display/gamedisplay.py:212 #, python-brace-format msgid "Use {key} then move to talk to the entity" +msgstr "Usa {key} y luego muévete para hablar con la entidad" + +#: squirrelbattle/display/menudisplay.py:124 +#: squirrelbattle/display/menudisplay.py:149 +#: squirrelbattle/display/menudisplay.py:304 +msgid "Credits" msgstr "" -#: squirrelbattle/entities/friendly.py:33 +#: squirrelbattle/display/menudisplay.py:173 +msgid "INVENTORY" +msgstr "INVENTORIO" + +#: squirrelbattle/display/menudisplay.py:219 +msgid "STALL" +msgstr "PUESTO" + +#: squirrelbattle/display/menudisplay.py:263 +msgid "CHEST" +msgstr "COFRE" + +#: squirrelbattle/display/menudisplay.py:308 +msgid "Developers:" +msgstr "Desarrollador:" + +#: squirrelbattle/display/menudisplay.py:314 +msgid "Translators:" +msgstr "Traductores:" + +#: squirrelbattle/entities/friendly.py:38 msgid "I don't sell any squirrel" msgstr "No vendo ninguna ardilla" -#: squirrelbattle/entities/friendly.py:55 +#: squirrelbattle/entities/friendly.py:68 +msgid "You have opened the chest" +msgstr "Abriste el cofre" + +#: squirrelbattle/entities/friendly.py:77 +msgid "The chest exploded" +msgstr "El cofre explotó" + +#: squirrelbattle/entities/friendly.py:78 +msgid "It's not really effective" +msgstr "No es realmente efectivo" + +#: squirrelbattle/entities/friendly.py:101 msgid "Flower power!!" msgstr "Poder de las flores!!" -#: squirrelbattle/entities/friendly.py:55 +#: squirrelbattle/entities/friendly.py:101 msgid "The sun is warm today" msgstr "El sol está caliente hoy" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:178 +#: squirrelbattle/entities/items.py:189 msgid "Bomb is exploding." msgstr "La bomba está explotando." -#: squirrelbattle/entities/items.py:365 +#: squirrelbattle/entities/items.py:385 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} intercambió su cuerpo con {entity}." -#: squirrelbattle/game.py:200 +#: squirrelbattle/entities/items.py:519 +msgid "" +"The ennemies have -{max(1, self.held_by.intelligence // 2)}strength for 3 " +"turns" +msgstr "" +"Los enemigos tienen - {max(1, self.held_by.intelligence // 2)} fuerza durante 3" +"turnos" + +#: squirrelbattle/entities/items.py:552 +#, python-brace-format +msgid "{name}" +msgstr "{name}" + +#: squirrelbattle/entities/player.py:83 +msgid "It worked! Nearby ennemies will be confused for 3 turns." +msgstr "¡Funcionó! Los enemigos cercanos se confundirán durante 3 turnos." + +#: squirrelbattle/entities/player.py:86 +msgid "It worked, but there is no one nearby..." +msgstr "Funcionó, pero no hay nadie cerca ..." + +#: squirrelbattle/entities/player.py:89 +msgid "The dance was not effective..." +msgstr "El baile no fue efectivo ..." + +#: squirrelbattle/game.py:214 #, python-brace-format msgid "The player climbs down to the floor {floor}." -msgstr "" +msgstr "El jugador desciende alla planta {floor}." -#: squirrelbattle/game.py:213 +#: squirrelbattle/game.py:227 #, python-brace-format msgid "The player climbs up the floor {floor}." -msgstr "" +msgstr "El jugador sube por la planta {floor}." -#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603 +#: squirrelbattle/game.py:348 squirrelbattle/tests/game_test.py:631 msgid "The buyer does not have enough money" msgstr "El comprador no tiene suficiente dinero" -#: squirrelbattle/game.py:349 +#: squirrelbattle/game.py:423 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -133,7 +174,7 @@ msgstr "" "Algunas claves faltan en su archivo de guarda.\n" "Su guarda parece a ser corruptido. Fue eliminado." -#: squirrelbattle/game.py:357 +#: squirrelbattle/game.py:431 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -141,7 +182,7 @@ msgstr "" "No jugador encontrado sobre la carta !\n" "¿ Quizas murió ?" -#: squirrelbattle/game.py:379 +#: squirrelbattle/game.py:454 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -149,26 +190,31 @@ msgstr "" "El JSON archivo no es correcto.\n" "Su guarda parece corrupta. Fue eliminada." -#: squirrelbattle/interfaces.py:718 -msgid "It's a critical hit!" -msgstr "" +#: squirrelbattle/interfaces.py:758 squirrelbattle/tests/game_test.py:264 +#, python-brace-format +msgid "{name} is confused, it can not hit {opponent}." +msgstr "{name} está confundido, no puede golpear a {opponent}." -#: squirrelbattle/interfaces.py:719 +#: squirrelbattle/interfaces.py:766 +msgid "It's a critical hit!" +msgstr "¡Es un golpe crítico!" + +#: squirrelbattle/interfaces.py:767 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} golpea a {opponent}." -#: squirrelbattle/interfaces.py:733 +#: squirrelbattle/interfaces.py:781 #, python-brace-format msgid "{name} takes {damage} damage." -msgstr "" +msgstr "{name} recibe {damage} daño." -#: squirrelbattle/interfaces.py:735 +#: squirrelbattle/interfaces.py:783 #, python-brace-format msgid "{name} dies." msgstr "{name} se muere." -#: squirrelbattle/interfaces.py:769 +#: squirrelbattle/interfaces.py:817 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} dijo : {message}" @@ -177,8 +223,8 @@ msgstr "{entity} dijo : {message}" msgid "Back" msgstr "Volver" -#: squirrelbattle/tests/game_test.py:368 squirrelbattle/tests/game_test.py:371 -#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377 +#: squirrelbattle/tests/game_test.py:395 squirrelbattle/tests/game_test.py:398 +#: squirrelbattle/tests/game_test.py:401 squirrelbattle/tests/game_test.py:404 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nuevo partido" @@ -305,7 +351,7 @@ msgstr "tigre" #: squirrelbattle/tests/translations_test.py:70 msgid "eagle" -msgstr "" +msgstr "águila" #: squirrelbattle/tests/translations_test.py:72 msgid "body snatch potion" @@ -329,24 +375,24 @@ msgstr "espada" #: squirrelbattle/tests/translations_test.py:77 msgid "helmet" -msgstr "" +msgstr "casco" #: squirrelbattle/tests/translations_test.py:78 msgid "chestplate" -msgstr "" +msgstr "pechera" #: squirrelbattle/tests/translations_test.py:79 msgid "shield" -msgstr "" +msgstr "escudo" #: squirrelbattle/tests/translations_test.py:80 msgid "ring of critical damage" -msgstr "" +msgstr "anillo de daño crítico" #: squirrelbattle/tests/translations_test.py:82 msgid "ring of more experience" -msgstr "" +msgstr "anillo de más experiencia" #: squirrelbattle/tests/translations_test.py:84 msgid "monocle" -msgstr "" +msgstr "monóculo" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 4657fe8..c8835bd 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -1,3 +1,7 @@ +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} prend {amount} points de dégât." + # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao # This file is distributed under the same license as the squirrelbattle package. @@ -8,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-08 15:15+0100\n" +"POT-Creation-Date: 2021-01-10 20:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,110 +21,147 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} prend {amount} points de dégât." - -#: squirrelbattle/display/creditsdisplay.py:28 -#: squirrelbattle/display/menudisplay.py:123 -#: squirrelbattle/display/menudisplay.py:148 -msgid "Credits" -msgstr "" - -#: squirrelbattle/display/creditsdisplay.py:32 -msgid "Developers:" -msgstr "" - -#: squirrelbattle/display/creditsdisplay.py:38 -msgid "Translators:" -msgstr "" - -#: squirrelbattle/display/menudisplay.py:168 -msgid "INVENTORY" -msgstr "INVENTAIRE" - -#: squirrelbattle/display/menudisplay.py:214 -msgid "STALL" -msgstr "STAND" - -#: squirrelbattle/display/statsdisplay.py:44 +#: squirrelbattle/display/gamedisplay.py:150 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/gamedisplay.py:167 msgid "Equipped main:" msgstr "Équipement principal :" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/gamedisplay.py:171 msgid "Equipped secondary:" msgstr "Équipement secondaire :" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/gamedisplay.py:176 msgid "Equipped chestplate:" msgstr "Plastron équipé :" -#: squirrelbattle/display/statsdisplay.py:74 +#: squirrelbattle/display/gamedisplay.py:180 msgid "Equipped helmet:" msgstr "Casque équipé :" -#: squirrelbattle/display/statsdisplay.py:81 +#: squirrelbattle/display/gamedisplay.py:187 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" -#: squirrelbattle/display/statsdisplay.py:85 +#: squirrelbattle/display/gamedisplay.py:191 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "Appuyez sur {key} pour utiliser l'échelle" -#: squirrelbattle/display/statsdisplay.py:94 +#: squirrelbattle/display/gamedisplay.py:210 msgid "Move to the friendly entity to talk to it" msgstr "Avancez vers l'entité pour lui parler" -#: squirrelbattle/display/statsdisplay.py:96 +#: squirrelbattle/display/gamedisplay.py:212 #, python-brace-format msgid "Use {key} then move to talk to the entity" msgstr "Appuyez sur {key} puis déplacez-vous pour parler" +#: squirrelbattle/display/menudisplay.py:124 +#: squirrelbattle/display/menudisplay.py:149 +#: squirrelbattle/display/menudisplay.py:304 +msgid "Credits" +msgstr "Crédits" + +#: squirrelbattle/display/menudisplay.py:173 +msgid "INVENTORY" +msgstr "INVENTAIRE" + +#: squirrelbattle/display/menudisplay.py:219 +msgid "STALL" +msgstr "STAND" + +#: squirrelbattle/display/menudisplay.py:263 +msgid "CHEST" +msgstr "COFFRE" + +#: squirrelbattle/display/menudisplay.py:308 +msgid "Developers:" +msgstr "Développeurs:" + +#: squirrelbattle/display/menudisplay.py:314 +msgid "Translators:" +msgstr "Traducteurs:" + #. TODO -#: squirrelbattle/entities/friendly.py:33 +#: squirrelbattle/entities/friendly.py:38 msgid "I don't sell any squirrel" msgstr "Je ne vends pas d'écureuil" -#: squirrelbattle/entities/friendly.py:55 +#: squirrelbattle/entities/friendly.py:68 +msgid "You have opened the chest" +msgstr "Vous avez ouvert le coffre" + +#: squirrelbattle/entities/friendly.py:77 +msgid "The chest exploded" +msgstr "Le coffre a explosé" + +#: squirrelbattle/entities/friendly.py:78 +msgid "It's not really effective" +msgstr "Ce n'est pas très efficace" + +#: squirrelbattle/entities/friendly.py:101 msgid "Flower power!!" msgstr "Pouvoir des fleurs !!" -#: squirrelbattle/entities/friendly.py:55 +#: squirrelbattle/entities/friendly.py:101 msgid "The sun is warm today" msgstr "Le soleil est chaud aujourd'hui" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:178 +#: squirrelbattle/entities/items.py:189 msgid "Bomb is exploding." msgstr "La bombe explose." -#: squirrelbattle/entities/items.py:365 +#: squirrelbattle/entities/items.py:385 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/game.py:200 +#: squirrelbattle/entities/items.py:519 +msgid "" +"The ennemies have -{max(1, self.held_by.intelligence // 2)}strength for 3 " +"turns" +msgstr "" +"TLes ennemis ont -{max(1, self.held_by.intelligence // 2)} de force pour 3 " +"tours" + +#: squirrelbattle/entities/items.py:552 +#, python-brace-format +msgid "{name}" +msgstr "{name}" + +#: squirrelbattle/entities/player.py:83 +msgid "It worked! Nearby ennemies will be confused for 3 turns." +msgstr "Ça a marché ! Les ennemis proches seront confus pendant 3 tours" + +#: squirrelbattle/entities/player.py:86 +msgid "It worked, but there is no one nearby..." +msgstr "Ça a marché, mais il n'y a personne à proximité..." + +#: squirrelbattle/entities/player.py:89 +msgid "The dance was not effective..." +msgstr "La dance n'a pas fonctionné..." + +#: squirrelbattle/game.py:214 #, python-brace-format msgid "The player climbs down to the floor {floor}." msgstr "Le joueur descend à l'étage {floor}." -#: squirrelbattle/game.py:213 +#: squirrelbattle/game.py:227 #, python-brace-format msgid "The player climbs up the floor {floor}." msgstr "Le joueur monte à l'étage {floor}." -#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603 +#: squirrelbattle/game.py:348 squirrelbattle/tests/game_test.py:631 msgid "The buyer does not have enough money" msgstr "L'acheteur n'a pas assez d'argent" -#: squirrelbattle/game.py:349 +#: squirrelbattle/game.py:423 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -128,7 +169,7 @@ msgstr "" "Certaines clés de votre ficher de sauvegarde sont manquantes.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/game.py:357 +#: squirrelbattle/game.py:431 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -136,7 +177,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:379 +#: squirrelbattle/game.py:454 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -144,26 +185,31 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:718 +#: squirrelbattle/interfaces.py:758 squirrelbattle/tests/game_test.py:264 +#, python-brace-format +msgid "{name} is confused, it can not hit {opponent}." +msgstr "{name} est confus et ne peut pas frapper {opponent}." + +#: squirrelbattle/interfaces.py:766 msgid "It's a critical hit!" msgstr "C'est un coup critique !" -#: squirrelbattle/interfaces.py:719 +#: squirrelbattle/interfaces.py:767 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:733 +#: squirrelbattle/interfaces.py:781 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "{name} prend {damage} dégâts." -#: squirrelbattle/interfaces.py:735 +#: squirrelbattle/interfaces.py:783 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/interfaces.py:769 +#: squirrelbattle/interfaces.py:817 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} a dit : {message}" @@ -172,8 +218,8 @@ msgstr "{entity} a dit : {message}" msgid "Back" msgstr "Retour" -#: squirrelbattle/tests/game_test.py:368 squirrelbattle/tests/game_test.py:371 -#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377 +#: squirrelbattle/tests/game_test.py:395 squirrelbattle/tests/game_test.py:398 +#: squirrelbattle/tests/game_test.py:401 squirrelbattle/tests/game_test.py:404 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" -- 2.39.5 From 893a23f95ccbbb617a012a8e7356ddead48a6fd5 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 21:15:42 +0100 Subject: [PATCH 085/122] Repaired a but : bears dealt 1 damage instead of 0. --- squirrelbattle/interfaces.py | 3 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 30 +++++++++++-------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 22 +++++++------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 8 ++--- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 330301c..c71d7c8 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -774,7 +774,8 @@ class FightingEntity(Entity): The entity takes damage from the attacker based on their respective stats. """ - damage = max(1, amount - self.constitution) + if amount != 0 : + damage = max(1, amount - self.constitution) self.health -= damage if self.health <= 0: self.die() diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 1aed270..a468076 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,12 +1,12 @@ -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." +msgid "ring_of_critical_damage" +msgstr "" msgid "ring_of_more_experience" msgstr "" -msgid "ring_of_critical_damage" -msgstr "" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao @@ -18,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 20:35+0100\n" +"POT-Creation-Date: 2021-01-10 21:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -63,7 +63,8 @@ msgstr "Ziehen Sie zu der freundlichen Einheit hin, um mit ihr zu sprechen" #: squirrelbattle/display/gamedisplay.py:212 #, python-brace-format msgid "Use {key} then move to talk to the entity" -msgstr "Verwenden Sie {key} dann bewegen Sie sich, um mit der Einheit zu sprechen" +msgstr "" +"Verwenden Sie {key} dann bewegen Sie sich, um mit der Einheit zu sprechen" #: squirrelbattle/display/menudisplay.py:124 #: squirrelbattle/display/menudisplay.py:149 @@ -132,8 +133,9 @@ msgstr "{player} täuscht seinem Körper mit {entity} aus." msgid "" "The ennemies have -{max(1, self.held_by.intelligence // 2)}strength for 3 " "turns" -msgstr "Die Feinde haben 3 Runden lang - {max(1, self.held_by.intelligence // 2)} Stärke" -"" +msgstr "" +"Die Feinde haben 3 Runden lang - {max(1, self.held_by.intelligence // 2)} " +"Stärke" #: squirrelbattle/entities/items.py:552 #, python-brace-format @@ -142,7 +144,9 @@ msgstr "{name}" #: squirrelbattle/entities/player.py:83 msgid "It worked! Nearby ennemies will be confused for 3 turns." -msgstr "Es funktionierte! In der Nähe befindliche Feinde werden 3 Runden lang verwirrt." +msgstr "" +"Es funktionierte! In der Nähe befindliche Feinde werden 3 Runden lang " +"verwirrt." #: squirrelbattle/entities/player.py:86 msgid "It worked, but there is no one nearby..." @@ -204,17 +208,17 @@ msgstr "Es ist ein kritischer Treffer!" msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:781 +#: squirrelbattle/interfaces.py:782 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "{name} erleidet {damage} Schaden." -#: squirrelbattle/interfaces.py:783 +#: squirrelbattle/interfaces.py:784 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/interfaces.py:817 +#: squirrelbattle/interfaces.py:818 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} hat gesagt: {message}" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 7b19c4e..7428cd8 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -1,12 +1,12 @@ -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} recibe {amount} daño." +msgid "ring_of_critical_damage" +msgstr "ring_of_critical_damage" msgid "ring_of_more_experience" msgstr "ring_of_more_experience" -msgid "ring_of_critical_damage" -msgstr "ring_of_critical_damage" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} recibe {amount} daño." # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao @@ -18,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 20:35+0100\n" +"POT-Creation-Date: 2021-01-10 21:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -132,8 +132,8 @@ msgid "" "The ennemies have -{max(1, self.held_by.intelligence // 2)}strength for 3 " "turns" msgstr "" -"Los enemigos tienen - {max(1, self.held_by.intelligence // 2)} fuerza durante 3" -"turnos" +"Los enemigos tienen - {max(1, self.held_by.intelligence // 2)} fuerza " +"durante 3turnos" #: squirrelbattle/entities/items.py:552 #, python-brace-format @@ -204,17 +204,17 @@ msgstr "¡Es un golpe crítico!" msgid "{name} hits {opponent}." msgstr "{name} golpea a {opponent}." -#: squirrelbattle/interfaces.py:781 +#: squirrelbattle/interfaces.py:782 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "{name} recibe {damage} daño." -#: squirrelbattle/interfaces.py:783 +#: squirrelbattle/interfaces.py:784 #, python-brace-format msgid "{name} dies." msgstr "{name} se muere." -#: squirrelbattle/interfaces.py:817 +#: squirrelbattle/interfaces.py:818 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} dijo : {message}" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index c8835bd..bdd5132 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 20:35+0100\n" +"POT-Creation-Date: 2021-01-10 21:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -199,17 +199,17 @@ msgstr "C'est un coup critique !" msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:781 +#: squirrelbattle/interfaces.py:782 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "{name} prend {damage} dégâts." -#: squirrelbattle/interfaces.py:783 +#: squirrelbattle/interfaces.py:784 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/interfaces.py:817 +#: squirrelbattle/interfaces.py:818 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} a dit : {message}" -- 2.39.5 From 4f4223c514acb6fde43ce3277a66cefcb1b3dd6c Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 21:25:15 +0100 Subject: [PATCH 086/122] More translations. --- .../locale/de/LC_MESSAGES/squirrelbattle.po | 64 +++++++++++-------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 60 +++++++++-------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 52 ++++++++------- squirrelbattle/tests/translations_test.py | 4 ++ 4 files changed, 104 insertions(+), 76 deletions(-) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index a468076..3d9595a 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,12 +1,12 @@ -msgid "ring_of_critical_damage" -msgstr "" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." msgid "ring_of_more_experience" msgstr "" -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." +msgid "ring_of_critical_damage" +msgstr "" # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao @@ -18,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 21:14+0100\n" +"POT-Creation-Date: 2021-01-10 21:20+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -103,7 +103,7 @@ msgstr "Sie haben der Kaste geöffnet" #: squirrelbattle/entities/friendly.py:77 msgid "The chest exploded" -msgstr "" +msgstr "Der Kaste explodierte" #: squirrelbattle/entities/friendly.py:78 msgid "It's not really effective" @@ -318,85 +318,93 @@ msgid "Key used to use ladders" msgstr "Leitertaste" #: squirrelbattle/tests/translations_test.py:58 +msgid "Key used to use a bow" +msgstr "Bogentaste" + +#: squirrelbattle/tests/translations_test.py:60 +msgid "Key used to dance" +msgstr "Tanztaste" + +#: squirrelbattle/tests/translations_test.py:62 msgid "Texture pack" msgstr "Textur-Packung" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:63 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:66 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:68 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:69 msgid "merchant" msgstr "Kaufmann" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:70 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:71 msgid "sunflower" msgstr "Sonnenblume" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:72 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:73 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:74 msgid "eagle" msgstr "Adler" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:76 msgid "body snatch potion" msgstr "Leichenfleddererzaubertrank" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:77 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:74 +#: squirrelbattle/tests/translations_test.py:78 msgid "explosion" msgstr "Explosion" -#: squirrelbattle/tests/translations_test.py:75 +#: squirrelbattle/tests/translations_test.py:79 msgid "heart" msgstr "Herz" -#: squirrelbattle/tests/translations_test.py:76 +#: squirrelbattle/tests/translations_test.py:80 msgid "sword" msgstr "schwert" -#: squirrelbattle/tests/translations_test.py:77 +#: squirrelbattle/tests/translations_test.py:81 msgid "helmet" -msgstr "" +msgstr "Helm" -#: squirrelbattle/tests/translations_test.py:78 +#: squirrelbattle/tests/translations_test.py:82 msgid "chestplate" msgstr "Brustpanzer" -#: squirrelbattle/tests/translations_test.py:79 +#: squirrelbattle/tests/translations_test.py:83 msgid "shield" msgstr "Schild" -#: squirrelbattle/tests/translations_test.py:80 +#: squirrelbattle/tests/translations_test.py:84 msgid "ring of critical damage" msgstr "Ring des kritischen Schadens" -#: squirrelbattle/tests/translations_test.py:82 +#: squirrelbattle/tests/translations_test.py:86 msgid "ring of more experience" msgstr "Ring der mehr Erfahrung" -#: squirrelbattle/tests/translations_test.py:84 +#: squirrelbattle/tests/translations_test.py:88 msgid "monocle" msgstr "Monokel" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 7428cd8..e77f059 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -1,12 +1,12 @@ -msgid "ring_of_critical_damage" -msgstr "ring_of_critical_damage" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} recibe {amount} daño." msgid "ring_of_more_experience" msgstr "ring_of_more_experience" -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} recibe {amount} daño." +msgid "ring_of_critical_damage" +msgstr "ring_of_critical_damage" # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao @@ -18,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 21:14+0100\n" +"POT-Creation-Date: 2021-01-10 21:20+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -314,85 +314,93 @@ msgid "Key used to use ladders" msgstr "Tecla para el uso de las escaleras" #: squirrelbattle/tests/translations_test.py:58 +msgid "Key used to use a bow" +msgstr "Tecla para usar un arco" + +#: squirrelbattle/tests/translations_test.py:60 +msgid "Key used to dance" +msgstr "Tecla para bailar" + +#: squirrelbattle/tests/translations_test.py:62 msgid "Texture pack" msgstr "Paquete de texturas" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:63 msgid "Language" msgstr "Languaje" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:66 msgid "player" msgstr "jugador" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:68 msgid "hedgehog" msgstr "erizo" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:69 msgid "merchant" msgstr "comerciante" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:70 msgid "rabbit" msgstr "conejo" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:71 msgid "sunflower" msgstr "girasol" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:72 msgid "teddy bear" msgstr "osito de peluche" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:73 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:74 msgid "eagle" msgstr "águila" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:76 msgid "body snatch potion" msgstr "poción de intercambio" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:77 msgid "bomb" msgstr "bomba" -#: squirrelbattle/tests/translations_test.py:74 +#: squirrelbattle/tests/translations_test.py:78 msgid "explosion" msgstr "explosión" -#: squirrelbattle/tests/translations_test.py:75 +#: squirrelbattle/tests/translations_test.py:79 msgid "heart" msgstr "corazón" -#: squirrelbattle/tests/translations_test.py:76 +#: squirrelbattle/tests/translations_test.py:80 msgid "sword" msgstr "espada" -#: squirrelbattle/tests/translations_test.py:77 +#: squirrelbattle/tests/translations_test.py:81 msgid "helmet" msgstr "casco" -#: squirrelbattle/tests/translations_test.py:78 +#: squirrelbattle/tests/translations_test.py:82 msgid "chestplate" msgstr "pechera" -#: squirrelbattle/tests/translations_test.py:79 +#: squirrelbattle/tests/translations_test.py:83 msgid "shield" msgstr "escudo" -#: squirrelbattle/tests/translations_test.py:80 +#: squirrelbattle/tests/translations_test.py:84 msgid "ring of critical damage" msgstr "anillo de daño crítico" -#: squirrelbattle/tests/translations_test.py:82 +#: squirrelbattle/tests/translations_test.py:86 msgid "ring of more experience" msgstr "anillo de más experiencia" -#: squirrelbattle/tests/translations_test.py:84 +#: squirrelbattle/tests/translations_test.py:88 msgid "monocle" msgstr "monóculo" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index bdd5132..91c1731 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 21:14+0100\n" +"POT-Creation-Date: 2021-01-10 21:20+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -127,7 +127,7 @@ msgid "" "The ennemies have -{max(1, self.held_by.intelligence // 2)}strength for 3 " "turns" msgstr "" -"TLes ennemis ont -{max(1, self.held_by.intelligence // 2)} de force pour 3 " +"Les ennemis ont -{max(1, self.held_by.intelligence // 2)} de force pour 3 " "tours" #: squirrelbattle/entities/items.py:552 @@ -309,85 +309,93 @@ msgid "Key used to use ladders" msgstr "Touche pour utiliser les échelles" #: squirrelbattle/tests/translations_test.py:58 +msgid "Key used to use a bow" +msgstr "Touche pour utiliser un arc" + +#: squirrelbattle/tests/translations_test.py:60 +msgid "Key used to dance" +msgstr "Touche pour danser" + +#: squirrelbattle/tests/translations_test.py:62 msgid "Texture pack" msgstr "Pack de textures" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:63 msgid "Language" msgstr "Langue" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:66 msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:68 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:69 msgid "merchant" msgstr "marchand" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:70 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:71 msgid "sunflower" msgstr "tournesol" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:72 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:73 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:74 msgid "eagle" msgstr "pygargue" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:76 msgid "body snatch potion" msgstr "potion d'arrachage de corps" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:77 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:74 +#: squirrelbattle/tests/translations_test.py:78 msgid "explosion" msgstr "explosion" -#: squirrelbattle/tests/translations_test.py:75 +#: squirrelbattle/tests/translations_test.py:79 msgid "heart" msgstr "cœur" -#: squirrelbattle/tests/translations_test.py:76 +#: squirrelbattle/tests/translations_test.py:80 msgid "sword" msgstr "épée" -#: squirrelbattle/tests/translations_test.py:77 +#: squirrelbattle/tests/translations_test.py:81 msgid "helmet" msgstr "casque" -#: squirrelbattle/tests/translations_test.py:78 +#: squirrelbattle/tests/translations_test.py:82 msgid "chestplate" msgstr "plastron" -#: squirrelbattle/tests/translations_test.py:79 +#: squirrelbattle/tests/translations_test.py:83 msgid "shield" msgstr "bouclier" -#: squirrelbattle/tests/translations_test.py:80 +#: squirrelbattle/tests/translations_test.py:84 msgid "ring of critical damage" msgstr "anneau de coup critique" -#: squirrelbattle/tests/translations_test.py:82 +#: squirrelbattle/tests/translations_test.py:86 msgid "ring of more experience" msgstr "anneau de plus d'expérience" -#: squirrelbattle/tests/translations_test.py:84 +#: squirrelbattle/tests/translations_test.py:88 msgid "monocle" msgstr "monocle" diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index bdf0238..c0ad3c0 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -55,6 +55,10 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("Key used to wait"), "Touche pour attendre") self.assertEqual(_("Key used to use ladders"), "Touche pour utiliser les échelles") + self.assertEqual(_("Key used to use a bow"), + "Touche pour utiliser un arc") + self.assertEqual(_("Key used to dance"), + "Touche pour danser") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") -- 2.39.5 From 26e66a5796c66a4b61de62330b1d04a9ec28fc6d Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Sun, 10 Jan 2021 21:30:18 +0100 Subject: [PATCH 087/122] Implement method add_loops along with tests --- squirrelbattle/mapgeneration/broguelike.py | 109 ++++++++++++++++----- squirrelbattle/tests/mapgeneration_test.py | 22 +++++ 2 files changed, 108 insertions(+), 23 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index e3f86a7..9d5bfaa 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -6,7 +6,6 @@ from typing import List, Tuple from ..interfaces import Map, Tile - DEFAULT_PARAMS = { "width": 120, "height": 35, @@ -21,8 +20,28 @@ DEFAULT_PARAMS = { "max_h_corr": 12, "large_circular_room": .10, "circular_holes": .5, + "loop_tries" : 40, + "loop_max" : 5, + "loop_threshold" : 15, } +def dist(level, y1, x1, y2, x2): + copy = [[t for t in l] for l in level] + dist = -1 + queue, next_queue = [[y1, x1]], [0] + while next_queue: + next_queue = [] + dist += 1 + while queue: + y, x = queue.pop() + copy[y][x] = Tile.EMPTY + if y == y2 and x == x2: + return dist + for y, x in Map.neighbourhood(copy, y, x): + if copy[y][x].can_walk(): + next_queue.append([y, x]) + queue = next_queue + return -1 class Generator: def __init__(self, params: dict = None): @@ -66,6 +85,39 @@ class Generator: if room[ry][rx] == Tile.FLOOR: level[y - door_y + ry][x - door_x + rx] = Tile.FLOOR + @staticmethod + def add_loop(level: List[List[Tile]], y: int, x: int) -> None: + h, w = len(level), len(level[0]) + if level[y][x] != Tile.EMPTY: + return False + # loop over both axis + for dx, dy in [[0, 1], [1, 0]]: + # then we find two floor tiles, exiting if we ever move oob + y1, x1, y2, x2 = y, x, y, x + while x1 >= 0 and y1 >= 0 and level[y1][x1] == Tile.EMPTY: + y1, x1 = y1 - dy, x1 - dx + while x2 < w and y2 < h and level[y2][x2] == Tile.EMPTY: + y2, x2 = y2 + dy, x2 + dx + if not(0 <= x1 <= x2 < w and 0 <= y1 <= y2 < h): + continue + + # if adding the path would make the two tiles significantly closer + # and its sides don't touch already placed terrain, build it + def verify_sides(): + for Dx, Dy in [[dy, dx], [-dy, -dx]]: + for i in range(1, y2-y1+x2-x1): + if not(0<= y1+Dy+i*dy < h and 0 <= x1+Dx+i*dx < w) or \ + level[y1+Dy+i*dy][x1+Dx+i*dx].can_walk(): + return False + return True + if dist(level, y1, x1, y2, x2) < 20 and verify_sides(): + y, x = y1+dy, x1+dx + while level[y][x] == Tile.EMPTY: + level[y][x] = Tile.FLOOR + y, x = y+dy, x+dx + return True + return False + @staticmethod def place_walls(level: List[List[Tile]]) -> None: h, w = len(level), len(level[0]) @@ -87,9 +139,29 @@ class Generator: return h_sup, w_sup, h_off, w_off return 0, 0, 0, 0 - def attach_door(self, room: List[List[Tile]], h_sup: int, w_sup: int, - h_off: int, w_off: int) \ - -> Tuple[int, int, int, int]: + @staticmethod + def build_door(room, y, x, dy, dx, length): + rh, rw = len(room), len(room[0]) + # verify we are pointing away from a floor tile + if not(0 <= y - dy < rh and 0 <= x - dx < rw) \ + or room[y - dy][x - dx] != Tile.FLOOR: + return False + # verify there's no other floor tile around us + for ny, nx in [[y + dy, x + dx], [y - dx, x - dy], + [y + dx, x + dy]]: + if 0 <= ny < rh and 0 <= nx < rw \ + and room[ny][nx] != Tile.EMPTY: + return False + for i in range(length): + if room[y + i * dy][x + i * dx] != Tile.EMPTY: + return False + for i in range(length): + room[y + i * dy][x + i * dx] = Tile.FLOOR + return True + + @staticmethod + def attach_door(room: List[List[Tile]], h_sup: int, w_sup: int, + h_off: int, w_off: int) -> Tuple[int, int, int, int]: length = h_sup + w_sup dy, dx = 0, 0 if length > 0: @@ -108,25 +180,10 @@ class Generator: shuffle(yxs) for pos in yxs: y, x = pos // rw, pos % rw - if room[y][x] == Tile.EMPTY: - # verify we are pointing away from a floor tile - if not(0 <= y - dy < rh and 0 <= x - dx < rw) \ - or room[y - dy][x - dx] != Tile.FLOOR: - continue - # verify there's no other floor tile around us - for ny, nx in [[y + dy, x + dx], [y - dx, x - dy], - [y + dx, x + dy]]: - if 0 <= ny < rh and 0 <= nx < rw \ - and room[ny][nx] != Tile.EMPTY: - break - else: - for i in range(length): - if room[y + i * dy][x + i * dx] != Tile.EMPTY: - break - else: - for i in range(length): - room[y + i * dy][x + i * dx] = Tile.FLOOR - break + if room[y][x] == Tile.EMPTY and \ + Generator.build_door(room, y, x, dy, dx, length): + break + return y + length * dy, x + length * dx, dy, dx def create_circular_room(self) -> Tuple[List[List[Tile]], int, int, @@ -204,6 +261,12 @@ class Generator: # post-processing self.place_walls(level) + tries, loops = 0, 0 + while tries < self.params["loop_tries"] and \ + loops < self.params["loop_max"]: + tries += 1 + y, x = randint(0, height-1), randint(0, width-1) + loops += self.add_loop(level, y, x) # place an exit ladder y, x = randint(0, height - 1), randint(0, width - 1) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index fc1de8d..b58e10c 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -7,11 +7,19 @@ from typing import List from squirrelbattle.interfaces import Map, Tile from squirrelbattle.mapgeneration import broguelike +from squirrelbattle.display.texturepack import TexturePack class TestBroguelike(unittest.TestCase): def setUp(self) -> None: self.generator = broguelike.Generator() + self.stom = lambda x : Map.load_from_string("0 0\n" + x) + self.mtos = lambda x : x.draw_string(TexturePack.ASCII_PACK) + + def test_dist(self): + m = self.stom(".. ..\n ... ") + distance = broguelike.dist(m.tiles, 0, 0, 0, 4) + self.assertEqual(distance, 6) def is_connex(self, grid: List[List[Tile]]) -> bool: h, w = len(grid), len(grid[0]) @@ -29,3 +37,17 @@ class TestBroguelike(unittest.TestCase): def test_connexity(self) -> None: m = self.generator.run() self.assertTrue(self.is_connex(m.tiles)) + + def test_doors(self) -> None: + # corridors shouldn't loop back into the room + pass + + def test_loops(self) -> None: + m = self.stom(3*".. ..\n") + self.generator.add_loop(m.tiles, 1, 3) + s = self.mtos(m) + self.assertEqual(s, ".. ..\n.......\n.. ..") + self.assertFalse(self.generator.add_loop(m.tiles, 0, 0)) + m = self.stom("...\n. .\n...") + self.assertFalse(self.generator.add_loop(m.tiles, 1, 1)) + -- 2.39.5 From 96bbc5088f62ee2c9113fb8463d0d82bc3c7edca Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Sun, 10 Jan 2021 21:32:58 +0100 Subject: [PATCH 088/122] Add a test case for non connex maps in distance computation --- squirrelbattle/tests/mapgeneration_test.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index b58e10c..07ed05d 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -20,6 +20,9 @@ class TestBroguelike(unittest.TestCase): m = self.stom(".. ..\n ... ") distance = broguelike.dist(m.tiles, 0, 0, 0, 4) self.assertEqual(distance, 6) + m = self.stom(". .") + distance = broguelike.dist(m.tiles, 0, 0, 0, 2) + self.assertEqual(distance, -1) def is_connex(self, grid: List[List[Tile]]) -> bool: h, w = len(grid), len(grid[0]) @@ -38,10 +41,6 @@ class TestBroguelike(unittest.TestCase): m = self.generator.run() self.assertTrue(self.is_connex(m.tiles)) - def test_doors(self) -> None: - # corridors shouldn't loop back into the room - pass - def test_loops(self) -> None: m = self.stom(3*".. ..\n") self.generator.add_loop(m.tiles, 1, 3) -- 2.39.5 From 6cf0590586e034a864c146a9503c72b248135f62 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 21:38:21 +0100 Subject: [PATCH 089/122] even more translations. --- squirrelbattle/entities/items.py | 4 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 44 ++++++++++++++---- .../locale/es/LC_MESSAGES/squirrelbattle.po | 46 +++++++++++++++---- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 34 ++++++++++++-- squirrelbattle/tests/translations_test.py | 5 ++ 5 files changed, 111 insertions(+), 22 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index ac5f74d..f4587f3 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -597,7 +597,7 @@ class Bow(LongRangeWeapon): @property def string(self) -> str: - return " is shot by an arrow." + return _(" is shot by an arrow.") class FireBallStaff(LongRangeWeapon): @@ -619,7 +619,7 @@ class FireBallStaff(LongRangeWeapon): @property def string(self) -> str: - return " is shot by a fire ball." + return _(" is shot by a fire ball.") def throw(self, direction: int) -> Any: """ diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 3d9595a..f4e3cf8 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,12 +1,12 @@ -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." +msgid "ring_of_critical_damage" +msgstr "" msgid "ring_of_more_experience" msgstr "" -msgid "ring_of_critical_damage" -msgstr "" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao @@ -18,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 21:20+0100\n" +"POT-Creation-Date: 2021-01-10 21:30+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -142,6 +142,14 @@ msgstr "" msgid "{name}" msgstr "{name}" +#: squirrelbattle/entities/items.py:600 +msgid " is shot by an arrow." +msgstr " wird von einem Pfeil erschossen." + +#: squirrelbattle/entities/items.py:622 +msgid " is shot by a fire ball." +msgstr " wird von eine Feuerball erschossen." + #: squirrelbattle/entities/player.py:83 msgid "It worked! Nearby ennemies will be confused for 3 turns." msgstr "" @@ -398,13 +406,33 @@ msgid "shield" msgstr "Schild" #: squirrelbattle/tests/translations_test.py:84 +msgid "ruler" +msgstr "Lineal" + +#: squirrelbattle/tests/translations_test.py:85 +msgid "scroll of damage" +msgstr "Schriftrolle des Schadens" + +#: squirrelbattle/tests/translations_test.py:86 +msgid "scroll of weakness" +msgstr "Schriftrolle der Schwäche" + +#: squirrelbattle/tests/translations_test.py:87 +msgid "bow" +msgstr "Bogen" + +#: squirrelbattle/tests/translations_test.py:88 +msgid "fire ball staff" +msgstr "Feuerball Stab" + +#: squirrelbattle/tests/translations_test.py:89 msgid "ring of critical damage" msgstr "Ring des kritischen Schadens" -#: squirrelbattle/tests/translations_test.py:86 +#: squirrelbattle/tests/translations_test.py:91 msgid "ring of more experience" msgstr "Ring der mehr Erfahrung" -#: squirrelbattle/tests/translations_test.py:88 +#: squirrelbattle/tests/translations_test.py:93 msgid "monocle" msgstr "Monokel" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index e77f059..2d8ee64 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -1,12 +1,12 @@ -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} recibe {amount} daño." +msgid "ring_of_critical_damage" +msgstr "ring_of_critical_damage" msgid "ring_of_more_experience" msgstr "ring_of_more_experience" -msgid "ring_of_critical_damage" -msgstr "ring_of_critical_damage" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} recibe {amount} daño." # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao @@ -18,7 +18,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 21:20+0100\n" +"POT-Creation-Date: 2021-01-10 21:30+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -69,7 +69,7 @@ msgstr "Usa {key} y luego muévete para hablar con la entidad" #: squirrelbattle/display/menudisplay.py:149 #: squirrelbattle/display/menudisplay.py:304 msgid "Credits" -msgstr "" +msgstr "Creditos" #: squirrelbattle/display/menudisplay.py:173 msgid "INVENTORY" @@ -140,6 +140,14 @@ msgstr "" msgid "{name}" msgstr "{name}" +#: squirrelbattle/entities/items.py:600 +msgid " is shot by an arrow." +msgstr " es disparado por una flecha." + +#: squirrelbattle/entities/items.py:622 +msgid " is shot by a fire ball." +msgstr " es disparado por una bola de fuego." + #: squirrelbattle/entities/player.py:83 msgid "It worked! Nearby ennemies will be confused for 3 turns." msgstr "¡Funcionó! Los enemigos cercanos se confundirán durante 3 turnos." @@ -394,13 +402,33 @@ msgid "shield" msgstr "escudo" #: squirrelbattle/tests/translations_test.py:84 +msgid "ruler" +msgstr "Regla" + +#: squirrelbattle/tests/translations_test.py:85 +msgid "scroll of damage" +msgstr "rollo de daño" + +#: squirrelbattle/tests/translations_test.py:86 +msgid "scroll of weakness" +msgstr "rollo de debilidad" + +#: squirrelbattle/tests/translations_test.py:87 +msgid "bow" +msgstr "arco" + +#: squirrelbattle/tests/translations_test.py:88 +msgid "fire ball staff" +msgstr "bastón de bola de fuego" + +#: squirrelbattle/tests/translations_test.py:89 msgid "ring of critical damage" msgstr "anillo de daño crítico" -#: squirrelbattle/tests/translations_test.py:86 +#: squirrelbattle/tests/translations_test.py:91 msgid "ring of more experience" msgstr "anillo de más experiencia" -#: squirrelbattle/tests/translations_test.py:88 +#: squirrelbattle/tests/translations_test.py:93 msgid "monocle" msgstr "monóculo" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 91c1731..93b524d 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-10 21:20+0100\n" +"POT-Creation-Date: 2021-01-10 21:30+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -135,6 +135,14 @@ msgstr "" msgid "{name}" msgstr "{name}" +#: squirrelbattle/entities/items.py:600 +msgid " is shot by an arrow." +msgstr " est frappé par une flèche." + +#: squirrelbattle/entities/items.py:622 +msgid " is shot by a fire ball." +msgstr " est frappé par une boule de feu." + #: squirrelbattle/entities/player.py:83 msgid "It worked! Nearby ennemies will be confused for 3 turns." msgstr "Ça a marché ! Les ennemis proches seront confus pendant 3 tours" @@ -389,13 +397,33 @@ msgid "shield" msgstr "bouclier" #: squirrelbattle/tests/translations_test.py:84 +msgid "ruler" +msgstr "règle" + +#: squirrelbattle/tests/translations_test.py:85 +msgid "scroll of damage" +msgstr "parchemin de dégâts" + +#: squirrelbattle/tests/translations_test.py:86 +msgid "scroll of weakness" +msgstr "parchemin de faiblesse" + +#: squirrelbattle/tests/translations_test.py:87 +msgid "bow" +msgstr "arc" + +#: squirrelbattle/tests/translations_test.py:88 +msgid "fire ball staff" +msgstr "baton de boule de feu" + +#: squirrelbattle/tests/translations_test.py:89 msgid "ring of critical damage" msgstr "anneau de coup critique" -#: squirrelbattle/tests/translations_test.py:86 +#: squirrelbattle/tests/translations_test.py:91 msgid "ring of more experience" msgstr "anneau de plus d'expérience" -#: squirrelbattle/tests/translations_test.py:88 +#: squirrelbattle/tests/translations_test.py:93 msgid "monocle" msgstr "monocle" diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index c0ad3c0..2db869a 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -81,6 +81,11 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("helmet"), "casque") self.assertEqual(_("chestplate"), "plastron") self.assertEqual(_("shield"), "bouclier") + self.assertEqual(_("ruler"), "règle") + self.assertEqual(_("scroll of damage"), "parchemin de dégâts") + self.assertEqual(_("scroll of weakness"), "parchemin de faiblesse") + self.assertEqual(_("bow"), "arc") + self.assertEqual(_("fire ball staff"), "baton de boule de feu") self.assertEqual(_("ring of critical damage"), "anneau de coup critique") self.assertEqual(_("ring of more experience"), -- 2.39.5 From 18ace5144c298add03dffb49b82d3a187566876d Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 21:41:45 +0100 Subject: [PATCH 090/122] Repaired a bug : a variable was not declared before it was used in interfaces.py take_damage --- squirrelbattle/interfaces.py | 1 + 1 file changed, 1 insertion(+) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index c71d7c8..ea59a04 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -774,6 +774,7 @@ class FightingEntity(Entity): The entity takes damage from the attacker based on their respective stats. """ + damage = 0 if amount != 0 : damage = max(1, amount - self.constitution) self.health -= damage -- 2.39.5 From 236481ae1c10de731a1876466568532eba9bfd1c Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 21:44:45 +0100 Subject: [PATCH 091/122] linting --- squirrelbattle/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index ea59a04..306f857 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -775,7 +775,7 @@ class FightingEntity(Entity): based on their respective stats. """ damage = 0 - if amount != 0 : + if amount != 0: damage = max(1, amount - self.constitution) self.health -= damage if self.health <= 0: -- 2.39.5 From e639ad62553a37f4b93f90bf5afa37f44057340b Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Sun, 10 Jan 2021 21:48:12 +0100 Subject: [PATCH 092/122] Getting to full cover, and minor fix to bug that allowed corridors to create loops in a room, resulting in implacability --- squirrelbattle/mapgeneration/broguelike.py | 2 +- squirrelbattle/tests/mapgeneration_test.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 9d5bfaa..d5be37f 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -152,7 +152,7 @@ class Generator: if 0 <= ny < rh and 0 <= nx < rw \ and room[ny][nx] != Tile.EMPTY: return False - for i in range(length): + for i in range(length+1): if room[y + i * dy][x + i * dx] != Tile.EMPTY: return False for i in range(length): diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index 07ed05d..5ee4eb3 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -37,6 +37,10 @@ class TestBroguelike(unittest.TestCase): queue += Map.neighbourhood(grid, y, x) return not any([t.can_walk() for row in grid for t in row]) + def test_build_doors(self): + m = self.stom(". .\n. .\n. .\n") + self.assertFalse(self.generator.build_door(m.tiles, 1, 1, 0, 1, 2)) + def test_connexity(self) -> None: m = self.generator.run() self.assertTrue(self.is_connex(m.tiles)) -- 2.39.5 From d3607248c0020f1978c5733b2b725b4212780581 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 21:48:46 +0100 Subject: [PATCH 093/122] Remove unused translations --- squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po | 10 ---------- squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index f4e3cf8..74eb1a9 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,13 +1,3 @@ -msgid "ring_of_critical_damage" -msgstr "" - -msgid "ring_of_more_experience" -msgstr "" - -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." - # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao # This file is distributed under the same license as the squirrelbattle package. diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 2d8ee64..909ff48 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -1,13 +1,3 @@ -msgid "ring_of_critical_damage" -msgstr "ring_of_critical_damage" - -msgid "ring_of_more_experience" -msgstr "ring_of_more_experience" - -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} recibe {amount} daño." - # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao # This file is distributed under the same license as the squirrelbattle package. -- 2.39.5 From 12e19759aa808892236f3509e9b8df7783b98d91 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Sun, 10 Jan 2021 21:49:39 +0100 Subject: [PATCH 094/122] Implement populate method, so map generation also handles entity spawn --- squirrelbattle/mapgeneration/broguelike.py | 50 +++++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index d5be37f..7fe9f88 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -1,10 +1,10 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -from random import random, randint, shuffle +from random import random, randint, shuffle, choice, choices from typing import List, Tuple -from ..interfaces import Map, Tile +from ..interfaces import Map, Tile, Entity DEFAULT_PARAMS = { "width": 120, @@ -23,6 +23,7 @@ DEFAULT_PARAMS = { "loop_tries" : 40, "loop_max" : 5, "loop_threshold" : 15, + "spawn_per_region" : [1, 2], } def dist(level, y1, x1, y2, x2): @@ -46,6 +47,8 @@ def dist(level, y1, x1, y2, x2): class Generator: def __init__(self, params: dict = None): self.params = params or DEFAULT_PARAMS + self.spawn_areas = [] + self.queued_area = None @staticmethod def room_fits(level: List[List[Tile]], y: int, x: int, @@ -186,8 +189,8 @@ class Generator: return y + length * dy, x + length * dx, dy, dx - def create_circular_room(self) -> Tuple[List[List[Tile]], int, int, - int, int]: + def create_circular_room(self, spawnable: bool = True) \ + -> Tuple[List[List[Tile]], int, int, int, int]: if random() < self.params["large_circular_room"]: r = randint(5, 10) else: @@ -214,21 +217,49 @@ class Generator: else: room[-1].append(Tile.EMPTY) + if spawnable: + self.register_spawn_area(room) door_y, door_x, dy, dx = self.attach_door(room, h_sup, w_sup, h_off, w_off) return room, door_y, door_x, dy, dx - def create_random_room(self) -> Tuple[List[list], int, int, int, int]: + def create_random_room(self, spawnable: bool = True) \ + -> Tuple[List[list], int, int, int, int]: return self.create_circular_room() + def register_spawn_area(self, area:List[List[Tile]]): + spawn_positions = [] + for y, line in enumerate(area): + for x, tile in enumerate(line): + if tile == Tile.FLOOR: + spawn_positions.append([y, x]) + self.queued_area = spawn_positions + + def update_spawnable(self, y, x): + if self.queued_area != None: + translated_area = [[y+ry, x+rx] for ry, rx in self.queued_area] + self.spawn_areas.append(translated_area) + self.queued_area = None + + def populate(self, rv): + min_c, max_c = self.params["spawn_per_region"] + for region in self.spawn_areas: + entity_count = randint(min_c, max_c) + for _dummy in range(entity_count): + entity = choices(Entity.get_all_entity_classes(), + weights=Entity.get_weights(), k=1)[0]() + y, x = choice(region) + entity.move(y, x) + rv.add_entity(entity) + def run(self) -> Map: height, width = self.params["height"], self.params["width"] level = [width * [Tile.EMPTY] for _ignored in range(height)] # the starting room must have no corridor mem, self.params["corridor_chance"] = self.params["corridor_chance"], 0 - starting_room, _, _, _, _ = self.create_random_room() + starting_room, _, _, _, _ = self.create_random_room(spawnable = False) dim_v, dim_h = len(starting_room), len(starting_room[0]) pos_y, pos_x = randint(0, height - dim_v - 1),\ randint(0, width - dim_h - 1) @@ -254,6 +285,7 @@ class Generator: for pos in positions: y, x = pos // width, pos % width if self.room_fits(level, y, x, room, door_y, door_x, dy, dx): + self.update_spawnable(y - door_y, x - door_x) self.place_room(level, y, x, room, door_y, door_x) rooms_built += 1 break @@ -276,4 +308,8 @@ class Generator: y, x = randint(0, height - 1), randint(0, width - 1) level[y][x] = Tile.LADDER - return Map(width, height, level, sy, sx) + # spawn entities + rv = Map(width, height, level, sy, sx) + self.populate(rv) + + return rv -- 2.39.5 From 01cdea6edcbf6e48a2ef61a1d1523dd999bea5b2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 21:57:51 +0100 Subject: [PATCH 095/122] Don't spawn random entities on each level anymore --- squirrelbattle/entities/player.py | 3 --- squirrelbattle/game.py | 1 - squirrelbattle/interfaces.py | 16 ---------------- 3 files changed, 20 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 615dfd5..81a0c55 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -71,9 +71,6 @@ class Player(InventoryHolder, FightingEntity): self.max_xp = self.level * 10 self.health = self.maxhealth self.strength = self.strength + 1 - # TODO Remove it, that's only fun - self.map.spawn_random_entities(randint(3 * self.level, - 10 * self.level)) def add_xp(self, xp: int) -> None: """ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index fb10404..87317e0 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -61,7 +61,6 @@ class Game: self.player = Player() self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) - self.map.spawn_random_entities(randint(3, 10)) self.inventory_menu.update_player(self.player) @property diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index e9b2407..6e3065c 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -180,22 +180,6 @@ class Map: return "\n".join("".join(tile.char(pack) for tile in line) for line in self.tiles) - def spawn_random_entities(self, count: int) -> None: - """ - Puts randomly {count} entities on the map, only on empty ground tiles. - """ - for _ignored in range(count): - y, x = 0, 0 - while True: - y, x = randint(0, self.height - 1), randint(0, self.width - 1) - tile = self.tiles[y][x] - if tile.can_walk(): - break - entity = choices(Entity.get_all_entity_classes(), - weights=Entity.get_weights(), k=1)[0]() - entity.move(y, x) - self.add_entity(entity) - def compute_visibility(self, y: int, x: int, max_range: int) -> None: """ Sets the visible tiles to be the ones visible by an entity at point -- 2.39.5 From 9df1ac78832498838617b9b440ab738a1e044236 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 22:08:42 +0100 Subject: [PATCH 096/122] Linting --- squirrelbattle/game.py | 1 - squirrelbattle/interfaces.py | 2 +- squirrelbattle/mapgeneration/broguelike.py | 58 ++++++++++++---------- squirrelbattle/tests/mapgeneration_test.py | 11 ++-- 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 87317e0..89818d0 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later from json import JSONDecodeError -from random import randint from typing import Any, Optional, List import curses import json diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 6e3065c..ba9be08 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,7 @@ from enum import Enum, auto from math import ceil, sqrt from itertools import product -from random import choice, choices, randint +from random import choice, randint from typing import List, Optional, Any, Dict, Tuple from queue import PriorityQueue from functools import reduce diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 7fe9f88..d192d3c 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -20,14 +20,15 @@ DEFAULT_PARAMS = { "max_h_corr": 12, "large_circular_room": .10, "circular_holes": .5, - "loop_tries" : 40, - "loop_max" : 5, - "loop_threshold" : 15, - "spawn_per_region" : [1, 2], + "loop_tries": 40, + "loop_max": 5, + "loop_threshold": 15, + "spawn_per_region": [1, 2], } -def dist(level, y1, x1, y2, x2): - copy = [[t for t in l] for l in level] + +def dist(level: List[List[Tile]], y1: int, x1: int, y2: int, x2: int) -> int: + copy = [[t for t in row] for row in level] dist = -1 queue, next_queue = [[y1, x1]], [0] while next_queue: @@ -36,7 +37,7 @@ def dist(level, y1, x1, y2, x2): while queue: y, x = queue.pop() copy[y][x] = Tile.EMPTY - if y == y2 and x == x2: + if y == y2 and x == x2: return dist for y, x in Map.neighbourhood(copy, y, x): if copy[y][x].can_walk(): @@ -44,6 +45,7 @@ def dist(level, y1, x1, y2, x2): queue = next_queue return -1 + class Generator: def __init__(self, params: dict = None): self.params = params or DEFAULT_PARAMS @@ -89,7 +91,7 @@ class Generator: level[y - door_y + ry][x - door_x + rx] = Tile.FLOOR @staticmethod - def add_loop(level: List[List[Tile]], y: int, x: int) -> None: + def add_loop(level: List[List[Tile]], y: int, x: int) -> bool: h, w = len(level), len(level[0]) if level[y][x] != Tile.EMPTY: return False @@ -106,18 +108,21 @@ class Generator: # if adding the path would make the two tiles significantly closer # and its sides don't touch already placed terrain, build it - def verify_sides(): - for Dx, Dy in [[dy, dx], [-dy, -dx]]: - for i in range(1, y2-y1+x2-x1): - if not(0<= y1+Dy+i*dy < h and 0 <= x1+Dx+i*dx < w) or \ - level[y1+Dy+i*dy][x1+Dx+i*dx].can_walk(): + def verify_sides() -> bool: + for delta_x, delta_y in [[dy, dx], [-dy, -dx]]: + for i in range(1, y2 - y1 + x2 - x1): + if not (0 <= y1 + delta_y + i * dy < h + and 0 <= x1 + delta_x + i * dx < w) or \ + level[y1 + delta_y + i * dy][x1 + delta_x + + i * dx]\ + .can_walk(): return False return True if dist(level, y1, x1, y2, x2) < 20 and verify_sides(): - y, x = y1+dy, x1+dx + y, x = y1 + dy, x1 + dx while level[y][x] == Tile.EMPTY: level[y][x] = Tile.FLOOR - y, x = y+dy, x+dx + y, x = y + dy, x + dx return True return False @@ -143,7 +148,8 @@ class Generator: return 0, 0, 0, 0 @staticmethod - def build_door(room, y, x, dy, dx, length): + def build_door(room: List[List[Tile]], y: int, x: int, + dy: int, dx: int, length: int) -> bool: rh, rw = len(room), len(room[0]) # verify we are pointing away from a floor tile if not(0 <= y - dy < rh and 0 <= x - dx < rw) \ @@ -155,7 +161,7 @@ class Generator: if 0 <= ny < rh and 0 <= nx < rw \ and room[ny][nx] != Tile.EMPTY: return False - for i in range(length+1): + for i in range(length + 1): if room[y + i * dy][x + i * dx] != Tile.EMPTY: return False for i in range(length): @@ -163,8 +169,8 @@ class Generator: return True @staticmethod - def attach_door(room: List[List[Tile]], h_sup: int, w_sup: int, - h_off: int, w_off: int) -> Tuple[int, int, int, int]: + def attach_door(room: List[List[Tile]], h_sup: int, w_sup: int, + h_off: int, w_off: int) -> Tuple[int, int, int, int]: length = h_sup + w_sup dy, dx = 0, 0 if length > 0: @@ -228,7 +234,7 @@ class Generator: -> Tuple[List[list], int, int, int, int]: return self.create_circular_room() - def register_spawn_area(self, area:List[List[Tile]]): + def register_spawn_area(self, area: List[List[Tile]]) -> None: spawn_positions = [] for y, line in enumerate(area): for x, tile in enumerate(line): @@ -236,13 +242,13 @@ class Generator: spawn_positions.append([y, x]) self.queued_area = spawn_positions - def update_spawnable(self, y, x): - if self.queued_area != None: - translated_area = [[y+ry, x+rx] for ry, rx in self.queued_area] + def update_spawnable(self, y: int, x: int) -> None: + if self.queued_area is not None: + translated_area = [[y + ry, x + rx] for ry, rx in self.queued_area] self.spawn_areas.append(translated_area) self.queued_area = None - def populate(self, rv): + def populate(self, rv: Map) -> None: min_c, max_c = self.params["spawn_per_region"] for region in self.spawn_areas: entity_count = randint(min_c, max_c) @@ -259,7 +265,7 @@ class Generator: # the starting room must have no corridor mem, self.params["corridor_chance"] = self.params["corridor_chance"], 0 - starting_room, _, _, _, _ = self.create_random_room(spawnable = False) + starting_room, _, _, _, _ = self.create_random_room(spawnable=False) dim_v, dim_h = len(starting_room), len(starting_room[0]) pos_y, pos_x = randint(0, height - dim_v - 1),\ randint(0, width - dim_h - 1) @@ -297,7 +303,7 @@ class Generator: while tries < self.params["loop_tries"] and \ loops < self.params["loop_max"]: tries += 1 - y, x = randint(0, height-1), randint(0, width-1) + y, x = randint(0, height - 1), randint(0, width - 1) loops += self.add_loop(level, y, x) # place an exit ladder diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index 5ee4eb3..141864d 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -13,10 +13,10 @@ from squirrelbattle.display.texturepack import TexturePack class TestBroguelike(unittest.TestCase): def setUp(self) -> None: self.generator = broguelike.Generator() - self.stom = lambda x : Map.load_from_string("0 0\n" + x) - self.mtos = lambda x : x.draw_string(TexturePack.ASCII_PACK) + self.stom = lambda x: Map.load_from_string("0 0\n" + x) + self.mtos = lambda x: x.draw_string(TexturePack.ASCII_PACK) - def test_dist(self): + def test_dist(self) -> None: m = self.stom(".. ..\n ... ") distance = broguelike.dist(m.tiles, 0, 0, 0, 4) self.assertEqual(distance, 6) @@ -37,7 +37,7 @@ class TestBroguelike(unittest.TestCase): queue += Map.neighbourhood(grid, y, x) return not any([t.can_walk() for row in grid for t in row]) - def test_build_doors(self): + def test_build_doors(self) -> None: m = self.stom(". .\n. .\n. .\n") self.assertFalse(self.generator.build_door(m.tiles, 1, 1, 0, 1, 2)) @@ -46,11 +46,10 @@ class TestBroguelike(unittest.TestCase): self.assertTrue(self.is_connex(m.tiles)) def test_loops(self) -> None: - m = self.stom(3*".. ..\n") + m = self.stom(3 * ".. ..\n") self.generator.add_loop(m.tiles, 1, 3) s = self.mtos(m) self.assertEqual(s, ".. ..\n.......\n.. ..") self.assertFalse(self.generator.add_loop(m.tiles, 0, 0)) m = self.stom("...\n. .\n...") self.assertFalse(self.generator.add_loop(m.tiles, 1, 1)) - -- 2.39.5 From 88471f4361be64d29aac6de979325ef64e2a2abc Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Sun, 10 Jan 2021 22:15:32 +0100 Subject: [PATCH 097/122] Changed equipment behaviour, now equipped items stay in the inventory --- squirrelbattle/entities/items.py | 90 ++++++++++++--------------- squirrelbattle/interfaces.py | 1 + squirrelbattle/tests/entities_test.py | 7 +-- squirrelbattle/tests/game_test.py | 21 ++----- 4 files changed, 51 insertions(+), 68 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index ac5f74d..0f05d39 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -12,16 +12,19 @@ class Item(Entity): """ A class for items. """ - held: bool held_by: Optional[InventoryHolder] price: int - def __init__(self, held: bool = False, + def __init__(self, equipped: bool = False, held_by: Optional[InventoryHolder] = None, + hold_slot: str = "equipped_secondary", price: int = 2, *args, **kwargs): super().__init__(*args, **kwargs) - self.held = held self.held_by = held_by + self.equipped = equipped + self.hold_slot = hold_slot + if equipped: + self.equip() self.price = price @property @@ -35,11 +38,11 @@ class Item(Entity): """ The item is dropped from the inventory onto the floor. """ - if self.held: + if self.held_by is not None: + self.unequip() self.held_by.remove_from_inventory(self) self.held_by.map.add_entity(self) self.move(self.held_by.y, self.held_by.x) - self.held = False self.held_by = None def use(self) -> None: @@ -52,28 +55,41 @@ class Item(Entity): Indicates what should be done when the item is thrown. """ + def on_equip(self) -> None: + """ + Indicates a special behaviour when equipping + """ + + def on_unequip(self) -> None: + """ + Indicates a special behaviour when unequipping + """ + def equip(self) -> None: """ Indicates what should be done when the item is equipped. """ # Other objects are only equipped as secondary. - if self.held_by.equipped_secondary: - self.held_by.equipped_secondary.unequip() - self.held_by.remove_from_inventory(self) - self.held_by.equipped_secondary = self + if not self.equipped: + if getattr(self.held_by, self.hold_slot): + getattr(self.held_by, self.hold_slot).unequip() + self.equipped = True + setattr(self.held_by, self.hold_slot, self) + self.on_equip() def unequip(self) -> None: """ Indicates what should be done when the item is unequipped. """ - self.held_by.remove_from_inventory(self) - self.held_by.add_to_inventory(self) + if self.equipped: + setattr(self.held_by, self.hold_slot, None) + self.equipped = False + self.on_unequip() def hold(self, holder: InventoryHolder) -> None: """ The item is taken from the floor and put into the inventory. """ - self.held = True self.held_by = holder self.held_by.map.remove_entity(self) holder.add_to_inventory(self) @@ -83,7 +99,7 @@ class Item(Entity): Saves the state of the item into a dictionary. """ d = super().save_state() - d["held"] = self.held + d["equipped"] = self.equipped return d @staticmethod @@ -103,10 +119,12 @@ class Item(Entity): inventory. """ if for_free: + self.unequip() if self.equipped else None self.hold(buyer) seller.remove_from_inventory(self) return True elif buyer.hazel >= self.price: + self.unequip() if self.equipped else None self.hold(buyer) seller.remove_from_inventory(self) buyer.change_hazel_balance(-self.price) @@ -169,7 +187,7 @@ class Bomb(Item): """ When the bomb is used, it is thrown and then it explodes. """ - if self.held: + if self.held_by is not None: self.owner = self.held_by super().drop() self.exploding = True @@ -236,7 +254,7 @@ class Weapon(Item): damage: int def __init__(self, damage: int = 3, *args, **kwargs): - super().__init__(*args, **kwargs) + super().__init__(hold_slot="equipped_main", *args, **kwargs) self.damage = damage @property @@ -251,20 +269,17 @@ class Weapon(Item): d["damage"] = self.damage return d - def equip(self) -> None: + def on_equip(self) -> None: """ When a weapon is equipped, the player gains strength. """ - self.held_by.remove_from_inventory(self) - self.held_by.equipped_main = self self.held_by.strength += self.damage - def unequip(self) -> None: + def on_unequip(self) -> None: """ Remove the strength earned by the weapon. :return: """ - super().unequip() self.held_by.strength -= self.damage @@ -301,12 +316,10 @@ class Armor(Item): return f"CON+{self.constitution}" if self.constitution \ else super().description - def equip(self) -> None: - super().equip() + def on_equip(self) -> None: self.held_by.constitution += self.constitution - def unequip(self) -> None: - super().unequip() + def on_unequip(self) -> None: self.held_by.constitution -= self.constitution def save_state(self) -> dict: @@ -332,13 +345,7 @@ class Helmet(Armor): def __init__(self, name: str = "helmet", constitution: int = 2, price: int = 18, *args, **kwargs): super().__init__(name=name, constitution=constitution, price=price, - *args, **kwargs) - - def equip(self) -> None: - if self.held_by.equipped_helmet: - self.held_by.equipped_helmet.unequip() - self.held_by.remove_from_inventory(self) - self.held_by.equipped_helmet = self + hold_slot="equipped_helmet", *args, **kwargs) class Chestplate(Armor): @@ -348,13 +355,7 @@ class Chestplate(Armor): def __init__(self, name: str = "chestplate", constitution: int = 4, price: int = 30, *args, **kwargs): super().__init__(name=name, constitution=constitution, price=price, - *args, **kwargs) - - def equip(self) -> None: - if self.held_by.equipped_armor: - self.held_by.equipped_armor.unequip() - self.held_by.remove_from_inventory(self) - self.held_by.equipped_armor = self + hold_slot="equipped_armor", *args, **kwargs) class BodySnatchPotion(Item): @@ -426,8 +427,7 @@ class Ring(Item): ("CRI", self.critical), ("XP", self.experience)] return ", ".join(f"{key}+{value}" for key, value in fields if value) - def equip(self) -> None: - super().equip() + def on_equip(self) -> None: self.held_by.maxhealth += self.maxhealth self.held_by.strength += self.strength self.held_by.intelligence += self.intelligence @@ -437,8 +437,7 @@ class Ring(Item): self.held_by.critical += self.critical self.held_by.xp_buff += self.experience - def unequip(self) -> None: - super().unequip() + def on_unequip(self) -> None: self.held_by.maxhealth -= self.maxhealth self.held_by.strength -= self.strength self.held_by.intelligence -= self.intelligence @@ -557,13 +556,6 @@ class LongRangeWeapon(Weapon): self.held_by.map.logs.add_message(line) return (to_kill.y, to_kill.x) if to_kill else None - def equip(self) -> None: - """ - Equip the weapon. - """ - self.held_by.remove_from_inventory(self) - self.held_by.equipped_main = self - @property def stat(self) -> str: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 330301c..997cd1d 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -837,6 +837,7 @@ class InventoryHolder(Entity): for i in range(len(inventory)): if isinstance(inventory[i], dict): inventory[i] = self.dict_to_item(inventory[i]) + inventory[i].held_by = self return inventory def dict_to_item(self, item_dict: dict) -> Entity: diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index a0e2548..83a74dc 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -155,9 +155,9 @@ class TestEntities(unittest.TestCase): """ item = Item() self.map.add_entity(item) - self.assertFalse(item.held) + self.assertIsNone(item.held_by) item.hold(self.player) - self.assertTrue(item.held) + self.assertEqual(item.held_by, self.player) item.drop() self.assertEqual(item.y, 1) self.assertEqual(item.x, 6) @@ -165,7 +165,6 @@ class TestEntities(unittest.TestCase): # Pick up item self.player.move_left() self.player.move_right() - self.assertTrue(item.held) self.assertEqual(item.held_by, self.player) self.assertIn(item, self.player.inventory) self.assertNotIn(item, self.map.entities) @@ -208,7 +207,7 @@ class TestEntities(unittest.TestCase): # The player can't hold the explosion explosion.hold(self.player) self.assertNotIn(explosion, self.player.inventory) - self.assertFalse(explosion.held) + self.assertIsNone(explosion.held_by) # The explosion disappears after one tick explosion.act(self.map) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 895af12..b0d8946 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -491,10 +491,8 @@ class TestGame(unittest.TestCase): # Drop an item bomb = self.game.player.inventory[-1] self.assertEqual(self.game.inventory_menu.validate(), bomb) - self.assertTrue(bomb.held) self.assertEqual(bomb.held_by, self.game.player) self.game.handle_key_pressed(KeyValues.DROP) - self.assertFalse(bomb.held) self.assertIsNone(bomb.held_by) self.assertIsNone(bomb.owner) self.assertFalse(bomb.exploding) @@ -504,10 +502,8 @@ class TestGame(unittest.TestCase): # Use the bomb bomb = self.game.player.inventory[-1] self.assertEqual(self.game.inventory_menu.validate(), bomb) - self.assertTrue(bomb.held) self.assertEqual(bomb.held_by, self.game.player) self.game.handle_key_pressed(KeyValues.USE) - self.assertFalse(bomb.held) self.assertIsNone(bomb.held_by) self.assertEqual(bomb.owner, self.game.player) self.assertTrue(bomb.exploding) @@ -660,42 +656,37 @@ class TestGame(unittest.TestCase): sword.hold(self.game.player) self.game.handle_key_pressed(KeyValues.EQUIP) self.assertEqual(self.game.player.equipped_main, sword) - self.assertFalse(self.game.player.inventory) # shield goes into the secondary equipment slot shield = Shield() shield.hold(self.game.player) - self.game.handle_key_pressed(KeyValues.EQUIP) + shield.equip() self.assertEqual(self.game.player.equipped_secondary, shield) - self.assertFalse(self.game.player.inventory) # helmet goes into the helmet slot helmet = Helmet() helmet.hold(self.game.player) - self.game.handle_key_pressed(KeyValues.EQUIP) + helmet.equip() self.assertEqual(self.game.player.equipped_helmet, helmet) - self.assertFalse(self.game.player.inventory) # helmet goes into the armor slot chestplate = Chestplate() chestplate.hold(self.game.player) - self.game.handle_key_pressed(KeyValues.EQUIP) + chestplate.equip() self.assertEqual(self.game.player.equipped_armor, chestplate) - self.assertFalse(self.game.player.inventory) # Use bomb bomb = Bomb() bomb.hold(self.game.player) - self.game.handle_key_pressed(KeyValues.EQUIP) + bomb.equip() self.assertEqual(self.game.player.equipped_secondary, bomb) - self.assertIn(shield, self.game.player.inventory) + self.assertFalse(shield.equipped) self.game.state = GameMode.PLAY self.game.handle_key_pressed(KeyValues.USE) self.assertIsNone(self.game.player.equipped_secondary) self.game.state = GameMode.INVENTORY - self.game.handle_key_pressed(KeyValues.EQUIP) + shield.equip() self.assertEqual(self.game.player.equipped_secondary, shield) - self.assertFalse(self.game.player.inventory) # Reequip, which is useless but covers code sword.equip() -- 2.39.5 From 0ea10546ace7b34f60e90086ae9100dafd4a00c3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 22:19:15 +0100 Subject: [PATCH 098/122] Fix merge issues --- squirrelbattle/game.py | 2 +- squirrelbattle/interfaces.py | 2 +- squirrelbattle/mapgeneration/broguelike.py | 4 ++-- squirrelbattle/tests/mapgeneration_test.py | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ce5ce53..6b8bc0f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -420,7 +420,7 @@ class Game: except KeyError as error: self.message = _("Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted.")\ - + f"\n{error}" + + f"\n{error}" os.unlink(ResourceManager.get_config_path("save.json")) self.display_actions(DisplayActions.UPDATE) return diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index b6a472c..a17ab45 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -7,7 +7,7 @@ from functools import reduce from itertools import product from math import ceil, sqrt from queue import PriorityQueue -from random import choice, choices, randint +from random import choice, randint from typing import Any, Dict, List, Optional, Tuple from .display.texturepack import TexturePack diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index d192d3c..8ae6582 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -1,10 +1,10 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -from random import random, randint, shuffle, choice, choices +from random import choice, choices, randint, random, shuffle from typing import List, Tuple -from ..interfaces import Map, Tile, Entity +from ..interfaces import Entity, Map, Tile DEFAULT_PARAMS = { "width": 120, diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index 141864d..d840b69 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -1,13 +1,13 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -import unittest from random import randint from typing import List +import unittest -from squirrelbattle.interfaces import Map, Tile -from squirrelbattle.mapgeneration import broguelike -from squirrelbattle.display.texturepack import TexturePack +from ..display.texturepack import TexturePack +from ..interfaces import Map, Tile +from ..mapgeneration import broguelike class TestBroguelike(unittest.TestCase): -- 2.39.5 From 519504fc32bdb4526309af26e7cc142bcdf1d819 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Sun, 10 Jan 2021 22:26:43 +0100 Subject: [PATCH 099/122] Mark equipped items and allow unequipping --- squirrelbattle/display/menudisplay.py | 3 ++- squirrelbattle/game.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index ccc3561..8dba478 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -176,7 +176,8 @@ class PlayerInventoryDisplay(MenuDisplay): selection = f"[{rep}]" if i == self.menu.position \ and self.selected else f" {rep} " self.addstr(self.pad, i + 1, 0, selection - + " " + item.translated_name.capitalize() + + " " + ("[E]" if item.equipped else "") + + item.translated_name.capitalize() + (f" ({item.description})" if item.description else "") + (": " + str(item.price) + " Hazels" if self.store_mode else "")) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 9cfa721..28bb1a3 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -309,7 +309,8 @@ class Game: if key == KeyValues.USE: self.inventory_menu.validate().use() elif key == KeyValues.EQUIP: - self.inventory_menu.validate().equip() + item = self.inventory_menu.validate() + item.unequip() if item.equipped else item.equip() elif key == KeyValues.DROP: self.inventory_menu.validate().drop() -- 2.39.5 From 5e378fc2d02f87b6d5581cf4e4c24e4a6592fa76 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 22:27:46 +0100 Subject: [PATCH 100/122] Update game rules --- docs/rules.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/rules.rst b/docs/rules.rst index 77cfc6b..4b86976 100644 --- a/docs/rules.rst +++ b/docs/rules.rst @@ -11,8 +11,9 @@ prêt à tout pour s'en sortir. Sa vision de rongeur lui permet d'observer l'intégralité de la carte_, et à l'aide d'objets_, il va pouvoir affronter les monstres_ présents dans le donjon et gagner en expérience et en force. -Le jeu fonctionne par niveau. À chaque niveau ``n`` du joueur, entre ``3n`` et -``7n`` entités apparaissent aléatoirement sur la carte. +Le jeu fonctionne par étage. À chaque étage, différents monstres sont présents, +et à l'aide d'objets, il pourra progresser dans le donjon et descendre de plus +en plus bas. En tuant des ennemis, ce qu'il parvient à faire en fonçant directement sur eux ayant mangé trop de noisettes (ou étant armé d'un couteau), l'écureuil va -- 2.39.5 From f05652d9b89d2ca01a068bc89165f51371d7e614 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Sun, 10 Jan 2021 22:39:52 +0100 Subject: [PATCH 101/122] Fixed tests and reached 100% coverage --- squirrelbattle/entities/player.py | 15 --------------- squirrelbattle/tests/game_test.py | 2 ++ 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index a241fef..9613618 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -120,21 +120,6 @@ class Player(InventoryHolder, FightingEntity): self.current_xp += int(xp * self.xp_buff) self.level_up() - def remove_from_inventory(self, obj: Item) -> None: - """ - Remove the given item from the inventory, even if the item is equipped. - """ - if obj == self.equipped_main: - self.equipped_main = None - elif obj == self.equipped_armor: - self.equipped_armor = None - elif obj == self.equipped_secondary: - self.equipped_secondary = None - elif obj == self.equipped_helmet: - self.equipped_helmet = None - else: - return super().remove_from_inventory(obj) - # noinspection PyTypeChecker,PyUnresolvedReferences def check_move(self, y: int, x: int, move_if_possible: bool = False) \ -> bool: diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index b0d8946..032ff64 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -49,6 +49,7 @@ class TestGame(unittest.TestCase): # Add items in the inventory to check that it is well loaded bomb.hold(self.game.player) sword.hold(self.game.player) + sword.equip() # Ensure that merchants can be saved merchant = Merchant() @@ -708,6 +709,7 @@ class TestGame(unittest.TestCase): self.assertIn(shield, self.game.player.inventory) self.assertIn(helmet, self.game.player.inventory) self.assertIn(chestplate, self.game.player.inventory) + self.game.display_actions(DisplayActions.REFRESH) # Test rings self.game.player.inventory.clear() -- 2.39.5 From e74431086157a792b442f76f20587a30b387b7c3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 22:51:01 +0100 Subject: [PATCH 102/122] Place doors at the beginning of the corridor --- squirrelbattle/display/texturepack.py | 3 +++ squirrelbattle/interfaces.py | 1 + squirrelbattle/mapgeneration/broguelike.py | 3 +-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 16cad4f..d8d04cc 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -82,6 +82,7 @@ TexturePack.ASCII_PACK = TexturePack( BOW=')', CHEST='□', CHESTPLATE='(', + DOOR='&', EAGLE='µ', EMPTY=' ', EXPLOSION='%', @@ -124,6 +125,8 @@ TexturePack.SQUIRREL_PACK = TexturePack( BOW='🏹', CHEST='🧰', CHESTPLATE='🦺', + DOOR=('🚪', curses.COLOR_WHITE, (1000, 1000, 1000), + curses.COLOR_WHITE, (1000, 1000, 1000)), EAGLE='🦅', EMPTY=' ', EXPLOSION='💥', diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index a17ab45..ea39cac 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -390,6 +390,7 @@ class Tile(Enum): WALL = auto() FLOOR = auto() LADDER = auto() + DOOR = auto() @staticmethod def from_ascii_char(ch: str) -> "Tile": diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 8ae6582..c537cf8 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -83,8 +83,7 @@ class Generator: def place_room(level: List[List[Tile]], y: int, x: int, room: List[List[Tile]], door_y: int, door_x: int) -> None: rh, rw = len(room), len(room[0]) - # maybe place Tile.DOOR here ? - level[y][x] = Tile.FLOOR + level[y][x] = Tile.DOOR for ry in range(rh): for rx in range(rw): if room[ry][rx] == Tile.FLOOR: -- 2.39.5 From 6c0aaffd77b315e0cb285e45709c138dab915514 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 22:53:27 +0100 Subject: [PATCH 103/122] Doors are walls --- squirrelbattle/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index ea39cac..80be06b 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -431,7 +431,7 @@ class Tile(Enum): """ Is this Tile a wall? """ - return self == Tile.WALL + return self == Tile.WALL or self == Tile.DOOR def is_ladder(self) -> bool: """ -- 2.39.5 From 099a0eab3177c3d3f1a82c58d8ad635640a8c963 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Sun, 10 Jan 2021 22:54:48 +0100 Subject: [PATCH 104/122] Add comments and docstring --- squirrelbattle/mapgeneration/broguelike.py | 116 +++++++++++++++++++-- 1 file changed, 109 insertions(+), 7 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 7fe9f88..cc7ddae 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -27,6 +27,10 @@ DEFAULT_PARAMS = { } def dist(level, y1, x1, y2, x2): + """ + Compute the minimum walking distance between points (y1, x1) and (y2, x2) on a Tile grid + """ + # simple breadth first search copy = [[t for t in l] for l in level] dist = -1 queue, next_queue = [[y1, x1]], [0] @@ -54,12 +58,19 @@ class Generator: def room_fits(level: List[List[Tile]], y: int, x: int, room: List[List[Tile]], door_y: int, door_x: int, dy: int, dx: int) -> bool: + """ + Using point (door_y, door_x) in the room as a reference and placing it + over point (y, x) in the level, returns whether or not the room fits + here + """ lh, lw = len(level), len(level[0]) rh, rw = len(room), len(room[0]) if not(0 < y + dy < lh and 0 < x + dx < lw): return False + # door must be placed on an empty tile, and point into a floor tile if level[y][x] != Tile.EMPTY or level[y + dy][x + dx] != Tile.FLOOR: return False + # now we verify floor tiles in both grids do not overlap for ry in range(rh): for rx in range(rw): if room[ry][rx] == Tile.FLOOR: @@ -80,8 +91,12 @@ class Generator: @staticmethod def place_room(level: List[List[Tile]], y: int, x: int, room: List[List[Tile]], door_y: int, door_x: int) -> None: + """ + Mutates level in place to add the room. Placement is determined by + making (door_y, door_x) in the room correspond with (y, x) in the level + """ rh, rw = len(room), len(room[0]) - # maybe place Tile.DOOR here ? + # maybe place Tile.DOOR here instead ? level[y][x] = Tile.FLOOR for ry in range(rh): for rx in range(rw): @@ -90,12 +105,20 @@ class Generator: @staticmethod def add_loop(level: List[List[Tile]], y: int, x: int) -> None: + """ + Try to add a corridor between two far apart floor tiles, passing + through point (y, x). + """ h, w = len(level), len(level[0]) + if level[y][x] != Tile.EMPTY: return False - # loop over both axis + + # loop over both directions, trying to place both veritcal + # and horizontal corridors for dx, dy in [[0, 1], [1, 0]]: - # then we find two floor tiles, exiting if we ever move oob + # then we find two floor tiles, one on each side of (y, x) + # exiting if we don't find two (reach the edge of the map before) y1, x1, y2, x2 = y, x, y, x while x1 >= 0 and y1 >= 0 and level[y1][x1] == Tile.EMPTY: y1, x1 = y1 - dy, x1 - dx @@ -104,15 +127,18 @@ class Generator: if not(0 <= x1 <= x2 < w and 0 <= y1 <= y2 < h): continue - # if adding the path would make the two tiles significantly closer - # and its sides don't touch already placed terrain, build it def verify_sides(): + # switching up dy and dx here pivots the axis, so + # (y+dx, x+dy) and (y-dx, x-dy) are the tiles adjacent to + # (y, x), but not on the original axis for Dx, Dy in [[dy, dx], [-dy, -dx]]: for i in range(1, y2-y1+x2-x1): if not(0<= y1+Dy+i*dy < h and 0 <= x1+Dx+i*dx < w) or \ level[y1+Dy+i*dy][x1+Dx+i*dx].can_walk(): return False return True + # if adding the path would make the two tiles significantly closer + # and its sides don't touch already placed terrain, build it if dist(level, y1, x1, y2, x2) < 20 and verify_sides(): y, x = y1+dy, x1+dx while level[y][x] == Tile.EMPTY: @@ -123,6 +149,10 @@ class Generator: @staticmethod def place_walls(level: List[List[Tile]]) -> None: + """ + Place wall tiles on every empty tile that is adjacent (in the largest + sense), to a floor tile + """ h, w = len(level), len(level[0]) for y in range(h): for x in range(w): @@ -132,11 +162,25 @@ class Generator: level[ny][nx] = Tile.WALL def corr_meta_info(self) -> Tuple[int, int, int, int]: + """ + Return info about the extra grid space that should be allocated for the + room, and where the room should be built along this extra grid space. + Because grids are usually thight around the room, this gives us extra + place to add a corridor later. Corridor length and orientation is + implicitly derived from this info. + + h_sup and w_sup represent the extra needed space along each axis, + and h_off and w_off are the offset at which to build the room + """ if random() < self.params["corridor_chance"]: h_sup = randint(self.params["min_v_corr"], self.params["max_v_corr"]) if random() < .5 else 0 + # we only allow extra space allocation along one axis, + # because there won't more than one exit corridor w_sup = 0 if h_sup else randint(self.params["min_h_corr"], self.params["max_h_corr"]) + # implicitly choose which direction along the axis + # the corridor will be pointing to h_off = h_sup if random() < .5 else 0 w_off = w_sup if random() < .5 else 0 return h_sup, w_sup, h_off, w_off @@ -144,6 +188,12 @@ class Generator: @staticmethod def build_door(room, y, x, dy, dx, length): + """ + Tries to build the exit from the room at given coordinates + Depending on parameter length, it will either attempt to build a + simple door, or a long corridor. Return value is a boolean + signifying whether or not the exit was successfully built + """ rh, rw = len(room), len(room[0]) # verify we are pointing away from a floor tile if not(0 <= y - dy < rh and 0 <= x - dx < rw) \ @@ -155,6 +205,7 @@ class Generator: if 0 <= ny < rh and 0 <= nx < rw \ and room[ny][nx] != Tile.EMPTY: return False + # see if the path ahead is clear. needed in the case of non convex room for i in range(length+1): if room[y + i * dy][x + i * dx] != Tile.EMPTY: return False @@ -165,6 +216,10 @@ class Generator: @staticmethod def attach_door(room: List[List[Tile]], h_sup: int, w_sup: int, h_off: int, w_off: int) -> Tuple[int, int, int, int]: + """ + Attach an exit to the room. If extra space was allocated to + the grid, make sure a corridor is properly built + """ length = h_sup + w_sup dy, dx = 0, 0 if length > 0: @@ -173,11 +228,13 @@ class Generator: else: dx = -1 if w_off else 1 else: + # determine door direction for rooms without corridors if random() < .5: dy = -1 if random() < .5 else 1 else: dx = -1 if random() < .5 else 1 + # loop over all possible positions in a random order rh, rw = len(room), len(room[0]) yxs = [i for i in range(rh * rw)] shuffle(yxs) @@ -186,11 +243,18 @@ class Generator: if room[y][x] == Tile.EMPTY and \ Generator.build_door(room, y, x, dy, dx, length): break + else: + return None, None return y + length * dy, x + length * dx, dy, dx def create_circular_room(self, spawnable: bool = True) \ -> Tuple[List[List[Tile]], int, int, int, int]: + """ + Create and return as a tile grid a room that is circular in shape, and + may have a center, also circular hole + Also return door info so we know how to place the room in the level + """ if random() < self.params["large_circular_room"]: r = randint(5, 10) else: @@ -217,8 +281,11 @@ class Generator: else: room[-1].append(Tile.EMPTY) + # log all placed tiles as spawn positions if spawnable: self.register_spawn_area(room) + + # attach exit door_y, door_x, dy, dx = self.attach_door(room, h_sup, w_sup, h_off, w_off) @@ -226,9 +293,18 @@ class Generator: def create_random_room(self, spawnable: bool = True) \ -> Tuple[List[list], int, int, int, int]: + """ + Randomly select a room shape and return one such room along with its + door info. Set spawnable to False is the room should be marked as a + potential spawning region on the map + """ return self.create_circular_room() def register_spawn_area(self, area:List[List[Tile]]): + """ + Register all floor positions relative to the input grid + for later use + """ spawn_positions = [] for y, line in enumerate(area): for x, tile in enumerate(line): @@ -237,12 +313,22 @@ class Generator: self.queued_area = spawn_positions def update_spawnable(self, y, x): + """ + Convert previous spawn positions relative to the room grid to actual + actual spawn positions on the level grid, using the position of the + top left corner of the room on the level, then log them as a + spawnable region + """ if self.queued_area != None: translated_area = [[y+ry, x+rx] for ry, rx in self.queued_area] self.spawn_areas.append(translated_area) self.queued_area = None def populate(self, rv): + """ + Populate every spawnable area with some randomly chosen, randomly + placed entity + """ min_c, max_c = self.params["spawn_per_region"] for region in self.spawn_areas: entity_count = randint(min_c, max_c) @@ -254,6 +340,10 @@ class Generator: rv.add_entity(entity) def run(self) -> Map: + """ + Using procedural generation, build and return a full map, populated + with entities + """ height, width = self.params["height"], self.params["width"] level = [width * [Tile.EMPTY] for _ignored in range(height)] @@ -261,24 +351,30 @@ class Generator: mem, self.params["corridor_chance"] = self.params["corridor_chance"], 0 starting_room, _, _, _, _ = self.create_random_room(spawnable = False) dim_v, dim_h = len(starting_room), len(starting_room[0]) + # because Generator.room_fits checks that the exit door is correctly + # placed, but the starting room has no exit door, we find a positoin + # manually pos_y, pos_x = randint(0, height - dim_v - 1),\ randint(0, width - dim_h - 1) self.place_room(level, pos_y, pos_x, starting_room, 0, 0) + # remove the door that was placed if starting_room[0][0] != Tile.FLOOR: level[pos_y][pos_x] = Tile.EMPTY self.params["corridor_chance"] = mem - # find a starting position + # find a starting position for the player sy, sx = randint(0, height - 1), randint(0, width - 1) while level[sy][sx] != Tile.FLOOR: sy, sx = randint(0, height - 1), randint(0, width - 1) level[sy][sx] = Tile.LADDER - # now we loop until we've tried enough, or we've added enough rooms + # now we loop until we're bored, or we've added enough rooms tries, rooms_built = 0, 0 while tries < self.params["tries"] \ and rooms_built < self.params["max_rooms"]: + # build a room, try to fit it everywhere in a random order, and + # place it at the first possible position room, door_y, door_x, dy, dx = self.create_random_room() positions = [i for i in range(height * width)] shuffle(positions) @@ -293,6 +389,12 @@ class Generator: # post-processing self.place_walls(level) + + # because when a room is placed, it leads to exactly one previously + # placed room, the level has a tree like structure with the starting + # room as the root + # to avoid boring player backtracking, we add some cycles to the room + # graph in post processing by placing additional corridors tries, loops = 0, 0 while tries < self.params["loop_tries"] and \ loops < self.params["loop_max"]: -- 2.39.5 From 11daa8573ceabe55f7aaf346be60246fc5862c36 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 22:59:34 +0100 Subject: [PATCH 105/122] The players can open doors --- squirrelbattle/display/texturepack.py | 2 +- squirrelbattle/entities/player.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index d8d04cc..92df405 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -126,7 +126,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( CHEST='🧰', CHESTPLATE='🦺', DOOR=('🚪', curses.COLOR_WHITE, (1000, 1000, 1000), - curses.COLOR_WHITE, (1000, 1000, 1000)), + curses.COLOR_WHITE, (1000, 1000, 1000)), EAGLE='🦅', EMPTY=' ', EXPLOSION='💥', diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 17ee7df..a04eed2 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -6,7 +6,7 @@ from random import randint from typing import Dict, Optional, Tuple from .items import Item -from ..interfaces import FightingEntity, InventoryHolder +from ..interfaces import FightingEntity, InventoryHolder, Tile from ..translations import gettext as _ @@ -152,6 +152,12 @@ class Player(InventoryHolder, FightingEntity): return True elif entity.is_item(): entity.hold(self) + tile = self.map.tiles[y][x] + if tile == Tile.DOOR and move_if_possible: + # Open door + self.map.tiles[y][x] = Tile.FLOOR + self.map.compute_visibility(y, x, self.vision) + return super().check_move(y, x, move_if_possible) return super().check_move(y, x, move_if_possible) def save_state(self) -> dict: -- 2.39.5 From 8f845d1e4cb32c257ab496d15727c7a19d6ced88 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 23:03:24 +0100 Subject: [PATCH 106/122] Doors don't break the connexity of map --- squirrelbattle/tests/mapgeneration_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index d840b69..3879f8f 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -27,15 +27,16 @@ class TestBroguelike(unittest.TestCase): def is_connex(self, grid: List[List[Tile]]) -> bool: h, w = len(grid), len(grid[0]) y, x = randint(0, h - 1), randint(0, w - 1) - while not (grid[y][x].can_walk()): + while not (grid[y][x].can_walk() or grid[y][x] == Tile.DOOR): y, x = randint(0, h - 1), randint(0, w - 1) queue = Map.neighbourhood(grid, y, x) while queue: y, x = queue.pop() - if grid[y][x].can_walk(): + if grid[y][x].can_walk() or grid[y][x] == Tile.DOOR: grid[y][x] = Tile.WALL queue += Map.neighbourhood(grid, y, x) - return not any([t.can_walk() for row in grid for t in row]) + return not any([t.can_walk() or t == Tile.DOOR + for row in grid for t in row]) def test_build_doors(self) -> None: m = self.stom(". .\n. .\n. .\n") -- 2.39.5 From b0ca1d4edfa7835107f0b4f345dce0b92778a2d0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 23:05:49 +0100 Subject: [PATCH 107/122] Cover everytime the map generation test --- squirrelbattle/tests/mapgeneration_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/tests/mapgeneration_test.py b/squirrelbattle/tests/mapgeneration_test.py index 3879f8f..5fa19fd 100644 --- a/squirrelbattle/tests/mapgeneration_test.py +++ b/squirrelbattle/tests/mapgeneration_test.py @@ -26,8 +26,8 @@ class TestBroguelike(unittest.TestCase): def is_connex(self, grid: List[List[Tile]]) -> bool: h, w = len(grid), len(grid[0]) - y, x = randint(0, h - 1), randint(0, w - 1) - while not (grid[y][x].can_walk() or grid[y][x] == Tile.DOOR): + y, x = -1, -1 + while not grid[y][x].can_walk(): y, x = randint(0, h - 1), randint(0, w - 1) queue = Map.neighbourhood(grid, y, x) while queue: -- 2.39.5 From 60675d78593e69c0d89cbc4def3047cd7b9ac5d2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 23:21:28 +0100 Subject: [PATCH 108/122] Cover doors code --- squirrelbattle/assets/example_map.txt | 6 +++--- squirrelbattle/tests/entities_test.py | 6 +++--- squirrelbattle/tests/game_test.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/assets/example_map.txt b/squirrelbattle/assets/example_map.txt index be2e798..68c3ae1 100644 --- a/squirrelbattle/assets/example_map.txt +++ b/squirrelbattle/assets/example_map.txt @@ -2,17 +2,17 @@ ####### ############# #.H...# #...........# #.....# #####...........# - #.....# #............H..# + #.....# #...&........H..# #.##### #.###...........# #.# #.# #...........# #.# #.# ############# #.# #.# #.#### #.# #....# #.# - ####.###################.# + ####&###################&# #.....................# ################# #.....................# #...............# #.....................#######...............# - #...........................................# + #.....................&.....&...............# #.....................#######...............# ####################### ################# diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index a0e2548..396d87c 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -134,13 +134,13 @@ class TestEntities(unittest.TestCase): self.map.remove_entity(entity2) # Test following the player and finding the player as target - self.player.move(5, 5) - fam.move(4, 5) + self.player.move(6, 5) + fam.move(5, 5) fam.target = None self.player.move_down() self.map.tick(self.player) self.assertTrue(fam.target == self.player) - self.assertEqual(fam.y, 5) + self.assertEqual(fam.y, 6) self.assertEqual(fam.x, 5) # Test random move diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index dbeaa9e..5716854 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -728,6 +728,7 @@ class TestGame(unittest.TestCase): self.game.player.inventory.clear() ring = RingCritical() ring.hold(self.game.player) + self.game.display_actions(DisplayActions.REFRESH) old_critical = self.game.player.critical self.game.handle_key_pressed(KeyValues.EQUIP) self.assertEqual(self.game.player.critical, @@ -951,3 +952,18 @@ class TestGame(unittest.TestCase): # Exit the menu self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.PLAY) + + def test_doors(self) -> None: + """ + Check that the user can open doors. + """ + self.game.state = GameMode.PLAY + + self.game.player.move(9, 8) + self.assertEqual(self.game.map.tiles[10][8], Tile.DOOR) + # Open door + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertEqual(self.game.map.tiles[10][8], Tile.FLOOR) + self.assertEqual(self.game.player.y, 10) + self.assertEqual(self.game.player.x, 8) + self.game.display_actions(DisplayActions.REFRESH) -- 2.39.5 From 5aaef15b2ba269100e1ae0e42a25c2e1b1c18217 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Sun, 10 Jan 2021 23:38:29 +0100 Subject: [PATCH 109/122] Add new room type : chunk rooms --- squirrelbattle/mapgeneration/broguelike.py | 61 +++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 21408ae..a2d1532 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -13,7 +13,7 @@ DEFAULT_PARAMS = { "max_rooms": 20, "max_room_tries": 15, "cross_room": 1, - "corridor_chance": .6, + "corridor_chance": .2, "min_v_corr": 2, "max_v_corr": 6, "min_h_corr": 4, @@ -24,6 +24,10 @@ DEFAULT_PARAMS = { "loop_max": 5, "loop_threshold": 15, "spawn_per_region": [1, 2], + "room_chances" : { + "circular" : 5, + "chunks" : 1, + }, } def dist(level, y1, x1, y2, x2): @@ -253,6 +257,47 @@ class Generator: return y + length * dy, x + length * dx, dy, dx + def create_chunk_room(self, spawnable: bool = True) \ + -> Tuple[List[List[Tile]], int, int, int, int]: + """ + Create and return as a tile grid a room that is composed of multiple + overlapping circles of the same radius + Also return door info so we know how to place the room in the level + """ + height, width = 15, 15 + nb_chunks, r = 6, 3 + + h_sup, w_sup, h_off, w_off = self.corr_meta_info() + room = [[Tile.EMPTY] * (width + w_sup) \ + for _dummy in range(height + h_sup)] + + def draw_chunk(room, y, x): + for i in range(y - r, y + r + 1): + d = (y - i)**2 + for j in range(x - r, x + r + 1): + if d + (x - j)**2 < r**2: + room[i][j] = Tile.FLOOR + + draw_chunk(room, height//2 + 1, width//2 + 1) + + min_w, max_w = w_off + r + 1, width + w_off - r -1 + min_h, max_h = h_off + r + 1, height + h_off - r - 1 + for i in range(nb_chunks): + y, x = randint(min_h, max_h), randint(min_w, max_w) + while room[y][x] != Tile.FLOOR: + y, x = randint(min_h, max_h), randint(min_w, max_w) + draw_chunk(room, y, x) + + # log all placed tiles as spawn positions + if spawnable: + self.register_spawn_area(room) + + # attach exit + door_y, door_x, dy, dx = self.attach_door(room, h_sup, w_sup, + h_off, w_off) + + return room, door_y, door_x, dy, dx + def create_circular_room(self, spawnable: bool = True) \ -> Tuple[List[List[Tile]], int, int, int, int]: """ @@ -303,7 +348,19 @@ class Generator: door info. Set spawnable to False is the room should be marked as a potential spawning region on the map """ - return self.create_circular_room() + coef_dict = self.params["room_chances"] + sum_coefs = sum(coef_dict[key] for key in coef_dict) + target = randint(1, sum_coefs) + for key in coef_dict: + if target > coef_dict[key]: + target -= coef_dict[key] + else: + break + + if key == "circular": + return self.create_circular_room(spawnable = spawnable) + elif key == "chunks": + return self.create_chunk_room(spawnable = spawnable) def register_spawn_area(self, area: List[List[Tile]]) -> None: """ -- 2.39.5 From 65ae99a26db613fad98cf83a8c74d2865711470e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 23:41:51 +0100 Subject: [PATCH 110/122] The logs of the map was not updated --- squirrelbattle/game.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 6b8bc0f..2e467fb 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -199,7 +199,9 @@ class Game: self.map_index = 0 return while self.map_index >= len(self.maps): - self.maps.append(broguelike.Generator().run()) + m = broguelike.Generator().run() + m.logs = self.logs + self.maps.append(m) new_map = self.map new_map.floor = self.map_index old_map.remove_entity(self.player) @@ -417,6 +419,7 @@ class Game: self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]] for i, m in enumerate(self.maps): m.floor = i + m.logs = self.logs except KeyError as error: self.message = _("Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted.")\ -- 2.39.5 From 588357e5bf9d6d0154701c9ce337230330e7a8e8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 23:49:43 +0100 Subject: [PATCH 111/122] Linting --- squirrelbattle/mapgeneration/broguelike.py | 45 ++++++++++------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 4a97e44..f746f37 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -26,9 +26,11 @@ DEFAULT_PARAMS = { "spawn_per_region": [1, 2], } -def dist(level, y1, x1, y2, x2): + +def dist(level: List[List[Tile]], y1: int, x1: int, y2: int, x2: int) -> int: """ - Compute the minimum walking distance between points (y1, x1) and (y2, x2) on a Tile grid + Compute the minimum walking distance between points (y1, x1) and (y2, x2) + on a Tile grid """ # simple breadth first search copy = [[t for t in row] for row in level] @@ -60,9 +62,9 @@ class Generator: room: List[List[Tile]], door_y: int, door_x: int, dy: int, dx: int) -> bool: """ - Using point (door_y, door_x) in the room as a reference and placing it + Using point (door_y, door_x) in the room as a reference and placing it over point (y, x) in the level, returns whether or not the room fits - here + here """ lh, lw = len(level), len(level[0]) rh, rw = len(room), len(room[0]) @@ -93,7 +95,7 @@ class Generator: def place_room(level: List[List[Tile]], y: int, x: int, room: List[List[Tile]], door_y: int, door_x: int) -> None: """ - Mutates level in place to add the room. Placement is determined by + Mutates level in place to add the room. Placement is determined by making (door_y, door_x) in the room correspond with (y, x) in the level """ rh, rw = len(room), len(room[0]) @@ -106,11 +108,11 @@ class Generator: @staticmethod def add_loop(level: List[List[Tile]], y: int, x: int) -> bool: """ - Try to add a corridor between two far apart floor tiles, passing + Try to add a corridor between two far apart floor tiles, passing through point (y, x). """ h, w = len(level), len(level[0]) - + if level[y][x] != Tile.EMPTY: return False @@ -128,8 +130,8 @@ class Generator: continue def verify_sides() -> bool: - # switching up dy and dx here pivots the axis, so - # (y+dx, x+dy) and (y-dx, x-dy) are the tiles adjacent to + # switching up dy and dx here pivots the axis, so + # (y+dx, x+dy) and (y-dx, x-dy) are the tiles adjacent to # (y, x), but not on the original axis for delta_x, delta_y in [[dy, dx], [-dy, -dx]]: for i in range(1, y2 - y1 + x2 - x1): @@ -194,8 +196,8 @@ class Generator: dy: int, dx: int, length: int) -> bool: """ Tries to build the exit from the room at given coordinates - Depending on parameter length, it will either attempt to build a - simple door, or a long corridor. Return value is a boolean + Depending on parameter length, it will either attempt to build a + simple door, or a long corridor. Return value is a boolean signifying whether or not the exit was successfully built """ rh, rw = len(room), len(room[0]) @@ -247,15 +249,15 @@ class Generator: if room[y][x] == Tile.EMPTY and \ Generator.build_door(room, y, x, dy, dx, length): break - else: - return None, None + else: # pragma: no cover + return None, None, None, None return y + length * dy, x + length * dx, dy, dx def create_circular_room(self, spawnable: bool = True) \ -> Tuple[List[List[Tile]], int, int, int, int]: """ - Create and return as a tile grid a room that is circular in shape, and + Create and return as a tile grid a room that is circular in shape, and may have a center, also circular hole Also return door info so we know how to place the room in the level """ @@ -298,7 +300,7 @@ class Generator: def create_random_room(self, spawnable: bool = True) \ -> Tuple[List[list], int, int, int, int]: """ - Randomly select a room shape and return one such room along with its + Randomly select a room shape and return one such room along with its door info. Set spawnable to False is the room should be marked as a potential spawning region on the map """ @@ -319,12 +321,12 @@ class Generator: def update_spawnable(self, y: int, x: int) -> None: """ Convert previous spawn positions relative to the room grid to actual - actual spawn positions on the level grid, using the position of the - top left corner of the room on the level, then log them as a + actual spawn positions on the level grid, using the position of the + top left corner of the room on the level, then log them as a spawnable region """ - if self.queued_area != None: - translated_area = [[y+ry, x+rx] for ry, rx in self.queued_area] + if self.queued_area is not None: + translated_area = [[y + ry, x + rx] for ry, rx in self.queued_area] self.spawn_areas.append(translated_area) self.queued_area = None @@ -333,11 +335,6 @@ class Generator: Populate every spawnable area with some randomly chosen, randomly placed entity """ - if self.queued_area is not None: - translated_area = [[y + ry, x + rx] for ry, rx in self.queued_area] - self.spawn_areas.append(translated_area) - self.queued_area = None - min_c, max_c = self.params["spawn_per_region"] for region in self.spawn_areas: entity_count = randint(min_c, max_c) -- 2.39.5 From 57605c969fdc474e2788eef0f3a4fb43e111ebd0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 10 Jan 2021 23:57:39 +0100 Subject: [PATCH 112/122] Bump to version 23.14 --- README.md | 2 +- debian/changelog | 6 ++++++ docs/deployment.rst | 12 ++++++------ docs/install.rst | 4 ++-- docs/translation.rst | 2 +- setup.py | 2 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 2 +- .../locale/es/LC_MESSAGES/squirrelbattle.po | 2 +- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 2 +- squirrelbattle/translations.py | 2 +- 10 files changed, 21 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ae7be80..48b2220 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Download one of these two packages on the AUR : Available on our git repository, has a dependency on fonts-noto-color-emoji (to be found in the official Debian repositories). Run ``` - dpkg -i python3-squirrelbattle_3.14.1_all.deb + dpkg -i python3-squirrelbattle_23.14_all.deb ``` after downloading In all cases, execute via command line : ```squirrel-battle``` diff --git a/debian/changelog b/debian/changelog index 2399e41..3100a70 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +python3-squirrel-battle (23.14) beta; urgency=low + + * Big update + + -- Yohann D'ANELLO Sun, 10 Jan 2021 23:56:42 +0100 + python3-squirrel-battle (3.14.1) beta; urgency=low * Some graphical improvements. diff --git a/docs/deployment.rst b/docs/deployment.rst index b96246b..cfb95d3 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -3,7 +3,7 @@ Déploiement du projet .. _PyPI: https://pypi.org/project/squirrel-battle/ .. _AUR: https://aur.archlinux.org/packages/python-squirrel-battle/ -.. _Debian: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14.1_all.deb?job=build-deb +.. _Debian: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_23.14_all.deb?job=build-deb .. _installation: install.html À chaque nouvelle version du projet, il est compilé et déployé dans PyPI_, dans @@ -46,7 +46,7 @@ paquet ainsi que des détails à fournir à PyPI : setup( name="squirrel-battle", - version="3.14.1", + version="23.14", author="ÿnérant, eichhornchen, nicomarg, charlse", author_email="squirrel-battle@crans.org", description="Watch out for squirrel's knives!", @@ -172,7 +172,7 @@ du dépôt Git. Le fichier ``PKGBUILD`` dispose de cette structure : pkgbase=squirrel-battle pkgname=python-squirrel-battle-git - pkgver=3.14.1 + pkgver=23.14 pkgrel=1 pkgdesc="Watch out for squirrel's knives!" arch=('any') @@ -222,7 +222,7 @@ les releases, est plus ou moins similaire : pkgbase=squirrel-battle pkgname=python-squirrel-battle - pkgver=3.14.1 + pkgver=23.14 pkgrel=1 pkgdesc="Watch out for squirrel's knives!" arch=('any') @@ -232,7 +232,7 @@ les releases, est plus ou moins similaire : makedepends=('gettext' 'python-setuptools') depends=('noto-fonts-emoji') checkdepends=('python-tox') - source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v3.14.1/$pkgbase-v$pkgver.tar.gz") + source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v23.14/$pkgbase-v$pkgver.tar.gz") sha256sums=("6090534d598c0b3a8f5acdb553c12908ba8107d62d08e17747d1dbb397bddef0") build() { @@ -317,7 +317,7 @@ On peut ensuite construire le paquet : dpkg-buildpackage mkdir build && cp ../*.deb build/ -Le paquet sera installé dans ``build/python3-squirrel-battle_3.14.1_all.deb``. +Le paquet sera installé dans ``build/python3-squirrel-battle_23.14_all.deb``. Le paquet Debian_ est construit par l'intégration continue Gitlab et ajouté à chaque release. diff --git a/docs/install.rst b/docs/install.rst index 5cc2351..2c78d0a 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -61,7 +61,7 @@ Le jeu peut être ensuite lancé via la commande ``squirrel-battle``. Sur Ubuntu/Debian ~~~~~~~~~~~~~~~~~ -.. _paquet: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14.1_all.deb?job=build-deb +.. _paquet: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_23.14_all.deb?job=build-deb Un paquet_ est généré par l'intégration continue de Gitlab à chaque commit. Ils sont également attachés à chaque nouvelle release. @@ -73,7 +73,7 @@ Pour installer ce paquet, il suffit de le télécharger et d'appeler ``dpkg`` : .. code:: bash - dpkg -i python3-squirrelbattle_3.14.1_all.deb + dpkg -i python3-squirrelbattle_23.14_all.deb Ce paquet inclut un patch pour afficher les émojis écureuil correctement. diff --git a/docs/translation.rst b/docs/translation.rst index f3d2584..d9ae25d 100644 --- a/docs/translation.rst +++ b/docs/translation.rst @@ -65,7 +65,7 @@ d'exécuter pour chaque langue ```` : find squirrelbattle -iname '*.py' | xargs xgettext --from-code utf-8 --add-comments --package-name=squirrelbattle - --package-version=3.14.1 + --package-version=23.14 "--copyright-holder=ÿnérant, eichhornchen, nicomarg, charlse" --msgid-bugs-address=squirrel-battle@crans.org -o squirrelbattle/locale//LC_MESSAGES/squirrelbattle.po diff --git a/setup.py b/setup.py index 5a2a5e7..334d71c 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ for language in ["de", "es", "fr"]: setup( name="squirrel-battle", - version="3.14.1", + version="23.14", author="ÿnérant, eichhornchen, nicomarg, charlse", author_email="squirrel-battle@crans.org", description="Watch out for squirrel's knives!", diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 74eb1a9..bc802fa 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -6,7 +6,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: squirrelbattle 3.14.1\n" +"Project-Id-Version: squirrelbattle 23.14\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" "POT-Creation-Date: 2021-01-10 21:30+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 909ff48..13ed1e9 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -6,7 +6,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: squirrelbattle 3.14.1\n" +"Project-Id-Version: squirrelbattle 23.14\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" "POT-Creation-Date: 2021-01-10 21:30+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 93b524d..9111fee 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -10,7 +10,7 @@ msgstr "{name} prend {amount} points de dégât." #, fuzzy msgid "" msgstr "" -"Project-Id-Version: squirrelbattle 3.14.1\n" +"Project-Id-Version: squirrelbattle 23.14\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" "POT-Creation-Date: 2021-01-10 21:30+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 19fffa9..4c13648 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -65,7 +65,7 @@ class Translator: args = ["xargs", "xgettext", "--from-code", "utf-8", "--add-comments", "--package-name=squirrelbattle", - "--package-version=3.14.1", + "--package-version=23.14", "--copyright-holder=ÿnérant, eichhornchen, " "nicomarg, charlse, ifugao", "--msgid-bugs-address=squirrel-battle@crans.org", -- 2.39.5 From c854d4157916a4abe0909b8de0eeff2954541711 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Mon, 11 Jan 2021 01:18:20 +0100 Subject: [PATCH 113/122] Fix merging mistakes and chunk rooms --- squirrelbattle/mapgeneration/broguelike.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index a2d1532..6cdf3f8 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -278,7 +278,7 @@ class Generator: if d + (x - j)**2 < r**2: room[i][j] = Tile.FLOOR - draw_chunk(room, height//2 + 1, width//2 + 1) + draw_chunk(room, h_off + height//2 + 1, w_off + width//2 + 1) min_w, max_w = w_off + r + 1, width + w_off - r -1 min_h, max_h = h_off + r + 1, height + h_off - r - 1 @@ -381,8 +381,8 @@ class Generator: top left corner of the room on the level, then log them as a spawnable region """ - if self.queued_area != None: - translated_area = [[y+ry, x+rx] for ry, rx in self.queued_area] + if self.queued_area is not None: + translated_area = [[y + ry, x + rx] for ry, rx in self.queued_area] self.spawn_areas.append(translated_area) self.queued_area = None @@ -391,11 +391,6 @@ class Generator: Populate every spawnable area with some randomly chosen, randomly placed entity """ - if self.queued_area is not None: - translated_area = [[y + ry, x + rx] for ry, rx in self.queued_area] - self.spawn_areas.append(translated_area) - self.queued_area = None - min_c, max_c = self.params["spawn_per_region"] for region in self.spawn_areas: entity_count = randint(min_c, max_c) -- 2.39.5 From 79d8ef3a442f8f0a61d7af31a43e64bb145256e1 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Sun, 10 Jan 2021 23:38:29 +0100 Subject: [PATCH 114/122] Add new room type : chunk rooms --- squirrelbattle/mapgeneration/broguelike.py | 61 +++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index f746f37..550e90c 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -13,7 +13,7 @@ DEFAULT_PARAMS = { "max_rooms": 20, "max_room_tries": 15, "cross_room": 1, - "corridor_chance": .6, + "corridor_chance": .2, "min_v_corr": 2, "max_v_corr": 6, "min_h_corr": 4, @@ -24,6 +24,10 @@ DEFAULT_PARAMS = { "loop_max": 5, "loop_threshold": 15, "spawn_per_region": [1, 2], + "room_chances" : { + "circular" : 5, + "chunks" : 1, + }, } @@ -254,6 +258,47 @@ class Generator: return y + length * dy, x + length * dx, dy, dx + def create_chunk_room(self, spawnable: bool = True) \ + -> Tuple[List[List[Tile]], int, int, int, int]: + """ + Create and return as a tile grid a room that is composed of multiple + overlapping circles of the same radius + Also return door info so we know how to place the room in the level + """ + height, width = 15, 15 + nb_chunks, r = 6, 3 + + h_sup, w_sup, h_off, w_off = self.corr_meta_info() + room = [[Tile.EMPTY] * (width + w_sup) \ + for _dummy in range(height + h_sup)] + + def draw_chunk(room, y, x): + for i in range(y - r, y + r + 1): + d = (y - i)**2 + for j in range(x - r, x + r + 1): + if d + (x - j)**2 < r**2: + room[i][j] = Tile.FLOOR + + draw_chunk(room, height//2 + 1, width//2 + 1) + + min_w, max_w = w_off + r + 1, width + w_off - r -1 + min_h, max_h = h_off + r + 1, height + h_off - r - 1 + for i in range(nb_chunks): + y, x = randint(min_h, max_h), randint(min_w, max_w) + while room[y][x] != Tile.FLOOR: + y, x = randint(min_h, max_h), randint(min_w, max_w) + draw_chunk(room, y, x) + + # log all placed tiles as spawn positions + if spawnable: + self.register_spawn_area(room) + + # attach exit + door_y, door_x, dy, dx = self.attach_door(room, h_sup, w_sup, + h_off, w_off) + + return room, door_y, door_x, dy, dx + def create_circular_room(self, spawnable: bool = True) \ -> Tuple[List[List[Tile]], int, int, int, int]: """ @@ -304,7 +349,19 @@ class Generator: door info. Set spawnable to False is the room should be marked as a potential spawning region on the map """ - return self.create_circular_room() + coef_dict = self.params["room_chances"] + sum_coefs = sum(coef_dict[key] for key in coef_dict) + target = randint(1, sum_coefs) + for key in coef_dict: + if target > coef_dict[key]: + target -= coef_dict[key] + else: + break + + if key == "circular": + return self.create_circular_room(spawnable = spawnable) + elif key == "chunks": + return self.create_chunk_room(spawnable = spawnable) def register_spawn_area(self, area: List[List[Tile]]) -> None: """ -- 2.39.5 From 03c45a970c1985e9bae3f4bd917a14ba0c22edf9 Mon Sep 17 00:00:00 2001 From: Charles Peyrat Date: Mon, 11 Jan 2021 01:18:20 +0100 Subject: [PATCH 115/122] Fix merging mistakes and chunk rooms --- squirrelbattle/mapgeneration/broguelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 550e90c..62c8b03 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -279,7 +279,7 @@ class Generator: if d + (x - j)**2 < r**2: room[i][j] = Tile.FLOOR - draw_chunk(room, height//2 + 1, width//2 + 1) + draw_chunk(room, h_off + height//2 + 1, w_off + width//2 + 1) min_w, max_w = w_off + r + 1, width + w_off - r -1 min_h, max_h = h_off + r + 1, height + h_off - r - 1 -- 2.39.5 From 7b019ce1491635fa7a3646af3f88ed489b329a81 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 11 Jan 2021 01:21:52 +0100 Subject: [PATCH 116/122] Linting --- squirrelbattle/mapgeneration/broguelike.py | 22 +++++++++++----------- squirrelbattle/tests/game_test.py | 2 -- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/squirrelbattle/mapgeneration/broguelike.py b/squirrelbattle/mapgeneration/broguelike.py index 62c8b03..973485b 100644 --- a/squirrelbattle/mapgeneration/broguelike.py +++ b/squirrelbattle/mapgeneration/broguelike.py @@ -24,10 +24,10 @@ DEFAULT_PARAMS = { "loop_max": 5, "loop_threshold": 15, "spawn_per_region": [1, 2], - "room_chances" : { - "circular" : 5, - "chunks" : 1, - }, + "room_chances": { + "circular": 5, + "chunks": 1, + }, } @@ -269,19 +269,19 @@ class Generator: nb_chunks, r = 6, 3 h_sup, w_sup, h_off, w_off = self.corr_meta_info() - room = [[Tile.EMPTY] * (width + w_sup) \ + room = [[Tile.EMPTY] * (width + w_sup) for _dummy in range(height + h_sup)] - def draw_chunk(room, y, x): + def draw_chunk(room: List[List[Tile]], y: int, x: int) -> None: for i in range(y - r, y + r + 1): d = (y - i)**2 for j in range(x - r, x + r + 1): - if d + (x - j)**2 < r**2: + if d + (x - j) ** 2 < r ** 2: room[i][j] = Tile.FLOOR - draw_chunk(room, h_off + height//2 + 1, w_off + width//2 + 1) + draw_chunk(room, h_off + height // 2 + 1, w_off + width // 2 + 1) - min_w, max_w = w_off + r + 1, width + w_off - r -1 + min_w, max_w = w_off + r + 1, width + w_off - r - 1 min_h, max_h = h_off + r + 1, height + h_off - r - 1 for i in range(nb_chunks): y, x = randint(min_h, max_h), randint(min_w, max_w) @@ -359,9 +359,9 @@ class Generator: break if key == "circular": - return self.create_circular_room(spawnable = spawnable) + return self.create_circular_room(spawnable=spawnable) elif key == "chunks": - return self.create_chunk_room(spawnable = spawnable) + return self.create_chunk_room(spawnable=spawnable) def register_spawn_area(self, area: List[List[Tile]]) -> None: """ diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 4f80977..e5573ec 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses -import os import unittest from ..bootstrap import Bootstrap @@ -101,7 +100,6 @@ class TestGame(unittest.TestCase): Yeah, that's only for coverage. """ self.assertRaises(Exception, Bootstrap.run_game) - self.assertEqual(os.getenv("TERM", "unknown"), "unknown") def test_key_translation(self) -> None: """ -- 2.39.5 From 7ce3b8cd5d0a615cb8411411b7258a76f774ab7f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 11 Jan 2021 01:40:55 +0100 Subject: [PATCH 117/122] Fix spanish translation --- .../locale/es/LC_MESSAGES/squirrelbattle.po | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 13ed1e9..e12e4f0 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -23,19 +23,19 @@ msgstr "Inventorio :" #: squirrelbattle/display/gamedisplay.py:167 msgid "Equipped main:" -msgstr "Principal equipado:" +msgstr "Equipado principal :" #: squirrelbattle/display/gamedisplay.py:171 msgid "Equipped secondary:" -msgstr "Equipado secundario:" +msgstr "Equipado segundario :" #: squirrelbattle/display/gamedisplay.py:176 msgid "Equipped chestplate:" -msgstr "Pechera equipada:" +msgstr "Pechera equipada :" #: squirrelbattle/display/gamedisplay.py:180 msgid "Equipped helmet:" -msgstr "Casco equipado:" +msgstr "Casco equipado :" #: squirrelbattle/display/gamedisplay.py:187 msgid "YOU ARE DEAD" @@ -44,22 +44,22 @@ msgstr "ERES MUERTO" #: squirrelbattle/display/gamedisplay.py:191 #, python-brace-format msgid "Use {key} to use the ladder" -msgstr "Usa {key} para usar la escalera" +msgstr "Presiona {key} para utilizar la escala" #: squirrelbattle/display/gamedisplay.py:210 msgid "Move to the friendly entity to talk to it" -msgstr "Muévete hacia la entidad amiga para hablar con ella." +msgstr "Moverse hasta la entitad amistosa para hablar con ella" #: squirrelbattle/display/gamedisplay.py:212 #, python-brace-format msgid "Use {key} then move to talk to the entity" -msgstr "Usa {key} y luego muévete para hablar con la entidad" +msgstr "Presionar {key} pues moverse para hablar con la entitad" #: squirrelbattle/display/menudisplay.py:124 #: squirrelbattle/display/menudisplay.py:149 #: squirrelbattle/display/menudisplay.py:304 msgid "Credits" -msgstr "Creditos" +msgstr "Créditos" #: squirrelbattle/display/menudisplay.py:173 msgid "INVENTORY" @@ -75,11 +75,11 @@ msgstr "COFRE" #: squirrelbattle/display/menudisplay.py:308 msgid "Developers:" -msgstr "Desarrollador:" +msgstr "Desarrolladores :" #: squirrelbattle/display/menudisplay.py:314 msgid "Translators:" -msgstr "Traductores:" +msgstr "Traductores :" #: squirrelbattle/entities/friendly.py:38 msgid "I don't sell any squirrel" @@ -153,12 +153,12 @@ msgstr "El baile no fue efectivo ..." #: squirrelbattle/game.py:214 #, python-brace-format msgid "The player climbs down to the floor {floor}." -msgstr "El jugador desciende alla planta {floor}." +msgstr "El jugador baja a la planta {floor}." #: squirrelbattle/game.py:227 #, python-brace-format msgid "The player climbs up the floor {floor}." -msgstr "El jugador sube por la planta {floor}." +msgstr "El jugador sube a la planta {floor}." #: squirrelbattle/game.py:348 squirrelbattle/tests/game_test.py:631 msgid "The buyer does not have enough money" @@ -417,7 +417,7 @@ msgstr "anillo de daño crítico" #: squirrelbattle/tests/translations_test.py:91 msgid "ring of more experience" -msgstr "anillo de más experiencia" +msgstr "anillo de mejorada experiencia" #: squirrelbattle/tests/translations_test.py:93 msgid "monocle" -- 2.39.5 From 87e896bd062c8512caa18c142675c590aa6c7ff5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 16 Jan 2021 00:26:33 +0100 Subject: [PATCH 118/122] Item owners are correctly set --- squirrelbattle/interfaces.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 18ac895..7c2c4c0 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -848,7 +848,6 @@ class InventoryHolder(Entity): for i in range(len(inventory)): if isinstance(inventory[i], dict): inventory[i] = self.dict_to_item(inventory[i]) - inventory[i].held_by = self return inventory def dict_to_item(self, item_dict: dict) -> Entity: @@ -859,7 +858,9 @@ class InventoryHolder(Entity): entity_classes = self.get_all_entity_classes_in_a_dict() item_class = entity_classes[item_dict["type"]] - return item_class(**item_dict) + item = item_class(**item_dict) + item.held_by = self + return item def save_state(self) -> dict: """ @@ -875,6 +876,7 @@ class InventoryHolder(Entity): Adds an object to the inventory. """ if obj not in self.inventory: + obj.held_by = self self.inventory.append(obj) def remove_from_inventory(self, obj: Any) -> None: -- 2.39.5 From d978d319bcc16ba14f61ac9bd2090b6bb39a0366 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 16 Jan 2021 00:40:32 +0100 Subject: [PATCH 119/122] Entities are living during two ticks, fixes #80 --- squirrelbattle/entities/items.py | 9 +++++++-- squirrelbattle/tests/entities_test.py | 3 ++- squirrelbattle/tests/game_test.py | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index a80f675..023c1a4 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -232,14 +232,19 @@ class Explosion(Item): """ When a bomb explodes, the explosion is displayed. """ - def __init__(self, *args, **kwargs): + living_ticks: int + + def __init__(self, living_ticks: int = 2, *args, **kwargs): super().__init__(name="explosion", *args, **kwargs) + self.living_ticks = living_ticks def act(self, m: Map) -> None: """ The bomb disappears after exploding. """ - m.remove_entity(self) + self.living_ticks -= 1 + if self.living_ticks <= 0: + m.remove_entity(self) def hold(self, player: InventoryHolder) -> None: """ diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 9d50f58..6f059f2 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -209,7 +209,8 @@ class TestEntities(unittest.TestCase): self.assertNotIn(explosion, self.player.inventory) self.assertIsNone(explosion.held_by) - # The explosion disappears after one tick + # The explosion disappears after two ticks + explosion.act(self.map) explosion.act(self.map) self.assertNotIn(explosion, self.map.entities) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index e5573ec..786de35 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -255,6 +255,7 @@ class TestGame(unittest.TestCase): self.game.map.add_entity(explosion) self.assertIn(explosion, self.game.map.entities) self.game.handle_key_pressed(KeyValues.WAIT) + self.game.handle_key_pressed(KeyValues.WAIT) self.assertNotIn(explosion, self.game.map.entities) rabbit = Rabbit() -- 2.39.5 From 3d019d3ca8c316460665055646f95065a62a3e9c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 21 Jan 2021 01:12:55 +0100 Subject: [PATCH 120/122] Don't create an english translation file --- squirrelbattle/translations.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 4c13648..b6adf06 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -25,6 +25,8 @@ class Translator: Loads compiled translations. """ for language in cls.SUPPORTED_LOCALES: + if language == "en": + continue rep = Path(__file__).parent / "locale" / language / "LC_MESSAGES" rep.mkdir(parents=True) if not rep.is_dir() else None if os.path.isfile(rep / "squirrelbattle.mo"): -- 2.39.5 From 776f8ed88c885e75f24c407eeaa0035fe8f5bf83 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 25 Jan 2021 14:31:37 +0100 Subject: [PATCH 121/122] Fixes #81 --- squirrelbattle/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 7d1bc4c..1513695 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -192,7 +192,7 @@ class Game: # We move up on the ladder of the beginning, # down at the end of the stage - move_down = y != self.map.start_y and x != self.map.start_x + move_down = y != self.map.start_y or x != self.map.start_x old_map = self.map self.map_index += 1 if move_down else -1 if self.map_index == -1: -- 2.39.5 From a28909bb70eef8d3dc179df66615a909ab0fc3e8 Mon Sep 17 00:00:00 2001 From: ynerant Date: Mon, 25 Jan 2021 15:04:47 +0100 Subject: [PATCH 122/122] Fix Readme --- README.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 48b2220..ac8b13c 100644 --- a/README.md +++ b/README.md @@ -11,31 +11,37 @@ Squirrel Battle is an infinite rogue-like game with randomly generated levels, in which the player controls a squirrel in its quest down in a dungeon, using diverse items to defeat monsters, and trying not to die. -##Installation +## Installation -####Via PyPI : -``` pip install --user squirrel-battle -``` to install +#### Via PyPI : +``` +$ pip install --user squirrel-battle +``` +to install -``` pip install --user --upgrade squirrel-battle -``` to upgrade +``` +$ pip install --user --upgrade squirrel-battle +``` +to upgrade -####Via ArchLinux package : +#### Via ArchLinux package : Download one of these two packages on the AUR : * python-squirrel-battle * python-squirrel-battle-git -####Via Debian package : +#### Via Debian package : Available on our git repository, has a dependency on fonts-noto-color-emoji (to be found in the official Debian repositories). -Run ``` - dpkg -i python3-squirrelbattle_23.14_all.deb -``` after downloading +Run +``` +$ dpkg -i python3-squirrelbattle_23.14_all.deb +``` +after downloading -In all cases, execute via command line : ```squirrel-battle``` +In all cases, execute via command line : `squirrel-battle` -##For first-time players +## For first-time players The game is played in a terminal only, preferably one that supports color, markdown and emojis, but it can be played with only grey levels and relatively classic unicode characters. -- 2.39.5