3
\$\begingroup\$

This is very simple poker server. Target is a REST server.

In real poker you do not act until action is on you (well you are not supposed to).

On a server it is nice to accept action (bet, call, call any, raise, and fold) in advance and then only process when it comes the player's turn. It makes for faster play and is more scalable. I would prefer not to debate if this is a proper model. In this simplified model there is only one action at this time that is nominally bet/call.

Next step would be to fold a player if they do not act in x period of time. I wanted to ask for feedback on this relatively simple model. Things get sticky fast background processing.

As tested it takes action for 1, 2, 4 and when action is submitted by 3 it processes 3 and 4.

Test - I know not a great test

PkrTable pkrTable = new PkrTable(new List<PokerPlayer>());
Debug.WriteLine("test submit action ID = 3");
pkrTable.pkrPlayers.FirstOrDefault(x => x.ID == 3).SbmtAction(14);

.

public class PkrAction
{
    //in real poker there are different actions  
    //this is just simplified  
    public PkrPlayer PkrPlayer { get; }
    public int Amt { get; }
    public PkrAction(PkrPlayer pkrPlayer, int amt)
    {
        PkrPlayer = pkrPlayer;
        Amt = amt;
    }
}  

.

public class PkrPlayer
{
    private PkrTable pkrTable;

    public int ID { get; }

    public void SbmtAction(int amount)
    {
        PkrAction pa = new PkrAction(this, amount);
        pkrTable.SbmtAction(pa);
    }

    public override int GetHashCode()
    {
        return ID;
    }
    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is PokerPlayer))
            return false;
        else
            return ID == ((PokerPlayer)obj).ID;
    }

    public PkrPlayer(int id, PkrTable _pkrTable)
    {
        ID = id;
        pkrTable = _pkrTable;
    }
}      

.

public class PkrTable 
{
    //the next two will be private - temp public for test
    public List<PkrAction> pkrActions = new List<PkrAction>();
    public List<PkrPlayer> pkrPlayers = new List<PkrPlayer>();
    private PkrPlayer waitingOn = null;

    public void SbmtAction(PkrAction pkrAction)
    {
        //on the main thread and the idea is to keep this open 
        //on a real table players are not supposed to act out of turn 
        //an a server a player can act out of turn as you just don't show it to other players
        //for scale and speed the server will take action out of turn 
        //this will be a REST service
        Debug.WriteLine($"SbmtAction {pkrAction.PkrPlayer.ID}");
        pkrActions.RemoveAll(x => x.PkrPlayer == pkrAction.PkrPlayer);//only keep the latest action
        //List is not concurrent - will put in a concurrent List later 
        pkrActions.Add(pkrAction);  //in final code will not add if the action is cancel 
        if (BeenWaitinOnYou(pkrAction))
        {
            Debug.WriteLine($"   SbmtAction BeenWaitinOnYou {pkrAction.PkrPlayer.ID}");
            Task taskA = new Task(() => ProcessQueue());
            taskA.Start();
            //below for test only
            taskA.Wait();
        }
        //in final code need to figure out how to fold out a player that does not submitt action in x time 
        //need to send notice to player that they are on the clock
    }

    private bool BeenWaitinOnYou(PkrAction pkrAction)
    {
        if(waitingOn != null && waitingOn == pkrAction.PkrPlayer)
        {
            return true;
        }
        return false;
    }           
    private bool ProcessAction(PkrAction pa)
    {
        return true; //for now assume any action is sucessful
    }
    private void ProcessQueue()
    {
        //in final product will be more complex as action can be more than one round 
        Debug.WriteLine("ProcessQueue");
        foreach(PkrPlayer pp in pkrPlayers)
        {
            Debug.WriteLine($"  ProcessQueue {pp.ID}");
            if(waitingOn != null && waitingOn != pp)
            {
                Debug.WriteLine($"  ProcessQueue continue {pp.ID}");
                continue;
            }
            waitingOn = null;
            PkrAction pa = pkrActions.FirstOrDefault(x => x.PkrPlayer == pp);
            if(pa == null)
            {
                //need to wait on the player 
                Debug.WriteLine($"  ProcessQueue waitingOn {pp.ID}");
                waitingOn = pp;
                break;
            }
            else
            {
                Debug.WriteLine($"  ProcessQueue ProcessAction {pp.ID}");
                if (ProcessAction(pa))
                {
                    pkrActions.Remove(pa);
                }
                else
                {
                    throw new Exception("process action failed - it should not");
                }
            }
        }
    }

    public PkrTable(List<PokerPlayer> pokerPlayers)
    {

        //below is for test 
        //not a great test but for now what I have
        PkrPlayer pp = new PkrPlayer(1, this);
        pp.SbmtAction(12);
        pkrPlayers.Add(pp);
        pp = new PkrPlayer(2, this);
        pp.SbmtAction(12);
        pkrPlayers.Add(pp);
        PkrPlayer pp3 = new PkrPlayer(3, this);
        pkrPlayers.Add(pp3);//this player has not submitted action
        pp = new PkrPlayer(4, this);
        pp.SbmtAction(12);
        pkrPlayers.Add(pp);

        //the idea is to leave SbmtAction open on main thread 
        Debug.WriteLine("PkrTable ctor call taskA");
        Task taskA = new Task(() => ProcessQueue());
        taskA.Start();
        taskA.Wait();  // this is only for test
        Debug.WriteLine("PkrTable ctor done");
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Is Pkr really that much shorter than Poker and couldn't you just type Pok and hit tab for auto-complete? Abbreviations to already short words confuse me. \$\endgroup\$ – Shelby115 Apr 2 '18 at 19:05
  • \$\begingroup\$ Wait, what's the difference between PkrPlayer and PokerPlayer? And why isn't PokerPlayer included above? \$\endgroup\$ – Shelby115 Apr 2 '18 at 19:07
  • \$\begingroup\$ @Shelby115 Because I am just working on one small piece of it right now. \$\endgroup\$ – paparazzo Apr 3 '18 at 15:36
2
\$\begingroup\$

Naming: Don't use abbreviations for already-short words. Amount instead of Amt, Poker instead of Pkr, Sbmt instead of Submit; you aren't being charged by the character. Don't use very similar names. It looks like you have classes PokerPlayer and PkrPlayer, and variables pokerPlayers and pkrPlayers? If you really can't think of names that differentiate them, are they the same?

Structure: ProcessQueue implies to me that you are going to iterate over all of your messages and process each one, but that's not what this does. Should ProcessQueue return a value to indicate whether it was able to process any messages? Your foreach(PkrPlayer pp in pkrPlayers) loop is pretty confusing; the tests against/updating of waitingOn are hard to follow.

I would suggest 2 major changes. First, just track the next player and have it always be non-null. Second, store the incoming actions in a Dictionary<PokerPlayer, PokerAction>. This will make it easy to find the next action or determine that you need to wait for a particular message to come in. ProcessQueue (or whatever you rename it to) can return a value indicating that it was able to move forward or if it is waiting for a player's action.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.