use serde::{Deserialize, Serialize};
use super::rider::{RiderPhase, RiderPhaseKind};
use crate::entity::EntityId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum InternalRiderPhase {
Waiting {
stop: EntityId,
},
Boarding {
elevator: EntityId,
stop: EntityId,
},
Riding {
elevator: EntityId,
},
Exiting {
elevator: EntityId,
stop: EntityId,
},
Walking,
Arrived {
stop: EntityId,
},
Abandoned {
stop: EntityId,
},
Resident {
stop: EntityId,
},
}
impl InternalRiderPhase {
#[must_use]
pub const fn at_stop(&self) -> Option<EntityId> {
match *self {
Self::Waiting { stop }
| Self::Boarding { stop, .. }
| Self::Exiting { stop, .. }
| Self::Arrived { stop }
| Self::Abandoned { stop }
| Self::Resident { stop } => Some(stop),
Self::Riding { .. } | Self::Walking => None,
}
}
#[must_use]
#[allow(dead_code)] pub const fn aboard(&self) -> Option<EntityId> {
match *self {
Self::Boarding { elevator, .. }
| Self::Riding { elevator }
| Self::Exiting { elevator, .. } => Some(elevator),
_ => None,
}
}
#[must_use]
pub const fn kind(&self) -> RiderPhaseKind {
match self {
Self::Waiting { .. } => RiderPhaseKind::Waiting,
Self::Boarding { .. } => RiderPhaseKind::Boarding,
Self::Riding { .. } => RiderPhaseKind::Riding,
Self::Exiting { .. } => RiderPhaseKind::Exiting,
Self::Walking => RiderPhaseKind::Walking,
Self::Arrived { .. } => RiderPhaseKind::Arrived,
Self::Abandoned { .. } => RiderPhaseKind::Abandoned,
Self::Resident { .. } => RiderPhaseKind::Resident,
}
}
#[must_use]
pub const fn as_phase(self) -> RiderPhase {
match self {
Self::Waiting { .. } => RiderPhase::Waiting,
Self::Boarding { elevator, .. } => RiderPhase::Boarding(elevator),
Self::Riding { elevator } => RiderPhase::Riding(elevator),
Self::Exiting { elevator, .. } => RiderPhase::Exiting(elevator),
Self::Walking => RiderPhase::Walking,
Self::Arrived { .. } => RiderPhase::Arrived,
Self::Abandoned { .. } => RiderPhase::Abandoned,
Self::Resident { .. } => RiderPhase::Resident,
}
}
#[must_use]
#[allow(dead_code)] pub const fn from_phase(phase: RiderPhase, current_stop: Option<EntityId>) -> Option<Self> {
match (phase, current_stop) {
(RiderPhase::Waiting, Some(stop)) => Some(Self::Waiting { stop }),
(RiderPhase::Boarding(elevator), Some(stop)) => Some(Self::Boarding { elevator, stop }),
(RiderPhase::Riding(elevator), _) => Some(Self::Riding { elevator }),
(RiderPhase::Exiting(elevator), Some(stop)) => Some(Self::Exiting { elevator, stop }),
(RiderPhase::Walking, _) => Some(Self::Walking),
(RiderPhase::Arrived, Some(stop)) => Some(Self::Arrived { stop }),
(RiderPhase::Abandoned, Some(stop)) => Some(Self::Abandoned { stop }),
(RiderPhase::Resident, Some(stop)) => Some(Self::Resident { stop }),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::world::World;
fn ids() -> (EntityId, EntityId, EntityId) {
let mut world = World::new();
(world.spawn(), world.spawn(), world.spawn())
}
#[test]
fn at_stop_returns_stop_for_at_stop_variants() {
let (stop, elev, _) = ids();
assert_eq!(InternalRiderPhase::Waiting { stop }.at_stop(), Some(stop));
assert_eq!(InternalRiderPhase::Arrived { stop }.at_stop(), Some(stop));
assert_eq!(InternalRiderPhase::Abandoned { stop }.at_stop(), Some(stop));
assert_eq!(InternalRiderPhase::Resident { stop }.at_stop(), Some(stop));
assert_eq!(
InternalRiderPhase::Boarding {
elevator: elev,
stop
}
.at_stop(),
Some(stop)
);
assert_eq!(
InternalRiderPhase::Exiting {
elevator: elev,
stop
}
.at_stop(),
Some(stop)
);
}
#[test]
fn at_stop_returns_none_for_in_transit_variants() {
let (_, elev, _) = ids();
assert_eq!(
InternalRiderPhase::Riding { elevator: elev }.at_stop(),
None
);
assert_eq!(InternalRiderPhase::Walking.at_stop(), None);
}
#[test]
fn aboard_returns_elevator_for_aboard_variants() {
let (stop, elev, _) = ids();
assert_eq!(
InternalRiderPhase::Boarding {
elevator: elev,
stop
}
.aboard(),
Some(elev)
);
assert_eq!(
InternalRiderPhase::Riding { elevator: elev }.aboard(),
Some(elev)
);
assert_eq!(
InternalRiderPhase::Exiting {
elevator: elev,
stop
}
.aboard(),
Some(elev)
);
assert_eq!(InternalRiderPhase::Waiting { stop }.aboard(), None);
assert_eq!(InternalRiderPhase::Walking.aboard(), None);
}
#[test]
fn round_trip_through_phase_preserves_state() {
let (stop, elev, _) = ids();
let cases = [
InternalRiderPhase::Waiting { stop },
InternalRiderPhase::Boarding {
elevator: elev,
stop,
},
InternalRiderPhase::Riding { elevator: elev },
InternalRiderPhase::Exiting {
elevator: elev,
stop,
},
InternalRiderPhase::Walking,
InternalRiderPhase::Arrived { stop },
InternalRiderPhase::Abandoned { stop },
InternalRiderPhase::Resident { stop },
];
for state in cases {
let phase = state.as_phase();
let stop_back = state.at_stop();
assert_eq!(
InternalRiderPhase::from_phase(phase, stop_back),
Some(state),
"round-trip mismatch for {state:?} (phase={phase:?}, stop={stop_back:?})"
);
}
}
#[test]
fn riderphase_kind_matches_state_kind() {
let (stop, elev, _) = ids();
let pairs: &[(InternalRiderPhase, RiderPhase)] = &[
(InternalRiderPhase::Waiting { stop }, RiderPhase::Waiting),
(
InternalRiderPhase::Boarding {
elevator: elev,
stop,
},
RiderPhase::Boarding(elev),
),
(
InternalRiderPhase::Riding { elevator: elev },
RiderPhase::Riding(elev),
),
(
InternalRiderPhase::Exiting {
elevator: elev,
stop,
},
RiderPhase::Exiting(elev),
),
(InternalRiderPhase::Walking, RiderPhase::Walking),
(InternalRiderPhase::Arrived { stop }, RiderPhase::Arrived),
(
InternalRiderPhase::Abandoned { stop },
RiderPhase::Abandoned,
),
(InternalRiderPhase::Resident { stop }, RiderPhase::Resident),
];
for (state, phase) in pairs {
assert_eq!(state.kind(), phase.kind(), "kind mismatch for {state:?}");
}
}
#[test]
fn from_phase_rejects_inconsistent_inputs() {
let (_, elev, _) = ids();
assert_eq!(
InternalRiderPhase::from_phase(RiderPhase::Waiting, None),
None
);
assert_eq!(
InternalRiderPhase::from_phase(RiderPhase::Boarding(elev), None),
None
);
assert_eq!(
InternalRiderPhase::from_phase(RiderPhase::Arrived, None),
None
);
}
#[test]
fn riding_ignores_supplied_stop() {
let (stale_stop, elev, _) = ids();
assert_eq!(
InternalRiderPhase::from_phase(RiderPhase::Riding(elev), Some(stale_stop)),
Some(InternalRiderPhase::Riding { elevator: elev }),
);
}
#[test]
fn kind_matches_phase_kind() {
let (stop, elev, _) = ids();
let pairs: &[(InternalRiderPhase, RiderPhaseKind)] = &[
(
InternalRiderPhase::Waiting { stop },
RiderPhaseKind::Waiting,
),
(
InternalRiderPhase::Boarding {
elevator: elev,
stop,
},
RiderPhaseKind::Boarding,
),
(
InternalRiderPhase::Riding { elevator: elev },
RiderPhaseKind::Riding,
),
(
InternalRiderPhase::Exiting {
elevator: elev,
stop,
},
RiderPhaseKind::Exiting,
),
(InternalRiderPhase::Walking, RiderPhaseKind::Walking),
(
InternalRiderPhase::Arrived { stop },
RiderPhaseKind::Arrived,
),
(
InternalRiderPhase::Abandoned { stop },
RiderPhaseKind::Abandoned,
),
(
InternalRiderPhase::Resident { stop },
RiderPhaseKind::Resident,
),
];
for (state, expected_kind) in pairs {
assert_eq!(state.kind(), *expected_kind);
assert_eq!(state.as_phase().kind(), *expected_kind);
}
}
}