Tuesday 24 September 2024

Web Tutorial: ReactJS Liar's Dice (Part 2/4)

Back to the grind!

We'll be creating a new component. To that end, create the components directory in the src folder. In it, create the Game subfolder. And in that subfolder, create Game.js and Game.css. Keep Game.css empty for now but start Game.js with this code. We want to use useState, we want an import link to the CSS file, and we want to define the Game() function with the parameter props. And of course, we want to export Game with the export statement.

src/components/Game/Game.js
import React, { useState } from 'react';
import './Game.css';

function Game(props) {

}

export default Game;


Back to the file App.js, we make sure we import Game.

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

import GetLabels from './utils/GetLabels';


Near the end, add the Game component. This means that the Game component will appear onscreen only if stage is not 0.

src/App.js
          <label id="DashboardDialogSpeed">
            { GetLabels("dialogSpeed", lang) } 
            <select data-testid="dashboard-dialogspeed" onChange={ (e)=>{ setDialogSpeed(e.currentTarget.value); }}>
              <option value="500">{ GetLabels("fast", lang) }</option>
              <option value="1000">{ GetLabels("medium", lang) }</option>
              <option value="1500">{ GetLabels("slow", lang) }</option>
            </select>
          </label>
        </div>
      </div>

      <Game  />  
    </div>
  );
}


But we also want to pass in these values.

src/App.js
<Game
  stage = { stage }
  setStage = { setStage }
  gameStarted = { gameStarted }
  setGameStarted = { setGameStarted }
  lang = { lang }
  dialogSpeed = { dialogSpeed }

/>


We'll want to grab all the stuff we passed in props, and assign them to local variables.

src/components/Game/Game.js
function Game(props) {
  let stage = props.stage;
  let setStage = props.setStage;
  let gameStarted = props.gameStarted;
  let setGameStarted = props.setGameStarted;
  let lang = props.lang;
  let dialogSpeed = props.dialogSpeed;

}


Then we'll declare all these values, and their setters. I'll explain them as we go along.

src/components/Game/Game.js
function Game(props) {
  let stage = props.stage;
  let setStage = props.setStage;
  let gameStarted = props.gameStarted;
  let setGameStarted = props.setGameStarted;
  let lang = props.lang;
  let dialogSpeed = props.dialogSpeed;

  const [playerIntoxication, setPlayerIntoxication] = useState(100);
  const [opponentIntoxication, setOpponentIntoxication] = useState(100);
  const [playerDice, setPlayerDice] = useState([1, 1, 1, 1, 1]);
  const [opponentDice, setOpponentDice] = useState([1, 1, 1, 1, 1]);
  const [opponentDialog, setOpponentDialog] = useState("");
  const [playerGuessQty, setPlayerGuessQty] = useState(3);
  const [playerGuessDice, setPlayerGuessDice] = useState(2);
  const [guessQty, setGuessQty] = useState(3);
  const [guessDice, setGuessDice] = useState(2);
  const [shake, setShake] = useState(false);
  const [show, setShow] = useState(false);
  const [isPlayerTurn, setIsPlayerTurn] = useState(true);
  const [round, setRound] = useState(0);
  const [stageStarted, setStageStarted] = useState(false);
  const [roundStarted, setRoundStarted] = useState(false);
  const [turns, setTurns] = useState(0);

}


This next bit should make much difference but let's add it anyway. If stage is 0, we return Game as an empty div.

src/components/Game/Game.js
function Game(props) {
  let stage = props.stage;
  let setStage = props.setStage;
  let gameStarted = props.gameStarted;
  let setGameStarted = props.setGameStarted;
  let lang = props.lang;
  let dialogSpeed = props.dialogSpeed;

  const [playerIntoxication, setPlayerIntoxication] = useState(100);
  const [opponentIntoxication, setOpponentIntoxication] = useState(100);
  const [playerDice, setPlayerDice] = useState([1, 1, 1, 1, 1]);
  const [opponentDice, setOpponentDice] = useState([1, 1, 1, 1, 1]);
  const [opponentDialog, setOpponentDialog] = useState("");
  const [playerGuessQty, setPlayerGuessQty] = useState(3);
  const [playerGuessDice, setPlayerGuessDice] = useState(2);
  const [guessQty, setGuessQty] = useState(3);
  const [guessDice, setGuessDice] = useState(2);
  const [shake, setShake] = useState(false);
  const [show, setShow] = useState(false);
  const [isPlayerTurn, setIsPlayerTurn] = useState(true);
  const [round, setRound] = useState(0);
  const [stageStarted, setStageStarted] = useState(false);
  const [roundStarted, setRoundStarted] = useState(false);
  const [turns, setTurns] = useState(0);

  if (stage === 0) {
    return (
      <div id="Game">

      </div>        
    );  
  }

}


We'll also cater for if stage is 6. But this is just a placeholder; leave it alone for now because we have other cases we do need to handle.

src/components/Game/Game.js
if (stage === 0) {
  return (
    <div id="Game">

    </div>        
  );  
}

if (stage === 6) {

}


This next If block is probably redundant, but let's do it anyway. If stage is a value between 1 to 5 inclusive, we return the Main div.
src/components/Game/Game.js
if (stage === 0) {
  return (
    <div id="Game">

    </div>        
  );  
}

if (stage === 6) {

}

if (stage >= 1 && stage <= 5) {
  return (
    <div id="Main">

    </div>        
  );
}


Main is styled with full width and a specified height.

src/components/Game/Game.css
#Main {
  width: 100%;
  height: 600px;
}


Add the Opponent div inside Main.

src/components/Game/Game.js
if (stage >= 1 && stage <= 5) {
  return (
    <div id="Main">
      <div id="Opponent">

      </div>

    </div>        
  );
}


Style it this way. It's supposed to be a tall div with rounded corners occupying the entire left side of Main. There will be a background specified, but dynamically.

src/components/Game/Game.css
#Main {
  width: 100%;
  height: 600px;
}

#Opponent {
  width: 290px;
  height: 540px;
  float: left;
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
  border-radius: 30px;
}


There it is!


We want the class to be the string returned by GetOpponentImage(). stage and opponentIntoxication are passed in as arguments. You know what stage is, but what's opponentIntoxication? It's a value from 0 to 100; it starts at 100. In essence, it represents the "drunkenness" of the girl. Each time the girl loses, she takes a drink and the level goes down.  Once it reaches 0, the user has won the game. But the image of the girl also changes depending on the value of opponentIntoxication.

src/components/Game/Game.js
if (stage >= 1 && stage <= 5) {
  return (
    <div id="Main">
      <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>

      </div>
    </div>        
  );
}


In the CSS, we prepare CSS class strings that have different image URLs. These images should exist in the image directory. You can get those images here.

src/components/Game/Game.css
#Opponent {
  width: 290px;
  height: 540px;
  float: left;
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
  border-radius: 30px;
}

.opponent1_75 {
  background-image: url(../../img/opponent1_75.png);
}

.opponent1_50 {
  background-image: url(../../img/opponent1_50.png);
}

.opponent1_25 {
  background-image: url(../../img/opponent1_25.png);
}

.opponent1_0 {
  background-image: url(../../img/opponent1_0.png);
}

.opponent2_75 {
  background-image: url(../../img/opponent2_75.png);
}

.opponent2_50 {
  background-image: url(../../img/opponent2_50.png);
}

.opponent2_25 {
  background-image: url(../../img/opponent2_25.png);
}

.opponent2_0 {
  background-image: url(../../img/opponent2_0.png);
}

.opponent3_75 {
  background-image: url(../../img/opponent3_75.png);
}

.opponent3_50 {
  background-image: url(../../img/opponent3_50.png);
}

.opponent3_25 {
  background-image: url(../../img/opponent3_25.png);
}

.opponent3_0 {
  background-image: url(../../img/opponent3_0.png);
}

.opponent4_75 {
  background-image: url(../../img/opponent4_75.png);
}

.opponent4_50 {
  background-image: url(../../img/opponent4_50.png);
}

.opponent4_25 {
  background-image: url(../../img/opponent4_25.png);
}

.opponent4_0 {
  background-image: url(../../img/opponent4_0.png);
}

.opponent5_75 {
  background-image: url(../../img/opponent5_75.png);
}

.opponent5_50 {
  background-image: url(../../img/opponent5_50.png);
}

.opponent5_25 {
  background-image: url(../../img/opponent5_25.png);
}

.opponent5_0 {
  background-image: url(../../img/opponent5_0.png);
}


GetOpponentImage() is actually a function we will define... in a utility we create. Create this file and save it in utils. We define the function here and export it.

src/utils/GetOpponentImage.js
const GetOpponentImage = (stage, intoxication) => {
  
}

export default GetOpponentImage;


And, using a series of If blocks, we return the CSS string based on the value of stage and intoxication.

src/utils/GetOpponentImage.js
const GetOpponentImage = (stage, intoxication) => {
  if (intoxication === 0) {
    return "opponent" + stage + "_0";
  }

  if (intoxication > 0 && intoxication <= 25) {
    return "opponent" + stage + "_25";
  }  

  if (intoxication > 25 && intoxication <= 50) {
    return "opponent" + stage + "_50";
  }  

  if (intoxication > 50) {
    return "opponent" + stage + "_75";
  }  

}

export default GetOpponentImage;


At the beginning of the Gamejs file, make sure we import the utility.

src/components/Game/Game.js
import React, { useState } from 'react';
import './Game.css';

import GetOpponentImage from '../../utils/GetOpponentImage';

function Game(props) {


Because the default value of opponentIntoxication is currently 100 and stage is 1, the CSS class is opponent_1_75. And this is the image we use.


What if we changed this?

src/components/Game/Game.js
const [opponentIntoxication, setOpponentIntoxication] = useState(20);


Because the default value of opponentIntoxication is currently 20 and stage is 1, the CSS class is opponent_1_25. Looks pretty drunk, I think! Remember to change the value back to 100.


In this div, we want to add a button. If gameStarted is true, we style it using btnQuit and actionButton (remember we defined this CSS class earlier?) and if false, we style it using hidden. Probably unnecessary, but can't hurt.

src/components/Game/Game.js
<div id="Main">
  <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>
    <button className={ gameStarted ? "btnQuit actionButton" : "hidden" } }></button>
  </div>
</div>  


For the text, we add a HTML icon, and before that, we use GetLabels() to get a string.

src/components/Game/Game.js
<div id="Main">
  <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>
    <button className={ gameStarted ? "btnQuit actionButton" : "hidden" } }>{ GetLabels("quit", lang) } &#9650;</button>
  </div>
</div>  


Should go without saying that we need to add to the GetLabels utility
.
src/utils/GetLabels.js
let labels = [
  { labelName: "start", lang: "en", value: "BEGIN"},
  { labelName: "start", lang: "cn", value: "开始"},
  { labelName: "quit", lang: "en", value: "Quit"},
  { labelName: "quit", lang: "cn", value: "退出"},

  { labelName: "language", lang: "en", value: "Language"},
  { labelName: "language", lang: "cn", value: "语言"},
  { labelName: "dialogSpeed", lang: "en", value: "Dialog Speed"},
  { labelName: "dialogSpeed", lang: "cn", value: "对话速度"},
  { labelName: "fast", lang: "en", value: "Fast"},
  { labelName: "fast", lang: "cn", value: "快"},
  { labelName: "medium", lang: "en", value: "Medium"},
  { labelName: "medium", lang: "cn", value: "中"},
  { labelName: "slow", lang: "en", value: "Slow"},
  { labelName: "slow", lang: "cn", value: "慢"},
  { labelName: "stage1", lang: "en", value: "STAGE ONE"},
  { labelName: "stage1", lang: "cn", value: "第一关"},
  { labelName: "stage2", lang: "en", value: "STAGE TWO"},
  { labelName: "stage2", lang: "cn", value: "第二关"},
  { labelName: "stage3", lang: "en", value: "STAGE THREE"},
  { labelName: "stage3", lang: "cn", value: "第三关"},
  { labelName: "stage4", lang: "en", value: "STAGE FOUR"},
  { labelName: "stage4", lang: "cn", value: "第四关"},
  { labelName: "stage5", lang: "en", value: "STAGE FIVE"},
  { labelName: "stage5", lang: "cn", value: "第五关"},
  { labelName: "opponent1", lang: "en", value: "LITTLE GRASS"},
  { labelName: "opponent1", lang: "cn", value: "小草"},
  { labelName: "opponent2", lang: "en", value: "CHANEL"},
  { labelName: "opponent2", lang: "cn", value: "CHANEL"},
  { labelName: "opponent3", lang: "en", value: "LULU"},
  { labelName: "opponent3", lang: "cn", value: "鹿鹿"},
  { labelName: "opponent4", lang: "en", value: "GONGSUN LAN"},
  { labelName: "opponent4", lang: "cn", value: "公孙蓝"},
  { labelName: "opponent5", lang: "en", value: "BIG SISTER SPRING"},
  { labelName: "opponent5", lang: "cn", value: "春姐"}
];


And if we're going to use GetLabels(), don't forget to import it!

src/components/Game/Game.js
import React, { useState } from 'react';
import './Game.css';

import GetOpponentImage from '../../utils/GetOpponentImage';
import GetLabels from '../../utils/GetLabels';

function Game(props) {


Use the onClick attribute to run quit() when this button is clicked.

src/components/Game/Game.js
<div id="Main">
  <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>
    <button className={ gameStarted ? "btnQuit actionButton" : "hidden" }  onClick={ ()=>{ quit(); } }>{ GetLabels("quit", lang) } &#9650;</button>
  </div>
</div>  


Add this function. In it, we use the setters to set stage, roundStarted, stageStarted and gameStarted to their initial values.

src/components/Game/Game.js
if (stage === 6) {

}

function quit() {
  setStage(0);
  setRoundStarted(false);
  setStageStarted(false);
  setGameStarted(false);
}


if (stage >= 1 && stage <= 5) {
  return (
    <div id="Main">
    <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>
      <button className={ gameStarted ? "btnQuit actionButton" : "hidden" }  onClick={ ()=>{ quit(); } }>{ GetLabels("quit", lang) } &#9650;</button>
    </div>
    </div>        
  );
}


Finally, the styling for btnQuit. Just some positioning.

src/components/Game/Game.css
.opponent5_0 {
  background-image: url(../../img/opponent5_0.png);
}

.btnQuit {
  margin: 20px auto 0 20px;
}


I've changed the language to English to make it more obvious. Do you see the Quit button? And if you click it does it send the game back to the intro page?


Great! Now let's add add some flavor to this. We want the div OpponentIntro here. In it, have a paragraph tag and a button.

src/components/Game/Game.js
<div id="Main">
  <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>
    <button className={ gameStarted ? "btnQuit actionButton" : "hidden" }  onClick={ ()=>{ quit(); } }>{ GetLabels("quit", lang) } &#9650;</button>
  </div>
  
  <div id="OpponentIntro" className={ stageStarted ? "hidden" : "" }>
    <p></p>
    <button></button>
  </div>  
          
</div>  


Style OpponentIntro like so. It's a box on the right side of the screen, with an enlarged font size and white text.

src/components/Game/Game.css
.opponent5_0 {
  background-image: url(../../img/opponent5_0.png);
}

#OpponentIntro {
  width: 400px;
  height: 400px;
  margin: 100px 100px 0 auto;
  float: right;
  text-align: center;
  font-size: 3em;
  color: rgb(255, 255, 255);
}


.btnQuit {
  margin: 20px auto 0 20px;
}


The button is styled using btnStartStage and actionButton. We set it to run startStage() when clicked, and for its text label, we use GetLabels() with a HTML icon.

src/components/Game/Game.js
<div id="Main">
  <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>
    <button className={ gameStarted ? "btnQuit actionButton" : "hidden" }  onClick={ ()=>{ quit(); } }>{ GetLabels("quit", lang) } &#9650;</button>
  </div>
  
  <div id="OpponentIntro" className={ stageStarted ? "hidden" : "" }>
    <p></p>
    <button className="btnStartStage actionButton" onClick={ ()=>{ startStage(); } }>{ GetLabels("startstage", lang) } &#9658;</button>
  </div>            
</div>  


This is the styling for the button. Again, it's just positioning.
src/components/Game/Game.css
.opponent5_0 {
  background-image: url(../../img/opponent5_0.png);
}

#OpponentIntro {
  width: 400px;
  height: 400px;
  margin: 100px 100px 0 auto;
  float: right;
  text-align: center;
  font-size: 3em;
  color: rgb(255, 255, 255);
}

.btnStartStage {
  margin: 50px auto 0 auto;
}


.btnQuit {
  margin: 20px auto 0 20px;
}


Add the labels for "startstage".

src/utils/GetLabels.js
let labels = [
  { labelName: "start", lang: "en", value: "BEGIN"},
  { labelName: "start", lang: "cn", value: "开始"},
  { labelName: "startstage", lang: "en", value: "Start Stage"},
  { labelName: "startstage", lang: "cn", value: "开始"},

  { labelName: "quit", lang: "en", value: "Quit"},
  { labelName: "quit", lang: "cn", value: "退出"},
  { labelName: "language", lang: "en", value: "Language"},
  { labelName: "language", lang: "cn", value: "语言"},
  { labelName: "dialogSpeed", lang: "en", value: "Dialog Speed"},
  { labelName: "dialogSpeed", lang: "cn", value: "对话速度"},
  { labelName: "fast", lang: "en", value: "Fast"},
  { labelName: "fast", lang: "cn", value: "快"},
  { labelName: "medium", lang: "en", value: "Medium"},
  { labelName: "medium", lang: "cn", value: "中"},
  { labelName: "slow", lang: "en", value: "Slow"},
  { labelName: "slow", lang: "cn", value: "慢"},
  { labelName: "stage1", lang: "en", value: "STAGE ONE"},
  { labelName: "stage1", lang: "cn", value: "第一关"},
  { labelName: "stage2", lang: "en", value: "STAGE TWO"},
  { labelName: "stage2", lang: "cn", value: "第二关"},
  { labelName: "stage3", lang: "en", value: "STAGE THREE"},
  { labelName: "stage3", lang: "cn", value: "第三关"},
  { labelName: "stage4", lang: "en", value: "STAGE FOUR"},
  { labelName: "stage4", lang: "cn", value: "第四关"},
  { labelName: "stage5", lang: "en", value: "STAGE FIVE"},
  { labelName: "stage5", lang: "cn", value: "第五关"},
  { labelName: "opponent1", lang: "en", value: "LITTLE GRASS"},
  { labelName: "opponent1", lang: "cn", value: "小草"},
  { labelName: "opponent2", lang: "en", value: "CHANEL"},
  { labelName: "opponent2", lang: "cn", value: "CHANEL"},
  { labelName: "opponent3", lang: "en", value: "LULU"},
  { labelName: "opponent3", lang: "cn", value: "鹿鹿"},
  { labelName: "opponent4", lang: "en", value: "GONGSUN LAN"},
  { labelName: "opponent4", lang: "cn", value: "公孙蓝"},
  { labelName: "opponent5", lang: "en", value: "BIG SISTER SPRING"},
  { labelName: "opponent5", lang: "cn", value: "春姐"}
];


Create the function startStage().

src/components/Game/Game.js
function quit() {
  setStage(0);
  setRoundStarted(false);
  setStageStarted(false);
  setGameStarted(false);
}

const startStage = function() {

};

if (stage >= 1 && stage <= 5) {


In here, we use the setters to set certain values to default values. round is 1, because stage starts at round 1. And stageStarted is true.

src/components/Game/Game.js
function quit() {
  setStage(0);
  setRoundStarted(false);
  setStageStarted(false);
  setGameStarted(false);
}

const startStage = function() {
  setPlayerIntoxication(100);
  setOpponentIntoxication(100);
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setOpponentDice([1, 1, 1, 1, 1]);
  setPlayerDice([1, 1, 1, 1, 1]);
  setRound(1);
  setTurns(0);
  setShow(false);
  setStageStarted(true);

};

if (stage >= 1 && stage <= 5) {


There's the button. Don't click it yet.


We're going to add some intro text above the button. Use getPhrases() and pass stage, the string "intro" and lang in as arguments.

src/components/Game/Game.js
<div id="Main">
  <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>
    <button className={ gameStarted ? "btnQuit actionButton" : "hidden" }  onClick={ ()=>{ quit(); } }>{ GetLabels("quit", lang) } &#9650;</button>
  </div>
  
  <div id="OpponentIntro" className={ stageStarted ? "hidden" : "" }>
    <p>"{ GetPhrases(stage, "intro", lang) }"</p>
    <button className="btnStartStage actionButton" onClick={ ()=>{ startStage(); } }>{ GetLabels("startstage", lang) } &#9658;</button>
  </div>            
</div>  


We're going to create the utility GetPhrases next. These are the phrases that the opponents will use in the course of the game. Create the file in the utils directory. Similar to GetLabels, you will create the function and export it. The parameters are personality, phraseName and lang.

src/utils/GetPhrases.js
const GetPhrases = (personality, phraseName, lang) => {

}

export default GetPhrases;  


Create the phrases array.

src/utils/GetPhrases.js
const GetPhrases = (personality, phraseName, lang) => {
  let phrases = [

  ];

}

export default GetPhrases;  


Now, we will have the content. Each element in phrases has personality, phraseName, lang and value properties. personality refers to the opponent who is currently "talking". You have different values for value based on lang.

src/utils/GetPhrases.js
const GetPhrases = (personality, phraseName, lang) => {
  let phrases = [
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. Please go easy on me!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 请高抬贵手!"},
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. I'm new at this. Pease show me the ropes!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 我对这游戏不是很熟. 请多指教!"}

  ];
}

export default GetPhrases;  


As with GetLabels, we define match and run the filter() method on phrases, assigning the value to match. We filter by comparing the phraseName, lang and personality properties to their equivalents in the parameters.

src/utils/GetPhrases.js
const GetPhrases = (personality, phraseName, lang) => {
  let phrases = [
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. Please go easy on me!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 请高抬贵手!"},
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. I'm new at this. Pease show me the ropes!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 我对这游戏不是很熟. 请多指教!"}
  ];

  let match = phrases.filter((x)=> { return (x.phraseName === phraseName && x.lang === lang && (x.personality == null || x.personality === personality)); });

}

export default GetPhrases;  


There will be multiple matches; at least two. But just in case, return an empty string if there are no matches.

src/utils/GetPhrases.js
const GetPhrases = (personality, phraseName, lang) => {
  let phrases = [
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. Please go easy on me!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 请高抬贵手!"},
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. I'm new at this. Pease show me the ropes!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 我对这游戏不是很熟. 请多指教!"}
  ];

  let match = phrases.filter((x)=> { return (x.phraseName === phraseName && x.lang === lang && (x.personality == null || x.personality === personality)); });

  if (match.length === 0) return "";
  if (match.length > 1) {

  }

}

export default GetPhrases;  


And if there's an array of elements, we use a random function to choose between the available matches, then return that choice's value property.

src/utils/GetPhrases.js
const GetPhrases = (personality, phraseName, lang) => {
  let phrases = [
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. Please go easy on me!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 请高抬贵手!"},
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. I'm new at this. Pease show me the ropes!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 我对这游戏不是很熟. 请多指教!"}
  ];

  let match = phrases.filter((x)=> { return (x.phraseName === phraseName && x.lang === lang && (x.personality == null || x.personality === personality)); });

  if (match.length === 0) return "";
  if (match.length > 1) {
    var r = Math.floor(Math.random() * match.length);
    return match[r].value;

  }
}

export default GetPhrases;  


And if somehow none of that stuck, we just return the first element.

src/utils/GetPhrases.js
const GetPhrases = (personality, phraseName, lang) => {
  let phrases = [
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. Please go easy on me!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 请高抬贵手!"},
    { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. I'm new at this. Pease show me the ropes!"},
    { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 我对这游戏不是很熟. 请多指教!"}
  ];

  let match = phrases.filter((x)=> { return (x.phraseName === phraseName && x.lang === lang && (x.personality == null || x.personality === personality)); });

  if (match.length === 0) return "";
  if (match.length > 1) {
    var r = Math.floor(Math.random() * match.length);
    return match[r].value;
  }

  return match[0].value;
}

export default GetPhrases;  


Remember to import the utility.

src/components/Game/Game.js
import React, { useState } from 'react';
import './Game.css';

import GetOpponentImage from '../../utils/GetOpponentImage';
import GetLabels from '../../utils/GetLabels';
import GetPhrases from '../../utils/GetPhrases';

function Game(props) {


The text appears!


Starting the stage

Here, we add the Game div that will be hidden if stageStarted is false. We added a Game div earlier, but that was empty, so styling was unnecessary.

src/components/Game/Game.js
<div id="Main">
  <div id="Opponent" className={ GetOpponentImage(stage, opponentIntoxication) }>
    <button className={ gameStarted ? "btnQuit actionButton" : "hidden" }  onClick={ ()=>{ quit(); } }>{ GetLabels("quit", lang) } &#9650;</button>
  </div>
  
  <div id="OpponentIntro" className={ stageStarted ? "hidden" : "" }>
    <p>"{ GetPhrases(stage, "intro", lang) }"</p>
    <button className="btnStartStage actionButton" onClick={ ()=>{ startStage(); } }>{ GetLabels("startstage", lang) } &#9658;</button>
  </div>  

  <div id="Game" className={ stageStarted ? "" : "hidden" }>
        
  </div> 
       
</div>  


We will need to style it now. Just a box on the right side of the screen.

src/components/Game/Game.css
#OpponentIntro {
  width: 400px;
  height: 400px;
  margin: 100px 100px 0 auto;
  float: right;
  text-align: center;
  font-size: 3em;
  color: rgb(255, 255, 255);
}

#Game {
  width: 550px;
  height: 540px;
  float: right;
}


.btnStartStage {
  margin: 50px auto 0 auto;
}


More layout coming up! Put four divs in there, each styled using GameRow.

src/components/Game/Game.js
<div id="Game" className={ stageStarted ? "" : "hidden" }>
  <div className="GameRow">

  </div>

  <div className="GameRow">

  </div>  

  <div className="GameRow">

  </div>  

  <div className="GameRow">

  </div>   
   
</div>


GameRow takes up full width and has a very specific height, a quarter of Game's.

src/components/Game/Game.css
#Game {
  width: 550px;
  height: 540px;
  float: right;
}

.GameRow {
  width: 550px;
  height: 135px;
  float: left;
}


.btnStartStage {
  margin: 50px auto 0 auto;
}


These four rows are important. The upper two are for rendering the opponent's portion and the lower two are for the player.


Add these divs in, two divs each in the divs styled using GameRow. Depending on which row it is, the two divs will be floated left and right, with varying widths.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_long">

  </div>  

  <div className="right width_short">

  </div>

</div>

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>  

</div>  

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>

</div>  

<div className="GameRow">
  <div className="left width_long">

  </div>  

  <div className="right width_short">

  </div>

</div>      


On the first and last row, in the first nested divs styled using left and width_long, add a div and style it using the speechballoon CSS class. Add some test text.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_long">
    <div className="speechballoon">
      test
    </div>

  </div>  

  <div className="right width_short">

  </div>
</div>

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>  
</div>  

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>
</div>  

<div className="GameRow">
  <div className="left width_long">
    <div className="speechballoon">
      test
    </div>

  </div>  

  <div className="right width_short">

  </div>
</div>  


This is the styling. We want  a grey rectangle with ground corners and white text. Text is also aligned center.

src/components/Game/Game.css
.GameRow {
  width: 550px;
  height: 135px;
  float: left;
}

.speechballoon {
  width: 350px;
  height: 100px;
  padding: 10px;
  font-size: 16px;
  text-align: center;
  float: left;
  border-radius: 10px;
  background-color: rgb(50, 50, 50);
  color: rgb(255, 255, 255);
}


.btnStartStage {
  margin: 50px auto 0 auto;
}


After tht, we define a pseudoselector, after, for speechballoon. It's a grey right-pointing arrow that sticks out at the right side of its parent.

src/components/Game/Game.css
.GameRow {
  width: 550px;
  height: 135px;
  float: left;
}

.speechballoon {
  width: 350px;
  height: 100px;
  padding: 10px;
  font-size: 16px;
  text-align: center;
  float: left;
  border-radius: 10px;
  background-color: rgb(50, 50, 50);
  color: rgb(255, 255, 255);
}

.speechballoon::before {
  position: absolute;
  margin: 0 0px 0px 350px;
  display: block;
  width: 20px;
  font-size: 2em;
  content: "▶";
  color: rgb(50, 50, 50);
}


.btnStartStage {
  margin: 50px auto 0 auto;
}


There be the speech balloons.


And in their sibling divs, we add divs that are styled using the portrait CSS class. In the first row, the div is also styled using the string returned by calling GetOpponentImage(), with the current value of stage and a value of 100 passed in as arguments. The bottom row is styled using player as well as portrait.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_long">
    <div className="speechballoon">
      test
    </div>
  </div>  

  <div className="right width_short">
    <div className={ "portrait " + GetOpponentImage(stage, 100) }></div>
  </div>
</div>

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>  
</div>  

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>
</div>  

<div className="GameRow">
  <div className="left width_long">
    <div className="speechballoon">
      test
    </div>
  </div>  

  <div className="right width_short">
    <div className="portrait player"></div>
  </div>
</div>  


We'll be using this image...
(img)

Here are the stylings for portrait and player. player basically just makes the background image the image above. And portrait is just a small circular div that features an unrepeated background image.

src/components/Game/Game.css
.opponent5_0 {
  background-image: url(../../img/opponent5_0.png);
}

.player {
  background-image: url(../../img/player.png);
}


#OpponentIntro {
  width: 400px;
  height: 400px;
  margin: 100px 100px 0 auto;
  float: right;
  text-align: center;
  font-size: 3em;
  color: rgb(255, 255, 255);
}

#Game {
  width: 550px;
  height: 540px;
  float: right;
}

.GameRow {
  width: 550px;
  height: 135px;
  float: left;
}

.portrait {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  margin: 0 auto 0 auto;
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
}


.speechballoon {
  width: 350px;
  height: 100px;
  padding: 10px;
  font-size: 16px;
  text-align: center;
  float: left;
  border-radius: 10px;
  background-color: rgb(50, 50, 50);
  color: rgb(255, 255, 255);
}


Nice!


We will now add divs, styled using meter, beneath the portraits.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_long">
    <div className="speechballoon">
      test
    </div>
  </div>  

  <div className="right width_short">
    <div className={ "portrait " + GetOpponentImage(stage, 100) }></div>
    <br />
    <div className="meter">

    </div>

  </div>
</div>

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>  
</div>  

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>
</div>  

<div className="GameRow">
  <div className="left width_long">
    <div className="speechballoon">
      test
    </div>
  </div>  

  <div className="right width_short">
    <div className="portrait player"></div>
    <br />
    <div className="meter">

    </div>

  </div>
</div>        


And in them, we add a div that is styled using metervalue, and a value derived from appending "metercolor_" with the string value returned by running the getMeterColor() function with either opponentIntoxication or playerIntoxication, depending on the row, as an argument. Lastly, note that the margin-left property has been set to a negative value; the value being 100 less opponentIntoxication or playerIntoxication, again depending on the row. What this is supposed to accomplish, is a bar that acts like a value indicator of opponentIntoxication or playerIntoxication.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_long">
    <div className="speechballoon">
      test
    </div>
  </div>  

  <div className="right width_short">
    <div className={ "portrait " + GetOpponentImage(stage, 100) }></div>
    <br />
    <div className="meter">
      <div className={ "metervalue metercolor_" + getMeterColor(opponentIntoxication) } style={{ marginLeft: "-" + (100 - opponentIntoxication) + "px" }}></div>
    </div>
  </div>
</div>

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>  
</div>  

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>
</div>  

<div className="GameRow">
  <div className="left width_long">
    <div className="speechballoon">
      test
    </div>
  </div>  

  <div className="right width_short">
    <div className="portrait player"></div>
    <br />
    <div className="meter">
      <div className={ "metervalue metercolor_" + getMeterColor(playerIntoxication) }  style={{ marginLeft: "-" + (100 - playerIntoxication) + "px" }}></div>
    </div>
  </div>
</div>          


Style it! meter has a height and width, round corners and is set to the middle of its parent, with a translucent white border. Those are aesthetic choices; what's truly important is that the overflow property is set to hidden.

src/components/Game/Game.css
.portrait {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  margin: 0 auto 0 auto;
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
}

.meter {
  width: 100px;
  height: 15px;
  margin: 0 auto 0 auto;
  border-radius: 5px;
  border: 2px solid rgba(255, 255, 255, 0.8);
  overflow: hidden;
}


.speechballoon {
  width: 350px;
  height: 100px;
  padding: 10px;
  font-size: 16px;
  text-align: center;
  float: left;
  border-radius: 10px;
  background-color: rgb(50, 50, 50);
  color: rgb(255, 255, 255);
}


metervalue has full height and width of its parent, meter, and is floated left. That's straightforward.

src/components/Game/Game.css
.portrait {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  margin: 0 auto 0 auto;
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
}

.meter {
  width: 100px;
  height: 15px;
  margin: 0 auto 0 auto;
  border-radius: 5px;
  border: 2px solid rgba(255, 255, 255, 0.8);
  overflow: hidden;
}

.metervalue {
  width: 100%;
  height: 100%;
  float: left;
}


.speechballoon {
  width: 350px;
  height: 100px;
  padding: 10px;
  font-size: 16px;
  text-align: center;
  float: left;
  border-radius: 10px;
  background-color: rgb(50, 50, 50);
  color: rgb(255, 255, 255);
}


Now pause for a bit. We'll define the getMeterColor() function.  It's simple enough that we won't need to make a utility for this. By default, we return "low". Two other If blocks will return other values depending on val.

src/components/Game/Game.js
const startStage = function() {
  setPlayerIntoxication(100);
  setOpponentIntoxication(100);
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setOpponentDice([1, 1, 1, 1, 1]);
  setPlayerDice([1, 1, 1, 1, 1]);
  setRound(1);
  setTurns(0);
  setShow(false);
  setStageStarted(true);
};

const getMeterColor = function(val) {
  if (val > 80) return "high";
  if (val > 50) return "half";
  return "low";
};


if (stage >= 1 && stage <= 5) {


And here are the styles.

src/components/Game/Game.css
.metervalue {
  width: 100%;
  height: 100%;
  float: left;
}

.metercolor_high {
  background-color: rgba(255, 200, 0, 0.8);
}

.metercolor_half {
  background-color: rgba(200, 100, 0, 0.8);
}

.metercolor_low {
  background-color: rgba(100, 0, 0, 0.8);
}


.speechballoon {
  width: 350px;
  height: 100px;
  padding: 10px;
  font-size: 16px;
  text-align: center;
  float: left;
  border-radius: 10px;
  background-color: rgb(50, 50, 50);
  color: rgb(255, 255, 255);
}


Here are your meters, at full value.


Try this! At the startStage() function, change the initial values for these function calls.
src/components/Game/Game.js
const startStage = function() {
  setPlayerIntoxication(60);
  setOpponentIntoxication(20);
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setOpponentDice([1, 1, 1, 1, 1]);
  setPlayerDice([1, 1, 1, 1, 1]);
  setRound(1);
  setTurns(0);
  setShow(false);
  setStageStarted(true);
};


Now, restart the app and click through. See how the values of 20, and 60 are represented! Also see how the image changes at the left now that the opponentIntoxication value is 20!


Change the values back. We'll now work on populating the speech balloons. Add this to the startStage() function, to set opponentDialog. opponentDialog is essentially the text that appears in the top speech balloon. For this, we will derive the value by running GetPhrases(). The phrase type will be "newround".

src/components/Game/Game.js
const startStage = function() {
  setPlayerIntoxication(100);
  setOpponentIntoxication(100);
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setOpponentDice([1, 1, 1, 1, 1]);
  setPlayerDice([1, 1, 1, 1, 1]);
  setRound(1);
  setTurns(0);
  setShow(false);
  setStageStarted(true);

  setOpponentDialog(GetPhrases(stage, "newround", lang));
};


In the GetPhrases utility, we add "newround".

src/utils/GetPhrases.js
let phrases = [
  { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. Please go easy on me!"},
  { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 请高抬贵手!"},
  { personality: 1, phraseName: "intro", lang: "en", value: "Hello, I'm Little Grass. I'm new at this. Pease show me the ropes!"},
  { personality: 1, phraseName: "intro", lang: "cn", value: "你好, 我是小草. 我对这游戏不是很熟. 请多指教!"},
  { personality: 1, phraseName: "newround", lang: "en", value: "I'm so excited. Let's start!"},
  { personality: 1, phraseName: "newround", lang: "cn", value: "好兴奋! 开始吧!"},
  { personality: 1, phraseName: "newround", lang: "en", value: "Please go slow!"},
  { personality: 1, phraseName: "newround", lang: "cn", value: "慢点哦!"}

];  


For the first speech balloon, we implement a conditional block for determining the class. Instead of just straight out defining it as speechballoon, we first check if opponentDialog is an empty string, and hide the div if so. Otherwise, we style it using speechballoon.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_long">
    <div className={ (opponentDialog === "" ? "hidden" : "speechballoon") }>
      test
    </div>
  </div>  

  <div className="right width_short">
    <div className={ "portrait " + GetOpponentImage(stage, 100) }></div>
    <br />
    <div className="meter">
      <div className={ "metervalue metercolor_" + getMeterColor(opponentIntoxication) } style={{ marginLeft: "-" + (100 - opponentIntoxication) + "px" }}></div>
    </div>
  </div>
</div>


And we replace the test string with opponentDialog.

src/components/Game/Game.js
<div className={ (opponentDialog === "" ? "hidden" : "speechballoon") }>
  { opponentDialog }
</div>


Restart the server and click through till you get to this screen. It's there!


Now we'll populate the bottom speech balloon. This is where we place the controls for the user. Start wth three buttons, styled using the actionButton CSS clsss.

src/components/Game/Game.js
<div className="left width_long">
  <div className="speechballoon">
    <button className="actionButton">{ GetLabels("endround", lang) } &#9673;</button>
    <button className="actionButton">{ GetLabels("startnewround", lang) } &#9658;</button>
    <button className="actionButton">{ GetLabels("restartstage", lang) } &#9658;</button>
  </div>
</div>  


Add these labels in the GetLabels utility's labels array.

src/utils/GetLabels.js
{ labelName: "start", lang: "en", value: "BEGIN"},
{ labelName: "start", lang: "cn", value: "开始"},
{ labelName: "startstage", lang: "en", value: "Start Stage"},
{ labelName: "startstage", lang: "cn", value: "开始"},
{ labelName: "restartstage", lang: "en", value: "Restart Stage"},
{ labelName: "restartstage", lang: "cn", value: "重新开始"},
{ labelName: "endround", lang: "en", value: "End Round"},
{ labelName: "endround", lang: "cn", value: "结束"},
{ labelName: "startnewround", lang: "en", value: "Start New Round"},
{ labelName: "startnewround", lang: "cn", value: "新轮开始"},

{ labelName: "quit", lang: "en", value: "Quit"},
{ labelName: "quit", lang: "cn", value: "退出"},
{ labelName: "language", lang: "en", value: "Language"},
{ labelName: "language", lang: "cn", value: "语言"},


And here be the buttons.


Now add this div layout. We first have a div with id playerDashboard. I'm using this id because it makes it easier to refer to as we go along.

src/components/Game/Game.js
<div className="left width_long">
  <div className="speechballoon">
    <div id="playerDashboard">

    </div>


    <button className="actionButton">{ GetLabels("endround", lang) } &#9673;</button>
    <button className="actionButton">{ GetLabels("startnewround", lang) } &#9658;</button>
    <button className="actionButton">{ GetLabels("restartstage", lang) } &#9658;</button>
  </div>
</div>  


Add guessDashboard inside playerDashboard.

src/components/Game/Game.js
<div className="left width_long">
  <div className="speechballoon">
    <div id="playerDashboard">
      <div id="guessDashboard">

      </div>

    </div>

    <button className="actionButton">{ GetLabels("endround", lang) } &#9673;</button>
    <button className="actionButton">{ GetLabels("startnewround", lang) } &#9658;</button>
    <button className="actionButton">{ GetLabels("restartstage", lang) } &#9658;</button>
  </div>
</div>  


And in guessDashboard, there's a series of nested divs for the content that we're about to add. You can see from the use of left, right, width_half, width_long and width_short, what the layout is supposed to look like.

src/components/Game/Game.js
<div className="left width_long">
  <div className="speechballoon">
    <div id="playerDashboard">
      <div id="guessDashboard">
        <div className="left width_long">
          <div className="left width_half">

          </div>

          <div className="right width_half">

          </div>
        </div>
        <div className="right width_short">

        </div>

      </div>
    </div>

    <button className="actionButton">{ GetLabels("endround", lang) } &#9673;</button>
    <button className="actionButton">{ GetLabels("startnewround", lang) } &#9658;</button>
    <button className="actionButton">{ GetLabels("restartstage", lang) } &#9658;</button>
  </div>
</div>  


Here, we add two more divs, with the left one longer than the right one. The left one also has the guessQty CSS class, and the right one has guessButtons.

src/components/Game/Game.js
<div id="guessDashboard">
  <div className="left width_long">
    <div className="left width_half">
      <div className="guessQty left width_long">

      </div>
      <div className="guessButtons right width_short">

      </div>

    </div>

    <div className="right width_half">

    </div>
  </div>
  <div className="right width_short">

  </div>
</div>


Add these classes into the CSS file. guessQty only specifies font properties. We want the number to be nice and big. For guessButtons, we just want the buttons to be aligned left. While we're here, let's specify that buttons in guessButtons will be styled this way - as small squares.

src/components/Game/Game.js
.speechballoon::before {
  position: absolute;
  margin: 0 0px 0px 350px;
  display: block;
  width: 20px;
  font-size: 2em;
  content: "▶";
  color: rgb(50, 50, 50);
}

.guessQty {
  font-weight: bold;
  font-size: 2.5em;
  text-align: right;
}

.guessButtons {
  text-align: left;
}

.guessButtons button {
  width: 8px;
  height: 8px;
  font-size: 1.2em;
  background: none;
  border: none;
  color: rgb(255, 255, 255);
}


.btnStartStage {
  margin: 50px auto 0 auto;
}


Then we add the value of playerGuessQty as text inside the first div.

src/components/Game/Game.js
<div id="guessDashboard">
  <div className="left width_long">
    <div className="left width_half">
      <div className="guessQty left width_long">
        { playerGuessQty }
      </div>
      <div className="guessButtons right width_short">

      </div>
    </div>

    <div className="right width_half">

    </div>
  </div>
  <div className="right width_short">

  </div>
</div>


And add two buttons in here, with icons that indicate Up and Down.

src/components/Game/Game.js
<div id="guessDashboard">
  <div className="left width_long">
    <div className="left width_half">
      <div className="guessQty left width_long">
        { playerGuessQty }
      </div>
      <div className="guessButtons right width_short">
        <button>&#9650;</button>
        <br />
        <button>&#9660;</button>

      </div>
    </div>

    <div className="right width_half">

    </div>
  </div>
  <div className="right width_short">

  </div>
</div>


And this is what we get! The buttons are displaced, but that's fine. Not all of them are meant to be visible at the same time anyway.


Time to add more stuff. As with the previous series of divs, we add a long left div and a short right div. For now, we add the value of playerGuessDice in the left div and Up and Down buttons in the right.

src/components/Game/Game.js
<div id="guessDashboard">
  <div className="left width_long">
    <div className="left width_half">
      <div className="guessQty left width_long">
        { playerGuessQty }
      </div>
      <div className="guessButtons right width_short">
        <button>&#9650;</button>
        <br />
        <button>&#9660;</button>
      </div>
    </div>

    <div className="right width_half">
      <div className="left width_long">
      { playerGuessDice }
      </div>
      <div className="guessButtons right width_short">
        <button>&#9650;</button>
        <br />
        <button>&#9660;</button>

      </div>
    </div>
  </div>
  <div className="right width_short">

  </div>
</div>


You'll notice the font of playerGuessDice is much smaller in size, because we haven't styled it.


Finally, here we will add more buttons.

src/components/Game/Game.js
<div id="guessDashboard">
  <div className="left width_long">
    <div className="left width_half">
      <div className="guessQty left width_long">
        { playerGuessQty }
      </div>
      <div className="guessButtons right width_short">
        <button>&#9650;</button>
        <br />
        <button>&#9660;</button>
      </div>
    </div>

    <div className="right width_half">
      <div className="left width_long">
      { playerGuessDice }
      </div>
      <div className="guessButtons right width_short">
        <button>&#9650;</button>
        <br />
        <button>&#9660;</button>
      </div>
    </div>
  </div>
  <div className="right width_short">
    <button className="actionButton">{ GetLabels("guess", lang) }</button>
    <button className="actionButton">{ GetLabels("openup", lang) }</button>

  </div>
</div>


Obviously, we'll also want to add the labels.

src/utils/GetLabels.js
{ labelName: "opponent5", lang: "en", value: "BIG SISTER SPRING"},
{ labelName: "opponent5", lang: "cn", value: "春姐"},
{ labelName: "guess", lang: "en", value: "Guess"},
{ labelName: "guess", lang: "cn", value: "猜"},  
{ labelName: "openup", lang: "en", value: "Open Up!"},
{ labelName: "openup", lang: "cn", value: "开!"}


And here you see two more buttons!


The Dice component

 We next want to create the component Dice. This will be used to visually represent dice values, such as with playerGuessDice. So create the Dice directory in the components directory, and within it, create Dice.js, Dice.css and index.js.

As before we start with the standard stuff.

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

function Dice(props) {

}

export default Dice;


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


Some initial styling tells us that the dice CSS class is a 48 pixel square with round corners, floated right with a buffer of 15 pixels on the left side. That last part is because later on we will have a few dice in a row.

src/components/Dice/Dice.css
.dice {
  width: 48px;
  height: 48px;
  border-radius: 5px;
  float: right;
  margin-left: 15px;
}


We have the dots array. There are six possible values (from 1 to 6), so we have six elements.

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

function Dice(props) {
  var dots = [
    [

    ],
    [

    ],
    [

    ],
    [

    ],
    [

    ],
    [

    ]
  ];

}

export default Dice;  


This should be visually clear. Each of these array elements are themselves arrays. There are nine possible dot positions in the grid, therefore nine elements in each of these arrays. The 0s are empty spaces and the 1s are filled dots on a dice.

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

function Dice(props) {
  var dots = [
    [
      0, 0, 0,
      0, 1, 0,
      0, 0, 0

    ],
    [
      0, 0, 1,
      0, 0, 0,
      1, 0, 0

    ],
    [
      0, 0, 1,
      0, 1, 0,
      1, 0, 0

    ],
    [
      1, 0, 1,
      0, 0, 0,
      1, 0, 1

    ],
    [
      1, 0, 1,
      0, 1, 0,
      1, 0, 1

    ],
    [
      1, 0, 1,
      1, 0, 1,
      1, 0, 1

    ]
  ];
}

export default Dice;  


We enter in some values that will be passed through props. dice is the value shown on the dice, diceIndex is the index of the dice (in cases where multiple dice are shown) and classPrefix determines the color scheme of the dice.

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

function Dice(props) {
  let dice = props.dice;
  let diceIndex = props.diceIndex;
  let classPrefix = props.classPrefix;


  var dots = [
    [
      0, 0, 0,
      0, 1, 0,
      0, 0, 0
    ],
    [
      0, 0, 1,
      0, 0, 0,
      1, 0, 0
    ],
    [
      0, 0, 1,
      0, 1, 0,
      1, 0, 0
    ],
    [
      1, 0, 1,
      0, 0, 0,
      1, 0, 1
    ],
    [
      1, 0, 1,
      0, 1, 0,
      1, 0, 1
    ],
    [
      1, 0, 1,
      1, 0, 1,
      1, 0, 1
    ]
  ];
}

export default Dice;  


At the end of the function, we return the div, styled using dice and classPrefix. The key attribute is determined using classPrefix and diceIndex. We will need this because in the case of multiple dice, React will complain if we call the component multiple times without a key.

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

function Dice(props) {
  let dice = props.dice;
  let diceIndex = props.diceIndex;
  let classPrefix = props.classPrefix;

  var dots = [
    [
      0, 0, 0,
      0, 1, 0,
      0, 0, 0
    ],
    [
      0, 0, 1,
      0, 0, 0,
      1, 0, 0
    ],
    [
      0, 0, 1,
      0, 1, 0,
      1, 0, 0
    ],
    [
      1, 0, 1,
      0, 0, 0,
      1, 0, 1
    ],
    [
      1, 0, 1,
      0, 1, 0,
      1, 0, 1
    ],
    [
      1, 0, 1,
      1, 0, 1,
      1, 0, 1
    ]
  ];

  return <div className={ "dice " + classPrefix } key={ classPrefix + diceIndex }>
  {

  }
  </div>

}

export default Dice;  


Here, we take the element of dots pointed to by dice minus 1. Arrays begin at index 0, thus if, for example, the value is 3, we want the second value in dots, which is index 2. Then we run the map() method. In this, we'll need both the value and the index.

src/components/Dice/Dice.js
return <div className={ "dice " + classPrefix } key={ classPrefix + diceIndex }>
{
  dots[dice - 1].map(function(dot, dotIndex){

  })

}
</div>


We define css as "dot val" wth the value of dot appended. Then we return a div styled using the value of css, and with key and title defined using the permutation of classPrefix, diceIndex and dotIndex.

src/components/Dice/Dice.js
return <div className={ "dice " + classPrefix + " " + (highlight ? "highlighted_dice" : "") } key={ classPrefix + diceIndex }>
{
  dots[dice - 1].map(function(dot, dotIndex){
    var css = "dot val" + dot;

    return <div className={ css } title={ css } key={ classPrefix + diceIndex + "_" + dotIndex }>

    </div>

  })
}
</div>


The CSS for the dice is here. opponentDice and guessDice have the same color scheme, deep grey background and border. playerDice is bone white.

src/components/Dice/Dice.css
.dice {
  width: 48px;
  height: 48px;
  border-radius: 5px;
  float: right;
  margin-left: 15px;
}

.opponentDice, .guessDice {
  background-color: rgb(20, 20, 20);
  border: 3px solid rgb(20, 20, 20);
}

.playerDice {
  background-color: rgb(255, 255, 250);
  border: 3px solid rgb(255, 255, 250);
}


dot is a 16 pixel square in the grid, floated left.

src/components/Dice/Dice.css
.dice {
  width: 48px;
  height: 48px;
  border-radius: 5px;
  float: right;
  margin-left: 15px;
}

.opponentDice, .guessDice {
  background-color: rgb(20, 20, 20);
  border: 3px solid rgb(20, 20, 20);
}

.playerDice {
  background-color: rgb(255, 255, 250);
  border: 3px solid rgb(255, 255, 250);
}

.dot {
  width: 16px;
  height: 16px;
  float: left;
}


Now each div styled using dot will also be styled using val0 or val1. There's no need to style val0 because there's no additional styling required. But for val1, we want a pseudoselector, after. It'll be a circular div set right in the middle of val1.

src/components/Dice/Dice.css
.dice {
  width: 48px;
  height: 48px;
  border-radius: 5px;
  float: right;
  margin-left: 15px;
}

.opponentDice, .guessDice {
  background-color: rgb(20, 20, 20);
  border: 3px solid rgb(20, 20, 20);
}

.playerDice {
  background-color: rgb(255, 255, 250);
  border: 3px solid rgb(255, 255, 250);
}

.dot {
  width: 16px;
  height: 16px;
  float: left;
}

.val1::after {
  display: block;
  content: "";
  margin: 1px 0 0 1px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}


Again, color schemes differ. opponentDicce and guessDice will have light grey dots with grey borders. playerDice will have red dots with brown borders.

src/components/Dice/Dice.css
.dice {
  width: 48px;
  height: 48px;
  border-radius: 5px;
  float: right;
  margin-left: 15px;
}

.opponentDice, .guessDice {
  background-color: rgb(20, 20, 20);
  border: 3px solid rgb(20, 20, 20);
}

.playerDice {
  background-color: rgb(255, 255, 250);
  border: 3px solid rgb(255, 255, 250);
}

.dot {
  width: 16px;
  height: 16px;
  float: left;
}

.val1::after {
  display: block;
  content: "";
  margin: 1px 0 0 1px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

.opponentDice .val1::after, .guessDice .val1::after {
  border: 2px solid rgb(200, 200, 200);
  background-color: rgb(180, 180, 180);
}

.playerDice .val1::after {
  border: 2px solid rgb(180, 0, 0);
  background-color: rgb(100, 0, 0);
}


In here, add in the Dice component, passing in playerGuessDice in the properties. diceIndex will be 0 and classPrefix is guessDice, which means it will be rendered in shades of grey.

src/components/Game/Game.js
<div id="guessDashboard">
  <div className="left width_long">
    <div className="left width_half">
      <div className="guessQty left width_long">
        { playerGuessQty }
      </div>
      <div className="guessButtons right width_short">
        <button>&#9650;</button>
        <br />
        <button>&#9660;</button>
      </div>
    </div>

    <div className="right width_half">
      <div className="left width_long">
        <Dice
          dice = { playerGuessDice }
          diceIndex = "0"
          classPrefix = "guessDice"
        />

      </div>
      <div className="guessButtons right width_short">
        <button>&#9650;</button>
        <br />
        <button>&#9660;</button>
      </div>
    </div>
  </div>
  <div className="right width_short">
    <button className="actionButton">{ GetLabels("guess", lang) }</button>
    <button className="actionButton">{ GetLabels("openup", lang) }</button>
  </div>
</div>


The current value of playerGuessDice is 2, and this is displayed as 2 on the dice!


Populating the middle rows

We're going to showcase the dice of the opponent and player here. Both will have five dice each. Let's start with opponentDice. It's an array, and currently we have populated it with all 1s. So we run the map() method on it. We'll need both the element and the index.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">
      {
        opponentDice.map(function(dice, diceIndex){

        })
      }
  </div>  
</div>  

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>
</div>  


In here, we pass in the Dice component, adding in the element and the index as properties. classPrefix is opponentDice because we want it rendered in greys.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">
      {
        opponentDice.map(function(dice, diceIndex){
          return (
            <Dice
              dice = { dice }
              diceIndex = { diceIndex }
              classPrefix = "opponentDice"
            />
          );
        })
      }
  </div>  
</div>  

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">

  </div>
</div>  


Same for the next row, but we use the array playerDice instead, and the value of classPrefix is "playerDice".

src/components/Game/Game.js
<div className="GameRow">
  <div className="right width_long">
      {
        opponentDice.map(function(dice, diceIndex){
          return (
            <Dice
              dice = { dice }
              diceIndex = { diceIndex }
              classPrefix = "opponentDice"
            />
          );
        })
      }
  </div>  
</div>  

<div className="GameRow">
  <div className="left width_short">

  </div>  

  <div className="right width_long">
      {
        playerDice.map(function(dice, diceIndex){
          return (
            <Dice
              dice = { dice }
              diceIndex = { diceIndex }
              classPrefix = "playerDice"
            />
          );
        })
      }
  </div>
</div>  


There's the dice!

One last touch...

I got this image of a dice shaker off the internet. You may recognize this; I also used it in the game logo.

src/img/shaker.png



We define the shaker CSS class that uses this as a background image in a square div.

src/components/Game/Game.css
.speechballoon::before {
  position: absolute;
  margin: 0 0px 0px 350px;
  display: block;
  width: 20px;
  font-size: 2em;
  content: "▶";
  color: rgb(50, 50, 50);
}

.shaker {
  width: 80px;
  height: 80px;
  margin: 0 auto 0 auto;
  background-image: url(../../img/shaker.png);
  background-size: contain;
  background-position: center center;
  background-repeat: no-repeat;
}


.guessQty {
  font-weight: bold;
  font-size: 2.5em;
  text-align: right;
}


Add the divs in, and style them using shaker.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_short">
    <div className="shaker"></div>
  </div>  

  <div className="right width_long">
      {
        opponentDice.map(function(dice, diceIndex){
          return (
            <Dice
              dice = { dice }
              diceIndex = { diceIndex }
              classPrefix = "opponentDice"
            />
          );
        })
      }
  </div>  
</div>  

<div className="GameRow">
  <div className="left width_short">
    <div className="shaker"></div>
  </div>  

  <div className="right width_long">
      {
        playerDice.map(function(dice, diceIndex){
          return (
            <Dice
              dice = { dice }
              diceIndex = { diceIndex }
              classPrefix = "playerDice"
            />
          );
        })
      }
  </div>
</div>  


And we're done here! For now.


After this, we will be adding functionality to the buttons that we've created, which will in turn affect the visibility and visual representation of other elements.

Next

Gameplay.

No comments:

Post a Comment