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

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.

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: 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.

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($_POST)
    {
        //player pressed the button to start a new game
        if ($_POST['newgame'] || empty($this->wordList))
            $this->newGame();

        //player is trying to guess a letter
        if (!$this->isOver() && $_POST['letter'])
            echo $this->guessLetter($_POST['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.
                                Your final score was: $this->score");             else if ($this->health < 0)             {                 echo errorMsg("Game Over! Good try.
                                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 %sn")) {                 $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 "Hangman";                 else                     return "ERROR: images/" . ($count-1) . ".jpg is missing from the hangman images folder.";             }             $count++;         }         return "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 (!$_SESSION['game']['hangman'])
    $_SESSION['game']['hangman'] = new hangman();

?>

    
        Hangman
        
    
    
        
        
        

Let's Play Hangman!

        playGame($_POST);         ?>         
        
    

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

.zip archive or Fork it on GitHub

You may also like...

3 Responses

  1. money says:

    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.

    • Jade says:

      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 🙂

  2. Nawshad M Mitul says:

    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.

Leave a Reply