refactoring: How can I improve this as a clean code - javascript

I am currently studying JS myself.And make the pig-game which is project of the course that I watched. I'm always curious how can I improve my code but I got no idea where do I begin.
Is there some principle that I can improve any code?
If there's a way, Could you let me know? Thanks!
https://github.com/wonkooklee/pig-game
result : https://wonkooklee.github.io/pig-game/
below is main functions
document.querySelector('.btn-roll').addEventListener('click', function() {
if (gamePlaying) {
dice = Math.floor(Math.random() * 6) + 1;
diceDOM.style.display = 'block';
diceDOM.src = `dice-${dice}.png`;
if (dice === 6 && lastDice === 6) {
// Player looses score
scores[activePlayer] = 0;
document.getElementById(`score-${activePlayer}`).textContent = '0';
nextPlayer();
} else if (dice !== 1 && dice !== 6) {
roundScore += dice;
document.getElementById(`current-${activePlayer}`).textContent = roundScore;
lastDice = 0;
} else if (dice === 6) {
roundScore += dice;
document.getElementById(`current-${activePlayer}`).textContent = roundScore;
lastDice = dice;
} else {
nextPlayer();
}
}
});
document.querySelector('.btn-hold').addEventListener('click', function() {
if (gamePlaying) {
scores[activePlayer] += roundScore;
document.getElementById(`score-${activePlayer}`).textContent = scores[activePlayer];
let input = document.getElementById('scoreSet').value;
let winningScore;
if (isNaN(input) === false) {
winningScore = input;
} else {
document.getElementById('scoreSet').value = '100';
}
if (scores[activePlayer] >= winningScore) {
document.getElementById(`name-${activePlayer}`).textContent = 'WINNER!';
document.querySelector(`.player-${activePlayer}-panel`).classList.add('winner');
document.querySelector(`.player-${activePlayer}-panel`).classList.remove('active');
diceDOM.style.display = 'none';
gamePlaying = false;
} else {
nextPlayer();
}
}
});

Martin Fowler wrote a great book "Refactoring". Besides, Fowler has a great blog Refactoring.com, where you can find a lot of information about refactoring with examples in Javascript.
I'm not strong in Javascript, but can let you some advices about your code.
1.Simplify Conditional Logic
For example like this:
if (dice === 6 && lastDice === 6) {
// Player looses score
scores[activePlayer] = 0;
document.getElementById(`score-${activePlayer}`).textContent = '0';
nextPlayer();
return;
}
if (dice !== 1 && dice !== 6) {
roundScore += dice;
document.getElementById(`current-${activePlayer}`).textContent = roundScore;
lastDice = 0;
return;
}
if (dice === 6) {
roundScore += dice;
document.getElementById(`current-${activePlayer}`).textContent = roundScore;
lastDice = dice;
return;
}
nextPlayer();
2.Delete duplicated code and extract function
For example
function someFunctionName(diffRoundScore, lastDiceValue){
roundScore += diffRoundScore;
document.getElementById(`current-${activePlayer}`).textContent = roundScore;
lastDice = lastDiceValue;
}
if (dice !== 1 && dice !== 6) {
someFunctionName(dice, 0);
return;
}
if (dice === 6) {
someFunctionName(dice, dice);
return;
}
3.Change check "dice for 6" to function
function isDiceEqualsSix { return dice === 6};
if (isDiceEqualsSix && lastDice === 6) {
// Player looses score
scores[activePlayer] = 0;
document.getElementById(`score-${activePlayer}`).textContent = '0';
nextPlayer();
return;
}
if (dice !== 1 && !isDiceEqualsSix) {
someFunctionName(dice, 0);
return;
}
if (isDiceEqualsSix) {
someFunctionName(dice, dice);
return;
}
4.Change "6" to a constant variable or a function
const LIMIT_SCORE = 6;
function isDiceEqualsSix { return dice === LIMIT_SCORE};
if (isDiceEqualsSix && lastDice === LIMIT_SCORE) {
// Player looses score
scores[activePlayer] = 0;
document.getElementById(`score-${activePlayer}`).textContent = '0';
nextPlayer();
return;
}
I hope I helped you.

Related

Reuse button to trigger another function

So I'm still practicing and learning and I'm creating a number guessing game and I was planning on re-using a existing button to trigger the reset of the game however for some reason it will reset however upon doing so-- resetGame() will reset variables but not start the checkGuess() like it's suppose to and only continue to randomize the randomNumber when 'submit' button is clicked..
I'm assuming that this must be bad practice and figure I shouldn't do this but wanted to ask why this wasn't re-starting the game as it should... what am I missing?
let randomNumber = Math.floor(Math.random() * 10) + 1;
let guessField = document.getElementById('guessField');
let enterButton = document.getElementById('userSubmit');
let lastResult = document.querySelector('.lastResult');
let lowOrHigh = document.querySelector('.lowOrHigh');
let guesses = document.querySelector('.guesses');
let guessRemaining = 5;
enterButton.addEventListener('click', checkGuess);
// function log() {
// console.log(Number(document.getElementById('guessField').value));
// }
function checkGuess() {
let userGuess = Number(guessField.value);
if (guessRemaining === 5) {
guesses.textContent = 'Previous guesses: ';
}
guesses.textContent += userGuess + ' ';
if (userGuess === randomNumber) {
lastResult.textContent = 'Congratulations! You got it right!';
lastResult.style.background = 'green';
gameOver();
} else if (guessRemaining < 1) {
lastResult.textContent = 'GAME OVER!';
gameOver();
} else {
lastResult.textContent = 'Wrong answer!';
lastResult.style.background = 'red';
lastResult.style.color = 'white';
if (userGuess < randomNumber) {
lowOrHigh.textContent = 'Too low!';
} else if (userGuess > randomNumber) {
lowOrHigh.textContent = 'Too high!';
}
}
guessRemaining--;
guessField.value = '';
guessField.focus();
}
function gameOver() {
guessField.disabled = true;
enterButton.setAttribute('value', 'Replay');
enterButton.addEventListener('click', resetGame);
}
function resetGame() {
randomNumber = Math.floor(Math.random() * 10) + 1;
// document.getElementById('userGuess').value = '';
lastResult.textContent = '';
guesses.textContent = '';
lowOrHigh.textContent = '';
guessField.disabled = false;
enterButton.setAttribute('value', 'Submit');
guessRemaining = 5;
}
The problem is that after you finish the game the resetGame callback is added as event listener and is triggered every time you click the userSubmit button. There are few possibilities how to solve this problem:
1. Check if the game is running
In this solution, you don't add the resetGame callback, but you call it within the checkGuess callback. To make this solution work you have to add a variable which represents if the game is running or not. This way if the game is still running the checkGuess callback will call the default behaviour, but after the gameOver is called, checkGuess will reset the game.
let randomNumber = Math.floor(Math.random() * 10) + 1;
let guessField = document.getElementById('guessField');
let enterButton = document.getElementById('userSubmit');
let lastResult = document.querySelector('.lastResult');
let lowOrHigh = document.querySelector('.lowOrHigh');
let guesses = document.querySelector('.guesses');
let guessRemaining = 5;
let isRunning = true;
enterButton.addEventListener('click', checkGuess);
function checkGuess() {
if (isRunning) { // Check if the game is running
let userGuess = Number(guessField.value);
if (guessRemaining === 5) {
guesses.textContent = 'Previous guesses: ';
}
guesses.textContent += userGuess + ' ';
if (userGuess === randomNumber) {
lastResult.textContent = 'Congratulations! You got it right!';
lastResult.style.background = 'green';
gameOver();
} else if (guessRemaining < 1) {
lastResult.textContent = 'GAME OVER!';
gameOver();
} else {
lastResult.textContent = 'Wrong answer!';
lastResult.style.background = 'red';
lastResult.style.color = 'white';
if (userGuess < randomNumber) {
lowOrHigh.textContent = 'Too low!';
} else if (userGuess > randomNumber) {
lowOrHigh.textContent = 'Too high!';
}
}
guessRemaining--;
guessField.value = '';
guessField.focus();
} else {
resetGame();
}
}
function gameOver() {
guessField.disabled = true;
enterButton.setAttribute('value', 'Replay');
isRunning = false;
// enterButton.addEventListener('click', resetGame); Move this line to the beggining
}
function resetGame() {
randomNumber = Math.floor(Math.random() * 10) + 1;
lastResult.textContent = '';
guesses.textContent = '';
lowOrHigh.textContent = '';
guessField.disabled = false;
enterButton.setAttribute('value', 'Submit');
guessRemaining = 5;
isRunning = true;
}
2. Remove the resetGame callback after reseting
Here you just remove the resetGame callback after it is called. First you add the resetGame (just like you have it now) after the game is finished, but remove the checkGuess as well so it doesn't trigger your logic. Next you remove the resetGame and add the guessCheck callbacks after the resetGame is called (you can see two lines at the end of resetGame function).
let randomNumber = Math.floor(Math.random() * 10) + 1;
let guessField = document.getElementById('guessField');
let enterButton = document.getElementById('userSubmit');
let lastResult = document.querySelector('.lastResult');
let lowOrHigh = document.querySelector('.lowOrHigh');
let guesses = document.querySelector('.guesses');
let guessRemaining = 5;
enterButton.addEventListener('click', checkGuess);
function checkGuess() {
let userGuess = Number(guessField.value);
if (guessRemaining === 5) {
guesses.textContent = 'Previous guesses: ';
}
guesses.textContent += userGuess + ' ';
if (userGuess === randomNumber) {
lastResult.textContent = 'Congratulations! You got it right!';
lastResult.style.background = 'green';
gameOver();
} else if (guessRemaining < 1) {
lastResult.textContent = 'GAME OVER!';
gameOver();
} else {
lastResult.textContent = 'Wrong answer!';
lastResult.style.background = 'red';
lastResult.style.color = 'white';
if (userGuess < randomNumber) {
lowOrHigh.textContent = 'Too low!';
} else if (userGuess > randomNumber) {
lowOrHigh.textContent = 'Too high!';
}
}
guessRemaining--;
guessField.value = '';
guessField.focus();
}
function gameOver() {
guessField.disabled = true;
enterButton.setAttribute('value', 'Replay');
enterButton.removeEventListener('click', checkGuess); // Remove checkGuess and add resetGame
enterButton.addEventListener('click', resetGame);
}
function resetGame() {
randomNumber = Math.floor(Math.random() * 10) + 1;
lastResult.textContent = '';
guesses.textContent = '';
lowOrHigh.textContent = '';
guessField.disabled = false;
enterButton.setAttribute('value', 'Submit');
guessRemaining = 5;
enterButton.removeEventListener('click', resetGame); // Remove resetGame and add checkGuess
enterButton.addEventListener('click', checkGuess);
}
How about a slightly improved approach?
You define a boolean variable named gameIsOver. When the game is over, you set the value to true and when you reset, set the value to false.
Then you update your enterButton's click event listener. If game is over, you call the resetGame() function, else you call checkGuess function.
let gameIsOver = false; // keep track if the game is over, initially its false
enterButton.addEventListener("click", function (e) {
if (gameIsOver) {
resetGame();
} else {
checkGuess();
}
});
function gameOver() {
/* your existing code */
gameIsOver = true;
}
function resetGame() {
/* your existing code */
gameIsOver = false;
}

How can I make different levels for a game? Would I have to make different classes?

I already have a sketch.js file with some gameStates.
var gameState = "wait";
I added conditional statements such as:
if(gameState == "wait"){
background(backgroundImg);
textSize(50);
fill("blue");
text("Project42 Own",250,400);
startButton.visible = true;
gun.visible = false;
backBoard.visible = false;
}
Below is the code that has level 1, wait, play and end gameState:
function draw() {
if(gameState == "wait"){
background(backgroundImg);
textSize(50);
fill("blue");
text("Project42 Own",250,400);
startButton.visible = true;
gun.visible = false;
backBoard.visible = false;
}
if(mousePressedOver(startButton)&& gameState == "wait"){
gunSound.play();
gunSound.setVolume(0)
gameState = "play";
}
if(gameState == "play"){
background("#BDA297")
fill("red");
textSize(30);
text("Score:" + score + " ",300,100);
fill("green");
text("Life:" + life + " ",800,100)
gun.y=mouseY
startButton.visible = false;
gun.visible = true;
backBoard.visible = true;
if(keyDown("SHIFT")){
shootBullets();
}
if (frameCount % 80 === 0) {
drawBlueBubble();
}
if (frameCount % 100 === 0) {
drawRedBubble();
}
if(blueBubbleGroup.collide(bulletGroup)){
handleBubbleCollision(blueBubbleGroup);
score +=5;
}
if(redBubbleGroup.collide(bulletGroup)){
handleBubbleCollision(redBubbleGroup);
score += 10;
}
if (blueBubbleGroup.collide(backBoard)){
handleGameover(blueBubbleGroup);
}
if (redBubbleGroup.collide(backBoard)) {
handleGameover(redBubbleGroup);
}
}
if (life == 0) {
gameState == "end"
}
if(gameState == "end"){
background(gameOverImg)
redBubbleGroup.hideEverything()
blueBubbleGroup.hideEverything();
}
drawSprites();
}
However, I now want to make level 2 in which there will be more obstacles. Also, I'm not a professional coder so please be clear in your answer.

why I'm getting "max call stack size exceeded" error

I'm working on minimax part of a tic tac toe game, and I keep getting "max call stack size exceeded" error. I isolated the bug to line # 110 which is emptyIndexies(board) function. But I don't see anything wrong with the function. I tested it on the console, and the board seems to update when I click on it; however, the computer turn don't work. Here's the code
var board = [0, 1, 2, 3, 4, 5, 6, 7, 8];
var player, sqrId, user, computer, row, col;
const ARR_LENGTH = 9;
$(document).ready(function() {
//1 checkbox event listener
$(".checkBox").click(function() {
if($(this).is(":checked")) {
user = $(this).val();
player = user;
computer = (user == 'X') ? 'O' : 'X';
}
});
//2 square even listener
$(".square").click(function() {
sqrId = $(this).attr("id");
playerMove();
minimax(board);
if(checkWinner(board, turn)) {
alert(turn+" Wins the game!");
resetBoard();
}
if(!checkDraw()) {
alert("It's a draw!");
}
player = (player == user) ? computer : user;
});
//reset board
$(".reset").click(function() {
resetBoard();
})
});
//player move
function playerMove() {
if($("#"+sqrId).text() == "") {
$("#"+sqrId).text(player);
board[sqrId] = player;
console.log(board);
}
else {
alert("Wrong move");
}
}
/* computer AI generate random number between 0 - 8 */
function computerAI() {
var random;
var min = 0, max = 8;
do {
random = Math.floor(Math.random() * (max + min));
}while($("#"+random).text() != "")
$("#"+random).text(computer);
row = getRow();
col = getCol();
board[row][col] = computer;
}
//getting row number
function getRow() {
return Math.floor(sqrId / ARR_LENGTH);
}
//getting col number
function getCol() {
return sqrId % ARR_LENGTH;
}
/* checking for winner */
// winning combinations using the board indexies
function winning(board){
if (
(board[0] == player && board[1] == player && board[2] == player) ||
(board[3] == player && board[4] == player && board[5] == player) ||
(board[6] == player && board[7] == player && board[8] == player) ||
(board[0] == player && board[3] == player && board[6] == player) ||
(board[1] == player && board[4] == player && board[7] == player) ||
(board[2] == player && board[5] == player && board[8] == player) ||
(board[0] == player && board[4] == player && board[8] == player) ||
(board[2] == player && board[4] == player && board[6] == player)
) {
return true;
} else {
return false;
}
}
function resetBoard() {
$(".square").text("");
$(".checkBox").prop("checked", false);
user = "";
turn = "";
computer = "";
for(var i = 0; i < ARR_LENGTH; i++) {
board[i] = "";
}
}
// returns list of the indexes of empty spots on the board
function emptyIndexies(board){
return board.filter(s => s != "O" && s != "X");
}
// the main minimax function
function minimax(newBoard, player){
//available spots
var availSpots = emptyIndexies(newBoard);
// checks for the terminal states such as win, lose, and tie
//and returning a value accordingly
if (winning(newBoard, user)){
return {score:-10};
}
else if (winning(newBoard, computer)){
return {score:10};
}
else if (availSpots.length === 0){
return {score:0};
}
// an array to collect all the objects
var moves = [];
// loop through available spots
for (var i = 0; i < availSpots.length; i++){
//create an object for each and store the index of that spot
var move = {};
move.index = newBoard[availSpots[i]];
// set the empty spot to the current player
newBoard[availSpots[i]] = player;
/*collect the score resulted from calling minimax
on the opponent of the current player*/
if (player == computer){
var result = minimax(newBoard, user);
move.score = result.score;
}
else{
var result = minimax(newBoard, computer);
move.score = result.score;
}
// reset the spot to empty
newBoard[availSpots[i]] = move.index;
// push the object to the array
moves.push(move);
}
// if it is the computer's turn loop over the moves and choose the move with the highest score
var bestMove;
if(player === computer){
var bestScore = -10000;
for(var i = 0; i < moves.length; i++){
if(moves[i].score > bestScore){
bestScore = moves[i].score;
bestMove = i;
}
}
}else{
// else loop over the moves and choose the move with the lowest score
var bestScore = 10000;
for(var i = 0; i < moves.length; i++){
if(moves[i].score < bestScore){
bestScore = moves[i].score;
bestMove = i;
}
}
}
// return the chosen move (object) from the moves array
return moves[bestMove];
}

How do i randomize a quiz in javascript while recording right and wrong answers?

I'm trying to get the quiz to loop 5 times while recording the correct answer for each question before returning to start menu but I'm struggling to get it working
Any help with this will be much appreciated.
function cleartxt()
{
setTimeout("document.getElementById('ans').innerHTML = ''", 3000);
}
var random = new Array(5);
var count = 0;
function next()
{
var store = 0;
do
{
store = (Math.round(Math.ceil(Math.random() * 40) -1));
}while(random.indexOf(store) > -1);
document.getElementById("ques").innerHTML = questions[store][0];
document.getElementById("rad1").innerHTML = questions[store][1];
document.getElementById("rad2").innerHTML = questions[store][2];
document.getElementById("rad3").innerHTML = questions[store][3];
document.getElementById("rad4").innerHTML = questions[store][4];
document.getElementById("image").src = images[store];
var radio = document.getElementsByName("rad");
while(store <= 5)
{
count++;
if(store == 5)
startMenu();
if(radio[0].checked == true)
{
if(questions[store][0] == questions[store][5])
document.getElementById("ans").innerHTML = "Correct";
else
document.getElementById("ans").innerHTML = "Incorrect";
}
else if(radio[1].checked == true)
{
if(questions[store][1] == questions[store][5])
document.getElementById("ans").innerHTML = "Correct";
else
document.getElementById("ans").innerHTML = "Incorrect";
}
else if(radio[2].checked == true)
{
if(questions[store][2] == questions[store][5])
document.getElementById("ans").innerHTML = "Correct";
else
document.getElementById("ans").innerHTML = "Incorrect";
}
else if(radio[3].checked == true)
{
if(questions[store][3] == questions[store][5])
document.getElementById("ans").innerHTML = "Correct";
else
document.getElementById("ans").innerHTML = "Incorrect";
}
else
document.getElementById("ans").innerHTML = "Please select an answer!";
}
}
function startMenu()
{
window.history.back();
}

rps function not working (javascript)

Rps game code isn't working. My battle function is supposed to set win to a 1,2, or 3 but it always stays at the default 0. I've checked the other values through the console, and they all seem to be working. here is my code.
function Player(number) {
this.number = number;
this.choice = 0;
}
var player1 = new Player(1);
var player2 = new Player(2);
var win = 0;
var battle = function() {
if (player1.choice === player2.choice) {
win = 3;
} else if (player1.choice + 2 === player2.choice && player1.choice === 1){
win = 2;
} else if (player1.choice + 1 === player2.choice && player1.choice === 1) {
win = 1;
} else if (player1.choice + 1 === player2.choice && player1.choice === 2) {
win = 1;
} else if (player1.choice - 1 === player2.choice && player1.choice === 2) {
win = 2;
} else if (player1.choice - 1 === player2.choice && player1.choice === 3) {
win = 2;
} else if (player1.choice - 2 === player2.choice && player1.choice === 3) {
win = 1;
} else {
alert ('someone pressed the wrong button')
}
}
var Reset = function () {
win = 0;
player1.choice = 0;
player2.choice = 0;
}
$(document).ready(function() {
$(document).keydown(function(event) {
if (event.which === 81) {
player1.choice = 1;
} else if (event.which === 87){
player1.choice = 2;
} else if (event.which === 69){
player1.choice = 3;
} else if (event.which === 37){
player2.choice = 1;
} else if (event.which === 40){
player2.choice = 2;
} else if (event.which === 39){
player2.choice = 3;
}
})
if (player1.choice > 0 && player2.choice > 0) {
battle();
if (win === 1) {
$('.winner').append('<p>player1 wins!</p>')
} else if (win === 2) {
$('.winner').append('<p>player2 wins!</p>')
} else if (win === 3) {
$('.winner').append('<p>It is a draw!</p>')
}
}
})
My html has the div with class 'winner' in it.
Additional question
How do I cancel the keydown function after both players have chosen their options.
Are you giving your player a choice in the Player class you created? It should look like this:
function Player(number, choice) {
this.number = number;
this.choice = choice;
}

Categories