Creating Coin Flip using Vanilla Javascript and decrement and this - javascript

I need to make a coin flip 20x and display the coin, and the results x times each. I am trying to use decrement to determine the coin flip. And then need to display the flip x number of times. I am running into an issue of how to write it using this. and decrement.
const coin = {
state: 0,
flip: function () {
this.state = Math.floor(Math.random() * 2) == 0 ? "tails" : "heads";
// 0 or 1: use "this.state" to access the "state" property on this object.
},
toString: function () {
if (this.state === 0) {
sides = "heads";
} else if (this.state === 1) {
sides = "tails";
}
return sides;
},
toHTML: function () {
const image = document.createElement("img");
let h1 = document.querySelector("h1");
h1.append(image);
if (this.state === 0) {
image.src = "images/tails.png";
} else if (this.state === 1) {
image.src = "image/heads.png";
}
return image;
},
};
function display20Flips() {
const results = [];
for (let i = 0; i < 20; i++) {
coin.flip();
h3.innerHTML += coin.state;
results.push(coin.state++);
}

You can also approach it functionally. This will help you focus on one problem at a time:
// Store the coin flip state as a boolean (true or false)
function randomBoolean () {
return Boolean(Math.floor(Math.random() * 2));
}
// Convert the boolean state to a "side" string
// heads is false, tails is true
function asCoinSide (bool) {
return bool ? 'tails' : 'heads';
}
function createCoinImage (bool) {
const side = asCoinSide(bool);
const image = document.createElement('img');
image.alt = side;
// StackOverflow doesn't have access to your local images,
// so this will show an error in the code snippet demo
// when the URL is loaded, but will work if the images exist:
image.src = `images/${side}.png`;
return image;
}
function displayCoinFlips (count = 20) {
const div = document.querySelector('div.coins');
const results = [];
for (let i = 0; i < count; i += 1) {
// Get a state
const state = randomBoolean();
results.push(state);
// Use the state to create the image
const image = createCoinImage(state);
// And append it to the container div
div.appendChild(image);
}
return results;
}
function displaySummary (states) {
const div = document.querySelector('div.summary');
let headsCount = 0;
let tailsCount = 0;
// Count the heads vs. tails results
// Remember: heads is false, tails is true
for (const state of states) {
if (state) tailsCount += 1;
else headsCount += 1;
}
div.textContent = `Heads: ${headsCount}, Tails: ${tailsCount}`;
}
const states = displayCoinFlips();
displaySummary(states);
<div class="coins"></div>
<div class="summary"></div>

You had some issues with your code. I fixed them. Still, the code needs to be a little redesigned to have a class Coin and then coin = new Coin().
const coin = {
state: 0,
flip: function() {
this.state = Math.floor(Math.random() * 2) == 0 ? "tails" : "heads";
},
toString: function() {
if (this.state === 0) {
sides = "heads";
} else if (this.state === 1) {
sides = "tails";
}
return sides;
},
toHTML: function() {
let h1 = document.querySelector(".container");
const div = document.createElement("div");
if (this.state === "tails") {
div.innerText = "images/tails.png";
} else {
div.innerText = "image/heads.png";
}
h1.append(div);
return div;
},
};
function displayFlips(n) {
const results = [];
for (let i = 0; i < n; i++) {
coin.flip();
coin.toHTML();
results.push(coin.state);
}
return results;
}
console.log(displayFlips(20));
<div class="container">
</div>

Related

Why shuffle doesn't work after I added new functionality of changing a gameboard size in a slider-puzzle game?

I created a slider-puzzle game and recently decided to reafctor the code using OOP. Besides I added a new functionality, so a player can change the size of the game board (3X3, 4X4, 5X5 etc.) by pressing a sertain button. But somehow a shuffle function stopped working. If you have any ideas of what I might do wrong, I will be very grateful if you let me know.
"use strict";
///////////////////////////////////////////////////////////////////////////
// Elements
const body = document.body;
const root = document.querySelector(".root");
const buttons = document.createElement("div");
const table = document.querySelector(".table");
const timeAndMoves = document.createElement("div");
const cubeSize = 125;
let cubes = [];
let gameRoundResults = {round: [], moves: [], time: []};
let countMoves = 0;
let gameRound = 0;
let cube, timer, left, top, tr, td;
let rowColumnLength = [3, 4, 5, 6, 7, 8]; // the default size of the game board - 4 columns and 4 rows
let boardSize = [9, 16, 15, 36, 49, 64];
let cubesArray = [];
let blankSpace = document.querySelector(".blank_space");
blankSpace = {
// blank space coords
};
// calculating coordinates of each cube and setting style
function getCoords(a, b) {
return Math.abs(a - b);
}
function setElementStyle(a, b) {
return `${a * b}px`;
}
// changing position of cubes
function changePosition(i) {
let cube = cubes[i];
const blankSpaceLeft = blankSpace.left;
const blankSpaceTop = blankSpace.top;
let coordsLeft = getCoords(blankSpace.left, cube.left);
let coordsTop = getCoords(blankSpace.top, cube.top);
if (
(blankSpaceLeft === cube.left && blankSpaceTop === cube.top) ||
coordsLeft + coordsTop > 1
) {
return;
} else {
countMoves++;
calcMoves.textContent = `Moves: ${countMoves}`;
}
cube.element.style.left = setElementStyle(blankSpace.left, cubeSize);
cube.element.style.top = setElementStyle(blankSpace.top, cubeSize);
blankSpace.top = cube.top;
blankSpace.left = cube.left;
cube.top = blankSpaceTop;
cube.left = blankSpaceLeft;
const winCondition = cubes.every(
(cube) => cube.value - 1 === cube.top * rowColumnLength + cube.left
);
if (winCondition) {
winAnnouncement();
clearInterval(timer);
gameRound++;
gameRoundResults.round.push(gameRound);
gameRoundResults.moves.push(countMoves);
gameRoundResults.time.push(setTimer.textContent);
}
}
// resetting the game, so after clicking on a shuffle button the original game board won't overlay newly shuffled cubes
const resetGame = () => {
clearInterval(timer);
setTimer.textContent = `Time: 00:00`;
countMoves = 0;
calcMoves.textContent = `Moves: ${countMoves}`;
[...root.children].forEach((el) => {
root.removeChild(el); //removing elements from the board
});
[...table.children].forEach((el) => {
table.removeChild(el); //removing elements from the board
});
cubes.splice(0); // deleting all elements starting from index 0
blankSpace.top = 0; // resetting coordinates
blankSpace.left = 0;
};
// creating a timer
const setTimer = document.createElement("div");
setTimer.classList.add("timer");
setTimer.textContent = `Time: 00:00`;
body.append(setTimer);
function startTimer() {
let sec = 0;
let min = 0;
timer = setInterval(() => {
setTimer.textContent =
"Time: " + `${min}`.padStart(2, 0) + ":" + `${sec}`.padStart(2, 0);
sec++;
if (sec >= 60) {
sec = 0;
min++;
}
}, 1000);
}
// creating moves counter
const calcMoves = document.createElement("div");
calcMoves.classList.add("count_moves");
calcMoves.textContent = `Moves: ${countMoves}`;
body.append(calcMoves);
////////////////////////////////////////////////////////////////////////////////////
// Creating buttons using OOP and adding event listeners to each of them
class Button {
constructor(name, attribute, classList, textContent, value) {
this.name = document.createElement("button");
this.name.setAttribute("id", attribute);
this.name.classList.add(classList);
this.textContent = this.name.textContent = textContent;
buttons.append(this.name);
}
//Methods that will be added to .prototype property
addEventShuffle() {
this.name.addEventListener("click", function () {
shuffle(cubesArray);
});
}
addEventStop() {
this.name.addEventListener("click", function () {
clearInterval(timer);
});
}
addEventResult() {
this.name.addEventListener("click", function () {
clearInterval(timer);
tableCreate();
showResults();
});
}
clickOnlyOnce() {
this.name.addEventListener("click", function () {
this.disabled = "disabled";
});
}
clickOnlyOnceDisabled() {
this.name.removeAttribute("disabled");
}
renderCubesNumber(number){
this.name.addEventListener("click", function () {
cubesArray.push(...Array(boardSize[number]).keys());
setPosition(boardSize = boardSize[number], rowColumnLength = rowColumnLength[number]);
console.log(cubesArray);
});
}
}
const shuffleButton = new Button(
"shuffleButton",
"button",
"shuffleButton",
"SHUFFLE"
);
const stopButton = new Button("stopButton", "button", "stopButton", "STOP");
const saveButton = new Button("saveButton", "button", "saveButton", "SAVE");
const resultsButton = new Button( "resultsButton", "button", "resultsButton", "RESULT");
shuffleButton.addEventShuffle();
stopButton.addEventStop();
resultsButton.addEventResult();
resultsButton.clickOnlyOnce();
// Creating size buttons
for(let i = 3; i <= 8; i++){
new Button("rowsCols", "button", "row_col_length", `${i}X${i}`).renderCubesNumber(i - 3);
}
// implementing shuffle functionality
function shuffle(cubesArray) {
for (let i = cubesArray.length - 1; i > 0; i--) {
resetGame(); // deleting original game board
setPosition(cubesArray); // creating a new shuffled game board
startTimer();
let j = Math.floor(Math.random() * (i + 1));
[cubesArray[i], cubesArray[j]] = [cubesArray[j], cubesArray[i]];
// [cubesArray[i], cubesArray[newPos]] = [cubesArray[newPos], cubesArray[i]]; //swapping original and randomized coordinates
console.log(
([cubesArray[i], cubesArray[j]] = [cubesArray[j], cubesArray[i]])
);
}
}
////////////////////////////////////////////////////////////////////////////////////
// Creating banners using OOP
class Banner {
constructor(name, classList, textContent, bannerMessage, bannerMessageClass) {
this.name = document.createElement("div");
this.name.classList.add(classList);
this.bannerMessage = document.createElement("h2");
this.bannerMessage.classList.add(bannerMessageClass);
this.bannerMessage.textContent = textContent;
root.append(this.bannerMessage);
root.append(this.name);
}
removeBanner() {
this.name.addEventListener("click", () => {
resultBanner.classList.remove("result_banner");
resultsButton.clickOnlyOnceDisabled();
});
}
}
// const startBanner = new Banner("startBanner","start_banner", `Slider puzzle challenge Click on 'Shuffle' to start the game!`, "bannerMessage","banner_message"
// );
// create pop-up if the user wins
function winAnnouncement() {
const winMessage = new Banner(
"winAnnounce",
"win_announce",
`Congrats! You solved the puzzle! Moves: ${countMoves} | ${setTimer.textContent}`,
"winMessage",
"win_message"
);
}
// Creating a result banner
function showResults() {
const resultBanner = document.createElement("div");
resultBanner.classList.add("result_banner");
const closeBanner = document.createElement("div");
closeBanner.classList.add("close_banner");
root.append(resultBanner);
table.append(closeBanner);
tableCreate();
const resultMessage = document.createElement("h2");
resultMessage.classList.add("banner_message");
resultBanner.append(resultMessage);
closeBanner.addEventListener("click", () => {
resultBanner.classList.remove("result_banner");
resultsButton.clickOnlyOnceDisabled();
[...table.children].forEach((el) => {
table.removeChild(el); //removing elements from the board
});
});
}
function tableCreate() {
const tbl = document.createElement("table");
tbl.classList.add("score_table");
for (let i = 0; i < 11; i++) {
tr = tbl.insertRow();
td = tr.insertCell();
if (gameRound === i + 1) {
td.appendChild(
document.createTextNode(
`Round: ${i + 1} | Moves: ${gameRoundResults.moves[i]} | ${gameRoundResults.time[i]}`
)
);
} else {
td.appendChild(
document.createTextNode(`Round: ${i + 1} | Moves: 0 | Time: 00:00`)
);
}
td.style.border = "1px solid black";
}
table.appendChild(tbl);
}
buttons.classList.add("button");
body.append(buttons);
// creating cubes
function setPosition() {
for (let i = 0; i < boardSize; i++) {
cube = document.createElement("div");
cube.classList.add("cube");
const value = i + 1; // adding +1 to the cubes' values to start the order from 1 (123...) instead of 0 (0123...)
cube.textContent = value;
left = i % rowColumnLength; // setting horizontal position
cube.style.left = setElementStyle(cubeSize, left);
top = (i - left) / rowColumnLength; // setting vertical position
cube.style.top = setElementStyle(cubeSize, top);
root.append(cube);
if (value === boardSize) {
// separating basic cubes with a blank space which is going to be the 16th cube
cube.classList.add("blank_space"); // adding a class to the last cube, so it'll be possible to hide it using css
blankSpace.top = top;
blankSpace.left = left;
blankSpace.value = value;
blankSpace.element = cube;
cubes.push(blankSpace);
} else {
cubes.push({
top: top,
left: left,
value: value,
element: cube,
});
}
cube.addEventListener("click", () => {
changePosition(i);
});
}
}
setPosition();
To solve the problem I've tried to refactor the shuffle function, but nothing works. In the screenshot you can see the results of console.logs

A*(A-star) algorithm gives wrong path and crashes

I'm implementing A*(A-star) algorithm in react.js but my program crashes whenever startNode(green) or destinationNode(blue) have more than one neighbour or if there is a cycle in the graph. There is something wrong when adding and deleting the neighbours from/to openList or when updating the parentId in the getPath() function. I cant even see the console because the website goes down.
Each node has: id, name, x, y, connectedToIdsList:[], gcost:Infinity, fcost:0, heuristic:0, parentId:null.
I'm sending the path to another component "TodoList" which prints out the path. My program should not return the path but keeps updating the path as i'm adding nodes and edges to the list. Please help, I've been stuck for hours now:/
My code:
export default class TurnByTurnComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = { shortestPath: [] }
}
render() {
const {
destinationLocationId,
locations,
originLocationId
} = this.props;
let path = []
if (destinationLocationId != null && originLocationId != null) {
if (originLocationId == destinationLocationId) { //check if the startNode node is the end node
return originLocationId;
}
var openList = [];
let startNode = getNodeById(originLocationId);
let destinationNode = getNodeById(destinationLocationId)
if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) { //check if start and destination nodes are connected first
startNode.gcost = 0
startNode.heuristic = manhattanDistance(startNode, destinationNode)
startNode.fcost = startNode.gcost + startNode.heuristic;
//perform A*
openList.push(startNode); //starting with the startNode
while (openList.length > 0) {
console.log("inside while")
var currentNode = getNodeOfMinFscore(openList); //get the node of the minimum f cost of all nodes in the openList
if (currentIsEqualDistanation(currentNode)) {
path = getPath(currentNode);
}
deleteCurrentFromOpenList(currentNode, openList);
for (let neighbourId of currentNode.connectedToIds) {
var neighbourNode = getNodeById(neighbourId);
currentNode.gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
if (currentNode.gcost < neighbourNode.gcost) {
neighbourNode.parentId = currentNode.id; // keep track of the path
// total cost saved in neighbour.g
neighbourNode.gcost = currentNode.gcost;
neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic; //calculate f cost of the neighbourNode
addNeighbourNodeToOpenList(neighbourNode, openList);
}
}
}
path = path.reverse().join("->");
}
}
function addNeighbourNodeToOpenList(neighbourNode, openList) {
//add neighbourNode to the open list to be discovered later
if (!openList.includes(neighbourNode)) {
openList.push(neighbourNode);
}
}
function deleteCurrentFromOpenList(currNode, openList) {
const currIndex = openList.indexOf(currNode);
openList.splice(currIndex, 1); //deleting currentNode from openList
}
function currentIsEqualDistanation(currNode) {
//check if we reached out the distanation node
return (currNode.id == destinationLocationId)
}
function getNodeById(nid) {
var node;
for (let i = 0; i < locations.length; i++) {
if (locations[i].id == nid) {
node = locations[i]
}
}
return node
}
function getPath(destNode) {
console.log("inside getpath")
var parentPath = []
var parent;
while (destNode.parentId != null) {
parentPath.push(destNode.name)
parent = destNode.parentId;
destNode = getNodeById(parent);
}
//adding startNode to the path
parentPath.push(getNodeById(originLocationId).name)
return parentPath;
}
function getNodeOfMinFscore(openList) {
var minFscore = openList[0].fcost; //initValue
var nodeOfminFscore;
for (let i = 0; i < openList.length; i++) {
if (openList[i].fcost <= minFscore) {
minFscore = openList[i].fcost //minFvalue
nodeOfminFscore = openList[i]
}
}
return nodeOfminFscore
}
//manhattan distance is for heuristic and gScore. Here I use Manhattan instead of Euclidean
//because in this example we dont have diagnosal path.
function manhattanDistance(stNode, dstNode) {
var x = Math.abs(dstNode.x - stNode.x);
var y = Math.abs(dstNode.y - stNode.y);
var dist = x + y;
return dist;
}
return (
<div className="turn-by-turn-component">
<TodoList
list={"Shortest path: ", [path]}
/>
<TodoList
list={[]}
/>
</div>
);
}
}
TurnByTurnComponent.propTypes = {
destinationLocationId: PropTypes.number,
locations: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
connectedToIds: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired
})),
originLocationId: PropTypes.number
};
Update new issue
Pictures of before and after linking a new node. When I update the graph and add a new node the path disappears. And sometimes come back and so on if I still add new nodes and edges to the graph. As I said, each node has: id, name, x, y, connectedToIdsList:[], gcost:Infinity, fcost:0, heuristic:0, parentId:null.
My new code now is:
export default class TurnByTurnComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = { shortestPath: [] }
}
render() {
const {
destinationLocationId,
locations,
originLocationId
} = this.props;
let path = []
if (destinationLocationId != null && originLocationId != null) {
console.log(JSON.stringify(locations))
path = [getNodeById(originLocationId).namne];
if (originLocationId != destinationLocationId) {
var openList = [];
let startNode = getNodeById(originLocationId);
let destinationNode = getNodeById(destinationLocationId)
if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) {
startNode.gcost = 0
startNode.heuristic = manhattanDistance(startNode, destinationNode)
startNode.fcost = startNode.gcost + startNode.heuristic;
openList.push(startNode); //starting with the startNode
while (openList.length > 0) {
var currentNode = getNodeOfMinFscore(openList); //get the node of the minimum f cost of all nodes in the openList
if (currentIsEqualDistanation(currentNode)) {
path = getPath(currentNode);
break;
}
deleteCurrentFromOpenList(currentNode, openList);
for (let neighbourId of currentNode.connectedToIds) {
var neighbourNode = getNodeById(neighbourId);
let gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
if (gcost < (neighbourNode.gcost ?? Infinity)) {
neighbourNode.parentId = currentNode.id;
// keep track of the path
// total cost saved in neighbour.g
neighbourNode.gcost = gcost;
neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic; //calculate f cost of the neighbourNode
addNeighbourNodeToOpenList(neighbourNode, openList);
}
}
}
}
}
}
path = path.reverse().join("->");
function addNeighbourNodeToOpenList(neighbourNode, openList) {
//add neighbourNode to the open list to be discovered later
if (!openList.includes(neighbourNode)) {
openList.push(neighbourNode);
}
}
function deleteCurrentFromOpenList(currentNode, openList) {
const currIndex = openList.indexOf(currentNode);
openList.splice(currIndex, 1); //deleting currentNode from openList
}
function currentIsEqualDistanation(currentNode) {
//check if we reached out the distanation node
return (currentNode.id == destinationLocationId)
}
function getNodeById(id) {
var node;
for (let i = 0; i < locations.length; i++) {
if (locations[i].id == id) {
node = locations[i]
}
}
return node
}
function getPath(destinationNode) {
console.log("inside getpath")
var parentPath = []
var parent;
while (destinationNode.parentId != null) {
parentPath.push(destinationNode.name)
parent = destinationNode.parentId;
destinationNode = getNodeById(parent);
}
//adding startNode to the path
parentPath.push(getNodeById(originLocationId).name)
return parentPath;
}
function getNodeOfMinFscore(openList) {
var minFscore = openList[0].fcost; //initValue
var nodeOfminFscore;
for (let i = 0; i < openList.length; i++) {
if (openList[i].fcost <= minFscore) {
minFscore = openList[i].fcost //minFvalue
nodeOfminFscore = openList[i]
}
}
return nodeOfminFscore
}
//manhattan distance is for heuristic and gScore. Here I use Manhattan instead of Euclidean
//because in this example we dont have diagnosal path.
function manhattanDistance(startNode, destinationNode) {
var x = Math.abs(destinationNode.x - startNode.x);
var y = Math.abs(destinationNode.y - startNode.y);
var dist = x + y;
return dist;
}
return (
<div className="turn-by-turn-component">
<TodoList
title="Mandatory work"
list={[path]}
/>
<TodoList
title="Optional work"
list={[]}
/>
</div>
);
}
}
TurnByTurnComponent.propTypes = {
destinationLocationId: PropTypes.number,
locations: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
connectedToIds: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired
})),
originLocationId: PropTypes.number
};
There are a few issues in your code:
return originLocationId; should not happen. When the source is equal to the target, then set the path, and make sure the final return of this function is executed, as you want to return the div element.
When the target is found in the loop, not only should the path be built, but the loop should be exited. So add a break
currentNode.gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode); is not right: you don't want to modify currentNode.gcost: its value should not depend on the outgoing edge to that neighbour. Instead store this sum in a temporary variable (like gcost)
The comparison with neighbourNode.gcost will not work when that node does not yet have a gcost member. I don't see in your code that it got a default value that makes sure this condition is true, so you should use a default value here, like (neighbourNode.gcost ?? Infinity)
path = path.reverse().join("->"); should better be executed always, also when there is no solution (so path is a string) or when the source and target are the same node.
Here is a corrected version, slightly adapted to run here as a runnable snippet:
function render() {
const {
destinationLocationId,
locations,
originLocationId
} = this.props;
let path = [];
if (destinationLocationId != null && originLocationId != null) {
path = [originLocationId]; // The value for when the next if condition is not true
if (originLocationId != destinationLocationId) {
var openList = [];
let startNode = getNodeById(originLocationId);
let destinationNode = getNodeById(destinationLocationId)
if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) {
startNode.gcost = 0
startNode.heuristic = manhattanDistance(startNode, destinationNode)
startNode.fcost = startNode.gcost + startNode.heuristic;
openList.push(startNode);
while (openList.length > 0) {
var currentNode = getNodeOfMinFscore(openList);
if (currentIsEqualDistanation(currentNode)) {
path = getPath(currentNode);
break; // Should end the search here!
}
deleteCurrentFromOpenList(currentNode, openList);
for (let neighbourId of currentNode.connectedToIds) {
var neighbourNode = getNodeById(neighbourId);
// Should not modify the current node's gcost. Use a variable instead:
let gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
// Condition should also work when neighbour has no gcost yet:
if (gcost < (neighbourNode.gcost ?? Infinity)) {
neighbourNode.parentId = currentNode.id;
neighbourNode.gcost = gcost; // Use the variable
neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic;
addNeighbourNodeToOpenList(neighbourNode, openList);
}
}
}
}
}
}
// Convert the path to string in ALL cases:
path = path.reverse().join("->");
function addNeighbourNodeToOpenList(neighbourNode, openList) {
if (!openList.includes(neighbourNode)) {
openList.push(neighbourNode);
}
}
function deleteCurrentFromOpenList(currNode, openList) {
const currIndex = openList.indexOf(currNode);
openList.splice(currIndex, 1);
}
function currentIsEqualDistanation(currNode) {
return (currNode.id == destinationLocationId)
}
function getNodeById(nid) {
var node;
for (let i = 0; i < locations.length; i++) {
if (locations[i].id == nid) {
node = locations[i]
}
}
return node
}
function getPath(destNode) {
var parentPath = []
var parentId;
while (destNode.parentId != null) {
parentPath.push(destNode.name)
parentId = destNode.parentId;
destNode = getNodeById(parentId);
}
parentPath.push(getNodeById(originLocationId).name)
return parentPath;
}
function getNodeOfMinFscore(openList) {
var minFscore = openList[0].fcost;
var nodeOfminFscore;
for (let i = 0; i < openList.length; i++) {
if (openList[i].fcost <= minFscore) {
minFscore = openList[i].fcost
nodeOfminFscore = openList[i]
}
}
return nodeOfminFscore
}
function manhattanDistance(stNode, dstNode) {
var x = Math.abs(dstNode.x - stNode.x);
var y = Math.abs(dstNode.y - stNode.y);
var dist = x + y;
return dist;
}
return "Shortest path: " + path;
}
// Demo
let props = {
locations: [
{ id: 1, x: 312, y: 152, connectedToIds: [4,2], name: "Thetaham" },
{ id: 2, x: 590, y: 388, connectedToIds: [1,3], name: "Deltabury" },
{ id: 3, x: 428, y: 737, connectedToIds: [2], name: "Gammation" },
{ id: 4, x: 222, y: 430, connectedToIds: [1], name: "Theta City" },
],
originLocationId: 1,
destinationLocationId: 3,
};
console.log(render.call({props}));

How to stop looping text animation

I am a student studying JavaScript.
I found the js code for the scrambled text animation, but I want to stop looping.
(Because I want to read the contents)
Anyone can explain to stop looping in the 'for or if' part?
Also, are there any unnecessary parts of the JavaScript code?
Thanks in advance for the answer.
html
// ——————————————————————————————————————————————————
// TextScramble
// ——————————————————————————————————————————————————
class TextScramble {
constructor(el) {
this.el = el
this.chars = 'かきくけこらりるれろ'
this.update = this.update.bind(this)
}
setText(newText) {
const oldText = this.el.innerText
const length = Math.max(oldText.length, newText.length)
const promise = new Promise((resolve) => this.resolve = resolve)
this.queue = []
for (let i = 0; i < length; i++) {
const from = oldText[i] || ''
const to = newText[i] || ''
const start = Math.floor(Math.random() * 40)
const end = start + Math.floor(Math.random() * 40)
this.queue.push({
from,
to,
start,
end
})
}
cancelAnimationFrame(this.frameRequest)
this.frame = 0
this.update()
return promise
}
update() {
let output = ''
let complete = 0
for (let i = 0, n = this.queue.length; i < n; i++) {
let {
from,
to,
start,
end,
char
} = this.queue[i]
if (this.frame >= end) {
complete++
output += to
} else if (this.frame >= start) {
if (!char || Math.random() < 0.28) {
char = this.randomChar()
this.queue[i].char = char
}
output += `<span class="dud">${char}</span>`
} else {
output += from
}
}
this.el.innerHTML = output
if (complete === this.queue.length) {
this.resolve()
} else {
this.frameRequest = requestAnimationFrame(this.update)
this.frame++
}
}
randomChar() {
return this.chars[Math.floor(Math.random() * this.chars.length)]
}
}
// ——————————————————————————————————————————————————
// Example
// ——————————————————————————————————————————————————
const phrases = [
'ロレム・イプサムの嘆き、トマト大好き学部のエリット、しかし時と活力、そのような労働と悲しみ、ブラインド行うにはいくつかの重要な事柄に座ります。長年にわたり、私は学区と長寿であれば、そのような刺激の取り組み、彼女のうち、運動の利点を分注を邪魔されたする人が来ます。クピダタットのつるの痛みになりたい宿題に、批判されてきたら痛み、マグナ逃亡しても結果の喜びを生成しません。先例クピダタットブラックは先例していない、つまり、彼らはあなたの悩みに責任がある人の、一般的な義務を捨て、魂を癒しています。'
]
const el = document.querySelector('.text')
const fx = new TextScramble(el)
let counter = 0
const next = () => {
fx.setText(phrases[counter]).then(() => {
setTimeout(next, 800)
})
counter = (counter + 1) % phrases.length
}
next()
<div id="main">
<div class="container">
<div class="glitch" data-text="About">About</div>
<div class="glow">About</div>
</div>
<div class="scanlines"></div>
<div class="text"></div>
</div>
I think you should try adding delay in your loop
see if this helps you
for (let i=0; i<10; i++) {
task(i);
}
function task(i) {
setTimeout(function() {
// Add tasks to do
}, 2000 * i);
}

Converting working functional Javascript Tic Tac Toe game to Class based to practice OOP

I'm making an exercise for myself to better understand OOP design by taking a working Javascript functional Tic Tac Toe game with AI to a Class based one. I'm getting stuck on the usual issues with what to put where in classes, single source of truth, loose coupling, etc. Not looking for complete answers here but perhaps some hints on a better strategy?
Here is the original working functional TTT:
import "./styles.css";
// functional TIC TAC TOE
// Human is 'O'
// Player is 'X'
let ttt = {
board: [], // array to hold the current game
reset: function() {
// reset board array and get HTML container
ttt.board = [];
const container = document.getElementById("ttt-game"); // the on div declared in HTML file
container.innerHTML = "";
// redraw swuares
// create a for loop to build board
for (let i = 0; i < 9; i++) {
// push board array with null
ttt.board.push(null);
// set square to create DOM element with 'div'
let square = document.createElement("div");
// insert " " non-breaking space to square
square.innnerHTML = " ";
// set square.dataset.idx set to i of for loop
square.dataset.idx = i;
// build square id's with i from loop / 'ttt-' + i - concatnate iteration
square.id = "ttt-" + i;
// add click eventlistener to square to fire ttt.play();
square.addEventListener("click", ttt.play);
// appendChild with square (created element 'div') to container
container.appendChild(square);
}
},
play: function() {
// ttt.play() : when the player selects a square
// play is fired when player selects square
// (A) Player's move - Mark with "O"
// set move to this.dataset.idx
let move = this.dataset.idx;
// assign ttt.board array with move to 0
ttt.board[move] = 0;
// assign "O" to innerHTML for this
this.innerHTML = "O";
// add "Player" to a classList for this
this.classList.add("Player");
// remove the eventlistener 'click' and fire ttt.play
this.removeEventListener("click", ttt.play);
// (B) No more moves available - draw
// check to see if board is full
if (ttt.board.indexOf(null) === -1) {
// alert "No winner"
alert("No Winner!");
// ttt.reset();
ttt.reset();
} else {
// (C) Computer's move - Mark with 'X'
// capture move made with dumbAI or notBadAI
move = ttt.dumbAI();
// assign ttt.board array with move to 1
ttt.board[move] = 1;
// assign sqaure to AI move with id "ttt-" + move (concatenate)
let square = document.getElementById("ttt-" + move);
// assign "X" to innerHTML for this
square.innerHTML = "X";
// add "Computer" to a classList for this
square.classList.add("Computer");
// square removeEventListener click and fire ttt.play
square.removeEventListener("click", ttt.play);
// (D) Who won?
// assign win to null (null, "x", "O")
let win = null;
// Horizontal row checks
for (let i = 0; i < 9; i += 3) {
if (
ttt.board[i] != null &&
ttt.board[i + 1] != null &&
ttt.board[i + 2] != null
) {
if (
ttt.board[i] == ttt.board[i + 1] &&
ttt.board[i + 1] == ttt.board[i + 2]
) {
win = ttt.board[i];
}
}
if (win !== null) {
break;
}
}
// Vertical row checks
if (win === null) {
for (let i = 0; i < 3; i++) {
if (
ttt.board[i] !== null &&
ttt.board[i + 3] !== null &&
ttt.board[i + 6] !== null
) {
if (
ttt.board[i] === ttt.board[i + 3] &&
ttt.board[i + 3] === ttt.board[i + 6]
) {
win = ttt.board[i];
}
if (win !== null) {
break;
}
}
}
}
// Diaganal row checks
if (win === null) {
if (
ttt.board[0] != null &&
ttt.board[4] != null &&
ttt.board[8] != null
) {
if (ttt.board[0] == ttt.board[4] && ttt.board[4] == ttt.board[8]) {
win = ttt.board[4];
}
}
}
if (win === null) {
if (
ttt.board[2] != null &&
ttt.board[4] != null &&
ttt.board[6] != null
) {
if (ttt.board[2] == ttt.board[4] && ttt.board[4] == ttt.board[6]) {
win = ttt.board[4];
}
}
}
// We have a winner
if (win !== null) {
alert("WINNER - " + (win === 0 ? "Player" : "Computer"));
ttt.reset();
}
}
},
dumbAI: function() {
// ttt.dumbAI() : dumb computer AI, randomly chooses an empty slot
// Extract out all open slots
let open = [];
for (let i = 0; i < 9; i++) {
if (ttt.board[i] === null) {
open.push(i);
}
}
// Randomly choose open slot
const random = Math.floor(Math.random() * (open.length - 1));
return open[random];
},
notBadAI: function() {
// ttt.notBadAI() : AI with a little more intelligence
// (A) Init
var move = null;
var check = function(first, direction, pc) {
// checkH() : helper function, check possible winning row
// PARAM square : first square number
// direction : "R"ow, "C"ol, "D"iagonal
// pc : 0 for player, 1 for computer
var second = 0,
third = 0;
if (direction === "R") {
second = first + 1;
third = first + 2;
} else if (direction === "C") {
second = first + 3;
third = first + 6;
} else {
second = 4;
third = first === 0 ? 8 : 6;
}
if (
ttt.board[first] === null &&
ttt.board[second] === pc &&
ttt.board[third] === pc
) {
return first;
} else if (
ttt.board[first] === pc &&
ttt.board[second] === null &&
ttt.board[third] === pc
) {
return second;
} else if (
ttt.board[first] === pc &&
ttt.board[second] === pc &&
ttt.board[third] === null
) {
return third;
}
return null;
};
// (B) Priority #1 - Go for the win
// (B1) Check horizontal rows
for (let i = 0; i < 9; i += 3) {
move = check(i, "R", 1);
if (move !== null) {
break;
}
}
// (B2) Check vertical columns
if (move === null) {
for (let i = 0; i < 3; i++) {
move = check(i, "C", 1);
if (move !== null) {
break;
}
}
}
// (B3) Check diagonal
if (move === null) {
move = check(0, "D", 1);
}
if (move === null) {
move = check(2, "D", 1);
}
// (C) Priority #2 - Block player from winning
// (C1) Check horizontal rows
for (let i = 0; i < 9; i += 3) {
move = check(i, "R", 0);
if (move !== null) {
break;
}
}
// (C2) Check vertical columns
if (move === null) {
for (let i = 0; i < 3; i++) {
move = check(i, "C", 0);
if (move !== null) {
break;
}
}
}
// (C3) Check diagonal
if (move === null) {
move = check(0, "D", 0);
}
if (move === null) {
move = check(2, "D", 0);
}
// (D) Random move if nothing
if (move === null) {
move = ttt.dumbAI();
}
return move;
}
};
document.addEventListener("DOMContentLoaded", ttt.reset());
Here is what I have so far of my class based version:
import "./styles.css";
class Gameboard {
constructor() {
this.board = [];
this.container = document.getElementById("ttt-game");
this.container.innerHTML = "";
}
reset() {
this.board = [];
}
build() {
for (let i = 0; i < 9; i++) {
this.board.push(null);
const square = document.createElement("div");
square.innerHTML = " ";
square.dataset.idx = i;
square.id = "ttt-" + i;
square.addEventListener("click", () => {
// What method do I envoke here?
console.log(square)
});
this.container.appendChild(square);
}
}
};
class Game {
constructor() {
this.gameBoard = new Gameboard();
this.player = new Player();
this.computer = new Computer();
}
play() {
this.gameBoard.build();
}
};
class Player {
};
class Computer {
};
class DumbAI {
};
const game = new Game();
document.addEventListener("DOMContentLoaded", game.play());
My HTML file is very simple with only a <div id="ttt-game"></div> to get started and CSS file is grid.
The biggest issue I'm having is capturing the squares in Game. And where should I put eventListeners ? (my next project is to do a React version).
Here's what I think, good, maintainable and testable code looks like: a bunch of small, self-contained functions, each with as few side-effects as possible. And rather than have state spread around the application, state should exist in a single, central location.
So, what I have done is decompose your code into small functions. I have pulled the state into a single store that enforces immutability. No weird half-way houses - the application state changes, or it doesn't. If it changes, the entire game is re-rendered. Responsibility for interacting with the UI exists in a single render function.
And you asked about classes in your question. createGame becomes:
class Game {
constructor() { ... },
start() { ... },
reset() { ... },
play() { ... }
}
createStore becomes:
class Store {
constructor() { ... }
getState() { ... },
setState() { ... }
}
playAI and playHuman become:
class AIPlayer {
constructor(store) { ... }
play() { ... }
}
class HumanPlayer {
constructor(store) { ... }
play() { ... }
}
checkForWinner becomes:
class WinChecker {
check(board) { ... }
}
...and so on.
But I ask the rhetorical question: would adding these classes add anything to the code? In my view there are three fundamental and intrinsic problems with class-oriented object orientation:
It leads you down the path of mixing application state and functionality,
Classes are like snowballs - they accrete functionality and quickly become over-large, and
People are terrible at coming up with meaningful class ontologies
All of the above mean that classes invariably lead to critically unmaintainable code.
I think code is usually simpler and more maintainable without new, and without this.
index.js
import { createGame } from "./create-game.js";
const game = createGame("#ttt-game");
game.start();
create-game.js
import { initialState } from "./initial-state.js";
import { createStore } from "./create-store.js";
import { render } from "./render.js";
const $ = document.querySelector.bind(document);
function start({ store, render }) {
createGameLoop({ store, render })();
}
function createGameLoop({ store, render }) {
let previousState = null;
return function loop() {
const state = store.getState();
if (state !== previousState) {
render(store);
previousState = store.getState();
}
requestAnimationFrame(loop);
};
}
export function createGame(selector) {
const store = createStore({ ...initialState, el: $(selector) });
return {
start: () => start({ store, render })
};
}
initial-state.js
export const initialState = {
el: null,
board: Array(9).fill(null),
winner: null
};
create-store.js
export function createStore(initialState) {
let state = Object.freeze(initialState);
return {
getState() {
return state;
},
setState(v) {
state = Object.freeze(v);
}
};
}
render.js
import { onSquareClick } from "./on-square-click.js";
import { winners } from "./winners.js";
import { resetGame } from "./reset-game.js";
export function render(store) {
const { el, board, winner } = store.getState();
el.innerHTML = "";
for (let i = 0; i < board.length; i++) {
let square = document.createElement("div");
square.id = `ttt-${i}`;
square.innerText = board[i];
square.classList = "square";
if (!board[i]) {
square.addEventListener("click", onSquareClick.bind(null, store));
}
el.appendChild(square);
}
if (winner) {
const message =
winner === winners.STALEMATE ? `Stalemate!` : `${winner} wins!`;
const msgEL = document.createElement("div");
msgEL.classList = "message";
msgEL.innerText = message;
msgEL.addEventListener("click", () => resetGame(store));
el.appendChild(msgEL);
}
}
on-square-click.js
import { play } from "./play.js";
export function onSquareClick(store, { target }) {
const {
groups: { move }
} = /^ttt-(?<move>.*)/gi.exec(target.id);
play({ move, store });
}
winners.js
export const winners = {
HUMAN: "Human",
AI: "AI",
STALEMATE: "Stalemate"
};
reset-game.js
import { initialState } from "./initial-state.js";
export function resetGame(store) {
const { el } = store.getState();
store.setState({ ...initialState, el });
}
play.js
import { randomMove } from "./random-move.js";
import { checkForWinner } from "./check-for-winner.js";
import { checkForStalemate } from "./check-for-stalemate.js";
import { winners } from "./winners.js";
function playHuman({ move, store }) {
const state = store.getState();
const updatedBoard = [...state.board];
updatedBoard[move] = "O";
store.setState({ ...state, board: updatedBoard });
}
function playAI(store) {
const state = store.getState();
const move = randomMove(state.board);
const updatedBoard = [...state.board];
updatedBoard[move] = "X";
store.setState({ ...state, board: updatedBoard });
}
export function play({ move, store }) {
playHuman({ move, store });
if (checkForWinner(store)) {
const state = store.getState();
store.setState({ ...state, winner: winners.HUMAN });
return;
}
if (checkForStalemate(store)) {
const state = store.getState();
store.setState({ ...state, winner: winners.STALEMATE });
return;
}
playAI(store);
if (checkForWinner(store)) {
const state = store.getState();
store.setState({ ...state, winner: winners.AI });
return;
}
}
Running version:
const $ = document.querySelector.bind(document);
const winners = {
HUMAN: "Human",
AI: "AI",
STALEMATE: "Stalemate"
};
function randomMove(board) {
let open = [];
for (let i = 0; i < board.length; i++) {
if (board[i] === null) {
open.push(i);
}
}
const random = Math.floor(Math.random() * (open.length - 1));
return open[random];
}
function onSquareClick(store, target) {
const {
groups: { move }
} = /^ttt-(?<move>.*)/gi.exec(target.id);
play({ move, store });
}
function render(store) {
const { el, board, winner } = store.getState();
el.innerHTML = "";
for (let i = 0; i < board.length; i++) {
let square = document.createElement("div");
square.id = `ttt-${i}`;
square.innerText = board[i];
square.classList = "square";
if (!board[i]) {
square.addEventListener("click", ({ target }) =>
onSquareClick(store, target)
);
}
el.appendChild(square);
}
if (winner) {
const message =
winner === winners.STALEMATE ? `Stalemate!` : `${winner} wins!`;
const msgEL = document.createElement("div");
msgEL.classList = "message";
msgEL.innerText = message;
msgEL.addEventListener("click", () => resetGame(store));
el.appendChild(msgEL);
}
}
function resetGame(store) {
const { el } = store.getState();
store.setState({ ...initialState, el });
}
function playHuman({ move, store }) {
const state = store.getState();
const updatedBoard = [...state.board];
updatedBoard[move] = "O";
store.setState({ ...state, board: updatedBoard });
}
function playAI(store) {
const state = store.getState();
const move = randomMove(state.board);
const updatedBoard = [...state.board];
updatedBoard[move] = "X";
store.setState({ ...state, board: updatedBoard });
}
const patterns = [
[0,1,2], [3,4,5], [6,7,8],
[0,4,8], [2,4,6],
[0,3,6], [1,4,7], [2,5,8]
];
function checkForWinner(store) {
const { board } = store.getState();
return patterns.find(([a,b,c]) =>
board[a] === board[b] &&
board[a] === board[c] &&
board[a]);
}
function checkForStalemate(store) {
const { board } = store.getState();
return board.indexOf(null) === -1;
}
function play({ move, store }) {
playHuman({ move, store });
if (checkForWinner(store)) {
const state = store.getState();
store.setState({ ...state, winner: winners.HUMAN });
return;
}
if (checkForStalemate(store)) {
const state = store.getState();
store.setState({ ...state, winner: winners.STALEMATE });
return;
}
playAI(store);
if (checkForWinner(store)) {
const state = store.getState();
store.setState({ ...state, winner: winners.AI });
return;
}
}
function createStore(initialState) {
let state = Object.freeze(initialState);
return {
getState() {
return state;
},
setState(v) {
state = Object.freeze(v);
}
};
}
function start({ store, render }) {
createGameLoop({ store, render })();
}
function createGameLoop({ store, render }) {
let previousState = null;
return function loop() {
const state = store.getState();
if (state !== previousState) {
render(store);
previousState = store.getState();
}
requestAnimationFrame(loop);
};
}
const initialState = {
el: null,
board: Array(9).fill(null),
winner: null
};
function createGame(selector) {
const store = createStore({ ...initialState, el: $(selector) });
return {
start: () => start({ store, render })
};
}
const game = createGame("#ttt-game");
game.start();
* {
box-sizing: border-box;
padding: 0;
margin: 0;
font-size: 0;
}
div.container {
width: 150px;
height: 150px;
box-shadow: 0 0 0 5px red inset;
}
div.square {
font-family: sans-serif;
font-size: 26px;
color: gray;
text-align: center;
line-height: 50px;
vertical-align: middle;
cursor: grab;
display: inline-block;
width: 50px;
height: 50px;
box-shadow: 0 0 0 2px black inset;
}
div.message {
font-family: sans-serif;
font-size: 26px;
color: white;
text-align: center;
line-height: 100px;
vertical-align: middle;
cursor: grab;
position: fixed;
top: calc(50% - 50px);
left: 0;
height: 100px;
width: 100%;
background-color: rgba(100, 100, 100, 0.7);
}
<div class="container" id="ttt-game"></div>

Initializing a functions in javascript

I have created a function that creates some ammount of random cards, but my Init function returns undefined when I call it like init(1). Why doesn't it work? It should be like:
1. I'm calling a function for example init(1)
2. Init creates and shuffles cards and appends them to the body
// INIT
{
function init(difficulty) {
switch (difficulty) {
case 1:
createCards(4);
break;
case 2:
createCards(12);
break;
case 0:
createCards(24);
}
}
//FLIP CARD
function createCards(ammount) {
const gameCards = []
for (let i = 0; i < ammount; i++) {
const gameCard = document.createElement("div");
gameCard.className = "card card--click";
const gameCardFront = document.createElement("div");
const gameCardBack = document.createElement("div");
gameCard.appendChild(gameCardFront);
gameCard.appendChild(gameCardBack);
gameCardFront.className = "card__front card--reversed";
gameCardBack.className = "card__back";
const img = new Image();
function randImg() {
const uniqueSrc = {}
const imgArray = ["ball", "car", "fork", "spoon", "sun"];
const gameArray = [];
for (let i = 0; i < ammount * 2 + 1; i++) {
const randomSrc = Math.floor(Math.random() * (imgArray.length));
if (!uniqueSrc[randomSrc]) {
uniqueSrc[randomSrc] = randomSrc;
img.src = "img/" + imgArray[randomSrc] + ".png";
img.alt = imgArray[randomSrc];
gameArray.push(img);
} else {
i--;
}
}
return gameArray;
}
randImg();
gameCardBack.appendChild(img);
gameCards.push(gameCard)
}
return gameCards;
}
const cards = document.querySelectorAll(".card.card--click");
//FETCHING ALL CARDS
for (let i = 0; i < cards.length; i++) {
const card = cards[i];
//ADDING FLIP EFFECT TO EACH CARD
flipCard(card);
};
//FLIP EFFECT FUNCTION
function flipCard(card) {
card.addEventListener("click", function() {
const list = this.classList;
list.contains("card--flip") === true ? list.remove("card--flip") : list.add("card--flip");
});
};
function randomizer(array) {
for (let i = 0; i < array.length; i++) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
return array;
}
}
1) The thing is that youre not appending your cards to body. So you may want to return the cards in the init function:
function init(difficulty) {
switch (difficulty) {
case 1:
return createCards(4);
break;
case 2:
return createCards(12);
break;
case 0:
return createCards(24);
}
}
Then you can append the Array returned by init to body:
init(0).forEach(function(card){
document.body.appendChild(card);
});
2)Also this:
randImg();
doesnt make sense as randImg returns an array, you may wanna catch it:
arr=randImg();
3) Also the randImg will not work, you need to put the
const img=new Image();
into the loop.
4) And
const cards = document.querySelectorAll(".card.card--click");
Will be an empty collection unless you run the snippet above before...

Categories