Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ The UI draws a generated floor room by room, including the doors between the roo
and an arrow from a teleport room to its target. Every generated floor is kept,
so you can switch between them. A legend next to the floor shows the room types
and how often they appear on the current floor.
The status bar shows the seed and floor id of the shown floor
and its position in the history.
All actions are also available in the File, View and Floor menus.

| Key | Action |
|---|---|
| `Space` | Generate a new floor with the seed from the "Next seed" field |
| `Enter` in "Next seed" | Generate a new floor with the entered seed |
| `←` / `→` | Switch to the previous / next floor |
| `Space` | Generate a new floor with the seed from the "Next seed" field and the floor id from the "Floor" field |
| `Enter` in "Next seed" or "Floor" | Generate a new floor with the entered values |
| `Page Up` / `Page Down` | Increase / decrease the floor id in the "Floor" field |
| `←` / `→` | Go back / forward in the history of generated floors |
| `Ctrl+O` | Open a floor from a JSON file |
| `Ctrl+S` | Save the current floor |
| `Ctrl+Shift+S` | Save the current floor as a new file |
Expand Down Expand Up @@ -96,6 +99,7 @@ A negative floor id returns `422`.
{
"_width": 9,
"_height": 8,
"_floor_id": 2,
"_floor": {
"_seed": "2",
"_rooms": [
Expand Down Expand Up @@ -123,6 +127,7 @@ A negative floor id returns `422`.
| Field | Description |
|---|---|
| `_width`, `_height` | Size of the floor in rooms |
| `_floor_id` | Floor id the floor was generated for. Together with the seed it reproduces the floor |
| `_floor._seed` | Seed the floor was generated with |
| `_floor._rooms` | List of all rooms |
| `_id` | Id of the room, unique within the floor |
Expand Down
3 changes: 3 additions & 0 deletions src/floors/floor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def __init__(self, height: int, width: int, seed: str):
self._height: int = height
self._room_id: int = 0
self.seed: str = seed
# Id of the floor the rooms were generated for, unknown for old files
self.floor_id: Optional[int] = None

def top_left(self) -> Tuple[int, int]:
"""
Expand Down Expand Up @@ -99,6 +101,7 @@ def from_json(cls, json_string: str):
floor._rooms.append(room)
floor.add_to_floor_grid(room[0], room[1])
floor._room_id = max((room.get_id() for room in floor._rooms), default=-1) + 1
floor.floor_id = json_dict.get("_floor_id")
return floor

def add_to_floor_grid(self, x: int, y: int) -> None:
Expand Down
1 change: 1 addition & 0 deletions src/floors/tkinter_floor.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def from_floor(cls, floor: Floor, canvas, name: str):
name,
floor.seed,
)
tkinter_floor.floor_id = floor.floor_id
for room in floor.get_rooms():
tkinter_room: TkinterRoom
if isinstance(room, TeleportRoom):
Expand Down
55 changes: 30 additions & 25 deletions src/generators/floor_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,21 @@ def __init__(self, output_file_name: str) -> None:
self.current_floor_name = tk.StringVar()
self.current_floor_name.set(output_file_name)
self.current_floor_seed_var = tk.StringVar()
# Floor id of the shown floor, "?" for files without a floor id
self.current_floor_id_var = tk.StringVar()
# Position of the shown floor in the history, e.g. "2 / 3"
self.position = tk.StringVar()
self.next_seed = tk.StringVar()
self.next_seed.set(secrets.token_hex(16))

def add_floor(self, floor: TkinterFloor) -> None:
"""
Adds a floor to the floor manager.
Adds a floor to the floor manager and shows it.
@param floor: floor to add
"""
self._floors.append(floor)
self.current_floor_index = len(self._floors) - 1
self.current_floor_name.set(floor.name)
self.current_floor_seed_var.set(floor.seed)
self._update_position()
self._show_current_floor_info()

def get_current_floor(self) -> TkinterFloor:
"""
Expand All @@ -57,32 +58,36 @@ def set_current_floor_name(self, name: str) -> None:

def decrease_floor(self) -> None:
"""
Lowers the index of the current floor.
Shows the previous floor of the history.
"""
if self.current_floor_index > 0:
self.get_current_floor().stop_drawing()
self.current_floor_index -= 1
self._floors[self.current_floor_index].draw()
self.current_floor_name.set(self._floors[self.current_floor_index].name)
self.current_floor_seed_var.set(self.get_current_floor().seed)
self._update_position()
self._show_floor_at(self.current_floor_index - 1)

def increase_floor(self) -> None:
"""
Increases the index of the current floor.
"""
last_floor = len(self._floors) - 1
if self.current_floor_index < last_floor:
self.get_current_floor().stop_drawing()
self.current_floor_index += 1
current_floor = self._floors[self.current_floor_index]
current_floor.draw()
self.current_floor_name.set(current_floor.name)
self.current_floor_seed_var.set(self.get_current_floor().seed)
self._update_position()
Shows the next floor of the history.
"""
if self.current_floor_index < len(self._floors) - 1:
self._show_floor_at(self.current_floor_index + 1)

def _show_floor_at(self, index: int) -> None:
"""
Stops drawing the current floor and draws the floor at the given index.
@param index: index of the floor in the history
"""
self.get_current_floor().stop_drawing()
self.current_floor_index = index
self.get_current_floor().draw()
self._show_current_floor_info()

def _update_position(self) -> None:
def _show_current_floor_info(self) -> None:
"""
Updates the text that shows which floor is displayed.
Updates the texts that describe the current floor.
"""
self.position.set(f"Floor {self.current_floor_index + 1} / {len(self._floors)}")
floor = self.get_current_floor()
self.current_floor_name.set(floor.name)
self.current_floor_seed_var.set(floor.seed)
self.current_floor_id_var.set(
"?" if floor.floor_id is None else str(floor.floor_id)
)
self.position.set(f"{self.current_floor_index + 1} / {len(self._floors)}")
19 changes: 16 additions & 3 deletions src/generators/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,15 @@ def to_dict(self) -> dict:
Creates a dictionary representation of the generator object.
@return: dictionary with the size and the floor of the generator
"""
return {
generator_dict: dict = {
"_width": my_globals.FLOOR_WIDTH,
"_height": my_globals.FLOOR_HEIGHT,
"_floor": self._floor.to_dict(),
"_generated_by": "python",
}
if self._floor.floor_id is not None:
generator_dict["_floor_id"] = self._floor.floor_id
generator_dict["_floor"] = self._floor.to_dict()
generator_dict["_generated_by"] = "python"
return generator_dict

def to_json(self, indent: int = 1) -> str:
"""
Expand All @@ -70,6 +73,15 @@ def to_json(self, indent: int = 1) -> str:
"""
return json.dumps(self.to_dict(), indent=len(my_globals.BASE_INDENT) * indent)

def set_stage_id(self, stage_id: int) -> None:
"""
Sets the id of the floor that the next generation creates.
@param stage_id: id for the floor (must be 0 or greater)
"""
if stage_id < 0:
raise ValueError(f"stage_id must be 0 or greater, got {stage_id}")
self._stage_id = stage_id

def _seed_random(self, seed: str) -> None:
"""
Seeds the random number generator with the seed and the floor id,
Expand All @@ -83,6 +95,7 @@ def _create_floor(self) -> None:
Creates a new floor.
"""
self._floor = Floor(my_globals.FLOOR_HEIGHT, my_globals.FLOOR_WIDTH, self._seed)
self._floor.floor_id = self._stage_id

def _add_new_room(
self, new_room_tuple: Tuple[int, int], room_tuple_queue: deque
Expand Down
96 changes: 87 additions & 9 deletions src/generators/tkinter_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
from rooms.tkinter.legend_panel import LegendPanel
from utils import util_functions, globals as my_globals

# Highest floor id that can be entered in the floor field
_MAX_FLOOR_ID = 99


def _is_floor_id_input(value: str) -> bool:
"""
Checks if the text of the floor field is allowed while typing.
@param value: new text of the floor field
@return: True for an empty text or a number up to the highest floor id
"""
return value == "" or (value.isdigit() and int(value) <= _MAX_FLOOR_ID)


class TkinterGenerator(Generator):
"""
Expand Down Expand Up @@ -53,6 +65,8 @@ def __init__(
self._legend = LegendPanel(self._content)
self.show_legend = tk.BooleanVar(self._tk, value=True)
self._floor_manager = FloorManager(output_file_name)
# Floor id for the next generated floor, edited in the floor field
self.next_floor_id = tk.StringVar(self._tk, value=str(stage_id))
# The position changes whenever another floor is shown
self._floor_manager.position.trace_add(
"write", lambda *args: self._update_legend()
Expand All @@ -61,7 +75,9 @@ def __init__(

def _create_floor(self) -> None:
"""
Creates a new TkinterFloor and appends it to the floor queue.
Creates a new TkinterFloor.
The floor is added to the history once the generation has finished,
because a generation can create several floors until one fits.
"""
self._floor = TkinterFloor(
my_globals.FLOOR_HEIGHT,
Expand All @@ -70,18 +86,39 @@ def _create_floor(self) -> None:
my_globals.DEFAULT_FLOOR_NAME + my_globals.JSON_SUFFIX,
self._seed,
)
self._floor_manager.add_floor(self._floor)
self._floor.floor_id = self._stage_id

def _generate_and_draw_floor(self) -> None:
"""
Calls the generate method and the draw method of the floor.
"""
self._floor.stop_drawing()
self.set_stage_id(self._read_next_floor_id())
self.generate(self._floor_manager.next_seed.get())
self._floor_manager.next_seed.set(secrets.token_hex(16))
self._floor_manager.get_current_floor().draw_step_by_step()
self._floor = self._floor_manager.get_current_floor()

def _read_next_floor_id(self) -> int:
"""
Returns the floor id from the floor field.
An empty field is reset to the floor id of the last generation.
@return: floor id for the next floor
"""
try:
return int(self.next_floor_id.get())
except ValueError:
self.next_floor_id.set(str(self._stage_id))
return self._stage_id

def change_floor_id(self, step: int) -> None:
"""
Changes the floor id in the floor field.
@param step: amount to add to the floor id, negative to lower it
"""
floor_id = min(max(self._read_next_floor_id() + step, 0), _MAX_FLOOR_ID)
self.next_floor_id.set(str(floor_id))

def open(self) -> None:
"""
Shows a dialog for opening a file.
Expand Down Expand Up @@ -178,9 +215,10 @@ def _redraw_floor(self) -> None:

def generate(self, new_seed=None) -> None:
"""
Generates a new floor and updates the legend.
Generates a new floor, adds it to the history and updates the legend.
"""
super().generate(new_seed)
self._floor_manager.add_floor(self._floor)
self._update_legend()

def _update_legend(self) -> None:
Expand Down Expand Up @@ -233,10 +271,20 @@ def add_information_frame(self) -> None:
textvariable=self._floor_manager.current_floor_seed_var,
style="Muted.TLabel",
).pack(side=tk.LEFT)
ttk.Label(status, text=" · ", style="Muted.TLabel").pack(side=tk.LEFT)
ttk.Label(status, text=" · Floor ", style="Muted.TLabel").pack(side=tk.LEFT)
ttk.Label(
status, textvariable=self._floor_manager.position, style="Muted.TLabel"
status,
textvariable=self._floor_manager.current_floor_id_var,
style="Muted.TLabel",
).pack(side=tk.LEFT)
ttk.Label(status, text=" · ", style="Muted.TLabel").pack(side=tk.LEFT)
position_label = ttk.Label(
status, textvariable=self._floor_manager.position, style="Muted.TLabel"
)
position_label.pack(side=tk.LEFT)
_ToolTip(
position_label, tk.StringVar(self._tk, value="Position in the history")
)

def _increase_floor(self) -> None:
"""
Expand Down Expand Up @@ -278,13 +326,31 @@ def add_buttons(self) -> None:
button_next.pack(side=tk.LEFT)
# Right arrow to increase
self._tk.bind("<Right>", self._shortcut(lambda: self._increase_floor()))
# Floor id for the next floor
ttk.Label(button_frame, text="Floor", style="Muted.TLabel").pack(
side=tk.LEFT, padx=(20, 6)
)
self._floor_id_entry = ttk.Spinbox(
button_frame,
textvariable=self.next_floor_id,
from_=0,
to=_MAX_FLOOR_ID,
width=4,
validate="key",
validatecommand=(self._tk.register(_is_floor_id_input), "%P"),
)
self._floor_id_entry.bind("<Return>", lambda event: self._submit_seed())
self._floor_id_entry.bind("<Escape>", lambda event: self._tk.focus_set())
self._floor_id_entry.pack(side=tk.LEFT)
self._tk.bind("<Prior>", self._shortcut(lambda: self.change_floor_id(1)))
self._tk.bind("<Next>", self._shortcut(lambda: self.change_floor_id(-1)))
# Next seed entry
next_seed_label = ttk.Label(
button_frame, text="Next seed", style="Muted.TLabel"
)
next_seed_label.pack(side=tk.LEFT, padx=(20, 6))
next_seed_label.pack(side=tk.LEFT, padx=(16, 6))
self._next_seed_entry = ttk.Entry(
button_frame, textvariable=self._floor_manager.next_seed, width=34
button_frame, textvariable=self._floor_manager.next_seed, width=30
)
# Enter generates a floor with the entered seed, Escape leaves the field.
# Both give the focus back so that the shortcuts work again.
Expand Down Expand Up @@ -358,11 +424,23 @@ def create_menu_bar(self) -> None:
floor_menu.add_command(
label="Generate", command=self._generate_and_draw_floor, accelerator="Space"
)
floor_menu.add_separator()
floor_menu.add_command(
label="Higher Floor ID",
command=lambda: self.change_floor_id(1),
accelerator="Page Up",
)
floor_menu.add_command(
label="Lower Floor ID",
command=lambda: self.change_floor_id(-1),
accelerator="Page Down",
)
floor_menu.add_separator()
floor_menu.add_command(
label="Previous Floor", command=self._decrease_floor, accelerator="←"
label="Back", command=self._decrease_floor, accelerator="←"
)
floor_menu.add_command(
label="Next Floor", command=self._increase_floor, accelerator="→"
label="Forward", command=self._increase_floor, accelerator="→"
)
menu_bar.add_cascade(label="Floor", menu=floor_menu)

Expand Down
Loading
Loading