// heuristic evaluation function for minimax algorithm public int calculateWeight(ref MapUnit[] modelUnits, ref BattleEngine modelBattleEngine, playerEnum player, int maxUnits) { int battlefieldWeight = Constants.MINIMUM_WEIGHT; int playerRedWeight = 0; int playerBlueWeight = 0; playerEnum enemyPlayer = (player == playerEnum.Red)? playerEnum.Blue : playerEnum.Red; int numberRedUnits = modelBattleEngine.countLivingUnits(ref modelUnits, playerEnum.Red, maxUnits); int numberBlueUnits = modelBattleEngine.countLivingUnits(ref modelUnits, playerEnum.Blue, maxUnits); if (player == playerEnum.Blue) { switch (computerCharacter) { case (enumCharacter.Aggressive): playerRedWeight = calculateWeightFacade(ref modelUnits, playerEnum.Red, maxUnits); playerRedWeight += numberRedUnits; battlefieldWeight = 0 - playerRedWeight; break; case (enumCharacter.Suicidal): playerRedWeight = calculateWeightFacade(ref modelUnits, playerEnum.Red, maxUnits); playerRedWeight += numberRedUnits; battlefieldWeight = playerRedWeight; break; case (enumCharacter.Defensive): playerBlueWeight = calculateWeightFacade(ref modelUnits, playerEnum.Blue, maxUnits); playerBlueWeight += numberBlueUnits; battlefieldWeight = playerBlueWeight; break; case (enumCharacter.Neutral): playerRedWeight = calculateWeightFacade(ref modelUnits, playerEnum.Red, maxUnits); playerBlueWeight = calculateWeightFacade(ref modelUnits, playerEnum.Blue, maxUnits); battlefieldWeight = (playerBlueWeight + numberBlueUnits) - (playerRedWeight + numberRedUnits); break; default: break; } } if (player == playerEnum.Red) { switch (computerCharacter) { case (enumCharacter.Aggressive): playerBlueWeight = calculateWeightFacade(ref modelUnits, playerEnum.Blue, maxUnits); playerBlueWeight += numberBlueUnits; battlefieldWeight = 0 - playerBlueWeight; break; case (enumCharacter.Suicidal): playerBlueWeight = calculateWeightFacade(ref modelUnits, playerEnum.Blue, maxUnits); playerBlueWeight += numberBlueUnits; battlefieldWeight = playerBlueWeight; break; case (enumCharacter.Defensive): playerRedWeight = calculateWeightFacade(ref modelUnits, playerEnum.Red, maxUnits); playerRedWeight += numberRedUnits; battlefieldWeight = playerRedWeight; break; case (enumCharacter.Neutral): playerRedWeight = calculateWeightFacade(ref modelUnits, playerEnum.Red, maxUnits); playerBlueWeight = calculateWeightFacade(ref modelUnits, playerEnum.Blue, maxUnits); battlefieldWeight = (playerRedWeight + numberRedUnits) - (playerBlueWeight + numberBlueUnits); break; default: break; } } return(battlefieldWeight); }
// using minimax type of algorithm public int makeMove(ref MapUnit[] playerUnits, playerEnum player, int maxUnits, int thisDepth, int maxDepth, int bestWeight) { traceProgress("makeMove() - *** DEPTH *** " + thisDepth.ToString() + " bestweight:" + bestWeight.ToString()); // already got end-condition, then this recurse not needed if (bestWeight == Constants.MAXIMUM_WEIGHT) { return(Constants.MINIMUM_WEIGHT); } if (modelBattleEngine == null) { modelBattleEngine = new BattleEngine(); modelBattleEngine.newBattle(mapX, mapY); } MapUnit[] modelUnits = new MapUnit[maxUnits]; for (int mu = 0; mu < maxUnits; mu++) { modelUnits[mu] = (MapUnit)playerUnits[mu].Clone(); } if (thisDepth == 0) { movesConsidered = 0; deadUnits = modelBattleEngine.countDeadUnits(ref modelUnits, player, maxUnits); bestU = -1; bestF = -1; bestX = -1; bestY = -1; bestMoveType = enumMoveType.Nothing; bestStance = stanceEnum.Nothing; traceProgress("makeMove() - *** INITIALISE *** max:" + mapX.ToString() + "," + mapY.ToString()); } int startingWeight = calculateWeight(ref modelUnits, ref modelBattleEngine, player, maxUnits); traceProgress("makeMove() - startingWeight:" + startingWeight.ToString()); int u = 0; while (u < maxUnits && bestWeight != Constants.MAXIMUM_WEIGHT) { if (player == modelUnits[u].player && modelUnits[u].strength > 0) { traceProgress("makeMove() - UNIT:" + modelUnits[u].description); // Moves int moveOffset = modelBattleEngine.possibleMoves(modelUnits[u].typeUnit, modelUnits[u].stanceUnit); int startX = modelUnits[u].x - moveOffset; int endX = modelUnits[u].x + moveOffset; if (startX < 0) { startX = 0; } if (endX >= mapX) { endX = mapX - 1; } for (int i = startX; i <= endX; i++) { int startY = modelUnits[u].y - moveOffset; int endY = modelUnits[u].y + moveOffset; if (startY < 0) { startY = 0; } if (endY >= mapY) { endY = mapY - 1; } for (int j = startY; j <= endY; j++) { if (bestWeight != Constants.MAXIMUM_WEIGHT) { playerEnum enemyPlayer = (player == playerEnum.Red)? playerEnum.Blue : playerEnum.Red; int livingEnemyBefore = modelBattleEngine.countLivingUnits(ref modelUnits, enemyPlayer, maxUnits); MapUnit oldUnit = (MapUnit)modelUnits[u].Clone(); MapUnit oldAttackedUnit = null; int nearestEnemyBefore = modelBattleEngine.findNearestFriend(ref modelUnits, u, maxUnits, true); int nearestEnemyDistanceBefore = modelBattleEngine.calculateDistance(ref modelUnits, nearestEnemyBefore, u); int f = modelBattleEngine.findUnit(ref modelUnits, i, j, maxUnits); int playerWeight = Constants.MINIMUM_WEIGHT; bool noChange = false; if (f > -1) { if (f != u && modelUnits[f].player != modelUnits[u].player && modelUnits[f].strength > 0) { movesConsidered++; // nearby enemy unit, try attacking oldAttackedUnit = (MapUnit)modelUnits[f].Clone(); modelUnits[u].x = i; modelUnits[u].y = j; modelBattleEngine.fightUnits(ref modelUnits, u, f, maxUnits, false); traceProgress("makeMove() - Attack:" + modelUnits[f].description); } else { noChange = true; } } else { movesConsidered++; // empty square, so try moving modelUnits[u].x = i; modelUnits[u].y = j; traceProgress("makeMove() - Move:" + i.ToString() + "," + j.ToString()); } if (noChange == false) { if (thisDepth < maxDepth) { if (pruneFar == true) { // stop moves away from enemy int nearestEnemyAfter = modelBattleEngine.findNearestFriend(ref modelUnits, u, maxUnits, true); int nearestEnemyDistanceAfter = modelBattleEngine.calculateDistance(ref modelUnits, nearestEnemyAfter, u); if (nearestEnemyDistanceAfter > nearestEnemyDistanceBefore) { playerWeight = Constants.MINIMUM_WEIGHT; } else { // if not at maxDepth, then recurse for unpruned tree playerWeight = makeMove(ref modelUnits, player, maxUnits, thisDepth + 1, maxDepth, bestWeight); } } else { // if all enemy dead, then this is end condition! int livingEnemyAfter = modelBattleEngine.countLivingUnits(ref modelUnits, enemyPlayer, maxUnits); if (livingEnemyAfter <= 0) { playerWeight = Constants.MAXIMUM_WEIGHT; } else { // if not at maxDepth, then recurse playerWeight = makeMove(ref modelUnits, player, maxUnits, thisDepth + 1, maxDepth, bestWeight); if (livingEnemyAfter < livingEnemyBefore) { playerWeight++; } } } traceProgress("makeMove() - RecursedWeight:" + playerWeight.ToString()); } else { // finally at max depth, what is the weighting? playerWeight = calculateWeight(ref modelUnits, ref modelBattleEngine, player, maxUnits); traceProgress("makeMove() - playerWeight:" + playerWeight.ToString()); } if (playerWeight > bestWeight) { if (f > -1) { if (modelUnits[u].player != modelUnits[f].player) { bestWeight = playerWeight; bestX = i; bestY = j; bestU = u; bestF = f; bestMoveType = enumMoveType.Attack; traceProgress("makeMove() - Set bestWeight:" + playerWeight.ToString()); traceProgress("makeMove() - To Attack:" + modelUnits[f].description); } } else { bestWeight = playerWeight; bestX = i; bestY = j; bestU = u; bestF = -1; bestMoveType = enumMoveType.Move; traceProgress("makeMove() - Set bestWeight:" + playerWeight.ToString()); traceProgress("makeMove() - To Move:" + i.ToString() + "," + j.ToString()); } } } // undo hypothetical move modelUnits[u] = (MapUnit)oldUnit.Clone(); // undo hypothetical attack if (f > -1 && oldAttackedUnit != null) { modelUnits[f] = (MapUnit)oldAttackedUnit.Clone(); } } } // try changing stance int stanceWeight = Constants.MINIMUM_WEIGHT; int numberStances = 0; MapUnit oldStanceUnit = (MapUnit)modelUnits[u].Clone(); switch (modelUnits[u].typeUnit) { case (unitEnum.Artillery): numberStances = 2; break; case (unitEnum.Cavalry): numberStances = 2; break; case (unitEnum.Infantry): numberStances = 3; break; default: numberStances = 0; break; } int us = 1; while (us <= numberStances) { bool stanceChange = false; stanceEnum currentStance = stanceEnum.Nothing; switch (us) { case (1): if (modelUnits[u].stanceUnit != stanceEnum.Line) { modelUnits[u].stanceUnit = stanceEnum.Line; currentStance = stanceEnum.Line; stanceChange = true; } break; case (2): if (modelUnits[u].stanceUnit != stanceEnum.Column) { //modelUnits[u].stanceUnit = stanceEnum.Column; //currentStance = stanceEnum.Column; //stanceChange = true; } break; case (3): if (modelUnits[u].stanceUnit != stanceEnum.Square) { modelUnits[u].stanceUnit = stanceEnum.Square; currentStance = stanceEnum.Square; stanceChange = true; } break; } if (stanceChange == false) { stanceWeight = Constants.MINIMUM_WEIGHT; } else { traceProgress("makeMove() - Set stance:" + currentStance.ToString()); movesConsidered++; if (thisDepth < maxDepth) { stanceWeight = makeMove(ref modelUnits, player, maxUnits, thisDepth + 1, maxDepth, bestWeight); traceProgress("makeMove() - recursedWeight:" + stanceWeight.ToString()); } else { stanceWeight = calculateWeight(ref modelUnits, ref modelBattleEngine, player, maxUnits); traceProgress("makeMove() - stanceWeight:" + stanceWeight.ToString()); } } if (stanceWeight > bestWeight && currentStance != stanceEnum.Nothing) { bestWeight = stanceWeight; bestU = u; bestStance = currentStance; bestMoveType = enumMoveType.Stance; traceProgress("makeMove() - Set bestWeight:" + stanceWeight.ToString()); traceProgress("makeMove() - stance:" + currentStance.ToString()); } // undo hypothetical stance change modelUnits[u] = (MapUnit)oldStanceUnit.Clone(); us++; } // try firing at all nearby units int fireWeight = Constants.MINIMUM_WEIGHT; int fireOffset = modelBattleEngine.possibleFire(modelUnits[u].typeUnit, modelUnits[u].stanceUnit); if (fireOffset > 0) { int uf = 0; while (uf < maxUnits && bestWeight != Constants.MAXIMUM_WEIGHT) { if (modelUnits[u].player != modelUnits[uf].player && modelUnits[uf].strength > 0) { playerEnum enemyPlayer = (player == playerEnum.Red)? playerEnum.Blue : playerEnum.Red; int livingEnemyBefore = modelBattleEngine.countLivingUnits(ref modelUnits, enemyPlayer, maxUnits); MapUnit oldFireUnit = (MapUnit)modelUnits[uf].Clone(); movesConsidered++; traceProgress("makeMove() - Fire At:" + modelUnits[uf].description); int distanceFire = modelBattleEngine.calculateDistance(ref modelUnits, uf, u); if (distanceFire <= fireOffset) { traceProgress("makeMove() - Distance:" + distanceFire.ToString()); modelBattleEngine.fireAtUnit(ref modelUnits, u, uf, maxUnits, false); if (thisDepth < maxDepth) { // if all enemy dead, then this is end condition! int livingEnemyAfter = modelBattleEngine.countLivingUnits(ref modelUnits, enemyPlayer, maxUnits); if (livingEnemyAfter <= 0) { fireWeight = Constants.MAXIMUM_WEIGHT; } else { // if not at maxDepth, then recurse fireWeight = makeMove(ref modelUnits, player, maxUnits, thisDepth + 1, maxDepth, bestWeight); if (livingEnemyAfter < livingEnemyBefore) { fireWeight++; } } traceProgress("makeMove() - recursedWeight:" + fireWeight.ToString()); } else { fireWeight = calculateWeight(ref modelUnits, ref modelBattleEngine, player, maxUnits); traceProgress("makeMove() - fireWeight:" + fireWeight.ToString()); } if (fireWeight > bestWeight) { bestWeight = fireWeight; bestU = u; bestF = uf; bestMoveType = enumMoveType.Fire; traceProgress("makeMove() - Set Fire:" + modelUnits[uf].description); } // undo hypothetical fire modelUnits[uf] = (MapUnit)oldFireUnit.Clone(); } } uf++; } } } } u++; } if (bestU > -1 && thisDepth == 0) { bool moveMade = false; traceProgress("makeMove() - *** MAKE COMPUTER MOVE ***"); switch (bestMoveType) { case (enumMoveType.Move): if (bestX > -1 && bestY > -1) { modelBattleEngine.moveUnit(ref playerUnits, bestU, bestX, bestY); moveMade = true; traceProgress("makeMove() - *** MOVE UNIT ***"); traceProgress("makeMove() - " + playerUnits[bestU].description + " to " + bestX.ToString() + "," + bestY.ToString()); } break; case (enumMoveType.Attack): if (bestX > -1 && bestY > -1) { playerUnits[bestU].x = bestX; playerUnits[bestU].y = bestY; modelBattleEngine.fightUnits(ref playerUnits, bestU, bestF, maxUnits, true); moveMade = true; traceProgress("makeMove() - *** ATTACK UNIT ***"); traceProgress("makeMove() - " + playerUnits[bestU].description + " to " + playerUnits[bestF].description); } break; case (enumMoveType.Fire): modelBattleEngine.fireAtUnit(ref playerUnits, bestU, bestF, maxUnits, true); moveMade = true; traceProgress("makeMove() - *** FIRE ON UNIT ***"); traceProgress("makeMove() - " + playerUnits[bestU].description + " to " + playerUnits[bestF].description); break; case (enumMoveType.Stance): modelBattleEngine.changeUnitStance(ref playerUnits, bestU, bestStance); moveMade = true; traceProgress("makeMove() - *** CHANGE STANCE ***"); traceProgress("makeMove() - " + playerUnits[bestU].description + " to " + bestStance.ToString()); break; default: traceProgress("makeMove() - *** ERROR - DEFAULT CASE ***"); moveMade = false; break; } traceProgress("makeMove() - moves considered:" + movesConsidered.ToString()); Debug.Assert(moveMade == true, "No move made", "madeMove() failed to create a move!"); if (moveMade == false) { bestWeight = Constants.MINIMUM_WEIGHT; } } traceProgress("makeMove() - return weight:" + bestWeight.ToString()); return(bestWeight); }