diff --git a/src/behavior_generation_lecture_python/finite_state_machine/traffic_light_state_machine_with_visu.py b/src/behavior_generation_lecture_python/finite_state_machine/traffic_light_state_machine_with_visu.py index d035a7a..7e4bfab 100644 --- a/src/behavior_generation_lecture_python/finite_state_machine/traffic_light_state_machine_with_visu.py +++ b/src/behavior_generation_lecture_python/finite_state_machine/traffic_light_state_machine_with_visu.py @@ -1,15 +1,11 @@ -import time import warnings - from statemachine import State, StateMachine - from behavior_generation_lecture_python.finite_state_machine.traffic_light_visualization import ( VisualizeTrafficLights, ) - class TrafficLightStateMachine(StateMachine): - """A traffic light machine""" + """A traffic light machine with non-blocking visualization""" veh_green = State("veh_green", initial=True) veh_yellow = State("veh_yellow") @@ -33,40 +29,41 @@ def __init__(self, start_mainloop=True): if start_mainloop: self.visu.mainloop() + def _schedule(self, delay_sec, callback): + """Helper to schedule an event without blocking the UI""" + self.visu.windows.after(int(delay_sec * 1000), callback) + def on_enter_veh_yellow(self): self.visu.pedestrian_press_red() - time.sleep(3) + # Wait 3s before turning yellow (simulating reaction/delay phase) + self._schedule(3.0, self._phase_turn_yellow) + + def _phase_turn_yellow(self): self.visu.vehicle_prepare_to_stop() - time.sleep(3) - self.send("veh_stop") + # Wait 3s before stopping + self._schedule(3.0, lambda: self.send("veh_stop")) def on_enter_veh_red_ped_red_1(self): self.visu.vehicle_stop() - time.sleep(2) - self.send("ped_go") + self._schedule(2.0, lambda: self.send("ped_go")) def on_enter_veh_red_ped_green(self): self.visu.pedestrian_go() - time.sleep(5) - self.send("ped_stop") + self._schedule(5.0, lambda: self.send("ped_stop")) def on_enter_veh_red_ped_red_2(self): self.visu.pedestrian_stop() - time.sleep(5) - self.send("veh_prepare_to_go") + self._schedule(5.0, lambda: self.send("veh_prepare_to_go")) def on_enter_veh_red_yellow(self): self.visu.vehicle_prepare_to_go() - time.sleep(2) - self.send("veh_go") + self._schedule(2.0, lambda: self.send("veh_go")) def on_enter_veh_green(self): self.visu.vehicle_go() def button_press(self, event): - if self.state == "veh_green": + if self.current_state.id == "veh_green": self.send("veh_prepare_to_stop") else: - warnings.warn( - "The button only has effect if currently vehicles have green." - ) + warnings.warn("The button only works when vehicles have green.") diff --git a/src/behavior_generation_lecture_python/finite_state_machine/traffic_light_visualization.py b/src/behavior_generation_lecture_python/finite_state_machine/traffic_light_visualization.py index ca2a254..59d6be8 100644 --- a/src/behavior_generation_lecture_python/finite_state_machine/traffic_light_visualization.py +++ b/src/behavior_generation_lecture_python/finite_state_machine/traffic_light_visualization.py @@ -1,115 +1,117 @@ import tkinter as tk - class VisualizeTrafficLights: def __init__(self): self.windows = tk.Tk() self.windows.title("Traffic Light FSM") - self.canvas = tk.Canvas(self.windows) - - self.vehicle_red = self.__create_circle(55, 55, 50) - self.vehicle_yellow = self.__create_circle(55, 165, 50) - self.vehicle_green = self.__create_circle(55, 275, 50) - self.pedestrian_red = self.__create_circle(180, 55, 50) - self.pedestrian_green = self.__create_circle(180, 165, 50) - self.pedestrian_press_button = self.canvas.create_rectangle(140, 260, 220, 290) - self.pedestrian_press_label = self.canvas.create_text(180, 275, text="Press") - # self.pedestrian_press = tk.Button(text="Press", command=pedestrian_press_fun) - # self.canvas.create_window(180, 265, window=self.pedestrian_press) - - self.__pedestrian_press_white() - - self.canvas.config(width=400, height=400) - self.canvas.pack() + self.windows.configure(bg="#2c3e50") # Dark background for window + + # Canvas settings + self.width = 400 + self.height = 400 + self.canvas = tk.Canvas( + self.windows, + width=self.width, + height=self.height, + bg="#2c3e50", + highlightthickness=0 + ) + self.canvas.pack(padx=20, pady=20) + + # Traffic Light Colors + self.colors = { + "off_red": "#4a0000", "on_red": "#ff3b30", + "off_yellow": "#4a4a00", "on_yellow": "#ffcc00", + "off_green": "#002a00", "on_green": "#4cd964", + "housing": "#1a1a1a", + "text": "#ecf0f1" + } + + # Draw Housing (Background Boxes) + # Vehicle Light Housing + self._draw_housing(50, 20, 160, 360) + # Pedestrian Light Housing + self._draw_housing(240, 20, 350, 240) + + # Labels + self.canvas.create_text(105, 375, text="Vehicle", fill=self.colors["text"], font=("Helvetica", 12, "bold")) + self.canvas.create_text(295, 255, text="Pedestrian", fill=self.colors["text"], font=("Helvetica", 12, "bold")) + + # Create Lights + self.vehicle_red = self.__create_circle(105, 80, 40, fill=self.colors["off_red"]) + self.vehicle_yellow = self.__create_circle(105, 190, 40, fill=self.colors["off_yellow"]) + self.vehicle_green = self.__create_circle(105, 300, 40, fill=self.colors["off_green"]) + + self.pedestrian_red = self.__create_circle(295, 80, 40, fill=self.colors["off_red"]) + self.pedestrian_green = self.__create_circle(295, 190, 40, fill=self.colors["off_green"]) + + # Pedestrian Button + self.pedestrian_press_button = self.canvas.create_rectangle( + 240, 280, 350, 320, + fill="#95a5a6", outline="", width=0 + ) + self.pedestrian_press_label = self.canvas.create_text( + 295, 300, + text="PRESS BUTTON", + fill="#2c3e50", + font=("Helvetica", 10, "bold") + ) self.__update() + def _draw_housing(self, x1, y1, x2, y2): + r = 15 # radius for rounded corners effect + self.canvas.create_rectangle(x1, y1, x2, y2, fill=self.colors["housing"], outline="black", width=2) + def __create_circle(self, x, y, r, fill=None): - x0 = x - r - y0 = y - r - x1 = x + r - y1 = y + r - return self.canvas.create_oval(x0, y0, x1, y1, fill=fill) + return self.canvas.create_oval(x - r, y - r, x + r, y + r, fill=fill, outline="black", width=2) def __update(self): self.canvas.update_idletasks() self.canvas.update() - self.windows.update_idletasks() - self.windows.update() - - def vehicle_go(self): - self.canvas.itemconfig(self.vehicle_red, fill="black") - self.canvas.itemconfig(self.vehicle_yellow, fill="black") - self.canvas.itemconfig(self.vehicle_green, fill="green") - self.canvas.itemconfig(self.pedestrian_red, fill="red") - self.canvas.itemconfig(self.pedestrian_green, fill="black") + # Helper to set lights easily + def _set_lights(self, v_red, v_yel, v_grn, p_red, p_grn): + self.canvas.itemconfig(self.vehicle_red, fill=self.colors["on_red"] if v_red else self.colors["off_red"]) + self.canvas.itemconfig(self.vehicle_yellow, fill=self.colors["on_yellow"] if v_yel else self.colors["off_yellow"]) + self.canvas.itemconfig(self.vehicle_green, fill=self.colors["on_green"] if v_grn else self.colors["off_green"]) + + self.canvas.itemconfig(self.pedestrian_red, fill=self.colors["on_red"] if p_red else self.colors["off_red"]) + self.canvas.itemconfig(self.pedestrian_green, fill=self.colors["on_green"] if p_grn else self.colors["off_green"]) self.__update() - def vehicle_prepare_to_stop(self): - self.canvas.itemconfig(self.vehicle_red, fill="black") - self.canvas.itemconfig(self.vehicle_yellow, fill="yellow") - self.canvas.itemconfig(self.vehicle_green, fill="black") - self.canvas.itemconfig(self.pedestrian_red, fill="red") - self.canvas.itemconfig(self.pedestrian_green, fill="black") + def vehicle_go(self): + self._set_lights(False, False, True, True, False) - self.__update() + def vehicle_prepare_to_stop(self): + self._set_lights(False, True, False, True, False) def vehicle_stop(self): - self.canvas.itemconfig(self.vehicle_red, fill="red") - self.canvas.itemconfig(self.vehicle_yellow, fill="black") - self.canvas.itemconfig(self.vehicle_green, fill="black") - self.canvas.itemconfig(self.pedestrian_red, fill="red") - self.canvas.itemconfig(self.pedestrian_green, fill="black") - - self.__update() + self._set_lights(True, False, False, True, False) def pedestrian_go(self): - self.__pedestrian_press_white() - - self.canvas.itemconfig(self.vehicle_red, fill="red") - self.canvas.itemconfig(self.vehicle_yellow, fill="black") - self.canvas.itemconfig(self.vehicle_green, fill="black") - self.canvas.itemconfig(self.pedestrian_red, fill="black") - self.canvas.itemconfig(self.pedestrian_green, fill="green") - - self.__update() + self.__pedestrian_press_reset() + self._set_lights(True, False, False, False, True) def pedestrian_stop(self): - self.canvas.itemconfig(self.vehicle_red, fill="red") - self.canvas.itemconfig(self.vehicle_yellow, fill="black") - self.canvas.itemconfig(self.vehicle_green, fill="black") - self.canvas.itemconfig(self.pedestrian_red, fill="red") - self.canvas.itemconfig(self.pedestrian_green, fill="black") - - self.__update() + self._set_lights(True, False, False, True, False) def vehicle_prepare_to_go(self): - self.canvas.itemconfig(self.vehicle_red, fill="red") - self.canvas.itemconfig(self.vehicle_yellow, fill="yellow") - self.canvas.itemconfig(self.vehicle_green, fill="black") - self.canvas.itemconfig(self.pedestrian_red, fill="red") - self.canvas.itemconfig(self.pedestrian_green, fill="black") - - self.__update() + self._set_lights(True, True, False, True, False) def pedestrian_press_red(self): - self.canvas.itemconfig(self.pedestrian_press_button, fill="red") - + self.canvas.itemconfig(self.pedestrian_press_button, fill="#e74c3c") # Red button + self.canvas.itemconfig(self.pedestrian_press_label, fill="white") self.__update() - def __pedestrian_press_white(self): - self.canvas.itemconfig(self.pedestrian_press_button, fill="white") - + def __pedestrian_press_reset(self): + self.canvas.itemconfig(self.pedestrian_press_button, fill="#95a5a6") # Grey button + self.canvas.itemconfig(self.pedestrian_press_label, fill="#2c3e50") self.__update() def mainloop(self): self.windows.mainloop() def register_button_event(self, button_press_function): - self.canvas.tag_bind( - self.pedestrian_press_button, "", button_press_function - ) - self.canvas.tag_bind( - self.pedestrian_press_label, "", button_press_function - ) + self.canvas.tag_bind(self.pedestrian_press_button, "", button_press_function) + self.canvas.tag_bind(self.pedestrian_press_label, "", button_press_function)