Drop first version of random walk
This commit is contained in:
parent
c216a6089e
commit
9b853324ad
|
@ -12,7 +12,7 @@ import sys
|
||||||
from .entities.player import Player
|
from .entities.player import Player
|
||||||
from .enums import GameMode, KeyValues, DisplayActions
|
from .enums import GameMode, KeyValues, DisplayActions
|
||||||
from .interfaces import Map, Logs
|
from .interfaces import Map, Logs
|
||||||
from .mapgeneration import randomwalk, broguelike
|
from .mapgeneration import broguelike
|
||||||
from .resources import ResourceManager
|
from .resources import ResourceManager
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
from . import menus
|
from . import menus
|
||||||
|
|
|
@ -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
|
|
|
@ -5,7 +5,7 @@ import unittest
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
from squirrelbattle.interfaces import Map, Tile
|
from squirrelbattle.interfaces import Map, Tile
|
||||||
from squirrelbattle.mapgeneration import randomwalk, broguelike
|
from squirrelbattle.mapgeneration import broguelike
|
||||||
|
|
||||||
def is_connex(grid):
|
def is_connex(grid):
|
||||||
h, w = len(grid), len(grid[0])
|
h, w = len(grid), len(grid[0])
|
||||||
|
@ -20,25 +20,6 @@ def is_connex(grid):
|
||||||
queue += Map.neighbourhood(grid, y, x)
|
queue += Map.neighbourhood(grid, y, x)
|
||||||
return not(any([any([t.can_walk() for t in l]) for l in grid]))
|
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):
|
class TestBroguelike(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.generator = broguelike.Generator()
|
self.generator = broguelike.Generator()
|
||||||
|
|
Loading…
Reference in New Issue