I just place, with a little bit of randomness, some cheese on the map and have my mice (army stacks) find the cheese.
Brilliant :)
So the first time I ever made into first place was with a pretty small bot without a lot of logic that only knew to take SA and take advantage of people not knowing how to play AU (ironic, right?). This took me from 50th or so place straight to first. Once there, it became obvious that I was playing against bots that actually knew what they were doing (and competing with other smart SA bots) and really had to step up my game.
I will say, every time I played against Dalek was frustrating as all hell. Even in my final bots. Screw you and your better-than-me-at-warlight coding efforts :P (that was sarcasm in case you missed it. I actually have great respect for Norman and his bot)
Anyways, I'm no where near as good of a WL player as the rest of the top guys in the AI challenge, so I basically had to adapt by copying. I see something awesome your bot did that was awesome, I would code up something similar.
...or against. Perfect example here is Gadzbot's notorious ability to take Indonesia from Siam turn 1 and me not be totally prepared for it. I coded up a whole routine to not only protect against that scenario (I'm sure you noticed 125ch209), but start taking advantage of those picks that can sabotage the opponent early. Of course, I forgot one small detail before submitting my final code that keeps making me sad every time I see it happen: When Siam is available and Central America isn't, make South America picks my 1st and 2nd. Whoops! This is the functionality I was referring to when I said "I actually named some of my routines after the offending player. Before calling it "CheckForPossibleSabotage" it was "GoF***UpGadzbot", haha.
My biggest weaknesses in WL is early game and large frontiers. The latter I didn't have to worry too much in this map, thankfully. But the early game, even at the end of the coding challenge, is something that causes me strife. A main issue with my bot is that it didn't detect when I lost a bonus on turn 2 or 3 due to a good luck roll by my opponent (11 took a 9. I mean, c'mon, who
does that?).
Eventually I moved my bot over to an AU-pick first bot - hard coded, but I like the idea someone proposed earlier about using the "harder to get to" method - mostly because I started using the knowledge of knowing where you were to my advantage. For example, if you had both SA picks and our 3rd picks were kissing in Africa, I knew you needed at least 5 armies to take South America. So, I would sit there and count what you lay down every turn that wasn't in SA and know that you were still at a 5-army income. If I get my first three picks or only see one of your armies, I guess your last two picks are in Europe and do the same thing, hopefully interrupting you in time. Australia was only ever given priority over SA if: I received both picks in AU, or; I received a starting region in 3 different continents. Other than that, my bot was actually still an SA bot, just with AU picked first.
Genius, right?
Anyways, I keep seeing you guys talking about how you're calculating attacks and percentages and statistics and blah blah blah... Here, I 100% agree with Norman and I only code battles for 0% luck. On average, it's what will statistically happen more often. No matter what, you're going to lose to a perfect roll by your opponent even if you calculate everything exact.
public enum BattleRegions
{
Attacker,
Defender,
Neither
}
private BattleRegions WhoWinsOnAttack(BotState currentState, int fromRegionArmies, int toRegionArmies, int opponentIncome, bool noOpponentIncomeThisRound)
{
var attackingArmies = fromRegionArmies;
var defendingArmies = toRegionArmies + (noOpponentIncomeThisRound ? 0 : opponentIncome);
var defendersDestroyed = attackingArmies * .6;
var attackersDestroyed = defendingArmies * .7;
var attackingArmiesLeft = attackingArmies - attackersDestroyed;
var defendingArmiesLeft = defendingArmies - defendersDestroyed;
if (attackingArmiesLeft <= 0)
return BattleRegions.Defender;
if (defendingArmiesLeft <= 0)
return BattleRegions.Attacker;
if ((int)attackingArmiesLeft + currentState.StartingArmies == fromRegionArmies && (int)defendingArmiesLeft == toRegionArmies)
return BattleRegions.Neither;
return WhoWinsOnAttack(currentState, (int)attackingArmiesLeft + currentState.StartingArmies, (int)defendingArmiesLeft, opponentIncome, false);
}
private BattleRegions WhoWinsOnDefend(BotState currentState, int toRegionArmies, int fromRegionArmies, int opponentIncome, bool noOpponentIncomeThisRound)
{
var attackingArmies = fromRegionArmies + (noOpponentIncomeThisRound ? 0 : opponentIncome);
var defendingArmies = toRegionArmies;
var defendersDestroyed = attackingArmies * .6;
var attackersDestroyed = defendingArmies * .7;
var attackingArmiesLeft = attackingArmies - attackersDestroyed;
var defendingArmiesLeft = defendingArmies - defendersDestroyed;
if (attackingArmiesLeft <= 0)
return BattleRegions.Defender;
if (defendingArmiesLeft <= 0)
return BattleRegions.Attacker;
if ((int)attackingArmiesLeft - 1 == toRegionArmies && (int)defendingArmiesLeft + currentState.StartingArmies == fromRegionArmies)
return BattleRegions.Neither;
return WhoWinsOnDefend(currentState, (int)defendingArmiesLeft + currentState.StartingArmies, (int)attackingArmiesLeft - 1, opponentIncome, false);
}
As you can see, I'm a big fan of recursive functions :)
The boolean noOpponentIncomeThisRound comes from a private class-level variable that tells me if I calculated or guessed where you are placing your armies for the turn. Basically, these routines simulate the attack, take what armies are left over, and plug them back in assuming that you and I are going to put all of our armies on these stacks again. Keep going until one region loses all their armies. If you're going to say "It should be until the attacker has 1 army left, not zero" -- maybe for your code, not mine. That's taken into consideration on the number of armies initially passed in.
Not perfect (if these regions don't get army placements, they might not next turn, etc), but it seems to do the trick for what I need it to do.
This can be quite easily modified to determine the number of turns it will take to get to desired defeat by creating a class level variable that is reset in the calling function and then gets incremented every time the routine is called recursively.
I also use these routines to detect stalemates (if the winner for both regions is always the defender, we're most likely in a stalemate on that region).
One last final piece of advise I truly want to recommend: Learn how to figure out your opponent's income. I notice a lot of you try to "hide" your income -- which is smart unless you've already given it away to me 5 turns ago :) If I calculate that you have South America, I also know you're going to have it until I own a Region in South America. The method to calculate opponent income is actually a lot less trivial than you think!
First, determine what super regions you don't know anything about, meaning ALL regions are owned by player "unknown" (if you see a neutral region, you already have enough information on that super region to know it's unowned).
If your opponent lays down more than 5 armies in your field of sight, figure it out according to those unknown owned super region how many armies your opponent put down:
foreach (var unknownSuper in state.UnknownStatusSuper)
{
switch (unknownSuper.Id)
{
case (int)SuperRegionNames.Africa:
if (VisibleIncome > 7)
AddOwnedSuperRegion(unknownSuper.Id, state.FullMap, state.RoundNumber);
break;
case (int)SuperRegionNames.Asia:
if (VisibleIncome > 14)
AddOwnedSuperRegion(unknownSuper.Id, state.FullMap, state.RoundNumber);
break;
case (int)SuperRegionNames.Australia:
AddOwnedSuperRegion(unknownSuper.Id, state.FullMap, state.RoundNumber);
break;
case (int)SuperRegionNames.Europe:
if (VisibleIncome > 9)
AddOwnedSuperRegion(unknownSuper.Id, state.FullMap, state.RoundNumber);
break;
case (int)SuperRegionNames.NorthAmerica:
if (VisibleIncome > 10)
AddOwnedSuperRegion(unknownSuper.Id, state.FullMap, state.RoundNumber);
break;
case (int)SuperRegionNames.SouthAmerica:
AddOwnedSuperRegion(unknownSuper.Id, state.FullMap, state.RoundNumber);
break;
}
}
(Note, I have an enum set up for both Regions and SuperRegions to identify their IDs by name)
If you think the numbers above won't work in certain situations, you're probably right, but those situations are games you've already lost. When I detect the opponent owns, I have a routine that takes care of adding important opponent information to my opponent tracking class, you can put whatever you want in between the "case:" and the "break;" :D
Anyways, that's a quick peek into the guts of Trogabot. I'll bring more to the party later and I hope I taught you new coders something :)
Edited 5/29/2014 16:31:10