Пример #1
0
        public Randomizer(IGameLayer gameLayer, ILoggerFactory loggerFactory, TurnStrategyBase strategy = null)
        {
            _gameLayer = gameLayer;
            _gameState = gameLayer.GetState();
            _logger    = loggerFactory.CreateLogger <Randomizer>();

            if (strategy == null)
            {
                _strategy = TurnStrategyBase.Build(loggerFactory)
                            .Append <BuildUtilityCloseToResidencesTurnStrategy>(c =>
                {
                    c.BuildingName         = "Mall";
                    c.MaxNumberOfBuildings = 3;
                })
                            .Append <BuildUtilityCloseToResidencesTurnStrategy>(c =>
                {
                    c.BuildingName         = "Park";
                    c.MaxNumberOfBuildings = 2;
                })
                            .Append <BuildBuildingWhenCloseToPopMaxTurnStrategy>()
                            .Append <BuyUpgradeTurnStrategy>()
                            .Append <MaintenanceWhenBuildingIsGettingDamagedTurnStrategy>()
                            .Append <BuildWhenHasBuildingsUnderConstructionTurnStrategy>()
                            .Append <AdjustBuildingTemperaturesTurnStrategy>()
                            .Append <SingletonBuildingTurnStrategy>()
                            .Compile();
            }
            else
            {
                _strategy = strategy;
            }
            _logger.LogInformation($"Strategy: {Environment.NewLine + _strategy}");
        }
Пример #2
0
        public virtual bool TryExecuteStrategy(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            var result = _predicate == null || _predicate(state);

            if (result)
            {
                // Has no predicate or has meet the requirements
                result = TryExecuteTurn(randomizer, gameLayer, state);
            }

            var globalResult = result;

            if (!globalResult && _parent != null)
            {
                // Could not execute, continue with the parent
                globalResult = _parent.TryExecuteStrategy(randomizer, gameLayer, state);
            }

            // Make a callback that the strategy has succeeded
            if (_callback != null)
            {
                _callback?.Invoke(state, result, globalResult);
            }
            return(globalResult);
        }
Пример #3
0
        protected override bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            // Only build when has nothing else to do
            var action = randomizer.GetRandomAction(x => x != GameActions.StartBuild);

            if (action == GameActions.Wait)
            {
                action = randomizer.GetRandomAction();
                if (action == GameActions.StartBuild)
                {
                    var building = state.AvailableResidenceBuildings.Find(x => x.BuildingName == "Cabin");
                    if (building.Cost > state.Funds)
                    {
                        Logger.LogWarning("Wanted to build building, but cannot afford it");
                        return(false);
                    }

                    // Prioritize building Cabins
                    var position = randomizer.GetRandomBuildablePosition();
                    if (position == null)
                    {
                        Logger.LogWarning("No valid positions to build building");
                        return(false);
                    }

                    gameLayer.StartBuild(position, building.BuildingName, state.GameId);
                    return(true);
                }
            }
            return(false);
        }
Пример #4
0
        protected override bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            BlueprintUtilityBuilding building;
            var buildingName = BuildingName;

            if (!string.IsNullOrWhiteSpace(buildingName))
            {
                building = state.AvailableUtilityBuildings.Find(x => x.BuildingName == buildingName);
            }
            else
            {
                var affordableBuildings = state.AvailableUtilityBuildings
                                          .Where(x => x.Cost <= state.Funds)
                                          .ToArray();
                building = affordableBuildings.ElementAtOrDefault(randomizer.Random.Next(0, affordableBuildings.Length));
            }

            if (building == null)
            {
                // No valid building
                return(false);
            }

            var buildings = state.GetBuiltBuildings().Where(x => x.BuildingName == building.BuildingName).ToArray();

            if (buildings.Length >= MaxNumberOfBuildings)
            {
                // Don't build any more buildings
                return(false);
            }
            if (buildings.Any(x => x.BuildProgress < 100))
            {
                // Already one in progress
                return(false);
            }

            if (building.Cost > state.Funds)
            {
                // Cannot afford it
                Logger.LogWarning("Wanted to build building, but cannot afford it");
                return(false);
            }


            var position = randomizer.GetRandomBuildablePositionNearResidence();

            if (position == null || !state.IsBuildablePosition(position))
            {
                Logger.LogWarning("No valid positions to build utility building");
                return(false);
            }

            gameLayer.StartBuild(position, building.BuildingName, state.GameId);
            return(true);
        }
Пример #5
0
 public static void ExecuteAction(this IGameLayer gameLayer, GameActions action)
 {
     if (action == GameActions.Wait)
     {
         ExecuteAction(gameLayer, action, position: null);
     }
     else
     {
         throw new InvalidOperationException($"Only 'GameActions.Wait' can be invoked without a position");
     }
 }
Пример #6
0
        protected override bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            var upgrades = state.AvailableUpgrades;

            if (!upgrades.Any())
            {
                return(false);
            }

            var completedBuildings = state.GetCompletedBuildings().OfType <BuiltResidenceBuilding>().ToArray();

            if (!completedBuildings.Any())
            {
                return(false);
            }


            var allowedUpgrades = IncludedUpgrades != null
                                ? upgrades.Where(x => IncludedUpgrades.Contains(x.Name)).ToArray()
                                : upgrades.ToArray();

            var affordedUpgrades = allowedUpgrades.Where(x => x.Cost < state.Funds).ToArray();
            var upgrade          = affordedUpgrades.ElementAtOrDefault(randomizer.Random.Next(0, affordedUpgrades.Length));

            if (upgrade == null)
            {
                // No allowed upgrades
                return(false);
            }

            var applicableBuildings = completedBuildings.Where(x => !x.Effects.Contains(upgrade.Name)).ToArray();
            var building            = applicableBuildings.ElementAtOrDefault(randomizer.Random.Next(0, applicableBuildings.Length));

            if (building == null)
            {
                // No applicable buildings for chosen upgrade
                return(false);
            }

            var resultingFunds = state.Funds - upgrade.Cost;

            if (resultingFunds < 30000)
            {
                // Always leave a good margin on the bank account
                return(false);
            }

            var position = building.Position;

            gameLayer.BuyUpgrade(position, upgrade.Name, state.GameId);
            return(true);
        }
Пример #7
0
        protected override bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            var currentBuildingCount = state.GetBuiltBuildings().Count();

            if (currentBuildingCount > 0)
            {
                return(false);
            }

            var position = randomizer.GetRandomBuildablePosition();

            if (position == null)
            {
                Logger.LogWarning("No valid positions to build building");
                return(false);
            }


            BlueprintResidenceBuilding building;
            var buildingName = BuildingName;

            if (!string.IsNullOrWhiteSpace(buildingName))
            {
                building = state.AvailableResidenceBuildings.Find(x => x.BuildingName == buildingName);
            }
            else
            {
                var affordableBuildings = state.AvailableResidenceBuildings
                                          .Where(x => x.Cost <= state.Funds)
                                          .ToArray();
                building = affordableBuildings.ElementAtOrDefault(randomizer.Random.Next(0, affordableBuildings.Length));
            }

            if (building == null)
            {
                // No valid building
                return(false);
            }

            if (building.Cost > state.Funds)
            {
                // Cannot afford it
                return(false);
            }

            gameLayer.StartBuild(position, building.BuildingName, state.GameId);
            return(true);
        }
Пример #8
0
        protected override bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            var buildingsUnderConstruction = state.GetBuildingsUnderConstruction().ToArray();

            // Randomly choose building
            //var building = buildingsUnderConstruction.ElementAt(randomizer.Random.Next(0, buildingsUnderConstruction.Length));

            // Choose the one closest to completion
            var building = buildingsUnderConstruction.OrderByDescending(x => x.BuildProgress).FirstOrDefault();


            if (building != null)
            {
                var position = building.Position;
                gameLayer.ExecuteAction(GameActions.Build, position);

                building = state.GetCompletedBuildings().FirstOrDefault(x => x.Position.ToString() == position.ToString());

                if (building is BuiltResidenceBuilding residence)
                {
                    if (AdjustHeatOnConstructed)
                    {
                        var blueprint = state.AvailableResidenceBuildings.Find(x => x.BuildingName == building.BuildingName);

                        var energy = blueprint.BaseEnergyNeed + (residence.Temperature - state.CurrentTemp)
                                     * blueprint.Emissivity / 1 - 0.5 - residence.CurrentPop * 0.04;

                        if (IsBetween(energy,
                                      residence.RequestedEnergyIn - AllowedDiffMargin,
                                      residence.RequestedEnergyIn + AllowedDiffMargin))
                        {
                            // current energy setting is sufficient
                        }
                        else
                        {
                            // adjust energy as the first action after completing building (will take one turn)
                            gameLayer.AdjustEnergy(building.Position, energy, state.GameId);
                        }
                    }
                }
                else
                {
                }
                return(true);
            }
            return(false);
        }
        protected override bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            var damagedBuildings = state.ResidenceBuildings.Where(x => x.Health < ThresholdHealth)
                                   .OrderBy(x => x.Health)
                                   .ToArray();

            if (!damagedBuildings.Any())
            {
                return(false);
            }

            var affordedBuildings = damagedBuildings.Where(x =>
            {
                var blueprint = state.AvailableResidenceBuildings.Single(p => p.BuildingName == x.BuildingName);
                var afforded  = blueprint.MaintenanceCost < state.Funds;
                return(afforded);
            }).ToArray();

            BuiltResidenceBuilding building;

            if (PrioritizeWeakest)
            {
                building = damagedBuildings.First();
                if (!affordedBuildings.Contains(building))
                {
                    // Can't afford it, then skip hole strategy to save money for next time
                    return(false);
                }
            }
            else
            {
                // Can afford maintenance on the weakest building
                building = affordedBuildings.FirstOrDefault();
                if (building == null)
                {
                    // Can't afford it, must wait until can afford
                    return(false);
                }
            }


            var position = building.Position;

            gameLayer.Maintenance(position, state.GameId);
            return(true);
        }
Пример #10
0
        public static void ExecuteAction(this IGameLayer gameLayer, GameActions action, Position position, object argument = null)
        {
            var state = gameLayer.GetState();

            switch (action)
            {
            case GameActions.StartBuild:
                var buildingName = (string)argument ?? throw new ArgumentNullException(nameof(argument));
                gameLayer.StartBuild(position, buildingName, state.GameId);
                break;

            case GameActions.Build:
                gameLayer.Build(position, state.GameId);
                break;

            case GameActions.Maintenance:
                gameLayer.Maintenance(position, state.GameId);
                break;

            case GameActions.BuyUpgrade:
                var upgradeName = (string)argument ?? throw new ArgumentNullException(nameof(argument));
                gameLayer.BuyUpgrade(position, upgradeName, state.GameId);
                break;



            case GameActions.Wait:
                gameLayer.Wait(state.GameId);
                break;

            case GameActions.None:
                throw new NotSupportedException();

            default:
                throw new NotImplementedException();
            }
        }
Пример #11
0
        public static IEnumerable <GameActions> GetPossibleActions(this IGameLayer gameLayer, bool includeWait = true)
        {
            var state = gameLayer.GetState();

            // If has money availble for a building, then can start building
            var blueprints = state.GetBuildingBlueprints().ToArray();

            if (blueprints.Any(x => x.Cost <= state.Funds))
            {
                // Only build if will have plenty of money left...

                var fraction = state.Funds / blueprints.Average(x => x.Cost);
                if (fraction >= 2)
                {
                    yield return(GameActions.StartBuild);
                }
                else
                {
                }
            }

            // If has non-completed buildings, then can build
            if (state.GetBuildingsUnderConstruction().Any(x => x.BuildProgress < 100))
            {
                yield return(GameActions.Build);
            }

            // If has completed buildings and they are low on hp, then can do maintenance
            var damagedBuildings = state.ResidenceBuildings.Where(x => x.Health < 50).ToArray();

            if (damagedBuildings.Any())
            {
                var buildingTypes       = damagedBuildings.Select(x => x.BuildingName).Distinct().ToArray();
                var affordedMaintenance = state.AvailableResidenceBuildings
                                          .Where(x => buildingTypes.Contains(x.BuildingName))
                                          .Where(x => x.MaintenanceCost < state.Funds)
                                          .ToArray();
                if (affordedMaintenance.Any())
                {
                    yield return(GameActions.Maintenance);
                }
            }

            // If has completed buildings and they are low on hp, then can do maintenance
            if (state.GetCompletedBuildings().Any())
            {
                if (state.AvailableUpgrades.Any(x => x.Cost < state.Funds))
                {
                    var fraction = state.Funds / state.AvailableUpgrades.OrderByDescending(x => x.Cost).First().Cost;
                    if (fraction >= 6)
                    {
                        // todo: enable when logic is more stable
                        //yield return GameActions.BuyUpgrade;
                    }
                    else
                    {
                    }
                }
            }

            if (includeWait)
            {
                yield return(GameActions.Wait);
            }
        }
Пример #12
0
 protected abstract bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state);
        protected override bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            var buildings = state.GetBuiltBuildings().OfType <BuiltResidenceBuilding>().ToArray();

            if (buildings.Length >= MaxNumberOfResidences)
            {
                // Don't build any more buildings
                return(false);
            }

            var completedResidences = state.GetCompletedBuildings().OfType <BuiltResidenceBuilding>().ToArray();

            // Only build when has nothing else to do
            var currentPop = completedResidences.Sum(x => x.CurrentPop);

            var currentPopMax = completedResidences
                                .Join(state.AvailableResidenceBuildings, ok => ok.BuildingName, ik => ik.BuildingName,
                                      (rb, bp) => new { bp, rb })
                                .Sum(x => x.bp.MaxPop);

            var pendingPopMaxIncrease = state.GetBuildingsUnderConstruction().OfType <BuiltResidenceBuilding>()
                                        .Join(state.AvailableResidenceBuildings, ok => ok.BuildingName, ik => ik.BuildingName,
                                              (rb, bp) => new { bp, rb })
                                        .Sum(x => x.bp.MaxPop);

            var currentPopPercentage = currentPopMax > 0 ? currentPop / (double)currentPopMax : 0;


            Logger.LogDebug($"Pop {currentPop}/{currentPopMax} = {currentPopPercentage:P2}		(+ {pendingPopMaxIncrease})");

            if (currentPopPercentage > PopulationPercentageThreshold ||
                buildings.Length < 1)
            {
                var position = randomizer.GetRandomBuildablePosition();
                if (position == null)
                {
                    Logger.LogWarning("No valid positions to build building");
                    return(false);
                }

                BlueprintResidenceBuilding building;
                var buildingName = BuildingName;
                if (!string.IsNullOrWhiteSpace(buildingName))
                {
                    building = state.AvailableResidenceBuildings.Find(x => x.BuildingName == buildingName);
                }
                else
                {
                    var affordableBuildings = state.AvailableResidenceBuildings
                                              .Where(x => x.Cost <= state.Funds)
                                              .ToArray();
                    building = affordableBuildings.ElementAtOrDefault(randomizer.Random.Next(0, affordableBuildings.Length));
                }

                if (building == null)
                {
                    // No valid building
                    return(false);
                }

                if (building.Cost > state.Funds)
                {
                    // Cannot afford it
                    Logger.LogWarning("Wanted to build building, but cannot afford it");
                    return(false);
                }

                gameLayer.StartBuild(position, building.BuildingName, state.GameId);
                return(true);
            }
            return(false);
        }
        protected override bool TryExecuteTurn(Randomizer randomizer, IGameLayer gameLayer, GameState state)
        {
            double?predictedTrend = null;
            var    outdoorTemp    = state.CurrentTemp;

            if (state.TemperatureHistory?.Count > 2)
            {
                // Predict outdoor temperature
                var prePreviousTemp = state.TemperatureHistory.Reverse().Skip(2).First().Value;
                var previousTemp    = state.TemperatureHistory.Reverse().Skip(1).First().Value;

                var previousDiff = previousTemp - prePreviousTemp;
                var currentDiff  = state.CurrentTemp - previousTemp;
                if (previousDiff > 0 && currentDiff > 0)
                {
                    // Trend is "getting hotter"
                    predictedTrend = (previousDiff + currentDiff) / 2;
                }
                else if (previousDiff < 0 && currentDiff < 0)
                {
                    // Trend is "getting colder"
                    predictedTrend = (previousDiff + currentDiff) / 2;
                }


                if (predictedTrend.HasValue)
                {
                    // Add the trend twice to more quickly react to big temperature changes
                    predictedTrend = predictedTrend.Value * 2;

                    outdoorTemp = state.CurrentTemp + predictedTrend.Value;
                    Logger.LogTrace($"Using prediction for OutdoorTemp: {outdoorTemp:N3}, CurrentTemp: {state.CurrentTemp:N1} (weighted trend: {predictedTrend:N3})");
                }
            }

            var buildings = state.GetCompletedBuildings()
                            .OfType <BuiltResidenceBuilding>()
                            //.Where(x => x.Temperature < MinTemperature + AllowedTemperatureDiffMargin)
                            .OrderBy(x => x.Temperature)
                            .ToArray();

            foreach (var building in buildings)
            {
                var blueprint = state.AvailableResidenceBuildings.Find(x => x.BuildingName == building.BuildingName);

                // Predict next temperature
                var newTemp =
                    building.Temperature +
                    (building.EffectiveEnergyIn - blueprint.BaseEnergyNeed) * _degreesPerExcessMwh +
                    _degreesPerPop * building.CurrentPop -
                    (building.Temperature - outdoorTemp) * blueprint.Emissivity;
                if (IsBetween(newTemp,
                              TargetTemperature - AllowedTemperatureDiffMargin,
                              TargetTemperature + AllowedTemperatureDiffMargin))
                {
                    // close enough to target temperature already...
                    continue;
                }

                if (newTemp < MinTemperature)
                {
                    // Is below minimum, fake that it is much colder than it is to make a faster recovery
                    outdoorTemp -= Math.Abs(TargetTemperature - building.Temperature);
                }
                if (building.Temperature < MinTemperature)
                {
                    // Is below minimum, fake that it is much colder than it is to make a faster recovery
                    outdoorTemp -= Math.Abs(TargetTemperature - building.Temperature);
                }

                if (newTemp > MaxTemperature)
                {
                    // Is above maximum, fake that it is much hotter than it is to make a faster recovery
                    outdoorTemp += Math.Abs(TargetTemperature - building.Temperature);
                }
                if (building.Temperature > MaxTemperature)
                {
                    // Is above maximum, fake that it is much hotter than it is to make a faster recovery
                    outdoorTemp += Math.Abs(TargetTemperature - building.Temperature);
                }


                var energyOld = blueprint.BaseEnergyNeed + (building.Temperature - outdoorTemp)
                                * blueprint.Emissivity / 1 + 0.5 - building.CurrentPop * 0.04;

                var energyNew = (TargetTemperature - building.Temperature
                                 + blueprint.BaseEnergyNeed * _degreesPerExcessMwh
                                 - _degreesPerPop * building.CurrentPop
                                 + building.Temperature * blueprint.Emissivity
                                 - outdoorTemp * blueprint.Emissivity) / _degreesPerExcessMwh;

                var energy = UseSecondaryAlgorithm ? energyNew : energyOld;

                if (predictedTrend.GetValueOrDefault() > 0)
                {
                    // Trend is getting hotter
                    // Then reduce energy to save heating resources and to cooldown appartment...
                    energy *= 0.9;
                }
                else if (predictedTrend.GetValueOrDefault() < 0)
                {
                    // Trend is getting colder
                    // Then apply more energy to make sure has enough heating...
                    energy *= 1.1;
                }

                if (energy < blueprint.BaseEnergyNeed)
                {
                    Logger.LogWarning($"Wanted to set lower energy than BaseEnergyNeed, restoring to base: {blueprint.BaseEnergyNeed:N3} Mwh from {energy:N3} Mwh");
                    energy = blueprint.BaseEnergyNeed;
                }


                // Predict next temperature, if change energy
                var predictedNewTemp =
                    building.Temperature +
                    (energy - blueprint.BaseEnergyNeed) * _degreesPerExcessMwh +
                    _degreesPerPop * building.CurrentPop -
                    (building.Temperature - outdoorTemp) * blueprint.Emissivity;

                Logger.LogTrace($"{building.BuildingName} at {building.Position}");
                Logger.LogTrace($"Current building temp: \t\t{building.Temperature:N3}");
                Logger.LogTrace($"Next building temp: \t\t\t{newTemp:N3}");
                Logger.LogTrace($"Predicted New Temp: \t\t\t{predictedNewTemp:N3}");
                Logger.LogTrace($"Current building energy: \t{building.EffectiveEnergyIn}/{building.RequestedEnergyIn} Mwh");
                Logger.LogTrace($"New requested energy: \t\t{energyOld:N3} Mwh");
                Logger.LogTrace($"New requested energy v2: \t{energyNew:N3} Mwh");

                if (newTemp < TargetTemperature)
                {
                    // Colder than target
                    if (energy < building.RequestedEnergyIn)
                    {
                        // Should not lower energy if already too cold
                        continue;
                    }
                    else
                    {
                    }
                }
                else if (newTemp > TargetTemperature)
                {
                    // Hotter than target
                    if (energy > building.RequestedEnergyIn)
                    {
                        // Should not increase energy if already too hot
                        continue;
                    }
                    else
                    {
                    }
                }

                if (IsBetween(energy,
                              building.RequestedEnergyIn - AllowedDiffMargin,
                              building.RequestedEnergyIn + AllowedDiffMargin))
                {
                    // minimal change, wait to update energy (to reduce calls and save money)
                    continue;
                }


                if (state.Funds < _adjustCost)
                {
                    Logger.LogWarning($"Wanted to apply energy '{energy:N1}' to building at {building.Position}, but has insufficient funds ({state.Funds})");
                    continue;
                }

                Logger.LogInformation($"Adjusting energy for {building.Position} from {building.RequestedEnergyIn} to {energy:N1} (TEMP:: current={building.Temperature:N3}, predicted={predictedNewTemp:N3})");
                gameLayer.AdjustEnergy(building.Position, energy, state.GameId);
                return(true);
            }

            return(false);
        }