TicTacToe - minimax-function always returns -10 (Javascript) - javascript
I'm working on a tictactoe Person vs Person game. As a bonus, we can try to implement a minimax algorithm to play against the computer. After numerous trials and errors, I think I have the function so far, that it goes through the calculation without an error. However, the return value for best Score is always -10, assuming the player and not the computer would win.
I have two problems in the code:
the minimax-function always returns the value -10, assuming the person would win.
I cant play the last turn. In the debugger I can see that my gameData array, that keeps track of the visible field, works normally like so: [0,"X","X","O","X","O","O",7,"X"]. But the arrays player.person and player.computer have already stored the 0 and the 7. This should not happen.
At this point I am stuck. And I would tell you what I have been trying so far, but the last couple hours were just poking in the dark pretty much. Therefore the least I can do is write down the steps the code takes till the end:
Important variables etc:
Computer is represented by "O", person by "X"
Array gameData: Starts as [0,1,2,...,8] and replaces the numbers with "X" or "O". It tries to prevent that the tictactoe fields can be selected more than once.
Object player with .person-Array and .computer-Array both start as empty. I use these to check if either of them won the game.
isWinner(PLAYER) iterates through the winning conditions and checks if person or computer match it. The function returns the object ({win:true}) if so.
isTie() checks if there is a tie. It returns the object ({tie:true]), if the conditions isWinner(player.computer) = false, isWinner(player.person) = false and emptySpaces(gameData).length = 0 are met.
the function bestMove() searches for the bestMove of computer. Here's where minimax gets executed.
Code Logic overall:
the code starts at line 140 with function vsComputerGame()
next step is function personMove() in line 301
the player's choice "X" gets stored in the gameData Array. EG gameData = [0,"X",2,...,8]. And the position of "X" (in the exp 1`) gets pushed into player.person.
next it checks, if the person won the game or the game is a tie.
next the function bestMove() gets executed. It chooses a tictactoe-field for the computer.
next player.person and player.computer get updated. Without this step, minimax keeps pushing numbers in those arrays.
Code logic bestMove() and minimax():
bestMove() starts at line 190
the initial player.person and player.computer are saved, to restore them, right after the minimax() function returns a value. Same problem as above: Otherwise, isWinner() returns true on the second click of the player.
for loop gets executed. It selects the first available spot in gameData and replaces it with an "O". player.computer gets updated with this possible move from the computer.
then minimax gets executed, which basically has the same code for both player.computer and player.person, as described in this for loop.
when minimax() returns a value, it get stored in the variable score.
now the gameData Array, player.person and player.computer are reset, so the next iteration of the for loop does not flood them.
at last the if (score.eval > bestScore) checks for the highest score. If highest, the index of the current iteration gets stored in the move variable and is then used to place the "O" on the visible field and inside gameData.
"use strict"
// this function stores the chosen players (condition: player1, player2, computer). The start-game is in another module
let menupage = (function() {
let playerSelection = document.querySelectorAll(".player-selection");
let modalContainer = document.querySelector(".modal-bg");
let submitName = document.querySelector("#submit");
let btnColorPlayerTwo = document.querySelector("#player-two");
let btnColorComputer = document.querySelector("#computer");
let btnColorplayerOne = document.querySelector("#player-one");
let modalClose = document.querySelector(".modal-close");
let inputField = document.querySelector("#name");
let isplayerOne;
let gameModeData = {
playerOne : "",
playerTwo : "",
computer : false,
}
function closeModal() {
inputField.value = "";
modalContainer.classList.remove("bg-active");
}
function submitPlayer() {
if (isplayerOne === true) {
if (inputField.value === "") {
alert("Please enter your battle-tag");
} else if (inputField.value !== "") {
btnColorplayerOne.style.backgroundColor = "#4CAF50";
gameModeData.playerOne = inputField.value;
inputField.value = "";
modalContainer.classList.remove("bg-active");
}
}
if (isplayerOne === false) {
if (inputField.value === "") {
alert("Please enter your battle-tag");
} else if (inputField.value !== "") {
gameModeData.playerTwo = inputField.value;
btnColorPlayerTwo.style.backgroundColor = "#f44336";
gameModeData.computer = false;
btnColorComputer.style.backgroundColor = "#e7e7e7";
inputField.value = "";
modalContainer.classList.remove("bg-active");
}
}
}
function definePlayer(id, color) {
modalClose.addEventListener("click", closeModal);
if (id === "player-one") {
if (color.backgroundColor === "" || color.backgroundColor === "rgb(231, 231, 231)") {
isplayerOne = true;
modalContainer.classList.add("bg-active");
submitName.addEventListener("click", submitPlayer);
} else if (color.backgroundColor === "rgb(76, 175, 80)") {
color.backgroundColor = "#e7e7e7";
gameModeData.playerOne = "";
}
}
if (id === "player-two") {
if (color.backgroundColor === "" || color.backgroundColor === "rgb(231, 231, 231)") {
isplayerOne = false;
modalContainer.classList.add("bg-active");
submitName.addEventListener("click", submitPlayer);
}
}
}
function defineOponent(target) {
if (target.backgroundColor === "rgb(0, 140, 186)") {
return;
} else if (target.backgroundColor === "rgb(231, 231, 231)" || target.backgroundColor === "") {
target.backgroundColor = "#008CBA";
btnColorPlayerTwo.style.backgroundColor = "#e7e7e7";
gameModeData.playerTwo = "";
gameModeData.computer = true;
}
}
let setupPlayers = function setupPlayers() {
if (this.id === "player-one" || this.id === "player-two") {
definePlayer(this.id, this.style);
} else if (this.id === "computer") {
defineOponent(this.style);
}
}
playerSelection.forEach(button => button.addEventListener("click", setupPlayers))
return gameModeData;
}())
let startRound = (function startRound() {
let startGameBtn = document.querySelector("#start-game");
let startScreen = document.querySelector(".start-screen");
let gameboard = document.querySelector(".gameboard");
let selectionMenu = document.querySelector(".window-container");
let frame = document.querySelector(".frame");
let scoreboardPlayer = document.querySelector(".scoreboard-left");
let scoreboardOponent = document.querySelector(".scoreboard-right");
let scorePlayer = document.querySelector(".scoreboard-player");
let scoreOponent = document.querySelector(".scoreboard-oponent");
function displayScore() {
scorePlayer.innerText = menupage.playerOne;
menupage.computer === false ? scoreOponent.innerText = menupage.playerTwo : scoreOponent.innerText = "Computer";
}
function startGame() {
if (menupage.playerOne === "") {
alert("Please choose your profile.");
} else if (menupage.playerTwo === "" && menupage.computer === false) {
alert("Please choose an opponent.")
} else {
startScreen.style.display = "none";
gameboard.style.display = "grid";
scoreboardPlayer.style.display = "grid";
scoreboardOponent.style.display = "grid";
frame.style.display = "none";
selectionMenu.style.gridTemplateAreas = '"header header header" "scoreboard-left gameboard scoreboard-right" "frame frame frame"';
displayScore();
game();
}
}
startGameBtn.addEventListener("click", startGame);
}())
/* ***************************** GAME VS COMPUTER FUNCTION STARTS HERE ************************* */
let vsComputerGame = (function vsComputerGame() {
let player = {
person : [],
computer : [],
}
let gameData = [0, 1, 2, 3, 4, 5, 6, 7, 8]
let isWinner = function isWinner(PLAYER) {
let check = PLAYER.join();
let condition = {
1 : ["0","1","2"],
2 : ["3","4","5"],
3 : ["6","7","8"],
4 : ["0","4","8"],
5 : ["2","4","6"],
6 : ["0","3","6"],
7 : ["1","4","7"],
9 : ["2","5","8"]
}
for (const property in condition) {
if (condition[property].every(v => check.includes(v)) === true) {
return ({win : true});
}
}
return ({win : false });
};
let isTie = function isTie() {
if (emptySpaces(gameData).length === 0 && isWinner(player.computer).win === false && isWinner(player.person).win === false) {
return ({tie: true});
} else {
return ({tie : false});
}
}
function emptySpaces(gameData) {
let updatedBoard = [];
for (let i = 0; i < gameData.length; i++) {
if (gameData[i] !== "X") {
if (gameData[i] !== "O") {
updatedBoard.push(gameData[i]);
}
}
}
return updatedBoard;
}
function bestMove() {
let bestScore = -Infinity;
let move;
// the object player with the values {player:[], computer:[]} is used in isWinner to check who won,
// storedComputer and storedPlayer is needed, to reset both arrays after they go through minimax,
// without, the two object-arrays get fludded
let storedComputer = player.computer.map(x => x);
let storedPlayer = player.person.map(x => x);
// first round of the for loop sets a field for the computer,
// first execution of minimax jumps to player.person
for (let i = 0; i < 9; i++) {
// gameData is the Array, that stores the players' moves. Example: [0, 1, 2, "X", 4, 5, "O", 7, 8]
if (gameData[i] !== "X") {
if (gameData[i] !== "O") {
gameData[i] = "O";
player.computer.push(i);
let score = minimax(gameData, player.person);
gameData[i] = i;
player.person = storedPlayer;
player.computer = storedComputer;
if (score.eval > bestScore) {
bestScore = score.eval;
move = i;
console.log(bestScore);
}
}
}
}
// after a move is found for the computer, O gets logged in the gameData Array and on the visible gameboard
let positionO = document.getElementsByName(move);
gameData[move] = "O";
positionO[0].innerText = "O";
}
function minimax(gameData, PLAYER) {
// the BASE of minimax.
// ************ console.log shows, that it always returns -10 ***************
if (isWinner(player.person).win === true) { return ({eval:-10});}
if (isWinner(player.computer).win === true) { return ({eval:10});}
if (isTie().tie === true) {return ({eval:0});};
/*
PLAYER.push pushes the index-number into either player.computer or player.person
This is needed to check the isWinner function
After that, these Arrays get stored in storedComputer and storedPlayer
*/
if (PLAYER === player.computer) {
let bestScore = -Infinity;
for (let i = 0; i < 9; i++) {
if (gameData[i] !== "X") {
if (gameData[i] !== "O") {
PLAYER.push(i);
//let storedComputer = player.computer.map(x => x);
//let storedPlayer = player.person.map(x => x);
gameData[i] = "O";
let score = minimax(gameData, player.person);
//player.person, player.computer and gameData are resetted, after minimax returns a value
gameData[i] = i;
//player.person = storedPlayer;
//player.computer = storedComputer;
if (score.eval > bestScore) {
bestScore = score.eval;
}
}
}
}
return bestScore;
} else {
let bestScore = Infinity;
for (let i = 0; i < 9; i++) {
if (gameData[i] !== "X") {
if (gameData[i] !== "O") {
PLAYER.push(i);
//let storedComputer = player.computer.map(x => x);
//let storedPlayer = player.person.map(x => x);
gameData[i] = "X";
let score = minimax(gameData, player.computer);
//player.person = storedPlayer;
//player.computer = storedComputer;
gameData[i] = i;
if (score.eval < bestScore) {
bestScore = score.eval;
}
}
}
}
return bestScore;
}
}
let cells = document.querySelectorAll(".cell");
cells.forEach(cell => cell.addEventListener("click", personMove));
function personMove() {
if (this.innerText === "X" || this.innerText === "O") {
return;
} else {
let playersChoice = this.getAttribute("data-parent");
player.person.push(Number(this.getAttribute("data-parent")));
this.innerText = "X";
gameData[playersChoice] = "X";
if (isWinner(player.person).win === true) {
console.log("Win");
};
isTie();
bestMove();
player.computer = [];
player.person = [];
for (let i = 0; i < 9; i++) {
if (gameData[i] === "X") {
player.person.push(i);
} else if (gameData[i] === "O") {
player.computer.push(i);
}
}
}
}
})
/* ***************************** GAME VS COMPUTER FUNCTION ENDS HERE ************************* */
let vsPersonGame = (function vsPersonGame() {
let i = 1;
let player = [];
let oponent = [];
let buttons = document.querySelectorAll(".cell");
buttons.forEach(button => button.addEventListener("click", selection));
function selection() {
let newLocal = this
storeSelection(newLocal);
checkWinner();
}
function storeSelection(input) {
if (i >= 10 || input.innerText !== "") {
return;
} else if (i % 2 === 0) {
return input.innerText = "O", oponent.push(input.dataset.parent),
i++;
} else if (i % 2 !== 0) {
return input.innerText = "X", player.push(input.dataset.parent),
i++;
}
}
function checkWinner() {
let condition = {
1 : ["0","1","2"],
2 : ["3","4","5"],
3 : ["6","7","8"],
4 : ["0","4","8"],
5 : ["2","4","6"],
6 : ["0","3","6"],
7 : ["1","4","7"],
9 : ["2","5","8"]
}
for (const property in condition) {
let toStringplayer = player.join();
let toStringoponent = oponent.join();
if (condition[property].every(v => toStringplayer.includes(v)) === true) {
return alert(menupage.playerOne + " won");
} else if (condition[property].every(v => toStringoponent.includes(v)) === true) {
return alert(menupage.playerTwo + " won");
} else if (i === 10) {
if (condition[property].every(v => toStringplayer.includes(v)) === true) {
return alert(menupage.playerOne + " won");
} else if (condition[property].every(v => toStringoponent.includes(v)) === true) {
return alert(menupage.playerTwo + " won");
} else {
return alert("You tied");
}
}
}
}
})
let game = (function() {
if (menupage.computer === true) {
vsComputerGame();
} else {
vsPersonGame();
}
});
html, body {
display: grid;
height: 100%;
margin: 0;
padding: 0;
}
.window-container {
display: grid;
height: 100%;
grid-template-rows: 10% 80% 10%;
grid-template-areas:
"header header header"
". start-screen ."
"frame frame frame";
}
.header {
grid-area: header;
margin-top: 50px;
font-size: 30px;
text-align: center;
color: red;
font-weight: bolder;
}
.start-screen {
grid-area: start-screen;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 30%;
}
.selection-menu {
flex: 1;
display: grid;
grid-template-rows: 40% 20% 40%;
grid-template-areas:
". . player-two"
"player-one vs ."
". . computer"
}
#player-one {
grid-area: player-one;
background-color: #e7e7e7;
}
#player-two {
grid-area: player-two;
background-color: #e7e7e7;
}
#computer {
grid-area: computer;
background-color: #e7e7e7;
}
#start-game {
display: flex;
cursor: pointer;
border: none;
color: white;
background-color: rgb(37, 36, 36);
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
#vs {
grid-area: vs;
text-align: center;
font-size: 30px;
font-weight: bold;
color: #4CAF50;
}
.player-selection {
cursor: pointer;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
.gameboard {
grid-area: gameboard;
margin-top: 10%;
display: none;
justify-content: center;
grid-template-rows: 150px 150px 150px;
grid-template-columns: 150px 150px 150px;
grid-template-areas:
"tL tM tR"
"mL mM mR"
"bL bM bR";
}
.frame {
grid-area: frame;
display: flex;
position: fixed;
height: 250px;
bottom: 0px;
left: 0px;
right: 0px;
margin-bottom: 0px;
justify-content: center;
align-items: center;
}
.cell {
display: flex;
justify-content: center;
align-items: center;
font-size: 40px;
cursor: pointer;
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#tL {
grid-area: tL;
border-bottom: 2px solid black;
border-right: 2px solid black;
}
#tM {
grid-area: tM;
border-bottom: 2px solid black;
border-right: 2px solid black;
}
#tR {
grid-area: tR;
border-bottom: 2px solid black;
}
#mL {
grid-area: mL;
border-bottom: 2px solid black;
border-right: 2px solid black;
}
#mM {
grid-area: mM;
border-bottom: 2px solid black;
border-right: 2px solid black;
}
#mR {
grid-area: mR;
border-bottom: 2px solid black;
}
#bL {
grid-area: bL;
border-right: 2px solid black;
}
#bM {
grid-area: bM;
border-right: 2px solid black;
}
#bR {
grid-area: bR;
}
.modal-bg {
position: fixed;
width: 100%;
height: 100vh;
top: 0;
left: 0;
background-color: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.5s;
}
.bg-active {
visibility: visible;
opacity: 1;
}
.modal {
position: relative;
background-color: white;
border-radius: 5px 5px 5px 5px;
width: 30%;
height: 20%;
display: flex;
justify-content: space-around;
align-items: center;
flex-direction: column;
}
.modal button {
padding: 10px 50px;
background-color: #2980b9;
color: white;
border: none;
cursor: pointer;
}
.modal-close {
position: absolute;
top: 10px;
right: 10px;
font-weight: bold;
cursor: pointer;
}
#modal-headline {
font-size: 20px;
}
#submit {
margin-top: 5px;
}
.scoreboard-left {
display: none;
}
.scoreboard-player {
grid-area: scoreboard-player;
display: flex;
}
.scoreboard-right {
display: none;
}
.scoreboard-oponent {
grid-area: scoreboard-oponent;
display: flex;
}
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
<link href="styles.css" rel="stylesheet" type="text/css" />
<script src="script.js" defer></script>
<title>Tic Tac Toe</title>
</head>
<body>
<div class="window-container">
<div class="header">Tic Tac Toe</div>
<div class="start-screen">
<div class="selection-menu">
<button class="player-selection" id="player-one">Player One</button>
<button class="player-selection" id="player-two">Player Two</button>
<div id="vs">vs</div>
<button class="player-selection" id="computer">Computer</button>
</div>
</div>
<div class="gameboard">
<div class="cell unselectable" id="tL" data-parent="0" name="0"></div>
<div class="cell unselectable" id="tM" data-parent="1" name="1"></div>
<div class="cell unselectable" id="tR" data-parent="2" name="2"></div>
<div class="cell unselectable" id="mL" data-parent="3" name="3"></div>
<div class="cell unselectable" id="mM" data-parent="4" name="4"></div>
<div class="cell unselectable" id="mR" data-parent="5" name="5"></div>
<div class="cell unselectable" id="bL" data-parent="6" name="6"></div>
<div class="cell unselectable" id="bM" data-parent="7" name="7"></div>
<div class="cell unselectable" id="bR" data-parent="8" name="8"></div>
</div>
<div class="modal-bg">
<div class="modal">
<h2 id="modal-headline">Choose a Name:</h2>
<input type="text" id="name">
<button id="submit" class="submit">Submit</button>
<span class="modal-close">X</span>
</div>
</div>
<div class="frame">
<button id="start-game">Start Game</button>
</div>
<div class="scoreboard-left">
<div class="display-player">
<div class="scoreboard-player"></div>
<div class="p-win">Wins: </div><div class="player-win">0</div>
<div class="p-loss">Losses: </div><div class="player-loss">0</div>
<div class="p-tie">Ties: </div><div class="player-tie">0</div>
</div>
</div>
<div class="scoreboard-right">
<div class="display-oponent">
<div class="scoreboard-oponent"></div>
<div class="o-win">Wins: </div><div class="oponent-win">0</div>
<div class="o-loss">Losses: </div><div class="oponent-loss">0</div>
<div class="o-tie">Ties: </div><div class="oponent-tie">0</div>
</div>
</div>
</div>
</body>
</html>
I appreciate any feedback and hints to solve the problem.
Related
Creating a number guessing game that has stages
Create a number guessing game to generate a number between the range of 1 and 2. The game should prompt users for their username. Set range as function parameter and prompt the player to predict the generated number between the given range. At a correct guess, the game should award the player a point and move them to stage 2 by increasing the range limit value by 1, e.g. range is from 1 and 3 for stage 2 and so on. I created it but to flow from one level to another is the problem
This is a working solution. Feel free to comment with any questions. let rangeTracker = [1, 2] let totalPointsTracker = [0] function randomIntFromInterval(min, max) { return Math.floor(Math.random() * (max - min + 1) + min) } document.getElementById('continueBtn').onclick = function() { let username = document.getElementById("newUsername").value if (username != '') { document.querySelector('.usernameCon').style.display = 'none' document.querySelector('.userInfo').style.display = 'flex' document.querySelector('.gameCon').style.display = 'flex' document.getElementById("username").innerHTML = `Hello ${username}, this is stage ${totalPointsTracker[0] + 1}` document.getElementById("totalPoints").innerHTML = `Total Points: ${totalPointsTracker[0]}` document.getElementById("title").innerHTML = `Guess a number between ${rangeTracker[0]} and ${rangeTracker[1]}` } } const randomInt = randomIntFromInterval(rangeTracker[0], rangeTracker[1]) document.getElementById('guessBtn').onclick = function() { let userGuess = document.getElementById("userGuess").value if (userGuess != '') { if (userGuess == randomInt) { document.getElementById("status").innerHTML = `Correct!` document.getElementById("status").style.color = 'forestgreen' const previousMaxRange = rangeTracker[1] rangeTracker[1] = previousMaxRange + 1 const previousTotalPoints = totalPointsTracker[0] totalPointsTracker[0] = previousTotalPoints + 1 success() } else { document.getElementById("userGuess").value = '' document.getElementById("status").innerHTML = `Incorrect, guess again!` document.getElementById("status").style.color = 'red' } } else if (userGuess == '') { document.getElementById("status").innerHTML = `Please guess a number!` document.getElementById("status").style.color = 'red' } } function success() { let username = document.getElementById("newUsername").value document.getElementById("userGuess").value = '' document.getElementById("username").innerHTML = `Hello ${username}, this is stage ${totalPointsTracker[0] + 1}` document.getElementById("totalPoints").innerHTML = `Total Points: ${totalPointsTracker[0]}` document.getElementById("title").innerHTML = `Guess a number between ${rangeTracker[0]} and ${rangeTracker[1]}` } .usernameCon { display: flex; flex-direction: column; } .usernameCon label { font-size: 26px; } .usernameCon #newUsername { width: 230px; margin-top: 10px; } .usernameCon #continueBtn { font-size: 17px; width: 130px; margin-top: 10px; cursor: pointer; } .userInfo { display: none; flex-direction: column; height: fit-content; } .userInfo #username { font-size: 26px; } .userInfo #totalPoints { font-size: 24px; margin-top: 10px; } .gameCon { display: none; flex-direction: column; } .gameCon #title { font-size: 22px; margin-top: 10px; } .gameCon #status { font-size: 20px; margin-top: 10px; } .gameCon #userGuess { width: 230px; margin-top: 8px; } .gameCon #guessBtn { font-size: 17px; width: 130px; margin-top: 10px; cursor: pointer; } <div class="usernameCon"> <label>Enter Your Username</label> <input id="newUsername" /> <button id="continueBtn">Continue</buttom> </div> <div class="userInfo"> <label id="username"></label> <label id="totalPoints"></label> </div> <div class="gameCon"> <label id="title">Guess a number between 1 and 2</label> <label id="status"></label> <input id="userGuess" /> <button id="guessBtn">Guess</button> </div>
Add editable drop-down with multi select items through html css or vanilla js
Just wanted to know easiest way to achieve this
Perhaps something like this: (()=> { const formEls = document.querySelectorAll(".input-tags"); for(let i = 0; i < formEls.length; i++) { const formEl = formEls[i], inputEl = document.createElement("input"), tagsEl = document.createElement("span"), listEl = document.createElement("datalist"); formEl.tags = []; Object.defineProperties(formEl, { list: { get(){return getData(this, "list")}, set(val){this.dataset.list = val} }, tags: { get(){return getData(this, "tags")}, set(val){this.dataset.tags = val} }, value: { get(){return this.dataset.value || ""}, set(val){this.dataset.value = val} } }); const list = formEl.list; listEl.id = "input-tags-datalist" + i; inputEl.setAttribute("list", listEl.id); inputEl.type = "text"; tagsEl.className = "tags"; for(let i = 0, optionEl = document.createElement("option"); i < list.length; i++) { optionEl = optionEl.cloneNode(false); optionEl.value = list[i]; listEl.appendChild(optionEl); } formEl.appendChild(tagsEl); formEl.appendChild(inputEl); formEl.appendChild(listEl); inputEl._isClicked = true; inputEl.addEventListener("keydown", e => inputEl._isClicked = !e.keyCode || e.keyCode==13); inputEl.addEventListener("keyup", e => inputEl._isClicked = true); inputEl.addEventListener("input", e => { formEl.value = inputEl.value; if (!inputEl._isClicked && !inputEl.value.match(/(^[^"']+ $)|(^(["']).+\3$)/)) { dispatchEvent(formEl, "input"); return inputWidth(formEl); } const val = inputEl.value.replace(/^\s*((["'])([^"']+)\2|([^"']+)\s+)$/, "$4$3").replace(/[^\w -_]+/g, "").replace(/[ ]{2,}/g, " "); if (formEl.dataset.autotags !== undefined || formEl.list.indexOf(val) != -1) { inputEl.value = val; addTag(inputEl); } formEl.value = inputEl.value; dispatchEvent(formEl, "input"); inputWidth(formEl); });//inputEl.oninput() tagsEl.addEventListener("click", e => { if (!e.target.parentNode.classList.contains("tag")) return; const tag = e.target.parentNode.textContent, list = formEl.list, tags = formEl.tags, index = list.indexOf(tag), optionEl = listEl.children[index]; if (optionEl.classList.contains("new")) { list.splice(index, 1); optionEl.parentNode.removeChild(optionEl); } else optionEl.disabled = false; tags.splice(tags.indexOf(tag), 1); formEl.tags = tags; formEl.list = list; e.target.parentNode.parentNode.removeChild(e.target.parentNode); inputWidth(formEl); e.stopPropagation(); formEl.click(); dispatchEvent(formEl, "input"); });//tagsEl.onclick() formEl.addEventListener("click", e => inputEl.focus()); inputWidth(formEl); } function dispatchEvent(el, type, opts) { return el.dispatchEvent(new Event(type, opts)); } function inputWidth(formEl) { const inputEl = formEl.querySelector("input"); inputEl.style.width = "1em"; //min width const inputStyle = window.getComputedStyle(inputEl), formStyle = window.getComputedStyle(inputEl.parentNode), inputRect = inputEl.getBoundingClientRect(), formRect = inputEl.parentNode.getBoundingClientRect(), canvas = document.createElement('canvas'), ctx = canvas.getContext("2d"); ctx.font = inputStyle.font; const widthText = (ctx.measureText(inputEl.value).width + parseFloat(inputStyle.paddingLeft) + parseFloat(inputStyle.paddingRight) + parseFloat(inputStyle.textIndent) + parseFloat(inputStyle.borderLeftWidth) + parseFloat(inputStyle.borderRightWidth) + 1 ), widthBox = formRect.right - inputRect.left - parseFloat(formStyle.paddingLeft) - parseFloat(formStyle.paddingRight) - 1; inputEl.style.width = Math.max(widthText, widthBox) + "px"; } function getData(el, key) { return el.dataset[key] ? el.dataset[key].split(",") : []; } function addTag(input) { const formEl = input.parentNode, tag = input.value.trim(), list = formEl.list, tags = formEl.tags; if (tag === "" || tags.indexOf(tag) != -1) return; const tagsEl = formEl.querySelector(".tags"), tagEl = document.createElement("span"), datalistEl = formEl.querySelector("datalist"); if (formEl.dataset.autotags !== undefined && list.indexOf(tag) == -1) { const option = document.createElement("option"); option.value = tag; option.className = "new"; datalistEl.appendChild(option); list[list.length] = tag; } tags[tags.length] = tag; formEl.list = list; formEl.tags = tags; const index = list.indexOf(tag); datalistEl.children[index].disabled = true; tagEl.className = "tag"; tagEl.textContent = tag; tagEl.appendChild(document.createElement("span")); tagsEl.appendChild(tagEl); input.value = ""; } })(); //example: const test = document.getElementById("test"); test.addEventListener("input", e => { if (e.target !== test) return; console.log('value:', test.value); console.log("tags:", JSON.stringify(test.tags)); console.log("list:", JSON.stringify(test.list)); }, false); .input-tags { display: inline-block; border: 1px solid black; font-size: 0.8em; padding: 0.1em 0.1em 0.1em 0.05em; width: 100%; line-height: 1em; } .input-tags > input, .input-tags > input:focus, .input-tags > input:active { outline: none; border: none; margin: 0.15em 0; vertical-align: middle; max-width: 100%; box-sizing: border-box; } .input-tags > input::-webkit-calendar-picker-indicator { display: none !important; } .input-tags > .tags { vertical-align: middle; } .input-tags .tags .tag { display: inline-block; background-color: lightblue; border: 1px solid blue; border-radius: 2px; font-family: "Segoe UI","Liberation Sans",sans-serif; margin: 0.1em; padding: 0 0.2em; line-height: 1.3em; } .input-tags .tags .tag > span { margin: -0.05em -0.2em 0 0.05em; cursor: pointer; display: inline-block; font-size: 1.3em; transform: rotate(45deg); border-radius: 2em; line-height: 0.7em; float: right; } .input-tags .tags .tag > span:before { content: "+"; position: relative; top: -0.1em; } .input-tags .tags .tag > span:hover { background-color: #60B3CE; } <div style="display: grid; grid-template-columns: auto auto"> <span>Auto-add new tags, suggestions:</span> <div style="display: inline-block; width: 50vw;"> <div id="test" class="input-tags" data-autotags data-list="test,sometag,SOMETAG,another tag,another tag2,another tag3,another,tag"></div> </div> <span>Auto-add new tags, no suggestions:</span> <div style="display: inline-block; width: 50vw;"> <span class="input-tags" data-autotags></span> </div> <span>No new tags, suggestions:</span> <div style="display: inline-block; min-width: 10em;"> <div class="input-tags" data-list="test,some tag,very long tag,blah"></div> </div> <div>
addEventListenner on js : the bubble does not want to be canceled
I coded a game for 2 people: Initialization is done with player1 () with the displayScore () function I can reiterate the score as much as I want (here everything is fine) but when I switch to the other player with the changePlayer (player) function, the event goes well with player2 I can also reiterate but the concern is that the first player continues to play while my functions and variables are well partitioned the problem comes from the event of the first which continues despite my stopPropagation () which I did not forget to report. Where's the problem I've been on for a day and a half. I join the two files html and js and also css Thank you in advance, cordially. jeu de dés <body> <div id="launchGame"> <h1>NEW GAME</h1> </div> <div id="players"> <div id="player_1"><h2>PLAYER 1</h2><span id="score_player_1">Score :</span></div> <picture><img id="dice">img</picture> <div id="player_2"><h2>PLAYER 2</h2><span id="score_player_2">Score :</span></div> </div> <div id="current_score"> <div id="current_score_1"><p>current<span id="round_player_1"></span></p></div> <div id="controls_game"> <h3 id="roll_dice"><img src="images/re_game.png">ROLL DICE</h3> <h3 id="hold"><img src="images/charge_score.png">HOLD</h3> </div> <div id="current_score_2"><p>current<span id="round_player_2"></span></p></div> </div> </body> (function(){ window.addEventListener("DOMContentLoaded", function() { const launch = document.getElementById("launchGame"); const rollDice = document.getElementById("roll_dice"); function currentPlayer(player) { const hold = document.getElementById("hold"); if (player.getElementsByTagName("img").length < 1) { player.firstChild.style.position = "relative"; var currentPlayer = new Image(); currentPlayer.src = "images/current_player.png"; currentPlayer.setAttribute("class", player.getAttribute("id")); currentPlayer.style.position = "absolute"; player.firstChild.appendChild(currentPlayer); } else { player.getElementsByTagName("img")[0].style.visibility = "visible"; } changePlayer(player); rollDice.addEventListener("click", function(e) { e.stopPropagation(); displayScore(player); }, {capture:false, once: false, useCapture: true}); } function player1() { let player1 = document.getElementById("player_1"); currentPlayer(player1); } function player2() { let player2 = document.getElementById("player_2"); currentPlayer(player2); } function changePlayer(player) { hold.addEventListener("click", function(e) { e.stopPropagation(); player.getElementsByTagName("img")[0].style.visibility = "hidden"; if (player.getAttribute("id") === "player_1") { console.log(player.getAttribute("id")); player2(); } else if (player.getAttribute("id") === "player_2") { console.log(player.getAttribute("id")); player1(); } }, {capture:false, once:true, useCapture: true}); } function displayScore(player) { let scoreDice = getScoreDice(); let scorePlayer = document.getElementById("round_" + player.getAttribute("id")); scorePlayer.textContent = scoreDice(); } function getScoreDice() { var result = 0; var faceDice = Math.floor(Math.random()*7); if ((faceDice > 0) && (faceDice < 7)) { result = faceDice; } else { result = faceDice+1; } function innerGetScoreDice() { return result; } return innerGetScoreDice; } launch.addEventListener("click", player1(), {capture:false, once:true}); }); })() body { margin: 0; padding: 0; background-color: silver; background: linear-gradient(to left, white, white 50%, rgb(228, 227, 227) 50%, rgb(228, 227, 227)); text-align: center; color: gray; } h1 { width: 40%; margin: 1% auto 5%; font-size: 1.5rem; } h1::before { content: "⊕ "; font-size: 2rem; align-items: center; color: tomato; } h2 { height: 30px; line-height: 30px; } h3 { margin: 7% 0; } span { display: block; margin-top: 50%; } img { max-width: 10%; vertical-align: middle; margin-right: 3%; } #players, #final_score, #current_score, #current_score_1, #current_score_2, #controls_game { display: flex; } #players, #final_score, #current_score { flex-direction: row; justify-content:space-evenly; align-items: center; } #players { justify-content:space-around; margin: 1% 1% 10%; } #current_score_1, #current_score_2 { border-radius: 20%; padding: 2% 4%; background-color: tomato; color: white; } #controls_game { flex-direction: column; max-width: 33%; }
In function changePlayer(player), isn't variable 'hold' undefined ?
No The variable hold is defined and ok All variables are ok Problems is in event of first players Bubble or capture I don't now whats this
Javascript calculator display 0 when press any of mathematical operators
I have just started learning JavaScript and doing my first project by following Frontend Masters tutorial. Calculator is working perfectly fine except a problem. When i press any mathematical operator button (after passing the first number) the screen shows 0. I want it to show the last pressed number and not 0. I just want to display the last entered number on screen when someone press any of maths operators like addition, subtraction. let runningTotal = 0; let bufferNumber = '0'; let previousOperator; const display = document.querySelector('.display') //initializing function to execute when a button is clicked document .querySelector(".calc-buttons") .addEventListener("click", function(event) { clickButton(event.target.innerText); }); //This function checks if selected value is number or symbol function clickButton(value) { if (isNaN(parseInt(value))) { handleSymbol(value); } else { handleNumber(value); } rerender() } //This function handles the display of number //this is where initia displayed 0 will be handled function handleNumber(value) { if (bufferNumber === '0') { bufferNumber = value; } else { bufferNumber += value; } console.log('buffer number is ', bufferNumber); } //This function executes different symbols. function handleSymbol(value) { switch(value) { case 'C': bufferNumber = "0"; runningTotal = 0; previousOperator =null; break; case '←': if (bufferNumber.length === 1) { bufferNumber = "0"; } else { bufferNumber = bufferNumber.substring(0, bufferNumber.length-1); } break; case '=': console.log('= operator displaying result') if (previousOperator === null) { return; } flushOperation(parseInt(bufferNumber)); previousOperator = null bufferNumber = "" + runningTotal; runningTotal = 0; break; default: console.log('default'); handleMath(value); break; } } //handles DMAS // We need to convert strings to integers before doing maths calculations on it function handleMath(value) { console.log('handle math function') const intBuffer = parseInt(bufferNumber); if (runningTotal === 0) { runningTotal = intBuffer; } else { flushOperation(intBuffer); } previousOperator = value; console.log('previous operator is', previousOperator) bufferNumber = "0"; } function flushOperation(intBuffer){ console.log('flush operation') if (previousOperator === "+"){ runningTotal += intBuffer; } else if (previousOperator === "−"){ runningTotal -= intBuffer; } else if (previousOperator === "×"){ runningTotal *= intBuffer; } else { runningTotal /= intBuffer; } } function rerender() { display.innerText = bufferNumber; } * { box-sizing: border-box; } body { margin-left: 30%; padding: 0; } .calc { width: 400px; background-color: black; color: white; } .display { font-family: 'Courier New', Courier, monospace; font-size: 40px; text-align: right; padding: 20px 5px; } .calc-button { background-color: rgb(206, 203, 203); color: black; height: 100px; width: 24.5%; border: none; font-size: 40px; cursor: pointer; } .calc-button:hover { background-color: rgb(252, 249, 249); } .calc-button:active { background-color: gray; } .calc-button:last-child { background-color: rgb(209, 158, 62); } .calc-button:last-child:hover { background-color: rgb(228, 189, 118); } .calc-button:last-child:active { background-color:white; } .double { width: 49.7%; } .triple { width: 74.9%; } .calc-rows { display: flex; align-content: stretch; justify-content: space-between; margin-bottom: 0.5%; } <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Calculator</title> <link rel="stylesheet" href="styles.css"> </head> <body> <!-- container for overall calculator--> <div class='calc'> <section class="display"> 0 </section> <section class="calc-buttons"> <div class="calc-rows"> <button class="double calc-button">C</button> <button class="calc-button">←</button> <button class="calc-button">÷</button> </div> <div class="calc-rows"> <button class="calc-button">7</button> <button class="calc-button">8</button> <button class="calc-button">9</button> <button class="calc-button">×</button> </div> <div class="calc-rows"> <button class="calc-button">4</button> <button class="calc-button">5</button> <button class="calc-button">6</button> <button class="calc-button">−</button> </div> <div class="calc-rows"> <button class="calc-button">1</button> <button class="calc-button">2</button> <button class="calc-button">3</button> <button class="calc-button">+</button> </div> <div class="calc-rows"> <button class="calc-button triple">0</button> <button class="calc-button">=</button> </div> </section> </div> <script src="script.js"></script> </body> </html>
I have solved this problem by changing a few things. when clicking on any of the operator buttons the screen was showing 0 because when the operator was clicked handleSymbol() was called and then handleMath() get called from the default case and the handleMath() function assigned the bufferNumber value of 0. Removing bufferNumber = "0"; in handleNumber() creates another problem which is bufferNumber is concatenating two numbers into one. for e.g, if we do 5 + 6, the bufferNumber would become 56 the bufferNumber should store only one number which is the number before clicking the operator and there must be another variable to store another number. I have named this var as newBufferNumber in handleSymbol() function I have added these lines to make the calculator keep working if the calculation continues without clicking the = button. const ifAnySymbolSelected = value == '÷' || value == '×' || value == '+' || value == '−'; if(bufferNumber && newBufferNumber && ifAnySymbolSelected ) { console.log(value, bufferNumber, newBufferNumber) flushOperation(parseInt(bufferNumber), parseInt(newBufferNumber)); } handleNumber() function contains few changes Replacing the line if (bufferNumber === '0') { bufferNumber = value; } with if(symbolSelected) { newBufferNumber = newBufferNumber + value } to store another digit which the number we click, after clicking calculating operators. On the screen, I am displaying both numbers including the operator. you can always control what you want to show on the screen. these are the major things I changed. I want you to go through all of it and observe. hope this helps. Working fiddle: let runningTotal = 0; let bufferNumber = ''; let newBufferNumber = ''; let previousOperator; const display = document.querySelector('.display') //initializing function to execute when a button is clicked document .querySelector(".calc-buttons") .addEventListener("click", function(event) { clickButton(event.target.innerText); }); //This function checks if selected value is number or symbol function clickButton(value) { if (isNaN(parseInt(value))) { handleSymbol(value); } else { handleNumber(value); } rerender() } //This function handles the display of number //this is where initia displayed 0 will be handled let symbolSelected ; function handleNumber(value) { if(symbolSelected) { newBufferNumber = newBufferNumber + value } else { bufferNumber += value; } console.log(bufferNumber,newBufferNumber) } //This function executes different symbols. function handleSymbol(value) { // if calculation goes regularly without clicking '=' operator const ifAnySymbolSelected = value == '÷' || value == '×' || value == '+' || value == '−'; if(bufferNumber && newBufferNumber && ifAnySymbolSelected ) { console.log(value, bufferNumber, newBufferNumber) flushOperation(parseInt(bufferNumber), parseInt(newBufferNumber)); } if (ifAnySymbolSelected) { symbolSelected = true; newBufferNumber = '' } else{ symbolSelected = false; } switch (value) { case 'C': bufferNumber = "0"; runningTotal = 0; previousOperator = null; previousOperator = null; break; case '←': if (bufferNumber.length === 1) { bufferNumber = "0"; } else { bufferNumber = bufferNumber.substring(0, bufferNumber.length - 1); } break; case '=': if (previousOperator === null) { return; } flushOperation(parseInt(bufferNumber), parseInt(newBufferNumber)); previousOperator = null bufferNumber = runningTotal; break; default: handleMath(value); break; } } // handles DMAS // We need to convert strings to integers before doing maths calculations on it function handleMath(value) { const intBuffer = parseInt(bufferNumber); if (runningTotal === 0) { runningTotal = intBuffer; } previousOperator = value; } function flushOperation(intBuffer,newBufferNumber){ console.log('flush operation') if (previousOperator === "+"){ runningTotal = intBuffer + newBufferNumber; } else if (previousOperator === "−"){ runningTotal = intBuffer - newBufferNumber; } else if (previousOperator === "×"){ runningTotal = intBuffer * newBufferNumber; } else { runningTotal = intBuffer / newBufferNumber; } bufferNumber = runningTotal; } function rerender() { const displayText = `${bufferNumber} ${previousOperator ? previousOperator : ''} ${ previousOperator ? newBufferNumber : ''}` display.innerText = displayText; } * { box-sizing: border-box; } body { margin-left: 30%; padding: 0; } .calc { width: 400px; background-color: black; color: white; } .display { font-family: 'Courier New', Courier, monospace; font-size: 40px; text-align: right; padding: 20px 5px; } .calc-button { background-color: rgb(206, 203, 203); color: black; height: 100px; width: 24.5%; border: none; font-size: 40px; cursor: pointer; } .calc-button:hover { background-color: rgb(252, 249, 249); } .calc-button:active { background-color: gray; } .calc-button:last-child { background-color: rgb(209, 158, 62); } .calc-button:last-child:hover { background-color: rgb(228, 189, 118); } .calc-button:last-child:active { background-color:white; } .double { width: 49.7%; } .triple { width: 74.9%; } .calc-rows { display: flex; align-content: stretch; justify-content: space-between; margin-bottom: 0.5%; } .as-console-wrapper{display: none!important;} <!-- container for overall calculator--> <div class='calc'> <section class="display"> </section> <section class="calc-buttons"> <div class="calc-rows"> <button class="double calc-button">C</button> <button class="calc-button">←</button> <button class="calc-button">÷</button> </div> <div class="calc-rows"> <button class="calc-button">7</button> <button class="calc-button">8</button> <button class="calc-button">9</button> <button class="calc-button">×</button> </div> <div class="calc-rows"> <button class="calc-button">4</button> <button class="calc-button">5</button> <button class="calc-button">6</button> <button class="calc-button">−</button> </div> <div class="calc-rows"> <button class="calc-button">1</button> <button class="calc-button">2</button> <button class="calc-button">3</button> <button class="calc-button">+</button> </div> <div class="calc-rows"> <button class="calc-button triple">0</button> <button class="calc-button">=</button> </div> </section> </div>
Textarea which counting entered symbolys
function someFunc(){ var integer = document.getElementById('email').value.toString().length; var symbolCount = 0 + integer; // var last2 = 100 - integer2; if (symbolCount >= 100) { document.querySelector('.hidden_block').style.color = 'green'; } else if (symbolCount <= 100) { document.querySelector('.hidden_block').style.color = 'black'; document.querySelector('.error').style.display = "block"; } else { document.getElementById('max').style.color = 'black'; } document.getElementById('symbol_count').innerHTML = symbolCount; } email.addEventListener("click", function(){ document.querySelector('.hidden_block').style.display = 'block'; document.getElementById('max').style.display = 'none'; }); #max, #max2 { text-align: right; margin-right: 55px; } .hidden_block { display: none; text-align: right; margin-right: 55px; } .error { display: none; color: red; } <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/> <label for="email">Positive</label> <textarea type="email" class="form-control" id="email" oninput="someFunc()" placeholder="Tell people about your experience: describe the place or activity, recommendations for travelers?"></textarea> <p id="max">Minimal length - symbols</p> <div class="hidden_block"> <span id="count">Symbols : <span id="symbol_count">0 </span> (minimum:100)</span> </div> <span class="error">Your review must be at least 100 characters long. Adding details really helps travelers.</span> Hi everyone.I have a that simple textarea field.I need to realize something like that.When u write less than 100 words and click the outside of the email id the border color must be red.And error class must displayed.And i need to if the textarea field is empty the tag p with id max must be display block if the user will write any symbol the id max must bu display none.Thanks for help
function someFunc(){ var integer = document.getElementById('email').value.toString().length; var symbolCount = 0 + integer; var integerValue = document.getElementById('email'); var hidden_block = document.querySelector('.hidden_block'); var max = document.getElementById('max'); var error = document.querySelector('.error'); var positive = document.getElementById("positive"); // var last2 = 100 - integer2; if (integer >= 1) { hidden_block.style.display = 'inline-block'; max.style.display = 'none'; integerValue.classList.add("form-control"); } else { hidden_block.style.display = 'none'; max.style.display = 'block'; error.style.display = "none"; positive.style.color = "#002C38"; integerValue.classList.remove("form-redBorder"); } integerValue.addEventListener("click", function(){ error.style.display = "none"; positive.style.color = "#002C38"; integerValue.classList.remove("form-redBorder"); }); //Red error and border document.body.addEventListener("click", function(e) { var target = e.target || e.srcElement; if (target !== integerValue && !isChildOf(target, integerValue)) { error.style.display = "inline-block"; integerValue.classList.add("form-redBorder"); positive.style.color = "red"; } if (integer >= 100) { error.style.display = "none"; integerValue.classList.remove("form-redBorder"); positive.style.color = "#002C38"; } }, false); function isChildOf(child, parent) { if (child.parentNode === parent) { return true; } else if (child.parentNode === null) { return false; } else { return isChildOf(child.parentNode, parent); } } //Finished Red error and border //Start to count symbols if (symbolCount >= 100) { hidden_block.style.color = 'green'; } else if (symbolCount <= 100) { hidden_block.style.color = 'black'; } else { max.style.color = 'black'; // document.getElementById('max2').style.color = 'black'; } document.getElementById('symbol_count').innerHTML = symbolCount; } #email { display: block; padding: 6px 12px; margin: 0 auto; width: 90% !important; height: 120px !important; /*border:1px solid #44A1B7 !important;*/ } .form-control { margin: 0 auto; width: 90% !important; height: 120px !important; border:1px solid #44A1B7; } #positive, #negative { padding: 14px 15px 1px 55px; color: #002C38; font-size: 18px; } .form-redBorder { margin: 0 auto; border:1px solid #FF0000 !important; } #max, #max2 { position: absolute; right: 1%; margin-right: 55px; } .hidden_block { position: absolute; right: 1%; display: none; text-align: right; margin-right: 55px; } .error { margin-left: 55px; display: none; color: #FF0000; } <form role="form"> <div class="form-group"> <p class="help-block">About youre site.</p> <label for="email" id="positive">Positive</label> <textarea type="email" id="email" oninput="someFunc()" placeholder="Your review must be at least 100 characters long<br> Adding details really helps travelers"></textarea> <p id="max">(100 character minimum)</p><div class="hidden_block"> <span id="count">Symbols : <span id="symbol_count">0 </span> (min:100)</span> </div> <span class="error">Your review must be at least 100 characters long.<br> Adding details really helps travelers..</span> </div> </form>