Friday 27 September 2024

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

It's time to work on gameplay!

When we frst run startStage(), this sets stageStarted to true, and affects the visibility of certain elements. One of these is the button to start a new stage. Instead of just styling it using actionButton, make it be styled using hidden if roundStarted is true, or stageStarted is false, or if playerIntoxication is 0. (The last conditions means that the player has lost, and no more progression is possible). And otherwise, use actionButton.

src/components/Game/Game.js
<button className={ (roundStarted || !stageStarted || playerIntoxication === 0 ? "hidden" : "actionButton") }>{ GetLabels("startnewround", lang) } &#9658;</button>


Now, we want this button to run startNewRound() when clicked.

src/components/Game/Game.js
<button onClick={ ()=>{ startNewRound(); } } className={ (roundStarted || !stageStarted || playerIntoxication === 0 ? "hidden" : "actionButton") }>{ GetLabels("startnewround", lang) } &#9658;</button>


Le's create the function.

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));
};

const startNewRound = function() {
  
};


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


We set show to true. show is a flag that determines if the opponent's dice are revealed. We also set shake to true. shake is a flag that tells the system that the dice are being "shaken". And thirdly, we set roundStarted to true.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);  
};


Since shake has been set to true, that means the dice are "shaking". Declare variable shaking, and set it to the value returned by running the setInterval() function. This will run every 10 milliseconds. Since the dice are being "shaken", we want it to appear as if the numbers are constantly changing.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);
  
  var shaking = setInterval(()=>{
    
  },
  100);

};


Declare values_opponent and values_player as empty arrays.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);
  
  var shaking = setInterval(()=>{
    var values_opponent = [];
    var values_player = [];

  },
  100);
};


There are five dice for the opponent and the plyer, each. So we use For loop that will run five times. We populate values_opponent and values_player with a random number from 1 to 6. By the time we exit the For loop, these two arrays should have five values each.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);
  
  var shaking = setInterval(()=>{
    var values_opponent = [];
    var values_player = [];

    for (var i = 0; i < 5; i++) {
      var val = Math.floor(Math.random() * 6) + 1;
      values_opponent.push(val);
      val = Math.floor(Math.random() * 6) + 1;
      values_player.push(val);
    }

  },
  100);
};


Set opponentDice and playerDice to the values of values_opponent and values_player, respectively.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);
  
  var shaking = setInterval(()=>{
    var values_opponent = [];
    var values_player = [];

    for (var i = 0; i < 5; i++) {
      var val = Math.floor(Math.random() * 6) + 1;
      values_opponent.push(val);
      val = Math.floor(Math.random() * 6) + 1;
      values_player.push(val);
    }

    setOpponentDice(values_opponent);
    setPlayerDice(values_player);  

  },
  100);
};


Then run setTimeout(), setting the delay to 1 second.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);
  
  var shaking = setInterval(()=>{
    var values_opponent = [];
    var values_player = [];

    for (var i = 0; i < 5; i++) {
      var val = Math.floor(Math.random() * 6) + 1;
      values_opponent.push(val);
      val = Math.floor(Math.random() * 6) + 1;
      values_player.push(val);
    }

    setOpponentDice(values_opponent);
    setPlayerDice(values_player);  
  },
  100);

  setTimeout(
    ()=> {

    },
    1000
  );  

};


In it, we set show and shake to false, and use clearInterval() to stop the timer for shaking. This means that the dice shaking lasts only for 1 second. In addition, round is incremented, turns is set to 0 and isPlayerTurn is set to true. Which basically means every new round, the player goes first.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);
  
  var shaking = setInterval(()=>{
    var values_opponent = [];
    var values_player = [];

    for (var i = 0; i < 5; i++) {
      var val = Math.floor(Math.random() * 6) + 1;
      values_opponent.push(val);
      val = Math.floor(Math.random() * 6) + 1;
      values_player.push(val);
    }

    setOpponentDice(values_opponent);
    setPlayerDice(values_player);  
  },
  100);

  setTimeout(
    ()=> {
      setShow(false);
      setShake(false);
      clearInterval(shaking);
      setRound(round + 1);
      setTurns(0);
      setIsPlayerTurn(true);

    },
    1000
  );  
};


Since it's the player's turn, we run setOpponentDialog() to nudge the player.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);
  
  var shaking = setInterval(()=>{
    var values_opponent = [];
    var values_player = [];

    for (var i = 0; i < 5; i++) {
      var val = Math.floor(Math.random() * 6) + 1;
      values_opponent.push(val);
      val = Math.floor(Math.random() * 6) + 1;
      values_player.push(val);
    }

    setOpponentDice(values_opponent);
    setPlayerDice(values_player);  
  },
  100);

  setTimeout(
    ()=> {
      setShow(false);
      setShake(false);
      clearInterval(shaking);
      setRound(round + 1);
      setTurns(0);
      setIsPlayerTurn(true);

      setOpponentDialog(GetPhrases(stage, "yourturn", lang));
    },
    1000
  );  
};


Add "yourturn" phrases to the phrases array in the GetPhrases utility.

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: "慢点哦!"},
  { personality: 1, phraseName: "yourturn", lang: "en", value: "It's your turn, right?"},
  { personality: 1, phraseName: "yourturn", lang: "cn", value: "到你了,对吗?"},
  { personality: 1, phraseName: "yourturn", lang: "en", value: "It's your turn, what will you do?"},
  { personality: 1, phraseName: "yourturn", lang: "cn", value: "到你了, 怎么做?"}


When you click Start New Round, see the text in the speech balloon change. The dice change values rapidly for 1 second before settling, and the Start New Round button disappears!


But the dice shakers still haven't shaken. We need to adjust things this way. Instead of just styling using the shaker CSS class, add the class shaking if shake is true.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_short">
    <div className={ "shaker " + (shake ? "shaking" : "") }></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 " + (shake ? "shaking" : "") }></div>
  </div>

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


For shaking, we specify the animation to be shakingAnimation, and that the duration is half a second.

src/components/Game/Game.css
.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;
}

.shaking {
  animation-name: shakingAnimation;
  animation-duration: 0.5s;
}


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


shakingAnimation specifies that the element moves up and down. It'll look like it's jiggling.

src/components/Game/Game.css
.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;
}

.shaking {
  animation-name: shakingAnimation;
  animation-duration: 0.5s;
}

@keyframes shakingAnimation {
  0%   { margin: 5px auto 0 auto; }
  25%  { margin: 0 auto 0 auto; }
  50%  { margin: 5px auto 0 auto; }
  75%  { margin: 0 auto 0 auto; }
  100% { margin: 5px auto 0 auto; }
}


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


Let's write some code to handle the effects of the flag show on Dice. The value of show will be passed into props.

src/components/Dice/Dice.js
function Dice(props) {
  let dice = props.dice;
  let diceIndex = props.diceIndex;
  let classPrefix = props.classPrefix;
  let show = props.show;

  var dots = [


Further down in the function, where the string css is defined, we further define it with a conditional block. Our previous definition stands only if show is true, otherwise the string is "dot hideDice".

src/components/Dice/Dice.js
dots[dice - 1].map(function(dot, dotIndex){
  var css = (show ? "dot val" + dot : "dot hideDice");

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

  </div>
})


This is the CSS for hideDice. Firstly, hideDice will be styled like val1 as far as the dot is concerned. But the color will be a deep grey.

src/components/Dice/Dice.css
.hideDice::after, .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);
}

.hideDice::after {
  border: 2px solid rgb(50, 50, 50);
  background-color: rgb(30, 30, 30);
}


And here, make sure we add show into the component. Note that for the player's dice, show is always true.

src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_short">
    <div className={ "shaker " + (shake ? "shaking" : "") }></div>
  </div>

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

<div className="GameRow">
  <div className="left width_short">
    <div className={ "shaker " + (shake ? "shaking" : "") }></div>
  </div>

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


And here. show is always true for this context, as well.

src/components/Game/Game.js
<div className="left width_long">
    <Dice
      dice = { playerGuessDice }
      diceIndex = "0"
      classPrefix = "guessDice"
      show = { true }
    />
</div>


As you can see, now the opponent's dice are obscured!


Let's handle some of the other buttons. This button runs the restartStage() function. It appears only when playerIntoxication is 0, which means the player has lost the game. Otherwise, it's hidden.

src/components/Game/Game.js
<button className="actionButton">{ GetLabels("endround", lang) } &#9673;</button>
<button onClick={ ()=>{ startNewRound(); } } className={ (roundStarted || !stageStarted || playerIntoxication === 0 ? "hidden" : "actionButton") }>{ GetLabels("startnewround", lang) } &#9658;</button>
<button onClick={ ()=>{ restartStage(); } } className={ (playerIntoxication === 0 ? "actionButton" : "hidden") }>{ GetLabels("restartstage", lang) } &#9658;</button>


Here's the restartStage() function. We set these values back to their defaults. Note that stageStarted and roundStarted are false, which should send the user back to that stage's intro screen.

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

const restartStage = function() {
  setPlayerIntoxication(100);
  setOpponentIntoxication(100);
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setIsPlayerTurn(true);
  setStageStarted(false);
  setRoundStarted(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);

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


This button runs endRound(). It only appears when roundStarted and show are true, and playerIntoxication is greater than 0. This means that the round has started, and ended (because the dice are now shown). If playerIntoxication is still greater than 0, the game can continue, thus the user gets to end the round.

src/components/Game/Game.js
<button onClick={ ()=>{ endRound();} } className={ (roundStarted && show && playerIntoxication > 0 ? "actionButton" : "hidden") }>{ GetLabels("endround", lang) } &#9673;</button>
<button onClick={ ()=>{ startNewRound(); } } className={ (roundStarted || !stageStarted || playerIntoxication === 0 ? "hidden" : "actionButton") }>{ GetLabels("startnewround", lang) } &#9658;</button>
<button onClick={ ()=>{ restartStage(); } } className={ (playerIntoxication === 0 ? "actionButton" : "hidden") }>{ GetLabels("restartstage", lang) } &#9658;</button>


When ending the round, we reset these values back to their defaults. Then we check if opponentIntoxication is still greater than 0.

src/components/Game/Game.js
const startNewRound = function() {
  setShow(true);
  setShake(true);
  setRoundStarted(true);
  
  var shaking = setInterval(()=>{
    var values_opponent = [];
    var values_player = [];

    for (var i = 0; i < 5; i++) {
      var val = Math.floor(Math.random() * 6) + 1;
      values_opponent.push(val);
      val = Math.floor(Math.random() * 6) + 1;
      values_player.push(val);
    }

    setOpponentDice(values_opponent);
    setPlayerDice(values_player);  
  },
  100);

  setTimeout(
    ()=> {
      setShow(false);
      setShake(false);
      clearInterval(shaking);
      setRound(round + 1);
      setTurns(0);
      setIsPlayerTurn(true);

      setOpponentDialog(GetPhrases(stage, "yourturn", lang));
    },
    1000
  );  
};

const endRound = function() {
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setRoundStarted(false);
  setShow(false);

  if (opponentIntoxication > 0) {

  } else {

  }
}


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


If so, we indicate a new round by setting opponentDialog. But if not, we move on to the next opponent by incrementing stage. We act as we would when starting a new stage, by setting stageStarted to false, round to 0, turns to 0, resetting setPlayerIntoxication and setOpponentIntoxication, and setting opponentDialog to intro text.

src/components/Game/Game.js
const endRound = function() {
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setRoundStarted(false);
  setShow(false);

  if (opponentIntoxication > 0) {
    setOpponentDialog(GetPhrases(stage, "newround", lang));
  } else {
    setStage(stage + 1);
    setStageStarted(false);
    setRound(0);
    setTurns(0);
    setPlayerIntoxication(100);
    setOpponentIntoxication(100);
    setOpponentDialog(GetPhrases(stage, "intro", lang));
  }

}


Now you should only see the Start New Round button.


Let's work on the Guess and Open buttons. They'll run guess() and openup() functions respectively.

src/components/Game/Game.js
<div className="right width_short">
  <button onClick={ ()=>{ guess(); } } className="actionButton">{ GetLabels("guess", lang) }</button>
  <button onClick={ ()=>{ openup(); } } className="actionButton">{ GetLabels("openup", lang) }</button>
</div>


Here are the functions...

src/components/Game/Game.js
const getMeterColor = function(val) {
  if (val > 80) return "high";
  if (val > 50) return "half";
  return "low";
};

const guess = function() {

};

const openup = function() {

};


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


For guess(), we set opponentDialog for some flavor text (to be added later), then increment turns. Then we implement a delay of dialogSpeed milliseconds.

src/components/Game/Game.js
const getMeterColor = function(val) {
  if (val > 80) return "high";
  if (val > 50) return "half";
  return "low";
};

const guess = function() {
  setOpponentDialog(GetPhrases(stage, "doubt", lang));
  setTurns(turns + 1);

  window.setTimeout(()=> {

  },
  dialogSpeed);

};

const openup = function() {

};

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


After the delay, we set guessQty to playerGuessQty and guessDice to playerGuessDice. Why? Explain later. Since guessing counts as a turn, it's no longer the player's turn and thus we set isPlayerTurn to false. After that, we run the opponentAction() function because it's the opponent's turn, and pass in the values of playerGuessQty and playerGuessDice.

src/components/Game/Game.js
const getMeterColor = function(val) {
  if (val > 80) return "high";
  if (val > 50) return "half";
  return "low";
};

const guess = function() {
  setOpponentDialog(GetPhrases(stage, "doubt", lang));
  setTurns(turns + 1);

  window.setTimeout(()=> {
    setGuessQty(playerGuessQty);
    setGuessDice(playerGuessDice);
    setIsPlayerTurn(false);
    opponentAction(playerGuessQty, playerGuessDice);

  },
  dialogSpeed);
};

const openup = function() {

};

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


Here is the opponentAction() function. Leave it blank for now. Note that there are two parameters.

src/components/Game/Game.js
const getMeterColor = function(val) {
  if (val > 80) return "high";
  if (val > 50) return "half";
  return "low";
};

const opponentAction = function(currentGuessQty, currentGuessDice) {

};


const guess = function() {
  setOpponentDialog(GetPhrases(stage, "doubt", lang));
  setTurns(turns + 1);

  window.setTimeout(()=> {
    setGuessQty(playerGuessQty);
    setGuessDice(playerGuessDice);
    setIsPlayerTurn(false);
    opponentAction(playerGuessQty, playerGuessDice);
  },
  dialogSpeed);
};


For openup(), we again set opponentDialog for some flavor, then implement a delay of dialogSpeed milliseconds. When we want to open up, it means to show the die and call your opponent's bluff. Therefore, show is set to true. After opening up, we check if the player has won or lost, so run the checkWin() function. We pass in true as an argument to indicate that it's the player who requested to open up (you'll see why very soon), then the values of guessQty and guessDice.

src/components/Game/Game.js
const getMeterColor = function(val) {
  if (val > 80) return "high";
  if (val > 50) return "half";
  return "low";
};

const guess = function() {
  setOpponentDialog(GetPhrases(stage, "doubt", lang));
  setTurns(turns + 1);

  window.setTimeout(()=> {
    setGuessQty(playerGuessQty);
    setGuessDice(playerGuessDice);
    setIsPlayerTurn(false);
    opponentAction(playerGuessQty, playerGuessDice);
  },
  dialogSpeed);
};

const openup = function() {
  setOpponentDialog(GetPhrases(stage, "doubt", lang));

  window.setTimeout(()=> {
    setShow(true);
    checkWin(true, guessQty, guessDice);
  },
  dialogSpeed);

};

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


Declare checkWin(), but leave it empty for now. However, do add in the parameters isPlayerOpen, currentGuessQty and currentGuessDice.

src/components/Game/Game.js
const openup = function() {
  setOpponentDialog(GetPhrases(stage, "doubt", lang));

  window.setTimeout(()=> {
    setShow(true);
    checkWin(true, guessQty, guessDice);
  },
  dialogSpeed);
};

const checkWin = function(isPlayerOpen, currentGuessQty, currentGuessDice) {

};


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


Now, before we do anything else, playerDashboard and guessDashboard need to be hidden under the right conditions. playerDashboard will be hidden (very briefly) when it's not the player's turn and the round has not started. guessDashboard is shown only when show and shake are false, and (obviously) it is the player's turn.

src/components/Game/Game.js
<div id="playerDashboard" className={ (isPlayerTurn && roundStarted ? "" : "hidden") }>
  <div id="guessDashboard" className={ (!show && !shake && isPlayerTurn ? "" : "hidden") }>


Before we forget, add in the phrases for "doubt" in the GetPhrases utility.
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: "慢点哦!"},
  { personality: 1, phraseName: "doubt", lang: "en", value: "Are we supposed to play like that?"},
  { personality: 1, phraseName: "doubt", lang: "cn", value: "是这样玩的吗?"},
  { personality: 1, phraseName: "doubt", lang: "en", value: "Let me think..."},
  { personality: 1, phraseName: "doubt", lang: "cn", value: "我想想哦..."},

  { personality: 1, phraseName: "yourturn", lang: "en", value: "It's your turn, right?"},
  { personality: 1, phraseName: "yourturn", lang: "cn", value: "到你了,对吗?"},
  { personality: 1, phraseName: "yourturn", lang: "en", value: "It's your turn, what will you do?"},
  { personality: 1, phraseName: "yourturn", lang: "cn", value: "到你了, 怎么做?"}


Rerun the code, and click through till you start Stage 1. There should be only the Start New Round button visible. Click it...


...and the button disappears! Now the guess controls are visible!


Great! We will now work on the controls. They will be disabled under certain conditions.

src/components/Game/Game.js
<div className="right width_short">
  <button onClick={ ()=>{ guess(); } } disabled={  } >{ GetLabels("guess", lang) }</button>
  <button onClick={ ()=>{ openup(); } } disabled={  } >{ GetLabels("openup", lang) }</button>
</div>


The Guess button will be disabled if running the isValidGuess() function with playerGuessQty and playerGuessDice gets you false, which means the current values of playerGuessQty and playerGuessDice are invalid for guessing. We will work on that function soon.

src/components/Game/Game.js
<div className="right width_short">
  <button onClick={ ()=>{ guess(); } } disabled={ (isValidGuess(playerGuessQty, playerGuessDice) ? "" : "disabled") } className="actionButton">{ GetLabels("guess", lang) }</button>
  <button onClick={ ()=>{ openup(); } } disabled={  } >{ GetLabels("openup", lang) }</button></div>


The Open Up button is disabled if show is already true, or if guessQty and guessDice are at their minimum values.

src/components/Game/Game.js
<div className="right width_short">
  <button onClick={ ()=>{ guess(); } } disabled={ (isValidGuess(playerGuessQty, playerGuessDice) ? "" : "disabled") } className="actionButton">{ GetLabels("guess", lang) }</button>
  <button onClick={ ()=>{ openup(); } } disabled={ ((guessQty === 3 && guessDice === 2) || show ? "disabled" : "") } className="actionButton">{ GetLabels("openup", lang) }</button>
</div>


Now here's the function. It's pretty simple. qty and guessDice are parameters. As long as qty is greater than the current guess quantity or dice is greater than the current dice guess value, it's valid and we return true. Of course, qty also has to be greater than 3.

src/components/Game/Game.js
const endRound = function() {
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setRoundStarted(false);
  setShow(false);

  if (opponentIntoxication > 0) {
    setOpponentDialog(GetPhrases(stage, "newround", lang));
  } else {
    setStage(stage + 1);
    setStageStarted(false);
    setRound(0);
    setTurns(0);
    setPlayerIntoxication(100);
    setOpponentIntoxication(100);
    setOpponentDialog(GetPhrases(stage, "intro", lang));
  }
}

const isValidGuess = function(qty, dice) {
  return ((qty > guessQty || dice > guessDice) && qty > 3);
};


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


See? The value of guessQty is 3 and the value of guessDice is 2. Both buttons are disabled!


Now let's deal with the Up and Down buttons for the guess quantity. They will call the adjustPlayerGuessQty() function, but with different arguments.

src/components/Game/Game.js
<div className="left width_half">
  <div className="guessQty left width_long">
    { playerGuessQty }
  </div>
  <div className="guessButtons right width_short">
    <button onClick={ ()=>{ adjustPlayerGuessQty(1); } }>&#9650;</button>
    <br />
    <button onClick={ ()=>{ adjustPlayerGuessQty(-1); } }>&#9660;</button>
  </div>
</div>


Here is the function. It has a parameter, inc. We first define finalQty as the sum of the current value of playerGuessQty, and inc. So finalQty will be the projected quantity after playerGuessQty is incremented or decremented.

src/components/Game/Game.js
const endRound = function() {
  setGuessQty(3);
  setGuessDice(2);
  setPlayerGuessQty(3);
  setPlayerGuessDice(2);
  setRoundStarted(false);
  setShow(false);

  if (opponentIntoxication > 0) {
    setOpponentDialog(GetPhrases(stage, "newround", lang));
  } else {
    setStage(stage + 1);
    setStageStarted(false);
    setRound(0);
    setTurns(0);
    setPlayerIntoxication(100);
    setOpponentIntoxication(100);
    setOpponentDialog(GetPhrases(stage, "intro", lang));
  }
}

const adjustPlayerGuessQty = function(inc) {
  var finalQty = playerGuessQty + inc;
};


const isValidGuess = function(qty, dice) {
  return ((qty > guessQty || dice > guessDice) && qty > 3);
};


The minimum value is guessQty and the maximum is 10 (because the total number of dice is 10). Thus, if the value of finalQty falls outside of these values, exit the function.

src/components/Game/Game.js
const adjustPlayerGuessQty = function(inc) {
  var finalQty = playerGuessQty + inc;
  if (finalQty < guessQty || finalQty > 10) return;
};


And at the end of the function, now that we've verified that finalQty is valid, we set playerGuessQty to finalQty.

src/components/Game/Game.js
const adjustPlayerGuessQty = function(inc) {
  var finalQty = playerGuessQty + inc;
  if (finalQty < guessQty || finalQty > 10) return;

  setPlayerGuessQty(finalQty);
};


There you go. When you click the Up and Down buttons, the quantity should change accordingly. You can't go above 10, and once you rise above 3, note that the Guess button is enabled!


Similar to the last set of Up and Down buttons, a function is triggered when these buttons are clicked. In this case, the function is adjustPlayerGuessDice(). As before, we either pass in 1 or -1 as arguments.

src/components/Game/Game.js
<div className="right width_half">
  <div className="left width_long">
  <Dice
    dice = { playerGuessDice }
    diceIndex = "0"
    classPrefix = "guessDice"
    show = { true }
  />
  </div>
  <div className="guessButtons right width_short">
    <button onClick={ ()=>{ adjustPlayerGuessDice(1); } }>&#9650;</button>
    <br />
    <button onClick={ ()=>{ adjustPlayerGuessDice(-1); } }>&#9660;</button>
  </div>
</div>


Similar principle to the last function we wrote. In this case, the final number can't be greater than 6.

src/components/Game/Game.js
const adjustPlayerGuessQty = function(inc) {
  var finalQty = playerGuessQty + inc;
  if (finalQty < guessQty || finalQty > 10) return;

  setPlayerGuessQty(finalQty);
};

const adjustPlayerGuessDice = function(inc) {
  var finalDice = playerGuessDice + inc;
  if (finalDice < guessDice || finalDice > 6) return;

  setPlayerGuessDice(finalDice);
};


const isValidGuess = function(qty, dice) {
  return ((qty > guessQty || dice > guessDice) && qty > 3);
};


Try it! The dice will change accordingly. You can't go above 6.


Remember if you click on the Guess button, it calls guess(). And guess() eventually calls opponentAction(). That's what we will work on next. We begin by declaring action. Then we derive its value by running the GetActions() function, passing in the arguments presented below.

src/components/Game/Game.js
const opponentAction = function(currentGuessQty, currentGuessDice) {
  var action = GetActions(stage, turns, currentGuessQty, currentGuessDice, opponentDice, opponentIntoxication);
};


GetActions is a utility that we are going to write. It basically determines your opponent's next move depending on certain variables. Create GetActions.js in the utils directory. The GetActions() function has these parameters to help determine the opponent's next action.

src/utils/GetActions.js
const GetActions = (stage, turns, qty, dice, ownDice, intoxication) => {

}

export default GetActions;  


First, we declare action as an object. The type property is "open". qty and dice only matter if type is "guess", but we'll just add these in because they might need to be changed later. By default, we return action. This means that by default, the opponent chooses to open up.

src/utils/GetActions.js
const GetActions = (stage, turns, qty, dice, ownDice, intoxication) => {
  let action = { "type": "open", "qty": 0, "dice": 0};

  return action;

}

export default GetActions;  


However, we have to check qty and dice, which represent the current guess in the game. If qty and dice are at the minimum (4 and 2 respectively) opening up is not a valid move. Thus type is set to "guess".

src/utils/GetActions.js
const GetActions = (stage, turns, qty, dice, ownDice, intoxication) => {
  let action = { "type": "open", "qty": 0, "dice": 0};

  if (qty === 4 && dice === 2) { 
    action.type = "guess";
  } else {

  }


  return action;
}

export default GetActions;  


Now we declare intelligence, which is the effective intelligence of the current opponent. The higher the value of stage, the more intelligent. Which means as the player progresses his opponents become "smarter". Also, intoxication plays a part, so intelligence is offset by intoxication. The more sober the opponent is, the "cleverer" she is. We then check if the value of intelligence is above a certain threshold, say, 50.

src/utils/GetActions.js
const GetActions = (stage, turns, qty, dice, ownDice, intoxication) => {
  let action = { "type": "open", "qty": 0, "dice": 0};

  if (qty === 4 && dice === 2) { 
    action.type = "guess";
  } else {
    var intelligence = (stage * 10) + intoxication;

    if (intelligence >= 50) {

    } else {

    }

  }

  return action;
}

export default GetActions;  


Now we try to calculate the most reasonable course of action. We run the filter() method on the array ownDice to see how many are 1s or match the guess, dice, in the opponent's own dice. The size of the resultant array is assigned to ownQty. If the guessed qty is relatively equal to ownQty, the opponent will guess some more. If the variance is too great, type is set to "open".
src/utils/GetActions.js
const GetActions = (stage, turns, qty, dice, ownDice, intoxication) => {
  let action = { "type": "open", "qty": 0, "dice": 0};

  if (qty === 4 && dice === 2) { //if minimal, always guess
    action.type = "guess";
  } else {
    var intelligence = (stage * 10) + intoxication;

    if (intelligence >= 50) {
      var ownQty = ownDice.filter((x) => { return x === 1 || x === dice; } ).length;

      action.type = (qty <= ownQty + 3 ? "guess" : "open");

    } else {

    }
  }

  return action;
}

export default GetActions;  


If intelligence fails, the opponent will randomly guess or open up. However, at the end of it, I put in one last clause to state that if qty is 8 or more, the opponent always chooses to open up.

src/utils/GetActions.js
const GetActions = (stage, turns, qty, dice, ownDice, intoxication) => {
  let action = { "type": "open", "qty": 0, "dice": 0};

  if (qty === 4 && dice === 2) { //if minimal, always guess
    action.type = "guess";
  } else {
    var intelligence = (stage * 10) + intoxication;

    if (intelligence >= 50) {
      var ownQty = ownDice.filter((x) => { return x === 1 || x === dice; } ).length;

      action.type = (qty <= ownQty + 3 ? "guess" : "open");
    } else {
      var rand = Math.floor(Math.random() * 2);
      action.type = (rand === 0 ? "guess" : "open");

    }

    if (qty >= 8) action.type = "open";
  }

  return action;
}

export default GetActions;  


Now, here's an If block to check if the action type is "guess". If so, we return action without waiting to get to the last line of the function.
src/utils/GetActions.js
const GetActions = (stage, turns, qty, dice, ownDice, intoxication) => {
  let action = { "type": "open", "qty": 0, "dice": 0};

  if (qty === 4 && dice === 2) { //if minimal, always guess
    action.type = "guess";
  } else {
    var intelligence = (stage * 10) + intoxication;

    if (intelligence >= 50) {
      var ownQty = ownDice.filter((x) => { return x === 1 || x === dice; } ).length;

      action.type = (qty <= ownQty + 3 ? "guess" : "open");
    } else {
      var rand = Math.floor(Math.random() * 2);
      action.type = (rand === 0 ? "guess" : "open");
    }

    if (qty >= 8) action.type = "open";
  }

  if (action.type === "guess") {
    return action;
  }


  return action;
}

export default GetActions;  


First, we define newQty. For its value, we will take qty and add a random small value to it. For newDice, we will add a random small value to dice. Note that newQty's value is always greater than qty's due to a "+1", whereas for newDice it's possible to remain exactly the same as dice.

src/utils/GetActions.js
if (action.type === "guess") {
  var newQty = Math.floor(Math.random() * 2 + 1) + qty;
  var newDice = Math.floor(Math.random() * 3) + dice;


  return action;
}


Now we set the qty and dice properties of action. However, even here there are limits. The qty property cannot be greater than 10. The dice property cannot be greater than 6.

src/utils/GetActions.js
if (action.type === "guess") {
  var newQty = Math.floor(Math.random() * 2 + 1) + qty;
  var newDice = Math.floor(Math.random() * 3) + dice;

  action.qty = (newQty > 10 ? 10 : newQty);
  action.dice = (newDice > 6 ? 6 : newDice);

  return action;
}


And, of course, import the uility.

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

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

function Game(props) {


Back to opponentAction()! After action is defined, we increment turns.
src/components/Game/Game.js
const opponentAction = function(currentGuessQty, currentGuessDice) {
  var action = GetActions(stage, turns, currentGuessQty, currentGuessDice, opponentDice, opponentIntoxication);
  setTurns(turns + 1);
};


Then we use If blocks, checking the value of action's type property.
src/components/Game/Game.js
const opponentAction = function(currentGuessQty, currentGuessDice) {
  var action = GetActions(stage, turns, currentGuessQty, currentGuessDice, opponentDice, opponentIntoxication);
  setTurns(turns + 1);

  if (action.type === "open") {

  }

  if (action.type === "guess") {

  }

};


Let's handle the opponent action of opening up. We first define dialogStr. It was be a combination of two strings returned fro calling GetPhrases(), joined with a newline character.

src/components/Game/Game.js
if (action.type === "open") {
  var dialogStr = (GetPhrases(stage, "myturn", lang) + "\n" + GetPhrases(stage, "openup", lang));
}


To display this as HTML, convert dialogStr to an array using the split() method and the newline character, then use the map() method on the array to return a series of paragraph tags with the content.
src/components/Game/Game.js
if (action.type === "open") {
  var dialogStr = (GetPhrases(stage, "myturn", lang) + "\n" + GetPhrases(stage, "openup", lang));
  var dialog = dialogStr.split('\n').map(i => {
    return <p>{i}</p>
  });

}


Then set opponentDialog to dialog.

src/components/Game/Game.js
if (action.type === "open") {
  var dialogStr = (GetPhrases(stage, "myturn", lang) + "\n" + GetPhrases(stage, "openup", lang));
  var dialog = dialogStr.split('\n').map(i => {
    return <p>{i}</p>
  });

  setOpponentDialog(dialog);
}


Since the opponent is opening up, set show to true.

src/components/Game/Game.js
if (action.type === "open") {
  var dialogStr = (GetPhrases(stage, "myturn", lang) + "\n" + GetPhrases(stage, "openup", lang));
  var dialog = dialogStr.split('\n').map(i => {
    return <p>{i}</p>
  });

  setOpponentDialog(dialog);
  setShow(true);
}


And then a number of milliseconds later (double dialogSpeed should be fine), run the checkWin() function.
src/components/Game/Game.js
if (action.type === "open") {
  var dialogStr = (GetPhrases(stage, "myturn", lang) + "\n" + GetPhrases(stage, "openup", lang));
  var dialog = dialogStr.split('\n').map(i => {
    return <p>{i}</p>
  });

  setOpponentDialog(dialog);
  setShow(true);
  window.setTimeout(()=> {
    checkWin(false, currentGuessQty, currentGuessDice);
  },
  dialogSpeed * 2);

}


Add the phrases in the GetPhrases utility for "myturn and "openup". While you're there, you may as well add in phrases for "guess".
src/utils/GetPhrases.js
{ personality: 1, phraseName: "doubt", lang: "en", value: "Are we supposed to play like that?"},
{ personality: 1, phraseName: "doubt", lang: "cn", value: "是这样玩的吗?"},
{ personality: 1, phraseName: "doubt", lang: "en", value: "Let me think..."},
{ personality: 1, phraseName: "doubt", lang: "cn", value: "我想想哦..."},
{ personality: 1, phraseName: "myturn", lang: "en", value: "I think it's my turn."},
{ personality: 1, phraseName: "myturn", lang: "cn", value: "好像轮到我了"},
{ personality: 1, phraseName: "myturn", lang: "en", value: "I should make a decision... "},
{ personality: 1, phraseName: "myturn", lang: "cn", value: "该我来了..."},  

{ personality: 1, phraseName: "yourturn", lang: "en", value: "It's your turn, right?"},
{ personality: 1, phraseName: "yourturn", lang: "cn", value: "到你了,对吗?"},
{ personality: 1, phraseName: "yourturn", lang: "en", value: "It's your turn, what will you do?"},
{ personality: 1, phraseName: "yourturn", lang: "cn", value: "到你了, 怎么做?"},
{ personality: 1, phraseName: "guess", lang: "en", value: "Let me see... I guess"},
{ personality: 1, phraseName: "guess", lang: "cn", value: "我想想... 我猜..."},
{ personality: 1, phraseName: "guess", lang: "en", value: "I'm guessing..."},
{ personality: 1, phraseName: "guess", lang: "cn", value: "我在想... 我猜..."},

{ personality: 1, phraseName: "openup", lang: "en", value: "Can you show me your dice?"},
{ personality: 1, phraseName: "openup", lang: "cn", value: "能开给我看吗?"},
{ personality: 1, phraseName: "openup", lang: "en", value: "I think we should open up!"},
{ personality: 1, phraseName: "openup", lang: "cn", value: "我想我们开吧!"}


Now for guessing. We start by using the action object, which contains qty and dice. We will set guessQty and playerGuessQty to qty. And we set guessDice and payerGuessDice to dice.
src/components/Game/Game.js
if (action.type === "guess") {
  setGuessQty(action.qty);
  setGuessDice(action.dice);
  setPlayerGuessQty(action.qty);
  setPlayerGuessDice(action.dice);

}


Then we define a delay (again, double dialogSpeed).
src/components/Game/Game.js
if (action.type === "guess") {
  setGuessQty(action.qty);
  setGuessDice(action.dice);
  setPlayerGuessQty(action.qty);
  setPlayerGuessDice(action.dice);

  window.setTimeout(()=> {

  },
  dialogSpeed * 2);

}


In it, we define dialogStr like we did the last time. This time, the string is longer and involves both the GetPhrases() and GetLabels() functions. This is because we not only want the opponent to guess, we want her to say how many quantities of values she is guessing. 

src/components/Game/Game.js
if (action.type === "guess") {
  setGuessQty(action.qty);
  setGuessDice(action.dice);
  setPlayerGuessQty(action.qty);
  setPlayerGuessDice(action.dice);

  window.setTimeout(()=> {
    var dialogStr = (GetPhrases(stage, "myturn", lang) + " " + GetPhrases(stage, "guess", lang) + " " + GetLabels(action.qty + "dice", lang) + GetLabels(action.dice + "s", lang) + "! \n" + GetPhrases(stage, "yourturn", lang));
  },
  dialogSpeed * 2);
}


Then we do what we did earlier for opening up, culminating in setting opponentDialog.
src/components/Game/Game.js
if (action.type === "guess") {
  setGuessQty(action.qty);
  setGuessDice(action.dice);
  setPlayerGuessQty(action.qty);
  setPlayerGuessDice(action.dice);

  window.setTimeout(()=> {
    var dialogStr = (GetPhrases(stage, "myturn", lang) + " " + GetPhrases(stage, "guess", lang) + " " + GetLabels(action.qty + "dice", lang) + GetLabels(action.dice + "s", lang) + "! \n" + GetPhrases(stage, "yourturn", lang));
    var dialog = dialogStr.split('\n').map(i => {
      return <p>{i}</p>
    });
    setOpponentDialog(dialog);

  },
  dialogSpeed * 2);
}


And here, because this concluds the opponent's turn, we set isPlayerTurn to true.
src/components/Game/Game.js
if (action.type === "guess") {
  setGuessQty(action.qty);
  setGuessDice(action.dice);
  setPlayerGuessQty(action.qty);
  setPlayerGuessDice(action.dice);

  window.setTimeout(()=> {
    var dialogStr = (GetPhrases(stage, "myturn", lang) + " " + GetPhrases(stage, "guess", lang) + " " + GetLabels(action.qty + "dice", lang) + GetLabels(action.dice + "s", lang) + "! \n" + GetPhrases(stage, "yourturn", lang));
    var dialog = dialogStr.split('\n').map(i => {
      return <p>{i}</p>
    });
    setOpponentDialog(dialog);
    setIsPlayerTurn(true);
  },
  dialogSpeed * 2);
}


Don't forget to add the labels here. 
src/utils/GetLabels.js
{ labelName: "guess", lang: "en", value: "Guess"},
{ labelName: "guess", lang: "cn", value: "猜"},  
{ labelName: "openup", lang: "en", value: "Open Up!"},
{ labelName: "openup", lang: "cn", value: "开!"},
{ labelName: "1s", lang: "en", value: " ones"},
{ labelName: "1s", lang: "cn", value: "一"},
{ labelName: "2s", lang: "en", value: " twos"},
{ labelName: "2s", lang: "cn", value: "二"},
{ labelName: "3s", lang: "en", value: " threes"},
{ labelName: "3s", lang: "cn", value: "三"},
{ labelName: "4s", lang: "en", value: " fours"},
{ labelName: "4s", lang: "cn", value: "四"},
{ labelName: "5s", lang: "en", value: " fives"},
{ labelName: "5s", lang: "cn", value: "五"},
{ labelName: "6s", lang: "en", value: " sixes"},
{ labelName: "6s", lang: "cn", value: "六"},
{ labelName: "1dice", lang: "en", value: "One"},
{ labelName: "1dice", lang: "cn", value: "一个"},
{ labelName: "2dice", lang: "en", value: "Two"},
{ labelName: "2dice", lang: "cn", value: "两个"},
{ labelName: "3dice", lang: "en", value: "Three"},
{ labelName: "3dice", lang: "cn", value: "三个"},
{ labelName: "4dice", lang: "en", value: "Four"},
{ labelName: "4dice", lang: "cn", value: "四个"},
{ labelName: "5dice", lang: "en", value: "Five"},
{ labelName: "5dice", lang: "cn", value: "五个"},
{ labelName: "6dice", lang: "en", value: "Six"},
{ labelName: "6dice", lang: "cn", value: "六个"},
{ labelName: "7dice", lang: "en", value: "Seven"},
{ labelName: "7dice", lang: "cn", value: "七个"},
{ labelName: "8dice", lang: "en", value: "Eight"},
{ labelName: "8dice", lang: "cn", value: "八个"},
{ labelName: "9dice", lang: "en", value: "Nine"},
{ labelName: "9dice", lang: "cn", value: "九个"},
{ labelName: "10dice", lang: "en", value: "Ten"},
{ labelName: "10dice", lang: "cn", value: "十个"}


Try this now. Start a game and make a guess. In this case, we guess four threes.


And see, your opponent tried to open up. She lost! But it's not really apparent. Something to fix soon.


Now try again. Restart the game. This time, we try guessing four twos.


Now the opponent raises the guess to six threes. See how the dice reflect her choice? Now, you can adjust the dice to raise another guess (notice how you can't go below 6 for quantity, or 3 for dice), or just open up.


And when you open up, it looks like your opponent lost! Again, it's not apparent. Let's fix this.


We want to define the isHighlightedDice() function. The parameter is dice. It returns true only if shake is false and show is true. Because highlighting dice only happens then.
src/components/Game/Game.js
const isValidGuess = function(qty, dice) {
  return ((qty > guessQty || dice > guessDice) && qty > 3);
};

const isHighlightedDice = function(dice) {
  return (!shake && show);
};


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


And then only if dice matches the value of guessDice or 1.
src/components/Game/Game.js
const isHighlightedDice = function(dice) {
  return (!shake && show && (dice === guessDice || dice === 1));
};


And then pass in the highlight attribute in every instance of the Dice component, using isHighlightedDice() as the value, with the value of dice passed in as an argument.
src/components/Game/Game.js
<div className="GameRow">
  <div className="left width_short">
    <div className={ "shaker " + (shake ? "shaking" : "") }></div>
  </div>  

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

<div className="GameRow">
  <div className="left width_short">
    <div className={ "shaker " + (shake ? "shaking" : "") }></div>
  </div>  

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


In this instance, highlight is set to false because we never want to highlight this.
src/components/Game/Game.js
<div className="left width_long">
  <Dice
    dice = { playerGuessDice }
    diceIndex = "0"
    classPrefix = "guessDice"
    highlight = { false }
    show = { true }
  />
</div>


Now in the Dice component, we make sure to grab the value from props.
src/components/Dice/Dice.js
function Dice(props) {
  let dice = props.dice;
  let diceIndex = props.diceIndex;
  let classPrefix = props.classPrefix;
  let highlight = props.highlight;
  let show = props.show;


The div returned should be styled with highlighted_dice as well, or not, depending on the value of highlight.
src/components/Dice/Dice.js
return <div className={ "dice " + classPrefix + " " + (highlight ? "highlighted_dice" : "") } key={ classPrefix + diceIndex }>


And here's the CSS for that. Just gives the dice a 3 pixel red border.
src/components/Dice/Dice.css
.playerDice {
  background-color: rgb(255, 255, 250);
  border: 3px solid rgb(255, 255, 250);
}

.highlighted_dice {
  border: 3px solid rgb(255, 0, 0);
}


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


And to see this clearer, let's disable the red outline in the main CSS.
src/App.css
div {
  outline: 0px solid red;
}


Let's try this. Start a game and get to a point where either you or the opponent opens up. See? The guess was six fives. When opening up, the dice that show 5 and 1, have a thick red border!


Phew! That was quite a chunk! But after this, it's mostly downhill.

Next

Win conditions and testing.

No comments:

Post a Comment