I am new to React, so I am probably not using best practices. I am trying to build this "Simon Says" game, but I am stuck trying to put delay in between each of my for loops, they run at the same time. I've looked at other solutions for this but they don't seem to fit with mine. I've also tried using setTimeout but that just plays all the animations at once after the delay. This is the for loop and the functions i want to have a delay in between:
newRound() {
this.setState({
pcSequence: this.state.pcSequence.concat(
Math.floor(Math.random() * 4) + 1)
},() => {
this.state.pcSequence.forEach(element =>
this.startAnimations(element)
);
}
);
}
startAnimations(element) {
if (element == 1) {
this.activeBtn1(0);
} else if (element == 2) {
this.activeBtn2(1);
} else if (element == 3) {
this.activeBtn3(2);
} else if (element == 4) {
this.activeBtn4(3);
}
}
Thanks!!
Hi I like your question and manage to get it working, i will share code below:
state = {
pcSequence: [],
}
componentDidMount() {
this.newRound();
}
newRound() {
this.setState({
pcSequence: [1,4,3,2]
},() => this.startSequence()
);
}
startSequence() {
if (this.state.pcSequence.length > 0) {
setTimeout(() => {
this.startAnimations(this.state.pcSequence.pop());
this.startSequence();
}, 1000) // 1000ms === 1 second
}
}
startAnimations(element) {
if (element === 1) {
console.log('element 1', element)
} else if (element === 2) {
console.log('element 2', element)
} else if (element === 3) {
console.log('element 3', element)
} else if (element === 4) {
console.log('element 4', element)
}
}
you can call this.newRound each time you want to reset buttons and also fill the array in a better way, yours is only filling the array with 1 item not 4 like [1] or [4]
Hope it helps!
I found help elsewhere and wanted to show what I did in case anyone else ever comes along looking for something similar. I removed the for loop and used map:
newRound() {
this.setState({
pcSequence: this.state.pcSequence.concat(
Math.floor(Math.random() * 4) + 1),
round: this.state.round + 1
},() => {
this.state.pcSequence.map((element,index)=> {
setTimeout(() => {
this.startAnimations(element);
}, index * 1000);
})
}
);
}
startAnimations(element) {
if (element == 1) {
this.activeBtn1(0);
} else if (element == 2) {
this.activeBtn2(1);
} else if (element == 3) {
this.activeBtn3(2);
} else if (element == 4) {
this.activeBtn4(3);
}
}
Related
I've written the code below (please be noted that some unrelated functions have been removed).
I'm trying to make a Player-vs-Computer TicTacToe game. But every time I try to run the code on Android Studio, Uncaught error: Maximum call stack size exceeded will be shown, specifically when it's Computer's turn to make a move. I think the error is in the onTilePress function, but I'm not sure how to solve it. Does anyone have an idea what is wrong there? Any suggestion will be much appreciated. Thank you in advance.
/* eslint-disable prettier/prettier */
...
export default class TicTacToe2 extends React.Component {
constructor(props){
super(props);
this.state = {
gameState: [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
] ,
currentPlayer: 1,
}
//tilesSelected : 0;
}
componentDidMount(){ //To start the first render... I guess
this.initializeGame();
}
//Reset the gameboard
initializeGame = () => {
this.setState({gameState:
[
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
],
currentPlayer: 1,
});
}
checkTie = () => {
...
}
//To check if the tile selected is owned
checkTilesOwner = (row, col) => {
var arr = this.state.gameState;
if (arr[row][col] == 1){ //Tile owned by Player 1
//Alert.alert("Owned by player 1!"); //remove comment for trial-and-error
this.BotMove();
}
else if (arr[row][col] == -1) { //Tile owned by Bot
//Alert.alert("Owned by Bot!"); //remove comment for trial-and-error
this.BotMove();
}
else { //Empty tile
this.onTilePress(row, col); //Select the tile
}
}
//Return 1 if player 1 won, -1 if player 2 / bot won, or 0 if no one has won
getWinner = () => {
...
}
BotMove = () => {
var RandRow = Math.floor(Math.random() * 3) + 0; //Generate random number for Row
var RandCol = Math.floor(Math.random() * 3) + 0; //Generate random number for Col
this.checkTilesOwner(RandRow, RandCol);
}
onTilePress = (row, col) => {
//Dont allow tiles to change
var value = this.state.gameState[row][col];
if (value !== 0) { return;}
//Identify and grab current player
var currentPlayer = this.state.currentPlayer;
//Set the correct tile...
var arr = this.state.gameState.slice();
arr[row][col] = currentPlayer;
this.setState({gameState: arr});
//Switch to other player
var nextPlayer = (currentPlayer == 1) ? -1 : 1; //if yes, then change to -1; else, then change to 1
this.setState({currentPlayer: nextPlayer});
//Alert.alert(nextPlayer);
//Check if the match is tie
var checkDraw = this.checkTie();
//check winner
var winner = this.getWinner(); //get the winner update
if (winner == 1) {
Alert.alert("Player 1 has won!");
this.initializeGame();
}
else if (winner == -1){
Alert.alert("Bot has won!");
this.initializeGame();
}
else if (checkDraw == 9){
Alert.alert("It's a draw!");
this.initializeGame();
}
//Identify the current player: 1 is human, -1 is bot
var takePlayer = '' + nextPlayer;
if (takePlayer == 1) {
Alert.alert("Player 1's turn!");
}
else if (takePlayer == -1) { //If it is Bot's turn to nmake a move, then trigger BotMove
Alert.alert("Bot's turn!");
this.BotMove(); //Uncaught error: Maximum call stack size exceeded //possible cause: endless loop
}
}
onNewGamePress = () => {
this.initializeGame();
}
renderIcon = (row, col) => {
var value = this.state.gameState[row][col];
switch(value) {
case 1:
return <TouchableOpacity><Text style={styles.tileX}>X</Text></TouchableOpacity>;
case -1:
return <TouchableOpacity><Text style={styles.tileO}>O</Text></TouchableOpacity>;
default:
return <View />
}
}
render() {
return (
//Container for Gameboard
...
);
}
}
Yes, your BotMove and checkTilesOwner are circular.
Try to simplify your logic into
onTilePress(...) {
// think what needs to be done
// get only stuff you need without moving it
this.BotMove(...)
}
Don't blend the thinking code (logic) with the action itself. Because otherwise you will end up with lots of this.setState call. Not only this could cause lots of circular problem, but only it won't help you understand your business logic, in your case, your game logic :)
Separate your logic from your rendering.
Hi I have a timer running which is like it should show a component for 30sec after every 10 seconds. My code is like this`
import { timer } from "rxjs";
componentWillReceiveProps(nextProps, nextState) {
console.log("RECEIVED PROPS");
if (this.props.venuesBonusOfferDuration === 0) {
this.subscribe.unsubscribe();
}
this.firstTimerSubscription;
this.secondTimerSubscription;
// if (this.state.isMounted) {
if (
nextProps.showBonusObj &&
(!nextProps.exitVenue.exitVenueSuccess || nextProps.enterVenues)
) {
// console.log("isMounted" + this.state.isMounted);
//if (this.state.isMounted) {
let milliseconds = nextProps.venuesBonusOfferDuration * 1000;
this.source = timer(milliseconds);
this.firstTimerSubscription = this.source.subscribe(val => {
console.log("hiding bonus offer");
this.firstTimerSubscription.unsubscribe();
this.props.hideBonusOffer();
this.secondTimerSubscription = bonusApiTimer.subscribe(val => {
console.log("caling timer" + val);
this.props.getVenuesBonusOffer(
this.props.venues.venues.id,
this.props.uid,
this.props.accessToken
);
});
});
//}
} else {
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimer UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimer UNSUBSCRIBED");
}
} catch (error) {
console.log(
"error when removing bonusoffer timer" + JSON.stringify(error)
);
}
//}
}
}
`
Problem is if I try to unsubscribe this * this.firstTimerSubscription* and this.secondTimerSubscription like this
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimerunmount UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimerunmount UNSUBSCRIBED");
}
} catch (error) {
console.log("error bonusoffer timer" + JSON.stringify(error));
}
its still prints logs within timer like "hiding bonus offer" and "calling timer".
Can someone please point out the issue. It been a day since am into this.
Any help is appreciated.
The problem is that you subscribe multiple times (whenever component receives props) and reassign newest subscription to firstTimerSubscription or secondTimerSubscription references. But doing that, subscriptions does not magically vanish. To see how it works here is a demo:
const source = timer(1000, 1000);
let subscribe;
subscribe = source.subscribe(val => console.log(val));
subscribe = source.subscribe(val => console.log(val));
setTimeout(() => {
subscribe.unsubscribe();
}, 2000)
Even though you unsubscribed, the first subscription keeps emiting. And the problem is that you lost a reference to it, so you can't unsubscribe now.
Easy fix could be to check whether you already subscribed and unsubscribe if so, before subscribing:
this.firstTimerSubscription ? this.firstTimerSubscription.unsubscribe: false;
this.firstTimerSubscription = this.source.subscribe(...
I wouldn't use a second timer. Just do a interval of 10 seconds. The interval emits the iteration number 1, 2, 3..... You can use the modulo operator on that tick. Following example code (for example with 1 second interval) prints true and false in console. After true it needs 3 seconds to show false. After false it needs 1 second to show true.
interval(1000).pipe(
map(tick => tick % 4 !== 0),
distinctUntilChanged(),
).subscribe(x => console.log(x));
I have a function that calculate a "total price" based on a quantity argument passed from a different component (qty) and uses this to display a div:
calculateTotal(qty) {
var t;
if (qty === 1) {
t = 250 //total = total + 250
}
else if (qty > 1) {
t = (qty * 250); //total = qty * 250
}
else {
t = this.state.total
}
return this.setState( { total: this.state.total + t })
}
It would always display the previous calculation rather than the current. So if I enter 1, and then 2 as the quantity, the first time I enter 1, nothing is displayed, and the second time I press 2, the amount displayed is 250 (when it should be 500)
If anyone has any advice as to what the best course of action would be, it would be greatly appreciated.
If it helps, here is the function in the other component that triggers it (they enter a quantity, it sends that quantity to the function):
handleChange(event) {
const key = Number(event.key)
if (key === "Backspace") {
this.setState({qty: 0})
this.props.handleTotal(0);
} else {
this.setState({qty: key})
this.props.handleTotal(this.state.qty);
}
}
Looks like the problem is in the parent component's handleChange. You're calling setState and then hoping to pass the new value to the next function, but this.state.qty will be unchanged in the next line because setState is async.
handleChange(event) {
const key = Number(event.key)
if (key === "Backspace") {
this.setState({qty: 0})
this.props.handleTotal(0);
} else {
this.setState({qty: key})
// this.props.handleTotal(this.state.qty); // <-- this will return the old value because setState above isn't done running
this.props.handleTotal(key); // <-- try this instead
}
}
calculateTotal(qty) {
var t;
if (qty === 1) {
t = 250 //total = total + 250
}
else if (qty > 1) {
t = (qty * 250); //total = qty * 250
}
else {
t = (this.state.total * 2);
}
this.setState( { total: t });
return t;
}
Please, check if this works out!
I'm making a card game and I have a parent component which holds all my methods. The methods are my "logic" and it determines how to set the randomly generated hand according to 'house-way'. Think of house-way as a set of rules on how to play a certain hand.
I'm having a problem with how to structure my components. I've heard that you should keep your components small and my component is already 300 lines long.
Any advice on how to restructure this? I've tried putting methods on a different file and importing them, but I had trouble when it comes to setting state. In other words, 'this.setState()' throws an error unless it is a method of a class.
the code is a mess, but I basically need help with how to make my component less bloated.
export default class Layout extends Component {
constructor(props) {
console.log("starting up");
super(props);
//each hand holds a randomly generated tile object from { tilesSet }
this.state = {
//needs empty spots for when (mounting) <Hands hand1={this.state.hand[0].img} /> else error since hand[0] doesnt exist.
hand: ["", "", "", ""],
cards: false,
pairName: '',
rule: '',
show: false,
history: [],
test: 'test'
};
//binding in the constructor is recommended for performance.
this.handleToggle = this.handleToggle.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleHW = this.handleHW.bind(this);
this.assignHands = this.assignHands.bind(this);
this.checkPair = this.checkPair.bind(this);
this.checkTeenDey = this.checkTeenDey.bind(this);
this.hiLowMiddle = this.hiLowMiddle.bind(this);
this.compare = this.compare.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
//n = pairTL, n2 = otherTL
split(n, n2){
//Gee Joon
if (n[0].pair === 1) {
let combo1 = baccaratCount(n2[0], n[0]);
let combo2 = baccaratCount(n2[1], n[1]);
//if it meets the split requirements...
if((combo1 >= 7 && combo2 >= 9) || (combo1 >= 9 && combo2 >= 7)){
console.log('got em', combo1, combo2);
return true;
}
else {
return true;
}
//Teen/Dey
// } else if(high[0].val === 2) {
// var combo1 = baccaratCount(high[0].val, low[0].val);
// var combo2 = baccaratCount(high[0].val, low[1].val);
// //checks if any of the tiles are 7,8, or 9. for 9 gong and wong.
// var check7_9 = low[0].val >= 7 && low[0].val <= 9;
// var check7_9_2 = low[1].val >= 7 && low[1].val <= 9;
// //regular 6-8 split rule.
// if((combo1 >= 6 && combo2 >= 8) || (combo1 >= 8 && combo2 >= 6)){
// moveTiles("split");
// return true;
// //we might have 7,8,9 with T/D. (with 8 and 9, it turns to 0 and 1, so we need this part.)
// } else if (check7_9 === true || check7_9_2 === true){
// //if both are 7,8, or 9
// if (check7_9 === check7_9_2){
// moveTiles("split");
// return true;
// //only if 1..
// } else if (check7_9 === true && check7_9_2 === false){
// if (low[1].val >= 3 && low[1].val <=6){
// moveTiles("split");
// return true;
// } else {
// moveTiles();
// return true;
// }
// //if other one...
// } else{
// if (low[0].val >= 3 && low[0].val <=6){
// moveTiles("split");
// return true;
// } else {
// moveTiles();
// return true;
// }
// }
// //does not split.
// } else {
// moveTiles();
// return;
// }
// } else {
// // all other pairs. split pairs are in one array with a length of 2. ex: [7, 9]
// var combo1 = baccaratCount(high[0].val, low[0].val);
// var combo2 = baccaratCount(high[0].val, low[1].val);
// if(combo1 >= high[0].split[0] && combo2 >= high[0].split[0]){
// moveTiles("split");
// } else {
// moveTiles();
// }
// return true;
// }
}
}
//checks for pairs. takes an array as arg
checkPair(hand){
for(let i = 0; i < hand.length; i++) {
for (let ii = 0; ii < hand.length; ii++) {
// if there is a pair and it is not comparing to itself.
if (hand[i].pair === hand[ii].pair && i !== ii) {
let pairTL = hand.filter((x) => x.rank === hand[i].rank); //array of the pair tiles
let otherTL = hand.filter((x) => x.rank !== hand[i].rank); // array of the other 2 tiles. use these two to move tiles accordingly
//if we split this pair...
if (hand[i].split !== false) {
//returns true if it split..
if(this.split(pairTL, otherTL)) {
let copyArr = [pairTL[0], otherTL[0], pairTL[1], otherTL[1]];
this.setState(() => ({hand: copyArr}));
}
else {
let copyArr = otherTL.concat(pairTL);
this.setState(() => ({hand: copyArr}));
return true;
}
}
//don't split
else {
let copyArr = otherTL.concat(pairTL); //concats the two small arrays together and renders.
this.setState(() => ({hand: copyArr, pairName: pairTL[0].name, rule: 'Don\'t Split'}))
return true;
}
}
}
}
return false; // no pairs
}
//will not execute if there is a pair...(checkPair returns true)
checkTeenDey(hand){
//true if we have teen or dey
let teenDey = hand.find((el) => el.val === 2) !== undefined;
//if true...
if(teenDey){
let tile = hand.find((el) => el.val === 2); // teen/ dey object
let tempArr = hand.filter((el) => el.name !== tile.name); //new arr without marked teen/dey. arr.length = 3
let secondTeenDey = tempArr.find((el) => el.val === 2); //second teen/dey (not pair)
let seven = tempArr.find((el) => el.val === 7);
let eight = tempArr.find((el) => el.val === 8);
let nine = tempArr.find((el) => el.val === 9);
//if there is a second teen/dey
if (secondTeenDey){
let twoArr = tempArr.filter((el) => el.name !== secondTeenDey.name);
console.log(tile, secondTeenDey, twoArr);
return true;
}
//look for 7,8,9
else if (seven){
console.log (seven);
return true;
}
else if(eight){
return true;
}
else if(nine){
return true;
}
}
// no teen or dey...
else{
return false;
}
}
//point system used for sort()
compare(a,b){
let comparison = 0;//no change
if(a.realValue < b.realValue){
comparison = -1;//a comes before b
}
else if(a.realValue > b.realValue){
comparison = 1;//b comes before a
}
return comparison;
}
//will not execute if there is a teen dey...
hiLowMiddle(hand){
//makes a new copy of hand and sorts it using sort()'s point system.
let sortedArr = hand.slice().sort(this.compare); //slice used, else mutates hand.
let tempHair = [sortedArr[0], sortedArr[3]];
let tempBack = [sortedArr[1], sortedArr[2]];
let hiLow = tempHair.concat(tempBack); //newly sorted arr
this.setState(() => ({hand: hiLow, rule: 'Hi-Low-Middle'}));
}
//generates new hand and updates them to state.
assignHands() {
let tempArr = [0, 0, 0, 0]; //filler array
let testArr = tilesSet.slice(); //filler array. tilesSet is untouched
//loops through and assigns random tile from deck
let newArr = tempArr.map((x) => {
let counter = Math.floor(Math.random()* (testArr.length - 1));
//used to hold the selected obj. needed since splice changes arr.length and we splice/display the different indexes.
let dummyArr = testArr[counter];
testArr.splice(counter, 1);
return dummyArr;
})
//updates state
this.setState({hand: [newArr[0], newArr[1], newArr[2], newArr[3]], show: true, history: [...this.state.history, [...newArr]]})
}
handleSubmit = (e) => {
e.preventDefault();
}
//toggle effect.
handleToggle = () => {
this.setState(() => ({cards: !this.state.cards}));
}
handleClick = () => {
assignHands(tilesSet);
//works, but not 100% properly. the changes are one step behind. fix async.
//check to see the history length. max set # 20
if(this.state.history.length >= 10){
let temp = this.state.history.slice();
temp.shift();
this.setState(() => ({history: temp}))
}
}
//House Way
handleHW(){
if(!this.checkPair(this.state.hand)){
if(!this.checkTeenDey(this.state.hand)){
this.hiLowMiddle(this.state.hand);
}
}
}
render() {
return (
<div>
{this.state.show ? <h1>{baccaratCount(this.state.hand[0], this.state.hand[1]) + '/' + baccaratCount(this.state.hand[2], this.state.hand[3])}</h1> : <h1>Press New Hand to Start</h1>}
<form onSubmit={this.handleSubmit}>
<label>
<input type='text'/>
</label>
<button>submit</button>
</form>
<Input test={this.state.test}/>
<Hands
cards={this.state.cards}
hand1={this.state.hand[0].img}
hand2={this.state.hand[1].img}
hand3={this.state.hand[2].img}
hand4={this.state.hand[3].img}
/>
<Buttons
type="button"
className="btn btn-dark"
handleClick={this.handleClick}
handleHW={this.handleHW}
hand={this.state.hand}
/>
<h2>Value of tiles: {this.state.hand[0].val ? this.state.hand[0].val : "0"} - {this.state.hand[1].val ? this.state.hand[1].val : "0"} - {this.state.hand[2].val ? this.state.hand[2].val : "0"} - {this.state.hand[3].val ? this.state.hand[3].val : "0"}</h2>
<h2>Pair Name: {this.state.pairName}</h2>
<h2>Rule: {this.state.rule}</h2>
<h2>
History: <div>{this.state.history.map((el) => <li key={Math.random()}>{el.map((ele) => ele.name+ '--')}</li>)}</div>
</h2>
</div>
);
}
}
I am trying to add extra rows on a table that is generated using results.map, but the code that I have added to generate the extra rows isn't executing. Does anyone know why this wouldn't work?
< tbody >
{
results.map(result => {
let rowNodes = [];
rowNodes.push(<td className="hidden">{result.itemCtrlType}</td>);
if (this.props.gridNumber == 1) {
rowNodes.push(<td className="in-td-item">{result.metric}</td>);
} else {
rowNodes.push(<td className="in-td-item hidden">{result.metric}</td>);
}
for (const hour in result) {
if (hour.indexOf('t') == 0 && hour.length == 3) {
let now = Date.now();
let d = new Date(now);
let hours = d.getHours();
let time = hours.toString();
if (hours < 10) {
time = 't0' + hours;
}
else {
time = 't' + hours;
};
let classname = "in-td";
if (hour == time && this.props.dateAdjust == 0) {
classname += " cell-timenow";
}
if (hour == 't23') {
classname += " table-lastcolumn";
}
let date = [pad(d.getMonth() + 1), pad(d.getDate()), d.getFullYear()].join('/');
rowNodes.push(<td key={hour} className={classname} >{result[hour]}</td>)
}
}
if (this.props.gridNumber == this.props.totalGrids) {
rowNodes.push(<td className="in-td-addcolumn in-td"></td>);
}
return <tr key={result.metric} onClick={this.handleClick}>{rowNodes}</tr>
})
}
{
() => {
console.log(this.props.rowsCount > results.length);
if (this.props.rowsCount > results.length) {
let diff = this.props.rowsCount - results.length;
var rowNodes = [];
for (let m = 0; m < diff; m++) {
rowNodes.push(<td className="hidden"></td>);
if (this.props.gridNumber == 1) {
rowNodes.push(<td className="in-td-item"></td>);
} else {
rowNodes.push(<td className="in-td-item hidden"></td>);
}
for (let n = 0; n < 24; n++) {
rowNodes.push(<td className="in-td"></td>);
}
if (this.props.gridNumber == this.props.totalGrids) {
rowNodes.push(<td className="in-td-addcolumn in-td"></td>);
}
return <tr>{rowNodes}</tr>
}
}
}
}
</tbody>
I have tried various combinations of wrapping it in divs or changing where it returns but nothing seems to work.
Anyone know why this isn't firing?
First of all, that is not a good way of organising code, instead of putting the function directly inside JSX, better to define it outside of render method and then call.
Solution:
Issue with your code is, you defined a function inside JSX, but you are not calling it, Use IIFE and call that function, also return null when if condition fails, Like this:
{
(() => {
console.log(this.props.rowsCount > results.length);
if (this.props.rowsCount > results.length) {
let diff = this.props.rowsCount - results.length;
var rowNodes = [], elements = [];
for (let m = 0; m < diff; m++) {
rowNodes = [];
rowNodes.push(<td className="hidden"></td>);
if (this.props.gridNumber == 1) {
rowNodes.push(<td className="in-td-item"></td>);
} else {
rowNodes.push(<td className="in-td-item hidden"></td>);
}
for (let n = 0; n < 24; n++) {
rowNodes.push(<td className="in-td"></td>);
}
if (this.props.gridNumber == this.props.totalGrids) {
rowNodes.push(<td className="in-td-addcolumn in-td"></td>);
}
elements.push(<tr key={m}>{rowNodes}</tr>);
}
return elements;
}
return null;
})()
}
Note:
Whenever execution find return statement inside for loop, it will immediately break the loop and return that result, so instead of using return use one more variable and put the tr inside that, Return the result only after for loop completion.
Suggestion:
Assign unique key to each dynamically created elements inside loop.
Check DOC.
Check this answer for more details about keys: Understanding unique keys for array children in React.js