commit eb897e18aebd9caf4d03083973b7385a9f545b31 Author: Chris Davoren Date: Wed Oct 18 13:19:32 2023 +1000 Initial commit of files. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..19cd4ab --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Traffic Light System + +## Problem Description + +### Summary + +This test is a take home test, please return once completed + +### Requirements + +In this test we would like you to implement a traffic light system. We are required to have 4 sets of lights, as follows. + +- Lights 1: Traffic is travelling south +- Lights 2: Traffic is travelling west +- Lights 3: Traffic is travelling north +- Lights 4: Traffic is travelling east + +The lights in which traffic is travelling on the same axis can be green at the same time. During normal hours all lights stay green for 20 seconds, but during peak times north and south lights are green for 40 seconds while west and east are green for 10 seconds. Peak hours are 0800 to 1000 and 1700 to 1900. Yellow lights are shown for 5 seconds before red lights are shown. Red lights stay on until the cross-traffic is red for at least 4 seconds, once a red light goes off then the green is shown for the required time(eg the sequence is reset). + +Bonus: At this intersection north bound traffic has a green right-turn signal, which stops the south bound traffic and allows north bound traffic to turn right. This is green at the end of north/south green light and stays green for 10 seconds. During this time north bound is green, north right-turn is green and all other lights are red. + +### Implementation/Outcomes + +1. Implement a front-end and backend (you can use ‘dotnet new’ templates of your choice) +2. The backend will contain the logic and state of the running traffic lights. The front-end will be a visual representation of the traffic lights, with the data served from the backend. +3. There’s no need to have a perfect design on the front end, something simple and functional is fine (unless this is an area of strength you would like to show off). Noting* we will review the client side code. +4. There’s no need to implement entity framework (or similar) to store the data in a database, a in-memory store is fine +5. Code needs to follow architecture & best practices for enterprise grade systems + +Note: Code will be evaluated not just for function, but on the quality of the code. + diff --git a/state-read.py b/state-read.py new file mode 100644 index 0000000..21575eb --- /dev/null +++ b/state-read.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 + +import sys +import json +import datetime +import time + +def get_state_index(states, test_time : datetime.time): + state_result = None + + for i, state in enumerate(states): + if i == len(states) - 1: + return(i) + if test_time >= state["timestart"] and test_time < states[i+1]["timestart"]: + return(i) + + # Should NOT be reached since last state assumed to last until midnight + # TODO: Raise exception? + return None + +def simulate(states, start_time, run_time=90, time_factor=1.0): + simulation_start_time = time.time() + + simulation_start_datetime = datetime.datetime.fromisoformat("1900-01-01 " + start_time.isoformat()) + + current_state_index = get_state_index(states, simulation_start_datetime.time()) + current_cycle_index = 0 + + next_changeover_time = simulation_start_time + (states[current_state_index]["cycles"][current_cycle_index]["duration"] / time_factor) + + print("Starting with simulation time {}".format(simulation_start_datetime.time())) + print("Initial data: {}".format(states[current_state_index]["cycles"][current_cycle_index])) + print("Starting simulation of {} second(s) with time factor {}".format(run_time, time_factor)) + + while (run_time is None) or (run_time is not None and time.time() < simulation_start_time + run_time): + now = time.time() + + current_simulation_datetime = simulation_start_datetime + datetime.timedelta(seconds=int((now - simulation_start_time) * time_factor)) + timed_state_index = get_state_index(states, current_simulation_datetime.time()) + + if now >= next_changeover_time: + timed_state_index = get_state_index(states, current_simulation_datetime.time()) + if timed_state_index != current_state_index and current_cycle_index == len(states[current_state_index]["cycles"])-1: + # Safe to change states + print() + print("{} : Changing STATES from {} to {}".format(current_simulation_datetime.time(), states[current_state_index]["name"], states[timed_state_index]["name"])) + print(" Old cycle data: {}".format(states[current_state_index]["cycles"][current_cycle_index])) + + current_state_index = timed_state_index + current_cycle_index = 0 + + print(" New cycle data: {}".format(states[current_state_index]["cycles"][current_cycle_index])) + print(" Next changeover in {} second(s)".format(states[current_state_index]["cycles"][current_cycle_index]["duration"])) + else: + next_cycle_index = (current_cycle_index + 1) % len(states[current_state_index]["cycles"]) + + print() + print("{} : Changing cycle from {} to {}".format(current_simulation_datetime.time(), current_cycle_index, next_cycle_index)) + if current_state_index != timed_state_index: + print(" [state change pending]") + print(" Old cycle data: {}".format(states[current_state_index]["cycles"][current_cycle_index])) + print(" New cycle data: {}".format(states[current_state_index]["cycles"][next_cycle_index])) + print(" Next changeover in {} second(s)".format(states[current_state_index]["cycles"][next_cycle_index]["duration"])) + + current_cycle_index = next_cycle_index + + next_changeover_time = next_changeover_time + (states[current_state_index]["cycles"][current_cycle_index]["duration"] / time_factor) + else: + print(".", end="") + sys.stdout.flush() + + time.sleep(1.0 / time_factor) + + +def main(): + states = json.load(open("states.json")) + + # Essential due to assumed ordering further below + states.sort(key=lambda x : x["timestart"]) + + for state in states: + time = datetime.time.fromisoformat(state["timestart"]) + print("Name: {}\t\tTime start: {}".format(state["name"], time)) + print(" Number of cycles: {}".format(len(state["cycles"]))) + state["timestart"] = datetime.time.fromisoformat(state["timestart"]) + + test_times = [ + "00:00:00", + "07:59:59", + "08:00:00", + "09:59:59", + "10:00:00", + "16:59:59", + "17:00:00", + "18:59:59", + "19:00:00", + "23:59:59", + ] + + for test_time in test_times: + test_time_obj = datetime.time.fromisoformat(test_time) + + print("State matching time {} is '{}'".format(test_time, states[get_state_index(states, test_time_obj)]["name"])) + + simulate(states, datetime.time.fromisoformat("07:59"), 60, 4.0) + +if __name__ == '__main__': + main() diff --git a/states.json b/states.json new file mode 100644 index 0000000..57b2a90 --- /dev/null +++ b/states.json @@ -0,0 +1,77 @@ +[ + { + "name" : "offpeak-morning", + "timestart" : "00:00", + "cycles" : [ + { "duration" : 20, "north" : "green", "south" : "green", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 4, "north" : "green", "south" : "amber", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 2, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 10, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "green" }, + { "duration" : 4, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "amber" }, + { "duration" : 2, "north" : "red", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 20, "north" : "red", "south" : "red", "east" : "green", "west" : "green", "north-right" : "red" }, + { "duration" : 4, "north" : "red", "south" : "red", "east" : "amber", "west" : "amber", "north-right" : "red" }, + { "duration" : 2, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" } + ] + }, + { + "name" : "peak-morning", + "timestart" : "08:00", + "cycles" : [ + { "duration" : 40, "north" : "green", "south" : "green", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 4, "north" : "green", "south" : "amber", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 2, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 10, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "green" }, + { "duration" : 4, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "amber" }, + { "duration" : 2, "north" : "red", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 40, "north" : "red", "south" : "red", "east" : "green", "west" : "green", "north-right" : "red" }, + { "duration" : 4, "north" : "red", "south" : "red", "east" : "amber", "west" : "amber", "north-right" : "red" }, + { "duration" : 2, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" } + ] + }, + { + "name" : "offpeak-middle", + "timestart" : "10:00", + "cycles" : [ + { "duration" : 20, "north" : "green", "south" : "green", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 4, "north" : "green", "south" : "amber", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 2, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 10, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "green" }, + { "duration" : 4, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "amber" }, + { "duration" : 2, "north" : "red", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 20, "north" : "red", "south" : "red", "east" : "green", "west" : "green", "north-right" : "red" }, + { "duration" : 4, "north" : "red", "south" : "red", "east" : "amber", "west" : "amber", "north-right" : "red" }, + { "duration" : 2, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" } + ] + }, + { + "name" : "peak-afternoon", + "timestart" : "17:00", + "cycles" : [ + { "duration" : 40, "north" : "green", "south" : "green", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 4, "north" : "green", "south" : "amber", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 2, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 10, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "green" }, + { "duration" : 4, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "amber" }, + { "duration" : 2, "north" : "red", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 40, "north" : "red", "south" : "red", "east" : "green", "west" : "green", "north-right" : "red" }, + { "duration" : 4, "north" : "red", "south" : "red", "east" : "amber", "west" : "amber", "north-right" : "red" }, + { "duration" : 2, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" } + ] + }, + { + "name" : "offpeak-evening", + "timestart" : "19:00", + "cycles" : [ + { "duration" : 20, "north" : "green", "south" : "green", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 4, "north" : "green", "south" : "amber", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 2, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 10, "north" : "green", "south" : "red", "east" : "red", "west" : "red", "north-right" : "green" }, + { "duration" : 4, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "amber" }, + { "duration" : 2, "north" : "red", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" }, + { "duration" : 20, "north" : "red", "south" : "red", "east" : "green", "west" : "green", "north-right" : "red" }, + { "duration" : 4, "north" : "red", "south" : "red", "east" : "amber", "west" : "amber", "north-right" : "red" }, + { "duration" : 2, "north" : "amber", "south" : "red", "east" : "red", "west" : "red", "north-right" : "red" } + ] + } +]