Freigeben über


Testing a RockPaperAzure Bot in a Winform

In Friday’s webcast, I showed a quick ‘n dirty utility for testing a RockPaperAzure bot outside of the game server and even the Bot Lab. When we ran some of these tournaments in person, it was very difficult to get a good feel for your bot before deploying it, and that’s why we introduced the idea of a Bot Lab. But even with the Bot Lab, if you’re working out a complex algorithm, it can be handy to have a simple test hardness.

By pulling in the DLLs from the Bot Lab, it’s pretty easy to put together a little utility. To locate the DLLs, crack open the BotLabSite project and locate the ref directory, and grab the Compete.Bot, RockPaperScissorsPro, and RockPaperScissorsPro.RPA libraries:

One thing to note: the RockPaperScissorsPro.RPA assembly, in the MyBot project, has an XML file that you can also include if you’d like intellisense – for example, we call out that GetRandomMove returns Rock/Paper/Scissors, not dynamite/water balloon:

image

In our Windows Form app, we’ll create a few textboxes for the moves of each player, and the player’s log file:

image

In our project, we’ll build a couple of bots – these can be your actual bots (because they use the same interface) or completely different – it’s up to you. MyBot will be the main bot we want to test, and Opponent will be, obviously, the opponent.

image

In our form app, we’ll declare some of the player/game objects we need to test:

    1: IRockPaperScissorsBot myBot;
    2: IRockPaperScissorsBot opponentBot;
    3: Player rpsPlayer1;
    4: Player rpsPlayer2;
    5: PlayerLog player1Log;
    6: PlayerLog player2Log;
    7:    
    8: private void Form1_Load(object sender, EventArgs e)
    9: {
   10:     myBot = new MyBot();
   11:     opponentBot = new OpponentBot();
   12:  
   13:     rpsPlayer1 = new Player("you", myBot);
   14:     rpsPlayer2 = new Player("opponent", opponentBot);
   15:  
   16:     player1Log = new PlayerLog(rpsPlayer1, rpsPlayer1, rpsPlayer2);
   17:     player2Log = new PlayerLog(rpsPlayer2, rpsPlayer1, rpsPlayer2);          
   18: }

When the form loads, we initialize our players. Each player has a bunch of methods and properties, but most importantly, also has the player’s bot. This can be a little confusing because bot is a property of the player. We’ll also create some player logs – normally the engine does this for us, but we’re running outside the engine so we need to do that ourselves.

There are 2 ways we can have out bots move:

Option 1: Simulate the game environment.

This is a good option if you want to test timings against the game engine. I’m including this here really just for information sake – most people who want to test timings will be far better off testing in the bot lab.

    1: int i = 0;
    2:  
    3: private void button1_Click(object sender, EventArgs e)
    4: {
    5:     i++;
    6:       
    7:     PlayerMove ourMove = rpsPlayer1.GetMoveToMake(
    8:         rpsPlayer2, GameRules.Default, player1Log);
    9:    
   10:     PlayerMove opponentMove = rpsPlayer2.GetMoveToMake(
   11:       rpsPlayer2, GameRules.Default, player1Log);

By calling GetMoveToMake on the player, we’ll get back the Player’s move and this will be done just like it is in the real game – timeouts and all.

Option 2: Take your time!

This is really the best option – we’ll call into the bots MakeMove directly and this avoids threading/timeouts – the big advantage here is that it’s much easier to step through and debug. There is one tiny gotcha. The player log files are managed by the player, and in this case, we’re sidestepping that. In order to have a working log, we’ll need to inject that log via reflection. Reflection code won’t work on the real game server, but it comes in handy in our test app:

    1: int i = 0;
    2:  
    3: private void button1_Click(object sender, EventArgs e)
    4: {
    5:     i++;
    6:  
    7:     Type t = typeof(Player);
    8:     FieldInfo fieldInfo = t.GetField(
    9:         "<Log>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance);
   10:     fieldInfo.SetValue(rpsPlayer1, player1Log);
   11:     fieldInfo.SetValue(rpsPlayer2, player2Log);
   12:  
   13:     Move ourMove = myBot.MakeMove(rpsPlayer1, rpsPlayer2, GameRules.Default);
   14:     Move opponentMove = opponentBot.MakeMove(rpsPlayer2, rpsPlayer1, GameRules.Default);

What lines 7 – 11 do is use reflection to assign the player log. We need to use reflection because these are internal objects that we can’t typically access.

Once we have the move, we call SetLastMove which gives bots the visibility into their last moves.

    1: ...
    2: rpsPlayer1.SetLastMove(new PlayerMove(rpsPlayer1, ourMove));
    3: rpsPlayer2.SetLastMove(new PlayerMove(rpsPlayer2, opponentMove));
    4: ...
    5:  
    6: protected override void OnClosing(CancelEventArgs e)
    7: {
    8:    rpsPlayer1.MatchOver();
    9:    rpsPlayer2.MatchOver();  
   10:  
   11:    base.OnClosing(e);
   12: }

One other comment: in the form closing (or other location) you need to remember to close down the bots by calling MatchOver. This will make sure they shut down properly and the threads exit. With everything set up, we can run the app and see the output:

image

So let’s return to the original question: Why do this? Suppose we want a simple way to track number and percentage of the moves thrown. We could keep simple counters (in fact, that would be the fastest way to do that), but consider this code block:

    1: List<Move> opponentHistory = new List<Move>();
    2:  
    3: public Move MakeMove(IPlayer you, IPlayer opponent, GameRules rules)
    4: {       
    5:    gamenum++;
    6:  
    7:    if (gamenum == 1) {            
    8:        you.Log.AppendLine("starting up.");
    9:    } else {
   10:        opponentHistory.Add(opponent.LastMove);
   11:    }
   12:  
   13: if (opponentHistory.Count > 0)
   14: {
   15:    var moveHistory = (from m in opponentHistory
   16:                       group m by m.ToString() into g
   17:                       select new { MoveName = g.Key, MoveCount = g.Count(),
   18:                                    Percentage = (Convert.ToDouble(g.Count()) / 
   19:                                    Convert.ToDouble(opponentHistory.Count)*100)
   20:                       }
   21:                      ).ToList();
   22: }

What we’re doing is keeping a log of the opponent’s moves into the opponentHistory list. We’ll then aggregate that into a moveHistory list, which is derived from the aggregated data.

By looking at the opponentHistory, you can ask very specific questions of the data, such as move totals and percentages:

    1: Rock - 3 - 27.27%
    2: Paper - 6 - 54.55%
    3: Scissors - 2 - 18.18%

You could look at the past few moves, or the entire game – or adapt it to your project.

I’ve uploaded the project, with a link below. You’ll need to pull the assemblies above into the project – I’ve left them out for licensing clarity. If you use or improve on this solution, let me know! It’s obviously not terribly advanced but should be enough to get you started.

Download the solution