private Monster AssignMonsterStats(Monster monster, CombatContext combatContext) { monster.BaseStats = _statsGenerator.GenerateStats(monster.Class); monster.SetActionSelector(_actionSelector); monster.SetCombatContext(combatContext); monster.CurrentHitPoints = monster.MaxHitPoints; monster.CurrentEnergy = monster.MaxEnergy; var bossMultiplier = monster.IsBoss ? 4 : 1; monster.Gold = monster.Level * _dice.Roll(3 * bossMultiplier, 6); monster.Experience = (monster.Level * _dice.Roll(2 * bossMultiplier, 6)); if (monster.IsBoss) { monster.Weapon = weaponFactory.CreateWeapon(monster.Level + 1, true); } else { if (_dice.Random(1, 6) == 1) { // 1/6 monsters will have a weapon monster.Weapon = weaponFactory.CreateWeapon(monster.Level, false); } } monster = Itemize(monster); return monster; }
public ICombatAction SelectAction(CombatContext combatContext, IEnumerable<ICombatAction> allowedActions) { // Get a list of all possible actions that the monster can make var allowedActionsList = allowedActions.ToList(); // Calculate the weighted results based on past performance (or expected performance if action not yet exectued in the current combat) // This will return a Dictionary of Action Hashcode (int) to Weight (int) var evaluation = GetWeights(combatContext, allowedActionsList); // Sum the weights, and generate a number between 1 and the sum of the weights. // Example: If we have two actions weighted 20 and 80, we will generate a number between 1 and 100 (20+80) var sumOfWeights = evaluation.Sum(x => x.Value); var randomNumberInWeightedRange = dice.Random(1, sumOfWeights); // Use the randomly selected weight to choose the correct action. // Example: If we have two actions weighted 20 and 80, a random number of 1-20 will select the first option, and 21-100 will select the second option. int runningTotal = 0; foreach (var possibleAction in evaluation) { if (possibleAction.Value + runningTotal >= randomNumberInWeightedRange) { return allowedActionsList.Single(x => x.CreateHash() == possibleAction.Key); } else { runningTotal += possibleAction.Value; } } throw new InvalidOperationException(); }
public Dictionary<int, int> GetWeights(CombatContext combatContext, IEnumerable<ICombatAction> allowedActions) { // Examine the combat log and create a lookup of ICombatAction (using it's hashcode (int)) to CombatLogEntry var monsterActionsFromCombat = combatContext.CombatLog.Where(entry => entry.Attacker == combatContext.Monster && entry is CombatLogEntryFromAction).Cast<CombatLogEntryFromAction>(); ILookup<int, CombatLogEntryFromAction> lookups = monsterActionsFromCombat.ToLookup(x => x.Type.CreateHash(x.Name)); // Setup a dictionary to hold the weights of Action Hashcode (int) to Weight (int) var weights = new Dictionary<int, int>(); // Iterate through each action the monster can perform right now bool first = true; foreach (var allowedAction in allowedActions) { int weight = 0; // If the action's hashcode exists in the lookup table, average the historical outcomes to create the weight of that action if (lookups[allowedAction.CreateHash()].Any()) { weight = (int)lookups[allowedAction.CreateHash()].Average(x => x.CombatEffect.Damage + x.CombatEffect.Healing); } // If it doesn't exist in the lookup table, use the expected performance as the weight else { var potentialEffect = allowedAction.GetPotentialCombatOutcome(combatContext.Monster); weight = potentialEffect.Damage + potentialEffect.Healing; } // The first action must have a minimum weight of 1 weights[allowedAction.CreateHash()] = first ? Math.Max(1, weight) : weight; first = false; } return weights; }
public Monster CreateMonster(GameWorld gameWorld, CombatContext combatContext) { bool isBoss = gameWorld.NumberOfMonstersDefeatedInCurrentDungeonLevel >= gameWorld.RequiredNumberOfMonstersInCurrentDungeonLevelBeforeBoss; var monster = CreateMonster(gameWorld.CurrentDungeonLevel, isBoss, combatContext); Debug.WriteLine("Created {0} (Level {1}) HP:{6} ATK:{2} DEF:{3}. Player ATK:{4} DEF:{5}", monster.Name, monster.Level, monster.ToHitAttack, monster.ToHitDefense, combatContext.Player.ToHitAttack, combatContext.Player.ToHitDefense, monster.MaxHitPoints); if (monster.Weapon != null) Debug.WriteLine("Monster is wielding a {0} ({1})", monster.Weapon.GetLeveledName(), monster.Weapon.Damage); return monster; }
public CombatEngine(GameWorld gameWorld, IMonsterFactory monsterFactory, ICombatantSelector combantSelector, IDice dice) { _gameWorld = gameWorld; _combantSelector = combantSelector; _dice = dice; _combatContext = new CombatContext(); _combatContext.Player = _gameWorld.Player; _combatContext.Monster = monsterFactory.CreateMonster(_gameWorld, _combatContext); _combatContext.Player.HasFledCombat = false; _combatContext.Monster.HasFledCombat = false; }
public void GetWeights_NoActionsHaveTakenPlaceInCombat_DamageAndHealingHaveEqualWeights() { var dice = new Mock<IDice>(); var selector = new MonsterActionSelector(dice.Object); var context = new CombatContext(); var monster = new Monster(); monster.SetCombatContext(context); monster.SetActionSelector(selector); context.Monster = monster; var actionMock1 = new Mock<ICombatAction>(); var actionMock2 = new Mock<ICombatAction>(); actionMock1.Setup(m => m.GetPotentialCombatOutcome(It.IsAny<Combatant>())).Returns(new CombatOutcome() { Damage = 0, Healing = 10 }); actionMock1.SetupGet(m => m.Name).Returns("Action 1"); actionMock2.Setup(m => m.GetPotentialCombatOutcome(It.IsAny<Combatant>())).Returns(new CombatOutcome() { Damage = 10, Healing = 0 }); actionMock2.SetupGet(m => m.Name).Returns("Action 2"); var weights = selector.GetWeights(context, new[] { actionMock1.Object, actionMock2.Object }); Assert.AreEqual(weights[actionMock2.Object.CreateHash()], weights[actionMock1.Object.CreateHash()]); }
public Monster CreateMonster(int dungeonLevel, bool boss, CombatContext combatContext) { Monster newMonster; if (boss) { int bossLevel = dungeonLevel.GetBossLevel(); newMonster = CreateArchetypedMonster(bossLevel, bossLevel); newMonster.Name = newMonster.Name.ToUpper(); newMonster.Class = MonsterClass.Boss; } else { int minLevel = Convert.ToInt32((dungeonLevel * 1.2) / 2); int maxLevel = dungeonLevel; newMonster = CreateArchetypedMonster(minLevel, maxLevel); newMonster.Class = MonsterClass.Monster; } return AssignMonsterStats(newMonster, combatContext); }
public void SelectAction_DiceRollRequestedIsTheTotalOfAllWeights() { var dice = new Mock<IDice>(); int minCalled = -1; int maxCalled = -1; dice.Setup(x => x.Random(It.IsAny<int>(), It.IsAny<int>())).Returns((int min, int max) => { minCalled = min; maxCalled = max; return min; }); var selector = new MonsterActionSelector(dice.Object); var context = new CombatContext(); var monster = new Monster(); monster.SetCombatContext(context); monster.SetActionSelector(selector); context.Monster = monster; var actionMock1 = new Mock<ICombatAction>(); var actionMock2 = new Mock<ICombatAction>(); actionMock1.Setup(m => m.GetPotentialCombatOutcome(It.IsAny<Combatant>())).Returns(new CombatOutcome() { Damage = 10, Healing = 20 }); actionMock1.SetupGet(m => m.Name).Returns("Action 1"); actionMock2.Setup(m => m.GetPotentialCombatOutcome(It.IsAny<Combatant>())).Returns(new CombatOutcome() { Damage = 60, Healing = 0 }); actionMock2.SetupGet(m => m.Name).Returns("Action 2"); var weights = selector.SelectAction(context, new[] { actionMock1.Object, actionMock2.Object }); Assert.AreEqual(1, minCalled); Assert.AreEqual(90, maxCalled); }
public void SelectAction_NoActionsHaveTakenPlaceInCombat_DiceRollCorrespondsToSelection() { // Arrange var actionMock1 = new Mock<ICombatAction>(); var actionMock2 = new Mock<ICombatAction>(); var actionMock3 = new Mock<ICombatAction>(); actionMock1.Setup(m => m.GetPotentialCombatOutcome(It.IsAny<Combatant>())).Returns(new CombatOutcome() { Damage = 3, Healing = 0 }); actionMock1.SetupGet(m => m.Name).Returns("Action 1"); actionMock2.Setup(m => m.GetPotentialCombatOutcome(It.IsAny<Combatant>())).Returns(new CombatOutcome() { Damage = 5, Healing = 0 }); actionMock2.SetupGet(m => m.Name).Returns("Action 2"); actionMock3.Setup(m => m.GetPotentialCombatOutcome(It.IsAny<Combatant>())).Returns(new CombatOutcome() { Damage = 2, Healing = 0 }); actionMock3.SetupGet(m => m.Name).Returns("Action 3"); var diceRollToAction = new Dictionary<int, string>(); for (int i = 0; i < 10; i++) { // Arrange var dice = new Mock<IDice>(); dice.Setup(x => x.Random(It.IsAny<int>(), It.IsAny<int>())).Returns((int min, int max) => min + i); var selector = new MonsterActionSelector(dice.Object); var context = new CombatContext(); var monster = new Monster(); monster.SetCombatContext(context); monster.SetActionSelector(selector); context.Monster = monster; // Act diceRollToAction.Add(i, selector.SelectAction(context, new[] { actionMock1.Object, actionMock2.Object, actionMock3.Object }).Name); } // Assert Assert.AreEqual(diceRollToAction[0], "Action 1"); Assert.AreEqual(diceRollToAction[1], "Action 1"); Assert.AreEqual(diceRollToAction[2], "Action 1"); Assert.AreEqual(diceRollToAction[3], "Action 2"); Assert.AreEqual(diceRollToAction[4], "Action 2"); Assert.AreEqual(diceRollToAction[5], "Action 2"); Assert.AreEqual(diceRollToAction[6], "Action 2"); Assert.AreEqual(diceRollToAction[7], "Action 2"); Assert.AreEqual(diceRollToAction[8], "Action 3"); Assert.AreEqual(diceRollToAction[9], "Action 3"); }
public void SetCombatContext(CombatContext combatContext) { _combatContext = combatContext; }