Skip to content

Commit

Permalink
V0.0.4
Browse files Browse the repository at this point in the history
  • Loading branch information
domhnallmorr committed Mar 16, 2024
1 parent c2a70a0 commit 1756799
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 5 deletions.
Binary file modified images/preview.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
customtkinter.set_default_color_theme("blue") # Themes: blue (default), dark-blue, green

app = customtkinter.CTk() # create CTk window like you do with the Tk window
app.title("Race Engine V0.0.3")
app.title("Race Engine V0.0.4")
controller = race_engine_controller.RaceEngineController(app)

app.after(0, lambda:app.state("zoomed"))
Expand Down
6 changes: 6 additions & 0 deletions src/race_engine_controller/race_engine_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ def simulate_race(self):

self.model.advance()
self.view.timing_screen.update_view(self.model.get_data_for_view())

# GET PIT STRATEGY FROM VIEW
driver1_data = self.view.timing_screen.strategy_editor_driver1.get_data()
driver2_data = self.view.timing_screen.strategy_editor_driver2.get_data()
self.model.update_player_drivers_strategy(driver1_data, driver2_data)

self.app.after(3000, self.simulate_race)

def pause_resume(self):
Expand Down
10 changes: 9 additions & 1 deletion src/race_engine_model/race_engine_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ def __init__(self):

self.retirements = []

self.player_driver1 = self.get_particpant_model_by_name("Nico Rosberg")
self.player_driver2 = self.get_particpant_model_by_name("Michael Schumacher")

def log_event(self, event):
logging.info(f"Lap {self.current_lap}: {event}")

Expand Down Expand Up @@ -324,4 +327,9 @@ def get_data_for_view(self):
for p in self.participants:
data["laptimes"][p.name] = copy.deepcopy(p.laptimes)

return data
return data

def update_player_drivers_strategy(self, driver1_data, driver2_data):
self.player_driver1.update_player_pitstop_laps(driver1_data)
self.player_driver2.update_player_pitstop_laps(driver2_data)

16 changes: 15 additions & 1 deletion src/race_engine_model/race_engine_particpant_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def calculate_laptime(self, gap_ahead):

self.laptime = self.base_laptime + random.randint(0, 700) + self.car_model.fuel_effect + self.car_model.tyre_wear + dirty_air_effect

if self.current_lap == self.pit1_lap:
if self.current_lap in [self.pit1_lap, self.pit2_lap, self.pit3_lap]:
self.status = "pitting in"

if "pitting in" in self.status:
Expand Down Expand Up @@ -107,6 +107,8 @@ def recalculate_laptime_when_passed(self, revised_laptime):

def calculate_pitstop_laps(self):
self.pit1_lap = random.randint(19, 32)
self.pit2_lap = None
self.pit3_lap = None

def calculate_if_retires(self):
self.retires = False
Expand All @@ -121,3 +123,15 @@ def update_fastest_lap(self):
self.fastest_laptime = self.laptime
elif min(self.laptimes) == self.laptime:
self.fastest_laptime = self.laptime

def update_player_pitstop_laps(self, data):
'''
data is a dict optained from the view
{
"pit1_lap": 24,
"pit2_lap": ... etc
}
'''
self.pit1_lap = data["pit1_lap"]
self.pit2_lap = data["pit2_lap"]
self.pit3_lap = data["pit3_lap"]
206 changes: 206 additions & 0 deletions src/race_engine_view/custom_widgets/strategy_editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
from tkinter import ttk
from tkinter import *

import customtkinter



class StrategyEditor:
def __init__(self, parent, view, driver_name, start_col, start_row):
self.parent = parent
self.view = view
self.start_col = start_col
self.number_laps = 56

self.one_third_distance = int(self.number_laps / 3)
self.half_distance = int(self.number_laps / 2)
self.two_third_distance = int(2 * (self.number_laps / 3))
self.one_quarter_distance = int(self.number_laps / 4)
self.three_quarters_distance = int(3 * (self.number_laps / 4))

customtkinter.CTkLabel(self.parent, text=driver_name, anchor=W).grid(row=start_row, column=start_col, columnspan=4, padx=self.view.padx, pady=self.view.pady, sticky="EW")

self.setup_variables()

self.progress_bars = []
self.lap_labels = []
self.minus_buttons = []
self.plus_buttons = []

row = start_row + 1
for idx in [0, 1, 2]:
self.setup_edit_widgets(idx, row)
row += 2

self.set_default_pit_laps(1)

def setup_variables(self):
self.pit1_lap = None
self.pit2_lap = None
self.pit3_lap = None

self.pit1_var = customtkinter.StringVar(value="on")
self.pit2_var = customtkinter.StringVar(value="off")
self.pit3_var = customtkinter.StringVar(value="off")

self.pit_number_vars = [self.pit1_var, self.pit2_var, self.pit3_var]

def setup_edit_widgets(self, idx, row):
btn_width = 40

reduced_pady = 1
l = customtkinter.CTkLabel(self.parent, text="Some Lap")
l.grid(row=row, column=self.start_col + 2, sticky="EW", padx=self.view.padx, pady=(self.view.pady, reduced_pady))
self.lap_labels.append(l)

m = customtkinter.CTkButton(self.parent, text= "-", width=btn_width, command=lambda idx=idx:self.minus_lap_event(idx))
m.grid(row=row+1, column=self.start_col + 1, sticky="EW", padx=self.view.padx, pady=(reduced_pady, self.view.pady))
self.minus_buttons.append(m)

c = customtkinter.CTkCheckBox(self.parent, text=f"{idx + 1} Stop", variable=self.pit_number_vars[idx], onvalue="on", offvalue="off", width=10,
command=lambda idx=idx: self.pit_strategy_combo_event(idx))
c.grid(row=row, column=self.start_col, rowspan=2, sticky="SE", padx=self.view.padx, pady=(reduced_pady, self.view.pady))

p = customtkinter.CTkProgressBar(self.parent, orientation="horizontal")
p.grid(row=row+1, column=self.start_col + 2, sticky="EW", padx=self.view.padx, pady=(reduced_pady, self.view.pady))
self.progress_bars.append(p)

p = customtkinter.CTkButton(self.parent, text= "+", width=btn_width, command=lambda idx=idx:self.plus_lap_event(idx))
p.grid(row=row+1, column=self.start_col + 3, sticky="EW", padx=self.view.padx, pady=(reduced_pady, self.view.pady))
self.plus_buttons.append(p)

def pit_strategy_combo_event(self, idx_clicked):
for idx, var in enumerate(self.pit_number_vars):
if idx == idx_clicked:
var.set("on")
else:
var.set("off")

self.set_default_pit_laps(idx_clicked + 1)

def set_default_pit_laps(self, number_of_stops):
self.update_lap_label(1, None)
self.update_lap_label(2, None)

# disable buttons
self.minus_buttons[1].configure(state="disabled")
self.minus_buttons[2].configure(state="disabled")

self.plus_buttons[1].configure(state="disabled")
self.plus_buttons[2].configure(state="disabled")

# SET PROGRESS BARS
self.progress_bars[1].set(0)
self.progress_bars[2].set(0)

if number_of_stops == 1:
self.progress_bars[0].set(0.5)
self.pit1_lap = self.half_distance
self.update_lap_label(0, self.pit1_lap)

elif number_of_stops == 2:
self.progress_bars[0].set(0.33)
self.pit1_lap = self.one_third_distance
self.update_lap_label(0, self.pit1_lap)

self.progress_bars[1].set(0.66)
self.pit2_lap = self.two_third_distance
self.update_lap_label(1, self.pit2_lap)
self.minus_buttons[1].configure(state="normal")
self.plus_buttons[1].configure(state="normal")

elif number_of_stops == 3:
self.progress_bars[0].set(0.25)
self.pit1_lap = self.one_quarter_distance
self.update_lap_label(0, self.pit1_lap)

self.progress_bars[1].set(0.50)
self.pit2_lap = self.half_distance
self.update_lap_label(1, self.pit2_lap)
self.minus_buttons[1].configure(state="normal")
self.plus_buttons[1].configure(state="normal")

self.progress_bars[2].set(0.75)
self.pit3_lap = self.three_quarters_distance
self.update_lap_label(2, self.pit3_lap)
self.minus_buttons[2].configure(state="normal")
self.plus_buttons[2].configure(state="normal")

def update_lap_label(self, label_idx, pit_lap):
if pit_lap == None:
self.lap_labels[label_idx].configure(text="N/A")
else:
percentage = int((pit_lap/self.number_laps)*100)
self.lap_labels[label_idx].configure(text=f"Lap {pit_lap} / {self.number_laps} ({percentage}%)")
self.progress_bars[label_idx].set(percentage/100)

def minus_lap_event(self, idx):
process = True

# AVOID LAP 1 or BELOW
if idx == 0 and self.pit1_lap < 3:
process = False

# AVOID STOP 2 BEFORE STOP 1
if idx == 1 and self.pit2_lap - self.pit1_lap == 1:
process = False

# AVOID STOP 3 BEFORE STOP 2
if idx == 2 and self.pit3_lap - self.pit2_lap == 1:
process = False

if process is True:
if idx == 0:
self.pit1_lap -= 1
self.update_lap_label(idx, self.pit1_lap)

elif idx == 1:
self.pit2_lap -= 1
self.update_lap_label(idx, self.pit2_lap)

elif idx == 2:
self.pit3_lap -= 1
self.update_lap_label(idx, self.pit3_lap)

def plus_lap_event(self, idx):
process = True

# AVOID LAST 1 or ABOVE
if idx == 0 and self.pit1_lap == self.number_laps - 1:
process = False

if idx == 1 and self.pit2_lap == self.number_laps - 1:
process = False

if idx == 2 and self.pit3_lap == self.number_laps - 1:
process = False

# AVOID STOP 1 AFTTER STOP 2
if idx == 0 and self.pit2_lap is not None and self.pit2_lap - self.pit1_lap == 1:
process = False

# AVOID STOP 2 AFTER STOP 2
if idx == 1 and self.pit3_lap is not None and self.pit3_lap - self.pit2_lap == 1:
process = False

if process is True:
if idx == 0:
self.pit1_lap += 1
self.update_lap_label(idx, self.pit1_lap)

elif idx == 1:
self.pit2_lap += 1
self.update_lap_label(idx, self.pit2_lap)

elif idx == 2:
self.pit3_lap += 1
self.update_lap_label(idx, self.pit3_lap)

def get_data(self):
data = {
"pit1_lap": self.pit1_lap,
"pit2_lap": self.pit2_lap,
"pit3_lap": self.pit3_lap,
}

return data
3 changes: 3 additions & 0 deletions src/race_engine_view/race_engine_icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def setup_icons(view):
data= "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABDklEQVR4nO3dwQ3CQBAEwQsPyD8CCGSR3/6BJbetqgBOo+0Abi0AAICYmXnNzGei1n5v1XbD5xFBsjEuFmTzPiJI2rr4XkFOJkiMIDGCxAgSI0iMIDGCxAgSI0iMIDGCxAgSI0iMIDGCxAgSI0iMIDGCxAgSI0iMIHcLAgC/mrh18b2CnEyQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEFiBIkRJEaQGEHuFgQAfjVx6+J7BTmZIDGCxAgSI0iMIDGCxAgSI0iMIDGCxAgSI0iMIDGCxAgSI0iMIDGCxAgSI0iMIDGC3DCID+5jH9w/t4cmau33Vm03fPwdBAAAYB3rCwuHoscz8qtzAAAAAElFTkSuQmCC"
view.timing_icon2 = customtkinter.CTkImage(light_image=decode_base64_image(size, data), size=size)

data = "iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAYAAAA4qEECAAAACXBIWXMAAAsTAAALEwEAmpwYAAACdklEQVR4nO2cTU7DMBBGZ1W4DD9nKwuWyZJjFHoDFlXPUzhGqg8ZORKUluZnPJlJvrcG9fnJmCSNLUIIIYQQQgghSwFADeBFgoII/lmyxbdsVP8TSd+yUf0vSPqUjep/RdKXbFT/jpJuY4fw7ynpLnYI/4GSLWtT2fP+TxhOHSFyogFwbyJ73v8xO8BtbIXILa9FRf8fwxY61N4jJz6KSHYbxyf0qD1HTjSqgv3GMnbZKBcbwLOyHNTk+o9Fm0pbUDW2TITryCViy0S4j6wdWyYiRGTlf443JrK/vW8UvGtr6bEzew/g1tB3BeA9xEyOGhuRI/8YxHrktemu5DKSl4v0GUNpPDyf+SY9u0i31emOb2D0XYnYIyKnMRwAbADcSRTQ7U9XdRmZ4jNdAMOBLzayZYDFR7YIwcgGQRjZIAyWviZbBAIjl48NRi4/s8HIw0CPcIxsc8u86/gz5o9hQ4HxT9qWe3VhGJuRDWIzskFsRh4DGLo84NLhMnILl5Cu8PLOAPCGxSTyirfgjiLLiN9ZNOBjUt+RWzizrwB+lVUe8MvZmJFbuIxk+AKNAeArYfOKPOtlJG/73ebNkkfEf233mMfyBuBBPJA3sPNFdOebhvaBtlaEOq8j+mahUOd1tHD7m0FkmM2Kv/4alJ3ZmqccyERo+ReLrX2UhEyE5hjUYxc4rwOqgv3GArexeTBK3FNoDqpycznqp0DsTRHBbmNIt9UauD8prJly2296duH+OLYZHTC4dh955BESlTghlH9P2UqcEcq/o2wlTgnlf0W2EueE8r8g60tyLv4nsj4l5+KfL/1sL4EUie5PCCGEEEIIIdKXL/FeSve/F5iQAAAAAElFTkSuQmCC"
view.pit_icon2 = customtkinter.CTkImage(light_image=decode_base64_image(size, data), size=size)


def decode_base64_image(size, data):

Expand Down
34 changes: 32 additions & 2 deletions src/race_engine_view/timing_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure

from race_engine_view.custom_widgets import timing_screen_table
from race_engine_view.custom_widgets import timing_screen_table, strategy_editor

class TimingScreen(customtkinter.CTkFrame):
def __init__(self, master, view):
Expand Down Expand Up @@ -44,6 +44,11 @@ def config_grid(self):
self.player_frame.grid_columnconfigure(0, weight=1)
self.player_frame.grid_columnconfigure(1, weight=1)

# note, frames are used as seperators on the strategy frame, these are expanded by the commands below
self.strategy_frame.grid_columnconfigure(6, weight=1)
self.strategy_frame.grid_columnconfigure(12, weight=1)
self.strategy_frame.grid_rowconfigure(10, weight=1)

def setup_frames(self):
self.top_frame = customtkinter.CTkFrame(self)
self.top_frame.grid(row=0, column=0, columnspan=2, sticky="EW", pady=self.view.pady)
Expand All @@ -54,6 +59,9 @@ def setup_frames(self):
self.button_frame = customtkinter.CTkFrame(self)
self.button_frame.grid(row=2, column=0, sticky="NSEW", pady=self.view.pady, padx=(0, self.view.padx))

self.strategy_frame = customtkinter.CTkFrame(self)
self.strategy_frame.grid(row=2, column=1, sticky="NSEW", pady=self.view.pady, padx=(self.view.padx, 0))

self.laptimes_frame = customtkinter.CTkFrame(self)
self.laptimes_frame.grid(row=2, column=1, sticky="NSEW", pady=self.view.pady, padx=(self.view.padx, 0))

Expand Down Expand Up @@ -88,6 +96,7 @@ def setup_labels(self):
self.commentary_label = customtkinter.CTkLabel(self.commentary_frame, text="")
self.commentary_label.grid(row=1, column=1, padx=self.view.padx, pady=self.view.pady, sticky="EW")


# driver labels
customtkinter.CTkLabel(self.driver1_frame, text="Rosberg").grid(row=1, column=1, sticky="EW")
customtkinter.CTkLabel(self.driver2_frame, text="Schumacher").grid(row=1, column=1, sticky="EW")
Expand All @@ -106,6 +115,9 @@ def setup_buttons(self):
self.lap_times_button = customtkinter.CTkButton(self.button_frame, text="Lap Times", command=lambda window="laptimes": self.show_window(window), image=self.view.stopwatch_icon2, anchor="w")
self.lap_times_button.grid(row=1, column=0, sticky="EW", padx=self.view.padx, pady=self.view.pady)

self.strategy_button = customtkinter.CTkButton(self.button_frame, text="Strategy", command=lambda window="strategy": self.show_window(window), image=self.view.pit_icon2, anchor="w")
self.strategy_button.grid(row=2, column=0, sticky="EW", padx=self.view.padx, pady=self.view.pady)

self.start_btn = customtkinter.CTkButton(self.button_frame, text="Start Race", command=self.start_race, image=self.view.play_icon2, anchor="w")
self.start_btn.grid(row=10, column=0, padx=self.view.padx, pady=self.view.pady, sticky="SEW")

Expand All @@ -127,6 +139,20 @@ def setup_widgets(self):
self.driver2_laptime_combo.grid(row=1, column=3, sticky="EW", padx=(5, 5), pady=self.view.pady)
self.driver2_laptime_combo.set("Michael Schumacher")

# STRATEGY CHECKBOXES
self.driver1_pit1_var = customtkinter.StringVar(value="on")
self.driver1_pit2_var = customtkinter.StringVar(value="off")
self.driver1_pit3_var = customtkinter.StringVar(value="off")
self.driver1_pit_vars = [self.driver1_pit1_var, self.driver1_pit2_var, self.driver1_pit3_var]

# STRATEGY EDITORS
self.strategy_editor_driver1 = strategy_editor.StrategyEditor(self.strategy_frame, self.view, "Rosberg", 1, 1)

# hack to create a seperator
customtkinter.CTkFrame(self.strategy_frame, width=10).grid(column=6, row=1, rowspan=20, padx=(100, 10), sticky="NSEW")
self.strategy_editor_driver2 = strategy_editor.StrategyEditor(self.strategy_frame, self.view, "Schumacher", 7, 1)
customtkinter.CTkFrame(self.strategy_frame, width=10).grid(column=12, row=1, rowspan=20, padx=50, sticky="NSEW")

def setup_plots(self):
# LAPTIMES
self.laptimes_figure = Figure(figsize=(5,5), dpi=100)
Expand Down Expand Up @@ -163,6 +189,8 @@ def show_window(self, window):
self.timing_frame.tkraise()
elif window == "laptimes":
self.laptimes_frame.tkraise()
elif window == "strategy":
self.strategy_frame.tkraise()

def update_laptimes_plot(self, event=None):
self.laptimes_axis.cla()
Expand All @@ -186,4 +214,6 @@ def update_laptimes_plot(self, event=None):
self.laptimes_axis.set_yticks(default_y_ticks)
self.laptimes_axis.set_yticklabels([self.view.milliseconds_to_minutes_seconds(t) for t in default_y_ticks])

self.laptimes_canvas.draw()
self.laptimes_canvas.draw()


0 comments on commit 1756799

Please sign in to comment.