useEffect EventListener problem REACT HOOKS - javascript

I was trying to make a simple Snake-Game with REACT. Everything was good till I needed to use useEffect to move "snake" when keydown fire.
When I try to put moveSnake() into useEffect it's giving me some error. When I move it and call it outside effect it's making an infinity loop. I'm using functional components. App.js is messed around because I was stressed out because it's not working and tried every single option. Hope you will get everything.
https://github.com/Pijano97/snake-game code is here, thanks.
Also if someone can not access that, here is the code.
The snake component just renders snakeDots with a map.
Food component just creating random food on the map.
import { useEffect, useState } from "react";
import "./App.css";
import Snake from "./Snake";
import Food from "./Food";
function App() {
const getRandomFoodDot = () => {
let min = 1;
let max = 98;
let x = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
let y = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
return [x, y];
};
const [snakeDots, setSnakeDots] = useState([
[0, 0],
[2, 0],
]);
const [foodDots, setFoodDots] = useState(getRandomFoodDot());
const [direction, setDirection] = useState("");
const moveSnake = () => {
let dots = [...snakeDots];
let head = dots[dots.length - 1];
switch (direction) {
case "UP":
head = [head[0], head[1] - 2];
break;
case "DOWN":
head = [head[0], head[1] + 2];
break;
case "LEFT":
head = [head[0] - 2, head[1]];
break;
case "RIGHT":
head = [head[0] + 2, head[1]];
break;
default:
break;
}
// adding new head
dots.push(head);
// removing last dot
dots.shift();
setSnakeDots(dots);
};
moveSnake();
useEffect(() => {
const keyPressHandler = (e) => {
switch (e.keyCode) {
case 38:
setDirection("UP");
break;
case 40:
setDirection("DOWN");
break;
case 37:
setDirection("LEFT");
break;
case 39:
setDirection("RIGHT");
break;
default:
setDirection("");
break;
}
};
document.addEventListener("keydown", keyPressHandler);
return () => {
document.removeEventListener("keydown", keyPressHandler);
};
}, []);
console.log(direction);
return (
<div className="app">
<div className="snake">
<Snake snakeDots={snakeDots} />
<Food foodDots={foodDots} />
</div>
</div>
);
}
export default App;

I am not sure if I got it correct, but probably you can try this:
import { useEffect, useState } from "react";
import "./App.css";
import Snake from "./Snake";
import Food from "./Food";
const getDirection = e => {
switch (e.keyCode) {
case 38:
return 'UP';
case 40:
return 'DOWN';
case 37:
return 'LEFT';
case 39:
return 'RIGHT';
default:
return '';
}
}
function App() {
const getRandomFoodDot = () => {
let min = 1;
let max = 98;
let x = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
let y = Math.floor((Math.random() * (max - min + 1) + min) / 2) * 2;
return [x, y];
};
const [snakeDots, setSnakeDots] = useState([
[0, 0],
[2, 0],
]);
const [foodDots, setFoodDots] = useState(getRandomFoodDot());
const [direction, setDirection] = useState({val: ''});
useEffect(() => {
let dots = [...snakeDots];
let head = dots[dots.length - 1];
switch (direction.val) {
case "UP":
head = [head[0], head[1] - 2];
break;
case "DOWN":
head = [head[0], head[1] + 2];
break;
case "LEFT":
head = [head[0] - 2, head[1]];
break;
case "RIGHT":
head = [head[0] + 2, head[1]];
break;
default:
break;
}
// adding new head
dots.push(head);
// removing last dot
dots.shift();
setSnakeDots(dots);
}, [direction]);
useEffect(() => {
const keyPressHandler = (e) => {
setDirection({ val: getDirection(e) });
};
document.addEventListener("keydown", keyPressHandler);
return () => {
document.removeEventListener("keydown", keyPressHandler);
};
}, []);
console.log(direction);
return (
<div className="app">
<div className="snake">
<Snake snakeDots={snakeDots} />
<Food foodDots={foodDots} />
</div>
</div>
);
}
export default App;
I just created getDirection func for getting direction from a key event and added one more useEffect with logic from the moveSnake function
I didn't know the current functionality of your project, but the code above works, and the shake can be moved.

The reason you're getting that error which says React limits the amount of re-renders to provent infinate re-renders is because you call moveSnake() inside your App() component, and moveSnake() calls setSnakeDots() which updates the state of snakeDots. When the state of snakeDots gets updated, your App component rerenders, which triggers the calling of moveSnake(), which calls setSnakeDots() which updates the state of your snakeDots which re-renders your App component. Your stuck in an inifinate loop here.
Something that might fix it(although I tested it and the game doesn't proceed past one step) would be setting your moveSnake() function in a timer like
function timer(){
setInterval(function() {
moveSnake();
timer()
}, 1000);
}
timer()

Related

Javascript alternate player turn

I am currently working on a board game like chess.
I can't seem to make alternate turn work.
const clickPiece = (e: React.MouseEvent) => {
const element = e.target as HTMLElement;
const shogiBoard = shogiBoardRef.current;
let steps = 1;
if (steps % 2 === 1) {
setActivePlayer(Player.US);
steps++;
} else if (steps % 2 === 0) {
setActivePlayer(Player.ENEMY);
steps++;
}
if (element.classList.contains("shogiPiece") && shogiBoard && activePlayer) {
const takeX = Math.floor((e.clientX - shogiBoard.offsetLeft) / CELL_SIZE);
const takeY = Math.abs(Math.ceil((e.clientY - shogiBoard.offsetTop - BOARD_SIZE) / CELL_SIZE));
setTakePosition({
x: takeX,
y: takeY,
});
const x = e.clientX - CELL_SIZE / 2;
const y = e.clientY - CELL_SIZE / 2;
element.style.position = "absolute";
element.style.left = `${x}px`;
element.style.top = `${y}px`;
setActivePiece(element);
}
};
activePlayer initially Player.US which comes from an enum:
export enum Player {
ENEMY,
US,
}
activePlayer useState:
const [activePlayer, setActivePlayer] = useState<Player>(Player.US);
For me to alternate turns seemed the easiest when grabbing a piece, check which player is up then change, tried it with increasing the number of steps and check for remainder but it is not working.
Thank you in advance for the suggestions and help.
The root cause your code is not working is how you are using step.
Step is part of your application's state, so this should be an state, not a simple variable. Your are actually resetting step = 1 in each render
First define [step, setStep] using useState:
const [step, setStep] = useState<number>(1); // instead of let steps = 1
and then update the step like this:
setStep(i => i+1) // instead of steps++
You can use this function to toggle the active player: check which is the current player and then set the other one:
TS Playground
import {useCallback, useState} from 'react';
enum Player { ENEMY, US }
function Component () {
const [activePlayer, setActivePlayer] = useState<Player>(Player.US);
const setAlternatePlayer = useCallback(
() => setActivePlayer(player => player === Player.US ? Player.ENEMY : Player.US),
[],
);
// Use function "setAlternatePlayer"...
}
This is because you if condition is wrong !
Change your if statement to this:
if (steps % 2 === 0) {
setActivePlayer(Player.ENEMY);
steps++;
} else {
setActivePlayer(Player.US);
steps++;
}
This will work

Problem updating a state using setInterval in React application

I have a function called getRandomHexagram() which returns a random 6-character string that is used for props in my <hexagram string={stringstate} /> component in my I Ching divination app.
I also have a function called resettimer() which retrieves this random 6-character string multiple times and passes it to my component within a few seconds, so that while setInterval is running, the component will keep changing its appearance until clearInterval is called.
I would like to continuously update the state using resettimer() function run until it ends, and then use the new state in another function together with clearInterval.
What is strange to me is that while the if portion of the timer is running, the component continuously updates as it should. But when I try to call the final state in the } else { statement together with clearInterval, console always shows that stringstate is still "VVVVVV", or whatever was the last prop that my component used in my previous click, NOT what my component is currently using.
function getRandomHexagram() {
const flipcoins = () => {
return (
(Math.floor(Math.random() * 2) + 2) +
(Math.floor(Math.random() * 2) + 2) +
(Math.floor(Math.random() * 2) + 2)
);
};
const drawline = (coinvalue) => {
let line;
if (coinvalue == 6) {line = "B";}
if (coinvalue == 7) {line = "V";}
if (coinvalue == 8) {line = "P";}
if (coinvalue == 9) {line = "W";}
return line;
};
return (
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins())
);
}
function App() {
const [stringstate, setStringstate] = useState("VVVVVV");
function resettimer() {
var timesrun = 0;
var randomtime = Math.floor(Math.random() * 15) + 10;
const interval = setInterval(() => {
timesrun += 1;
if (timesrun < randomtime) {
thisString = getRandomHexagram();
setStringstate(thisString);
console.log("the current permutation is: " + thisString);
// console and component shows this correctly.
} else {
clearInterval(interval);
console.log("the final state state is: " + stringstate);
// Call another function here using the NEW stringstate
// But console shows that stringstate is still showing
// the old state of VVVVVV despite my component showing otherwise
}
}, 100);
}
... ...
return (
<div className="App">
<Button onClick={() => resettimer()}>
<Hexagram string={stringstate} />
</div>
);
}
When the button is clicked and the function invoked, my console shows this:
the current permutation is: VPPVPB
the current permutation is: VPPPVV
the current permutation is: BPBVBV
the current permutation is: VVPVPV
the current permutation is: PPBVPV
the current permutation is: PPPBBB
the current permutation is: VVPPPV
the final state state is: VVVVVV
Any idea why this is happening? Any advice is much appreciated.

How to create an arbitrary mapping in javascript?

I have a React app where I want to display lunar phases. To that end I tried to create an empty array and then map it appropriately:
import React from 'react'
export const MoonPhase = (props) => {
let start = new Date(props.start)
let end = new Date(props.end)
let delta = (end.getTime() - start.getTime()) / 1000
let quarters = new Array(Math.floor(
// Each lunar month is 2.55ᴇ6s.
(delta / (2.55 * Math.pow(10, 6))) * 4
))
return (
<div className='phases'>
{quarters.map((_, i) => {
switch(i % 4) {
case 0: return <span>🌑</span>
case 1: return <span>🌓</span>
case 2: return <span>🌕</span>
case 3: return <span>🌗</span>
}
})}
</div>
)
}
This doesn't produce anything, but if I change quarters to [1,2,3,4,5] I get output.
How should I approach this?

How do I code this collision event (between the snake head(last item in the array) and the fruit) in JS?

I am making a game of Snake using JS, html and CSS, having a problem with the JS. Specifically the collision event between the snake and fruit, where the snake eats the fruit and it disappears and reappears randomly. Is the if statement after the switch correct?
document.addEventListener('DOMContentLoaded', () => {
const squares = document.querySelectorAll('.grid div')
const width = 10
let currentIndex = 0
let snakes = [2,1,0] //2 is head, 1 is body, 0 is tail
let direction = 1
snakes.forEach(snake => squares[snake].classList.add('snake'))
setInterval(() => {
const tail = snakes.pop() //removes the last item of the array
squares[tail].classList.remove('snake')
snakes.unshift(snakes[0] + direction)
console.log(snakes)
squares[snakes[0]].classList.add('snake')
}, 1000)
function moveMySnake(e) {
squares[currentIndex].classList.remove('snake')
switch(e.keyCode) {
case 37:
direction = -1
break
case 38:
direction = -width
break
case 39:
direction = 1
break
case 40:
direction = width
break
}
if(snakes[0].contains('fruit')) {
snakes[0].remove('fruit')
fruitReappears()
}
function fruitReappears() {
const randomIndex = Math.floor(Math.random() * squares.length)
const randomSquare = squares[randomIndex]
randomSquare.classList.add('fruit')
}
document.addEventListener('keyup', moveMySnake)
document.addEventListener('keydown', e => e.preventDefault())
})
currently the snake just passes through the fruit and nothing happens?

Letter to GPA Conversion

I am getting a NaN return when I convert my letter input to number and then pass it to another function that totals and averages them. Is there a problem with my conversion function or the way I am calling the function?
function CalcGPA(a, b, c, d, e, f, g, h, i, j) {
var initial = a + b + c + d + e + f + g + h + i + j;
var total = initial / 10;
return total;
}
function Convert(z) {
var x = z.toString().toUpperCase();
switch (x.value) {
case "A":
return 4.0;
case "A-":
return 3.67;
case "B+":
return 3.33;
case "B":
return 3.0;
case "B-":
return 2.67;
case "C+":
return 2.33;
case "C":
return 2.0;
case "C-":
return 1.7;
case "D+":
return 1.3;
case "D":
return 1.0;
case "F":
return 0;
}
}
$(document).ready(function () {
var input2 = $('[name="grade1"],[name="grade2"],[name="grade3"],[name="grade4"],[name="grade5"],[name="grade6"],[name="grade7"],[name="grade8"],[name="grade9"],[name="grade10"],[name="grade11"]');
input2.keyup(function () {
var total2;
Convert((input2[0]));
Convert((input2[1]));
Convert((input2[2]));
Convert((input2[3]));
Convert((input2[4]));
Convert((input2[5]));
Convert((input2[6]));
Convert((input2[7]));
Convert((input2[8]));
Convert((input2[9]));
total2 = CalcGPA(input2[0], input2[1], input2[2], input2[3], input2[4], input2[5], input2[6], input2[7], input2[8], input2[9]);
total2.toFixed(2);
$(input2[10]).val(total2);
});
});
Call CalcGPA on the value obtained by running Convert() on the array elements. Alternatively, store the 'Converted' values to temporary variables, and then call CalcGPA with them as arguments. Something like this -
function CalcGPA(a, b, c, d, e, f, g, h, i, j) {
var initial = a + b + c + d + e + f + g + h + i + j;
var total = initial / 10;
return total;
}
function Convert(z) {
var x = z.toString().toUpperCase();
switch (x.value) {
case "A":
return 4.0;
case "A-":
return 3.67;
case "B+":
return 3.33;
case "B":
return 3.0;
case "B-":
return 2.67;
case "C+":
return 2.33;
case "C":
return 2.0;
case "C-":
return 1.7;
case "D+":
return 1.3;
case "D":
return 1.0;
case "F":
return 0;
}
}
$(document).ready(function () {
var input2 = $('[name="grade1"],[name="grade2"],[name="grade3"],[name="grade4"],[name="grade5"],[name="grade6"],[name="grade7"],[name="grade8"],[name="grade9"],[name="grade10"],[name="grade11"]');
input2.keyup(function () {
var total2;
total2 = CalcGPA(Convert(input2[0]), Convert(input2[1]), Convert(input2[2]), Convert(input2[3]), Convert(input2[4]), Convert(input2[5]), Convert(input2[6]), Convert(input2[7]), Convert(input2[8]), Convert(input2[9]));
total2.toFixed(2);
$(input2[10]).val(total2);
});
});
To answer your question:
You are not storing the value of your Convert() calls anywhere, so when you call CalcGPA(), you are just sending the original letter grades into the function.
To make your code better:
I would also strongly recommend that you rethink the way you are collecting values from the various input fields and using them within your calculations.
For example, I would start by applying a common class across the inputs. That way there could be 1 input or 20 inputs, and you could code to handle anything.
Let's say I give each input a class name of gradeLetterInput
Then you might be able to rewrite the $(document).ready() and CalcGPA() functions like this:
$(document).ready(function () {
// get collection of inputs
var $gradeLetterInputs = $('.gradeLetterInput');
var gpaValues = $.map($gradeLetterInputs, function($item, idx) {
return Convert($item.val());
});
var gpaAvg = CalcGPA(gpaValues);
// not shown place avg GPA on DOM somwhere
}
function CalcGPA(arr) {
var total = 0;
var count = arr.length;
for (i=0;i<count;i++) {
total += arr[i];
}
return total/count;
}
Anytime you see yourself relying on id's or input names like something# or adding a bunch of parameters to a function call for a set of similar items, you should recognize this as an anti-pattern and try to think about how you can refactor your code to eliminate such usage.

Categories