diff --git a/C7/Game.cs b/C7/Game.cs index e784ee49..1d084e03 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -213,10 +213,14 @@ public void processEngineMessages(GameData gameData) { // F6 is the science advisor. // TODO: Move the F* key strings to a set of constants/enum. EmitSignal(SignalName.ShowSpecificAdvisor, "F6"); - Tech tech = gameData.techs.Find(x => x.id == gameData.GetHumanPlayers()[0].currentlyResearchedTech); + Player player = gameData.GetHumanPlayers()[0]; + Tech tech = gameData.techs.Find(x => x.id == player.currentlyResearchedTech); - // TODO: calculate research speed. - EmitSignal(SignalName.UpdateTechProgress, tech.Name, -1); + if (tech != null) { + EmitSignal(SignalName.UpdateTechProgress, tech.Name, player.EstimateTurnsToResearch(tech)); + } else { + EmitSignal(SignalName.UpdateTechProgress, "Not selected", (int)1e9); + } break; case MsgUpdateUiAfterSliderChange mUUASC: // F1 is the science advisor. @@ -348,6 +352,13 @@ private void OnPlayerStartTurn() { Player player = gameDataAccess.gameData.GetHumanPlayers()[0]; EmitSignal(SignalName.TurnStarted, turnNumber, player.gold, /*goldPerTurn=*/0); + + Tech tech = gameDataAccess.gameData.techs.Find(x => x.id == player.currentlyResearchedTech); + if (tech != null) { + EmitSignal(SignalName.UpdateTechProgress, tech.Name, player.EstimateTurnsToResearch(tech)); + } else { + EmitSignal(SignalName.UpdateTechProgress, "Not selected", (int)1e9); + } CurrentState = GameState.PlayerTurn; GetNextAutoselectedUnit(gameDataAccess.gameData); diff --git a/C7/UIElements/GameStatus/LowerRightInfoBox.cs b/C7/UIElements/GameStatus/LowerRightInfoBox.cs index ca62400e..4b7a6230 100644 --- a/C7/UIElements/GameStatus/LowerRightInfoBox.cs +++ b/C7/UIElements/GameStatus/LowerRightInfoBox.cs @@ -152,7 +152,7 @@ public void SetTurnAndGold(int turnNumber, int gold, int goldPerTurn) { } public void UpdateTechProgress(string techName, int turnsRemaining) { - if (turnsRemaining > 0) { + if (turnsRemaining >= 1e9) { SetTextAndCenterLabel(scienceProgress, $"{techName} (-- turns)"); } else { SetTextAndCenterLabel(scienceProgress, $"{techName} ({turnsRemaining} turns)"); diff --git a/C7Engine/EntryPoints/TurnHandling.cs b/C7Engine/EntryPoints/TurnHandling.cs index 38caa291..9a3e981f 100644 --- a/C7Engine/EntryPoints/TurnHandling.cs +++ b/C7Engine/EntryPoints/TurnHandling.cs @@ -51,6 +51,16 @@ internal static void AdvanceTurn() { gameData.turn++; foreach (Player player in gameData.players) { player.turnsResearched++; + + // Check to see if the player has finished researching their + // tech, and if they have, add it to the list of known techs + if (player.currentlyResearchedTech != null) { + Tech tech = gameData.techs.Find(x => x.id == player.currentlyResearchedTech); + if (player.EstimateTurnsToResearch(tech) <= 0) { + player.knownTechs.Add(player.currentlyResearchedTech); + player.SetCurrentlyResearchedTech(null); + } + } } OnBeginTurn(); } diff --git a/C7GameData/Player.cs b/C7GameData/Player.cs index 6dd93689..616b6650 100644 --- a/C7GameData/Player.cs +++ b/C7GameData/Player.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using C7Engine.AI.StrategicAI; @@ -117,6 +118,50 @@ public override string ToString() { return civilization.cityNames.First(); return ""; } + + public int EstimateTurnsToResearch(Tech tech) { + // Cost formula from https://forums.civfanatics.com/threads/research-cost-formula-v1-29f.29485/. + // Research Cost = [MM * [10*COST * (1 - N/[CL*1.75])]/(CF * 10)] - progress + // + // MM = map modifier (tiny=160, small=200, standard=240, large=320, huge=400) + // COST = tech cost + // CF = difficulty factor, range 10 (easy) to 6 (hard) + // N = number of known civs that have discovered the tech + // CL = civs left in game + // + // We also have the min/max turns to research of 4 and 50. + // TODO: the min/max costs are in the biq, we should load them. + // TODO: implement the civ-related parts of the equation + // TODO: figure out what map size we are + // TODO: See this this whole equation can be configurable + int beakersPerTurn = 0; + foreach (City city in cities) { + beakersPerTurn += (int)Math.Floor(city.CurrentCommerceYield() * city.owner.scienceRate / 10.0); + } + + if (beakersPerTurn == 0) { + // No research is happening. + Console.WriteLine("no research"); + return (int)1e9; + } + + int mapModifier = 160; // small, to make testing faster + int difficultyFactor = 10; // easy difficulty + int researchCost = mapModifier * 10 * tech.Cost / (difficultyFactor * 10); + int remainingCost = researchCost - beakers; + int turnsRemaining = (int)Math.Ceiling((double)remainingCost / beakersPerTurn); + + // We never spend more than 50 turns per tech. + int maxTurnsRemaining = 50 - turnsResearched; + + int result = Math.Min(turnsRemaining, maxTurnsRemaining); + + // Ensure every tech takes at least 4 turns. + if (result < 4 && turnsResearched < 4) { + return 4; + } + return result; + } } }