2
\$\begingroup\$

This is the simple game of hangman. Guess letters and six wrong letters means you're dead.

This took me about 20 hours. And I learned enough to be certain I could make something better now in half the time. Still, give me your honest (and brutal) opinion, since that will help me improve the most.

<?php

    //init
    $woordenlijst = array(
        "knie",
        "rug", 
        "nek",
        "elleboog",
    );
    $gekozen = '';
    $woordstatus = array('*',);
    $aantalfouten = 0;

    //function

    function tekengalg($arg1)
    //tekent de juiste galg
    {
        if ($arg1 == 0) {
            //blanco galg
            echo (" +---+") . PHP_EOL;
            echo (" |   |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("=======") . PHP_EOL;
        }
        if ($arg1 == 1) {
            // galg1 
            echo (" +---+") . PHP_EOL;
            echo (" |   |") . PHP_EOL;
            echo (" o   |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("=======") . PHP_EOL; 
        }
        if ($arg1 == 2) {
            //galg2
            echo (" +---+") . PHP_EOL;
            echo (" |   |") . PHP_EOL;
            echo (" o   |") . PHP_EOL;
            echo (" |   |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("=======") . PHP_EOL;
        }
        if ($arg1 == 3){
            //galg3
            echo (" +---+") . PHP_EOL;
            echo (" |   |") . PHP_EOL;
            echo (" o   |") . PHP_EOL;
            echo ("/|   |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("=======") . PHP_EOL;
        }
        if ($arg1 == 4){
            //galg4
            echo (" +---+") . PHP_EOL;
            echo (" |   |") . PHP_EOL;
            echo (" o   |") . PHP_EOL;
            echo ("/|\  |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("=======") . PHP_EOL;
        }
        if ($arg1 == 5){
            //galg5
            echo (" +---+") . PHP_EOL;
            echo (" |   |") . PHP_EOL;
            echo (" o   |") . PHP_EOL;
            echo ("/|\  |") . PHP_EOL;
            echo ("/    |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("=======") . PHP_EOL;
        }
        if ($arg1 == 6){
            //galg6
            echo (" +---+") . PHP_EOL;
            echo (" |   |") . PHP_EOL;
            echo (" o   |") . PHP_EOL;
            echo ("/|\  |") . PHP_EOL;
            echo ("/ \  |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("     |") . PHP_EOL;
            echo ("=======") . PHP_EOL;
        }
    }

    function toonwoord($letter)
    //print de bekende letters in het woord, met verder sterretjes
    {
        global $galgwoord, $woordstatus;
        $galgarray = str_split($galgwoord);
        for ($i=0; $i < strlen($galgwoord); $i++) { 
            if ($galgarray[$i] == $letter) {
                $woordstatus[$i] = $letter; 
            }
        }

        for ($i=0; $i < strlen($galgwoord); $i++) { 
            echo $woordstatus[$i] . " ";
        }
        echo PHP_EOL;
    }

    function doorgaan($int){
    //true is doorgaan  
    //zes fouten dan return false
    //woord is af dan return false, 
        global $length, $woordstatus;
        $boolret = false;
        if ($int == 6) {
        } else {
            # stop als geen asterisk
            for ($i=0; $i < $length; $i++) { 
                if ($woordstatus[$i] == '*') {
                    $boolret = true;
                }
            }
        }
        return $boolret;
    }

    //kies een woord 
    $galgwoord = $woordenlijst[rand(1, sizeof($woordenlijst)) - 1];
    $length = strlen($galgwoord);
    for ($i=0; $i < $length; $i++) { 
        $woordstatus[$i] = '*';
    }
    echo "het woord heeft $length letters" . PHP_EOL;

    //blanco statusscherm
    tekengalg($aantalfouten);

    //gekozen letters, woordstatus, foute letters, stand van de galg
    echo "U heeft de volgende letters al gekozen: " . $gekozen . PHP_EOL;
    echo "U weet dit van het woord: " . $woordstatus[0] . PHP_EOL;
    echo "De volgende letters staan niet in het woord: " . PHP_EOL;
    //deel 3, computer vraagt een letter,

    while (doorgaan($aantalfouten)) {
        echo "Geef uw keuze voor een letter." . PHP_EOL;
        echo "> ";
        $input = trim(fgets(STDIN));

        //is het een letter? is die letter eerder gebruikt?
        if (ctype_alpha($input)) {
            if (strlen($input) == 1) {
                echo "De door u gekozen letter is: $input" . PHP_EOL;
                if (is_numeric(strpos($gekozen, $input))) {
                    echo "deze letter heeft u al eerder geprobeerd" . PHP_EOL;
                } 
                else {
                    echo "U heeft deze letter niet eerder gekozen" . PHP_EOL;
                    $gekozen .= $input;
                    if (is_numeric(strpos($galgwoord, $input))) {
                        echo "goedzo, deze letter komt voor in het woord." . PHP_EOL;
                        // toon woord met alle bekende letters
                        toonwoord($input);
                    } else {
                        echo "jammer, deze letter komt niet voor in het woord." . PHP_EOL;
                        $aantalfouten += 1;
                        tekengalg($aantalfouten);
                        // nieuw plaatje
                    }   
                }
                $gekozen .=  $input;
            } else {
                echo "U heeft meer dan één letter gekozen." . PHP_EOL;
            }
        } else {
            echo "dit is geen geldige letter voor galgje." . PHP_EOL;
        }
    }
    if ($aantalfouten == 6) {
        echo "sorry, u heeft verloren" . PHP_EOL;
    }else{
        echo "gefeliciteerd, u heeft gewonnen" . PHP_EOL;
    }
?>
\$\endgroup\$
1
\$\begingroup\$

Use elseif for mutually exclusive conditions

In tekengalg there are multiple if statements that cannot be true at the same time:

if ($arg1 == 0) {
    // ...
}
if ($arg1 == 1) {
    // ...
}
if ($arg1 == 2) {
    // ...
}
// ...

That is, after $arg1 is known to be 0, then there's no need to evaluate $arg == 1, and so on. Replace the second and later if with elseif.

Avoid flag variables when possible

In doorgaan, $boolret is set to true when an '*' is found. Its value never changes again. Instead of setting $boolret = true, you could return true at this point. This will have two nice effects:

  • Stop looping as soon as possible. It's pointless to continue in the loop, it won't change the outcome of the function.
  • Eliminate a variable. If the end of the loop is reached, that means we never returned because we haven't found '*', so you can return false. No need for the variable $boolret.

Avoid deeply nested statements

Deeply nested statements like this can be hard to read:

if (ctype_alpha($input)) {
    if (strlen($input) == 1) {
        // ...
    } else {
        echo "U heeft meer dan één letter gekozen." . PHP_EOL;
    }
} else {
    echo "dit is geen geldige letter voor galgje." . PHP_EOL;
}

In particular, when the main branch of an if is a long piece of code, then by the time you read the else, you might not remember well what it was about. In such cases it can be interesting to invert the conditions, making the code flatter, and perhaps easier to understand:

if (!ctype_alpha($input)) {
    echo "dit is geen geldige letter voor galgje." . PHP_EOL;
    continue;
}

if (strlen($input) != 1) {
    echo "U heeft meer dan één letter gekozen." . PHP_EOL;
    continue;
}

// ...

Use more helper functions

I find the echo "..." . PHP_EOL boilerplate tedious... I would create a helper function that appends PHP_EOL, so I don't have to repeatedly type that.

Use better names

It's important to use names that describe the values they represent, and help readers understand the code. For example $arg1 doesn't describe that it's the number of failed guesses.

\$\endgroup\$
  • 1
    \$\begingroup\$ To improve on "use better names": don't program in Dutch. Stick to English. That's what most of the rest of the world does too and makes it a lot more readable. \$\endgroup\$ – Mast Dec 18 '18 at 17:09
1
\$\begingroup\$

I didn't test any of my snippets below, so they may contain typos. I am only including them to give context to my advice.

I translated the Dutch to English so that I could follow your processes.

  • By storing the gallows data as a semi-matrix, you can swiftly update the image by adding a single character in its desired position. I could have condensed the $victim array further, but I felt that that would negatively impact readability / maintainability.

  • My custom update functions modify variables by reference, so there is no return value -- this makes the code slightly more succinct.

  • Iterating the letters in a string can be done without splitting the word into an array of letters because php allows access to each letter by its offset. If your hangman game may include multibyte characters, mb_ functions will need to be implemented.

  • In your loop I recommend writing all of your negative/invalid/erred processes first and saving the successful outcome for last -- I find this more readable.

The custom functions:

function game_not_finished($secret_word, $word_status, $wrong_count) {
    return $wrong_count < 6 && $secret_word != $word_status;
}

function update_gallows(&$gallows, $part) {
    $gallows[$part["row"]][$part["offset"]] = $part["symbol"];
}

function update_word_status($secret_word, &$word_status, $letter) {
    for ($offset = 0, $length = strlen($secret_word); $offset < $length; ++$offset) {
        if ($secret_word[$offset] == $letter) {
            $word_status[$offset] == $letter;
        }
    }
}

The gallows storage:

$gallows = [
    " +---+",
    " |   |",
    "     |",
    "     |",
    "     |",
    "     |",
    "=======",
];

$victim = [
    ["row" => 2, "offset" => 1, "symbol" => "o"], // head
    ["row" => 3, "offset" => 1, "symbol" => "|"], // torso
    ["row" => 3, "offset" => 0, "symbol" => "/"], // right arm
    ["row" => 3, "offset" => 2, "symbol" => "\\"], // left arm
    ["row" => 4, "offset" => 0, "symbol" => "/"], // right leg
    ["row" => 4, "offset" => 2, "symbol" => "\\"], // left leg
];

Initializing required variables:

$secret_word = $wordpool[rand(0, sizeof($wordpool) -1)];
$word_length = strlen($secret_word);
$word_status = str_repeat('*', $word_length);
$chosen_letters = '';
$wrong_letters = '';
$wrong_count = 0;

echo "The word has $word_length letters." , PHP_EOL;    

The loop:

while (game_not_finished($secret_word, $word_status, $wrong_count)) {
    echo "You have already chosen the following letters: " , $chosen_letters , PHP_EOL;
    echo "The letters revealed so far are: " , $word_status , PHP_EOL;
    echo "The following guessed letters are not in the word: " , $wrong_letters , PHP_EOL;
    echo implode(PHP_EOL, $gallows);

    echo "Give your choice for a letter." . PHP_EOL;
    echo ">";
    $input = trim(strtolower(fgets(STDIN)));  // or uppercase if fitting for your word list
    echo "The letter you have chosen is: $input" , PHP_EOL;
    // validate input
    if (strlen($input) != 1) {
        echo "You have chosen more than one letter." , PHP_EOL;
        continue;
    }
    if (!ctype_alpha($input)) {
        echo "This is not a valid letter for hangman." , PHP_EOL;
        continue;
    }
    if (strpos($chosen_letters, $input) === false) {
        echo "You have tried this letter before." , PHP_EOL;
        continue;
    }
    if (strpos($secret_word, $input) === false) {
        echo "Too bad, this letter does not appear in the word." , PHP_EOL;
        $wrong_letters .= $input;
        update_gallows($gallows, $victim[$wrong_count]);  // add another body part to gallows
        ++$wrong_count;
    } else {
        echo "Well done, this letter occurs in the word." , PHP_EOL;
        update_word_status($secret_word, &$word_status, $input); // no return value, overwrites $word_status in function
    }
    $chosen_letters .= $input;
}

The conclusion:

if ($wrong_count == 6) {
    echo "Sorry, you lost" , PHP_EOL;
    // show full hangman . echo implode(PHP_EOL, $gallows);
} else {
    echo "Congratulations, you won" , PHP_EOL;
}
\$\endgroup\$
0
\$\begingroup\$

Avoid global variables

This may be difficult to explain but there are many reasons to avoid global variables, many of which are explained in this article. The reasons that stand out the most to me are Implicit coupling and Testing and Confinement (testing becomes a lot more difficult when global variables are used).

Empty if statement

I see the following in doorgaan():

if ($int == 6) {
    } else {

It would be simpler to just use a negated condition from the if condition:

if ($int !== 6) {
    //statements currently in the else block
}

Use Nowdoc syntax for multi-line text that is static

Each of the gallows representations could be stored in a constant - e.g. with define() or const

const GALLOWS_0 = <<<'GALLOWS'
 +---+
 |   |
     |
     |
     |
     |
     |
=======
GALLOWS;

Then GALLOWS_0 could be used instead of the 8 echo() statements with PHP_EOL appended. Another option would be to construct the gallows dynamically based the number in $arg1. Of the six variations with the eight lines, there are only three lines that really change - i.e. the third, fourth and fifth lines.

Utilize more string functions

Code like

$length = strlen($galgwoord);
for ($i=0; $i < $length; $i++) { 
    $woordstatus[$i] = '*';
}

Could be simplified with str_repeat():

$woordstatus = str_repeat('*', strlen($galgwoord));
\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.