Skip to content

Instantly share code, notes, and snippets.

@talyian
Last active September 28, 2018 15:03
Show Gist options
  • Save talyian/f561d71cc2477a2c59416f76536ef179 to your computer and use it in GitHub Desktop.
Save talyian/f561d71cc2477a2c59416f76536ef179 to your computer and use it in GitHub Desktop.
Poker Demo
POC of applying some Async primitives to a Poker game for a discussion on async from gamedev.stackexchange.com
Copyright 2018 Jimmy Tang
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
/// All the network messages
class Message {
public static Message Parse(string msg) {
var parts = msg.Split(' ', 2);
switch (parts[0]) {
case "name": return new NameMessage { Name = parts[1] };
case "bet": return new BetMessage { Amount = int.Parse(parts[1]) };
case "fold": return new FoldMessage { };
case "quit": return new QuitMessage { };
default: Console.Error.WriteLine("unknown message: {0}", msg); return null;
}
}
}
class NameMessage : Message { public string Name; }
class BetMessage : Message { public int Amount; }
class FoldMessage : Message { }
class QuitMessage : Message { }
class GameServer {
public TcpListener server;
public Game game;
public GameServer(Game game) {
this.game = game;
this.server = new TcpListener(new IPAddress(new byte[] { 127, 0, 0, 1 }), 8001);
}
public async Task Run() {
this.server.Start();
while (true) {
var tcpclient = await this.server.AcceptTcpClientAsync();
var player = new Player(game);
var client = new NetworkPlayer(player, tcpclient);
Console.WriteLine("client connected");
game.Players.Add(player);
game.SignalPlayerUpdate();
}
}
}
// manages the messaging for a network player
class NetworkPlayer {
public Player player;
public TcpClient tcp;
public NetworkPlayer(Player p, TcpClient t) {
this.player = p;
this.tcp = t;
this.Run();
}
public async Task Run() {
var reader = new System.IO.StreamReader(tcp.GetStream());
while (tcp.Connected) {
try {
var msg = await reader.ReadLineAsync();
if (msg == null) {// disconnected
tcp.Close();
player.Handle(new QuitMessage { });
}
else if (msg == "") // no message
await Task.Delay(100);
else if (Message.Parse(msg) is Message message) {
Console.WriteLine("{0}: {1}", player.Name, msg);
player.Handle(message);
}
else
Console.WriteLine("{1}: {0}", msg, player.Name);
}
catch (Exception e) {
Console.Error.WriteLine("Error: {0}", e);
}
}
Console.WriteLine("{0} disconnected", player.Name);
}
}
using System;
/// A Poker player.
class Player {
public string Name { get; set; }
/// Amount of money in stack
public int Bank { get; set; }
/// Amount of money curretly in pot
public int Bet { get; set; }
public enum PlayerStatus {
Waiting, // hasn't made an action this turn
Moved, // bet already
Folded, // folded already
Quit, // left the table
}
public PlayerStatus Status { get; set; }
Game game;
public Player(Game game) { this.game = game; }
public void Handle(Message msg) {
Handle((dynamic) msg);
game.SignalPlayerUpdate();
}
public void Handle(NameMessage msg) {
this.Name = msg.Name;
}
public void Handle(BetMessage bet) {
if (bet.Amount > Bank)
throw new Exception("cannot afford bet!");
if (Status == PlayerStatus.Folded)
throw new Exception("Cannot bet when folded");
else if (Status == PlayerStatus.Moved)
throw new Exception("Already acted this round");
Bank -= bet.Amount;
Bet += bet.Amount;
Status = PlayerStatus.Moved;
}
public void Handle(FoldMessage fold) {
Status = PlayerStatus.Folded;
}
public void Handle(QuitMessage quit) {
Status = PlayerStatus.Quit;
}
public void NewTurn() {
switch(Status) {
case PlayerStatus.Folded: break;
case PlayerStatus.Quit: break;
case PlayerStatus.Waiting: throw new Exception("Round Ended and player hasn't moved");
case PlayerStatus.Moved:
// go back to waiting state
Status = PlayerStatus.Waiting; break;
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
</Project>
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Sockets;
class Game {
public List<Player> Players = new List<Player>();
public SemaphoreSlim PlayerUpdated = new SemaphoreSlim(0);
public void SignalPlayerUpdate() { PlayerUpdated.Release(); }
public async void GameLoop() {
// start
Console.WriteLine("Waiting for 3 players to connect");
while (Players.Count < 3) await PlayerUpdated.WaitAsync();
// Introduction
Console.WriteLine("Waiting for all players to Identify themselves");
while (Players.Any(p => p.Name == null)) await PlayerUpdated.WaitAsync();
// Round 1: Betting
Console.WriteLine("Round 1: Betting: Waiting for all players to take an action");
switch (Task.WaitAny(
WaitForAllPlayerActions(),
GameOverPlayerWon(),
GameOverPlayerDisconnected())) {
case 0: // all player actions finished, go to next round;
Console.WriteLine("disconnected, game over");
return;
case 1: // all but one player folded, round over
Console.WriteLine("game over, {0} won",
Players.First(x => x.Status == Player.PlayerStatus.Waiting || x.Status == Player.PlayerStatus.Moved).Name);
return;
case 2: // player disconnected, game over
Console.WriteLine("disconnected, game over");
return;
}
// TODO: Generate and Send Flop message to all players
// send flop
Console.WriteLine("OK!");
}
// A task that returns when all players have set FinishedRound.
public async Task WaitForAllPlayerActions() {
// TODO: this should really go in order of players around the table
// instead of letting everyone send actions in any order
while (Players.Any(p => p.Status == Player.PlayerStatus.Waiting))
await PlayerUpdated.WaitAsync();
}
public async Task GameOverPlayerWon() {
while (Players.Count(p => p.Status == Player.PlayerStatus.Waiting || p.Status == Player.PlayerStatus.Moved) > 1)
await PlayerUpdated.WaitAsync();
}
public async Task GameOverPlayerDisconnected() {
while (Players.All(p => p.Status != Player.PlayerStatus.Quit))
await PlayerUpdated.WaitAsync();
}
}
class Program {
static void Main(string[] args) {
var game = new Game();
game.GameLoop();
var server = new GameServer(game);
server.Run().Wait();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment