← Back to Portfolio

Object-Oriented Design · CSCI 4448

OmniFlow: City Traffic Simulator

A real-time 2D traffic simulator where cars, buses, emergency vehicles, bikes, and pedestrians all move through one shared, polymorphic agent model, built to show clean object-oriented design from the engine up.

Java 17 JavaFX Gradle JUnit 5
5 polymorphic agent types
4 classic design patterns applied
5 JUnit 5 test suites
2 person team

Overview

OmniFlow simulates a busy downtown intersection in real time: vehicles obeying signals, buses dwelling at stops, pedestrians crossing in phases, and emergency vehicles cutting through it all. It was the final project for CU Boulder's Object-Oriented Analysis & Design course, built with a teammate (Evan Mohan).

The point was never hyper-realistic traffic modeling. It was to demonstrate clean object orientation: polymorphism, dependency injection, design patterns, a responsive JavaFX UI, and basic persistence, all structured so the architecture is easy to reason about and explain. We deliberately narrowed an early, broader idea down to a single polished scene because it was easier to finish well and defend.

One interface, five behaviors

Every moving thing on the map (car, bus, emergency vehicle, bike, pedestrian) is updated through a single shared Agent interface. The simulation engine stores and steps agents polymorphically, so each type varies its own behavior through small override hooks rather than sprawling if/else trees or switch statements. Adding a new agent type means writing a class, not editing the engine.

Simulation behaviors

The UI exposes start / pause / single-step controls, a speed slider, agent-type filtering, click-to-select agent details, manual emergency-vehicle spawning, and save/load of the layout state.

Design patterns

Factory

An AgentFactory builds agents from shared type names; the engine and persistence layer code against an AgentProvider abstraction, never concrete classes.

Strategy

A MapLayout interface lets layouts be swapped in; the downtown layout owns its own signal timing, queueing, crossing rules, and preemption logic.

Template Method

A BaseAgent owns the shared update algorithm, and subclasses customize just the parts they need through small override hooks.

Observer / MVC

A controller wires UI actions to the model; JavaFX listeners react to control and selection changes, and rendering reads model state instead of owning logic.

Design philosophy

The engine depends on abstractions, not implementations: SimulationEngine takes its world, agent provider, and random source through its constructor (dependency injection), and stores agents behind the Agent interface. The result is a simulation you can extend and test without reaching into its core.

Testing

Coverage focuses where it matters most, the model and simulation logic rather than exhaustive UI testing, across five JUnit 5 suites: