Wednesday, 17 March 2021

Web Tutorial: ReactJS Hangman (Part 2/4)

In the previous part of this web tutorial, we created a swinging hanged man whose parts would display at different stages of the game. Today, we'll begin the process of providing user interfaces to generate and display the mystery word for Hangman, and a simple operation to handle word guesses.

We first set mysteryWord and guessedLetters and their mutators, setGuessedLetters() and setMysteryWord(), using useState(). The default value for mysteryWord is the first value of the wordList array, which is always an empty string. The default value for guessedLetters is an empty array.

src/App.js
const [wordList, setWordList] = useState(['']);
const [mysteryWord, setMysteryWord] = useState(wordList[0]);
const [guessedLetters, setGuessedLetters] = useState([]);


We will also declare a combined function, setMessageAndContext(), to call setMessage() and setMessageContext().

src/App.js
const [wordList, setWordList] = useState(['']);
const [mysteryWord, setMysteryWord] = useState(wordList[0]);
const [guessedLetters, setGuessedLetters] = useState([]);

const [stage, setStage] = useState(-1);
const [message, setMessage] = useState('Welcome to Hangman! Click button to Begin');
const [messageContext, setMessageContext] = useState('');

const setMessageAndContext = (strMessage, strContext)=> {
    setMessage(strMessage);
    setMessageContext(strContext);
}


And then we will use useEffect(), to set both the message and context upon the app rendering. Remember importing useEffect from react in the previous part? Well, now it will come in useful.

src/App.js
const setMessageAndContext = (strMessage, strContext)=> {
    setMessage(strMessage);
    setMessageContext(strContext);
}

useEffect(() => {

});


Then we use setMessageAndContext() to set the message to "Guess the mystery word".

src/App.js
useEffect(() => {
    setMessageAndContext('Guess the mystery word', '');
});


After that, we use setMysteryWord() to set mysteryWord to a random word in wordList. To do that, we use the function GetRandomIndex(), which we will soon write.

src/App.js
useEffect(() => {
    setMessageAndContext('Guess the mystery word', '');
    setMysteryWord(wordList[GetRandomIndex(wordList.length)]);
});


GetRandomIndex() is actually a function that isn't meant to be tied specifically to any one component. This means I wrote it to potentially be re-used by any component. For stuff like this, I recommend creating a separate folder within src. In this case, we will create the directory utils.

Here, we create GetRandomIndex.js. It should accept as a parameter an integer, wordListLength. And then use the random() method to select any number from 0 to wordListLength, plus 1. Because we don't want it to select element 0 of wordList, which is an empty string. However, we also don't want it to go above the boundaries of the array, so at the end, there's an If block to decrement randomIndex if it's out of bounds. Finally, we return randomIndex, and export GetRandomIndex as default.

src/utils/GetRandomIndex.js
const GetRandomIndex = (wordListLength) => {
    let randomIndex = Math.floor(Math.random() * wordListLength) + 1;
    if (randomIndex === wordListLength) randomIndex--;

    return randomIndex;
}

export default GetRandomIndex;  


We then, of course, import GetRandomIndex from its directory. At the same time, import Computer in preparation for the next step, because we will be creating that component.

src/App.js
import React, { useState, useEffect } from 'react';
import { useAsync } from 'react-async';
import './App.css';
import HangedMan from './components/HangedMan';
import Computer from './components/Computer';

import GetRandomIndex from './utils/GetRandomIndex';


Remember, we have set mysteryWord and guessedLetters. Now we will pass them down as props to Computer.

src/App.js
return (
    <div className="App">       
        <h1>HANGMAN</h1>
        <HangedMan stage={ stage } />
        <div>
            { isPending && 'Loading...' }
        </div>
        <div className={ 'Message ' + messageContext }>
            { message }
        </div>
        <Computer
            mysteryWord={ mysteryWord }
            guessedLetters={ guessedLetters }
        />

    </div>
);


And now we create the Computer component. This is the part that displays the mystery word, hiding the letters until they are guessed correctly. For this, create the Computer directory within the components directory, then create index.js. As with HangedMan, do the export.

src/components/Computer/index.js
export { default } from './Computer';


Create Computer.js. Do what we did for the HangedMan component. We will set up the CSS soon enough.

src/components/Computer/Computer.js

import React from 'react';
import './Computer.css';

function Computer(props) {
    return (

    );
}

export default Computer;


In the return statement, we have a div styled using Computer encapsulating another div styled using Letters.

src/components/Computer/Computer.js
import React from 'react';
import './Computer.css';

function Computer(props) {
    return (
        <div className="Computer">
            <div className="Letters">
                
            </div>
        </div>

    );
}

export default Computer;


Create the CSS file. Set width to 100% for Computer, and a height of, say, 70 pixels. For Letters, we want to display its contents center, so we'll use some very simple flex.

src/components/Computer/Computer.css
.Computer {
    width: 100%;
    height: 70px;
}

.Computer .Letters {
    display: flex;
    background-color: rgba(0, 0, 0, 1);
    box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.5);
    flex-flow: row wrap;
    justify-content: center;
}


Back to computer.js, we get mysteryWord and guessedLetters from props. Then we obtain mysteryLetters by converting mysteryWord to an array of its letters.

src/components/Computer/Computer.js
function Computer(props) {
    let mysteryWord = props.mysteryWord;
    let guessedLetters = props.guessedLetters;

    let mysteryLetters = mysteryWord.split('');


    return (
        <div className="Computer">
            <div className="Letters">

            </div>
        </div>
    );
}


Obtain the mystery component by running mysteryLetters through a map() operation. Define the key; it's good practice. And set the class to Letter if that particular letter in mysteryLetters is found within guessedLetters, and hide it using hidden if not.

src/components/Computer/Computer.js
function Computer(props) {
    let mysteryWord = props.mysteryWord;
    let guessedLetters = props.guessedLetters;

    let mysteryLetters = mysteryWord.split('');

    let mystery = mysteryLetters.map((item, index) => (
        <div
            key={ 'letter_' + index }
            className={guessedLetters.indexOf(item) !== -1 ? 'Letter' : 'Letter hidden'}
        >
                  
        </div>
    ));


    return (
        <div className="Computer">
            <div className="Letters">

            </div>
        </div>
    );
}


Cap it off by putting the letter in the div.

src/components/Computer/Computer.js
function Computer(props) {
    let mysteryWord = props.mysteryWord;
    let guessedLetters = props.guessedLetters;

    let mysteryLetters = mysteryWord.split('');

    let mystery = mysteryLetters.map((item, index) => (
        <div
            key={ 'letter_' + index }
            className={guessedLetters.indexOf(item) !== -1 ? 'Letter' : 'Letter hidden'}
        >
            { item }                   
        </div>
    ));

    return (
        <div className="Computer">
            <div className="Letters">

            </div>
        </div>
    );
}


Then put mystery into the JSX.

src/components/Computer/Computer.js
function Computer(props) {
    let mysteryWord = props.mysteryWord;
    let guessedLetters = props.guessedLetters;

    let mysteryLetters = mysteryWord.split('');

    let mystery = mysteryLetters.map((item, index) => (
        <div
            key={ 'letter_' + index }
            className={guessedLetters.indexOf(item) !== -1 ? 'Letter' : 'Letter hidden'}
        >
            { item }                   
        </div>
    ));

    return (
        <div className="Computer">
            <div className="Letters">
                { mystery }
            </div>
        </div>
    );
}


Naturally, what we need to do next is style Letter. Each div using Letter is a child of the div styled using Letters. The border color and background colors are set; use some artistic license here. For hidden, background color is set to black.

src/components/Computer/Computer.css
.Computer .Letters {
    display: flex;
    background-color: rgba(0, 0, 0, 1);
    box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.5);
    flex-flow: row wrap;
    justify-content: center;
}

.Computer .Letters .Letter {
    margin: 4px 0 4px 4px;
    width: 40px;
    height: 40px;
    border-radius: 5px;
    border: 1px solid rgba(200, 200, 200, 1);
    background-color: rgba(255, 255, 200, 1);
    color: rgba(100, 0, 0, 1);
    float: left;
    text-align: center;
    font-size: 2em;
    font-weight: bold;
    text-transform: uppercase;
}

.Computer .Letters .hidden {
    background-color: rgba(0, 0, 0, 1);
}


Here, you can see that the message has been set. The word, in this case, is "ungratefully". Refresh and you will see that the word changes!


Of course, you can't "guess" the mystery word because the mystery word has already been displayed for you, albeit with the "hidden" background. This is by design, because later on you are going to have to test the function for guessing the mystery word and as the developer, you want to see what the word is.

Also, at this point, the word should not even be showing. What we need to do when calling useEffect(), is ensure that nothing happens when message is an empty string, as will be the case when the app is first loaded.

src/App.js
useEffect(() => {
    if (message !== '') return;

    setMessageAndContext('Guess the mystery word', '');
    setMysteryWord(wordList[GetRandomIndex(wordList.length)]);
});


If you reload the app now, you will see that the letters are no longer showing!

Now for the next component...

We need another component to handle user input. This one will be called Player. In App, we'll include the component, and pass down stage, mysteryWord and guessedLetters, and their respective mutators. We will also pass down setMessageAndContext, error and isPending. This may not actually be the best way to accomplish what we want, but for the purposes of this exercise, it's the most expedient.

src/App.js
return (
    <div className="App">       
        <h1>HANGMAN</h1>
        <HangedMan stage={ stage } />
        <div>
            { isPending && 'Loading...' }
        </div>
        <div className={ 'Message ' + messageContext }>
            { message }
        </div>
        <Computer
            mysteryWord={ mysteryWord }
            guessedLetters={ guessedLetters }
        />
        <Player
            stage={ stage }
            setStage={ setStage }
            mysteryWord={ mysteryWord }
            guessedLetters={ guessedLetters }
            setGuessedLetters={ setGuessedLetters }
            setMessageAndContext={ setMessageAndContext }
            error={ error }
            isPending={ isPending }
        />

    </div>
);


We'll then want to create that component by creating Player folder inside the components folder of the src directory.

Inside it, create index.js. And then do what we did for the Computer and HangedMan components.
src/components/Player/index.js
export { default } from './Player';


Do the same with Player.js. But unlike the Computer and HangedMan components, we will omit the return statement for now.

src/components/Player/Player.js
import React from 'react';
import './Player.css';

function Player(props) {

}

export default Player;


There will be plenty to do for the CSS. For now, just style Player. We'll use the flexbox display in order to center everything. This will come in useful because there will be multiple nested divs within. Aesthetically, I've also added a bit of shadows and stuff, but it's not strictly neccessary.

src/components/Player/Player.css
.Player {
    display: flex;
    background-color: rgba(0, 0, 0, 1);
    box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.5);
    padding: 10px 0;
    flex-flow: row wrap;
    justify-content: center;
    align-items: center;
}


Now back to Player.js. Grab all the data passed down via props, and set them to local variables. Also, declare mysteryLetters. It is an array derived from mysteryWord.

src/components/Player/Player.js
import React from 'react';
import './Player.css';

function Player(props) {
    let stage = props.stage;
    let setStage = props.setStage;
    let guessedLetters = props.guessedLetters;
    let setGuessedLetters = props.setGuessedLetters;
    let mysteryWord = props.mysteryWord;
    let setMessageAndContext = props.setMessageAndContext;
    let error = props.error;
    let isPending = props.isPending;

    let mysteryLetters = mysteryWord.split('');

}

export default Player;


Back to Player.js. Remember we omitted the return statement? That's because we want the component to return conditionally; specifically, to return different things based  the value of stage. So set an If-else block, catering for the values of -1 and 6, and everything else. For a value of -1 for stage, the game has not started. For a value of 6, the game is over. And for every other value, the game is in progress.

src/components/Player/Player.js
import React from 'react';
import './Player.css';

function Player(props) {
    let stage = props.stage;
    let setStage = props.setStage;
    let guessedLetters = props.guessedLetters;
    let setGuessedLetters = props.setGuessedLetters;
    let mysteryWord = props.mysteryWord;
    let setMessageAndContext = props.setMessageAndContext;
    let error = props.error;
    let isPending = props.isPending;

    let mysteryLetters = mysteryWord.split('');

    if (stage === -1) {
        return (
          
        );
    } else if (stage === 6) {
        return (
           
        );
    } else {
        return (

        );          
    }

}

export default Player;


So if the game has not started, return a button with the text "Begin". If the game is over, render that button also, but with the text "Replay". If the game is in progress, return a div styled using Player.

src/components/Player/Player.js
import React from 'react';
import './Player.css';

function Player(props) {
    let stage = props.stage;
    let setStage = props.setStage;
    let guessedLetters = props.guessedLetters;
    let setGuessedLetters = props.setGuessedLetters;
    let mysteryWord = props.mysteryWord;
    let setMessageAndContext = props.setMessageAndContext;
    let error = props.error;
    let isPending = props.isPending;

    let mysteryLetters = mysteryWord.split('');

    if (stage === -1) {
        return (
        <button className="BtnBegin">
            Begin
        </button> 
          
        );
    } else if (stage === 6) {
        return (
        <button className="BtnBegin">
            Replay
        </button> 
           
        );
    } else {
        return (
        <div className="Player">

        </div>

        );          
    }
}

export default Player;


Here's some styling for the button. I've made it various shades of grey, even upon hovering. This is up to personal taste.

src/components/Player/Player.css

.Player {
    display: flex;
    background-color: rgba(0, 0, 0, 1);
    box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.5);
    padding: 10px 0;
    flex-flow: row wrap;
    justify-content: center;
    align-items: center;
}

.BtnBegin {
    display: block;
    margin: 0 auto 0 auto;
    padding: 0.5em;
    width: 8em;
    border-radius: 5px;
    border: 0px solid red;
    background-color: rgba(100, 100, 100, 1);
    color: rgba(0, 0, 0, 1);
    font-weight: bold;
    font-size: 2em;
    cursor: pointer;
}

.BtnBegin:hover{
    background-color: rgba(200, 200, 200, 1);
    color: rgba(100, 100, 100, 1);
}


You should see this. Because when the app loads, stage is set to -1.


Now, clicking the button will not accomplish anything simply because we haven't defined an event for it. So let's do it now.

src/components/Player/Player.js

if (stage === -1) {
    return (
    <button className="BtnBegin" onClick={ ()=>{BtnBegin_click();}}>
        Begin
    </button>           
    );
} else if (stage === 6) {
    return (
    <button className="BtnBegin" onClick={ ()=>{BtnBegin_click();}}>
        Replay
    </button>            
    );
} else {
    return (
    <div className="Player">

    </div>
    );          
}


And then we define BtnBegin_click(). First, we handle cases for the value of error and isPending, causing the function to exit prematurely if either is true. This will ensure that nothing awkward happens if the user clicks on the buttons before the required data has loaded.

src/components/Player/Player.js
function Player(props) {
    let stage = props.stage;
    let setStage = props.setStage;
    let guessedLetters = props.guessedLetters;
    let setGuessedLetters = props.setGuessedLetters;
    let mysteryWord = props.mysteryWord;
    let setMessageAndContext = props.setMessageAndContext;
    let error = props.error;
    let isPending = props.isPending;

    let mysteryLetters = mysteryWord.split('');

    const BtnBegin_click = ()=> {
        if (error) {
            alert("Error has occured. Please reload.");
            return;
        };

        if (isPending) {
            alert("Fetching words in progress. Please wait.");
            return;
        };
    };


    if (stage === -1) {
        return (
        <button className="BtnBegin" onClick={ ()=>{BtnBegin_click();}}>
            Begin
        </button>           
        );


After that, if neither error nor isPending is true, we set stage to 0 using setStage(). This basically means that the game has begun, or restarted. Then we use setGuessedLetters to, well, set guessedLetters, to an empty array. And then we call setMessageAndContext() to clear both message content and the context.

src/components/Player/Player.js
const BtnBegin_click = ()=> {
    if (error) {
        alert("Error has occured. Please reload.");
        return;
    };

    if (isPending) {
        alert("Fetching words in progress. Please wait.");
        return;
    };

    setStage(0);
    setGuessedLetters([]);
    setMessageAndContext("", "");

};


We follow up by calling setUsedLetters() to set usedLetters to an empty array. If you've been paying attention, you're probably wondering where this came from. That's because we haven't defined this yet.

src/components/Player/Player.js
const BtnBegin_click = ()=> {
    if (error) {
        alert("Error has occured. Please reload.");
        return;
    };

    if (isPending) {
        alert("Fetching words in progress. Please wait.");
        return;
    };

    setStage(0);
    setGuessedLetters([]);
    setUsedLetters([]);
    setMessageAndContext("", "");
};


usedLetters is an array that is defined and used only in the Player component, and isn't passed anywhere. We will define it here using useState(). We will also define guessedWord and its mutator, setGuessedWord(), using the same method.

src/components/Player/Player.js
function Player(props) {
    const [guessedWord, setguessedWord] = useState('');
    const [usedLetters, setUsedLetters] = useState([]);


    let stage = props.stage;
    let setStage = props.setStage;
    let guessedLetters = props.guessedLetters;
    let setGuessedLetters = props.setGuessedLetters;
    let mysteryWord = props.mysteryWord;
    let setMessageAndContext = props.setMessageAndContext;
    let error = props.error;
    let isPending = props.isPending;

    let mysteryLetters = mysteryWord.split('');

    const BtnBegin_click = ()=> {
        if (error) {
            alert("Error has occured. Please reload.");
            return;
        };

        if (isPending) {
            alert("Fetching words in progress. Please wait.");
            return;
        };

        setStage(0);
        setGuessedLetters([]);
        setUsedLetters([]);
        setMessageAndContext("", "");
    };


Now we will define some JSX under the constant dashboard. Within a fragment, we'll have a div styled using GuessWord. Include dashboard in the last return statement. This should show when the game is in progress.

src/components/Player/Player.js
const BtnBegin_click = ()=> {
    if (error) {
        alert("Error has occured. Please reload.");
        return;
    };

    if (isPending) {
        alert("Fetching words in progress. Please wait.");
        return;
    };

    setStage(0);
    setGuessedLetters([]);
    setUsedLetters([]);
    setMessageAndContext("", "");
};

const dashboard = (
    <>
        <div className="GuessWord">

        </div>
    </>
);


if (stage === -1) {
    return (
    <button className="BtnBegin" onClick={ ()=>{BtnBegin_click();}}>
        Begin
    </button>           
    );
} else if (stage === 6) {
    return (
    <button className="BtnBegin" onClick={ ()=>{BtnBegin_click();}}>
        Replay
    </button>            
    );
} else {
    return (
    <div className="Player">
        { dashboard }
    </div>
    );          
}  


This interface is for the user to guess the entire word at one go. We're going to add a h3 tag.

src/components/Player/Player.js
const dashboard = (
    <>
        <div className="GuessWord">
            <h3>Guess The Word</h3>
        </div>
    </>
)


Then add an input tag. Since the length of every word is between 5 to 13 characters, we will set the maxlength attribute to 13. The value will be guessedWord.

src/components/Player/Player.js
const dashboard = (
    <>
        <div className="GuessWord">
            <h3>Guess The Word</h3>
            <input
                type="text"
                maxLength="13"
                value={ guessedWord }
            />

        </div>
    </>
)


Then we have a button which is disabled if guessedWord is an empty string.

src/components/Player/Player.js
const dashboard = (
    <>
        <div className="GuessWord">
            <h3>Guess The Word</h3>
            <input
                type="text"
                maxLength="13"
                value={ guessedWord }
            />
            <br /><br />
            <button disabled={guessedWord.length === 0}>
                Confirm
            </button>

        </div>
    </>
)

All this needs to be styled. It should probably go without saying that you should feel free to style it however you want. Since the background color of Player is black, I've elected for white for text color. In any case, styling really isn't the point of this web tutorial here, and you should do what makes sense to you.

src/components/Player/Player.css
.Player {
    display: flex;
    background-color: rgba(0, 0, 0, 1);
    box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.5);
    padding: 10px 0;
    flex-flow: row wrap;
    justify-content: center;
    align-items: center;
}

.GuessWord {
    background-color: rgba(0, 0, 0, 1);
    width: 45%;
    height: 150px;
}

.GuessWord {
    float: right;
}

.GuessWord input{
    padding: 0.5em;
    width: 13em;
    border-radius: 5px;
    border: 0px solid red;
}

.GuessWord button{
    padding: 0.5em;
    width: 8em;
    border-radius: 5px;
    border: 0px solid red;
    background-color: rgba(100, 100, 100, 1);
    color: rgba(0, 0, 0, 1);
    font-weight: bold;
    cursor: pointer;
}

.GuessWord button:hover{
    background-color: rgba(200, 200, 200, 1);
    color: rgba(100, 100, 100, 1);
}

.GuessWord {
    text-align: center;
    width: 45%;
    min-width: 200px;
}

.GuessWord h3 {
    color: rgba(255, 255, 255, 1);
}


.BtnBegin {
    display: block;
    margin: 0 auto 0 auto;
    padding: 0.5em;
    width: 8em;
    border-radius: 5px;
    border: 0px solid red;
    background-color: rgba(100, 100, 100, 1);
    color: rgba(0, 0, 0, 1);
    font-weight: bold;
    font-size: 2em;
    cursor: pointer;
}

.BtnBegin:hover{
    background-color: rgba(200, 200, 200, 1);
    color: rgba(100, 100, 100, 1);
}


So when you click the BEGIN button, this is what you should see! Note that the mystery word (currently "EMOTE") is still visible because we want to run some tests.


Let's handle input to the text box. Using e to derive the value of that textbox, we then use setGuessedWord() to set guessedWord to that value.

src/components/Player/Player.js

<input
    type="text"
    maxLength="13"
    value={ guessedWord }
    onChange={ (e)=>{ setGuessedWord(e.target.value); }}
/>


Next, let's handle the button click. The button is disabled if there's no input. That's been handled. But if there's input and the button is clicked, we want it to do something.

src/components/Player/Player.js
<button onClick={ ()=>{BtnConfirm_click();}} disabled={guessedWord.length === 0}>
    Confirm
</button>


Create BtnConfirm_click() as an event handler.

src/components/Player/Player.js
let stage = props.stage;
let setStage = props.setStage;
let guessedLetters = props.guessedLetters;
let setGuessedLetters = props.setGuessedLetters;
let mysteryWord = props.mysteryWord;
let setMessageAndContext = props.setMessageAndContext;
let error = props.error;
let isPending = props.isPending;

let mysteryLetters = mysteryWord.split('');

const BtnConfirm_click = ()=> {

};


const BtnBegin_click = ()=> {
    if (error) {
        alert("Error has occured. Please reload.");
        return;
    };


First, examine the value of stage. If the game is over or has not yet begun, we exit the function.

src/components/Player/Player.js
const BtnConfirm_click = ()=> {
    if (stage === 6 || stage === -1) return;
};


Then we check guessedWord against mysteryWord. If the word was guessed correctly, we set guessedLetters to the value of mysteryLetters, automatically set stage to the value of -1, and set both message and messageContext.

src/components/Player/Player.js
const BtnConfirm_click = ()=> {
    if (stage === 6 || stage === -1) return;

    if (guessedWord === mysteryWord) {
        setGuessedLetters(mysteryLetters);
        setStage(-1);
        setMessageAndContext("You Win!", "success");
    } else {

    }

};


If not, we increment stage, and set both message and messageContext. Note the values for both these values; we will need to handle them as well later.

src/components/Player/Player.js
const BtnConfirm_click = ()=> {
    if (stage === 6 || stage === -1) return;

    if (guessedWord === mysteryWord) {
        setGuessedLetters(mysteryLetters);
        setStage(-1);
        setMessageAndContext("You Win!", "success");
    } else {
        setStage(stage + 1);
        setMessageAndContext("You guessed '" + guessedWord + "'. Wrong!", "failure");

    }
};


If stage is 5, that means the game is over. We display the mystery word after setting both message and messageContext.

src/components/Player/Player.js
const BtnConfirm_click = ()=> {
    if (stage === 6 || stage === -1) return;

    if (guessedWord === mysteryWord) {
        setGuessedLetters(mysteryLetters);
        setStage(-1);
        setMessageAndContext("You Win!", "success");
    } else {
        setStage(stage + 1);
        setMessageAndContext("You guessed '" + guessedWord + "'. Wrong!", "failure");

        if (stage === 5) {
            setMessageAndContext("You have run out of tries!", "failure");
            setGuessedLetters(mysteryLetters);
        }

    }
};


Now we handle the CSS classes success and failure in App.css. I am setting them to background colors of green and red respectively.

src/App.css
.Message {
    padding: 3px 0 3px 0;
    margin-bottom: 5px;
    background-color: rgba(100, 100, 255, 1);
    color: rgba(255, 255, 255, 1);
    font-weight: bold;
    font-size: 0.8em;
    text-align: center;
    visibility: visible;
}

.failure {
    background-color: rgba(255, 100, 100, 1);
}

.success {
    background-color: rgba(100, 255, 100, 1);
}


Now try typing in anything but the mysery word displayed, and click the CONFIRM button.


And then try it with the correct word.


Try the wrong word several times in a row. The hanged man should appear bit by bit, and finally, you get "GAME OVER" and a REPLAY button.


Final touches

We need to tighten this up a bit. What if you enter capital letters, spaces or a number?

To handle this, we run the value through the RemoveIllegalCharacters() function, which we will create after this step.

src/components/Player/Player.js
<input
    type="text"
    maxLength="13"
    value={ guessedWord }
    onChange={ (e)=>{ setGuessedWord(RemoveIllegalCharacters(e.target.value)); }}
/>


Remember the utils folder? This is where we will create this, because it could serve as a general-purpose function.

utils/RemoveIllegalCharacters.js
const RemoveIllegalCharacters = (word) => {

}

export default RemoveIllegalCharacters;


We use a regular expression to remove everything that isn't alphabetical. Then return the lowercase version of the result. It's overkill, but whatever, man.

utils/RemoveIllegalCharacters.js
const RemoveIllegalCharacters = (word) => {
    let newWord = word.replace(/[^a-z]/gi, '');
    return newWord.toLowerCase();

}

export default RemoveIllegalCharacters;


After this, if you try to type in anything that isn't a lowercase letter, it just won't show up!

Next

The last part of the game - guessing letter by letter.

Sunday, 14 March 2021

Web Tutorial: ReactJS Hangman (Part 1/4)

Time for another ReactJS app. Today, we will be creating the classic childrens' game known as Hangman.

This is when you have to guess a word with n number of letters. Each round, you guess a letter and if it matches any letters in the word, those letters are revealed. If there are no matches, one art of the hanged man is drawn. When the drawing is complete, you lose.

What you'll need

A working understanding of NodeJS and how to create your ReactJS app using NPM would be great. If not, just follow the instructions on the ReactJS website.

We will be rendering the hanged man using SVGs. So if you don't have the slightest clue what an SVG is, some supplementary reading is recommended.

Lastly, we will be using this very awesome and totally free API endpoint to get words.

Create your ReactJS app and let's begin!

As usual, I like to make certain initial changes such as the app's title. You can also change the favicon for the app (it's the favicon.ico file in the public directory), but that's also entirely optional.

public/index.html
<title>Hangman</title>


In the src folder, we can leave everything as-is except for App.js. Clear the whole file and let's start from a clean slate. Also, let's go to App.css and clear that too. Then add this into the file. This defines the visual space on-screen for your app. The h1 specification adds a nice shadow to all h1 tags, though you won't see it just yet.

src/App.css
.App {
    width: 600px;
    margin: 0 auto 0 auto;
}

.App h1 {
    text-align: center;
    text-shadow: 2px 2px rgba(0, 0, 0, 0.5);
    font-size: 4em;
}


For App.js, we begin by importing the usual stuff. We will be using useState and useEffect from react. And of course, we will import the CSS file that we just modified.

src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';


Next, let's define an async operation here. It's called fetchWordList, and it basically uses await and fetch to get the results of calling the API endpoint for getting random words.

src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

const fetchWordList = async () => {
    const res = await fetch('https://random-word-api.herokuapp.com/word?number=50');
};


If there's no response, an Error is thrown. But otherwise, return the json property of res.

src/App.js
import React, { useState, useEffect } from 'react';
import './App.css';

const fetchWordList = async () => {
    const res = await fetch('https://random-word-api.herokuapp.com/word?number=50');
    if (!res.ok) throw new Error(res.statusText)
    return res.json();

};


And of course, we define App. And export App as the default. index.js will import App, so we need this part.

src/App.js

import React, { useState, useEffect } from 'react';
import './App.css';

const fetchWordList = async () => {
    const res = await fetch('https://random-word-api.herokuapp.com/word?number=50');
    if (!res.ok) throw new Error(res.statusText)
    return res.json();
};

function App() {

}

export default App;


In here, we will use the useState() hook to set the wordList. Default value is an array with one element, which is an empty string.

src/App.js
function App() {
    const [wordList, setWordList] = useState(['']);
}


For this next part, we will need to import async from react-async. That's because we will be calling the async operation fetchWordList.

src/App.js
import React, { useState, useEffect } from 'react';
import { useAsync } from 'react-async';
import './App.css';


We use destructuring to obtain data, error and isPending from useAsync().

src/App.js
const fetchWordList = async () => {
    const res = await fetch('https://random-word-api.herokuapp.com/word?number=50');
    if (!res.ok) throw new Error(res.statusText)
    return res.json();
};

function App() {
    const { data, error, isPending } = useAsync({

    });


    const [wordList, setWordList] = useState(['']);
}


From here, we set the value of promiseFn to fetchWordList, which is the operation we defined earlier.
src/App.js
function App() {
    const { data, error, isPending } = useAsync({
        promiseFn: fetchWordList
    });

    const [wordList, setWordList] = useState(['']);    
}


And then for the onResolve property, we create a variable named tempList. The value is set to wordList. Grab all the random words in data and push them into tempList if they are between 5 to 13 characters in length, because that's all the space we have on-screen. Then use setWordList (which we defined earlier using useState()) to the value of tempList. wordList will now be a list of random words taken from the API endpoint, as soon as the data is returned, each 5 to 13 characters in length! The first element in wordList, of course, will still be an empty string.

src/App.js
function App() {
    const { data, error, isPending } = useAsync({
        promiseFn: fetchWordList,
        onResolve: (data) => {
            let tempList = wordList;

            data.forEach((item)=> {
                if (item.length >= 5 && item.length <= 13) {
                    tempList.push(item);
                }
            });

            setWordList(tempList);
        }

    });

    const [wordList, setWordList] = useState(['']);
}


App() needs to return something in JSX.

src/App.js
function App() {
    const { data, error, isPending } = useAsync({
        promiseFn: fetchWordList,
        onResolve: (data) => {
            let tempList = wordList;

            data.forEach((item)=> {
                if (item.length >= 5 && item.length <= 13) {
                    tempList.push(item);
                }
            });

            setWordList(tempList);
        }
    });

    const [wordList, setWordList] = useState(['']);

    return (
        <div className="App">       

        </div>
    );

}


Insert in some HTML.

src/App.js
return (
    <div className="App">       
        <h1>HANGMAN</h1>
    </div>
);


And here you can see your nicely styled h1!


Add this line. Basically it means that if isPending (remember the async operation?) is true, the word "Loading" will show.

src/App.js
return (
    <div className="App">       
        <h1>HANGMAN</h1>
        <div>
            { isPending && 'Loading...' }
        </div>
    </div>
);


Refresh. "Loading" should show for a few seconds while we load the response of the API endpoint.


Back to the hooks! We use useState() to set two more variables - message and messageContext. message is a line of informative text and messageContext determines if the message is positive or negative. If it's neutral, it's an empty string.

src/App.js
const [wordList, setWordList] = useState(['']);

const [message, setMessage] = useState('Welcome to Hangman! Click button to Begin');
const [messageContext, setMessageContext] = useState('');


Here, we add this bit. The div is supposed to be styled using the Message CSS class, along with whatever messageContext is. In this case, right now, messageContext is an empty string.

src/App.js
<div className="App">       
    <h1>HANGMAN</h1>
    <div>
        { isPending && 'Loading...' }
    </div>
    <div className={ 'Message ' + messageContext }>
        { message }
    </div>

</div>


Then let's add this to the CSS. This is really cosmetic - I want to give my message a bright blue background and white text... but really, do as you see fit.

src/App.css
.App {
    width: 600px;
    margin: 0 auto 0 auto;
}

.App h1 {
    text-align: center;
    text-shadow: 2px 2px rgba(0, 0, 0, 0.5);
    font-size: 4em;
}

.Message {
    padding: 3px 0 3px 0;
    margin-bottom: 5px;
    background-color: rgba(100, 100, 255, 1);
    color: rgba(255, 255, 255, 1);
    font-weight: bold;
    font-size: 0.8em;
    text-align: center;
    visibility: visible;
}


Nice.


Now for the Hanged Man!

It's basically a big SVG, where certain pieces are visible or invisible depending on the variable, stage. To facilitate that, let's declare stage using useState(), with a default value of -1.

src/App.js
const [wordList, setWordList] = useState(['']);

const [stage, setStage] = useState(-1);
const [message, setMessage] = useState('Welcome to Hangman! Click button to Begin');
const [messageContext, setMessageContext] = useState('');


Within the return statement, we want to show the component HangedMan, and pass in the value of stage.

src/App.js
<div className="App">       
    <h1>HANGMAN</h1>
    <HangedMan stage={ stage } />
    <div>
        { isPending && 'Loading...' }
    </div>
    <div className={ 'Message ' + messageContext }>
        { message }
    </div>
</div>


And we have to import HangedMan from the directory we will create in the next step.

src/App.js
import React, { useState, useEffect } from 'react';
import { useAsync } from 'react-async';
import './App.css';
import HangedMan from './components/HangedMan';


Now, what we need to do is create the components folder. And in there, we create another folder HangedMan. In the HangedMan directory, we create three new files - HangedMan.js, HangedMan.css and index.js.

In index.js, we export this from the HangedMan.js file so that any export from the HangedMan directory won't need to know what filename to call. Because the default directory is always index.js.

src/components/HangedMan/index.js
export { default } from './HangedMan';


We then prepare the CSS file for the SVG we're about to create. Here, we ensure that the div to be styled using HangedMan will take up full width and 300 pixels height. The SVG will fill the entire div and have a white background. That will do for starters; we can come back to this later.

src/components/HangedMan/HangedMan.css
.HangedMan {
    width: 100%;
    height: 300px;
}

.HangedMan svg{
    width: 100%;
    height: 100%;    
    background-color: rgba(255, 255, 255, 1);
}


Now for the HangedMan component itself. This is important because every other component we create from this point on will pretty much follow the same template. We start with import statements - obviously, React is one thing we need to import, followed by the CSS. Then we have the main function HangedMan() which accepts a parameter, props. It will return JSX. And finally, we export HangedMan as the default.

src/components/HangedMan/HangedMan.js
import React from 'react';
import './HangedMan.css';

function HangedMan(props) {
    return (

    );
}

export default HangedMan;


In here, we begin by adding a div styled using the HangedMan CSS class, which we've already defined, and an svg tag within it.

src/components/HangedMan/HangedMan.js
function HangedMan(props) {
    return (
        <div className="HangedMan">
            <svg>

            </svg>  
        </div>

    );
}


Then we add a polyline tag styled using gallows. Note that this is JSX, so we use className rather than class as an attribute name.

src/components/HangedMan/HangedMan.js
function HangedMan(props) {
    return (
        <div className="HangedMan">
            <svg>
                <polyline className="gallows" points="320,20 120,20 120,280 480,280"></polyline>
            </svg>  
        </div>
    );
}


Back to the CSS, add gallows. This will style the polyline tag we just added.

src/components/HangedMan/HangedMan.css
.HangedMan {
    width: 100%;
    height: 300px;
}

.HangedMan svg{
    width: 100%;
    height: 100%;    
    background-color: rgba(255, 255, 255, 1);
}

.HangedMan svg .gallows{
    stroke: rgba(0, 0, 0, 1);
    stroke-width: 10;
    fill: none;
}


You can see what we just drew - a nice frame from which to hang from.


Let's add one more CSS class. hidden will cause the styled element to be 100% transparent, effectively invisible.

src/components/HangedMan/HangedMan.css
.HangedMan {
    width: 100%;
    height: 300px;
}

.HangedMan svg{
    width: 100%;
    height: 100%;    
    background-color: rgba(255, 255, 255, 1);
}

.HangedMan svg .gallows{
    stroke: rgba(0, 0, 0, 1);
    stroke-width: 10;
    fill: none;
}

.HangedMan svg .hidden{
    stroke: rgba(255, 255, 255, 0);
    fill: rgba(255, 0, 0, 0);
}


Add a line tag here, and style it using rope.

src/components/HangedMan/HangedMan.js
function HangedMan(props) {
    return (
        <div className="HangedMan">
            <svg>
                <line
                    className="rope"
                    x1="300"
                    y1="20"
                    x2="300"
                    y2="60"
                ></line>


                <polyline className="gallows" points="320,20 120,20 120,280 480,280"></polyline>
            </svg>  
        </div>
    );
}


Add the rope CSS class.

src/components/HangedMan/HangedMan.css
.HangedMan {
    width: 100%;
    height: 300px;
}

.HangedMan svg{
    width: 100%;
    height: 100%;    
    background-color: rgba(255, 255, 255, 1);
}

.HangedMan svg .gallows{
    stroke: rgba(0, 0, 0, 1);
    stroke-width: 10;
    fill: none;
}

.HangedMan svg .rope{
    stroke: rgba(200, 200, 200, 1);
    stroke-width: 4;
}


.HangedMan svg .hidden{
    stroke: rgba(255, 255, 255, 0);
    fill: rgba(255, 0, 0, 0);
}


You should be abe to see a faint grey outline at the top of the gallows! That's the rope.


Now add this. It's a circle tag and represents the hanged man's head.

src/components/HangedMan/HangedMan.js
function HangedMan(props) {
    return (
        <div className="HangedMan">
            <svg>
                <line
                    className="rope"
                    x1="300"
                    y1="20"
                    x2="300"
                    y2="60"
                ></line>

                <circle
                    cx="300"
                    cy="80"
                    r="20"
                    data-testid="hangedMan_head"
                ></circle>


                <polyline className="gallows" points="320,20 120,20 120,280 480,280"></polyline>
            </svg>  
        </div>
    );
}


However, we want to add a condition in the class - if stage is 1 or more, style using man. Otherwise, hide it by using the CSS class hidden.

src/components/HangedMan/HangedMan.js
function HangedMan(props) {
    return (
        <div className="HangedMan">
            <svg>
                <line
                    className="rope"
                    x1="300"
                    y1="20"
                    x2="300"
                    y2="60"
                ></line>

                <circle
                    className={props.stage >= 1 ? 'man' : 'man hidden'}
                    cx="300"
                    cy="80"
                    r="20"
                ></circle>

                <polyline className="gallows" points="320,20 120,20 120,280 480,280"></polyline>
            </svg>  
        </div>
    );
}


You'll want to add the man CSS class as well.

src/components/HangedMan/HangedMan.css
.HangedMan svg .rope{
    stroke: rgba(200, 200, 200, 1);
    stroke-width: 4;
}

.HangedMan svg .man{
    stroke: rgba(100, 100, 100, 1);
    stroke-width: 4;
    fill: none;
}


.HangedMan svg .hidden{
    stroke: rgba(255, 255, 255, 0);
    fill: rgba(255, 0, 0, 0);
}


If you rerun your application, you'll see nothing changes. Because stage is not greater or equal to 1. state has been set to 0, remember? So it's 0 that was passed down to the HangedMan component. Unless you do this...

src/App.js
<div className="App">       
    <h1>HANGMAN</h1>
    <HangedMan stage="1" />
    <div>
        { isPending && 'Loading...' }
    </div>
    <div className={ 'Message ' + messageContext }>
        { message }
    </div>
</div>


... and there's the head!


Now for the rest of the hanged man. Note that each of these test for a different value of stage. That means as the value of stage grows larger, more and more of the hanged man is displayed.

src/components/HangedMan/HangedMan.js
<circle
    className={props.stage >= 1 ? 'man' : 'man hidden'}
    cx="300"
    cy="80"
    r="20"
>
</circle>

<line
    className={props.stage >= 2 ? 'man' : 'man hidden'}
    x1="300"
    y1="100"
    x2="280"
    y2="160"
></line>

<line
    className={props.stage >= 3 ? 'man' : 'man hidden'}
    x1="300"
    y1="100"
    x2="320"
    y2="160"
></line>

<line
    className={props.stage >= 4 ? 'man' : 'man hidden'}
    x1="300"
    y1="100"
    x2="300"
    y2="180"
></line>

<line
    className={props.stage >= 5 ? 'man' : 'man hidden'}
    x1="300"
    y1="180"
    x2="280"
    y2="250"
></line>

<line
    className={props.stage >= 6 ? 'man' : 'man hidden'}
    x1="300"
    y1="180"
    x2="320"
    y2="250"
></line>


<polyline className="gallows" points="320,20 120,20 120,280 480,280"></polyline>


Go ahead, try it with different values!

src/App.js
<div className="App">       
    <h1>HANGMAN</h1>
    <HangedMan stage="2" />
    <div>
        { isPending && 'Loading...' }
    </div>
    <div className={ 'Message ' + messageContext }>
        { message }
    </div>
</div>


Here we go...


For stage = 3


For stage = 4


For stage = 5


For stage = 6


But for stage = 6, that means the game is also over. So let's add some text. We will use the gameover CSS class to style this.

src/components/HangedMan/HangedMan.js
<line
    className={props.stage >= 6 ? 'man' : 'man hidden'}
    x1="300"
    y1="180"
    x2="320"
    y2="250"
></line>

<text
    x="350"
    y="100"
    className={props.stage >= 6 ? 'gameover' : 'gameover hidden'}
    data-testid="txtGameOver"
>
    <tspan x="350" y="100">GAME</tspan>
    <tspan x="350" y="145">OVER</tspan>
</text>


<polyline className="gallows" points="320,20 120,20 120,280 480,280"></polyline>


The gameover CSS class.

src/components/HangedMan/HangedMan.css
.HangedMan svg .man{
    stroke: rgba(100, 100, 100, 1);
    stroke-width: 4;
    fill: none;
}

.HangedMan svg .gameover{
    stroke: rgba(255, 0, 0, 1);
    stroke-width: 3;
    font-size: 3em;
    fill: rgba(255, 0, 0, 1);
}


.HangedMan svg .hidden{
    stroke: rgba(255, 255, 255, 0);
    fill: rgba(255, 0, 0, 0);
}


And now you see the red GAME OVER text.


Let's add an extra touch!

Because just a hanged man isn't macabre enough. How about we make it swing? To do this, add a g tag around the rope and the elements that make up the man.

src/components/HangedMan/HangedMan.js
<g>
    <line
        className="rope"
        x1="300"
        y1="20"
        x2="300"
        y2="60"
    ></line>

    <circle
        className={props.stage >= 1 ? 'man' : 'man hidden'}
        cx="300"
        cy="80"
        r="20"
    ></circle>

    <line
        className={props.stage >= 2 ? 'man' : 'man hidden'}
        x1="300"
        y1="100"
        x2="280"
        y2="160"
    ></line>

    <line
        className={props.stage >= 3 ? 'man' : 'man hidden'}
        x1="300"
        y1="100"
        x2="320"
        y2="160"
    ></line>

    <line
        className={props.stage >= 4 ? 'man' : 'man hidden'}
        x1="300"
        y1="100"
        x2="300"
        y2="180"
    ></line>

    <line
        className={props.stage >= 5 ? 'man' : 'man hidden'}
        x1="300"
        y1="180"
        x2="280"
        y2="250"
    ></line>

    <line
        className={props.stage >= 6 ? 'man' : 'man hidden'}
        x1="300"
        y1="180"
        x2="320"
        y2="250"
    ></line>
</g>


And then add a conditional class of swing.

src/components/HangedMan/HangedMan.js
<g className={ props.stage >= 6 ? 'swing' : '' }>
    <line
        className="rope"
        x1="300"
        y1="20"
        x2="300"
        y2="60"
    ></line>

    <circle
        className={props.stage >= 1 ? 'man' : 'man hidden'}
        cx="300"
        cy="80"
        r="20"
    ></circle>

    <line
        className={props.stage >= 2 ? 'man' : 'man hidden'}
        x1="300"
        y1="100"
        x2="280"
        y2="160"
    ></line>

    <line
        className={props.stage >= 3 ? 'man' : 'man hidden'}
        x1="300"
        y1="100"
        x2="320"
        y2="160"
    ></line>

    <line
        className={props.stage >= 4 ? 'man' : 'man hidden'}
        x1="300"
        y1="100"
        x2="300"
        y2="180"
    ></line>

    <line
        className={props.stage >= 5 ? 'man' : 'man hidden'}
        x1="300"
        y1="180"
        x2="280"
        y2="250"
    ></line>

    <line
        className={props.stage >= 6 ? 'man' : 'man hidden'}
        x1="300"
        y1="180"
        x2="320"
        y2="250"
    ></line>
</g>


First, style the g tag to put the point of rotation at the middle top. The middle is 300px because that's where the x1 attribute of the rope begins.

src/components/HangedMan/HangedMan.css
.HangedMan svg .hidden{
    stroke: rgba(255, 255, 255, 0);
    fill: rgba(255, 0, 0, 0);
}

g{
    -webKit-transform-origin: 300px 0px;
    transform-origin: 300px 0px;
}


And then create the CSS class swing. It's an animation that is 1 second in duration and runs on infinitely. For the keyframes, we set it to rotate between -10 and 10 degrees.

src/components/HangedMan/HangedMan.css
g{
    -webKit-transform-origin: 300px 0px;
    transform-origin: 300px 0px;
}

.swing{
    animation: swing 1s infinite;
}

@keyframes swing {
  50% {-webkit-transform: rotate(-10deg);transform: rotate(-10deg);}
  100% {-webkit-transform: rotate(10deg);transform: rotate(10deg);}
}


Look at this guy swing!


Set the value back in preparaton for the next part of this web tutorial.

src/App.js
return (
    <div className="App">       
        <h1>HANGMAN</h1>
        <HangedMan stage={ stage } />
        <div>
            { isPending && 'Loading...' }
        </div>
        <div className={ 'Message ' + messageContext }>
            { message }
        </div>
    </div>
);


Next

All right now, we have a hanged man. Soon, we will go into displaying the mystery words.

Wednesday, 10 March 2021

Who's Your Favorite Ninja Turtle?

This is one of those amusing little episodes that got me thinking after the fact.

I was in between jobs during the COVID-19 pandemic, and applying to various openings. Some of the interview questions came in the form of online questionnaires, I guess because people didn't want to ask those questions in face-to-face interviews. Fair enough.

Amid some of the usual drivel like "on a scale from 1 to 10, how would you rate your Java?" and "where do you see yourself five years from now?", a couple of them stood out because they were really quite amusing. This particular one actually got me thinking pretty seriously about it, even though - or maybe precisely because - it was so quirky.

"Who's your favorite Ninja Turtle, and why?"


Turtle Power!


I mean, wow, OK.

Before I arrive at the answer I eventually gave them, I'm first going to provide a link for some context, just in case you weren't born before the 90s or just didn't have much of a childhood.

Then let's examine each possible option.

The Turtles

Leonardo. This is the serious, de facto leader of the pack. He shoulders the responsibility of protecting their little family and looking out for their well-being. He's the one that sets the direction and makes the plans. Safe to say, that's not me. I'm mostly comfortable making decisions that affect only me.

Leo's weapons are the katana, the quintessential Samurai tool. His color is blue, reflecting his coolness under fire, and his steady, quiet courage.

Michelangelo. Mikey is the party dude. He's the guy who goes "Cowabunga", always ready with a goofy grin and a corny joke. He's laid-back and chill, always up for an adventure and looking on the bright side. If there ever was a personification of the term "YOLO", this guy would be it.

His weapon of choice, the nunchaku screams fun like your average chop-socky Bruce Lee flick. His color is orange, the bright, vibrant color of energy and positivity.

Raphael. This dark, brooding, angst-filled character is not at all my kind of guy. He constantly questions authority, has snark in spades and I'm amazed he can still move under the weight of that gigantic chip on his shoulder. I know people like that; they live for conflict. Not my thing, really. I outgrew that shit in my twenties.

His sai, the pronged daggers, reflect his personality - pointed and always on the defensive. His color is red, the hue of blood and rage.

Donatello. If there's anybody more serious than Leonardo, it's the tech geek Donatello, who's always tinkering with a gadget or two. He's the studious type, totally straight-laced and obsessed with his craft. Yep, Donnie bears probably the most obvious personality resemblance to me.

He wields the simple and effective bo stick. Totally his style. His color is purple, and honestly I don't think it's a great choice where symbolism is concerned. What does that color even mean?

And my favorite is...

...Michelangelo.

Leonardo and Raphael were eliminated right off the bat. Leonardo primarily not because I dislike that kind of character, but because not being a take-charge kind of guy, it's hard to relate. As for Raphael... let's just say that I used to be this guy when I was an angsty teenager. That's in my past. I neither admire Raphael, nor do I want to be him. In fact, I'm rather proud of the fact that I am no longer this guy, and aim to keep it this way.

While Donatello is perhaps the Turtle I can identify with the most, it is the jolly, fun-loving Michelangelo that I truly aspire to be. Watching Donatello just makes me feel even more like a nerd. Who needs that? I enjoy watching Mikey's antics. They remind me of a time when I had that same wide-eyed wonder about life.

Also, orange is my favorite color.

Afterthought

This was a pretty interesting question even if the original intent behind it seems more frivolous than earnest. As with most questions of this ilk, the who or what is probably less important than the why. Making a serious attempt to answer did provide some opportunity for introspection.

This question was turtle-y awesome!
T___T

Thursday, 4 March 2021

Facebook's Fatal Flex at Australia

What's up with Facebook? From that on-off war with Apple to the WhatsApp exodus, Facebook are making news for a variety of very poor reasons these days. And then there was this thing with Australia - yes, the country - just two weeks back.

It seems that this started when the Australian Government introduced a new bit of legislation that would compel Facebook (or other companies, such as Google) to pay news media outlets every time someone used their platform to share a link to a news article. Granted, this doesn't exactly sound fair to Facebook, but this isn't what I'm here for today. What I want to talk about is how Facebook reacted.

Google attempted a bit of bluster about removing themselves from Australia, but after Microsoft - rather opportunistically, I might add - backed Australia up, with the threat of being replaced by Microsoft's own search engine, Google decided to play ball.

No more Facebook for Australia?


Facebook, on the other hand, decided that they would no longer show any more news sources from Australia if that meant they would have to pay. From a business point of view, this is entirely legitimate, but quite understandably, some quarters saw this as an attempt at intimidation.

And then Facebook flexed their muscles. They blocked all news with Australian IP addresses from showing on their platform. Unfortunately, in the process, they also accidentally blocked emergency services and public information, and the backlash was immediate.

Where Facebook went wrong

As far as I can see, Mark Zuckerberg has always been about profits rather than power. Facebook is not really interested in promoting any sort of political idealogy or influencing people; rather, they're far more interested in making money and dominating the market. However, due to the nature and ubiquity of Social Media, they are in a power of position and they bear watching.

And many eyes have been watching them for a while. Nations wary that the platform might enable insurrectionists. Help spread misinformation, or even just inconvenient information. Ultimately, wary of the sheer influence and power of such a tech platform.

Pulling Australian media content was one thing. But it was very clumsily implemented, resulting in unintended consequences. Now those wary of Facebook's power are not only concerned with Facebook's power and willingness to use it, but also with Facebook's incompetence. Power is far more dangerous in the hands of those ill-equipped to handle it. It's like having a sleek powerful racecar... driven by an intoxicated child.

Is the FB racecar
driven by a child?

With that heavy-handed move, regardless of the eventual outcome with Australia, Facebook essentially made the target on their back a lot bigger and more prominent. Good going, Zuckerberg. Subtlety definitely isn't your strong suit.

In a nutshell

The term "speak softly and carry a big stick" comes to mind. Of course, one may argue, with the size of Facebook's stick, why should they have to speak softly? Because they can't take on the combined might of all the nations opposed to being held ransom by Facebook's power. It will be a long and costly battle, with little reward for victory. Unless you're some kind of V-For-Vendetta anti-Government nutjob where sticking it to the authorities is the reward, dying on this particular hill accomplishes pretty much nothing of actual value. Facebook is a business entity, and its primary and overriding directive is to remain profitable.

As a business, consumer trust can be a huge issue. Already Facebook faces challenges in the form of data breaches, privacy concerns and more. Add this latest mishap to the mix and you can see why that stunt really didn't help their case at all.

This looks inauspicious for Facebook!
T___T