diff --git a/katas/4-gilded_traffic_light/eddbot/README.md b/katas/4-gilded_traffic_light/eddbot/README.md new file mode 100644 index 0000000..9c09ee5 --- /dev/null +++ b/katas/4-gilded_traffic_light/eddbot/README.md @@ -0,0 +1,58 @@ +# The Gilded Traffic Light + +Refactor a messy legacy traffic light system into a proper domain model. + +You’ll start with a single-file procedural Ruby script that simulates an intersection with two directions of traffic lights and pedestrian signals. Your job is to gradually extract meaningful objects and ensure the behaviour doesn't change. + +## Problem Description + +Imagine a simplified but messy traffic controller. We have: + +- Two directions of traffic (North-South and East-West). +- Each direction cycles through Red → Green → Amber → Red. +- Pedestrian signals can only display “WALK” when that direction is red. + +In the provided code, everything (timers, states, and transitions) is lumped into one file and one giant method. Refactor it into a more object-oriented design, preserving functionality. + +## Requirements and Constraints + +### Requirements + +- The system should prevent conflicting green lights and only allow pedestrians to cross on red. +- The code must run forever unless interrupted. +- Do not break the existing console output or sequence logic. +- Preserve the timers for each colour transition. + +### Constraints + +- You must not remove or skip any stage of the light cycle (Red, Green, Amber). +- Keep the pedestrian signals tied to the traffic light states. + +## Examples and Test Cases + +A sample test file has been provided. Ensure these tests continue to pass as you refactor and rearrange the code. + +## Instructions + +Open the single-file Ruby script that contains the messy procedural code. +Run it directly to see the console output and confirm how the sequence changes over time. + +Refactor the system one step at a time. Introduce appropriate classes to develop a deeper and more meaningful domain model, while preserving all the existing behaviour. + +(Best to run the tests after each change to ensure everything still passes). + +## Evaluation Criteria + +- All tests pass for the final refactored solution. +- Quality of design: Classes and methods have clear responsibilities. +- Maintainability: Future changes (e.g. adding more directions or sensors) should be straightforward. + + +--- + +## How to run your main file + +``` +bundle exec ruby katas/4-gilded_traffic_light/edward.s/main.rb +``` + diff --git a/katas/4-gilded_traffic_light/eddbot/lib/printer.rb b/katas/4-gilded_traffic_light/eddbot/lib/printer.rb new file mode 100644 index 0000000..39b5f85 --- /dev/null +++ b/katas/4-gilded_traffic_light/eddbot/lib/printer.rb @@ -0,0 +1,37 @@ +class Printer + def initialize(out: STDOUT) + @out = out + end + + def print_traffic_lights(lights:) + lights.each do |light| + puts("Direction: #{light[:direction]}, State: #{state_colour(state: light[:state])}, Time left: #{light[:timer]}s") + end + end + + def print_pedestrian_signals(signals:) + puts "\nPedestrian signals:" + signals.each do |direction, can_walk| + signal_text = can_walk ? colourizer(:green, "WALK") : colourizer(:red, "DON'T WALK") + puts " #{direction}: #{signal_text}" + end + + puts "---------------------------------" + end + + private + + def state_colour(state:) = colourizer(state.to_sym, state) + + def colourizer(color, input) + case color + when :red then "\e[31m#{input}\e[0m" + when :green then "\e[32m#{input}\e[0m" + when :amber then "\e[33m#{input}\e[0m" + else + input + end + end + + attr_reader :out +end \ No newline at end of file diff --git a/katas/4-gilded_traffic_light/eddbot/main.rb b/katas/4-gilded_traffic_light/eddbot/main.rb new file mode 100644 index 0000000..14e53d9 --- /dev/null +++ b/katas/4-gilded_traffic_light/eddbot/main.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'bundler/inline' +require_relative 'lib/printer' + +gemfile do + source 'https://rubygems.org' + + gem 'minitest' +end + +class TrafficLightSystem + def initialize(out: STDOUT) + @lights = [ + { direction: "North-South", state: "red", timer: 10 }, + { direction: "East-West", state: "green", timer: 8 } + ] + + @pedestrian_signals = { + "North-South" => false, + "East-West" => false + } + + @printer = Printer.new(out:) + end + + # Advances the system by one second: prints current status, decrements timers, + # and updates states as needed, all in one giant procedural block. + def run + # Clear the screen each time (so this is consistent with the original request). + # You could skip this if you don't want console clearing in tests, but it's here for completeness. + system("clear") || system("cls") + + printer.print_traffic_lights(lights:) + + # Update timers and transition states in one big chunk + lights.each do |light| + light[:timer] -= 1 + if light[:timer] <= 0 + # Big case statement for state transitions + case light[:state] + when "red" + # Switch from red -> green + light[:state] = "green" + light[:timer] = 8 + pedestrian_signals[light[:direction]] = false + # Force the opposite light red if it's green or amber + opposite = lights.find { |l| l[:direction] != light[:direction] } + if %w[green amber].include?(opposite[:state]) + opposite[:state] = "red" + opposite[:timer] = 10 + pedestrian_signals[opposite[:direction]] = true + end + + when "green" + # Switch from green -> amber + light[:state] = "amber" + light[:timer] = 3 + pedestrian_signals[light[:direction]] = false + + when "amber" + # Switch from amber -> red + light[:state] = "red" + light[:timer] = 10 + pedestrian_signals[light[:direction]] = true + + else + # Fallback + light[:state] = "red" + light[:timer] = 10 + pedestrian_signals[light[:direction]] = true + end + end + end + + printer.print_pedestrian_signals(signals: pedestrian_signals) + end + private + + attr_reader :printer, :lights, :pedestrian_signals +end + +# Only run the perpetual loop if this file is executed directly. +# Each call to #run advances the system by one second. Tests can call #run manually. +if $PROGRAM_NAME == __FILE__ + system = TrafficLightSystem.new + loop do + system.run + sleep 1 + end + + +end diff --git a/katas/4-gilded_traffic_light/eddbot/test.rb b/katas/4-gilded_traffic_light/eddbot/test.rb new file mode 100644 index 0000000..79aa6e8 --- /dev/null +++ b/katas/4-gilded_traffic_light/eddbot/test.rb @@ -0,0 +1,66 @@ +# traffic_light_system_test.rb + +require_relative 'main' +require "minitest/autorun" + +class TrafficLightSystemTest < Minitest::Test + def test_initial_output + system = TrafficLightSystem.new + out, _err = capture_io do + system.run + end + + assert_includes out, "Direction: North-South, State: \e[31mred\e[0m, Time left: 10s" + assert_includes out, "Direction: East-West, State: \e[32mgreen\e[0m, Time left: 8s" + + assert_includes out, "North-South: \e[31mDON'T WALK\e[0m" + assert_includes out, "East-West: \e[31mDON'T WALK\e[0m" + end + + def test_red_transitions_to_green + system = TrafficLightSystem.new + 10.times { system.run } + + out, _err = capture_io do + system.run + end + + assert_includes out, "Direction: North-South, State: \e[32mgreen\e[0m, Time left: 8s" + end + + def test_green_transitions_to_amber + system = TrafficLightSystem.new + 8.times { system.run } + + out, _err = capture_io do + system.run + end + + assert_includes out, "Direction: East-West, State: \e[33mamber\e[0m, Time left: 3s" + end + + def test_amber_transitions_to_red + system = TrafficLightSystem.new + 11.times { system.run } # now East-West should be amber(3) + + out, _err = capture_io do + system.run + end + + assert_includes out, "Direction: East-West, State: \e[31mred\e[0m, Time left: 8s" + assert_includes out, "East-West: \e[32mWALK\e[0m" + end + + def test_opposite_direction_forced_red + system = TrafficLightSystem.new + + 10.times { system.run } + out, _err = capture_io do + system.run + end + + assert_includes out, "Direction: North-South, State: \e[32mgreen\e[0m, Time left: 8s" + assert_includes out, "Direction: East-West, State: \e[31mred\e[0m, Time left: 9s" + assert_includes out, "East-West: \e[32mWALK\e[0m" + end +end