Skip to content

Commit a518fec

Browse files
committed
extract printer class
1 parent 5bdc139 commit a518fec

File tree

4 files changed

+249
-0
lines changed

4 files changed

+249
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# The Gilded Traffic Light
2+
3+
Refactor a messy legacy traffic light system into a proper domain model.
4+
5+
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.
6+
7+
## Problem Description
8+
9+
Imagine a simplified but messy traffic controller. We have:
10+
11+
- Two directions of traffic (North-South and East-West).
12+
- Each direction cycles through Red → Green → Amber → Red.
13+
- Pedestrian signals can only display “WALK” when that direction is red.
14+
15+
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.
16+
17+
## Requirements and Constraints
18+
19+
### Requirements
20+
21+
- The system should prevent conflicting green lights and only allow pedestrians to cross on red.
22+
- The code must run forever unless interrupted.
23+
- Do not break the existing console output or sequence logic.
24+
- Preserve the timers for each colour transition.
25+
26+
### Constraints
27+
28+
- You must not remove or skip any stage of the light cycle (Red, Green, Amber).
29+
- Keep the pedestrian signals tied to the traffic light states.
30+
31+
## Examples and Test Cases
32+
33+
A sample test file has been provided. Ensure these tests continue to pass as you refactor and rearrange the code.
34+
35+
## Instructions
36+
37+
Open the single-file Ruby script that contains the messy procedural code.
38+
Run it directly to see the console output and confirm how the sequence changes over time.
39+
40+
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.
41+
42+
(Best to run the tests after each change to ensure everything still passes).
43+
44+
## Evaluation Criteria
45+
46+
- All tests pass for the final refactored solution.
47+
- Quality of design: Classes and methods have clear responsibilities.
48+
- Maintainability: Future changes (e.g. adding more directions or sensors) should be straightforward.
49+
50+
51+
---
52+
53+
## How to run your main file
54+
55+
```
56+
bundle exec ruby katas/4-gilded_traffic_light/edward.s/main.rb
57+
```
58+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class Printer
2+
def initialize(out: STDOUT)
3+
@out = out
4+
end
5+
6+
def print_traffic_lights(lights:)
7+
lights.each do |light|
8+
# Quick inline way to colour states, done in a messy way rather than a dedicated method.
9+
state_colour = case light[:state]
10+
when "red" then "\e[31m#{light[:state]}\e[0m"
11+
when "green" then "\e[32m#{light[:state]}\e[0m"
12+
when "amber" then "\e[33m#{light[:state]}\e[0m"
13+
else light[:state]
14+
end
15+
puts("Direction: #{light[:direction]}, State: #{state_colour}, Time left: #{light[:timer]}s")
16+
end
17+
end
18+
19+
def print_pedestrian_signals(signals:)
20+
puts "\nPedestrian signals:"
21+
signals.each do |direction, can_walk|
22+
signal_text = can_walk ? "\e[32mWALK\e[0m" : "\e[31mDON'T WALK\e[0m"
23+
puts " #{direction}: #{signal_text}"
24+
end
25+
26+
puts "---------------------------------"
27+
end
28+
29+
private
30+
31+
attr_reader :out
32+
end
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# frozen_string_literal: true
2+
3+
require 'bundler/inline'
4+
require_relative 'lib/printer'
5+
6+
gemfile do
7+
source 'https://rubygems.org'
8+
9+
gem 'minitest'
10+
end
11+
12+
class TrafficLightSystem
13+
def initialize(out: STDOUT)
14+
@lights = [
15+
{ direction: "North-South", state: "red", timer: 10 },
16+
{ direction: "East-West", state: "green", timer: 8 }
17+
]
18+
19+
@pedestrian_signals = {
20+
"North-South" => false,
21+
"East-West" => false
22+
}
23+
24+
@printer = Printer.new(out:)
25+
end
26+
27+
# Advances the system by one second: prints current status, decrements timers,
28+
# and updates states as needed, all in one giant procedural block.
29+
def run
30+
# Clear the screen each time (so this is consistent with the original request).
31+
# You could skip this if you don't want console clearing in tests, but it's here for completeness.
32+
system("clear") || system("cls")
33+
34+
printer.print_traffic_lights(lights:)
35+
36+
# Update timers and transition states in one big chunk
37+
@lights.each do |light|
38+
light[:timer] -= 1
39+
if light[:timer] <= 0
40+
# Big case statement for state transitions
41+
case light[:state]
42+
when "red"
43+
# Switch from red -> green
44+
light[:state] = "green"
45+
light[:timer] = 8
46+
pedestrian_signals[light[:direction]] = false
47+
# Force the opposite light red if it's green or amber
48+
opposite = @lights.find { |l| l[:direction] != light[:direction] }
49+
if %w[green amber].include?(opposite[:state])
50+
opposite[:state] = "red"
51+
opposite[:timer] = 10
52+
pedestrian_signals[opposite[:direction]] = true
53+
end
54+
55+
when "green"
56+
# Switch from green -> amber
57+
light[:state] = "amber"
58+
light[:timer] = 3
59+
pedestrian_signals[light[:direction]] = false
60+
61+
when "amber"
62+
# Switch from amber -> red
63+
light[:state] = "red"
64+
light[:timer] = 10
65+
pedestrian_signals[light[:direction]] = true
66+
67+
else
68+
# Fallback
69+
light[:state] = "red"
70+
light[:timer] = 10
71+
pedestrian_signals[light[:direction]] = true
72+
end
73+
end
74+
end
75+
76+
printer.print_pedestrian_signals(signals: pedestrian_signals)
77+
end
78+
private
79+
80+
attr_reader :printer, :lights, :pedestrian_signals
81+
end
82+
83+
# Only run the perpetual loop if this file is executed directly.
84+
# Each call to #run advances the system by one second. Tests can call #run manually.
85+
if $PROGRAM_NAME == __FILE__
86+
system = TrafficLightSystem.new
87+
loop do
88+
system.run
89+
sleep 1
90+
end
91+
92+
93+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# traffic_light_system_test.rb
2+
3+
require_relative 'main'
4+
require "minitest/autorun"
5+
6+
class TrafficLightSystemTest < Minitest::Test
7+
def test_initial_output
8+
system = TrafficLightSystem.new
9+
out, _err = capture_io do
10+
system.run
11+
end
12+
13+
assert_includes out, "Direction: North-South, State: \e[31mred\e[0m, Time left: 10s"
14+
assert_includes out, "Direction: East-West, State: \e[32mgreen\e[0m, Time left: 8s"
15+
16+
assert_includes out, "North-South: \e[31mDON'T WALK\e[0m"
17+
assert_includes out, "East-West: \e[31mDON'T WALK\e[0m"
18+
end
19+
20+
def test_red_transitions_to_green
21+
system = TrafficLightSystem.new
22+
10.times { system.run }
23+
24+
out, _err = capture_io do
25+
system.run
26+
end
27+
28+
assert_includes out, "Direction: North-South, State: \e[32mgreen\e[0m, Time left: 8s"
29+
end
30+
31+
def test_green_transitions_to_amber
32+
system = TrafficLightSystem.new
33+
8.times { system.run }
34+
35+
out, _err = capture_io do
36+
system.run
37+
end
38+
39+
assert_includes out, "Direction: East-West, State: \e[33mamber\e[0m, Time left: 3s"
40+
end
41+
42+
def test_amber_transitions_to_red
43+
system = TrafficLightSystem.new
44+
11.times { system.run } # now East-West should be amber(3)
45+
46+
out, _err = capture_io do
47+
system.run
48+
end
49+
50+
assert_includes out, "Direction: East-West, State: \e[31mred\e[0m, Time left: 8s"
51+
assert_includes out, "East-West: \e[32mWALK\e[0m"
52+
end
53+
54+
def test_opposite_direction_forced_red
55+
system = TrafficLightSystem.new
56+
57+
10.times { system.run }
58+
out, _err = capture_io do
59+
system.run
60+
end
61+
62+
assert_includes out, "Direction: North-South, State: \e[32mgreen\e[0m, Time left: 8s"
63+
assert_includes out, "Direction: East-West, State: \e[31mred\e[0m, Time left: 9s"
64+
assert_includes out, "East-West: \e[32mWALK\e[0m"
65+
end
66+
end

0 commit comments

Comments
 (0)