Compare commits

..

2 Commits

5 changed files with 633 additions and 388 deletions

View File

@@ -6,10 +6,11 @@ from utils.preload import button_texture, button_hovered_texture
from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar
class FileManager(arcade.gui.UIAnchorLayout): class FileManager(arcade.gui.UIAnchorLayout):
def __init__(self, width, allowed_extensions): def __init__(self, width, height, size_hint, allowed_extensions):
super().__init__(size_hint=(0.95, 0.875), vertical=False) super().__init__(size_hint=size_hint, vertical=False)
self.filemanager_width = width self.filemanager_width = width
self.filemanager_height = height
self.current_directory = os.path.expanduser("~") self.current_directory = os.path.expanduser("~")
self.allowed_extensions = allowed_extensions self.allowed_extensions = allowed_extensions
@@ -36,9 +37,9 @@ class FileManager(arcade.gui.UIAnchorLayout):
self.bottom_box = self.add(arcade.gui.UIBoxLayout(space_between=5), anchor_x="center", anchor_y="bottom", align_y=5) self.bottom_box = self.add(arcade.gui.UIBoxLayout(space_between=5), anchor_x="center", anchor_y="bottom", align_y=5)
self.filename_label = self.bottom_box.add(arcade.gui.UILabel(text="Filename:", font_name="Roboto", font_size=17)) self.filename_label = self.bottom_box.add(arcade.gui.UILabel(text="Filename:", font_name="Roboto", font_size=17))
self.filename_input = self.bottom_box.add(arcade.gui.UIInputText(width=self.filemanager_width * 0.35, height=self.filemanager_width * 0.02).with_border(color=arcade.color.WHITE)) self.filename_input = self.bottom_box.add(arcade.gui.UIInputText(width=self.filemanager_width * 0.35, height=self.filemanager_height * 0.05).with_border(color=arcade.color.WHITE))
self.submit_button = self.bottom_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="Submit", style=button_style, width=self.filemanager_width * 0.5, height=self.filemanager_width * 0.025)) self.submit_button = self.bottom_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="Submit", style=button_style, width=self.filemanager_width * 0.5, height=self.filemanager_height * 0.05))
self.submit_button.on_click = lambda event: self.submit(self.current_directory) self.submit_button.on_click = lambda event: self.submit(self.current_directory)
self.submit_button.visible = False self.submit_button.visible = False
@@ -55,9 +56,7 @@ class FileManager(arcade.gui.UIAnchorLayout):
def submit(self, content): def submit(self, content):
self.submitted_content = content if self.mode == "import" else f"{content}/{self.filename_input.text}" self.submitted_content = content if self.mode == "import" else f"{content}/{self.filename_input.text}"
self.disable()
def get_content(self, directory): def get_content(self, directory):
if not directory in self.content_cache or time.perf_counter() - self.content_cache[directory][-1] >= 30: if not directory in self.content_cache or time.perf_counter() - self.content_cache[directory][-1] >= 30:
try: try:
@@ -109,19 +108,16 @@ class FileManager(arcade.gui.UIAnchorLayout):
self.current_directory_label.text = self.current_directory self.current_directory_label.text = self.current_directory
self.file_buttons.append(self.files_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="Go up", style=button_style, width=self.filemanager_width / 1.5))) self.file_buttons.append(self.files_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text="Go up", style=button_style, width=self.filemanager_width / 1.5, height=self.filemanager_height * 0.05,)))
self.file_buttons[-1].on_click = lambda event, directory=self.current_directory: self.change_directory(os.path.dirname(directory)) self.file_buttons[-1].on_click = lambda event, directory=self.current_directory: self.change_directory(os.path.dirname(directory))
for file in self.get_content(self.current_directory): for file in self.get_content(self.current_directory):
self.file_buttons.append(self.files_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=file, style=button_style, width=self.filemanager_width / 1.5))) self.file_buttons.append(self.files_box.add(arcade.gui.UITextureButton(texture=button_texture, texture_hovered=button_hovered_texture, text=file, style=button_style, width=self.filemanager_width / 1.5, height=self.filemanager_height * 0.05,)))
if os.path.isdir(f"{self.current_directory}/{file}"): if os.path.isdir(f"{self.current_directory}/{file}"):
self.file_buttons[-1].on_click = lambda event, directory=f"{self.current_directory}/{file}": self.change_directory(directory) self.file_buttons[-1].on_click = lambda event, directory=f"{self.current_directory}/{file}": self.change_directory(directory)
else: else:
self.file_buttons[-1].on_click = lambda event, file=f"{self.current_directory}/{file}": self.submit(file) self.file_buttons[-1].on_click = lambda event, file=f"{self.current_directory}/{file}": self.submit(file)
def disable(self):
self.parent.parent.disable() # The FileManager UIManager. self.parent is the FileManager UIAnchorLayout
def change_directory(self, directory): def change_directory(self, directory):
if directory.startswith("//"): # Fix / paths if directory.startswith("//"): # Fix / paths
directory = directory[1:] directory = directory[1:]

View File

@@ -1,10 +1,12 @@
import arcade, arcade.gui, pyglet, random, json import arcade, arcade.gui, pyglet, random, json
from utils.preload import SPRITE_TEXTURES, button_texture, button_hovered_texture from dataclasses import asdict
from utils.constants import button_style, DO_RULES, IF_RULES, SHAPES, ALLOWED_INPUT
from game.rules import RuleUI from utils.preload import SPRITE_TEXTURES, button_texture, button_hovered_texture
from game.sprites import BaseShape, Rectangle, Circle, Triangle from utils.constants import button_style, DO_RULES, IF_RULES, SPRITES, ALLOWED_INPUT
from game.rules import RuleUI, Block, VarBlock
from game.sprites import BaseShape, Rectangle, Circle, Triangle, TexturedRectangle
from game.file_manager import FileManager from game.file_manager import FileManager
class Game(arcade.gui.UIView): class Game(arcade.gui.UIView):
@@ -21,7 +23,7 @@ class Game(arcade.gui.UIView):
self.rules_box = RuleUI(self.window) self.rules_box = RuleUI(self.window)
self.file_manager = FileManager(self.window.width * 0.95, [".json"]).with_border() self.file_manager = FileManager(self.window.width * 0.95, self.window.height * 0.875, (0.95, 0.875), [".json"]).with_border()
self.ui_selector_box = self.anchor.add(arcade.gui.UIBoxLayout(vertical=False, space_between=self.window.width / 100), anchor_x="left", anchor_y="bottom", align_y=5, align_x=self.window.width / 100) self.ui_selector_box = self.anchor.add(arcade.gui.UIBoxLayout(vertical=False, space_between=self.window.width / 100), anchor_x="left", anchor_y="bottom", align_y=5, align_x=self.window.width / 100)
self.add_ui_selector("Simulation", lambda event: self.simulation()) self.add_ui_selector("Simulation", lambda event: self.simulation())
@@ -35,9 +37,20 @@ class Game(arcade.gui.UIView):
self.y_gravity = self.settings.get("default_y_gravity", 5) self.y_gravity = self.settings.get("default_y_gravity", 5)
self.triggered_events = [] self.triggered_events = []
self.rulesets, self.if_rules = self.rules_box.get_rulesets() self.rulesets = self.rules_box.rulesets
self.sprites_box = arcade.gui.UIAnchorLayout(size_hint=(0.95, 0.9)) self.sprite_add_filemanager = FileManager(self.window.width * 0.9, self.window.height * 0.75, (0.9, 0.75), [".png", ".jpg", ".jpeg", ".bmp", ".gif"])
self.sprite_add_ui = arcade.gui.UIBoxLayout(size_hint=(0.95, 0.9), space_between=10)
self.sprite_add_ui.add(arcade.gui.UILabel(text="Add Sprite", font_size=24, text_color=arcade.color.WHITE))
self.sprite_add_ui.add(arcade.gui.UILabel(text="Sprite Name:", font_size=18, text_color=arcade.color.WHITE))
self.sprite_name_input = self.sprite_add_ui.add(arcade.gui.UIInputText(width=self.window.width * 0.4, height=self.window.height * 0.05).with_border(color=arcade.color.WHITE))
self.sprite_add_ui.add(arcade.gui.UILabel(text="Select a texture for the sprite:", font_size=18, text_color=arcade.color.WHITE))
self.sprite_add_ui.add(self.sprite_add_filemanager, anchor_x="center", anchor_y="bottom", align_y=25)
self.sprites_ui = arcade.gui.UIAnchorLayout(size_hint=(0.95, 0.9))
self.sprite_types = SPRITES
self.shapes = [] self.shapes = []
self.shape_batch = pyglet.graphics.Batch() self.shape_batch = pyglet.graphics.Batch()
@@ -49,6 +62,7 @@ class Game(arcade.gui.UIView):
button.on_click = on_click button.on_click = on_click
def move_x(self, a, shape): def move_x(self, a, shape):
a = float(a)
if isinstance(shape, Triangle): if isinstance(shape, Triangle):
shape.x += a shape.x += a
shape.x2 += a shape.x2 += a
@@ -57,6 +71,7 @@ class Game(arcade.gui.UIView):
shape.x += a shape.x += a
def move_y(self, a, shape): def move_y(self, a, shape):
a = float(a)
if isinstance(shape, Triangle): if isinstance(shape, Triangle):
shape.y += a shape.y += a
shape.y2 += a shape.y2 += a
@@ -65,6 +80,7 @@ class Game(arcade.gui.UIView):
shape.y += a shape.y += a
def change_x(self, a, shape): def change_x(self, a, shape):
a = float(a)
if isinstance(shape, Triangle): if isinstance(shape, Triangle):
offset_x2 = shape.x2 - shape.x offset_x2 = shape.x2 - shape.x
offset_x3 = shape.x3 - shape.x offset_x3 = shape.x3 - shape.x
@@ -76,6 +92,7 @@ class Game(arcade.gui.UIView):
shape.x = a shape.x = a
def change_y(self, a, shape): def change_y(self, a, shape):
a = float(a)
if isinstance(shape, Triangle): if isinstance(shape, Triangle):
offset_y2 = shape.y2 - shape.y offset_y2 = shape.y2 - shape.y
offset_y3 = shape.y3 - shape.y offset_y3 = shape.y3 - shape.y
@@ -87,18 +104,22 @@ class Game(arcade.gui.UIView):
shape.y = a shape.y = a
def change_x_velocity(self, a, shape): def change_x_velocity(self, a, shape):
a = float(a)
shape.x_velocity = a shape.x_velocity = a
self.triggered_events.append(["x_velocity_change", {"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color}]) self.triggered_events.append(["x_velocity_change", {"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color}])
def change_y_velocity(self, a, shape): def change_y_velocity(self, a, shape):
a = float(a)
shape.y_velocity = a shape.y_velocity = a
self.triggered_events.append(["y_velocity_change", {"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color}]) self.triggered_events.append(["y_velocity_change", {"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color}])
def change_x_gravity(self, a): def change_x_gravity(self, a):
a = float(a)
self.x_gravity = a self.x_gravity = a
self.triggered_events.append(["x_gravity_change", {}]) self.triggered_events.append(["x_gravity_change", {}])
def change_y_gravity(self, a): def change_y_gravity(self, a):
a = float(a)
self.y_gravity = a self.y_gravity = a
self.triggered_events.append(["y_gravity_change", {}]) self.triggered_events.append(["y_gravity_change", {}])
@@ -113,6 +134,7 @@ class Game(arcade.gui.UIView):
shape.delete() shape.delete()
def change_size(self, a, shape): def change_size(self, a, shape):
a = float(a)
if isinstance(shape, Circle): if isinstance(shape, Circle):
shape.radius = a shape.radius = a
elif isinstance(shape, Rectangle): elif isinstance(shape, Rectangle):
@@ -143,46 +165,66 @@ class Game(arcade.gui.UIView):
elif shape_type == "triangle": elif shape_type == "triangle":
self.shapes.append(Triangle(x, y, x + 10, y, x + 5, y + 10, color=arcade.color.WHITE, batch=self.shape_batch)) self.shapes.append(Triangle(x, y, x + 10, y, x + 5, y + 10, color=arcade.color.WHITE, batch=self.shape_batch))
else:
self.shapes.append(TexturedRectangle(shape_type, img=SPRITE_TEXTURES.get(shape_type, SPRITE_TEXTURES["rectangle"]), x=x, y=y, batch=self.shape_batch))
shape = self.shapes[-1] shape = self.shapes[-1]
self.triggered_events.append(["spawn", {"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color}]) self.triggered_events.append(["spawn", {"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color}])
def morph(self, a, shape): def add_sprite(self):
old_shape_x, old_shape_y, old_shape_size, old_shape_color = shape.x, shape.y, shape.shape_size, shape.shape_color self.disable_previous()
self.destroy(shape)
if a == "circle": self.mode = "sprite_add"
self.shapes.append(Circle(old_shape_x, old_shape_y, old_shape_size, color=getattr(arcade.color, old_shape_color), batch=self.shape_batch))
elif a == "rectangle": self.anchor.add(self.sprite_add_ui, anchor_x="center", anchor_y="center")
self.shapes.append(Rectangle(old_shape_x, old_shape_y, width=old_shape_size, height=old_shape_size, color=getattr(arcade.color, old_shape_color), batch=self.shape_batch))
def check_selection(delta_time):
elif a == "triangle": if self.sprite_add_filemanager.submitted_content:
self.shapes.append(Triangle(old_shape_x, old_shape_y, old_shape_x + old_shape_size, old_shape_y, old_shape_x + int(old_shape_size / 2), old_shape_y + old_shape_size, color=getattr(arcade.color, old_shape_color), batch=self.shape_batch)) texture = arcade.load_texture(self.sprite_add_filemanager.submitted_content)
SPRITE_TEXTURES[self.sprite_name_input.text] = texture
SPRITES[self.sprite_name_input.text] = self.sprite_add_filemanager.submitted_content
self.sprites_grid.clear()
for n, shape in enumerate(SPRITES):
row, col = n % 8, n // 8
box = self.sprites_grid.add(arcade.gui.UIBoxLayout(), row=row, column=col)
box.add(arcade.gui.UILabel(text=shape, font_size=16, text_color=arcade.color.WHITE))
box.add(arcade.gui.UIImage(texture=SPRITE_TEXTURES[shape], width=self.window.width / 15, height=self.window.width / 15))
self.anchor.remove(self.sprite_add_ui)
arcade.unschedule(check_selection)
arcade.schedule(check_selection, 0.1)
def on_show_view(self): def on_show_view(self):
super().on_show_view() super().on_show_view()
self.sprites_box.add(arcade.gui.UILabel(text="Sprites", font_size=24, text_color=arcade.color.WHITE), anchor_x="center", anchor_y="top") self.sprites_ui.add(arcade.gui.UILabel(text="Sprites", font_size=24, text_color=arcade.color.WHITE), anchor_x="center", anchor_y="top")
self.sprites_grid = self.sprites_box.add(arcade.gui.UIGridLayout(columns=8, row_count=8, align="left", vertical_spacing=10, horizontal_spacing=10, size_hint=(0.95, 0.85)), anchor_x="center", anchor_y="center").with_border() self.sprites_grid = self.sprites_ui.add(arcade.gui.UIGridLayout(columns=8, row_count=8, align="left", vertical_spacing=10, horizontal_spacing=10, size_hint=(0.95, 0.85), width=self.window.width * 0.95, height=self.window.height * 0.85), anchor_x="center", anchor_y="center")
for n, shape in enumerate(SHAPES): for n, shape in enumerate(SPRITES):
row, col = n % 8, n // 8 row, col = n % 8, n // 8
box = self.sprites_grid.add(arcade.gui.UIBoxLayout(), row=row, column=col) box = self.sprites_grid.add(arcade.gui.UIBoxLayout(), row=row, column=col)
box.add(arcade.gui.UILabel(text=shape, font_size=16, text_color=arcade.color.WHITE)) box.add(arcade.gui.UILabel(text=shape, font_size=16, text_color=arcade.color.WHITE))
box.add(arcade.gui.UIImage(texture=SPRITE_TEXTURES[shape], width=self.window.width / 15, height=self.window.width / 15)) box.add(arcade.gui.UIImage(texture=SPRITE_TEXTURES[shape], width=self.window.width / 15, height=self.window.width / 15))
self.triggered_events.append(["game_launch", {}]) add_sprite_button = self.sprites_ui.add(arcade.gui.UITextureButton(text="Add Sprite", width=self.window.width / 2, height=self.window.height / 10, texture=button_texture, texture_hovered=button_hovered_texture, style=button_style), anchor_x="center", anchor_y="bottom", align_y=10)
add_sprite_button.on_click = lambda event: self.add_sprite()
def get_rule_values(self, rule_dict, rule_values, event_args): self.triggered_events.append(["start", {}])
args = [rule_values[f"{user_var}_{n}"] for n, user_var in enumerate(rule_dict["user_vars"])]
def get_vars(self, rule_dict, vars, event_args):
args = [vars[n].value for n in range(len(rule_dict["user_vars"]))]
return args + [event_args[var] for var in rule_dict.get("vars", []) if not var in rule_dict["user_vars"]] return args + [event_args[var] for var in rule_dict.get("vars", []) if not var in rule_dict["user_vars"]]
def check_rule(self, rule_dict, rule_values, event_args): def check_rule(self, rule_dict, vars, event_args):
return rule_dict["func"](*self.get_rule_values(rule_dict, rule_values, event_args)) return rule_dict["func"](*self.get_vars(rule_dict, vars, event_args))
def get_action_function(self, action_dict): def get_action_function(self, action_dict):
ACTION_FUNCTION_DICT = { ACTION_FUNCTION_DICT = {
@@ -200,36 +242,91 @@ class Game(arcade.gui.UIView):
"change_y_velocity": self.change_y_velocity, "change_y_velocity": self.change_y_velocity,
"change_color": self.change_color, "change_color": self.change_color,
"change_size": self.change_size, "change_size": self.change_size,
"destroy": self.destroy, "destroy": self.destroy
"morph": self.morph
} }
} }
return ACTION_FUNCTION_DICT[action_dict["type"]][action_dict["name"]] return ACTION_FUNCTION_DICT[action_dict["type"]][action_dict["name"]]
def run_do_rule(self, rule_dict, rule_values, event_args): def run_do_rule(self, rule_dict, vars, event_args):
self.get_action_function(rule_dict["action"])(*self.get_rule_values(rule_dict, rule_values, event_args)) self.get_action_function(rule_dict["action"])(*self.get_vars(rule_dict, vars, event_args))
def recursive_execute_rule(self, rule, trigger_args):
for child_rule in rule.children:
child_rule_type = child_rule.rule_type
if child_rule_type == "for": # TODO: Extend this when i add more FOR loop types
if child_rule.rule == "every_shape":
for shape in self.shapes:
event_args = trigger_args.copy()
event_args.update({"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color})
self.recursive_execute_rule(child_rule, event_args)
elif child_rule_type == "if":
if self.check_rule(IF_RULES[child_rule.rule], child_rule.vars, trigger_args):
self.recursive_execute_rule(child_rule, trigger_args)
elif child_rule_type == "do":
self.run_do_rule(DO_RULES[child_rule.rule], child_rule.vars, trigger_args)
def get_max_rule_num(self):
max_num = -1
def recurse(block: Block):
nonlocal max_num
max_num = max(max_num, block.rule_num)
for child in block.children:
recurse(child)
for block in self.rulesets.values():
recurse(block)
return max_num
def on_update(self, delta_time): def on_update(self, delta_time):
if self.mode == "import" and self.file_manager.submitted_content: if self.mode == "import" and self.file_manager.submitted_content:
with open(self.file_manager.submitted_content, "r") as file: with open(self.file_manager.submitted_content, "r") as file:
data = json.load(file) data = json.load(file)
if not data or not "rulesets" in data or not "rule_values" in data: self.triggered_events = []
self.rulesets = {}
if not data:
self.add_widget(arcade.gui.UIMessageBox(message_text="Invalid file. Could not import rules.", width=self.window.width * 0.5, height=self.window.height * 0.25)) self.add_widget(arcade.gui.UIMessageBox(message_text="Invalid file. Could not import rules.", width=self.window.width * 0.5, height=self.window.height * 0.25))
return return
for rule_num, ruleset in data["rulesets"].items():
kwargs = ruleset
kwargs["children"] = [Block(**child) for child in ruleset["children"]]
kwargs["vars"] = [VarBlock(**var) for var in ruleset["vars"]]
block = Block(**kwargs)
self.rulesets[rule_num] = block
self.rule_values = data["rule_values"] self.sprite_types = data.get("sprites", SPRITES)
self.triggered_events = [] for sprite_name, sprite_path in self.sprite_types.items():
if not sprite_name in SPRITE_TEXTURES:
SPRITE_TEXTURES[sprite_name] = arcade.load_texture(sprite_path)
self.rules_box.rulesets = self.rulesets
self.rules_box.current_rule_num = self.get_max_rule_num() + 1
self.rules_box.block_renderer.refresh()
# TODO: add rule loading here self.rules()
if self.mode == "export" and self.file_manager.submitted_content: if self.mode == "export" and self.file_manager.submitted_content:
with open(self.file_manager.submitted_content, "w") as file: with open(self.file_manager.submitted_content, "w") as file:
file.write(json.dumps({ file.write(json.dumps(
"rulesets": self.rulesets, {
"rule_values": self.rule_values "rules": {
}, indent=4)) rule_num: asdict(block) for rule_num, block in self.rulesets.items()
},
"sprites": self.sprite_types
},
indent=4))
self.add_widget(arcade.gui.UIMessageBox(message_text="Rules and Sprites exported successfully!", width=self.window.width * 0.5, height=self.window.height * 0.25))
if not self.mode == "simulation": if not self.mode == "simulation":
return return
@@ -239,31 +336,11 @@ class Game(arcade.gui.UIView):
while len(self.triggered_events) > 0: while len(self.triggered_events) > 0:
trigger, trigger_args = self.triggered_events.pop(0) trigger, trigger_args = self.triggered_events.pop(0)
# In the new version, a DO rule's dependencies are the ruleset itself which trigger it for rule_num, rule in self.rulesets.items():
# Since there could be multiple IFs that depend on each other, we need to get the entrypoint values first and then interpret the tree. if not rule.rule_type == "trigger" or not trigger == rule.rule:
event_args = trigger_args continue
if_rule_values = {} self.recursive_execute_rule(rule, trigger_args)
for if_rule in self.if_rules:
if_rule_dict = IF_RULES[if_rule[0]]
if "shape_type" in if_rule_dict["user_vars"]:
is_true = False
for shape in self.shapes:
if is_true:
break
event_args = trigger_args.copy()
if not "event_shape_type" in trigger_args:
event_args.update({"event_shape_type": shape.shape_type, "shape_size": shape.shape_size, "shape_x": shape.x, "shape_y": shape.y, "shape": shape, "shape_color": shape.shape_color})
is_true = self.check_rule(if_rule_dict, if_rule[1], trigger_args)
if_rule_values[if_rule[2]] = is_true
else:
event_args = trigger_args.copy()
if_rule_values[if_rule[2]] = self.check_rule(if_rule_dict, if_rule[1], trigger_args)
for shape in self.shapes: for shape in self.shapes:
for shape_b in self.shapes: for shape_b in self.shapes:
@@ -282,7 +359,7 @@ class Game(arcade.gui.UIView):
def on_key_press(self, symbol, modifiers): def on_key_press(self, symbol, modifiers):
if symbol == arcade.key.ESCAPE: if symbol == arcade.key.ESCAPE:
self.main_exit() self.main_exit()
elif self.mode == "simulation" and symbol in [ord(key) for key in ALLOWED_INPUT]: elif self.mode == "simulation" and symbol in [ord(key) if len(key) == 1 else getattr(arcade.key, key.upper()) for key in ALLOWED_INPUT]:
self.triggered_events.append(["on_input", {"event_key": chr(symbol)}]) self.triggered_events.append(["on_input", {"event_key": chr(symbol)}])
def on_mouse_press(self, x, y, button, modifiers): def on_mouse_press(self, x, y, button, modifiers):
@@ -314,7 +391,9 @@ class Game(arcade.gui.UIView):
elif self.mode == "rules": elif self.mode == "rules":
self.anchor.remove(self.rules_box) self.anchor.remove(self.rules_box)
elif self.mode == "sprites": elif self.mode == "sprites":
self.anchor.remove(self.sprites_box) self.anchor.remove(self.sprites_ui)
elif self.mode == "sprite_add":
self.anchor.remove(self.sprite_add_ui)
self.anchor.trigger_full_render() self.anchor.trigger_full_render()
@@ -346,12 +425,14 @@ class Game(arcade.gui.UIView):
self.mode = "sprites" self.mode = "sprites"
self.anchor.add(self.sprites_box, anchor_x="center", anchor_y="top") self.anchor.add(self.sprites_ui, anchor_x="center", anchor_y="top")
def simulation(self): def simulation(self):
self.disable_previous() self.disable_previous()
self.x_gravity = self.settings.get("default_x_gravity", 0)
self.rulesets, self.if_rules = self.rules_box.get_rulesets() self.y_gravity = self.settings.get("default_y_gravity", 5)
self.triggered_events = []
self.rulesets = self.rules_box.rulesets
self.mode = "simulation" self.mode = "simulation"
def main_exit(self): def main_exit(self):

View File

@@ -1,83 +1,44 @@
from utils.constants import ( from utils.constants import (
DO_RULES, DO_RULES,
IF_RULES, IF_RULES,
NON_COMPATIBLE_WHEN,
NON_COMPATIBLE_DO_WHEN,
VAR_NAMES,
VAR_DEFAULT,
TRIGGER_RULES, TRIGGER_RULES,
FOR_RULES, FOR_RULES,
NEEDS_SHAPE,
PROVIDES_SHAPE,
button_style, button_style,
slider_style,
dropdown_style,
DO_COLOR, DO_COLOR,
IF_COLOR, IF_COLOR,
FOR_COLOR, FOR_COLOR,
TRIGGER_COLOR TRIGGER_COLOR,
RULE_DEFAULTS,
VAR_TYPES
) )
from typing import List from typing import List
from utils.preload import button_texture, button_hovered_texture, trash_bin from utils.preload import button_texture, button_hovered_texture, trash_bin
from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar
from dataclasses import dataclass, field from dataclasses import dataclass, field
import arcade, arcade.gui, pyglet, random import arcade, arcade.gui, pyglet, random, re
IF_KEYS = tuple(IF_RULES.keys()) def get_rule_dict(rule_type):
DO_KEYS = tuple(DO_RULES.keys())
BAD_WHEN = {tuple(sorted(pair)) for pair in NON_COMPATIBLE_WHEN}
BAD_DO_WHEN = {tuple(pair) for pair in NON_COMPATIBLE_DO_WHEN}
def generate_rule(rule_type):
if rule_type == "if": if rule_type == "if":
return random.choice(IF_KEYS) return IF_RULES
elif rule_type == "for":
return FOR_RULES
elif rule_type == "trigger":
return TRIGGER_RULES
elif rule_type == "do": elif rule_type == "do":
return random.choice(DO_KEYS) return DO_RULES
def get_rule_description(rule_type, rule): @dataclass
if rule_type == "if": class VarBlock:
return IF_RULES[rule]["description"] x: float
if rule_type == "for": y: float
return FOR_RULES[rule]["description"] label: str
if rule_type == "trigger": var_type: str
return TRIGGER_RULES[rule]["description"] connected_rule_num: str
if rule_type == "do": value: str | int
return DO_RULES[rule]["description"]
def per_widget_height(height, widget_count):
return height // widget_count
def get_rule_defaults(rule_type):
if rule_type == "if":
return {
rule_key: (
rule_dict["description"].format_map(
{
VAR_NAMES[n]: VAR_NAMES[n]
for n, variable in enumerate(rule_dict["user_vars"])
}
),
{
VAR_NAMES[n]: VAR_DEFAULT[variable]
for n, variable in enumerate(rule_dict["user_vars"])
},
)
for rule_key, rule_dict in IF_RULES.items()
}
elif rule_type == "do":
return {
rule_key: (
rule_dict["description"].format_map(
{
VAR_NAMES[n]: VAR_NAMES[n]
for n, variable in enumerate(rule_dict["user_vars"])
}
),
{
VAR_NAMES[n]: VAR_DEFAULT[variable]
for n, variable in enumerate(rule_dict["user_vars"])
},
)
for rule_key, rule_dict in DO_RULES.items()
}
@dataclass @dataclass
class Block: class Block:
@@ -87,17 +48,18 @@ class Block:
rule_type: str rule_type: str
rule: str rule: str
rule_num: int rule_num: int
rule_values: dict[str, int | str] vars: List["VarBlock"] = field(default_factory=list)
children: List["Block"] = field(default_factory=list) children: List["Block"] = field(default_factory=list)
class BlockRenderer: class BlockRenderer:
def __init__(self, blocks: List[Block], indent: int = 10): def __init__(self, blocks: List[Block], indent: int = 12):
self.blocks = blocks self.blocks = blocks
self.indent = indent self.indent = indent
self.shapes = pyglet.graphics.Batch() self.shapes = pyglet.graphics.Batch()
self.shapes_by_rule_num = {} self.shapes_by_rule_num = {}
self.text_objects = [] self.text_objects = []
self.text_by_rule_num = {} self.text_by_rule_num = {}
self.var_widgets = {}
self.refresh() self.refresh()
def refresh(self): def refresh(self):
@@ -113,12 +75,89 @@ class BlockRenderer:
self.shapes_by_rule_num = {} self.shapes_by_rule_num = {}
self.text_objects = [] self.text_objects = []
self.text_by_rule_num = {} self.text_by_rule_num = {}
self.var_widgets = {}
for b in self.blocks.values(): for b in self.blocks.values():
self._build_block(b, b.x, b.y) self._build_block(b, b.x, b.y)
def _build_var_ui(self, var: VarBlock, x: int, y: int, rule_num: int) -> tuple:
var_width = max(60, len(str(var.value)) * 8 + 20)
var_height = 24
var_color = (255, 255, 255)
var_rect = pyglet.shapes.BorderedRectangle(
x, y - var_height // 2, var_width, var_height,
2, var_color, arcade.color.BLACK, batch=self.shapes
)
var_text = pyglet.text.Label(
text=str(var.value),
x=x + var_width // 2,
y=y,
color=arcade.color.BLACK,
font_size=10,
anchor_x='center',
anchor_y='center'
)
if rule_num not in self.shapes_by_rule_num:
self.shapes_by_rule_num[rule_num] = []
if rule_num not in self.text_by_rule_num:
self.text_by_rule_num[rule_num] = []
if rule_num not in self.var_widgets:
self.var_widgets[rule_num] = []
self.shapes_by_rule_num[rule_num].append(var_rect)
self.text_by_rule_num[rule_num].append(var_text)
self.text_objects.append(var_text)
self.var_widgets[rule_num].append({
'var': var,
'rect': var_rect,
'text': var_text,
'x': x,
'y': y,
'width': var_width,
'height': var_height
})
return var_width, var_height
def _build_block_with_vars(self, b: Block, x: int, y: int) -> None:
lx, ly = x, y - 42
current_x = lx + 14
current_y = ly + 28
pattern = r'(?:^| )([a-z])(?= |$)'
parts = re.split(pattern, b.label)
var_index = 0
for i, part in enumerate(parts):
if i % 2 == 0:
if part:
text_obj = pyglet.text.Label(
text=part,
x=current_x,
y=current_y - 3,
color=arcade.color.BLACK,
font_size=12,
weight="bold"
)
self.text_objects.append(text_obj)
self.text_by_rule_num[b.rule_num].append(text_obj)
current_x += len(part) * 12
else:
if var_index < len(b.vars):
var = b.vars[var_index]
var_width, var_height = self._build_var_ui(
var, current_x, current_y, b.rule_num
)
current_x += var_width + 10
var_index += 1
def _build_block(self, b: Block, x: int, y: int) -> int: def _build_block(self, b: Block, x: int, y: int) -> int:
is_wrap = b.rule_type != "do" is_wrap = b.rule_type != "do"
h, w = 42, 280 h, w = 42, 380
if b.rule_type == "if": if b.rule_type == "if":
color = IF_COLOR color = IF_COLOR
@@ -129,7 +168,7 @@ class BlockRenderer:
elif b.rule_type == "for": elif b.rule_type == "for":
color = FOR_COLOR color = FOR_COLOR
lx, ly = x, y - h lx, ly = x, y - h
if b.rule_num not in self.shapes_by_rule_num: if b.rule_num not in self.shapes_by_rule_num:
self.shapes_by_rule_num[b.rule_num] = [] self.shapes_by_rule_num[b.rule_num] = []
@@ -139,21 +178,31 @@ class BlockRenderer:
rect = pyglet.shapes.BorderedRectangle(lx, ly, w, h, 2, color, arcade.color.BLACK, batch=self.shapes) rect = pyglet.shapes.BorderedRectangle(lx, ly, w, h, 2, color, arcade.color.BLACK, batch=self.shapes)
self.shapes_by_rule_num[b.rule_num].append(rect) self.shapes_by_rule_num[b.rule_num].append(rect)
text_obj = pyglet.text.Label(text=b.label, x=lx + 10, y=ly + 20, color=arcade.color.BLACK, font_size=12, weight="bold") if b.vars:
self.text_objects.append(text_obj) self._build_block_with_vars(b, x, y)
self.text_by_rule_num[b.rule_num].append(text_obj) else:
text_obj = pyglet.text.Label(
text=b.label,
x=lx + 7,
y=ly + 20,
color=arcade.color.BLACK,
font_size=12,
weight="bold"
)
self.text_objects.append(text_obj)
self.text_by_rule_num[b.rule_num].append(text_obj)
ny = ly next_y = ly
if is_wrap: if is_wrap:
iy = ny iy = next_y
for child in b.children: for child in b.children:
child.x = lx + self.indent + 5 child.x = lx + self.indent + 5
child.y = iy - 2 child.y = iy
iy = self._build_block(child, lx + self.indent + 5, iy - 2) iy = self._build_block(child, lx + self.indent + 5, iy)
bar_h = ny - iy bar_h = next_y - iy
bar_filled = pyglet.shapes.Rectangle(lx + 2, iy + 2, self.indent, bar_h, color, batch=self.shapes) bar_filled = pyglet.shapes.Rectangle(lx + 2, iy + 2, self.indent, bar_h, color, batch=self.shapes)
line1 = pyglet.shapes.Line(lx, ny, lx, iy, 2, arcade.color.BLACK, batch=self.shapes) line1 = pyglet.shapes.Line(lx, next_y, lx, iy, 2, arcade.color.BLACK, batch=self.shapes)
bottom = pyglet.shapes.BorderedRectangle(lx, iy - 8, w, 24, 2, color, arcade.color.BLACK, batch=self.shapes) bottom = pyglet.shapes.BorderedRectangle(lx, iy - 8, w, 24, 2, color, arcade.color.BLACK, batch=self.shapes)
self.shapes_by_rule_num[b.rule_num].extend([bar_filled, line1, bottom]) self.shapes_by_rule_num[b.rule_num].extend([bar_filled, line1, bottom])
@@ -161,19 +210,36 @@ class BlockRenderer:
return iy - 24 return iy - 24
else: else:
for child in b.children: for child in b.children:
ny = self._build_block(child, lx, ny) child.x = lx
return ny child.y = next_y
ly = self._build_block(child, lx, next_y)
return ly - 16
def move_block(self, x, y, rule_num): def move_block(self, x, y, rule_num):
for element in self.shapes_by_rule_num[rule_num] + self.text_by_rule_num[rule_num]: for element in self.shapes_by_rule_num[rule_num] + self.text_by_rule_num[rule_num]:
element.x += x element.x += x
element.y += y element.y += y
if rule_num in self.var_widgets:
for widget in self.var_widgets[rule_num]:
widget['x'] += x
widget['y'] += y
block = self._find_block(rule_num) block = self._find_block(rule_num)
for child in block.children: for child in block.children:
self.move_block(x, y, child.rule_num) self.move_block(x, y, child.rule_num)
def get_var_at_position(self, x, y):
for rule_num, widgets in self.var_widgets.items():
for widget in widgets:
wx, wy = widget['x'], widget['y']
ww, wh = widget['width'], widget['height']
if (wx <= x <= wx + ww and
wy - wh // 2 <= y <= wy + wh // 2):
return widget['var'], rule_num
return None, None
def _find_block(self, rule_num): def _find_block(self, rule_num):
if rule_num in self.blocks: if rule_num in self.blocks:
return self.blocks[rule_num] return self.blocks[rule_num]
@@ -198,6 +264,101 @@ class BlockRenderer:
for t in self.text_objects: for t in self.text_objects:
t.draw() t.draw()
class VarEditDialog(arcade.gui.UIAnchorLayout):
def __init__(self, var: VarBlock, on_save, on_cancel):
super().__init__()
self.var = var
self.on_save_callback = on_save
self.on_cancel_callback = on_cancel
self.background = self.add(
arcade.gui.UISpace(color=(0, 0, 0, 180)),
anchor_x="center",
anchor_y="center"
)
dialog_box = arcade.gui.UIBoxLayout(
space_between=10,
width=300,
height=200
)
dialog_box.with_padding(all=20)
dialog_box.with_background(color=(60, 60, 80))
dialog_box.add(arcade.gui.UILabel(
text=f"Edit {var.label}",
font_size=16,
text_color=arcade.color.WHITE
))
if var.var_type == "variable":
self.input_field = arcade.gui.UIInputText(
text=str(var.value),
width=260,
height=40
)
dialog_box.add(self.input_field)
elif var.var_type in ["shape_type", "target_type", "color", "key_input", "comparison"]:
from utils.constants import VAR_OPTIONS
options = VAR_OPTIONS[var.var_type]
self.dropdown = arcade.gui.UIDropdown(
default=str(var.value),
options=options,
width=260,
height=40,
style=dropdown_style
)
dialog_box.add(self.dropdown)
elif var.var_type == "size":
self.slider = arcade.gui.UISlider(
value=int(var.value),
min_value=1,
max_value=200,
width=260,
height=40,
style=slider_style
)
dialog_box.add(self.slider)
button_layout = arcade.gui.UIBoxLayout(vertical=False, space_between=10)
save_btn = arcade.gui.UIFlatButton(
text="Save",
width=125,
height=40
)
save_btn.on_click = self._on_save
cancel_btn = arcade.gui.UIFlatButton(
text="Cancel",
width=125,
height=40
)
cancel_btn.on_click = self._on_cancel
button_layout.add(save_btn)
button_layout.add(cancel_btn)
dialog_box.add(button_layout)
self.add(dialog_box, anchor_x="center", anchor_y="center")
def _on_save(self, event):
if hasattr(self, 'input_field'):
try:
self.var.value = int(self.input_field.text)
except ValueError:
self.var.value = self.input_field.text
elif hasattr(self, 'dropdown'):
self.var.value = self.dropdown.value
elif hasattr(self, 'slider'):
self.var.value = int(self.slider.value)
self.on_save_callback()
def _on_cancel(self, event):
self.on_cancel_callback()
class RuleUI(arcade.gui.UIAnchorLayout): class RuleUI(arcade.gui.UIAnchorLayout):
def __init__(self, window: arcade.Window): def __init__(self, window: arcade.Window):
super().__init__(size_hint=(1, 0.875)) super().__init__(size_hint=(1, 0.875))
@@ -205,6 +366,7 @@ class RuleUI(arcade.gui.UIAnchorLayout):
self.window = window self.window = window
self.current_rule_num = 0 self.current_rule_num = 0
self.rule_values = {} self.rule_values = {}
self.var_edit_dialog = None
self.rulesets: dict[int, Block] = {} self.rulesets: dict[int, Block] = {}
@@ -230,7 +392,7 @@ class RuleUI(arcade.gui.UIAnchorLayout):
self.create_sidebar = self.add(arcade.gui.UIBoxLayout(size_hint=(0.15, 1), vertical=False, space_between=5), anchor_x="left", anchor_y="bottom") self.create_sidebar = self.add(arcade.gui.UIBoxLayout(size_hint=(0.15, 1), vertical=False, space_between=5), anchor_x="left", anchor_y="bottom")
self.scroll_area = UIScrollArea(size_hint=(0.95, 1)) # center on screen self.scroll_area = UIScrollArea(size_hint=(0.95, 1)) # center on screen
self.scroll_area.scroll_speed = -50 self.scroll_area.scroll_speed = 0
self.create_sidebar.add(self.scroll_area) self.create_sidebar.add(self.scroll_area)
self.scrollbar = UIScrollBar(self.scroll_area) self.scrollbar = UIScrollBar(self.scroll_area)
@@ -239,74 +401,49 @@ class RuleUI(arcade.gui.UIAnchorLayout):
self.create_box = self.scroll_area.add(arcade.gui.UIBoxLayout(space_between=10)) self.create_box = self.scroll_area.add(arcade.gui.UIBoxLayout(space_between=10))
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 100)) self.add_rule_create_box("trigger")
self.create_box.add(arcade.gui.UILabel(text="Trigger Rules", font_size=18)) self.add_rule_create_box("if")
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 200)) self.add_rule_create_box("do")
for trigger_rule, trigger_rule_data in TRIGGER_RULES.items(): self.add_rule_create_box("for")
create_button = self.create_box.add(arcade.gui.UITextureButton(text=trigger_rule_data["description"].format_map({
"a": "a",
"b": "b",
"c": "c"
}), width=self.window.width * 0.125, multiline=True, height=self.window.height * 0.05, style=button_style, texture=button_texture, texture_hovered=button_hovered_texture))
create_button.on_click = lambda event, trigger_rule=trigger_rule: self.add_rule("trigger", trigger_rule)
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 100))
self.create_box.add(arcade.gui.UILabel(text="IF Rules", font_size=18))
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 200))
for if_rule, if_rule_data in IF_RULES.items():
create_button = self.create_box.add(arcade.gui.UITextureButton(text=if_rule_data["description"].format_map({
"a": "a",
"b": "b",
"c": "c"
}), width=self.window.width * 0.135, multiline=True, height=self.window.height * 0.05, style=button_style, texture=button_texture, texture_hovered=button_hovered_texture))
create_button.on_click = lambda event, if_rule=if_rule: self.add_rule("if", if_rule)
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 100))
self.create_box.add(arcade.gui.UILabel(text="DO Rules", font_size=18))
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 200))
for do_rule, do_rule_data in DO_RULES.items():
create_button = self.create_box.add(arcade.gui.UITextureButton(text=do_rule_data["description"].format_map({
"a": "a",
"b": "b",
"c": "c"
}), width=self.window.width * 0.135, multiline=True, height=self.window.height * 0.05, style=button_style, texture=button_texture, texture_hovered=button_hovered_texture))
create_button.on_click = lambda event, do_rule=do_rule: self.add_rule("do", do_rule)
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 100))
self.create_box.add(arcade.gui.UILabel(text="For Rules", font_size=18))
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 200))
for for_rule, for_rule_data in FOR_RULES.items():
create_button = self.create_box.add(arcade.gui.UITextureButton(text=for_rule_data["description"].format_map({
"a": "a",
"b": "b",
"c": "c"
}), width=self.window.width * 0.135, multiline=True, height=self.window.height * 0.05, style=button_style, texture=button_texture, texture_hovered=button_hovered_texture))
create_button.on_click = lambda event, for_rule=for_rule: self.add_rule("for", for_rule)
self.trash_spritelist = arcade.SpriteList() self.trash_spritelist = arcade.SpriteList()
self.trash_sprite = trash_bin self.trash_sprite = trash_bin
self.trash_sprite.scale = 0.5 self.trash_sprite.scale = 0.5
self.trash_sprite.position = (self.window.width * 0.9, self.window.height * 0.2) self.trash_sprite.position = (self.window.width * 0.9, self.window.height * 0.2)
self.trash_spritelist.append(self.trash_sprite) self.trash_spritelist.append(self.trash_sprite)
def get_rulesets(self): def add_rule_create_box(self, rule_type):
# TODO: remove this self.create_box.add(arcade.gui.UISpace(height=self.window.height / 100))
return [], [] self.create_box.add(arcade.gui.UILabel(text=f"{rule_type.capitalize()} Rules", font_size=18))
self.create_box.add(arcade.gui.UISpace(height=self.window.height / 200))
for rule in get_rule_dict(rule_type):
create_button = self.create_box.add(arcade.gui.UITextureButton(text=RULE_DEFAULTS[rule_type][rule][0], width=self.window.width * 0.135, multiline=True, height=self.window.height * 0.05, style=button_style, texture=button_texture, texture_hovered=button_hovered_texture))
create_button.on_click = lambda event, rule=rule: self.add_rule(rule_type, rule)
def generate_pos(self): def generate_pos(self):
return random.randint( return random.randint(
self.window.width * 0.1, int(self.window.width * 0.9) self.window.width * 0.1, int(self.window.width * 0.9)
), random.randint(self.window.height * 0.1, int(self.window.height * 0.7)) ), random.randint(self.window.height * 0.1, int(self.window.height * 0.7))
def add_rule(self, rule_type, force=None): def add_rule(self, rule_type, rule):
rule = force or generate_rule(rule_type) rule_dict = get_rule_dict(rule_type)[rule]
rule_box = Block( rule_box = Block(
*self.generate_pos(), *self.generate_pos(),
get_rule_description(rule_type, rule), RULE_DEFAULTS[rule_type][rule][0],
rule_type, rule_type,
rule, rule,
self.current_rule_num, self.current_rule_num,
{}, [
VarBlock(
*self.generate_pos(),
VAR_TYPES[var_type],
var_type,
self.current_rule_num,
RULE_DEFAULTS[rule_type][rule][1][n]
)
for n, var_type in enumerate(rule_dict["user_vars"])
],
[] []
) )
@@ -323,11 +460,14 @@ class RuleUI(arcade.gui.UIAnchorLayout):
self.trash_spritelist.draw() self.trash_spritelist.draw()
def drag_n_drop_check(self, blocks): def drag_n_drop_check(self, blocks):
if self.dragged_rule_ui.rule_type == "trigger":
return
for block in blocks: for block in blocks:
if block == self.dragged_rule_ui: if block == self.dragged_rule_ui or (self.dragged_rule_ui.rule in NEEDS_SHAPE and block.rule not in PROVIDES_SHAPE):
continue continue
if arcade.LBWH(block.x, block.y - 44, 280, 44).intersection(arcade.LBWH(self.dragged_rule_ui.x, self.dragged_rule_ui.y - 44, 280, 44)): if arcade.LBWH(block.x, block.y - 44, 380, 44).intersection(arcade.LBWH(self.dragged_rule_ui.x, self.dragged_rule_ui.y - 44, 380, 44)):
block.children.append(self.dragged_rule_ui) block.children.append(self.dragged_rule_ui)
del self.rulesets[self.dragged_rule_ui.rule_num] del self.rulesets[self.dragged_rule_ui.rule_num]
self.block_renderer.refresh() self.block_renderer.refresh()
@@ -351,7 +491,7 @@ class RuleUI(arcade.gui.UIAnchorLayout):
continue continue
projected_vec = self.camera.unproject((event.x, event.y)) projected_vec = self.camera.unproject((event.x, event.y))
if arcade.LBWH(block.x, block.y - 44, 280, 44).point_in_rect((projected_vec.x, projected_vec.y)): if arcade.LBWH(block.x, block.y - 44, 380, 44).point_in_rect((projected_vec.x, projected_vec.y)):
if block not in list(self.rulesets.values()): # its children if block not in list(self.rulesets.values()): # its children
self.remove_from_parent(block, list(self.rulesets.values())) self.remove_from_parent(block, list(self.rulesets.values()))
self.block_renderer.refresh() self.block_renderer.refresh()
@@ -361,6 +501,10 @@ class RuleUI(arcade.gui.UIAnchorLayout):
self.press_check(event, block.children) self.press_check(event, block.children)
def on_event(self, event): def on_event(self, event):
if self.var_edit_dialog:
super().on_event(event)
return
super().on_event(event) super().on_event(event)
if isinstance(event, arcade.gui.UIMouseDragEvent): if isinstance(event, arcade.gui.UIMouseDragEvent):
@@ -371,13 +515,33 @@ class RuleUI(arcade.gui.UIAnchorLayout):
self.block_renderer.move_block(event.dx, event.dy, self.dragged_rule_ui.rule_num) self.block_renderer.move_block(event.dx, event.dy, self.dragged_rule_ui.rule_num)
elif isinstance(event, arcade.gui.UIMousePressEvent): elif isinstance(event, arcade.gui.UIMousePressEvent):
projected_vec = self.camera.unproject((event.x, event.y))
var, _ = self.block_renderer.get_var_at_position(projected_vec.x, projected_vec.y)
if var:
self.open_var_edit_dialog(var)
return
self.press_check(event, list(self.rulesets.values())) self.press_check(event, list(self.rulesets.values()))
elif isinstance(event, arcade.gui.UIMouseReleaseEvent): elif isinstance(event, arcade.gui.UIMouseReleaseEvent):
if self.dragged_rule_ui: if self.dragged_rule_ui:
block_vec = self.camera.unproject((self.dragged_rule_ui.x, self.dragged_rule_ui.y)) block_screen_pos = self.camera.project((self.dragged_rule_ui.x, self.dragged_rule_ui.y))
if self.trash_sprite.rect.intersection(arcade.LBWH(block_vec.x, block_vec.y, 280, 44)) and not self.trash_sprite._current_keyframe_index == self.trash_sprite.animation.num_frames - 1:
del self.rulesets[self.dragged_rule_ui.rule_num] block_rect = arcade.LBWH(block_screen_pos[0], block_screen_pos[1], 380, 44)
trash_rect = arcade.LBWH(
self.trash_sprite.center_x - self.trash_sprite.width / 2,
self.trash_sprite.center_y - self.trash_sprite.height / 2,
self.trash_sprite.width,
self.trash_sprite.height
)
if block_rect.intersection(trash_rect):
self.remove_from_parent(self.dragged_rule_ui, list(self.rulesets.values()))
if self.dragged_rule_ui.rule_num in self.rulesets:
del self.rulesets[self.dragged_rule_ui.rule_num]
self.dragged_rule_ui = None self.dragged_rule_ui = None
self.block_renderer.refresh() self.block_renderer.refresh()
return return
@@ -386,11 +550,29 @@ class RuleUI(arcade.gui.UIAnchorLayout):
self.dragged_rule_ui = None self.dragged_rule_ui = None
def open_var_edit_dialog(self, var: VarBlock):
def on_save():
self.close_var_edit_dialog()
self.block_renderer.refresh()
def on_cancel():
self.close_var_edit_dialog()
self.var_edit_dialog = VarEditDialog(var, on_save, on_cancel)
self.add(self.var_edit_dialog)
def close_var_edit_dialog(self):
if self.var_edit_dialog:
self.remove(self.var_edit_dialog)
self.var_edit_dialog = None
self.trigger_full_render()
def on_update(self, dt): def on_update(self, dt):
if self.dragged_rule_ui: if self.dragged_rule_ui:
block_vec = self.camera.unproject((self.dragged_rule_ui.x, self.dragged_rule_ui.y)) block_screen_pos = self.camera.project((self.dragged_rule_ui.x, self.dragged_rule_ui.y))
if self.trash_sprite.rect.intersection(arcade.LBWH(block_vec.x, block_vec.y, 280, 44)) and not self.trash_sprite._current_keyframe_index == self.trash_sprite.animation.num_frames - 1: if self.trash_sprite.rect.intersection(arcade.LBWH(block_screen_pos[0], block_screen_pos[1], 380, 44)) and not self.trash_sprite._current_keyframe_index == self.trash_sprite.animation.num_frames - 1:
self.trash_sprite.update_animation() self.trash_sprite.update_animation()
else: else:
self.trash_sprite.time = 0 self.trash_sprite.time = 0
self.trash_sprite.update_animation() self.trash_sprite.update_animation()

View File

@@ -106,12 +106,11 @@ class Circle(pyglet.shapes.Circle, BaseShape):
return not (has_neg and has_pos) return not (has_neg and has_pos)
class Rectangle(pyglet.shapes.Rectangle, BaseShape):
def __init__(self, *args, **kwargs): class BaseRectangle(BaseShape):
super().__init__(*args, **kwargs) def __init__(self):
BaseShape.__init__(self) super().__init__()
self.shape_type = "rectangle"
@property @property
def shape_size(self): def shape_size(self):
return self.width return self.width
@@ -181,6 +180,18 @@ class Rectangle(pyglet.shapes.Rectangle, BaseShape):
return (ccw(x1, y1, x3, y3, x4, y4) != ccw(x2, y2, x3, y3, x4, y4) and return (ccw(x1, y1, x3, y3, x4, y4) != ccw(x2, y2, x3, y3, x4, y4) and
ccw(x1, y1, x2, y2, x3, y3) != ccw(x1, y1, x2, y2, x4, y4)) ccw(x1, y1, x2, y2, x3, y3) != ccw(x1, y1, x2, y2, x4, y4))
class Rectangle(pyglet.shapes.Rectangle, BaseRectangle):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
BaseRectangle.__init__(self)
self.shape_type = "rectangle"
class TexturedRectangle(pyglet.sprite.Sprite, BaseRectangle):
def __init__(self, shape_type, *args, **kwargs):
super().__init__(*args, **kwargs)
BaseRectangle.__init__(self)
self.shape_type = shape_type
class Triangle(pyglet.shapes.Triangle, BaseShape): class Triangle(pyglet.shapes.Triangle, BaseShape):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@@ -1,12 +1,21 @@
import arcade.color import os
import arcade.color, operator
from arcade.types import Color from arcade.types import Color
from arcade.gui.widgets.buttons import UITextureButtonStyle, UIFlatButtonStyle from arcade.gui.widgets.buttons import UITextureButtonStyle, UIFlatButtonStyle
from arcade.gui.widgets.slider import UISliderStyle from arcade.gui.widgets.slider import UISliderStyle
SHAPES = ["rectangle", "circle", "triangle"] # Get the directory where this module is located
_module_dir = os.path.dirname(os.path.abspath(__file__))
_assets_dir = os.path.join(os.path.dirname(_module_dir), 'assets')
SPRITES = {
os.path.splitext(file_name)[0]: os.path.join(_assets_dir, 'graphics', 'sprites', file_name)
for file_name in os.listdir(os.path.join(_assets_dir, 'graphics', 'sprites'))
}
VAR_NAMES = ["a", "b", "c", "d", "e", "f", "g"] VAR_NAMES = ["a", "b", "c", "d", "e", "f", "g"]
ALLOWED_INPUT = ["a", "b", "c", "d", "e", "q", "w", "s", "t"] ALLOWED_INPUT = ["a", "b", "c", "d", "e", "q", "w", "s", "t", "space", "left", "right", "up", "down"]
TRIGGER_COLOR = (255, 204, 102) TRIGGER_COLOR = (255, 204, 102)
DO_COLOR = (102, 178, 255) DO_COLOR = (102, 178, 255)
@@ -29,9 +38,18 @@ COLORS = [
COMPARISONS = [">", ">=", "<", "<=", "==", "!="] COMPARISONS = [">", ">=", "<", "<=", "==", "!="]
OPS = {
">": operator.gt,
"<": operator.lt,
">=": operator.ge,
"<=": operator.le,
"==": operator.eq,
"!=": operator.ne,
}
VAR_DEFAULT = { VAR_DEFAULT = {
"shape_type": SHAPES[0], "shape_type": "rectangle",
"target_type": SHAPES[1], "target_type": "circle",
"variable": 0, "variable": 0,
"color": "WHITE", "color": "WHITE",
"size": 10, "size": 10,
@@ -40,8 +58,8 @@ VAR_DEFAULT = {
} }
VAR_OPTIONS = { VAR_OPTIONS = {
"shape_type": SHAPES, "shape_type": SPRITES,
"target_type": SHAPES, "target_type": SPRITES,
"variable": (-700, 700), "variable": (-700, 700),
"color": COLORS, "color": COLORS,
"size": (1, 200), "size": (1, 200),
@@ -49,39 +67,37 @@ VAR_OPTIONS = {
"comparison": COMPARISONS "comparison": COMPARISONS
} }
VAR_TYPES = {
"shape_type": "Shape Type",
"target_type": "Target Type",
"variable": "Variable",
"color": "Color",
"size": "Size",
"key_input": "Key Input",
"comparison": "Comparison"
}
TRIGGER_RULES = { TRIGGER_RULES = {
"every_update": { "every_update": {
"key": "every_update", "key": "every_update",
"user_vars": [],
"vars": [],
"description": "Every Update", "description": "Every Update",
"func": lambda *v: True
}, },
"start": { "start": {
"key": "start", "key": "start",
"user_vars": [],
"vars": [],
"description": "On Game Start", "description": "On Game Start",
"func": lambda *v: True
}, },
"on_input": { "on_input": {
"key": "on_input", "key": "on_input",
"user_vars": ["key_input"],
"vars": ["key_input", "event_key"],
"description": "IF {a} key is pressed", "description": "IF {a} key is pressed",
}, "func": lambda *v: v[0] == v[1]
"x_position_compare": {
"key": "x_position_compare",
"description": "IF X for {a} shape is {b} {c}",
"user_vars": ["shape_type", "comparison", "variable"],
"vars": ["shape_type", "comparison", "variable", "event_shape_type", "shape_x"],
"func": lambda *v: (v[0] == v[3]) and eval(f"{v[4]} {v[1]} {v[2]}")
},
"y_position_compare": {
"key": "y_position_compare",
"description": "IF Y for {a} shape is {b} {c}",
"user_vars": ["shape_type", "comparison", "variable"],
"vars": ["shape_type", "comparison", "variable", "event_shape_type", "shape_y"],
"func": lambda *v: (v[0] == v[3]) and eval(f"{v[4]} {v[1]} {v[2]}")
},
"size_compare": {
"key": "size_compare",
"description": "IF {a} shape size is {b} {c}",
"user_vars": ["shape_type", "comparison", "variable"],
"vars": ["shape_type", "comparison", "variable", "event_shape_type", "shape_size"],
"func": lambda *v: (v[0] == v[3]) and eval(f"{v[4]} {v[1]} {v[2]}")
}, },
"spawns": { "spawns": {
"key": "spawns", "key": "spawns",
@@ -97,20 +113,6 @@ TRIGGER_RULES = {
"vars": ["shape_type", "event_shape_type"], "vars": ["shape_type", "event_shape_type"],
"func": lambda *v: v[0] == v[1] "func": lambda *v: v[0] == v[1]
}, },
"x_velocity_changes": {
"key": "x_velocity_changes",
"description": "IF {a} shape X velocity changes",
"user_vars": ["shape_type"],
"vars": ["shape_type", "event_shape_type"],
"func": lambda *v: v[0] == v[1]
},
"y_velocity_changes": {
"key": "y_velocity_changes",
"description": "IF {a} shape Y velocity changes",
"user_vars": ["shape_type"],
"vars": ["shape_type", "event_shape_type"],
"func": lambda *v: v[0] == v[1]
},
"color_changes": { "color_changes": {
"key": "color_changes", "key": "color_changes",
"description": "IF {a} shape color changes", "description": "IF {a} shape color changes",
@@ -125,13 +127,6 @@ TRIGGER_RULES = {
"vars": ["shape_type", "event_shape_type"], "vars": ["shape_type", "event_shape_type"],
"func": lambda *v: v[0] == v[1] "func": lambda *v: v[0] == v[1]
}, },
"morphs": {
"key": "morphs",
"description": "IF {a} shape morphs into {b}",
"user_vars": ["shape_type", "target_type"],
"vars": ["shape_type", "target_type", "event_a_type", "event_b_type"],
"func": lambda *v: (v[0] == v[2]) and (v[3] == v[1])
},
"collides": { "collides": {
"key": "collides", "key": "collides",
"description": "IF {a} shape collides with {b}", "description": "IF {a} shape collides with {b}",
@@ -139,11 +134,34 @@ TRIGGER_RULES = {
"vars": ["shape_type", "target_type", "event_a_type", "event_b_type"], "vars": ["shape_type", "target_type", "event_a_type", "event_b_type"],
"func": lambda *v: (v[0] == v[2]) and (v[3] == v[1]) "func": lambda *v: (v[0] == v[2]) and (v[3] == v[1])
}, },
"on_left_click": {
"key": "on_left_click",
"description": "IF you left click",
"user_vars": [],
"vars": [],
"func": lambda *v: True
},
"on_right_click": {
"key": "on_right_click",
"description": "IF you right click",
"user_vars": [],
"vars": [],
"func": lambda *v: True
},
"on_mouse_move": {
"key": "on_mouse_move",
"description": "IF mouse moves",
"user_vars": [],
"vars": [],
"func": lambda *v: True
},
} }
FOR_RULES = { FOR_RULES = {
"every_shape": { "every_shape": {
"key": "every_shape", "key": "every_shape",
"user_vars": [],
"vars": [],
"description": "For every shape", "description": "For every shape",
} }
} }
@@ -154,35 +172,35 @@ IF_RULES = {
"description": "IF X is {a} {b}", "description": "IF X is {a} {b}",
"user_vars": ["comparison", "variable"], "user_vars": ["comparison", "variable"],
"vars": ["comparison", "variable", "shape_x"], "vars": ["comparison", "variable", "shape_x"],
"func": lambda *v: eval(f"{v[2]} {v[0]} {v[1]}") "func": lambda *v: OPS[v[0]](v[2], v[1])
}, },
"y_position_compare": { "y_position_compare": {
"key": "y_position_compare", "key": "y_position_compare",
"description": "IF Y is {a} {b}", "description": "IF Y is {a} {b}",
"user_vars": ["comparison", "variable"], "user_vars": ["comparison", "variable"],
"vars": ["comparison", "variable", "shape_y"], "vars": ["comparison", "variable", "shape_y"],
"func": lambda *v: eval(f"{v[2]} {v[0]} {v[1]}") "func": lambda *v: OPS[v[0]](v[2], v[1])
}, },
"size_compare": { "size_compare": {
"key": "size_compare", "key": "size_compare",
"description": "IF size is {a} {b}", "description": "IF size is {a} {b}",
"user_vars": ["comparison", "variable"], "user_vars": ["comparison", "variable"],
"vars": ["comparison", "variable", "shape_size"], "vars": ["comparison", "variable", "shape_size"],
"func": lambda *v: eval(f"{v[2]} {v[0]} {v[1]}") "func": lambda *v: OPS[v[0]](v[2], v[1])
}, },
"x_velocity_compare": { "x_velocity_compare": {
"key": "x_velocity_compare", "key": "x_velocity_compare",
"description": "IF X velocity is {a} {b}", "description": "IF X velocity is {a} {b}",
"user_vars": ["comparison", "variable"], "user_vars": ["comparison", "variable"],
"vars": ["comparison", "variable", "shape_x_velocity"], "vars": ["comparison", "variable", "shape_x_velocity"],
"func": lambda *v: eval(f"{v[2]} {v[0]} {v[1]}") "func": lambda *v: OPS[v[0]](v[2], v[1])
}, },
"y_velocity_compare": { "y_velocity_compare": {
"key": "y_velocity_compare", "key": "y_velocity_compare",
"description": "IF Y velocity is {a} {b}", "description": "IF Y velocity is {a} {b}",
"user_vars": ["comparison", "variable"], "user_vars": ["comparison", "variable"],
"vars": ["comparison", "variable", "shape_y_velocity"], "vars": ["comparison", "variable", "shape_y_velocity"],
"func": lambda *v: eval(f"{v[2]} {v[0]} {v[1]}") "func": lambda *v: OPS[v[0]](v[2], v[1])
}, },
"color_is": { "color_is": {
"key": "color_is", "key": "color_is",
@@ -191,111 +209,15 @@ IF_RULES = {
"vars": ["color", "shape_color"], "vars": ["color", "shape_color"],
"func": lambda *v: v[0] == v[1] "func": lambda *v: v[0] == v[1]
}, },
"shape_type_is": {
"key": "shape_type_is",
"description": "IF shape type is {a}",
"user_vars": ["shape_type"],
"vars": ["shape_type", "event_shape_type"],
"func": lambda *v: v[0] == v[1]
},
} }
NON_COMPATIBLE_WHEN = [
("spawns", "destroyed"),
("spawns", "morphs"),
("spawns", "collides"),
("spawns", "x_velocity_changes"),
("spawns", "y_velocity_changes"),
("spawns", "x_gravity_changes"),
("spawns", "y_gravity_changes"),
("spawns", "color_changes"),
("spawns", "size_changes"),
("destroyed", "morphs"),
("destroyed", "collides"),
("destroyed", "x_velocity_changes"),
("destroyed", "y_velocity_changes"),
("destroyed", "x_gravity_changes"),
("destroyed", "y_gravity_changes"),
("destroyed", "color_changes"),
("destroyed", "size_changes"),
("morphs", "collides"),
("morphs", "x_velocity_changes"),
("morphs", "y_velocity_changes"),
("morphs", "x_gravity_changes"),
("morphs", "y_gravity_changes"),
("morphs", "color_changes"),
("morphs", "size_changes"),
("collides", "destroyed"),
("collides", "morphs"),
("every_update", "spawns"),
("every_update", "destroyed"),
("every_update", "morphs"),
("every_update", "collides"),
("every_update", "x_velocity_changes"),
("every_update", "y_velocity_changes"),
("every_update", "x_gravity_changes"),
("every_update", "y_gravity_changes"),
("every_update", "color_changes"),
("every_update", "size_changes"),
("every_update", "game_launch"),
("game_launch", "spawns"),
("game_launch", "destroyed"),
("game_launch", "morphs"),
("game_launch", "collides"),
("game_launch", "x_velocity_changes"),
("game_launch", "y_velocity_changes"),
("game_launch", "x_gravity_changes"),
("game_launch", "y_gravity_changes"),
("game_launch", "color_changes"),
("game_launch", "size_changes"),
]
NON_COMPATIBLE_DO_WHEN = [
("destroyed", "change_x"),
("destroyed", "change_y"),
("destroyed", "move_x"),
("destroyed", "move_y"),
("destroyed", "change_x_velocity"),
("destroyed", "change_y_velocity"),
("destroyed", "change_x_gravity"),
("destroyed", "change_y_gravity"),
("destroyed", "change_color"),
("destroyed", "change_size"),
("destroyed", "morph_into"),
("destroyed", "destroy"),
("morphs", "morph_into"),
("x_velocity_changes", "change_x_velocity"),
("y_velocity_changes", "change_y_velocity"),
("color_changes", "change_color"),
("size_changes", "change_size"),
("every_update", "change_x"),
("every_update", "change_y"),
("every_update", "move_x"),
("every_update", "move_y"),
("every_update", "change_x_velocity"),
("every_update", "change_y_velocity"),
("every_update", "change_color"),
("every_update", "change_size"),
("every_update", "destroy"),
("every_update", "morph_into"),
("game_launch", "change_x"),
("game_launch", "change_y"),
("game_launch", "move_x"),
("game_launch", "move_y"),
("game_launch", "change_x_velocity"),
("game_launch", "change_y_velocity"),
("game_launch", "change_x_gravity"),
("game_launch", "change_y_gravity"),
("game_launch", "change_color"),
("game_launch", "change_size"),
("game_launch", "destroy"),
("game_launch", "morph_into")
]
DO_RULES = { DO_RULES = {
"change_x": { "change_x": {
"key": "change_x", "key": "change_x",
@@ -369,14 +291,6 @@ DO_RULES = {
"vars": ["shape"] "vars": ["shape"]
}, },
"morph_into": {
"key": "morph_into",
"description": "Morph this into {a}",
"action": {"type": "shape_action", "name": "morph"},
"user_vars": ["shape_type"],
"vars": ["shape", "shape_type"]
},
"change_x_gravity": { "change_x_gravity": {
"key": "change_x_gravity", "key": "change_x_gravity",
"description": "Change X gravity to {a}", "description": "Change X gravity to {a}",
@@ -402,6 +316,67 @@ DO_RULES = {
} }
} }
PROVIDES_SHAPE = [
# Trigger
"spawns",
"color_changes",
"size_changes",
"collides",
# IFs, technically, these need and provide a shape to the next rule
"x_position_compare",
"y_position_compare",
"size_compare",
"x_velocity_compare",
"y_velocity_compare",
"color_is",
"shape_type_is",
# FOR
"every_shape"
]
NEEDS_SHAPE = [
# IF
"x_position_compare",
"y_position_compare",
"size_compare",
"x_velocity_compare",
"y_velocity_compare",
"color_is",
"shape_type_is",
# DO
"change_x",
"change_y",
"move_x",
"move_y",
"change_x_velocity",
"change_y_velocity",
"change_size",
"destroy",
]
RULE_DEFAULTS = {
rule_type: {
rule_key: (
rule_dict["description"].format_map(
{
VAR_NAMES[n]: VAR_NAMES[n]
for n, variable in enumerate(rule_dict["user_vars"])
}
),
[
VAR_DEFAULT[variable]
for variable in rule_dict["user_vars"]
],
)
for rule_key, rule_dict in rule_var.items()
}
for rule_type, rule_var in [("if", IF_RULES), ("do", DO_RULES), ("trigger", TRIGGER_RULES), ("for", FOR_RULES)]
}
menu_background_color = (30, 30, 47) menu_background_color = (30, 30, 47)
log_dir = 'logs' log_dir = 'logs'
discord_presence_id = 1440807203094138940 discord_presence_id = 1440807203094138940