How to Create new puzzle
This tutorial explains step by step how to create MyHeroPuzzle
Let's create a new puzzle that is similar to the existing HeroPuzzle from scratch.
Our hero must build his power and defeat enemies in different floor sections. Once the hero enters a floor section, he must complete it before moving to another one. We will place one directional cells for this purpose.
The code of our new puzzle will reside in file puzzle/myhero.py.
Our puzzle will have no constraints on the level configuration, so let's start to code it like this:
from . import *
class MyHeroPuzzle(Puzzle):
def assert_config(self):
pass
This puzzle includes special cell types like finish, portals and directional cells. This should be declared like this:
def has_finish(self):
return True
def has_portal(self):
return True
def has_dirs(self):
return True
The goal of our puzzle is to kill all enemies. This should be declared like this:
def is_goal_to_kill_enemies(self):
return True
Now it is a time to implement a random generation of floor sections (with enemies and powerups), directional cells and portals:
def generate_room(self):
self.set_area_from_config(default_size=(5, 5), request_odd_size=True, align_to_center=True)
num_floors = (self.area.size_y + 1) / 2
num_slots = self.area.size_x - 1
self.set_area_border_walls()
if self.area.x1 > self.room.x1:
self.map[self.area.x1 - 1, (self.area.y1 + self.area.y2) // 2] = CELL_FLOOR
for cell in self.area.cells:
if (cell[1] - self.area.y1) % 2 == 1:
if cell[0] != self.area.x1:
self.map[cell] = CELL_WALL
elif cell[0] == self.area.x1:
self.map[cell] = CELL_DIR_R
elif cell[0] == self.area.x2:
self.Globals.create_portal(cell, (self.area.x1, cell[1]))
else:
slot_type = randint(0, 2)
if slot_type == 0:
self.Globals.create_enemy(cell, randint(10, 50))
elif slot_type == 1:
op = choice('×÷+-')
factor = (2, 3)[randint(0, 1)] if op in ('×', '÷') else (50, 100)[randint(0, 1)]
drop_might.instantiate(cell, op, factor)
self.map[self.room.x1, self.room.y2] = CELL_FINISH
In the future we may want to add more useful features, like support for rooms (foor or nine), support for loading precreated maps, a solver like in some other puzzles and more. But for now this will be enough.
Here is complete myhero.py implementation:
from . import *
class MyHeroPuzzle(Puzzle):
def assert_config(self):
return bool(char.power)
def has_finish(self):
return True
def has_portal(self):
return True
def has_dirs(self):
return True
def is_goal_to_kill_enemies(self):
return True
def generate_room(self):
self.set_area_from_config(default_size=(5, 5), request_odd_size=True, align_to_center=True)
num_floors = (self.area.size_y + 1) / 2
num_slots = self.area.size_x - 1
self.set_area_border_walls()
if self.area.x1 > self.room.x1:
self.map[self.area.x1 - 1, (self.area.y1 + self.area.y2) // 2] = CELL_FLOOR
for cell in self.area.cells:
if (cell[1] - self.area.y1) % 2 == 1:
if cell[0] != self.area.x1:
self.map[cell] = CELL_WALL
elif cell[0] == self.area.x1:
self.map[cell] = CELL_DIR_R
elif cell[0] == self.area.x2:
self.Globals.create_portal(cell, (self.area.x1, cell[1]))
else:
slot_type = randint(0, 2)
if slot_type == 0:
self.Globals.create_enemy(cell, randint(10, 50))
elif slot_type == 1:
op = choice('×÷+-')
factor = (2, 3)[randint(0, 1)] if op in ('×', '÷') else (50, 100)[randint(0, 1)]
drop_might.instantiate(cell, op, factor)
self.map[self.room.x1, self.room.y2] = CELL_FINISH
Place this file myhero.py in puzzle/ directory.
Finally, add new puzzle level into file levels.py that includes "myhero_puzzle": {} line:
{
"n": 0.1,
"theme": "classic",
"music": "stoneage/08_the_golden_valley.mp3",
"char_power": 40,
"goal": "Complete MyHero puzzle",
"myhero_puzzle": {},
},
Finally run ./dungeon and enjoy your new puzzle.