building an asynchronous multiuser web app for fun... and maybe profit luke welling...
TRANSCRIPT
![Page 1: Building an Asynchronous Multiuser Web App for Fun... and Maybe Profit Luke Welling luke.welling@hitwise.com Laura Thomson laura@omniti.com](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/1.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/2.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/3.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/4.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/5.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/6.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/7.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/8.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/9.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/10.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/11.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/12.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/13.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/14.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/15.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/16.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/17.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/18.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/19.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/20.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/21.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/22.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/23.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/24.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/25.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/26.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/27.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/28.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/29.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/30.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/31.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/32.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/33.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/34.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/35.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/36.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/37.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/38.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/39.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/40.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/41.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/42.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/43.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/44.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/45.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/46.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/47.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/48.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/49.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/50.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/51.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/52.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/53.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/54.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/55.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/56.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/57.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/58.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/59.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/60.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/61.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/62.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/63.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/64.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/65.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/66.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/67.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/68.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/69.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/70.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/71.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/72.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/73.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/74.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/75.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/76.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/77.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/78.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/79.jpg)
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](https://reader037.vdocument.in/reader037/viewer/2022102710/5513c27a55034679748b4606/html5/thumbnails/80.jpg)
80
A word from our sponsor…
• PHP Lightning talks: [email protected]
• Book launch and signing at Powell’s onsite bookstore, 12.30 Thursday