Chapter 15
Real-Life Examples III
CONTENTS
This chapter includes a single example program: a poker solitaire
game written in JavaScript. This is the largest program presented
in this guide 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.
- The board consists of 25 squares in a 5-by-5 grid. A "draw"
card is on the lower right corner.
- The computer deals cards one at a time. Each card appears
in the draw pile. You can click on one of the 25 squares in the
grid to move the card to the board.
- Scoring is based on poker hands. Each row, column, and diagonal
is a separate hand, and is scored when it is completed. The total
score is a combination of all of these.
- The game ends when 25 cards have been dealt. You can also
click on the New Game link
to start over.
Before you move on to the program itself, let's look at a few
of the challenges of planning a project of this size:
- The board consists of 25 squares. By including a title graphic,
I conveniently made these the 1st through 25th images on the page-this
will make them easy to access via the document.images
array.
- I used a custom object, Card,
to store information about the cards in the game. This object
includes a number (1-13) and a suit (c, h, s, or d.)
- The deck of cards is stored in an array called deck;
each element is a Card object.
- The cards currently on the board are placed in an array called
board. Once again, these
are Card objects.
- I created 52 images-one for each card-and a blank image to
represent blank spaces on the board. These are all exactly the
same size, so they can be easily swapped inline.
- The whole game board is laid out using a table. The first
column includes a link to start a new game and the total score,
columns 2 through 6 are the game board, and the last column and
row are used for text fields. These will hold the score for each
column, row, and diagonal.
Tip |
You'll find the graphics you need for this example (cards, blank card, title) on the CD-ROM accompanying this guide.
|
Without further ado, Listing 15.1 shows the complete Poker Solitaire
application, including all HTML and JavaScript functions.
Listing 15.1. (CARDS.asp) 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.asp">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:
- Stores 52 Card objects,
in order, in the deck array
- Initializes the board
array with null values
- Shuffles the deck by swapping a random number of random cards
- Places the first card on the draw pile (otherwise known as
document.images[26])
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 | Score
|
Pair | 1 |
2 pair | 2
|
3 of a kind | 3
|
Straight | 4
|
Flush | 5 |
Full house | 8
|
4 of a kind | 25
|
Straight flush | 50
|
Royal Flush | 250
|
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.
|