Products

Hangman game

The hangman game is a classical paper and pencil guessing game. Here is the minimal implementation of JavaScript using LemonadeJS.

See this example on codesandbox

A working example

The game show name of fruits in english.

Source code

<html>
<script src="https://lemonadejs.com/v5/lemonade.js"></script>
<div id='root'></div>
<script>
let { onload } = lemonade;

function Hangman() {

    onload(() => {
        // Reset game
        reset();
    })

    // Possible words
    const words = ['apple', 'banana', 'orange', 'pear', 'lemon' ];

    const reset = () => {
        // Make sure to restart the game
        this.gameOver = '';
        // Reset answers
        this.answer = [];
        // Random index
        let index = Math.floor(Math.random() * words.length);
        // Letters
        this.letters = words[index].split('').map((v, k) => {
            return { answer: v.toUpperCase(), disabled: !! k };
        });
        // Make all parts of the figure hidden
        this.figure.map((item) => {
            item.visible = false;
        })
        // Focus on the first element
        this.letters[0].el.focus();
    }

    const input = (e, s) => {
        // Input letter
        let letter = e.target.value.toUpperCase();
        // Check letter
        if (s.answer === letter) {
            // Make sure capital letter
            e.target.value = letter;
            // Focus on the next one
            s.disabled = true;
            // Current position
            let index = this.letters.indexOf(s);
            // Next
            index++;
            // Over?
            if (this.letters[index]) {
                // Disable
                this.letters[index].disabled = false;
                // Next
                e.target.nextElementSibling.focus();
            } else {
                this.status = 'You win!';
            }
        } else {
            // Show figure
            this.figure[this.answer.length].visible = true;
            // Wrong answers
            this.answer.push({letter});
            // Refresh template
            this.refresh('answer');
            // Reset input
            e.target.value = '';
            // Check end of the game
            if (this.answer.length > 5) {
                // Focus on the reset button
                s.disabled = true;
                // Game over
                this.status = 'Game Over';
            }
        }
    }

    // Figure representation
    this.figure = [
        { name: 'head' },
        { name: 'torso' },
        { name: 'arm left' },
        { name: 'arm right' },
        { name: 'leg left' },
        { name: 'leg right'  }
    ];
    
    return render => render`<div>
        <div class="hangman" data-status="${this.status}">
            <div class="figure" :loop="${this.figure}">
                <div class="{{this.name}}" data-visible="{{this.visible}}"></div>
            </div><div>
                <div :loop="${this.answer}" class="answers">
                    <div>{{self.letter}}</div>
                </div>
                <div :loop="${this.letters}" class="word">
                    <input type="text" maxlength="1" oninput="${input}" disabled="{{this.disabled}}" />
                </div>
            </div>
        </div>
        <input type="button" value="Reset game" onclick="${reset}" :ref="this.button" />
    </div>`;
}
lemonade.render(Hangman, document.getElementById('root'));
</script>
</html>
import lemonade from 'lemonadejs';

export default function Hangman() {

    onload(() => {
        // Reset game
        reset();
    })

    // Possible words
    const words = ['apple', 'banana', 'orange', 'pear', 'lemon' ];

    const reset = () => {
        // Make sure to restart the game
        this.gameOver = '';
        // Reset answers
        this.answer = [];
        // Random index
        let index = Math.floor(Math.random() * words.length);
        // Letters
        this.letters = words[index].split('').map((v, k) => {
            return { answer: v.toUpperCase(), disabled: !! k };
        });
        // Make all parts of the figure hidden
        this.figure.map((item) => {
            item.visible = false;
        })
        // Focus on the first element
        this.letters[0].el.focus();
    }

    const input = (e, s) => {
        // Input letter
        let letter = e.target.value.toUpperCase();
        // Check letter
        if (s.answer === letter) {
            // Make sure capital letter
            e.target.value = letter;
            // Focus on the next one
            s.disabled = true;
            // Current position
            let index = this.letters.indexOf(s);
            // Next
            index++;
            // Over?
            if (this.letters[index]) {
                // Disable
                this.letters[index].disabled = false;
                // Next
                e.target.nextElementSibling.focus();
            } else {
                this.status = 'You win!';
            }
        } else {
            // Show figure
            this.figure[this.answer.length].visible = true;
            // Wrong answers
            this.answer.push({letter});
            // Refresh template
            this.refresh('answer');
            // Reset input
            e.target.value = '';
            // Check end of the game
            if (this.answer.length > 5) {
                // Focus on the reset button
                s.disabled = true;
                // Game over
                this.status = 'Game Over';
            }
        }
    }

    // Figure representation
    this.figure = [
        { name: 'head' },
        { name: 'torso' },
        { name: 'arm left' },
        { name: 'arm right' },
        { name: 'leg left' },
        { name: 'leg right'  }
    ];

    return render => render`<div>
        <div class="hangman" data-status="${this.status}">
            <div class="figure" :loop="${this.figure}">
                <div class="{{this.name}}" data-visible="{{this.visible}}"></div>
            </div><div>
                <div :loop="${this.answer}" class="answers">
                    <div>{{self.letter}}</div>
                </div>
                <div :loop="${this.letters}" class="word">
                    <input type="text" maxlength="1" oninput="${input}" disabled="{{this.disabled}}" />
                </div>
            </div>
        </div>
        <input type="button" value="Reset game" onclick="${reset}" :ref="this.button" />
    </div>`;
}

CSS required for this example


.hangman {
    display: flex;
    padding: 40px;
}

.hangman > div {
    padding: 20px;
}

.hangman .figure {
    width: 100px;
    height: 160px;
    border-top: 1px solid #000;
    border-left: 1px solid #000;
    position: relative;
    margin-right: 40px;
}

.hangman .figure > div {
    display: none;
}

.hangman input {
    border: 0px;
    border-bottom: 1px solid black;
    width: 30px;
    margin: 15px;
    outline: 0px;
    text-align: center;
}
.hangman input[disabled] {
    background-color: #eee;
}

.hangman .answers {
    display: flex;
    height: 80px;
    text-align: center;
    font-size: 20px;
    margin-bottom: 20px;
}

.hangman .answers > div {
    margin: 20px;
}

.hangman .word {
    height: 80px;
}

.hangman .figure > div {
    box-sizing: border-box;
}

.hangman .head {
    width: 30px;
    height: 40px;
    border: 1px solid black;
    border-radius: 50%;
    position: absolute;
    top: 20px;
    left: 60px;
}

.hangman .torso {
    width: 1px;
    height: 50px;
    border: 1px solid black;
    display: block;
    position: absolute;
    top: 60px;
    left: 75px;
    box-sizing: border-box;
}

.hangman .leg {
    width: 1px;
    height: 40px;
    border: 1px solid black;
    display: block;
    position: absolute;
    box-sizing: border-box;
    top: 110px;
    left: 75px;
}

.hangman .leg.left {
    transform: rotate(12deg);
    transform-origin: top;
}

.hangman .leg.right {
    transform: rotate(-12deg);
    transform-origin: top;
}

.hangman .arm {
    width: 30px;
    height: 1px;
    border: 1px solid black;
    position: absolute;

}

.hangman .arm.left {
    top: 70px;
    left: 45px;
}

.hangman .arm.right {
    top: 70px;
    left: 75px;
}

.hangman .over .word input {
    pointer-events: none;
}

.hangman div[data-visible=true] {
    display: block;
}

.hangman[data-status]::before {
    content: attr(data-status);
    position: absolute;
    margin: -40px 0 0 200px;
    font-weight: bold;
}