80
Building an Asynchronous Multiuser Web App for Fun ... and Maybe Profit Luke Welling [email protected] Laura Thomson [email protected]

Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling [email protected] Laura Thomson [email protected]

Embed Size (px)

Citation preview

Page 1: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

Building an Asynchronous

Multiuser Web App for

Fun ... and Maybe Profit Luke [email protected] [email protected]

Page 2: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

2

Introduction

Page 3: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

3

Today’s task: Texas Hold ‘Em, OSCON Variant

• In this tutorial we’ll step through designing and building a multiplayer game

• UI will be web based, which presents special challenges.

• The tools we will use are:

• HTML/CSS/JavaScript

• AJAX

• PHP

• MySQL

Page 4: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

4

Speakers

• Luke Welling is a senior software engineer from Hitwise in Melbourne, Australia

• Laura Thomson is Director of Web Development at OmniTI in Columbia, Maryland

• We wrote “PHP and MySQL Web Development” (3/e, Sams Publishing, 2004)

• The most popular parts of the book are the projects

• (This talk is a work in progress of one of the projects from the 4/e.)

Page 5: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

5

Motivation

• Today’s talk should give you insight into:

–Asynchronous web apps

–Multiuser web apps and the challenges thereof

–Using these technologies together

• But we were only joking about the profit part, sorry

Page 6: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

6

Audience

• You’ll get the most out of this talk if you:– Know PHP, but are not an expert

– Know a little about the other technologies.

Page 7: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

7

• Introduction (You are here)

•The rules

•The goal

•The architecture

•The components

Overview

Page 8: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

8

The Goal

Page 9: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

9

The rules

Page 10: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

10

Texas Hold ‘Em OSCON Variant:Basics

• Each player is dealt two pocket or hole cards.

• There are also 5 community cards.

• Each player’s hand consists of the best hand they can make out of those seven cards. (7C5, or 21 possible hands)

• Up to eight players

Page 11: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

11

Sequence of Play

• Before any cards are dealt, first player posts a bet, called a small blind, and then second player posts a bigger bet, called a big blind

• Basically a tax on sitting at the table

Page 12: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

12

Sequence of play - continued

• Each player is then dealt two cards

• A round of betting ensues

• Three community cards are dealt followed by a round of betting

• A fourth community card is dealt followed by a round of betting

• The fifth community card is dealt followed by a fourth and final round of betting

Page 13: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

13

Betting• Each player can either:

– Call (sometimes referred to as “see”) – either match the existing bet, or go “all in” if they don’t have enough money to match it

– Raise – bet an increased amount

– Fold – quit

• Betting rounds occur at several points throughout the game

• Each round may go through the players several times and ends when each player has either bet the same amount, folded, or gone all in

Page 14: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

14

Ranking of hands

• Winners are then determined according to the standard ranking of poker hands:– Straight flush: Five cards in sequence and of the same suit. (Q♦ J♦ 10♦ 9♦ 8♦)– Four of a kind: A hand with four cards of the same rank. ( 4♣ 4♦ 4♥ 4♠ 9♥)– Full house: A hand with three of one rank and two of another. ( 8♣ 8♦ 8♠ K♥ K♠)– Flush: Five cards of the same suit. (K♠ J♠ 8♠ 4♠ 3♠)– Straight: Five cards in sequence. (7♦ 6♥ 5♠ 4♦ 3♦)– Three of a kind: Three cards of the same rank. ( 7♣ 7♥ 7♠ K♦ 2♠)– Two pair: Two cards of one rank, two of another. ( A♣ A♦ 8♥ 8♠ Q♠)– One pair: Two cards of the same rank. (9♥ 9♠ A♣ J♠ 4♥)– High card: Also known as a "no pair" hand. The following example is considered

"Ace high." ( A♦ 10♦ 9♠ 5♣ 4♣)(Source: http://en.wikipedia.org/wiki/Poker_hand)

Page 15: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

15

Payout

• If you win you get the pot

• If there’s a draw then it’s split between people that draw

• (Handling “all in” is more complicated and not on today’s agenda)

Page 16: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

16

The goal

Page 17: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

17

The goal

• Build a system that allows people to play OSCON Texas Hold ‘Em over the web without using Flash/Java applets etc, just using HTML and Ajax.

• Up to eight players at once

• No AI players (easier in many respects), AS instead

• On the way, we might discover why Flash and Java are more appropriate popular for this task

Page 18: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

18

The architecture

Page 19: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

19

Basic architecture

UI

RulesEngine

Game Controller

Database

Ajax requests

Game statechanges, HTML

Serialization

Behaviors,Validity checking

RendererInitialHTML

fragments

Page 20: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

20

Overall architecture

• This is an MVC (Model – View – Controller) patterned architecture– UI + Renderer is the view

– Game controller is the controller

– Rules engine + DB is the model

Page 21: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

21

UI

• UI is vanilla HTML + CSS + JavaScript

• Changes occur via Ajax updates on a per player basis

• UI sends requests to the controller– Create Game

– Join Game

– Start Game

– Poll

– Play (Call, Raise, Fold)

Page 22: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

22

Controller

• Controller processes requests from the UI, and returns HTML for the portions of the UI that have changed.

• Controller processes the 4.2 different kinds of UI request

Page 23: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

23

Create Game

• Triggers these events in the rule engine:

– Creates the game object

– Creates and shuffles the deck

– Creates empty arrays and objects to hold the game objects

– Serializes the Game for the first time

Page 24: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

24

Game start

• Triggers these events in the rule engine:

– Unserializes the empty Game

– Adds dummy players

– Deals with small and big blinds

– Deals player cards

– Serializes again so other players can load the same data

Page 25: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

25

Poll

• UI gets state changes by polling the game controller

• Several different choices in how to implement game state updates in these kind of systems. Polling is the most common, and certainly the easiest to implement.

• Games are tracked via a gamestate (like a revision number). If a particular player is at revision x and the current gamestate is at y, we need to update their view to version y.

• This is the Periodic Refresh design pattern (http://ajaxpatterns.org/Periodic_Refresh)

Page 26: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

26

Clever Polling

• By tracking state number, we save processing and bandwidth

• You could refresh the whole screen every few seconds with plain HTML

• We can check every few seconds, but ignore most polls

• We only have to refresh if there has been a change

• We can just refresh the volatile parts

• We could fairly easily be a lot more clever and track at which state each UI item last changed, and only refresh the few that have changed recently

Page 27: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

27

Plays

• There are three possible play actions:– Raise

– Call (including all in)

– Fold

• Check the validity of the play with the rules engine: whether a play is valid at any point depends where we are in the sequence of the game

Page 28: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

28

Rules engine

• Rules engine is OO PHP, consisting of the following classes:– Game: representing the whole game– Card: a single card– CardCollection: a collection, strangely enough, of cards– Deck, a CardCollection, representing the deck to be used in

the game, including card ordering for dealing purposes– Hands, are CardCollections, representing a player’s possible

hands– HandCollection, includes features like sorting– CommunityCards, a CardCollection

Page 29: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

29

Database

• The database contains the following tables:–players

–games

Page 30: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

30

mysql> describe players;

+----------+------------------+------+-----+---------+--------------+| Field | Type | Null | Key | Default | Extra |+----------+------------------+------+-----+---------+--------------+| id | int(10) unsigned | | PRI | NULL |auto_increment|| password | varchar(40) | YES | | NULL | || login | varchar(50) | YES | MUL | NULL | || gameid | int(11) | YES | | 0 | || name | varchar(50) | YES | | NULL | |+----------+------------------+------+-----+---------+--------------+5 rows in set (0.00 sec)

Page 31: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

31

mysql> describe games;+-------+------------------+------+-----+---------+----------------+

| Field | Type | Null | Key | Default | Extra |

+-------+------------------+------+-----+---------+----------------+

| id | int(10) unsigned | | PRI | NULL | auto_increment |

| state | int(11) | | | 0 | |

| data | blob | YES | | NULL | |

+-------+------------------+------+-----+---------+----------------+

3 rows in set (0.00 sec)

Page 32: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

32

Database Serialization

• In general we are storing serialized objects from the Rules Engine as blobs, plus a game state version number

• Why not more granular data?• For the purpose of this app we only need to access the above

information. This is a pretty fast way to store and retrieve data for what we want to do.

• If we required any reporting or auditing functionality we would need to do more serious ORM

Page 33: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

33

Issues and alternatives

• Sending changes: ghetto version control

• Server overhead / scalability

• Serialization

• Security

Page 34: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

34

Version control

• About 75% of the way through implementation, complaints were heard: ‘Aren’t we just re-implementing Subversion?”

• There is a PHP extension for this: svn

• The difficulty would lie in writing a JavaScript client capable of doing svn update

• Nice idea though, and given more time and JS-Fu…

Page 35: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

35

Scalability

• Polling is easy to implement. • For one eight player game it’s pretty trivial for each browser to poll

the server every 3 seconds• If this became wildly successful this introduces a lot of load• Not difficult load to manage: it’s just HTML, and small pieces at

that. (Note we’re not resending the whole page, just portions of it)• Some alternatives exist:

– Keep connections open between requests – mod_pubsub to push from server to client– Implement the HTTP Streaming pattern

(http://ajaxpatterns.org/HTTP_Streaming)

Page 36: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

36

Serialization

• ORM done in this app is extremely simplistic (using PHP’s serialize() and unserialize() functions)

• Could be done in a more granular way using an ActiveRecord pattern implementation

Page 37: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

37

Security

• This particular implementation is for fun. If we were playing for actual money we would need:– Better session management

– A complete non-volatile audit trail of all plays (times, players, originating IPs)

– SSL

– More careful implementation of timing (or terms and conditions at least) to avoid litigation

– To make it harder to read the HTML into a bot

Page 38: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

38

The components

Page 39: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

39

JavaScript

// trigger a poll every three seconds, but don't

// do one immediately

// on page load as that would increase the risk of the

// script being only partially loaded

function periodicallyUpdate(first)

{

if(!first)

{

sendRequest("poll");

}

setTimeout('periodicallyUpdate(0)',3000)

}

Page 40: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

40

Recipe

• 1 JavaScript file• 1 CSS file• 2 simple database tables• Mostly PHP

Page 41: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

41

Object Model

HandCollection

Controller

Hand

Game

Player

Renderer

Deck

CardCollection

Card

Page 42: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

42

// create a cross browser object to make our ajax requests through

function createHttpRequestObject()

{

// use feature sniffing to find out what type of object to create

var httpRequest;

if (window.XMLHttpRequest)

{ // Mozilla, Safari, ...

httpRequest = new XMLHttpRequest();

}

else if (window.ActiveXObject)

{ // IE

httpRequest = new ActiveXObject("Microsoft.XMLHTTP");

}

return httpRequest;

}

// make an instance of it

var http = createHttpRequestObject();

Page 43: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

43

//make an Ajax request

function sendRequest(action)

{

// append what the client thinks the current state is to every request

var state = parseInt(document.getElementById('clientState').value);

http.open('get', 'gameController.php?clientState='+state+'&action='+action);

http.onreadystatechange = handleResponse;

http.send(null);

}

Page 44: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

44

// when we get an answer we need to parse it and do something with the chunks

// in this case, the pipe delimited format is very simple, and needs limited processing

function handleResponse() { if(http.readyState == 4) { var response = http.responseText; var updates = new Array(); var count = 0; if(response.indexOf('|') != -1) {

updates = response.split('|'); // make sure we have an even number of items, for pairs of id and text

count = Math.floor((updates.length)/2)*2; for(var i=0; i<count; i+=2) {

Page 45: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

45

if('status' == updates[i]){ document.getElementById('statusBox').value += "\

n"+updates[i+1]; }

else if('clientState' == updates[i]){ document.getElementById(updates[i]).value =

updates[i+1];}else{ document.getElementById(updates[i]).innerHTML =

updates[i+1];}

} } }}

Page 46: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

46

Data Format

communityCards|<div class= "cardSpace"> </div><div class= "cardSpace"> </div><div class= "cardSpace"> </div><div class= "cardSpace"> </div><div class= "cardSpace"> </div>|playerName|<h2>Not Playing This Game</h2>|playerCards|<div class= "cardSpace"> </div><div class= "cardSpace"> </div>|playerText|<p class = "importantText">Balance: $0.00</p> <p class = "importantText">Stake: $0.00</p> <p class = "importantText">Total Pot: $0.00</p>|player0Cards|<div class= "cardSpace"> </div><div class= "cardSpace"> </div>|player0Text|<h2>1: Empty Seat</h2> <p>Balance: $0.00</p> <p>Stake: $0.00</p>|player1Cards|<div class= "cardSpace"> </div><div class= "cardSpace"> </div>|player1Text|<h2>2: Empty Seat</h2> <p>Balance: $0.00</p> <p>Stake: $0.00</p>|player2Cards|<div class= "cardSpace"> </div><div class= "cardSpace"> </div>|player2Text|<h2>3: Empty Seat</h2> <p>Balance: $0.00</p> <p>Stake: $0.00 …

Page 47: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

47

CSS Makes some things very easy

// render a placeholder for a card so the

//layout does not collapse

public static function renderCardSpace()

{

return '<div class= "cardSpace"> </div>';

}

// you see the backs of other players' cards

public static function renderCardBack()

{

return '<div class="cardBack"> </div>';

}

Page 48: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

48

… and some not so

Page 49: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

49

Card CSS

.card, .cardBack, .cardSpace{ background-color: #fff; margin: 0.2em; float: left; border-color: #000; border-width: .05em; border-style: solid; position: relative; width: 11em; height: 14em; -moz-border-radius:0.75em; border-radius:0.75em;}

Page 50: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

50

Same code – Same result

public function renderCommunityCards($game = null) { if(is_a($game, 'Game')) { return $game->renderCommunityCards(); } else { // we are not in an active game return Card::renderCardSpace(). Card::renderCardSpace(). Card::renderCardSpace(). Card::renderCardSpace(). Card::renderCardSpace(); } }

Page 51: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

51

OO Delegation // render the collection, or with a count, blanks spaces to pad public function render($count=0) { if($count) { for($i=0; $i<$count; $i++) { if(is_a($this->cards[$i], 'Card')) { $return .= $this->cards[$i]->render(); } else { $return .= Card::renderCardSpace(); } } } else { foreach($this->cards as $card) { $return .= $card->render(); } } return $return; }

Page 52: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

52

public function render() { $colour = $this->getColour(); $entity = $this->getEntity(); $locations = $this->getLocations(); $return = "<div class=\"card $colour\"> <div class=\"rankArea $colour\"> <div class=\"rank $colour\">{$this->rank}</div> <div class=\"sideSuit $colour\">$entity</div> </div>"; if('J'==$this->rank||'Q'==$this->rank||'K'==$this->rank) { $return .= "<div class=\"locFace\"> <img src = 'images/".$this->getLongRank().".gif' class

= \"locFace\"> </div>"; } else if ('A' == $this->rank) { $return .= "<div class=\"locFace $colour\"> <span class=\"face $colour\">$entity</span>

Page 53: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

53

</div>"; } else { foreach($locations as $location) { if('Face' != $location) { $return .= "<div class=\"loc{$location} $colour\"> <div class=\"suit $colour\">{$entity}</div> </div>"; } } } $return .= " <div class=\"bottomRankArea $colour\"> <div class=\"sideSuit $colour\">$entity</div> <div class=\"rank $colour\">$this->rank</div> </div> </div>"; return $return;• }

Page 54: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

54

Playerfunction load($id) { // the id will be passed in since it's been retrieved from the player's session // comes from authentication.php $this->db = new mysqli('localhost', 'poker', 'pokerpass', 'poker'); $id = intval($id); $query = "select name, gameid from players where id = $id"; $res = $this->db->query($query);

if($res) { $name = mysqli_fetch_array($res); $name = $name[0]; $this->setName($name); $this->id = $id;

} else { unset($this); } }

Page 55: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

55

More Player

function setStatus($status) { switch($status) { case 'fold' : $this->cards = new CardCollection(); // fallthrough ... case 'raise' : case 'call' : case 'allIn' : $this->status = $status; break; default : trigger_error("invalid status", E_USER_WARNING); } }

Page 56: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

56

Game::serialize() function serialize() { global $db; $db->autocommit(false);

// serialize this object $query1 = "select state from games where id =". $this->id; $res1 = $db->query ($query1); $row = $res1->fetch_assoc(); $this->state = $row['state']; //update it in the object before serializing $this->state++;

$query = "update games set data= '".mysqli_real_escape_string($db, serialize($this))."' where id = ".$this->id;

$db->query($query); // update the state in the db too $query = "update games set state = (state+1) where id = ".$this->id; $db->query($query);

$db->commit(); }

Page 57: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

57

Game::unserialize()

public static function unserialize($id)

{

global $db;

$query = "select * from games where id = $id";

$result = $db->query($query);

$row = $result->fetch_assoc();

$old = unserialize($row['data']);

return $old;

}

Page 58: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

58

function bestHand($player) { if($player->getStatus()=='fold') { return null; } $stored = $this->bestHands->getHandByOwnerId($player->getId());

if($stored) { return $stored; } // note this code is assuming 2 hole cards and 5 community cards

// if you want to make another variant you will have to rewrite it

$cards = new CardCollection();

Page 59: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

59

$playerHands = new HandCollection();

$cards->add($this->communityCards->peek(0));

$cards->add($this->communityCards->peek(1));

$cards->add($this->communityCards->peek(2));

$cards->add($this->communityCards->peek(3));

$cards->add($this->communityCards->peek(4));

$cards->add($player->peekCard(0));

$cards->add($player->peekCard(1));

if($cards->count()!=7)

{

trigger_error("Cards missing", E_USER_WARNING);

return null;

Page 60: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

60

}

// generate all possible hands of 5 from 7 cards

for($i = 0; $i<6; $i++)

{

for($j = $i+1; $j<7; $j++)

{

$hand = array();

for($k=0; $k<7; $k++)

{

if($k!=$i&&$k!=$j)

{

$hand[]=$cards->peek($k);

}

Page 61: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

61

} $handObject = new Hand($hand, $player); $playerHands->add($handObject);

} } // 7C5 is 21, so if we are missing some possibilities, we have a problem

if($playerHands->count()!=21) { trigger_error("Hands missing", E_USER_WARNING); } // get the best from the 21 possibles. return $playerHands->getBestHand(); }

Page 62: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

62

Handfunction handCompare($a,$b) { $a = $a->getNumericRank(); $b = $b->getNumericRank();

if($a==$b) { return 0; } else if ($a<$b) { return -1; } else // ($a>$b) { return 1; } }

Page 63: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

63

// calculate completely arbitrary numbers to rank hands

// Yes, 15 is a magic number,

// As Aces are being ranked as 14, rank/15 will give a number less than one

public function getNumericRank()

{

if($this->isARoyalFlush())

{

return 9;

}

if($this->isAStraightFlush())

{

return 8+$this->getHighCard(1)->getNumericRank();

}

Page 64: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

64

if($this->isAFourOfAKind()) { return 7+Card::getNumericRank($this->isAFourOfAKind())/15;

} if($this->isAFullHouse()) { return 6+ Card::getNumericRank($this->isAThreeOfAKind())/15+

Card::getNumericRank($this->getHighCard(4))/150;

} if($this->isAFlush()) { return 5 + $this->getHighCard()->getNumericRank()/15 +

$this->getHighCard(1)->getNumericRank()/150 +

Page 65: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

65

$this->getHighCard(2)->getNumericRank()/1500 +

$this->getHighCard(3)->getNumericRank()/15000 +

$this->getHighCard(4)->getNumericRank()/150000;

} if($this->isAStraight()) { return 4+$this->getHighCard()->getNumericRank()/15; } if($this->isAThreeOfAKind()) { return 3+Card::getNumericRank($this->isAThreeOfAKind())/15;

} if($this->isATwoPairs()) {

Page 66: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

66

$pairRanks = $this->isATwoPairs();

$pairRanks[0] = Card::getNumericRank($pairRanks[0]);

$pairRanks[1] = Card::getNumericRank($pairRanks[1]);

// the hand was sorted, so the second rank is going to be the higher one

return 2+$pairRanks[1]/15+($pairRanks[0]/150);

}

if($this->isAPair())

{

return 1+Card::getNumericRank($this->isAPair())/15;

}

return $this->getHighCard()->getNumericRank()/15;

}

Page 67: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

67

Specific hand functionspublic function isAThreeOfAKind() { if( ($this->peek(0)->getRank() == $this->peek(1)->getRank() && $this->peek(1)->getRank() == $this->peek(2)->getRank() ) || ($this->peek(1)->getRank() == $this->peek(2)->getRank() && $this->peek(2)->getRank() == $this->peek(3)->getRank() ) || ($this->peek(2)->getRank() == $this->peek(3)->getRank() && $this->peek(3)->getRank() == $this->peek(4)->getRank() ) ) { // Three of a kind sorted into order will always have a card at position 2 return $this->peek(2)->getRank(); } else { return false; } }

Page 68: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

68

GameController

/* This is the AJAX based controller that runs the game */

// get the overall action$action = $_GET['action'];$clientState = intval($_GET['clientState']);

switch ($action) { case 'createGame': { $game = new Game(); $game->serialize(); break; }

Page 69: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

69

GameController case 'startGame': {

// temporary code until I write some join up code for players … $game->postSmallBlind(); $game->postBigBlind();

$game->dealPlayerCards(2); $game->serialize(); echo 'status|OK, game started|'; } else { echo 'status|Game already started|'; } } break;

Page 70: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

70

GameController

case 'fold' : case 'call' : case 'raise' : { $game = Game::unserialize($gameId); $userId = intval($_SESSION['userId']); switch ($action) { case 'fold': // mark player as folded $game->fold($game->getPlayerById($userId)); break;

Page 71: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

71

GameController

case 'call' : $game->call($game->getPlayerById($userId));

//all the robo-players can call too, we want to go home one day $game->call($game->getPlayer(1)); $game->call($game->getPlayer(2)); $game->call($game->getPlayer(3)); $game->call($game->getPlayer(4)); break; case 'raise': $amount = floatval($_GET['$raiseAmount']); $game->raise($game->getPlayerById($userId), $raiseAmount); break; default: }

Page 72: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

72

GameController

// have we rolled over to a new betting round? { if($game->getBettingRound()>1 && $game->countCommunityCards()<3) { $game->dealCommunityCards(3); } else if($game->getBettingRound()>2 && $game-

>countCommunityCards()<4) { $game->dealCommunityCards(1); } else if($game->getBettingRound()>3 && $game-

>countCommunityCards()<5) { $game->dealCommunityCards(1); }

Page 73: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

73

GameController

else if($game->isGameOver()) { $winningHand = $game->getWinningHand(); $winningPlayer = $winningHand->getOwner(); $winningLocation = $game->getPlayerLocation($winningPlayer-

>getId()); echo "status|Winner is ".$winningPlayer->getName()." with ". $winningHand->getRank().'|'; } }

$game->serialize(); } break; default: }

Page 74: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

74

GameController

if(!$game){ $game = Game::unserialize($gameId);}// update all - poll command, and after othersif($clientState<$game->getState()){ echo

'communityCards|'.SectionRenderer::renderCommunityCards($game).'|';

// update player data echo 'playerName|'.SectionRenderer::renderPlayerName($game).'|'; echo 'playerCards|'.SectionRenderer::renderPlayerCards($game).'|'; echo 'playerText|'.SectionRenderer::renderPlayerText($game).'|';

Page 75: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

75

GameController

// update small player cards and text for($i = 0; $i<MAX_PLAYERS; $i++) { echo 'player'.$i.'Cards|'.SectionRenderer::renderPlayerCards($game,

$i).'|'; echo 'player'.$i.'Text|'.SectionRenderer::renderPlayerText($game,

$i).'|'; } echo 'clientState|'.$game->getState().'|';}else{// echo "status|no updates from state $clientState|";}

Page 76: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

76

Conclusions

Page 77: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

77

Lessons learned

• JavaScript Libraries?• FireBug is great• Serialization in PHP is simple

Page 78: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

78

Questions?

• Slides are online at

• http://lukewelling.com

• http://omniti.com/resources/talks

Page 79: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

79

The J, Q, K Images

• Nicu Buculei

• http://www.nicubunu.ro/cards/

• Full sets of SVG cards

• Public Domain

Page 80: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com

80

A word from our sponsor…

• PHP Lightning talks: [email protected]

• Book launch and signing at Powell’s onsite bookstore, 12.30 Thursday