using System;
namespace Eu07.BlackBox;
/// What the driver of the user's train went by: the lights it is to show depend on it.
public enum Movement : byte
{
/// No driver, or standing by.
Other = 0,
/// A train run: Pc 1 or Pc 2 at the head, Pc 5 at the tail.
Train = 1,
/// Shunting: Tb 1 at both ends.
Shunting = 2,
/// A recording made before it was written.
Unknown = 0xFF
}
/// The light signals at an end of a train.
public enum HeadCode
{
Off,
/// The head of a train: both headlights and the upper one.
Pc1,
/// The head of a train on the wrong track: the left red marker, the right headlight and the upper one.
Pc2,
/// The tail of a train: both red markers.
Pc5,
/// Shunting: one headlight.
Tb1,
/// Any other pattern.
Other
}
/// The light signals a train shows and those it is to show, as the simulator's driver sets them.
public static class HeadCodes
{
private const Lights Signals = Lights.HeadlightLeft | Lights.RedMarkerLeft | Lights.HeadlightUpper | Lights.HeadlightRight |
Lights.RedMarkerRight | Lights.RearEndSignals;
/// The signal the lamps given show; the high beams and the auxiliary lamps tell nothing.
public static HeadCode Of(Lights lights)
{
var lit = lights & Signals;
if ((lit & Lights.RearEndSignals) != 0)
lit = (lit & ~Lights.RearEndSignals) | Lights.RedMarkerLeft | Lights.RedMarkerRight;
return lit switch
{
Lights.None => HeadCode.Off,
Lights.HeadlightLeft | Lights.HeadlightRight | Lights.HeadlightUpper => HeadCode.Pc1,
Lights.RedMarkerLeft | Lights.HeadlightRight | Lights.HeadlightUpper => HeadCode.Pc2,
Lights.RedMarkerLeft | Lights.RedMarkerRight => HeadCode.Pc5,
Lights.HeadlightLeft or Lights.HeadlightRight => HeadCode.Tb1,
_ => HeadCode.Other
};
}
/// The signal the head of the train is to show, null when nothing tells it.
public static HeadCode? RequiredHead(CabSample sample) => sample.Movement switch
{
Movement.Train => sample.LightHintHead < 0 ? HeadCode.Pc1 : Of((Lights)sample.LightHintHead),
Movement.Shunting => HeadCode.Tb1,
_ => null
};
/// The signal the tail of the train is to show, null when nothing tells it.
public static HeadCode? RequiredTail(CabSample sample) => sample.Movement switch
{
Movement.Train => sample.LightHintTail < 0 ? HeadCode.Pc5 : Of((Lights)sample.LightHintTail),
Movement.Shunting => HeadCode.Tb1,
_ => null
};
/// Whether the head of the train showed what it was to; null when nothing tells it.
public static bool? HeadRight(CabSample sample) => sample.Movement switch
{
Movement.Train => Meets(sample.LightsHead, sample.EquippedHead, sample.LightHintHead, HeadCode.Pc1),
Movement.Shunting => Shows(HeadCode.Tb1, sample.LightsHead, sample.EquippedHead),
_ => null
};
/// Whether the tail of the train showed what it was to; null when nothing tells it.
public static bool? TailRight(CabSample sample) => sample.Movement switch
{
Movement.Train => Meets(sample.LightsTail, sample.EquippedTail, sample.LightHintTail, HeadCode.Pc5),
Movement.Shunting => Shows(HeadCode.Tb1, sample.LightsTail, sample.EquippedTail),
_ => null
};
/// Whether both ends showed what they were to; null when nothing tells it.
public static bool? Right(CabSample sample) =>
HeadRight(sample) is { } head && TailRight(sample) is { } tail ? head && tail : null;
private const Lights HighBeams = Lights.HighBeamLeft | Lights.HighBeamRight;
private const Lights Auxiliary = Lights.AuxiliaryLeft | Lights.AuxiliaryRight;
// the pattern asked for at an end, the usual one when none is: the driver sets one asked for as it is
private static bool Meets(Lights lit, Lights? equipped, short hint, HeadCode usual)
{
if (hint < 0)
return Shows(usual, lit, equipped);
var pattern = (Lights)hint;
if (usual == HeadCode.Pc1 && pattern == (Lights.RedMarkerLeft | Lights.HeadlightRight | Lights.HeadlightUpper))
return Shows(HeadCode.Pc2, lit, equipped);
return (lit & ~HighBeams) == (equipped is { } lamps ? pattern & lamps : pattern);
}
///
/// Whether the lamps lit at an end show the signal, as the simulator's driver checks it against the lamps
/// the vehicle has there: a vehicle without the upper headlight shows Pc 1 with the two others, one with
/// auxiliary lamps may show them instead, and one with no lamps for it the rear-end signals. A recording
/// made before the lamps were written has the pattern alone. The high beams tell nothing.
///
public static bool Shows(HeadCode code, Lights lit, Lights? equipped)
{
if (equipped is not { } lamps)
return Of(lit) == code;
lit &= ~HighBeams;
bool auxiliary = (lamps & Auxiliary) != 0;
switch (code)
{
case HeadCode.Pc1:
{
var pattern = lamps & (Lights.HeadlightLeft | Lights.HeadlightRight | Lights.HeadlightUpper);
var fallback = lamps & (Auxiliary | Lights.HeadlightUpper);
return (pattern != 0 && lit == pattern) || (auxiliary && lit == fallback) ||
(pattern == 0 && fallback == 0 && lit == Lights.RearEndSignals);
}
case HeadCode.Pc2:
{
var pattern = lamps & (Lights.RedMarkerLeft | Lights.HeadlightRight | Lights.HeadlightUpper);
var fallback = lamps & (Lights.RedMarkerLeft | Lights.AuxiliaryRight | Lights.HeadlightUpper);
return lit == pattern || (auxiliary && lit == fallback);
}
case HeadCode.Pc5:
{
var pattern = lamps & (Lights.RedMarkerLeft | Lights.RedMarkerRight);
var fallback = lamps & Lights.RearEndSignals;
return (pattern != 0 && lit == pattern) || (fallback != 0 && lit == fallback);
}
case HeadCode.Tb1:
// one headlight, of those there are
return lit == (lamps & Lights.HeadlightLeft) || lit == (lamps & Lights.HeadlightRight);
case HeadCode.Off:
return lit == Lights.None;
default:
return false;
}
}
/// The name of a signal as the rules give it.
public static string Name(HeadCode code) => code switch
{
HeadCode.Pc1 => "Pc 1",
HeadCode.Pc2 => "Pc 2",
HeadCode.Pc5 => "Pc 5",
HeadCode.Tb1 => "Tb 1",
HeadCode.Off => "—",
_ => "?"
};
}