PHP is an easy-to-use, easy-to-learn, widely accessible programming language. It's well suited for developing simple scripts you can use to help you in all kinds of games. Whether you play simple pen-and-paper games by yourself, complex tabletop role-playing games with a group of people, or online games of any kind, this series will have something for you. Each article in this "30 game scripts you can write in PHP" series will cover 10 scripts in 300 words or less (3d10 stands for "roll three 10-sided dice") simple enough for even a beginning developer, but useful enough for a seasoned game player. The goal is to give you something you can modify to suit your needs, so you can impress your friends and players by busting out your laptop at your next gaming session.
Getting started
As both a game master/storyteller and a developer, I frequently find myself writing little utilities and scripts to help me when running, planning, and playing games. Sometimes I need a quick idea. Other times, I just need a whole pile of names for Non-Player Characters (NPCs). Occasionally, I need to geek out on numbers, work out some odds, or integrate some word puzzles into a game. Many of these tasks become more manageable with a little bit of script work ahead of time.
This article will explore 10 fundamental scripts that can be used in various types of games. The code archive contains the full source for each script we will discuss, and you can see the scripts in action at chaoticneutral.
We will blaze through these scripts pretty quickly. The topic of finding a host or setting up a server will not be covered. There are many Web hosting companies that offer PHP, and the XAMPP installer is easy to use if you want to set up your own. We won't spend a lot of time talking about PHP best practices or game design techniques. These scripts are designed to be simple to understand, simple to use, and quick to pick up.
|
A basic die roller
Many games and game systems need dice. Let's start with something simple: rolling a single six-sided die. Essentially, there's no difference between rolling a six-sided die and picking a random number between 1 and 6. In PHP, this is simple: echo rand(1,6);.
In many cases, that would be more or less fine. But when we deal with games of chance, we want something a little better. PHP provides a better random number generator: mt_rand(). Without going into detail on the differences between the two, it is safe to assume that mt_rand is a faster and better random number generator: echo mt_rand(1,6);. We'll be happier overall if we put this in a function.
Listing 1. Using the
mt_rand() random number-generator functionfunction roll () {
return mt_rand(1,6);
}
echo roll();
|
Then we can pass the type of die we want to roll as a parameter to the function.
Listing 2. Passing the type of die as a parameter
function roll ($sides) {
return mt_rand(1,$sides);
}
echo roll(6); // roll a six-sided die
echo roll(10); // roll a ten-sided die
echo roll(20); // roll a twenty-sided die
|
From here, we can go on to rolling multiple die at once, returning an array of results, or rolling multiple die of different kinds all at once, depending on our needs. But this simple script can be used for most tasks.
|
Random name generator
If you're running games, writing stories, or creating a bunch of characters all at once, sometimes it's hard to keep coming up with new names. Let's look at a simple random name generator you can use to solve this problem. Starting off, let's make two simple arrays — one with first names, one with last names.
Listing 3. Two simple arrays of first and last names
$male = array(
"William",
"Henry",
"Filbert",
"John",
"Pat",
);
$last = array(
"Smith",
"Jones",
"Winkler",
"Cooper",
"Cline",
);
|
Then we can just pick a random element from each array: echo $male[array_rand($male)] . ' ' . $last[array_rand($last)];. To pull a bunch of names at once, we can simply shuffle the arrays and pull as many as we like.
Listing 4. Shuffling the name arrays
shuffle($male);
shuffle($last);
for ($i = 0; $i <= 3; $i++) {
echo $male[$i] . ' ' . $last[$i];
}
|
Taking this basic concept, we can create text files to hold our first and last names. If we put one name per line in our text file, we can easily split the file contents on the newline character to build our source arrays.
Listing 5. Creating text files for our names
$male = explode('\n', file_get_contents('names.female.txt'));
$last = explode('\n', file_get_contents('names.last.txt'));
|
Build or find some good name files (a couple are included in the code archive), and we will never want for names again.
|
Scenario generator
Taking the same basic principles we used to make the name generator, we can make what's called a scenario generator. This is useful in role-playing games or other situations where we need to come up with a pseudo-random set of circumstances that can be used for role-play, improvisation, writing, etc. One of my favorite games, Paranoia, includes something called a "mission blender" in its GM Pack. The mission blender can be used to put together a full mission at the quick roll of a die. Let's put together our own scenario generator.
Take the following scenario: You wake up lost in the woods. You know you have to get to New York, but you don't know why. You can hear the barking of dogs and the unmistakable sound of hostile searchers nearby. You are cold, shivering, and without a weapon. Each sentence in that scenario introduces a specific aspect of the scenario:
- "You wake up lost in the woods" — This establishes the setting.
- "You know you have to get to New York" — This describes an objective.
- "You can hear the barking of dogs" — This introduces an antagonist.
- "You are cold, shivering, and without a weapon" — This adds a complication.
Just like we created text files for our first and last names, start by making a text file each for settings, objectives, antagonists, and complications. Sample files are included in the code archive. Once we have these files, the code to generate a scenario looks much the same as the code to generate names.
Listing 6. Generating a scenario
$settings = explode("\n", file_get_contents('scenario.settings.txt'));
$objectives = explode("\n", file_get_contents('scenario.objectives.txt'));
$antagonists = explode("\n", file_get_contents('scenario.antagonists.txt'));
$complications = explode("\n", file_get_contents('scenario.complications.txt'));
shuffle($settings);
shuffle($objectives);
shuffle($antagonists);
shuffle($complications);
echo $settings[0] . ' ' . $objectives[0] . ' ' . $antagonists[0] . ' '
. $complications[0] . "<br />\n";
|
We can add elements to our scenarios by adding new text files, or we may wish to add multiple complications. The more we add to our base text files, the more varied our scenarios will be over time.
|
Deck builder and shuffler
If you play cards and are interested in working on any scripts that are card-related, we want to put together a deck builder with a built in shuffler. To start, let's build a basic deck of standard playing cards. We need to build up two arrays — one to hold suits and one to hold faces. This will allow flexibility later if we want to add new suits or card types.
Listing 7. Building a basic deck of playing cards
$suits = array (
"Spades", "Hearts", "Clubs", "Diamonds"
);
$faces = array (
"Two", "Three", "Four", "Five", "Six", "Seven", "Eight",
"Nine", "Ten", "Jack", "Queen", "King", "Ace"
);
|
Then build a deck array to hold all the card values. We can do this simply using a pair of foreach loops.
Listing 8. Building a deck array
$deck = array();
foreach ($suits as $suit) {
foreach ($faces as $face) {
$deck[] = array ("face"=>$face, "suit"=>$suit);
}
}
|
Once we have a deck array built, we can easily shuffle the deck and draw a random card.
Listing 9. Shuffling the deck and drawing a random card
shuffle($deck); $card = array_shift($deck); echo $card['face'] . ' of ' . $card['suit']; |
From here, it's a short path to drawing hands of a set number of cards or building a multideck shoe.
|
Odds calculator: Card draw
Because we built the deck the way we did, keeping track of the face and suit individually for each card, we can make use of the deck programmatically to do something like calculate the odds of getting a specific card. Start by drawing out two hands of five cards each.
Listing 10. Drawing two hands of five cards each
$hands = array(1 => array(), 2=>array());
for ($i = 0; $i < 5; $i++) {
$hands[1][] = implode(" of ", array_shift($deck));
$hands[2][] = implode(" of ", array_shift($deck));
}
|
Then we can look in the deck to see how many cards are left and what the odds are of drawing a specific card. How many cards are left is an easy one. That's just a count of how many elements there are in the $deck array. To get the odds of drawing a specific card, we need a function to walk through the whole deck and evaluate the remaining cards to see if they match.
Listing 11. Calculating the odds of drawing specific card
function calculate_odds($draw, $deck) {
$remaining = count($deck);
$odds = 0;
foreach ($deck as $card) {
if ( ($draw['face'] == $card['face'] && $draw['suit'] ==
$card['suit'] ) ||
($draw['face'] == '' && $draw['suit'] == $card['suit'] ) ||
($draw['face'] == $card['face'] && $draw['suit'] == '' ) ) {
$odds++;
}
}
return $odds . ' in ' $remaining;
}
|
Now we can pick the card we are trying to draw. To make this easy, pass in an array that looks just like a card. We can look for a specific card.
Listing 12. Looking for a specific card
$draw = array('face' => 'Ace', 'suit' => 'Spades');
echo implode(" of ", $draw) . ' : ' . calculate_odds($draw, $deck);
|
Or we can look for a card of a given face or suit.
Listing 13. Looking for a card of a given face or suit
$draw = array('face' => '', 'suit' => 'Spades');
$draw = array('face' => 'Ace', 'suit' => '');
|
|
Simple poker dealer
Now that we've got a deck builder and something to help work out the odds of drawing specific cards, we can put together a really simple dealer to practice poker hands. For the purpose of this example, we build a dealer for five-card draw. The dealer will provide five cards from the deck. You specify which cards you want to discard by number, and the dealer will replace these cards with fresh ones from the deck. We won't bother putting in draw limits or house rules, though you may find that a rewarding personal exercise.
As shown in the previous section, generate and shuffle a deck, then create a single hand of five cards. Display these cards by their array index so that you can specify which cards to return. You might do it by using checkboxes to indicate which cards you are replacing.
Listing 14. Using checkboxes to indicate cards you are replacing
foreach ($hand as $index =>$card) {
echo "<input type='checkbox' name='card[" . $index . "]'>
" . $card['face'] . ' of ' . $card['suit'] . "<br />";
}
|
Then, evaluate the input array $_POST['card'] to see which cards have been checked for replacement.
Listing 15. Evaluating the input
$i = 0;
while ($i < 5) {
if (isset($_POST['card'][$i])) {
$hand[$i] = array_shift($deck);
}
}
|
Using this script, you can try your hand — pun intended — at figuring out the best ways to handle a specific set of cards.
|
Hangman player
Hangman is essentially a word-guessing game. Given a word of a certain length, we have a limited number of letter guesses. If you guess a letter that appears in the word correctly, all occurrences of the letter are filled in. After a set number of wrong guesses (typically six), you've lost the game. To put together a crude game of hangman, we need to start with a word list. For now, let's make it a simple array.
Listing 16. Creating a word list
$words = array (
"giants",
"triangle",
"particle",
"birdhouse",
"minimum",
"flood"
);
|
Using techniques covered earlier, we can move these words to an external word list text file and import them as we like.
Once we've got a list of words, we need to pick one out at random, display a blank for each letter, and start taking guesses. We need to keep track of right and wrong guesses from guess to guess. We'll do this cheaply by just serializing the guess arrays and passing them along with each guess. If we wanted to keep people from cheating by viewing the page source, we'd want to do something a little more secure.
Build up arrays to hold our letters, and our right/wrong guesses. For right guesses, we'll fill an array with the letters as keys and periods as values.
Listing 17. Building arrays to hold letters and guesses
$letters = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
'p','q','r','s','t','u','v','w','x','y','z');
$right = array_fill_keys($letters, '.');
$wrong = array();
|
Now we need a little but of code to evaluate guesses and show the word as it progresses through the guessing game.
Listing 18. Evaluating guesses and displaying progress
if (stristr($word, $guess)) {
$show = '';
$right[$guess] = $guess;
$wordletters = str_split($word);
foreach ($wordletters as $letter) {
$show .= $right[$letter];
}
} else {
$show = '';
$wrong[$guess] = $guess;
if (count($wrong) == 6) {
$show = $word;
} else {
foreach ($wordletters as $letter) {
$show .= $right[$letter];
}
}
}
|
In the source archive, we can see how we serialize the guess arrays and pass them from guess to guess.
|
Crossword helper
I know it's bad form, but sometimes when you're working on a crossword puzzle, you just get stuck trying to find a five-letter word that starts with C and ends with T. Using the same word list we put together for Hangman, we can easily search for words that fit a certain pattern. First, establish a way to pass in words. To make this easy, replace missing letters with periods: $guess = "c...t";. Since regular expressions treat a period as a single character, we can easily walk the word list, looking for matches.
Listing 19. Walking the word list
foreach ($words as $word) {
if (preg_match("/^" . $_POST['guess'] . "$/",$word)) {
echo $word . "<br />\n";
}
}
|
Depending on the quality of our word list and the accuracy of our guess, we should be able to get a reasonable list of words to use for possible matches. You'll have to decide yourself if "chest" or "cheat" is a better match for "a five-letter word that means 'to not play by the rules.'"
|
Mad Libber
Mad Libs is a word game where the player takes a short story and replace key types of words with different words of the same type to create a new, sillier version of the same story. Take the following text: "I was walking in the park when I found a lake. I jumped in and swallowed too much water. I had to go to the hospital." Start by replacing the word types with different word tokens. Start and end each token with an underscore to prevent accidental string matches.
Listing 20. Replacing word types with word tokens
$text = "I was _VERB_ing in the _PLACE_ when I found a _NOUN_. I _VERB_ed in, and _VERB_ed too much _NOUN_. I had to go to the _PLACE_."; |
Next, create a couple basic word lists. For this example, we won't get too fancy.
Listing 21. Creating a couple of basic word lists
$verbs = array('pump', 'jump', 'walk', 'swallow', 'crawl', 'wail', 'roll');
$places = array('park', 'hospital', 'arctic', 'ocean', 'grocery', 'basement',
'attic', 'sewer');
$nouns = array('water', 'lake', 'spit', 'foot', 'worm',
'dirt', 'river', 'wankel rotary engine');
|
Now we can repeatedly evaluate the text to replace the tokens as needed.
Listing 22. Evaluating the text
while (preg_match("/(_VERB_)|(_PLACE_)|(_NOUN_)/", $text, $matches)) {
switch ($matches[0]) {
case '_VERB_' :
shuffle($verbs);
$text = preg_replace($matches[0], current($verbs), $text, 1);
break;
case '_PLACE_' :
shuffle($places);
$text = preg_replace($matches[0], current($places), $text, 1);
break;
case '_NOUN_' :
shuffle($nouns);
$text = preg_replace($matches[0], current($nouns), $text, 1);
break;
}
}
echo $text;
|
Obviously, this is a simple and crude example. The more precise our word lists, and the more time we put into our base text, the better our results will be. We've already used text files to create lists of names and basic word lists. Using the same principle, we can create lists of words broken up by type and use them to create more diverse Mad Libs.
|
Lotto picker
Picking the right six numbers in a lotto is, to say the least, statistically improbable. Nevertheless, many people still pay to play, and if you like numbers, it can be entertaining to see the trends. Let's throw together a script that lets us keep track of winning numbers and provides the six least-picked numbers in our list.
(Disclaimer: This will not help you win the lotto, so please don't spend your money on tickets. This is just for fun.)
We save winning lotto picks in a text file. We separate individual numbers by comma and put each set of numbers on its own line. When we get the file contents, split on newlines, and split each line on commas, we get something that looks like Listing 23.
Listing 23. Saving winning lotto picks in a text file
$picks = array(
array('6', '10', '18', '21', '34', '40'),
array('2', '8', '13', '22', '30', '39'),
array('3', '9', '14', '25', '31', '35'),
array('11', '12', '16', '24', '36', '37'),
array('4', '7', '17', '26', '32', '33')
);
|
Obviously, that's not much of a base file for drawing stats. But it's a start, and enough to illustrate the principles.
Set a base array to hold the pick range. For example, if we pick numbers from 1 to 40 (e.g., $numbers = array_fill(1,40,0);, then walk through our picks, incrementing appropriate matching values.
Listing 24. Walking through our picks
foreach ($picks as $pick) {
foreach ($pick as $number) {
$numbers[$number]++;
}
}
|
Finally, sort the numbers based on value. This should put the least-picked numbers at the front of the array.
Listing 25. Sorting the numbers based on value
asort($numbers);
$pick = array_slice($numbers,0,6,true);
echo implode(',', array_keys($pick));
|
By regularly adding actual lotto picks to the text file containing our list of picks, we can trend number-picking over the long term. It's interesting to see how often some numbers appear.
For the role-playing game scripts, you will learn how to put together a weapon damage calculator, a character sheet stat tracker, and a Non-Player Character (NPC) generator, while teaching you how to save information to a file and work with arrays in PHP. The scripts related to games of chance will help you practice Blackjack, learn how to count cards, and get a quick understanding of Bingo fundamentals while teaching you how to work the element of chance into your PHP scripts. Finally, the word-game scripts will help you solve the Jumble game, create simple substitution cyphers, and generate word-search diagrams while taking your PHP array-handling skills one step further.
We will blaze through these scripts quickly without discussing finding a host or the setting up a server. There are many Web hosts that offer PHP, and the XAMPP installer is easy to use if you want to set up your own. We won't spend a lot of time talking about PHP best practices or game-design techniques either. The scripts covered here are designed to be simple to understand, easy to use, and quick to learn.
The code archive for this article contains the full source code for each script we will discuss, and you can see the scripts in action by visiting Chaoticneutral.net.
Damage calculators
In Part 1, we built a basic die roller. Let's look at using that die roller to help put together a basic damage calculator.
If you've ever run or played a table-top role-playing game, you've probably seen the big charts of weapons included in the books. Usually, these charts include the name of the weapon and how much damage the weapon does. It might look something like what is shown in Table 1.
Table 1. Damage calculations
| Weapon Name | Damage |
|---|---|
| Little Stick | 1d6 |
| Big Stick | 1d6+4 |
| Chainsaw | 2d8 |
You should be able to take the simple roll function created in Part 1 of this "30 game scripts you can write in PHP" series and use it to calculate the damage for a base set of weapons. To do this, you'll create an array to hold information about the weapons.
For this example, an individual weapon has three basic characteristics: a Name, a Die Roll, and a Damage Bonus. Using these characteristics, you might build your weapons array, like that shown below.
Listing 1. Weapons array
$weapons = array (
'littlestick' => array (
'name' => 'Little Stick',
'roll' => '1d6',
'bonus' => '0',
),
'bigstick' => array (
'name' => 'Little Stick',
'roll' => '1d6',
'bonus' => '4',
),
'chainsaw' => array (
'name' => 'Little Stick',
'roll' => '2d8',
'bonus' => '0',
),
);
|
Using this array, you can build a table that shows the weapon, the damage the weapon can do, and the result of a damage roll.
Listing 2. Building the weapons table
foreach ($weapons as $weapon) {
list($count, $sides) = explode('d', $weapon['roll']);
$result = 0;
for ($i = 0; $i < $count;$i++) {
$result = $result + roll($sides);
}
echo "<tr><td>" . $weapon['name'] . "</td><td>"
. $weapon['roll'];
if ($weapon['bonus'] > 0) {
echo "+" . $weapon['bonus'];
$result = $result + $weapon['bonus'];
}
echo "</td><td>" . $result . "</td></tr>";
}
|
Using this script, you can create a basic weapons chart with an integrated damage calculator.
|
Stat tracking
Now that you can calculate damage, let's look at keeping track of some basic character stats, like you might use if you were running a game with several NPCs. We create a simple array to hold character information and save it (serialized) to a text file. (There are many ways to save this information to a database, and you should move in that direction after you're comfortable working with PHP.) For this example, you'll craft a basic zombie character from a game I wrote called Shambles. Start with an array to hold the character information.
Listing 3. Array for character information
$character = array(
'name' => 'Fred The Zombie',
'health' => '36',
'gore' => '1',
'clutch' => '5',
'brawn' => '6',
'sense' => '4',
'flail' => '2',
'chuck' => '3',
'lurch' => '4',
);
|
You need to derive a filename to store this information. You really don't want to just use input as it's sent in, but you can use only the letters from the character name to create a file.
Listing 4. Creating a file to store the information
$filename = substr(preg_replace("/[^a-z0-9]/", "",
strtolower($character['name'])), 0, 20);
file_put_contents($filename, serialize($character));
|
Note: When making this script for public consumption, you'll want to filter user input to keep malicious people from injecting bad JavaScript or garbage into the files. This is just an example for your personal use. Finally, you need to present the information in a form to allow for easy updating. It's just a matter of creating a form with default values in the input fields.
Listing 5. Presenting the information in a form
<input name='health' value='<?php echo $character['health'] ?>' /> |
Use this basic script (see the code archive) to keep track of several characters and NPCs.
|
NPC generator
When you're putting together a big game, it's useful to have a pool of disposable NPCs. This is especially helpful if you need a large number of thugs or cannon fodder. To put together the NPC generator, let's build an array that contains the rules for building a character, then use the Random Name Generator and Die Roller to pull together a bunch of characters all at once.
Using Fred the Zombie as an example, you can define the rules to create a character. Some stats are static (health always starts at 36), some stats are derived (gore is health/6) and some stats are the result of die rolls (clutch is 1d6 — one die with six sides). The rules look like those shown in Listing 6.
Listing 6. Defining rules to create a character
$rules = array(
'health' => '36',
'gore' => 'health/6',
'clutch' => '1d6',
'brawn' => '1d6',
'sense' => '1d6',
'flail' => '1d6',
'chuck' => '1d6',
'lurch' => '1d6',
);
|
Now you can write a little code to evaluate the rules.
Listing 7. Code for evaluating the rules
foreach ($rules as $stat=>$rule) {
if (preg_match("/^[0-9]+$/", $rule)) {
// This is only a number, and is therefore a static value
$character[$stat] = $rule;
} else if (preg_match("/^([0-9]+)d([0-9]+)/", $rule, $matches)) {
// This is a die roll
$val = 0;
for ($n = 0;$n<$matches[1];$n++) {
$val = $val + roll($matches[2]);
}
$character[$stat] = $val;
} else if (preg_match("/^([a-z]+)\/([0-9]+)$/", $rule, $matches)) {
// This is a derived value of some kind.
$character[$stat] = $character[$matches[1]] / $matches[2];
}
echo $stat . ' : ' . $character[$stat] . "<br />\n";
}
|
Once you are properly evaluating your rules, you can pull in a random name using the Random Name Generator and make NPCs for days. Please review the script in the code archive for how this task is pulled together.
|
Odds calculator: Die roll
In Part 1, you put together a simple odds calculator to determine the chance of pulling a card of a specific face or suit from a given set of cards. Now let's build an odds calculator for die rolls. For this example, you will build a calculator that determines possible outcomes when rolling two six-sided dice (2d6). This is useful if you're using any RPG system that is d6-based, or playing a board game like Monopoly or Backgammon, or if you're trying to work out the odds in a dice game like Craps.
First, establish how possible outcomes are determined when rolling two dice. It will be helpful to think of rolling two differently colored dice, like a red die and a blue die. If the red die lands on a 1, there are six possible results, one for each side of the blue die. This means that the total number of possible die results are 6^2 or 36 outcomes. This may seem a little strange, since the highest value you can actually get is 12 (a red six and a blue six), but should make more sense if you realize you can get an 11 with two different outcomes (a red six and a blue five or a red five and a blue six).
Let's assume for this example that you want both sum (you rolled a 10) and face (you rolled a 6 and a 4) results and that there's no difference between a 6 on a red die and a 6 on a blue die. The important numbers are S (how many sides on the die) and N (how many die) — given N number of identical dice, the maximum number of outcomes is S^N — though some of these outcomes may be identical (a 6 and a 4, vs. a 4 and a 6).
Listing 8. Building an odds calculator for a die roll
$s = 6;
$n = 2;
$results = array(array());
for ($i = 0; $i < $n; $i ++) {
$newresults = array();
foreach ($results as $result) {
for ($x = 0; $x < $s; $x++) {
$newresults[] = array_merge($result, array($x+1));
}
}
$results = $newresults;
}
|
This probably seems a little clunky. You can streamline this code considerably, but I've done things a little more explicitly here so you can understand how the arrays are being handled. Once you have built up your final array, you can iterate through the array and generate a list of sums and what their respective odds are. This code is included in the code archive.
|
Simple Blackjack dealer
In Part 1, you built a simple poker dealer. The dealer didn't actually play against you; it just dealt cards from the shoe, to let you practice various hands. Now let's build a Blackjack dealer that actually plays against you. You will build this dealer using the simple house rule that the dealer must draw to at least 17.
Portions of this code will look like the simple Poker dealer built previously. For Blackjack, you need to build in iterative drawing and hand evaluation. You'll want an array that assigns face values to their numeric descriptions. You can do this in the original $faces array if you change how the deck is built.
Listing 9. Modifying the $faces array for Blackjack
$faces = array('Two' => 2, ... ... ... 'King' => 10, 'Ace' => 11);
|
Then you need a basic function to evaluate a given hand. This function will look at the cards in the hand and return the total value of the hand.
Listing 10. Function for evaluating a given hand
function evaluateHand($hand) {
global $faces;
$value = 0;
foreach ($hand as $card) {
if ($value > 11 && $card['face'] == 'Ace') {
$value = $value + 1; // An ace can be 11 or 1
} else {
$value = intval($value) + intval($faces[$card['face']]);
}
}
return $value;
}
|
Once you have a function in place to evaluate a single hand, it should be a simple matter to determine if your hand has gone over 21, or if the dealer should hit or stay. See the code archive for details on how this is all put together.
|
Card counter
When you're playing Blackjack, or trump-based games like Hearts, it can be useful to know how to count cards. In the case of Blackjack, you at least want to know how many aces, and face cards you have seen. For Hearts, you may want to know how many trump cards are still in play. You'll be building a rudimentary counter for Blackjack. You can take the same principles to build a card counter for a trump game.
You will build the deck for counting a bit differently. You don't need to know all the cards — just if they are cards you care about. For a single-deck shoe, it would look like that shown below.
Listing 11. Building a card counter for a single-deck shoe
$deck = array (
'faces' => 16, // 10,J,Q,K * 4 suits
'aces' => 4, // One per suit
'other' => 32, // 52 - (16 + 4)
);
|
Next, build a simple form with three buttons — one for face cards, one for aces and one for other cards. By clicking the appropriate button each time you see a card of a specific type, you can adjust what's left in the deck.
Listing 12. A simple form with three buttons
if ($_POST['submit'] == 'faces') {
$deck['faces']--;
}
|
Then you can calculate the odds of pulling a face, an ace, or a different card by looking at the deck after it's been modified.
Listing 13. Calculating the odds of pulling a face, an ace or different card
echo "Odds of pulling a face: " . $deck['faces'] . " out of " . $deck['faces'] + $deck['aces'] + $deck['other']; |
Using this rudimentary card counter as a starting point, you can build something much more robust.
|
Bingo engine
Based on what we've done in this article, you could easily put together a Bingo engine to generate cards and draw numbers from a pool.
Bingo numbers run from 1 to 75, broken up into groups of 15 (1-15 assigned to B, 16-30 assigned to I, etc.). You can build and shuffle a set of virtual Bingo balls in much the same way you build and shuffle a virtual deck of cards. But you don't need to track faces and suits in quite the same way. First, build the base set of numbers.
Listing 14. Building the base set of numbers for Bingo
for ($i = 1; $i < 16; $i++) {
$numbers['B'][] = $i;
$numbers['I'][] = $i+15;
$numbers['N'][] = $i+30;
$numbers['G'][] = $i+45;
$numbers['O'][] = $i+60;
}
|
Next, you can shuffle each subarray and make cards at the same time. (Don't forget: The very middle space is usually a free spot.) While you're there, create one master array you can use like a deck.
Listing 15. Creating a master array
$letters = array ('B','I','N','G','O');
foreach ($letters as $letter) {
shuffle($numbers[$letter]);
$chunks = array_chunk($numbers[$letter], 5);
$cards[$i][$letter] = $chunks[0];
if ($letter == 'N') {
$cards[$i][$letter][2] = ' '; // Free Space
}
shuffle($balls);
}
|
Now you're all set! Hand out your cards, and start drawing virtual Bingo balls from the master set, much like you did when you were drawing cards earlier. A fully functioning version of this script can be found in the code archive.
|
Jumble helper
The Jumble is that word puzzle found in most newspapers where you have a few words that have been all mixed up. You have to make real words out of the nonsense, then use certain letters to make a new phrase. Starting with the crossword helper created in Part 1, let's put together a quick script to help you with your Jumble.
To start, open up your word list. You want to iterate through the list, breaking each word into a sorted array of letters, then joining the array again. For example, this would change the word "COLOR" into the string "CLOOR"; this string represents all the letters from the word "COLOR," but in a predictable (and more importantly, matchable) order. This string is then used as the key for an array of matching words. It needs to be an array because "sword" and "words" have the same exact letters in different orders. In the end, it looks something like that shown below.
Listing 16. Script for a Jumble
foreach ($words as $word) {
$arr = str_split($word);
sort($arr);
$key = implode('', $arr);
if (isset($lookup[$key])) {
array_push($lookup[$key], $word);
} else {
$lookup[$key] = array($word);
}
}
|
Once you've gone this far, all you need to do is treat your Jumble just like any other word, look up the value for the corresponding key and output any results.
Listing 17. Finishing the Jumble script
$arr = str_split($_POST['jumble']);
sort($arr);
$search = implode('', $arr);
if (isset($lookup[$search])) {
foreach ($lookup[$search] as $word) {
echo $word;
}
}
|
You can see how this is all drawn together in the code archive.
|
Substitution cyphers
A substitution cypher is a simple encryption technique where every letter of the alphabet is substituted with another letter from the alphabet. By the standards of modern-day encryption techniques, substitution cyphers are child's play. But simple substitution cyphers can be fun to play with, especially in an RPG context by handing someone an encoded message that they can work out during game play.
Start by stealing some code from the hangman generator from Part 1 — namely, the letter array. Make a second copy of the array, shuffle the copy, and build an associative array, where the original letter gets a shuffled value.
Listing 18. Modifying the letter array
$letters = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p',
'q','r','s','t','u','v','w','x','y','z');
$code = $letters;
shuffle($code);
$key = array_combine($letters, $code);
|
Next, take input from $_POST, break the message up into letters, and output the value of each letter in the key.
Listing 19. Splitting and outputting letters
if (!empty($_POST)) {
$messageletters = str_split(strtolower($_POST['message']));
$show = '';
foreach ($messageletters as $letter) {
$show .= @$key[$letter];
}
}
|
Using the @ symbol before $key[$letter] tells PHP to ignore any errors that occur during encoding. If the message has punctuation, numbers or spaces, PHP will complain that there is no corresponding value in the $key array. That's just fine. The resultant encoded text will be stripped of spaces, numbers and punctuation, making it (marginally) more difficult to decode.
|
Word-search generator
Word search is probably the earliest letter-based puzzle we all learned. Using the techniques already put to use, and including some logic from the crossword helper, you can make a pretty basic word-search generator. To keep things simple, you'll make a word search that is 10x10 letters in size, with 10 words hidden inside (five vertical and five horizontal).
The first part, putting in the horizontal words, is pretty easy — build an array of arrays, 10 elements each, filled with periods. Then in every other array, push in the letters for individual words. To put in the vertical words, you need to walk all the arrays simultaneously, using the crossword helper code to look for possible matching words. Perhaps the trickiest bit is the piece shown in Listing 20 that fills in words or random letters on vertical lines, where the word is shorter than the whole line.
Listing 20. Filling in words or random letters on vertical lines
$wordletters = str_split($word);
for ($c = 0; $c < $height; $c++) {
if ( $grid[$c][$i] == '.' && ($c < $x || empty($wordletters))) {
$grid[$c][$i] = $letters[rand(0,25)];
} else {
if ($grid[$c][$i] == '.') {
$grid[$c][$i] = array_shift($wordletters);
} else if ($c >= $x) {
array_shift($wordletters);
}
}
}
|
The code archive contains a number of comments to help elucidate this whole process. There's plenty of room for performance improvement in that code, but this should give you a general idea for how to approach the problem. Try out the full script from the code archive to see the results
discuss this topic to forum
