Morten Albring

Information

This article was written on 14 Nov 2012, and is filled under Dominion, MATLAB.

MATLAB Dominion Simulator 2 - Two Players


In theĀ previous post in this series, we built the base code for simulating Dominion with one player using only treasure and victory cards. In this post, we will generalise some of that code so that we can add more players to the game.

The initialisation is pretty much the same as before

player.one.deck = [11,11,11,11,11,11,11,21,21,21];
player.one.hand = [];
player.one.discard = [];
player.one.vp = 0;

player.two.deck = [11,11,11,11,11,11,11,21,21,21];
player.two.hand = [];
player.two.discard = [];
player.two.vp = 0;

After this, each player needs to draw a new hand. I have put this code into a separate function

function [player] = drawnewhand(player)
player.discard = [player.discard player.hand];

if size(player.deck,2) >= 5
    player.hand = player.deck(1:5);
    player.deck = player.deck(6:end);

elseif size(player.deck,2) == 0
    player.deck = player.discard;
    player.discard = [];
    %shuffle
    player.deck = player.deck(randperm(size(player.deck,2)));
    player.hand = player.deck(1:5);
    player.deck = player.deck(6:end);
else
    player.hand = player.deck;
    player.deck = player.discard;
    player.discard = [];

    %shuffle
    player.deck = player.deck(randperm(size(player.deck,2)));
    s = size(player.hand,2);
    player.hand = [player.hand player.deck(1:(5-s))];
    player.deck = player.deck((6-s):end);
end
end

We then call this function for both players.

%Player One draw hand
[player.one] = drawnewhand(player.one);

%Player Two draw hand
[player.two] = drawnewhand(player.two);

Now we're ready to start the game. We need to change the loop slightly, because the game could end after player 1 buys a province.

while (table.gameActive == 1)
table.turn = table.turn + 1;
...
end

We still don't have any action cards on the table, so our next step is to count the amount of money on the table. I have put this in a separate function. This function only needs to know the cards currently in hand, so I don't need to pass it the entire player struct.

function money = countMoney(hand)
money = 0;
for i=1:5
   if hand(i) == 11
       money = money + 1;
   end

   if hand(i) == 12
       money = money + 3;
   end

   if hand(i) == 13
       money = money + 6;
   end
end
end

Which we then call for player one

player.one.money = countMoney(player.one.hand);

Now that player one knows how much money he has on his hand, he needs to implement his buying strategy. This is where the interesting variations and analysis will come out. For now, let's just use the 'justBuyMoney' strategy as with the previous post.

function [table,player] = justBuyMoney(table,player)
if ((player.money == 3) || (player.money == 4) || (player.money == 5)) && (table.silver > 0)
    %if I can buy silver, buy silver
    table.silver = table.silver - 1;
    player.discard(end+1) = 12;
end
if ((player.money == 6) || (player.money == 7)) && (table.gold > 0)
    %if I can buy gold, buy gold
    table.gold = table.gold - 1;
    player.discard(end+1) = 13;
end
if player.money >= 8
    table.province = table.province - 1;
    player.discard(end+1) = 23;
end
end

So in the main code, we call this buying strategy function and then we draw a new hand, and that's the end of player one's turn.

        if (table.gameActive == 1)
        %%%%Player One Turn                
        %countmoney
        player.one.money = countMoney(player.one.hand);              
        %buy strategy
        [table,player.one] = justBuyMoney(table,player.one);
        %Discard hand and draw new hand
        [player.one] = drawnewhand(player.one);
        if (table.province == 0) 
            table.gameActive = 0;
        end
        end               

For now, we'll let player two do exactly the same strategy.

if (table.gameActive == 1)
%%%%Player Two Turn
%countmoney
player.two.money = countMoney(player.two.hand);
%buy strategy
[table,player.two] = justBuyMoney(table,player.two);
%Discard hand and draw new hand
[player.two] = drawnewhand(player.two);
if (table.province == 0) 
table.gameActive = 0;
end
end

And that's it for the game loop. At the end of the game, we need to count the victory points.

function [player] = countVP(player)
%put all cards in discard pile
player.discard = [player.discard player.hand player.deck];
player.hand = [];
player.deck = [];

for i=1:size(player.discard,2)
    if (player.discard(i) == 21)
        player.vp = player.vp + 1;
    end
    if (player.discard(i) == 22)
        player.vp = player.vp + 3;
    end
    if (player.discard(i) == 23)
        player.vp = player.vp + 6;
    end
end

end

and we call that for both players

player.one = countVP(player.one);
player.two = countVP(player.two);

For us to do some analysis after the fact, we store some data about the results of this game. We can store whether this game was won by player 1, or player 2, or if it was a draw.

if player.one.vp > player.two.vp
   meta.winner(m) = 1;
elseif player.one.vp < player.two.vp
    meta.winner(m) = 2;
else
    meta.winner(m) = 0;
end

We can also store the information about how many victory points were obtained in that game by each player, and also how many turns it took to win the game.

meta.vp(m,1) = m;
meta.vp(m,2) = player.one.vp;
meta.vp(m,3) = player.two.vp;
meta.winturn(m) = table.turn;

So, what do we find with our current setup? Well, we can find out how much of an advantage the starting player has.

Player 1: justBuyMoney; Player 2: justBuyMoney

When both players are playing with the same strategy, over 10,000 games the game results in a draw 6054 times (60.5%), player 1 wins 2658 times (26.58%) and player 2 wins 1288 times (12.88%). The first player has a remarkable advantage!

There's a few things to note about this. In a 'real' game, two players probably would not be playing identical mirror strategies. Additionally, if you were playing numerous games one after the other with the same person, you would probably be switching who goes first so that you compensate for this.

Donald X has himself answered the issue of first player advantage too, and concludes it isn't such a big deal in real-world games.

Now that we have two players, we can compare strategies. Let's compare the justBuyMoney strategy with what happens if we buy Duchies as well, if we can.

function [table,player] = buyMoneyandDuchy(table,player)

    %11 = copper
    %12 = silver
    %13 = gold
    
    %21 = estates
    %22 = duchy
    %23 = province

if ((player.money == 3) || (player.money == 4)) && (table.silver > 0)
    %if I can buy silver, buy silver
    table.silver = table.silver - 1;
    player.discard(end+1) = 12;
end
if (player.money == 5) && (table.duchy > 0)
    %if I can buy duchy, buy duchy
    table.duchy = table.duchy - 1;
    player.discard(end+1) = 22;
end
if ((player.money == 6) || (player.money == 7)) && (table.gold > 0)
    %if I can buy gold, buy gold
    table.gold = table.gold - 1;
    player.discard(end+1) = 13;
end
if player.money >= 8
    table.province = table.province - 1;
    player.discard(end+1) = 23;
end
end

We'll let Player 1 use the justBuyMoney strategy, and set Player 2 as buyMoneyAndDuchy.

Player 1: justBuyMoney; Player 2: buyMoneyandDuchy

And the justBuyMoney strategy defeats the buying Duchies strategies handily! Over 10,000 games, there is a draw 491 times, player 1 wins 7830 times and player 2 wins 1679 times.

We'll swap the strategies around, just to account for first player advantage.

Player 1: buyMoneyandDuchy; Player 2: justBuyMoney

But despite that, justBuyMoney still wins significantly. There is a draw 705 times, Player 1 wins 2880 times and Player 2 wins 6515 times.

Just for fun, let's see what happens if you also buy estates..

function [table,player] = buyMoneyandDuchyandEstate(table,player)

if ((player.money == 2) && (table.estate > 0))
   %if I can buy estate, buy estate
   table.estate = table.estate - 1;
   player.discard(end+1) = 21;
end
    
if ((player.money == 3) || (player.money == 4)) && (table.silver > 0)
    %if I can buy silver, buy silver
    table.silver = table.silver - 1;
    player.discard(end+1) = 12;
end
if (player.money == 5) && (table.duchy > 0)
    %if I can buy duchy, buy duchy
    table.duchy = table.duchy - 1;
    player.discard(end+1) = 22;
end
if ((player.money == 6) || (player.money == 7)) && (table.gold > 0)
    %if I can buy gold, buy gold
    table.gold = table.gold - 1;
    player.discard(end+1) = 13;
end
if player.money >= 8
    table.province = table.province - 1;
    player.discard(end+1) = 23;
end
end

Player 1: justBuyMoney; Player 2: buyMoneyandDuchyandEstate

Oh dear.

You can download all the MATLAB code files I used for this here. The main game loop is in dominionsimulate_twoplayer.m.
2_twoplayerDominionSim

Next time, we'll add Action Cards!

Comments are closed.