PHP Tutorial: Free OOP Hangman Game (no database required)

Comments 10 Standard

At this year’s Siminar I’m presenting a live webinar on “Learning OOP” to many young female gamers. Part of that presentation includes a OOP hangman game they can easily install and incorporate on their existing websites. There is no database required, the only software requirements are php version 4 or higher. To get this game running all you have to do is upload the files to a folder on your website then edit the config/wordlist.txt file.

Getting Started

If you take a minute to think about it most games have a few things in common. So we’re going to start by making a class that has all the attributes and methods use in any generic game. For instance, most games keep track of the score, the player’s health, whether or not they’ve won the game and if they’ve had a game over. These are things we don’t want to write over and over again with every game we make so we’ll make one generic class for methods and attributes most games have in common.

Now we can use this generic class to extend any other game we make in the future. Extending a class means one class, the child, will inherit all of the attributes and methods of the other class, the parent. Our generic game class is a parent class of a specific game class for our hangman game. For more information on classes or if you want a refresher course on OOP programming I suggest you read this tutorial.

The Game Class

Our game class contains all of our generic attributes and methods. These are things we expect any game we make might have so we can use this game class over and over again. Because this game class will be extended by other classes it’s known as a parent class.

<?php

class game {

    var $health;    //int - player's health
    var $over;        //bool - toggle game over
    var $score;        //int - player's score
    var $won;        //bool - toggle game won

    /**
    * Purpose: setup game environment variables
    * Preconditions: turn the debugger on (optional)
    * Postconditions: the game environment is ready to start a game
    **/
    function start()
    {
        $this->score = 0;
        $this->health = 100;
        $this->over = false;
        $this->won = false;
    }

    /**
    * Purpose: end the game
    * Preconditions: turns on the game over flag
    * Postconditions: game over flag is true
    **/
    function end()
    {
        $this->over = true;
    }

    /**
    * Purpose: change or retrieve the player's score
    * Preconditions: amount (optional)
    * Postconditions: returns the player's updated score
    **/
    function setScore($amount = 0)
    {
        return $this->score += $amount;
    }

    /**
    * Purpose: change or retrieve the player's health
    * Preconditions: amount (optional)
    * Postconditions: returns the player's updated health
    **/
    function setHealth($amount = 0)
    {            
        return ceil($this->health += $amount);
    }

    /**
    * Purpose: return bool to indiciate whether or not the game is over
    * Preconditions: none
    * Postconditions: returns true or flase
    **/
    function isOver()
    {
        if ($this->won)
            return true;

        if ($this->over)
            return true;

        if ($this->health < 0)
            return true;

        return false;
    }

} //end game class

/**
* Purpose: display a formatted debug message
* Preconditions: the object or message to display
* Postconditions: returns the player's updated score
**/
function debug($object = NULL, $msg = "")
{
    if (isset($object) || isset($msg))
    {
        echo "<pre>";

        if (isset($object))
            print_r($object);

        if (isset($msg))
            echo "\n\t$msg\n";

        echo "\n</pre>";
    }
}

/**
* Purpose: return a formatted error message
* Preconditions: the message to format
* Postconditions: formatted message is returned
**/
function errorMsg($msg)
{
    return "
$msg
"; } /** * Purpose: return a formatted success message * Preconditions: the message to format * Postconditions: formatted message is returned **/ function successMsg($msg) {     return "
$msg
"; }

The Hangman Class

This is the most complicated part of our hangman game. The hangman class will drive all of our game setup, loading, and respond to the user’s entry choices. We’ve extended it so that it will inherit all of the attributes and methods we’ve created in our game class. This way we don’t have to write those attributes and methods over and over again each time we create a new game.

Overall this file is split into three main parts. The loading of the game is done by the newGame() method. This loads our word list from a separate file and then randomly selects a word for the player to guess. It also resets the player’s score and letter guesses in case they play again once the first game is over.

The second part of the file displays or drives the game in the playGame() method. It decides whether or not to start a new game, respond to a letter that was guessed, show error messages and check to see if they guess the right word or if they’ve run out of guesses.

The last part two methods in the file are helper methods. isLetter() checks to make sure they’ve entered a valid letter in the alphabet and wprdToArray converts our word to an array so we can compare it with the letters they’ve guessed so far.

<?php

class hangman extends game
{
    var $guesses;                //int - maximum guesses per word
    var $letters = array();        //array - letters guessed so far
    var $wordIndex;                //int - index of the current word
    var $wordLetters = array();    //array - array of the letters in the word
    var $wordList = array();    //array - list of words they can guess

    var $alphabet = array(        //array - all letters in the alphabet
        "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");

    var $punctuation = array(    //array - punctuation marks in the word list
        " ", "\,", "\'", "\"", "\/", "\\", "\[",
        "\]", "\+", "\-", "\_", "\=", "\!", "\~",
        "\~", "\@", "\.", "\?", "\$", "\#", "\%",
        "\^", "\&", "\*", "\(", "\)", "\{", "\}",
        "\|", "\:", "\;");

    /**
    * Purpose: default constructor
    * Preconditions: none
    * Postconditions: parent object started
    **/
    function __construct()
    {
        /**
        * instantiate the parent game class so this class
        * inherits all of the game class's attributes
        * and methods
        **/
        game::start();
    }

    /**
    * Purpose: start a new hangman game
    * Preconditions: maximum number of guesses
    * Postconditions: game is ready to be displayed
    **/
    function newGame($max_guesses = 5)
    {
        //setup the game
        $this->start();

        //make sure we clear out the last letters they guessed
        $this->letters = array();

        //set how many guesses they get before it's a game over
        if ($max_guesses)
            $this->setGuesses($max_guesses);

        //pick a word for them to try and guess
        $this->setWord();
    }

    /**
    * Purpose: set or retrieve maximum guesses before game over
    * Preconditions:
    * Postconditions:
    **/
    function playGame($game)
    {
        //player pressed the button to start a new game
        if (isset($game['newgame']) || empty($this->wordList))
            $this->newGame();

        //player is trying to guess a letter
        if (!$this->isOver() && isset($game['letter']))
            echo $this->guessLetter($game['letter']);

        //display the game
        $this->displayGame();
    }

    /**
    * Purpose: set or retrieve maximum guesses they can make
    * Preconditions: amount of guesses (optional)
    * Postconditions: guesses has been updated
    **/
    function setGuesses($amount = 0)
    {        
        $this->guesses += $amount;
    }

    /**
    * Purpose: display the game interface
    * Preconditions: none
    * Postconditions: start a game or keep playing the current game
    **/
    function displayGame()
    {
        //while the game isn't over
        if (!$this->isOver())
        {
            echo "
" . $this->picture() . "
                 
" . $this->solvedWord() . "
                 
                    Enter A Letter:                                                                    
";                   if (!empty($this->letters))                     echo "
Letters Guessed: " . implode($this->letters, ", ") . "
";         }         else         {             //they've won the game             if ($this->won)                 echo successMsg("Congratulations! You've won the game.<br/>                                 Your final score was: $this->score");             else if ($this->health < 0)             {                 echo errorMsg("Game Over! Good try.<br/>                                 Your final score was: $this->score");                 echo "
" . $this->picture() . "
";             }             echo "
";         }     }     /**     * Purpose: guess a letter in this word     * Preconditions: a game has started     * Postconditions: the game data is updated     **/     function guessLetter($letter)     {                     if ($this->isOver())             return;         if (!is_string($letter) || strlen($letter) != 1 || !$this->isLetter($letter))             return errorMsg("Oops! Please enter a letter.");         //check if they've already guessed the letter         if (in_array($letter, $this->letters))             return errorMsg("Oops! You've already guessed this letter.");         //only allow lowercase letters         $letter = strtolower($letter);         //if the word contains this letter         if (!(strpos($this->wordList[$this->wordIndex], $letter) === false))         {             //increase their score based on how many guesses they've used so far             if ($this->health > (100/ceil($this->guesses/5)))                 $this->setScore(5);             else if ($this->health > (100/ceil($this->guesses/4)))                 $this->setScore(4);             else if ($this->health > (100/ceil($this->guesses/3)))                 $this->setScore(3);             else if ($this->health > (100/ceil($this->guesses/2)))                 $this->setScore(2);             else                 $this->setScore(1);             //add the letter to the letters array             array_push($this->letters, $letter);             //if they've found all the letters in this word             if (implode(array_intersect($this->wordLetters, $this->letters), "") ==                 str_replace($this->punctuation, "", strtolower($this->wordList[$this->wordIndex])))                 $this->won = true;             else                 return successMsg("Good guess, that's correct!");         }         else //word doesn't contain the letter         {             //reduce their health             $this->setHealth(ceil(100/$this->guesses) * -1);             //add the letter to the letters array             array_push($this->letters, $letter);             if ($this->isOver())                 return;             else                 return errorMsg("There are no letter $letter's in this word.");         }     }     /**     * Purpose: pick a random word to try and solve     * Preconditions: none     * Postconditions: if the word exists a word index has been set     **/     function setWord()     {         //if the word list is empty we need to load it first         if (empty($this->wordList))             $this->loadWords();         //reset the word index to a new word         if (!empty($this->wordList))             $this->wordIndex = rand(0, count($this->wordList)-1);         //convert the string to an array we can use         $this->wordToArray();     }     /**     * Purpose: load the words from the config file into an array     * Preconditions: filename to load the words from (optional)     * Postconditions: the word list has been loaded     **/     function loadWords($filename = "config/wordlist.txt")     {         if (file_exists($filename))         {             $fstream = fopen($filename, "r");             while ($word = fscanf($fstream, "%s %s %s %s %s %s %s %s %s %s\n")) {                 $phrase = "";                 if (is_string($word[0]))                 {                     foreach ($word as $value)                         $phrase .= $value . " ";                     array_push($this->wordList, trim($phrase));                 }             }         }     }     /**     * Purpose: return the image that should be displayed with this number of wrong guesses     * Preconditions: none     * Postconditions: picture returned     **/     function picture()     {         $count = 1;         for ($i = 100; $i >= 0; $i-= ceil(100/$this->guesses))         {             if ($this->health == $i)             {                 if (file_exists("images/" . ($count-1) . ".jpg"))                     return "<img src=\"images/" . ($count-1) . ".jpg\" alt=\"Hangman\" title=\"Hangman\" />";                 else                     return "ERROR: images/" . ($count-1) . ".jpg is missing from the hangman images folder.";             }             $count++;         }         return "<img src=\"images/" . ($count-1) . ".jpg\" alt=\"Hangman\" title=\"Hangman\" />";     }     /**     * Purpose: display the part of the word they've solved so far     * Preconditions: a word has been set using setWord()     * Postconditions: the letters they've guessed correctly show up     **/     function solvedWord()     {         $result = "";         for ($i = 0; $i < count($this->wordLetters); $i++)         {             $found = false;             foreach($this->letters as $letter) {                 if ($letter == $this->wordLetters[$i])                 {                     $result .= $this->wordLetters[$i]; //they've guessed this letter                     $found = true;                 }             }             if (!$found && $this->isLetter($this->wordLetters[$i]))                 $result .= "_"; //they haven't guessed this letter             else if (!$found) //this is a space or non-alpha character             {                 //make spaces more noticable                 if ($this->wordLetters[$i] == " ")                     $result .= "&nbsp;&nbsp;&nbsp;";                 else                     $result .= $this->wordLetters[$i];             }         }         return $result;     }     /**     * Purpose: convert the selected word to an array     * Preconditions: a word has been selected     * Postconditions: wordLetters now contains an array representation of the     *    selected word     **/     function wordToArray()     {         $this->wordLetters = array();         for ($i = 0; $i < strlen($this->wordList[$this->wordIndex]); $i++)             array_push($this->wordLetters, $this->wordList[$this->wordIndex][$i]);     }     /**     * Purpose: check to see if this value is a letter     * Preconditions: value to check     * Postconditions: returns true if letter found     **/     function isLetter($value)     {         if (in_array($value, $this->alphabet))             return true;         return false;     } }

Playing The Game

Now that we have our classes in place we need to create a file that instantiates, or creates, the hangman object. We’ll save our object to a session so we don’t loose the player’s data as they refresh the page. Finally we call the hangman’s playGame() method passing it the $_POST data so we know how to respond to the buttons they’ve clicked.

//include the required files
require_once('oop/class.game.php');
require_once('oop/class.hangman.php');

//this will keep the game data as they refresh the page
session_start();

//if they haven't started a game yet let's load one
if (!isset($_SESSION['game']['hangman'])) {
    $_SESSION['game']['hangman'] = new hangman();
}
?>
<html>
    <head>
        <title>Hangman</title>
        <link rel="stylesheet" type="text/css" href="inc/style.css" />
    </head>
    <body>
        
        " method="POST">         

Let's Play Hangman!

        playGame($_POST); }       ?>              
    </body> </html>

Conclusion

Try the working version! In this tutorial we created two classes, a game class and a hangman class. Because all games have several aspects in common we’ve extended our game class to make our hangman class. This way we can use the attributes and methods of the game class for other games we might make in the future. When our classes were done we created a final file to load and store the game data in a session so we don’t loose it as the page reloads and then told the game to display itself by calling the playGame() method.

Download The Source

Get the .zip archive

PHP Tutorial: Learning OOP – Class Basics & Extending Classes

Comments 8 Standard

What Is OOP?

Object Oriented Programming (OOP) is an programming style that uses classes with attributes and methods to create objects. OOP can be used anywhere, however it’s most practical when working with relational database models or on projects where major aspects of the programming can be classified as objects.

What Is An Object?

An object is defined by a class. It has attributes and methods that describes the characteristics and actions it can perform.
  • Attribute – are commonly the objects appearance, characteristics, limitations or abilities. For example size, weight, height, shape, color, speed, hunger, and health are all attributes of an animal.
  • Methods – an action the object can perform. For example eat, sleep, drink, breed, hibernate, buy something, and equip an item are all actions an object might perform.

Class Basics

A class is a good way to abstract lots of smaller details from a bigger picture. For instance, let’s take a second to think about a vending machine. There are lots of different parts that make up a vending machine, but all combined together we only think of the vending machine as one complete unit, or object. A class uses the same kind of idea, lots of smaller parts making a bigger object.

For instance a vending machine has multiple attributes like size, shape, color, make, model and manufacturer. It also has many actions  it can perform such as responding to a button press, calculating the correct change, dispensing the change, refunding money, accepting a two letter code for an item, checking to see if there are items in that entry, dispensing the item if it has one or returning an error message. We can use classes to define the properties (attributes) and actions (methods) the vending machine can perform in PHP.

Since the vending machine is a bit more complicated than I want to get in this tutorial I’m going to choose something easier to start off with.

Fruit Class

We all know there are several different types of fruit but they all have some common characteristics. For instance all fruit has a name, color, size, and varying degrees of sweetness. These are the attributes we’re going to use for our fruit class.

Now let’s think about the kind of actions you can perform with fruit.  You can wash them, eat them and shine them to name a few. We’re going to use these actions as methods in our class.

Using this really simple break down, let’s make our fruit class:

<?php
class fruit
{
 //here are the attributes of our class
 var $name;
 var $color;
 var $size;
 var $sweet;
 var $clean;
 var $washed;

function fruit($name)
{
 //this is a default constructor. It always has the same name
 //as the class. Whenever we
 //make a fruit object this function is automatically called

 $this->name = $name; //we automatically give this fruit its name
 $this->clean = False; //our fruit always needs to be washed first
 $this->washed = 0; //we haven’t washed it any times yet
}

//but we want to be able to set other things about the fruit
function setColor($color)
{
 $this->color = $color;
}

function setSize($size)
{
 $this->size = $size;
}

function setSweet($sweet)
{
 $this->sweet = $sweet;
}

//lets make a way to wash our fruit
function wash()
{
 $this->clean = True; //our fruit is clean now
 $this->washed++; //we’ve washed our fruit one more time
}

//we want to eat our fruit now
function eat()
{
  if (!$this->clean)
    echo "You should always wash your $this->color $this->name first!!!";

 if ($this->clean && $this->washed < 2)
   echo "You’re eating a dull looking piece of $this->name...";

 if ($this->clean && $this->washed >= 2)
   echo "You're eating a shiny piece of $this->color $this->name!";

 if (!$this->clean && $this->washed >= 2)
   echo "Your $this->name is shiny but you probably should wash it first.";

 if ($this->sweet)
   echo "This fruit is sweet.";
 else
   echo "This fruit is classified as a vegetable!";
}

//we can make a shine function, that washes the surface of the fruit as well
function shine()
{
 $this->washed++;
}
} //end of our class

//let's make an orange
$orange = new fruit("Orange");
$orange->setSize("small");
$orange->setColor("orange");
$orange->setSweet(True);

//now we'll make a watermelon
$fruit = new fruit("Watermelon");
$fruit->setSize("large");
$fruit->setColor("green");
$fruit->setSweet(True);

//last but not least we'll make a tomato (which is also a fruit!)
$veggie = new fruit("Tomato");
$veggie->setSize("medium");
$veggie->setColor("red");
$veggie->setSweet(False);

echo "Washing my $orange->name.";
$orange->wash();

echo "<br>Eating my $veggie->size $veggie->name.";
$veggie->eat();

echo "<br/>Washing my $orange->name again.";
$orange->wash();

echo "<br/>Shining my $fruit->size $fruit->name.";
$fruit->shine();

echo "<br/>Eating my $fruit->name.";
$fruit->eat();

echo "<br/>Eating my $orange->size $orange->name.";
$orange->eat();

//let's see what the structure of our objects looks like
//this is useful when you're trying to find or fix a problem

print_r($orange);
echo "<br/><br/>";

print_r($fruit);
echo "<br/><br/>";

print_r($veggie);
echo "<br/><br/>";

?>

Try running this script. What happens? One of the neatest things about a class is any method you make can be applied to any object of that class. In the example above we had three different fruit objects. Even though they were all different objects they used the same methods of the fruit class and stored their own data.

You’ll also notice we can directly access or change the attributes of the class using the -> arrow the same way we call a method.

Up for a challenge?

  1. Try adding more methods to this class.
  2. Try making $veggie = $fruit. What happens when you do $veggie->name now?
  3. Try adding a method that lets you set all attributes (color, sweet, size) at the same time.
  4. What happens if you to access a method or attribute that doesn’t exist in the class?
    1. Try writing $fruit->sell();
    2. Try echoing $fruit->weight;
  5. What happens if you pass one object as a parameter to another object’s method?
    1. Add this method:

function splice($otherfruit)
{
echo “I’m trying to splice ” . $this->name . ” with this ” . $otherfruit->name . “.”;
}

Test your new method  by adding $orange->splice($fruit); to the bottom of your file.

Extending Classes

I think of an extended class as a shortcut to programming similar objects. Other people, and your traditional programming school books, will tell you it’s a class that extends the functionality of an existing class. It may also be referred to as a sub-class or child class.

Programatically an extended class is easy to create. Take this example:

class car {
  var $make;
  var $model;
  var $shift_auto;
  var $horsepower;
  var $color;
  var $fuel;
  var $mpg;

  function drive($distance)
  {
     if ($fuel - $distance > 0)
     {
       $fuel -= $distance;
       echo "Driving $distance miles";
     }
     else
       echo "You don't have enough gas to go that far.";
  }

  function refuel($amount)
  {
    $fuel += $amount;
  }

}

Our car class gives us some information about the car and the ability to drive and refuel. Now look at this extended class:

class truck extends car { 

  var $tow_capacity;
  var $has_hitch;

  function tow_vehicle($car_object)
  {
     return "Towing a " . $car_object->make . " " . $car_object->model;
  }
}

In our extended class, we automatically have all the functions and variables from the car class, but we’ve also added the ability to tow another vehicle. Try making another extended class called sportscars. What additional variables or functions do you think you’d add?

Default & Optional Parameter Values

One of the neatest things about PHP is the ability to have an optional method parameter just like you can with regular functions. Let’s refer back to our fruit class and add this method:

function fruit($type, $color = "red", size = null)
{

   if (!$size)
   {
      switch(rand(1, 3))
      {
           case 1: $size = "small";
               break;
           case 2: $size = "medium";
               break;
           case 3: $size = "large";
               break;
       }

       echo "$size $color $type<br/>";
}

Can you figure out how this method works on your own? If not that’s okay too! All of the examples below are acceptable ways to call this method:

fruit(“cherry”) — returns {size} red cherry where size is randomly selected.

fruit (“bananna”, “yellow”) — returns {size} yellow bannana where size is randomly selected.

fruit(“plumb”, “purple”, “small”) — returns small purple plum.

This fruit class now has the ability to create an object with some of the attributes set for you. It has two optional parameters, one default parameter and one required parameter. The type of fruit is required. The color of the fruit is optional, but if you don’t specify a color it will automatically default to red. The size of the fruit is also optional, but if you don’t give it a size then the function will randomly pick one.

What makes these parameters optional? We’ve assigned them to a value right in the method’s parameter list. However this doesn’t prevent us from changing the value by passing in our own.

Conclusion

In this tutorial we covered some of the basic principles of OOP and classes. You learned that classes define the abilities of an object and have both attributes and methods. You also learned how to extend a class so it inherits the attributes and methods of another class. Finally you learned how to make your methods take optional parameters or default values.

Think you have the hang of it now? Test your skills by creating a game using a class! If you get stuck you can always read this Free OOP Hangman Game Tutorial (no database required).

Are you still a bit unsure about the whole thing? That’s okay! Now you can quickly and easily generate class files based off of a table in your database using our new PHP Class File Generator Tutorial.