Last week I explained how I made part of the Game logic, first in C# and then in F#. This week I will zoom in a bit on the Player’s logic which I too made in C# first, in a train, to impress @tcoopman.
A player needs to be able to play cards. Duh! Initially, I thought, let’s program the simplest AI that does not break the rules. Following the rules just entails “following suit” which means that, if you can, you have to play the same suit as the player that started the suit.
I am going to make it a little bit more interesting,
- by always playing the highest of the “open suit”, and
- by always playing the highest trump when we run out of open suit cards.
- If our bot is the starting player themselves, we start with the highest card we have.
You could call the strategy greedy 🙂
I started, again, in C#, where I did shameful things like initializing fields of instances outside of a constructor:
var players = new List<Player>(4) {new Player(Desi.Player.North), new Player(Desi.Player.East), new Player(Desi.Player.South), new Player(Desi.Player.West) };
string deal = "[Deal \"N:.63.AKQ987.A97 A8.KQ.T.QJT J97.J987.3.K KQT.AT.J.\"]"; var hands = PBN.Reader.getHandList(deal); var dealer = PBN.Reader.getDealer(deal); for (int i = 0; i < 4; i++) { var firstHand = hands[i]; players[i].hand = ((Desi.Hand.Hand)(firstHand)).Item.ToList(); }
I know! I feel terrible. Let’s make things better!
I have made a Player type in F# now:
module Player = type T = {Direction : Desi.Player; Hand :Desi.Hand let create d h = {Direction = d; Hand = h} We can now initialize a list of those Players from C#, using the constructor: for (int i = 0; i < players.Capacity; i++) { var firstHand = hands[i]; Desi.Player Direction = Desi.playerFromInt(i); players.Add(Desiderius.Player.create(Direction, firstHand)); }
For this, I turned Desi.Player (which I realize now, should be called something like “direction” as it is now an argument of a class also called player. Will update that after this post) into an enum, like I did last week with Rank. We can then parse from an int and thus loop over the 4 directions. That, by the way, could aslo simplify the “partner” function:
let partner(p:Player) = match p with | West -> Player.East | East -> Player.West | South -> Player.North | North -> Player.South
Instead we could use the parse function and write:
let partner(p:Player):Player = let partnerInt = (int p + 2) % 4 enum partnerInt
But, I am not really sure if I like that. It is only a tiny bit shorter, but a whole lot less easy to read. Any thoughts?
Playing a card in C#
So, how does the actual “nextCard” of a player look like? I’ll give you a sneak peek of my ugly C#, so you can think how you can make it better all week 🙂
public Desi.Card nextCard(List<Desi.Card> history, int place, Desi.Suit trump) { //order the cards from high to low var orderedHand = hand.OrderBy(x => x.Item2); if (place == 0) { //play the highest card return orderedHand.First(); } else { //what is the suit we should follow? Desi.Suit openSuit = history[0].Item1; Desi.Rank openRank = history[0].Item2; //do we have a card in the openSuit? var cardInOpenSuit = orderedHand.FirstOrDefault(x => x.Item1 == openSuit); if (cardInOpenSuit != null) { //do we have a card in the openSuit higher than the first card? var cardInOpenSuitHigher = orderedHand.FirstOrDefault(x => x.Item1 == openSuit && x.Item2 > openRank); if (cardInOpenSuitHigher != null) { return cardInOpenSuitHigher; //yes? let's play it } else { return orderedHand.LastOrDefault(x => x.Item1 == openSuit); //no, just play a small card } } else { //we do not have to follow, can we play trump? var trumpCard = orderedHand.FirstOrDefault(x => x.Item1 == trump); if (trumpCard != null) { return trumpCard; } else { //I am a monkey, I don't know anything else :) just play the higherst card return orderedHand.FirstOrDefault(); } } } }
Ow, all the null checks :'( The horrible .Item1. Jugh! Next week you can read the F# version!
2 thoughts on “More playing [Desiderius part 10]”
Comments are closed.