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

Comments 8 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

Advertisements

8 thoughts on “PHP Tutorial: Free OOP Hangman Game (no database required)

  1. naturally like your web site but you need to test the spelling on several of your posts. Several of them are rife with spelling problems and I in finding it very troublesome to inform the truth nevertheless I?ll certainly come again again.

  2. Heh, talk about spelling mistakes when you use the wrong punctuation, word and have a duplicate “again” in your comment. The nice thing about programming is that you don’t have to spell anything right 🙂

  3. You have an error when the game runs more then one time, After playing first time you can guess more then 5 times and you run out of images from you images directory. To fix this you have to destroy the existing session on line 132. Add session_destroy(); inside your displayGame() function
    and the problem will be fixed. Although, many thanks for such a nice tutorial on OOP using PHP.

  4. Thanks I fixed the link. This game class was written back in PHP version 3 or 4, well before the __construct() function was available because PHP didn’t fully support classes at that time. As of PHP5 you should be using __construct() since the old style constructors will be deprecated in PHP7.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s