Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b3748fd
Initial traffic light commit
AgentAntelope Jan 23, 2025
1b7f0f8
Pull out basic traffic light object
AgentAntelope Jan 23, 2025
21a75b5
Implement to_s to simplify traffic output
AgentAntelope Jan 23, 2025
e100c70
Remove fallback
AgentAntelope Jan 23, 2025
92b6039
Move timer progression into traffic light
AgentAntelope Jan 24, 2025
89468f1
Add tests for attrs
AgentAntelope Jan 24, 2025
f2c1d13
Remove accidental comment
AgentAntelope Jan 24, 2025
695956f
Implement basic signal class
AgentAntelope Jan 24, 2025
4fd275a
Add rubocop
AgentAntelope Jan 24, 2025
38781aa
Remove frozen string literals
AgentAntelope Jan 24, 2025
c06bc3a
Make pedestrian_signals a object related to lights
AgentAntelope Jan 24, 2025
46a017e
remove stray frozen string comment
AgentAntelope Jan 24, 2025
276627d
Add pedestrian signal status to lights
AgentAntelope Jan 24, 2025
4a917b4
Move attrs to under initialize
AgentAntelope Jan 24, 2025
c89abb1
Move current state complete into light
AgentAntelope Jan 24, 2025
eebd02f
Extract constants for state
AgentAntelope Jan 24, 2025
584223d
Use declarative methods for setting light colours
AgentAntelope Jan 24, 2025
9d77794
Add comments explaining classes
AgentAntelope Jan 24, 2025
0f24ee3
Implement next_state! on traffic light
AgentAntelope Jan 24, 2025
0268bae
Create method on traffic light for if traffic goes
AgentAntelope Jan 24, 2025
855aa4a
Simplify logic for main loop
AgentAntelope Jan 24, 2025
7e0985a
Extract method in main to progress states
AgentAntelope Jan 24, 2025
1c35b07
Move progression of state into traffic_light
AgentAntelope Jan 26, 2025
8b06f69
Extract traffic light signals as their own object
AgentAntelope Jan 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions katas/4-gilded_traffic_light/fell/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
AllCops:
NewCops: enable
# This is the default on ruby 3.4, so we don't need it
Style/FrozenStringLiteralComment:
EnforcedStyle: never
6 changes: 6 additions & 0 deletions katas/4-gilded_traffic_light/fell/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source 'https://rubygems.org'

ruby '3.4.1'

gem 'minitest'
gem 'rubocop'
44 changes: 44 additions & 0 deletions katas/4-gilded_traffic_light/fell/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
json (2.9.1)
language_server-protocol (3.17.0.3)
minitest (5.25.4)
parallel (1.26.3)
parser (3.3.7.0)
ast (~> 2.4.1)
racc
racc (1.8.1)
rainbow (3.1.1)
regexp_parser (2.10.0)
rubocop (1.71.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.36.2, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.37.0)
parser (>= 3.3.1.0)
ruby-progressbar (1.13.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)

PLATFORMS
arm64-darwin-24
ruby

DEPENDENCIES
minitest
rubocop

RUBY VERSION
ruby 3.4.1p0

BUNDLED WITH
2.6.2
58 changes: 58 additions & 0 deletions katas/4-gilded_traffic_light/fell/README.md
Original file line number Diff line number Diff line change
@@ -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/fell/main.rb
```

7 changes: 7 additions & 0 deletions katas/4-gilded_traffic_light/fell/Rakefile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'minitest/test_task'

Minitest::TestTask.create do |t|
# t.framework = %(require "test/test_helper.rb")
t.libs = %w[test .]
t.test_globs = ['test/**/*_test.rb']
end
19 changes: 19 additions & 0 deletions katas/4-gilded_traffic_light/fell/lib/colorize.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Provides helper methods for colorized text
module Colorize
RED = -"\e[31m"
GREEN = -"\e[32m"
AMBER = -"\e[33m"
RESET = -"\e[0m"

def red(string)
"#{RED}#{string}#{RESET}"
end

def green(string)
"#{GREEN}#{string}#{RESET}"
end

def amber(string)
"#{AMBER}#{string}#{RESET}"
end
end
58 changes: 58 additions & 0 deletions katas/4-gilded_traffic_light/fell/main.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require_relative 'traffic_light'

# Main class providing orchestration of multiple traffic lights
class TrafficLightSystem
def initialize
@lights = [
TrafficLight.new(direction: 'North-South', state: 'red', timer: 10),
TrafficLight.new(direction: 'East-West', state: 'green', timer: 8)
]
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")

# Print traffic lights
puts @lights.map(&:to_s)

progress_lights

# Print pedestrian signals
puts "\nPedestrian signals:"
puts(@lights.map { |l| " #{l.pedestrian_signal_status}" })

puts '---------------------------------'
end

private

def progress_lights
@lights.each do |light|
light.progress!

# TODO: This is only necessary because the timings are such that we will
# have a clash each cycle; an ideal solution would be to change the
# timings, but the existing output remaining the same is a requirement
#
# Nothing to do unless there is goign to be a conflict
next unless @lights.all?(&:allows_traffic?)

# Since we're turning green, then the other light must turn red
@lights.find { it != light }.red!
end
end
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
31 changes: 31 additions & 0 deletions katas/4-gilded_traffic_light/fell/pedestrian_signal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require_relative 'lib/colorize'

# Tracks whether pedestrians can walk or not and provides presentation logic
class PedestrianSignal
include Colorize

WALK = -'WALK'
DONT_WALK = -"DON'T WALK"

def initialize(can_walk:)
@can_walk = can_walk
end

attr_accessor :can_walk

def to_s
if can_walk
can_walk_text
else
cannot_walk_text
end
end

def can_walk_text
green(WALK)
end

def cannot_walk_text
red(DONT_WALK)
end
end
26 changes: 26 additions & 0 deletions katas/4-gilded_traffic_light/fell/test/colorize_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require_relative '../lib/colorize'
require 'minitest/autorun'

class ColorizeTest < Minitest::Test
class ColorizedClass
include Colorize
end

def test_red
test_instance = ColorizeTest::ColorizedClass.new

assert_equal "\e[31mtest_string\e[0m", test_instance.red('test_string')
end

def test_green
test_instance = ColorizeTest::ColorizedClass.new

assert_equal "\e[32mtest_string\e[0m", test_instance.green('test_string')
end

def test_amber
test_instance = ColorizeTest::ColorizedClass.new

assert_equal "\e[33mtest_string\e[0m", test_instance.amber('test_string')
end
end
66 changes: 66 additions & 0 deletions katas/4-gilded_traffic_light/fell/test/main_test.rb
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions katas/4-gilded_traffic_light/fell/test/pedestrian_signal_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require_relative '../pedestrian_signal'
require 'minitest/autorun'

class PedestrianSignalTest < Minitest::Test
def test_can_walk
signal = PedestrianSignal.new(can_walk: true)

assert_equal true, signal.can_walk

signal.can_walk = false

assert_equal false, signal.can_walk
end

def test_can_walk_to_s
signal = PedestrianSignal.new(can_walk: true)

assert_equal "\e[32mWALK\e[0m", signal.to_s
end

def test_cannot_walk_to_s
signal = PedestrianSignal.new(can_walk: false)

assert_equal "\e[31mDON'T WALK\e[0m", signal.to_s
end
end
Loading