Javascript alternate player turn - javascript

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

Related

State seems to be reverting back to its initial state after useeffect is run

I am having an issue where it seems a state is reverting to its initial state without being prompted to. The state is change in a function, which then triggers a useeffect. The useffect then runs, recognizing and console.logging the update state. Then however, the next call to the function says the state is still in its initial state. My code is below, unfortunately I cant include a full snippet as it includes some things that cannot be shared/run.
Here is where I declare my states:
const [playerTeam, setPlayerTeam] = useState({});
const [movement, setMovement] = useState({ active: false, character: null });
Here is my useeffect:
useEffect(() => {
const canvas = canvasReference.current;
const context = canvas.getContext("2d");
console.log("drawing", movement);
setCharacterUpdate(false);
mapImage.onload = () => {
canvas.width = mapImage.width;
canvas.height = mapImage.height;
context.drawImage(mapImage, 0, 0);
for (const [key, value] of Object.entries(playerTeam)) {
context.drawImage(
playerTeam[key].spriteImg,
playerTeam[key].x,
playerTeam[key].y
);
}
};
}, [playerTeam, movement]);
Here is where I change states that I expect to trigger the useeffect:
function handleClick(canvas, e) {
console.log(movement);
//get the coordinates of the user's curson on click
let rect = canvas.getBoundingClientRect();
let x = e.clientX - rect.left;
let y = e.clientY - rect.top;
//movement mode is active (last click was a click on an ally character)
if (movement.active) {
moveCharacter(x, y);
}
//if we arent in movement mode...
else {
//cycle through player's team
for (const [key, value] of Object.entries(playerTeam)) {
//if the coordinates of the click are on the same 48 x 48 tile as one of the ally characters
if (
x >= playerTeam[key].x &&
x <= playerTeam[key].x + 47 &&
y >= playerTeam[key].y &&
y <= playerTeam[key].y + 47
) {
console.log("Batman!");
const enterMovement = {
active: true,
character: playerTeam[key].name,
};
//put us in movement mode
setMovement(enterMovement);
break;
}
}
}
}
//function for moving the character
function moveCharacter(x, y) {
console.log("here");
let newTeam = playerTeam;
newTeam[movement.character].x = x - (x % 48);
newTeam[movement.character].y = y - (y % 48);
setPlayerTeam(newTeam);
let exitMovement = { active: false, character: null };
setMovement(exitMovement);
console.log(movement, playerTeam["batman"]);
}
Here are some things I noticed that confuse me:
The handleClick function never reads movement.active as true. The console log at the beginning of handle click shows it is false on every call to the function.
The useeffect function, however, has a console log in it that shows that the movement field is, in fact, being changed to true. The useeffect runs on each click, indicating that the movement field is in fact changing, but somehow between the useffect triggering and the next click, the movement.active field is reverting back to its initial state.
*The playerTeam state is not empty by the way, it is populated in a separate section of code.
Thanks for any ideas/answers!

Create a function that returns the next/prev 4 results in an array of objects

I'm trying to create a function that renders the next & prev 4 results in an array of objects onClick. Currently I am only returning the first 4 items and their images and adjusting the values in the return statement onClick and am not happy with this solution. Any pointers?
const ImageSlider = ({ loadData, data, setData }) => {
const [current, setCurrent] = useState(0);
const length = data.length;
const [test, setTest] = useState(0);
const [another, setAnother] = useState(4);
useEffect(() => {
loadData().then((data) => setData(data));
}, []);
const getNextSlide = () => {
setCurrent(current === length - 1 ? 0 : current + 1);
if (current / 3 === 1) {
setTest(test + 4);
setAnother(another + 4);
}
};
const getPrevSlide = () => {
setCurrent(current === 0 ? length - 1 : current - 1);
// setTest(test - 1);
// setAnother(another - 1);
};
console.log(current);
if (!Array.isArray(data) || length <= 0) {
return null;
// Need that error message here
}
return (
<div className="slider">
<FaArrowCircleLeft className="slider-left-arrow" onClick={getPrevSlide} />
<FaArrowCircleRight
className="slider-right-arrow"
onClick={getNextSlide}
/>
{data.slice(test, another).map((program, index) => {
return (
<div
className={
index === current
? "slider-active-program"
: "slider-inactive-program"
}
key={index}
>
<img
src={program.image}
alt="program"
className="slider-program-image"
/>
</div>
);
})}
</div>
);
};
export default ImageSlider;
This may be one possible approach to achieve the desired objective:
const ImageSlider = ({ loadData, data, setData }) => {
const imageLength = 4; // number of images to be shown per-page
const totalImages = data.length;
const [startAt, setStartAt] = useSate(0); // start rendering from image-0
useEffect(() => {
loadData().then((data) => setData(data));
}, []);
const switchImages = (direction = 'forward') => (
setStartAt(prev => {
if (direction === 'forward') {
// if there are more images, reposition 'startAt' to next set
if (prev + imageLength < totalImages) return prev + imageLength;
return 0; // reset back to image-0
} else {
// if there is previous set, reposition to it's start
if (prev - imageLength >= 0) return prev - imageLength;
// otherwise, reposition to the start of the last-set
return (Math.floor((totalImages - 1) / imageLength) * imageLength)
};
})
);
return (
<div className="slider">
<FaArrowCircleLeft
className="slider-left-arrow"
onClick={() => switchImages('reverse')}
/>
<FaArrowCircleRight
className="slider-right-arrow"
onClick={() => switchImages()}
/>
{Array.isArray(data) && data.filter((el, idx) => idx >= startAt && idx < (startAt + imageLength)).map((program, index) => {
return (
<div
className={
index === current
? "slider-active-program"
: "slider-inactive-program"
}
key={index}
>
<img
src={program.image}
alt="program"
className="slider-program-image"
/>
</div>
);
})}
</div>
);
};
export default ImageSlider;
Explanation
imageLength is fixed (set to 4)
startAt is a state-variable (initially set to 0)
data is rendered on return only when it is an Array
switchImages is a method invoked on both left-arrow and right-arrow click-events (changing the parameter direction to reverse on left-arrow)
startAt is updated based on the direction, the prev value, the totalImages and imageLength.
Suggestion
If one needs to render a message to the user that the data array is either not-yet-loaded or it is empty, it may be acccomplished by changing the {Array.isArray(data) && .... into a conditional such as {Array.isArray(data) ? ....<existing> : <some message here>.
Warning
The above code is only an approach. It is not tested & may contain syntax errors or other issues. Please use comments to share feedback and this answer may be updated accordingly.
I don't think you have to change much about your approach to get what you're looking for here.
To render the images to the left and right of your current selection and allow looping from the front to the back (as it looks like you want to do), we can paste a copy of the array to the front and back and select the middle slice. It's a little bit like this:
The implementation would just require you to replace the current .map(...) method in your return statement with this one:
[...data, ...data, ...data].slice(
length + current - peekLeft,
length + current + peekRight + 1
).map((program, index) => {
...
}
And then you can also remove the test and another state objects as well since they aren't necessary. You just need to add the peekLeft and peekRight or replace them inline inside the .map(...) statement. The first option may look something like this:
const peekLeft = 4;
const peekRight = 4;
const ImageSlider = ({ loadData, data, setData }) => {
...
}

setAttribute not working consistently on change event

I'm having trouble getting a set of change events to work consistently: I have a table set up that checks to see whether either an input has been updated and then, depending on what input gets updated, the code submitted below either changes the margin or the price.
If I only change the price, I consistently calculate margin correctly, and if I only change the margin, I consistently calculate price correctly. If I change one and then the other, the second setAttribute doesn't work. (e.g. changing the price updates the margin, but, after that, changing margin, does not update price).
I'm new to JS, but I've tried debugging and can't seem to nail this down. Any help would be much appreciated.
see link for codepen: https://codepen.io/skeanerw/pen/mdBpppE
function calcBuySellMargin(bracket, rowID) {
const vendPriceID = document.getElementById(`VendPrice_${bracket}_${rowID}`);
const sellPriceID = document.getElementById(`ItemPrice_${bracket}_${rowID}`);
const marginID = document.getElementById(`Margin_${bracket}_${rowID}`);
const priceMeth = document.getElementById(`priceMeth_${rowID}`).innerText;
const vpUOM = document.getElementById(`vpUOM_${rowID}`).innerText;
//when we change the margin adjust the sell price.
marginID.addEventListener('change', () => {
let marginValue = (marginID.value) / 100
let vendPriceValue = (vpUOM === 'M') ? vendPriceID.value / 1000 : vendPriceID.value;
let sellPriceVal = (priceMeth === 'M') ? ((vendPriceValue / (1 - marginValue)) * 1000).toFixed(0) : (vendPriceValue / (1 - marginValue)).toFixed(3);
sellPriceID.setAttribute('value', parseFloat(sellPriceVal));
})
//when we change the buy price or the sell price, adjust the margin.
function setMargin() {
let vendPriceValue = (vpUOM === 'M') ? vendPriceID.value / 1000 : vendPriceID.value;
let sellPriceVal = (priceMeth === 'M') ? sellPriceID.value / 1000 : sellPriceID.value;
const marginValue = parseFloat( (sellPriceVal - vendPriceValue) / sellPriceVal * 100 )
marginID.setAttribute('value', parseFloat(marginValue).toFixed(0));
}
vendPriceID.addEventListener('change', () => {
setMargin()
})
sellPriceID.addEventListener('change', () => {
setMargin()
})
}
window.onload = (event) => {
document.querySelectorAll('.marginDisplay').forEach(element => {
const rowID = (element.id.replace(/Margin_\d_/, ''));
let bracket = (element.id.replace(/Margin_/,''))
bracket = (bracket.match(/^[0-9]/, ''))
calcBuySellMargin(bracket, rowID);
})
}
I was able to resolve by replacing the setAttribute methods:
marginID.addEventListener('change', () => {
let marginValue = (marginID.value) / 100
let vendPriceValue = (vpUOM === 'M') ? vendPriceID.value / 1000 : vendPriceID.value;
let sellPriceVal = (priceMeth === 'M') ? ((vendPriceValue / (1 - marginValue)) * 1000).toFixed(0) : (vendPriceValue / (1 - marginValue)).toFixed(3);
sellPriceID.value = sellPriceVal;
})
function setMargin() {
let vendPriceValue = (vpUOM === 'M') ? vendPriceID.value / 1000 : vendPriceID.value;
let sellPriceVal = (priceMeth === 'M') ? sellPriceID.value / 1000 : sellPriceID.value;
const marginValue = parseFloat( (sellPriceVal - vendPriceValue) / sellPriceVal * 100 )
marginID.value = marginValue
}

Building a React clock with hooks

I'm working on building a clock that counts up, just a practice exercise (I know there are better ways of doing this).
My issue is when a minute is added, the "addMinute" seems to run twice, and I can't for the life of me figure out how or why.
Here is a demo on codesandbox: https://codesandbox.io/s/restless-frost-bud7p
And here is the code at a glance:
(please note, the counter only counts up to 3 and counts faster; this is just for testing purposes)
const Clock = (props) => {
const [seconds, setSeconds] = useState(0)
const [minutes, setMinutes] = useState(0)
const [hours, setHours] = useState(0)
const addHour = () => {
setHours(p => p + 1)
}
const addMinute = () => {
setMinutes(prev => {
if (prev === 3) {
addHour()
return 0
} else {
return prev + 1
}
})
}
const addSecond = () => {
setSeconds(prevState => {
if (prevState === 3) {
addMinute()
return 0
} else {
return prevState + 1
}
})
}
useEffect(() => {
const timer = setInterval(addSecond, 600)
return () => clearInterval(timer)
}, [])
return (
<div>
<h1>time!</h1>
<p>
{hours < 10 ? 0 : ""}{hours}
:
{minutes < 10 ? 0 : ""}{minutes}
:
{seconds < 10 ? 0 : ""}{seconds}
</p>
<p>
{seconds.toString()}
</p>
</div>
)
}
The issue is that you are using the React.StrictMode wrapper in the index.js file.
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
So you should decide between using strict mode or having side effects, the easy way is just removing the React.StrictMode wrapper. The other way is removing side effects, where you only need to do the following:
Update your addSecond and addMinute functions to something like:
const addMinute = () => {
setMinutes((prev) => prev + 1);
};
const addSecond = () => {
setSeconds((prevState) => prevState + 1);
};
And your useEffect call to something like:
useEffect(() => {
if(seconds === 3) {
addMinute();
setSeconds(0);
};
if(minutes === 3) {
addHour();
setMinutes(0);
}
const timer = setInterval(addSecond, 600);
return () => clearInterval(timer);
}, [seconds, minutes]);
Here an updated version of your code: https://codesandbox.io/s/goofy-lake-1i9xf
A couple of issues,
first you need to use prev for minutes, so
const addMinute = () => {
setMinutes(prev => {
if (prev === 3) {
addHour()
return 0
} else {
return prev + 1
}
})
}
And then you need to remove the React.StrictMode wrapper component from index, which is what is actually causing the double increase, as part of what the strict mode does is
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
see: https://codesandbox.io/s/pensive-wildflower-pujmk
So I had no idea about strict mode and the intentional double renders. After reading the documentation I finally understand the purpose of this.
As such, it appears the best solution is to have no side effects from the useEffect, and instead, handle that logic outside of the effect, but still changing every second.
So, I set an effect that has a piece of state starting at zero and going up by one per second.
Then, with each change of "time", the useMemo will recalculate how many hours, mins and seconds the total time is.
The only thing I don't like is all those calculations running every render! (But realistically those take but a few miliseconds, so performance doesn't seem to be an issue).
const [time, setTime] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setTime(p => p + 1)
}, 999);
return () => clearTimeout(timer);
}, [userState]);
const timeJSON = useMemo(() => {
const hrs = Math.floor(time/3600)
const mins = Math.floor( (time-(3600*hrs)) / 60 )
const secs = time - (3600 * hrs) - (60*mins)
return {
hrs,
mins,
secs,
}
}, [time])
return (
<div>
<p>
{timeJSON.hrs < 10 ? 0 : ""}{timeJSON.hrs}
:
{timeJSON.mins < 10 ? 0 : ""}{timeJSON.mins}
:
{timeJSON.secs < 10 ? 0 : ""}{timeJSON.secs}
</p>
</div>
)
Thanks again for everyone pointing me in the right direction on this!

useEffect EventListener problem REACT HOOKS

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()

Categories