nfx
1 month ago
1 changed files with 0 additions and 274 deletions
@ -1,274 +0,0 @@ |
|||||||
""" |
|
||||||
LEVEL EDITOR(prototype) |
|
||||||
The file that allows to create and edit levels |
|
||||||
THIS PROGRAM IS DEPRECATED, USE LVL_EDIT.PY TO EDIT LEVELS |
|
||||||
""" |
|
||||||
import pyxel |
|
||||||
import json |
|
||||||
import shutil |
|
||||||
|
|
||||||
CELL_SIZE = 20 |
|
||||||
GRID_X = 11 |
|
||||||
GRID_Y = 6 |
|
||||||
|
|
||||||
WIN_X = CELL_SIZE*GRID_X |
|
||||||
WIN_Y = CELL_SIZE*GRID_Y |
|
||||||
|
|
||||||
|
|
||||||
def clamp(n: int, a: int, b: int): |
|
||||||
""" |
|
||||||
:param n: The number to be clamped |
|
||||||
:param a: The minimum limit |
|
||||||
:param b: The maximum limit |
|
||||||
:return: The clamped number |
|
||||||
Returns the provided number n clamped between a and b |
|
||||||
""" |
|
||||||
if n < a: |
|
||||||
return a |
|
||||||
if n > b: |
|
||||||
return b |
|
||||||
return n |
|
||||||
|
|
||||||
|
|
||||||
def get_file_content(lvl: int) -> list: |
|
||||||
""" |
|
||||||
:param lvl: (int) The id of the file to read |
|
||||||
:return: (list) The level content of the file |
|
||||||
Reads a level file content and returns its data |
|
||||||
""" |
|
||||||
file = open(f"lvl/{lvl}.json", "r", encoding="utf-8") |
|
||||||
decoder = json.JSONDecoder() |
|
||||||
content = decoder.decode(file.read()) |
|
||||||
file.close() |
|
||||||
return content |
|
||||||
|
|
||||||
|
|
||||||
def can_load(lvl: int) -> bool: |
|
||||||
""" |
|
||||||
:param lvl: (int) The id of the file to check for |
|
||||||
:return: Returns if the level file exists and can be loaded |
|
||||||
""" |
|
||||||
try: |
|
||||||
file = get_file_content(lvl) |
|
||||||
return len(file) == GRID_Y |
|
||||||
except FileNotFoundError: |
|
||||||
return False |
|
||||||
|
|
||||||
|
|
||||||
class LevelManager: |
|
||||||
def __init__(self): |
|
||||||
self.level = 0 # the id of the currently loaded stage |
|
||||||
self.cache = [] # the current stage state is stored there |
|
||||||
|
|
||||||
self.tileTypes = { # helps with tile identifiers |
|
||||||
"empty": 0, |
|
||||||
"wall": 1, |
|
||||||
"player": 3, |
|
||||||
"exit": 2, |
|
||||||
"switch_blue": 4, |
|
||||||
"s_wall_blue": 5 |
|
||||||
} |
|
||||||
|
|
||||||
def load(self, lvl: int): |
|
||||||
""" |
|
||||||
:param lvl: (int) the level id |
|
||||||
|
|
||||||
Reloads the game using the specified level |
|
||||||
""" |
|
||||||
self.level = lvl |
|
||||||
self.cache = get_file_content(lvl) |
|
||||||
|
|
||||||
def save(self, lvl: int): |
|
||||||
""" |
|
||||||
:param lvl: (int) the level id |
|
||||||
|
|
||||||
Saves the cache under the specified level id |
|
||||||
""" |
|
||||||
file = open(f"lvl/{lvl}.json", "w", encoding="utf-8") |
|
||||||
encoder = json.JSONEncoder() |
|
||||||
file.write(encoder.encode(self.cache)) |
|
||||||
file.close() |
|
||||||
|
|
||||||
def update_tile(self, position: tuple[int, int], val: int): |
|
||||||
""" |
|
||||||
:param position: (tuple) The position of the tile to modify |
|
||||||
:param val: (int) The new value of the tile |
|
||||||
|
|
||||||
Updates the specified tile from the cache to the specified value |
|
||||||
""" |
|
||||||
self.cache[position[1]][position[0]] = val |
|
||||||
|
|
||||||
def get_tile(self, position: tuple[int, int]) -> int: |
|
||||||
""" |
|
||||||
:param position: (tuple) the position of the requested tile |
|
||||||
:return: (int) the value of the tile |
|
||||||
Fetches the current value of the specified tile or -1 if out of range |
|
||||||
""" |
|
||||||
if not self.is_in_range(position): |
|
||||||
return -1 |
|
||||||
return self.cache[position[1]][position[0]] |
|
||||||
|
|
||||||
def is_in_range(self, position: tuple[int, int]) -> bool: |
|
||||||
""" |
|
||||||
:param position: (tuple) The position that will be checked |
|
||||||
:return: (bool) indicates whether the said position is in map bounds |
|
||||||
|
|
||||||
Checks if the given position is within the map range(a level should have been loaded first) |
|
||||||
""" |
|
||||||
if len(self.cache) == 0: |
|
||||||
return False |
|
||||||
if position[0] < 0 or position[1] < 0: |
|
||||||
return False |
|
||||||
if position[1] > GRID_Y-1: |
|
||||||
return False |
|
||||||
if position[0] > GRID_X-1: |
|
||||||
return False |
|
||||||
return True |
|
||||||
|
|
||||||
def move_tile(self, tile_a, tile_b): |
|
||||||
""" |
|
||||||
:param tile_a: (tuple) The position of the tile to be moved |
|
||||||
:param tile_b: (tuple) The position of the target tile |
|
||||||
Moves the content of tile_a to tile_b(overwrites tile_b and sets tile_a to 0) |
|
||||||
This is meant for player movement |
|
||||||
""" |
|
||||||
if tile_a != tile_b: |
|
||||||
content = self.get_tile(tile_a) |
|
||||||
self.update_tile(tile_b, content) |
|
||||||
self.update_tile(tile_a, self.tileTypes['empty']) |
|
||||||
|
|
||||||
def find_object(self, elt: int) -> tuple[int, int]: |
|
||||||
""" |
|
||||||
:param elt: (int) The type of element to find on the map(use self.tileTypes) |
|
||||||
:return: (tuple) The position of the tile(or (-1, -1) if not found) |
|
||||||
Finds the first instance of the provided element on the board, and returns its position |
|
||||||
""" |
|
||||||
for y in range(GRID_Y): |
|
||||||
for x in range(GRID_X): |
|
||||||
if self.cache[y][x] == elt: |
|
||||||
return x, y |
|
||||||
return -1, -1 |
|
||||||
|
|
||||||
def find_player(self): # finds the player in the stage grid and returns its coordinates |
|
||||||
return self.find_object(self.tileTypes['player']) |
|
||||||
|
|
||||||
|
|
||||||
def input_cmd(): |
|
||||||
cmd_txt = input("cmd => ").split(" ") |
|
||||||
if cmd_txt[0] == "load" and len(cmd_txt) > 1 and cmd_txt[1].isnumeric() and can_load(int(cmd_txt[1])): |
|
||||||
print(f"Now editing level {cmd_txt[1]}") |
|
||||||
runner.level.load(int(cmd_txt[1])) |
|
||||||
elif cmd_txt[0] == "create" and len(cmd_txt) > 1 and cmd_txt[1].isnumeric(): |
|
||||||
print(f"Now editing level {cmd_txt[1]}") |
|
||||||
shutil.copyfile("lvl_template.json", f'lvl/{cmd_txt[1]}.json') |
|
||||||
runner.level.load(int(cmd_txt[1])) |
|
||||||
elif cmd_txt[0] == "save": |
|
||||||
runner.level.save(runner.level.level) |
|
||||||
print(f"Saved successfully") |
|
||||||
else: |
|
||||||
print("Invalid Command") |
|
||||||
|
|
||||||
|
|
||||||
class Editor: |
|
||||||
def __init__(self): |
|
||||||
self.level = LevelManager() # An instance that manages level data and navigating |
|
||||||
self.isInit = False # Safety attribute that prevents init from running twice |
|
||||||
self.cursor = (0, 0) # Describes the mouse position(snapped to grid) |
|
||||||
self.toolToggle = 0 |
|
||||||
|
|
||||||
self.toolToggleRotation = [ |
|
||||||
self.level.tileTypes['wall'], |
|
||||||
self.level.tileTypes['exit'], |
|
||||||
self.level.tileTypes['switch_blue'], |
|
||||||
self.level.tileTypes['s_wall_blue'] |
|
||||||
] |
|
||||||
|
|
||||||
self.element_display = [ |
|
||||||
"empty", |
|
||||||
"wall", |
|
||||||
"exit", |
|
||||||
"player", |
|
||||||
"blue switch", |
|
||||||
"blue switch wall" |
|
||||||
] |
|
||||||
|
|
||||||
def on_click(self): # toggles the presence of an element at cursor position |
|
||||||
tile = self.level.get_tile(self.cursor) |
|
||||||
if tile != self.toolToggleRotation[self.toolToggle] and tile != self.level.tileTypes['player']: |
|
||||||
self.level.update_tile(self.cursor, self.toolToggleRotation[self.toolToggle]) |
|
||||||
elif tile != self.level.tileTypes['player']: |
|
||||||
self.level.update_tile(self.cursor, self.level.tileTypes['empty']) |
|
||||||
|
|
||||||
def move_player(self): # moves the player to the cursor(overwrites said tile) |
|
||||||
if self.cursor[0] >= 0 and self.cursor[1] >= 0: |
|
||||||
plr_pos = self.level.find_player() |
|
||||||
self.level.move_tile(plr_pos, self.cursor) |
|
||||||
|
|
||||||
def tool_rotation(self): # Changes the element that will be placed on click |
|
||||||
next_index = self.toolToggle + 1 |
|
||||||
if next_index >= len(self.toolToggleRotation): |
|
||||||
self.toolToggle = 0 |
|
||||||
else: |
|
||||||
self.toolToggle = next_index |
|
||||||
new_element = self.toolToggleRotation[self.toolToggle] |
|
||||||
print(f"Now placing {self.element_display[new_element]}") |
|
||||||
|
|
||||||
# manages controls and tools |
|
||||||
def upd(self): |
|
||||||
mx, my = pyxel.mouse_x, pyxel.mouse_y |
|
||||||
self.cursor = (mx//CELL_SIZE, my//CELL_SIZE) |
|
||||||
if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT): |
|
||||||
self.on_click() |
|
||||||
elif pyxel.btnp(pyxel.KEY_E): |
|
||||||
self.move_player() |
|
||||||
elif pyxel.btnp(pyxel.KEY_A): |
|
||||||
self.tool_rotation() |
|
||||||
elif pyxel.btnp(pyxel.KEY_C): |
|
||||||
input_cmd() |
|
||||||
|
|
||||||
# utility methods that draws the cursor to the mouse position |
|
||||||
def draw_cursor(self): |
|
||||||
x, y = self.cursor |
|
||||||
center_x = x*CELL_SIZE+CELL_SIZE//2 |
|
||||||
center_y = y*CELL_SIZE+CELL_SIZE//2 |
|
||||||
pyxel.circb(center_x, center_y, 5, 4) |
|
||||||
|
|
||||||
# self-explanatory, manages what's seen on screen |
|
||||||
def display(self): |
|
||||||
pyxel.cls(0) |
|
||||||
for y in range(GRID_Y): |
|
||||||
for x in range(GRID_X): |
|
||||||
tile = self.level.get_tile((x, y)) |
|
||||||
if tile == self.level.tileTypes['wall']: |
|
||||||
pyxel.rect(x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, CELL_SIZE, 7) |
|
||||||
elif tile == self.level.tileTypes['exit']: |
|
||||||
pyxel.rect(x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, CELL_SIZE, 11) |
|
||||||
elif tile == self.level.tileTypes['player']: |
|
||||||
pyxel.circ(x*CELL_SIZE+CELL_SIZE//2, y*CELL_SIZE+CELL_SIZE//2, CELL_SIZE//2-1, 8) |
|
||||||
elif tile == self.level.tileTypes['switch_blue']: |
|
||||||
pyxel.circ(x*CELL_SIZE+CELL_SIZE//2, y*CELL_SIZE+CELL_SIZE//2, CELL_SIZE//3-1, 12) |
|
||||||
elif tile == self.level.tileTypes['s_wall_blue']: |
|
||||||
pyxel.rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, 12) |
|
||||||
self.draw_cursor() |
|
||||||
|
|
||||||
def init(self, lvl: int): # opens the editor window and loads the specified stage |
|
||||||
if not self.isInit: |
|
||||||
self.isInit = True |
|
||||||
self.level.load(lvl) |
|
||||||
pyxel.init(WIN_X, WIN_Y, title="ice_walker editor") |
|
||||||
pyxel.mouse(True) |
|
||||||
pyxel.run(self.upd, self.display) |
|
||||||
|
|
||||||
|
|
||||||
def start_editor(): |
|
||||||
runner.init(int(lvl_id)) |
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": |
|
||||||
lvl_id = input("Please enter the id of the level to load: ") |
|
||||||
if lvl_id.isnumeric(): |
|
||||||
print(f"Now editing level {lvl_id}") |
|
||||||
runner = Editor() |
|
||||||
start_editor() |
|
||||||
else: |
|
||||||
print("Invalid level id provided, please rerun and try again") |
|
Loading…
Reference in new issue