using Car_simulation; using SFML.Window; using SFML.Graphics; using SFML.System; using System.Collections.Generic; using System; internal class Program { Car car = new Car(); private bool _isRunning = true; private RenderWindow _window; private Font _font; private List _displayTexts = new List(); private RectangleShape _tachometerBackground; private RectangleShape _tachometerNeedle; private RectangleShape _speedometerBackground; private RectangleShape _speedometerNeedle; // Colors private Color _backgroundColor = new Color(20, 20, 30); private Color _textColor = new Color(220, 220, 220); private Color _highlightColor = new Color(0, 150, 255); private Color _warningColor = new Color(255, 100, 100); // Timing for physics private Clock _clock = new Clock(); private Time _timePerUpdate = Time.FromSeconds(1.0f / 60.0f); // 60 FPS physics private Time _accumulatedTime = Time.Zero; private long _updateCount = 0; private Dictionary _previousKeyStates = new Dictionary(); private Dictionary _currentKeyStates = new Dictionary(); private static void Main(string[] args) { Program program = new Program(); program.Run(); } private void Run() { _window = new RenderWindow(new VideoMode(1000, 800), "Car Simulation", Styles.Default); _window.SetVisible(true); _window.SetFramerateLimit(60); _window.SetKeyRepeatEnabled(false); _window.Closed += (sender, e) => _isRunning = false; _window.KeyPressed += OnKeyPressed; _window.KeyReleased += OnKeyReleased; // Load font try { _font = new Font("arial.ttf"); } catch { _font = new Font("C:/Windows/Fonts/arial.ttf"); } InitializeDisplay(); InitializeTrackedKeys(); _clock.Restart(); while (_isRunning && _window.IsOpen) { _window.DispatchEvents(); Time elapsed = _clock.Restart(); _accumulatedTime += elapsed; while (_accumulatedTime >= _timePerUpdate) { ProcessInput(_timePerUpdate.AsSeconds()); car.Update(_timePerUpdate.AsSeconds()); _accumulatedTime -= _timePerUpdate; _updateCount++; } UpdateDisplay(); _window.Display(); UpdatePreviousKeyStates(); } _window.Close(); Console.WriteLine($"\nSimulation stopped after {_updateCount} updates"); } private void InitializeDisplay() { // Initialize display texts for (int i = 0; i < 30; i++) { Text text = new Text("", _font, 16); text.FillColor = _textColor; text.Position = new Vector2f(20, 20 + i * 24); _displayTexts.Add(text); } // Tachometer _tachometerBackground = new RectangleShape(new Vector2f(200, 200)); _tachometerBackground.Position = new Vector2f(700, 50); _tachometerBackground.FillColor = new Color(40, 40, 50); _tachometerBackground.OutlineThickness = 2; _tachometerBackground.OutlineColor = Color.White; _tachometerNeedle = new RectangleShape(new Vector2f(80, 4)); _tachometerNeedle.Position = new Vector2f(800, 150); _tachometerNeedle.FillColor = Color.Red; _tachometerNeedle.Origin = new Vector2f(70, 2); // Speedometer _speedometerBackground = new RectangleShape(new Vector2f(200, 200)); _speedometerBackground.Position = new Vector2f(700, 300); _speedometerBackground.FillColor = new Color(40, 40, 50); _speedometerBackground.OutlineThickness = 2; _speedometerBackground.OutlineColor = Color.White; _speedometerNeedle = new RectangleShape(new Vector2f(80, 4)); _speedometerNeedle.Position = new Vector2f(800, 400); _speedometerNeedle.FillColor = Color.Green; _speedometerNeedle.Origin = new Vector2f(70, 2); } private void UpdateDisplay() { _window.Clear(_backgroundColor); UpdateDisplayTexts(); foreach (var text in _displayTexts) { if (!string.IsNullOrEmpty(text.DisplayedString)) _window.Draw(text); } DrawGauges(); DrawKeyBindings(); } private void UpdateDisplayTexts() { // Clear all text for (int i = 0; i < _displayTexts.Count; i++) { _displayTexts[i].DisplayedString = ""; _displayTexts[i].FillColor = _textColor; } // Update text - using safe indexing int line = 0; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "ENGINE"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" RPM: {car.Engine.RPM,7:F0}"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Torque: {car.Engine.GetTorqueOutput(),7:F0} Nm"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Throttle: {car.Engine.GetActualThrottle() * 100,6:F1}%"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Power: {car.Engine.CurrentPower / 1000,6:F1} kW"; if (line < _displayTexts.Count) { _displayTexts[line].DisplayedString = $" Status: {(car.Engine.IsRunning ? "RUNNING" : "STALLED")}"; _displayTexts[line].FillColor = car.Engine.IsRunning ? _textColor : _warningColor; line++; } line++; // Blank line if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "DRIVETRAIN"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Gear: {car.Drivetrain.GetCurrentGearName(),3} (Ratio: {car.Drivetrain.GearRatio:F2}:1)"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Clutch: {car.ClutchInput * 100,6:F1}% disengaged"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Clutch Torque: {car.Drivetrain.ClutchTorque,6:F0} Nm"; if (line < _displayTexts.Count) { _displayTexts[line].DisplayedString = $" Clutch Slip: {car.Drivetrain.GetClutchSlipPercent(),6:F1}%"; _displayTexts[line].FillColor = car.Drivetrain.GetClutchSlipPercent() > 50 ? _warningColor : _textColor; line++; } if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Transmitted: {car.Drivetrain.TransmittedPower / 1000,6:F1} kW"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Speed Diff: {car.Drivetrain.GetSpeedDifferenceRPM(),6:F0} RPM"; line++; // Blank line if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "VEHICLE"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Speed: {car.Speed * 3.6f,7:F1} km/h"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Wheel RPM: {car.WheelSystem.RPM,7:F0}"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Brake: {car.BrakeInput * 100,6:F1}%"; line++; // Blank line if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "FORCES"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Total Resistance: {car.CalculateTotalResistanceForce(),6:F1} N"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Drag: {car.CalculateDragForce(),6:F1} N"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Rolling: {car.CalculateRollingResistanceForce(),6:F1} N"; line++; // Blank line if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = "ENERGY"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Engine: {car.Engine.FlywheelEnergy,7:F0} J"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Total: {car.WheelSystem.TotalEnergy,7:F0} J"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Wheel Rotation: {car.WheelSystem.GetRotationalEnergy(),7:F0} J"; if (line < _displayTexts.Count) _displayTexts[line++].DisplayedString = $" Car Translation: {car.WheelSystem.GetTranslationalEnergy(),7:F0} J"; } private void DrawGauges() { _window.Draw(_tachometerBackground); float rpmRatio = Math.Clamp(car.Engine.RPM / 8000f, 0f, 1f); float tachometerAngle = -90 + (270 * rpmRatio); _tachometerNeedle.Rotation = tachometerAngle; _window.Draw(_tachometerNeedle); Text tachLabel = new Text("RPM", _font, 20); tachLabel.FillColor = Color.White; tachLabel.Position = new Vector2f(770, 70); _window.Draw(tachLabel); Text rpmText = new Text($"{car.Engine.RPM:F0}", _font, 24); rpmText.FillColor = car.Engine.RPM > 7000 ? _warningColor : Color.White; rpmText.Position = new Vector2f(765, 100); _window.Draw(rpmText); _window.Draw(_speedometerBackground); float speedRatio = Math.Clamp(car.Speed * 3.6f / 200f, 0f, 1f); float speedometerAngle = -90 + (270 * speedRatio); _speedometerNeedle.Rotation = speedometerAngle; _window.Draw(_speedometerNeedle); Text speedLabel = new Text("SPEED", _font, 20); speedLabel.FillColor = Color.White; speedLabel.Position = new Vector2f(770, 320); _window.Draw(speedLabel); Text speedText = new Text($"{car.Speed * 3.6f:F1} km/h", _font, 24); speedText.FillColor = Color.White; speedText.Position = new Vector2f(750, 350); _window.Draw(speedText); Text gearText = new Text($"GEAR {car.Drivetrain.GetCurrentGearName()}", _font, 28); gearText.FillColor = _highlightColor; gearText.Position = new Vector2f(750, 520); _window.Draw(gearText); } private void DrawKeyBindings() { Text controls = new Text("CONTROLS\n\nW/S: Throttle/Brake\nUp/Down: Clutch\nLeft/Right: Gear Up/Down\nSpace: Toggle Force Clutch\nESC: Quit", _font, 14); controls.FillColor = new Color(180, 180, 180); controls.Position = new Vector2f(250, 625); _window.Draw(controls); } private void InitializeTrackedKeys() { var keys = new[] { Keyboard.Key.W, Keyboard.Key.S, Keyboard.Key.Down, Keyboard.Key.Up, Keyboard.Key.Space, Keyboard.Key.Escape, Keyboard.Key.Right, Keyboard.Key.Left }; foreach (var key in keys) { _previousKeyStates[key] = false; _currentKeyStates[key] = false; } } private void OnKeyPressed(object sender, KeyEventArgs e) { if (_currentKeyStates.ContainsKey(e.Code)) _currentKeyStates[e.Code] = true; } private void OnKeyReleased(object sender, KeyEventArgs e) { if (_currentKeyStates.ContainsKey(e.Code)) _currentKeyStates[e.Code] = false; } private void ProcessInput(float deltaTime) { // Throttle/Brake if (_currentKeyStates[Keyboard.Key.W]) car.ThrottleInput += deltaTime * 4f; else car.ThrottleInput -= deltaTime * 8f; if (_currentKeyStates[Keyboard.Key.S]) car.BrakeInput += deltaTime * 4f; else car.BrakeInput -= deltaTime * 8f; car.ThrottleInput = Math.Clamp(car.ThrottleInput, 0f, 1f); car.BrakeInput = Math.Clamp(car.BrakeInput, 0f, 1f); // Clutch if (_currentKeyStates[Keyboard.Key.Up]) car.ClutchInput += deltaTime * 0.5f; if(_currentKeyStates[Keyboard.Key.Down]) car.ClutchInput -= deltaTime * 0.5f; car.ClutchInput = Math.Clamp(car.ClutchInput, 0f, 1f); // Toggle force clutch if (_currentKeyStates[Keyboard.Key.Space] && !_previousKeyStates[Keyboard.Key.Space]) car.ForceClutch = !car.ForceClutch; // Gear changes if (_currentKeyStates[Keyboard.Key.Right] && !_previousKeyStates[Keyboard.Key.Right]) car.Drivetrain.GearUp(); if (_currentKeyStates[Keyboard.Key.Left] && !_previousKeyStates[Keyboard.Key.Left]) car.Drivetrain.GearDown(); // Quit if (_currentKeyStates[Keyboard.Key.Escape]) _isRunning = false; } private void UpdatePreviousKeyStates() { var keys = new List(_currentKeyStates.Keys); foreach (var key in keys) { if (_previousKeyStates.ContainsKey(key)) _previousKeyStates[key] = _currentKeyStates[key]; } } }