Overview
A discrete-event simulation of a physiotherapy center, built as a Cairo University DSA course project. The system manages patient flow across three treatment types — electro-therapy, ultrasound-therapy, and gym exercises — while scheduling shared resources, enforcing appointment rules, and producing a detailed statistics report at the end of each run.
The project was implemented entirely in C++ from scratch, with no use of the STL. All data structures — stacks, queues, and priority queues — were built and owned by me, then composed into the scheduler logic.
Patient lifecycle
Every patient moves through a strict set of states driven by timestep comparisons. Arrival before appointment triggers the early list; arrival after incurs a waiting penalty before the patient is re-inserted into the correct queue.
A late patient with appointment time PT and arrival time VT incurs a penalty of
(VT − PT) / 2 timesteps
before joining a waiting list, but is sorted in that list by PT + penalty
— preserving relative appointment order.
Data structure decisions
Each list in the system has a different access pattern, so each got a different structure. The constraint was that everything had to derive from the hand-rolled Stack, Queue, or Priority Queue base classes from the lab — no STL allowed.
| List | Structure | Reason |
|---|---|---|
| ALL Patients | LinkedQueue | Input is sorted by arrival time; FIFO dequeue naturally processes patients in order. |
| Early Patients | priQueue (min-heap by PT) | Must always serve the earliest appointment first. Extended with a Reschedule() method that picks a random node and re-inserts with a new PT. |
| Late Patients | priQueue (min-heap by PT+penalty) | Same earliest-first priority, but effective priority is shifted by the late penalty. |
| E-Wait / U-Wait | EU_Waitlist (sorted LinkedQueue) | Sorted insertion by appointment time. Also exposes calcTreatmentLatency() for recovering patient routing. |
| X-Wait (gym) | X_Waitlist | Same sorted insertion, plus probabilistic cancellation — randomly picks and removes a patient if they meet the cancel criteria. |
| In-Treatment | priQueue (min-heap by finish time) | The patient whose treatment ends soonest always dequeues first, without scanning the whole list. |
| Finished Patients | ArrayStack | Output requires descending finish-time order. Since patients finish and are pushed onto the stack chronologically, popping naturally gives the most-recently-finished patient first. |
Recovering vs. normal patients
Normal patients follow their treatment list in the exact input order. Recovering patients can take treatments in any order, so each time one is about to join a waiting list, the scheduler re-orders their treatment queue to minimise total waiting time.
The key insight: a recovering patient should start with the treatment whose waiting list
currently has the lowest total treatment backlog (treatment latency). The
organizeTreatmentList() function
computes this live at the moment of each transition.
void Scheduler::organizeTreatmentList(Patient* input)
{
if (input->getType() == 0) return; // normal patient — skip
LinkedQueue<Treatment*>& Temp = input->getTreatmentlist();
if (Temp.isEmpty()) return;
// Sample latency of each waiting list
struct Latency { int latency; char type; };
Latency arr[3] = {
{ E_Waiting.calcTreatmentLatency('E'), 'E' },
{ U_Waiting.calcTreatmentLatency('U'), 'U' },
{ X_Waiting.calcTreatmentLatency('X'), 'X' }
};
// 3-element insertion sort (always exactly 3 types)
if (arr[0].latency > arr[1].latency) std::swap(arr[0], arr[1]);
if (arr[1].latency > arr[2].latency) std::swap(arr[1], arr[2]);
if (arr[0].latency > arr[1].latency) std::swap(arr[0], arr[1]);
// Drain and re-enqueue in latency order
int count = Temp.count();
Treatment* treatments[3] = { nullptr, nullptr, nullptr };
for (int i = 0; i < count; ++i) Temp.dequeue(treatments[i]);
for (int i = 0; i < 3; ++i)
for (int j = 0; j < count; ++j)
if (treatments[j] && treatments[j]->getType() == arr[i].type) {
Temp.enqueue(treatments[j]);
treatments[j] = nullptr;
break;
}
}
Resource management
Electro and ultrasound devices are simple: one patient per device at a time, managed as a queue of available resources. Gym rooms are more involved — each room has a capacity, and multiple patients can share it simultaneously. A full room is removed from the available list and only returns when its first occupant leaves.
The Treatment base class
handles this through two pure virtual methods: canAssign()
checks resource availability, and moveToWait()
dispatches the patient to the correct waiting list via the scheduler. Deriving
ETherapy,
UTherapy, and
XTherapy from this base keeps
the scheduler's assignment loop uniform across all three treatment types.
Probabilistic events
Two random events fire each timestep based on configurable probabilities loaded from the input file:
- Cancellation (Pcancel): a random patient in the X-therapy waiting list, whose gym session is their last remaining treatment, may cancel and move directly to finished.
- Rescheduling (Presc): a random patient in the early list may receive a new, later appointment time and be re-inserted into the early priority queue at their new position.
Output and statistics
At simulation end, the program produces an output file listing every patient sorted by finish time
in descending order, followed by aggregate statistics: average waiting and treatment times
broken down by patient type, cancellation and rescheduling percentages, and the average late penalty.
The ArrayStack of finished patients
makes this ordering free — no post-sort needed.
The program supports two modes: interactive (pauses each timestep and prints all list states to console) and silent (runs to completion and writes only the output file).