diff --git a/game/play.py b/game/play.py index 92a7fd2..dd2bbcb 100644 --- a/game/play.py +++ b/game/play.py @@ -1,8 +1,10 @@ import arcade, arcade.gui, pyglet, random -from utils.constants import slider_style, dropdown_style, VAR_NAMES, VAR_DEFAULT, DEFAULT_GRAVITY, VAR_OPTIONS +from utils.constants import slider_style, dropdown_style, VAR_NAMES, VAR_DEFAULT, DEFAULT_GRAVITY, VAR_OPTIONS, DO_RULES, IF_RULES -from game.rules import generate_rules, generate_rule +from arcade.gui.experimental.scroll_area import UIScrollArea, UIScrollBar + +from game.rules import generate_rule from game.sprites import * class Game(arcade.gui.UIView): @@ -13,14 +15,27 @@ class Game(arcade.gui.UIView): self.pypresence_client.update(state="Causing Chaos") self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1))) - self.rules_box = self.anchor.add(arcade.gui.UIBoxLayout(align="left", size_hint=(0.25, 1)).with_background(color=arcade.color.DARK_GRAY), anchor_x="right", anchor_y="bottom") + + self.scroll_area = UIScrollArea(size_hint=(0.25, 1)) # center on screen + self.scroll_area.scroll_speed = -75 + self.anchor.add(self.scroll_area, anchor_x="right", anchor_y="center", align_x=-self.window.width * 0.02) + + self.scrollbar = UIScrollBar(self.scroll_area) + self.scrollbar.size_hint = (0.02, 1) + self.anchor.add(self.scrollbar, anchor_x="right", anchor_y="center") + + self.rules_box = arcade.gui.UIBoxLayout(align="left", size_hint=(0.25, 1)).with_background(color=arcade.color.DARK_GRAY) + self.scroll_area.add(self.rules_box) self.gravity = DEFAULT_GRAVITY - self.rules = generate_rules(1) + self.current_ruleset_num = 0 + self.rulesets = {} + self.rule_values = {} + self.triggered_events = [] self.rule_labels = {} - self.rule_sliders = {} + self.rule_var_changers = {} self.shapes = [] self.shape_batch = pyglet.graphics.Batch() @@ -43,8 +58,8 @@ class Game(arcade.gui.UIView): def change_y_velocity(self, shape, a): shape.y_velocity = a - def get_default_values(self, variable_list): - return {VAR_NAMES[n]: VAR_DEFAULT[variable] for n, variable in enumerate(variable_list)} + def change_gravity(self, a): + self.gravity = a def spawn(self, shape): x, y = random.randint(100, self.window.width - 100), random.randint(100, self.window.height - 100) @@ -58,38 +73,54 @@ class Game(arcade.gui.UIView): elif shape == "triangle": self.shapes.append(Triangle(x, y, x + 10, y, x + 5, y + 10, color=arcade.color.WHITE, batch=self.shape_batch)) - def create_rule_ui(self, rule_box, rule, rule_type="if"): - default_values = {VAR_NAMES[n]: VAR_DEFAULT[variable] for n, variable in enumerate(rule["user_vars"])} - description = rule["description"].format_map(default_values) + def create_rule_ui(self, rule_box: arcade.gui.UIBoxLayout, rule, rule_type, rule_num=1): + rule_dict = IF_RULES[rule] if rule_type == "if" else DO_RULES[rule] + + ruleset_num = self.current_ruleset_num + + default_values = {VAR_NAMES[n]: VAR_DEFAULT[variable] for n, variable in enumerate(rule_dict["user_vars"])} + description = rule_dict["description"].format_map(default_values) - rule_box.add(arcade.gui.UILabel(description if rule_type == "if" else f"THEN {description}", font_size=13, width=self.window.width * 0.25)) + desc_label = rule_box.add(arcade.gui.UILabel(description if rule_type == "if" else f"THEN {description}", font_size=13, width=self.window.width * 0.25)) + self.rule_labels[f"{self.current_ruleset_num}_{rule_num}_desc"] = desc_label - for n, variable in enumerate(rule["user_vars"]): - rule_box.add(arcade.gui.UILabel(f'{VAR_NAMES[n]}: {default_values[VAR_NAMES[n]]}', font_size=11, width=self.window.width * 0.25, height=self.window.height / 25)) + for n, variable_type in enumerate(rule_dict["user_vars"]): + key = f"{self.current_ruleset_num}_{rule_num}_{variable_type}_{n}" - if variable in ["variable", "size"]: - slider = rule_box.add(arcade.gui.UISlider(value=default_values[VAR_NAMES[n]], min_value=VAR_OPTIONS[variable][0], max_value=VAR_OPTIONS[variable][1], step=1, style=slider_style, width=self.window.width * 0.25, height=self.window.height / 25)) - slider._render_steps = lambda surface: None - elif variable in ["shape_type", "target_type", "color"]: - dropdown = rule_box.add(arcade.gui.UIDropdown(default=default_values[VAR_NAMES[n]], options=VAR_OPTIONS[variable], active_style=dropdown_style, primary_style=dropdown_style, dropdown_style=dropdown_style, width=self.window.width * 0.25, height=self.window.height / 25)) - def create_ruleset_ui(self, ruleset): + self.rule_values[key] = default_values[VAR_NAMES[n]] + + label = rule_box.add(arcade.gui.UILabel(f'{VAR_NAMES[n]}: {default_values[VAR_NAMES[n]]}', font_size=11, width=self.window.width * 0.25, height=self.window.height / 25)) + self.rule_labels[key] = label + + if variable_type in ["variable", "size"]: + slider = rule_box.add(arcade.gui.UISlider(value=default_values[VAR_NAMES[n]], min_value=VAR_OPTIONS[variable_type][0], max_value=VAR_OPTIONS[variable_type][1], step=1, style=slider_style, width=self.window.width * 0.25, height=self.window.height / 25)) + slider._render_steps = lambda surface: None + slider.on_change = lambda event, variable_type=variable_type, rule=rule, rule_type=rule_type, ruleset_num=ruleset_num, rule_num=rule_num, n=n: self.change_rule_value(ruleset_num, rule_num, rule, rule_type, variable_type, n, event.new_value) + self.rule_var_changers[key] = slider + elif variable_type in ["shape_type", "target_type", "color"]: + dropdown = rule_box.add(arcade.gui.UIDropdown(default=default_values[VAR_NAMES[n]], options=VAR_OPTIONS[variable_type], active_style=dropdown_style, primary_style=dropdown_style, dropdown_style=dropdown_style, width=self.window.width * 0.25, height=self.window.height / 25)) + dropdown.on_change = lambda event, variable_type=variable_type, rule=rule, rule_type=rule_type, ruleset_num=ruleset_num, rule_num=rule_num, n=n: self.change_rule_value(ruleset_num, rule_num, rule, rule_type, variable_type, n, event.new_value) + self.rule_var_changers[key] = dropdown + + def add_ruleset(self, ruleset): rule_box = self.rules_box.add(arcade.gui.UIBoxLayout(space_between=5, align="left").with_background(color=arcade.color.DARK_SLATE_GRAY)) if len(ruleset) == 2: - self.create_rule_ui(rule_box, ruleset[0]) + self.rulesets[self.current_ruleset_num] = ruleset + + self.create_rule_ui(rule_box, ruleset[0], "if") self.create_rule_ui(rule_box, ruleset[1], "do") else: + self.rulesets[self.current_ruleset_num] = ruleset + self.create_rule_ui(rule_box, ruleset[0], "if") - rule_box.add(arcade.gui.UILabel(ruleset[1].upper(), font_size=14, width=self.window.width * 0.25)) - - self.create_rule_ui(rule_box, ruleset[2], "if") - - self.create_rule_ui(rule_box, ruleset[3], "do") + self.create_rule_ui(rule_box, ruleset[2], "if", 2) + self.create_rule_ui(rule_box, ruleset[3], "do", 3) self.rules_box.add(arcade.gui.UISpace(height=self.window.height / 50)) - + def on_show_view(self): super().on_show_view() @@ -98,12 +129,111 @@ class Game(arcade.gui.UIView): self.rules_box.add(arcade.gui.UISpace(height=self.window.height / 50)) - for ruleset in self.rules: - self.create_ruleset_ui(ruleset) + for _ in range(8): + self.add_rule() + + self.triggered_events.append(["game_launch", {}]) def add_rule(self): - self.rules.append(generate_rule()) - self.create_ruleset_ui(self.rules[-1]) + self.rulesets[self.current_ruleset_num] = generate_rule() + self.add_ruleset(self.rulesets[self.current_ruleset_num]) + self.current_ruleset_num += 1 + + def get_rule_values(self, ruleset_num, rule_num, rule_dict, event_args): + args = [self.rule_values[f"{ruleset_num}_{rule_num}_{user_var}_{n}"] for n, user_var in enumerate(rule_dict["user_vars"])] + + return args + [event_args[var] for var in rule_dict.get("vars", []) if var in rule_dict["user_vars"]] + + def check_rule(self, ruleset_num, rule_num, rule_dict, event_args): + return rule_dict["func"](*self.get_rule_values(ruleset_num, rule_num, rule_dict, event_args)) + + def get_action_function(self, action_dict): + ACTION_FUNCTION_DICT = { + "global_action": { + "spawn": self.spawn, + "change_gravity": self.change_gravity + }, + "shape_action": { + "move_x": self.move_x, + "move_y": self.move_y, + "change_x": self.change_x, + "change_y": self.change_y, + "change_x_velocity": self.change_x_velocity, + "change_y_velocity": self.change_y_velocity + } + } + + return ACTION_FUNCTION_DICT[action_dict["type"]][action_dict["name"]] + + def run_do_rule(self, ruleset_num, rule_num, rule_dict, event_args): + self.get_action_function(rule_dict["action"])(*self.get_rule_values(ruleset_num, rule_num, rule_dict, event_args)) + + def on_update(self, delta_time): + self.triggered_events.append(["every_update", {}]) + + while len(self.triggered_events) > 0: + trigger, trigger_args = self.triggered_events.pop(0) + + for key, ruleset in self.rulesets.items(): + if len(ruleset) == 2: + if_rule_dict = IF_RULES[ruleset[0]] + do_rule_dict = DO_RULES[ruleset[1]] + + if not if_rule_dict["trigger"] == trigger: + continue + + if if_rule_dict["trigger"] in ["every_update"]: + for shape in self.shapes: + event_args = trigger_args + + if not "event_shape_type" in trigger_args: + event_args.update({"event_shape_type": shape.shape_type, "shape_size": shape.size}) + + if self.check_rule(key, 0, if_rule_dict, event_args): + self.run_do_rule(key, 1, do_rule_dict, event_args) + else: + event_args = trigger_args + if self.check_rule(key, 0, if_rule_dict, event_args): + self.run_do_rule(key, 1, do_rule_dict, event_args) + + else: + if_rule_dicts = IF_RULES[ruleset[0]], IF_RULES[ruleset[2]] + do_rule_dict = DO_RULES[ruleset[3]] + + if not (if_rule_dicts[0]["trigger"] == trigger and if_rule_dicts[0]["trigger"] == trigger): + continue + + for shape in self.shapes if not "event_shape_type" in trigger_args else [shape]: + event_args = trigger_args + if not "event_shape_type" in trigger_args: + event_args.update({"event_shape_type": shape.shape_type, "shape_size": shape.size}) + + if ruleset[1] == "and": + if self.check_rule(key, 0, if_rule_dicts[0], event_args) and self.check_rule(key, 2, if_rule_dicts[1], event_args): + self.run_do_rule(key, 3, do_rule_dict, event_args) + + elif ruleset[1] == "or": + if self.check_rule(key, 0, if_rule_dicts[0], event_args) or self.check_rule(key, 2, if_rule_dicts[1], event_args): + self.run_do_rule(key, 3, do_rule_dict, event_args) + + for shape in self.shapes: + shape.update(self.gravity) + + def change_rule_value(self, ruleset_num, rule_num, rule, rule_type, variable_type, n, value): + rule_dict = IF_RULES[rule] if rule_type == "if" else DO_RULES[rule] + key = f"{ruleset_num}_{rule_num}_{variable_type}_{n}" + + self.rule_values[key] = value + + values = {} + for i, variable in enumerate(rule_dict["user_vars"]): + lookup_key = f"{ruleset_num}_{rule_num}_{variable}_{i}" + values[VAR_NAMES[i]] = self.rule_values.get(lookup_key, VAR_DEFAULT[variable]) + + description = rule_dict["description"].format_map(values) + + self.rule_labels[f"{ruleset_num}_{rule_num}_desc"].text = description if rule_type == "if" else f"THEN {description}" + self.rule_labels[key].text = f'{VAR_NAMES[n]}: {value}' def on_draw(self): super().on_draw() diff --git a/game/rules.py b/game/rules.py index a864911..0c089be 100644 --- a/game/rules.py +++ b/game/rules.py @@ -2,28 +2,48 @@ from utils.constants import DO_RULES, IF_RULES, LOGICAL_OPERATORS, NON_COMPATIBL import random +IF_KEYS = tuple(IF_RULES.keys()) +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(): - when_a = random.choice(list(IF_RULES.keys())) - when_b = None + when_a = random.choice(IF_KEYS) if random.random() < 0.5: - when_b = random.choice(list(IF_RULES.keys())) - while (when_a, when_b) in NON_COMPATIBLE_WHEN or (when_b, when_a) in NON_COMPATIBLE_WHEN or when_a == when_b: - when_a = random.choice(list(IF_RULES.keys())) - when_b = random.choice(list(IF_RULES.keys())) + valid_b = [ + b for b in IF_KEYS + if b != when_a and tuple(sorted((when_a, b))) not in BAD_WHEN + ] - logical_operation = random.choice(LOGICAL_OPERATORS) + if not valid_b: + return [when_a, random.choice(DO_KEYS)] + + when_b = random.choice(valid_b) + logical = random.choice(LOGICAL_OPERATORS) else: - logical_operation = None + when_b = None + logical = None - do = random.choice(list(DO_RULES.keys())) - while (when_a, do) in NON_COMPATIBLE_DO_WHEN or (do, when_a) in NON_COMPATIBLE_DO_WHEN or (when_b, do) in NON_COMPATIBLE_DO_WHEN or (do, when_b) in NON_COMPATIBLE_DO_WHEN: - do = random.choice(list(DO_RULES.keys())) - - if logical_operation: - return [IF_RULES[when_a], logical_operation, IF_RULES[when_b], DO_RULES[do]] + if when_b: + valid_do = [ + d for d in DO_KEYS + if (when_a, d) not in BAD_DO_WHEN + and (when_b, d) not in BAD_DO_WHEN + and (d, when_a) not in BAD_DO_WHEN + and (d, when_b) not in BAD_DO_WHEN + ] else: - return [IF_RULES[when_a], DO_RULES[do]] - -def generate_rules(n): - return [generate_rule() for _ in range(n)] \ No newline at end of file + valid_do = [ + d for d in DO_KEYS + if (when_a, d) not in BAD_DO_WHEN + and (d, when_a) not in BAD_DO_WHEN + ] + + do = random.choice(valid_do) + + if logical: + return [when_a, logical, when_b, do] + else: + return [when_a, do] \ No newline at end of file diff --git a/game/sprites.py b/game/sprites.py index fddfa98..0e57c96 100644 --- a/game/sprites.py +++ b/game/sprites.py @@ -4,6 +4,7 @@ from utils.constants import DEFAULT_X_VELOCITY, DEFAULT_Y_VELOCITY class BaseShape(): def __init__(self): + self.shape_type = "" self.x_velocity = DEFAULT_X_VELOCITY self.y_velocity = DEFAULT_Y_VELOCITY @@ -15,11 +16,29 @@ class BaseShape(): class Circle(pyglet.shapes.Circle, BaseShape): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + BaseShape.__init__(self) + self.shape_type = "circle" + + @property + def shape_size(self): + return self.radius class Rectangle(pyglet.shapes.Rectangle, BaseShape): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + BaseShape.__init__(self) + self.shape_type = "rectangle" + + @property + def shape_size(self): + return self.width class Triangle(pyglet.shapes.Triangle, BaseShape): def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) \ No newline at end of file + super().__init__(*args, **kwargs) + BaseShape.__init__(self) + self.shape_type = "triangle" + + @property + def shape_size(self): + return self.x2 - self.x \ No newline at end of file diff --git a/utils/constants.py b/utils/constants.py index ed04a4b..e802a41 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -7,7 +7,7 @@ LOGICAL_OPERATORS = ["and", "or"] SHAPES = ["rectangle", "circle", "triangle"] VAR_NAMES = ["a", "b", "c", "d", "e", "f", "g"] -DEFAULT_GRAVITY = 5 +DEFAULT_GRAVITY = 2 DEFAULT_X_VELOCITY = 0 DEFAULT_Y_VELOCITY = 0 @@ -25,12 +25,13 @@ VAR_OPTIONS = { "shape_type": SHAPES, "target_type": SHAPES, "variable": (0, 2500), - "color": "WHITE", + "color": COLORS, "size": (1, 200), } IF_RULES = { "x_position": { + "key": "x_position", "description": "IF X for {a} shape is {b}", "trigger": "every_update", "user_vars": ["shape_type", "variable"], @@ -38,6 +39,7 @@ IF_RULES = { "func": lambda *v: (v[0] == v[2]) and (v[3] == v[1]) }, "y_position": { + "key": "y_position", "description": "IF Y for {a} shape is {b}", "trigger": "every_update", "user_vars": ["shape_type", "variable"], @@ -45,6 +47,7 @@ IF_RULES = { "func": lambda *v: (v[0] == v[2]) and (v[3] == v[1]) }, "color_is": { + "key": "color_is", "description": "IF {a} shape color is {b}", "trigger": "every_update", "user_vars": ["shape_type", "color"], @@ -52,6 +55,7 @@ IF_RULES = { "func": lambda *v: (v[0] == v[2]) and (v[3] == v[1]) }, "size_is": { + "key": "size_is", "description": "IF {a} shape size is {b}", "trigger": "every_update", "user_vars": ["shape_type", "size"], @@ -59,6 +63,7 @@ IF_RULES = { "func": lambda *v: (v[0] == v[2]) and (v[3] == v[1]) }, "spawns": { + "key": "spawns", "description": "IF {a} shape spawns", "trigger": "spawns", "user_vars": ["shape_type"], @@ -66,6 +71,7 @@ IF_RULES = { "func": lambda *v: v[0] == v[1] }, "destroyed": { + "key": "destroyed", "description": "IF {a} shape is destroyed", "trigger": "destroyed", "user_vars": ["shape_type"], @@ -73,6 +79,7 @@ IF_RULES = { "func": lambda *v: v[0] == v[1] }, "x_velocity_changes": { + "key": "x_velocity_changes", "description": "IF {a} shape X velocity changes", "trigger": "x_change", "user_vars": ["shape_type"], @@ -80,6 +87,7 @@ IF_RULES = { "func": lambda *v: v[0] == v[1] }, "y_velocity_changes": { + "key": "y_velocity_changes", "description": "IF {a} shape Y velocity changes", "trigger": "y_change", "user_vars": ["shape_type"], @@ -87,6 +95,7 @@ IF_RULES = { "func": lambda *v: v[0] == v[1] }, "x_gravity_changes": { + "key": "x_gravity_changes", "description": "IF {a} shape X gravity changes", "trigger": "gravity_x_change", "user_vars": ["shape_type"], @@ -94,6 +103,7 @@ IF_RULES = { "func": lambda *v: v[0] == v[1] }, "y_gravity_changes": { + "key": "y_gravity_changes", "description": "IF {a} shape Y gravity changes", "trigger": "gravity_y_change", "user_vars": ["shape_type"], @@ -101,6 +111,7 @@ IF_RULES = { "func": lambda *v: v[0] == v[1] }, "gravity_changes": { + "key": "gravity_changes", "description": "IF gravity changes", "user_vars": [], "trigger": "gravity_change", @@ -108,6 +119,7 @@ IF_RULES = { "func": lambda *v: True }, "color_changes": { + "key": "color_changes", "description": "IF {a} shape color changes", "trigger": "color_change", "user_vars": ["shape_type"], @@ -115,6 +127,7 @@ IF_RULES = { "func": lambda *v: v[0] == v[1] }, "size_changes": { + "key": "size_changes", "description": "IF {a} shape size changes", "trigger": "size_change", "user_vars": ["shape_type"], @@ -122,6 +135,7 @@ IF_RULES = { "func": lambda *v: v[0] == v[1] }, "morphs": { + "key": "morphs", "description": "IF {a} shape morphs into {b}", "trigger": "morph", "user_vars": ["shape_type", "target_type"], @@ -129,13 +143,15 @@ IF_RULES = { "func": lambda *v: (v[0] == v[2]) and (v[3] == v[1]) }, "collides": { + "key": "collides", "description": "IF {a} shape collides with {b}", "trigger": "collision", "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]) }, - "launch": { + "game_launch": { + "key": "game_launch", "description": "IF game launches", "trigger": "game_launch", "user_vars": [], @@ -143,6 +159,7 @@ IF_RULES = { "func": lambda *v: True }, "every_update": { + "key": "every_update", "description": "Every update", "trigger": "every_update", "user_vars": [], @@ -266,72 +283,84 @@ NON_COMPATIBLE_DO_WHEN = [ DO_RULES = { "change_x": { + "key": "change_x", "description": "Change this shape's X to {a}", "action": {"type": "shape_action", "name": "change_x"}, - "user_vars": ["variable"] + "user_vars": ["shape", "variable"] }, "change_y": { + "key": "change_y", "description": "Change this shape's Y to {a}", "action": {"type": "shape_action", "name": "change_y"}, - "user_vars": ["variable"] + "user_vars": ["shape", "variable"] }, "move_x": { + "key": "move_x", "description": "Move this shape's X by {a}", "action": {"type": "shape_action", "name": "move_x"}, - "user_vars": ["variable"] + "user_vars": ["shape", "variable"] }, "move_y": { + "key": "move_y", "description": "Move this shape's Y by {a}", "action": {"type": "shape_action", "name": "move_y"}, - "user_vars": ["variable"] + "user_vars": ["shape", "variable"] }, "change_x_velocity": { + "key": "change_x_velocity", "description": "Change X velocity of this to {a}", "action": {"type": "shape_action", "name": "change_x_vel"}, - "user_vars": ["variable"] + "user_vars": ["shape", "variable"] }, "change_y_velocity": { + "key": "change_y_velocity", "description": "Change Y velocity of this to {a}", "action": {"type": "shape_action", "name": "change_y_vel"}, - "user_vars": ["variable"] + "user_vars": ["shape", "variable"] }, "change_color": { + "key": "change_color", "description": "Change this shape's color to {a}", "action": {"type": "shape_action", "name": "change_color"}, - "user_vars": ["color"] + "user_vars": ["shape", "color"] }, "change_size": { + "key": "change_size", "description": "Change this shape's size to {a}", "action": {"type": "shape_action", "name": "change_size"}, - "user_vars": ["size"] + "user_vars": ["shape", "size"] }, "destroy": { + "key": "destroy", "description": "Destroy this", "action": {"type": "shape_action", "name": "destroy"}, - "user_vars": [] + "user_vars": ["shape"] }, "morph_into": { + "key": "morph_into", "description": "Morph this into {a}", "action": {"type": "shape_action", "name": "morph"}, - "user_vars": ["shape_type"] + "user_vars": ["shape", "shape_type"] }, "change_gravity": { - "description": "Change this shape's gravity to {a}", - "action": {"type": "shape_action", "name": "change_gravity"}, - "user_vars": ["variable"] + "key": "change_gravity", + "description": "Change gravity to {a}", + "action": {"type": "global_action", "name": "change_gravity"}, + "user_vars": ["shape", "variable"] }, "spawn": { + "key": "spawn", "description": "Spawn {a}", "action": {"type": "global_action", "name": "spawn"}, "user_vars": ["shape_type"]