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) } ►</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) } ►</button>
Le's create the function.
src/components/Game/Game.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jslet 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.jsfunction 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.jsdots[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) } ◉</button>
<button onClick={ ()=>{ startNewRound(); } } className={ (roundStarted || !stageStarted || playerIntoxication === 0 ? "hidden" : "actionButton") }>{ GetLabels("startnewround", lang) } ►</button>
<button onClick={ ()=>{ restartStage(); } } className={ (playerIntoxication === 0 ? "actionButton" : "hidden") }>{ GetLabels("restartstage", lang) } ►</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.jsfunction 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) } ◉</button>
<button onClick={ ()=>{ startNewRound(); } } className={ (roundStarted || !stageStarted || playerIntoxication === 0 ? "hidden" : "actionButton") }>{ GetLabels("startnewround", lang) } ►</button>
<button onClick={ ()=>{ restartStage(); } } className={ (playerIntoxication === 0 ? "actionButton" : "hidden") }>{ GetLabels("restartstage", lang) } ►</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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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.jsconst 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); } }>▲</button>
<br />
<button onClick={ ()=>{ adjustPlayerGuessQty(-1); } }>▼</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.jsconst 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.jsconst 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.jsconst 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); } }>▲</button>
<br />
<button onClick={ ()=>{ adjustPlayerGuessDice(-1); } }>▼</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.jsconst 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.jsconst 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.jsconst 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.jsif (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.jsimport 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.jsconst 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.jsconst 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.jsif (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.jsif (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.jsif (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.jsif (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.jsif (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.jsif (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.jsif (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.jsif (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.jsif (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.jsif (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.jsconst 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.jsconst 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.jsfunction 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.jsreturn <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.