This chapter includes a single example program: a poker solitaire game written in JavaScript. This is the largest program presented in this book and illustrates the following concepts from previous chapters:
In addition, it's a good demonstration of a large-scale JavaScript application. The next sections describe and present the program.
The task for this example is to create a JavaScript poker solitaire game. The game board, at the start of a game, is shown in Figure 15.1. Here is a summary of how the game is played:
Figure 15.1 : The Poker Solitaire game board at the start of a game.
Before you move on to the program itself, let's look at a few of the challenges of planning a project of this size:
Tip |
You'll find the graphics you need for this example (cards, blank card, title) on the CD-ROM accompanying this book. |
Without further ado, Listing 15.1 shows the complete Poker Solitaire application, including all HTML and JavaScript functions.
Listing 15.1. (CARDS.htm) The Poker Solitaire application (HTML and JavaScript).
<HTML> <HEAD> <TITLE>Poker Solitaire</TITLE> <SCRIPT LANGUAGE="JavaScript"> // global variables var tally = new Array(14) var nextcard = 1; var nexti = new Image(53,68); // numeric comparison for sort () function numsort (a,b){ return a-b; } function InitGame() { nextcard = 1; // clear scores for (i=0; i<5; i++) { document.form1.col[i].value = " "; document.form1.row[i].value = " "; document.form1.diag1.value = " "; document.form1.diag2.value = " "; document.form1.total.value = " "; } // array for board contents board = new Array(26); for (i=1; i<26; i++) { board[i] = new Card(0,"x"); document.images[i].src = "blank.gif"; } // fill the deck (in order, for now) deck = new Array(53); for (i=1; i<14; i++) { deck[i] = new Card(i,"c"); deck[i+13] = new Card(i,"h"); deck[i+26] = new Card(i,"s"); deck[i+39] = new Card(i,"d"); } // shuffle the deck n = Math.floor(52 * Math.random() + 200); for (i=1; i<n; i++) { card1 = Math.floor(52*Math.random() + 1); card2 = Math.floor(52*Math.random() + 1); if (card1 != card2) { temp = deck[card2]; deck[card2] = deck[card1]; deck[card1] = temp; } } // draw the first card on screen document.images[26].src = deck[nextcard].fname(); nexti.src = deck[nextcard+1].fname(); // end InitGame } // place the draw card on the board where clicked function PlaceCard(pos) { if (board[pos].suit != "x") { return; } document.images[pos].src = document.images[26].src; document.images[26].src = "blank.gif"; board[pos] = deck[nextcard]; nextcard++; Score(); if (nextcard > 25) { EndGame(); } else { document.images[26].src = deck[nextcard].fname(); // cache next image for draw pile nexti = new Image(53,68); nexti.src = deck[nextcard+1].fname(); } } // check for completed rows and display row scores function Score() { totscore = 0; // rows for (x=0; x<5; x++) { r = x * 5 + 1; a = AddScore(board[r],board[r+1],board[r+2],board[r+3],board[r+4]) if (a != -1) { document.form1.row[x].value = a; totscore += a; } } // columns for (x=0; x<5; x++) { r = x + 1; a = AddScore(board[r],board[r+5],board[r+10],board[r+15],board[r+20]) if (a != -1) { document.form1.col[x].value = a; totscore += a; } } // diagonals a = AddScore(board[5],board[9],board[13],board[17],board[21]) if (a != -1) { document.form1.diag1.value = a; totscore += a; } a = AddScore(board[1],board[7],board[13],board[19],board[25]) if (a != -1) { document.form1.diag2.value = a; totscore += a; } document.form1.total.value = totscore; } // check for poker hands function AddScore(c1,c2,c3,c4,c5) { straight = false; flush = false; pairs = 0; three = false; // sorted array for convenience nums = new Array(5); nums[0] = c1.num; nums[1] = c2.num; nums[2] = c3.num; nums[3] = c4.num; nums[4] = c5.num; nums.sort(numsort); // no score if row is not filled if (c1.num == 0 || c2.num == 0 || c3.num == 0 || c4.num == 0 || c5.num == 0) { return -1; } // flush if (c1.suit == c2.suit && c2.suit == c3.suit && c3.suit == c4.suit && c4.suit == c5.suit) { flush = true; } // straight if (nums[0] + 4 == nums[1] + 3 == nums[2] +2 == nums[3] + 1 == nums[4]) { straight = true; } // royal flush, straight flush, straight, flush if (straight && flush && nums[4]==13) return 250; if (straight && flush) return 50; if (straight) return 4; if (flush) return 5; // tally array is a count for each card value for (i=1; i<14; i++) { tally[i] = 0; } for (i=0; i<5; i++) { tally[nums[i]] += 1; } for (i=1; i<14; i++) { // four of a kind if (tally[i] == 4) return 25; if (tally[i] == 3) three = true; if (tally[i] == 2) pairs += 1; } // full house if (three && pairs == 1) return 8; // two pair if (pairs == 2) return 2; // three of a kind if (three) return 3; // just a pair if (pairs == 1) return 1; // nothing return 0; // end AddScore() } // game over - final score function EndGame() { document.images[26].src = "blank.gif"; window.alert("Game Over"); } // make a filename for an image, given Card object function fname() { return this.num + this.suit + ".gif"; } // constructor for Card objects function Card(num,suit) { this.num = num; this.suit = suit; this.fname = fname; } </SCRIPT> </HEAD> <BODY> <FORM NAME="form1"> <TABLE> <tr> <td> <img src="title.gif" height=59 width=150> </td> <td> <a href="#" onClick="PlaceCard(1);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(2);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(3);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(4);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(5);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="row"> </td> <td> </td> </tr> <tr> <td> </td> <td> <a href="#" onClick="PlaceCard(6);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(7);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(8);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(9);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(10);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="row"> </td> <td> </td> </tr> <tr> <td> <B>Total<BR>Score:</B> <INPUT TYPE="TEXT" SIZE=5 NAME="total"></td> <td> <a href="#" onClick="PlaceCard(11);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(12);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(13);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(14);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(15);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="row"> </td> <td> </td> </tr> <tr> <td> <a href="#" onClick="InitGame();"> <b>New Game</b></a> <br><b><a href="psoldoc.html">Instructions</b></a></td> <td> <a href="#" onClick="PlaceCard(16);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(17);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(18);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(19);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(20);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="row"> </td> <td> <b><BR>Next<BR>Card:</b></td> </tr> <tr> <td> </td> <td> <a href="#" onClick="PlaceCard(21);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(22);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(23);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(24);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <a href="#" onClick="PlaceCard(25);"> <img border=0 src="blank.gif" height=68 width=53></a> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="row"> </td> <td> <img src="blank.gif" height=68 width=53></td> </tr> <tr> <td align=right> <INPUT TYPE="TEXT" SIZE=4 NAME="diag1"> </td> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="col"> </td> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="col"> </td> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="col"> </td> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="col"> </td> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="col"> </td> <td> <INPUT TYPE="TEXT" SIZE=4 NAME="diag2"> </td> </TABLE> </FORM> </BODY> </HTML>
The HTML for this application is nearly as complicated as the JavaScript code, but it's easy because it's repetitive. Each of the images in the game board includes an onClick event handler that calls the PlaceCard() function, described later.
In the next few sections, you'll take a look at how the program works.
The InitGame() function, shown in Listing 15.2, is called when the page loads and also when you use the New Game link. It performs the following tasks:
Listing 15.2. The InitGame() function begins a new game.
function InitGame() { nextcard = 1; // clear scores for (i=0; i<5; i++) { document.form1.col[i].value = " "; document.form1.row[i].value = " "; document.form1.diag1.value = " "; document.form1.diag2.value = " "; document.form1.total.value = " "; } // array for board contents board = new Array(26); for (i=1; i<26; i++) { board[i] = new Card(0,"x"); document.images[i].src = "blank.gif"; } // fill the deck (in order, for now) deck = new Array(53); for (i=1; i<14; i++) { deck[i] = new Card(i,"c"); deck[i+13] = new Card(i,"h"); deck[i+26] = new Card(i,"s"); deck[i+39] = new Card(i,"d"); } // shuffle the deck n = Math.floor(52 * Math.random() + 200); for (i=1; i<n; i++) { card1 = Math.floor(52*Math.random() + 1); card2 = Math.floor(52*Math.random() + 1); if (card1 != card2) { temp = deck[card2]; deck[card2] = deck[card1]; deck[card1] = temp; } } // draw the first card on screen document.images[26].src = deck[nextcard].fname(); nexti.src = deck[nextcard+1].fname(); // end InitGame }
Once the InitGame() function is finished, the program isn't running at all-that's the beauty of event-based programming. The next step is up to the user, who should click on one of the squares of the game board.
When a square is clicked, the PlaceCard() function, shown in Listing 15.3, is called. This function moves the card from the draw pile to the appropriate square. Because it's already in the cache, this happens instantly. An image object is then created to preload the next card. Figure 15.2 shows a game in progress with several cards placed.
Figure 15.2 : The Poker Solitaire game in progress.
Listing 15.3. The PlaceCard() function places the next card.
function PlaceCard(pos) { if (board[pos].suit != "x") { return; } document.images[pos].src = document.images[26].src; document.images[26].src = "blank.gif"; board[pos] = deck[nextcard]; nextcard++; Score(); if (nextcard > 25) { EndGame(); } else { document.images[26].src = deck[nextcard].fname(); // cache next image for draw pile nexti = new Image(53,68); nexti.src = deck[nextcard+1].fname(); } }
The Score() function, shown in Listing 15.4, is called each time a card is placed. This routine scans through the five rows and five columns and the two diagonals. The cards for each row are passed to the AddScore() function, which does the tricky part-finding poker hands and scoring them.
Listing 15.4. The Score() function scores the rows and columns.
function Score() { totscore = 0; // rows for (x=0; x<5; x++) { r = x * 5 + 1; a = AddScore(board[r],board[r+1],board[r+2],board[r+3],board[r+4]) if (a != -1) { document.form1.row[x].value = a; totscore += a; } } // columns for (x=0; x<5; x++) { r = x + 1; a = AddScore(board[r],board[r+5],board[r+10],board[r+15],board[r+20]) if (a != -1) { document.form1.col[x].value = a; totscore += a; } } // diagonals a = AddScore(board[5],board[9],board[13],board[17],board[21]) if (a != -1) { document.form1.diag1.value = a; totscore += a; } a = AddScore(board[1],board[7],board[13],board[19],board[25]) if (a != -1) { document.form1.diag2.value = a; totscore += a; } document.form1.total.value = totscore; }
The following scores are assigned to the various poker hands,
based on their relative probability:
Poker hand | |
Pair | |
2 pair | |
3 of a kind | |
Straight | |
Flush | |
Full house | |
4 of a kind | |
Straight flush | |
Royal Flush |
The EndGame() function, shown in Listing 15.5, is called when 25 cards have been played. This function is simple; it "blanks out" the draw card and informs you that the game is over with an alert. Because the total score is updated continuously, there's no need to worry about it here.
Listing 15.5. The EndGame() function ends the game.
// game over - final score function EndGame() { document.images[26].src = "blank.gif"; window.alert("Game Over"); }
Figure 15.3 shows the screen after a reasonably successful game. That's it! I hope this example shows you how much you can do with JavaScript and inspires you to even bigger things.
Figure 15.3 : The Poker Solitaire game display after all cards have been played.