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 that allow movement to the right but not back to the left 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 one-directional cells. This should be declared like this:
def has_finish(self):
return True
def has_portal(self):
return True
def has_odirs(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
It's time to implement random generation of floor sections, which include enemies and powerups. Each floor section is enclosed by walls. A one-way cell to the right (CELL_ODIRR) serves as an entry lock, and a portal at the end of the section returns the player to its starting point:
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_ODIRR
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_odirs(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_ODIRR
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.