Fix importing and exporting, add variables (scratch blocks as well), recursively execute the rules, reset x and y gravity and events when switching to simulation, remove rule generation, fix indentation and padding, remove bloat, fix bugs

This commit is contained in:
csd4ni3l
2025-12-07 19:13:46 +01:00
parent fe7b42ec40
commit b74115b489
3 changed files with 498 additions and 319 deletions

View File

@@ -1,9 +1,11 @@
import arcade, arcade.gui, pyglet, random, json import arcade, arcade.gui, pyglet, random, json
from dataclasses import asdict
from utils.preload import SPRITE_TEXTURES, button_texture, button_hovered_texture from utils.preload import SPRITE_TEXTURES, button_texture, button_hovered_texture
from utils.constants import button_style, DO_RULES, IF_RULES, SHAPES, ALLOWED_INPUT from utils.constants import button_style, DO_RULES, IF_RULES, SHAPES, ALLOWED_INPUT
from game.rules import RuleUI from game.rules import RuleUI, Block, VarBlock
from game.sprites import BaseShape, Rectangle, Circle, Triangle from game.sprites import BaseShape, Rectangle, Circle, Triangle
from game.file_manager import FileManager from game.file_manager import FileManager
@@ -35,9 +37,10 @@ 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.sprites_box = arcade.gui.UIAnchorLayout(size_hint=(0.95, 0.9))
self.sprite_types = SHAPES
self.shapes = [] self.shapes = []
self.shape_batch = pyglet.graphics.Batch() self.shape_batch = pyglet.graphics.Batch()
@@ -174,15 +177,17 @@ class Game(arcade.gui.UIView):
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", {}]) self.sprites_box.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))
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 = {
@@ -207,29 +212,69 @@ class Game(arcade.gui.UIView):
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 = []
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
self.rule_values = data["rule_values"] for rule_num, ruleset in data.items():
self.triggered_events = [] 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
# TODO: add rule loading here self.rules_box.rulesets = self.rulesets
self.rules_box.current_rule_num = self.get_max_rule_num() + 1
self.rules_box.block_renderer.refresh()
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({rule_num: asdict(block) for rule_num, block in self.rulesets.items()}, indent=4))
"rulesets": self.rulesets,
"rule_values": self.rule_values
}, indent=4))
if not self.mode == "simulation": if not self.mode == "simulation":
return return
@@ -239,31 +284,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:
@@ -350,8 +375,10 @@ class Game(arcade.gui.UIView):
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,9 +75,86 @@ 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 + 10
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) * 10
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 + 7
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, 280
@@ -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._build_block_with_vars(b, x, y)
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_objects.append(text_obj)
self.text_by_rule_num[b.rule_num].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,49 +401,10 @@ 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
@@ -289,24 +412,38 @@ class RuleUI(arcade.gui.UIAnchorLayout):
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,8 +460,11 @@ 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, 280, 44).intersection(arcade.LBWH(self.dragged_rule_ui.x, self.dragged_rule_ui.y - 44, 280, 44)):
@@ -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,6 +515,13 @@ 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):
@@ -386,6 +537,23 @@ 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_vec = self.camera.unproject((self.dragged_rule_ui.x, self.dragged_rule_ui.y))
@@ -394,3 +562,4 @@ class RuleUI(arcade.gui.UIAnchorLayout):
else: else:
self.trash_sprite.time = 0 self.trash_sprite.time = 0
self.trash_sprite.update_animation() self.trash_sprite.update_animation()

View File

@@ -1,4 +1,4 @@
import arcade.color 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
@@ -29,6 +29,15 @@ 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": SHAPES[0],
"target_type": SHAPES[1], "target_type": SHAPES[1],
@@ -49,39 +58,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 +104,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",
@@ -139,11 +132,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 +170,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 +207,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",
@@ -402,6 +322,69 @@ DO_RULES = {
} }
} }
PROVIDES_SHAPE = [
# Trigger
"spawns",
"color_changes",
"size_changes",
"morphs",
"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",
"morph_into"
]
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