HangMan Game-Replacing blanks w/ Letters - javascript

At this current moment I've been trying to work through an issue I've had with my Hangman JS game. I've spent the last week attempting to replace "underscores", which I have as placeholders for the current secret word. My idea was to loop through the correctLettersOUT, and wherever that particular letter exists in the placeholder would replace it with said letter. This small part of my code below is where the issue is I believe, but I have also created a function in my whole code if a new function necessary.
Any advice is appreciated.
function startGame() {
var testWord = document.getElementById("randTest").innerHTML = secretWord;
var correctLettersOUT = "";
document.getElementById("currentGuess").innerHTML = secretBlanks(secretWord)
function secretBlanks(secretWord) {
for (var i = 0; i < secretWord.length; i++) {
correctLettersOUT += ("_ ");
}
return correctLettersOUT;
}
}
The snippet of my JS is below, it may be large but all of it is necessary. If you wish to view the whole code in it's entirety, the link is CodePen Link.
var guessWords = ["school", "test", "quiz", "pencil", "ruler", "protractor", "teacher", "homework", "science", "math", "english", "history", "language", "elective", "bully", "grades", "recess", ];
var secretWord = guessWords[Math.floor(Math.random() * guessWords.length)];
var wrongLetters = [];
var correctLetters = [];
var repeatLetters = [];
var guesses = Math.round((secretWord.length) + (.5 * secretWord.length));
var correctLettersOUT = "";
function startGame() {
var testWord = document.getElementById("randTest").innerHTML = secretWord;
var correctLettersOUT = "";
document.getElementById("currentGuess").innerHTML = secretBlanks(secretWord)
function secretBlanks(secretWord) {
for (var i = 0; i < secretWord.length; i++) {
correctLettersOUT += ("_ ");
}
return correctLettersOUT;
}
}
function correctWord() {
var guessLetter = document.getElementById("guessLetter").value;
document.getElementById("letter").innerHTML = guessLetter;
for (var i = 0; i < secretWord.length; i++) {
if (correctLetters.indexOf(guessLetter) === -1)
{
if (guessLetter === secretWord[i]) {
console.log(guessLetter === secretWord[i]);
correctLettersOUT[i] = guessLetter;
correctLetters.push(guessLetter);
break;
}}
}
if (wrongLetters.indexOf(guessLetter) === -1 && correctLetters.indexOf(guessLetter) === -1) {
wrongLetters.push(guessLetter);
}
console.log(correctLetters); //Used to see if the letters were added to the correct array**
console.log(wrongLetters);
wordGuess();
}
function wordGuess() {
if (guessLetter.value === '') {
alert("You didn't guess anything.");
} else if (guesses > 1) {
// Counts down.
guesses--;
console.log('Guesses Left: ' + guesses);
// Resets the input to a blank value.
let guessLetter = document.getElementById('guessLetter');
guessLetter.value = '';
} else {
console.log('Game Over');
}
//console.log(guesses)
}
function replWord() { }

One solution to see if the word has been guessed completely and create the partially guessed display with the same info, is to keep a Set with the not yet guessed letters, initialized at game start unGuessed = new Set(secretWord);
Since the Set's delete method returns true if the letter was actually removed (and thus existed), it can be used as a check if a correct letter was entered.
Subsequently, the display can be altered in a single place (on startup and after guessing), by mapping the letters of the word and checking if the letter is already guessed:
function alterDisplay(){
document.getElementById("currentGuess").innerHTML =
[...secretWord].map(c=>unGuessed.has(c) ? '_' : c).join(' ');
}
Some mockup code:
let guessWords = ["school", "test", "quiz", "pencil", "ruler", "protractor", "teacher", "homework", "science", "math", "english", "history", "language", "elective", "bully", "grades", "recess", ],
secretWord, unGuessed, guesses;
function startGame() {
secretWord = guessWords[Math.floor(Math.random() * guessWords.length)];
guesses = Math.round(1.5 * secretWord.length);
unGuessed = new Set(secretWord);
setStatus('');
alterDisplay();
}
function alterDisplay(){
document.getElementById("currentGuess").innerHTML = [...secretWord].map(c=>unGuessed.has(c) ? '_' : c).join(' ');
}
function setStatus(txt){
document.getElementById("status").innerHTML = txt;
}
function correctWord() {
let c= guessLetter.value[0];
guessLetter.value = '';
if (!c) {
setStatus("You didn't guess anything.");
}
else if(unGuessed.delete(c)){
alterDisplay();
if(!unGuessed.size) //if there are no unguessed letters left: the word is complete
setStatus('Yep, you guessed it');
}
else if(--guesses < 1){
setStatus('Game Over!');
}
else
setStatus('Guesses Left: ' + guesses);
}
startGame();
let gl = document.getElementById("guessLetter");
gl.onkeyup= correctWord; //for testing: bind to keyup
gl.value = secretWord[1];correctWord(); //for testing: try the 2nd letter on startup
<div id=currentGuess></div>
<input id=guessLetter><span id=letter></span>
<div id='status'></div>

Related

Why are my functions executing out of order at the end of this Connect Four game? It works in some browsers

I'm having two timing issues here, both involving the process in this game once the winning move has been made: https://codepen.io/acchang/pen/XWePpWB
Ideally, I should (1) pick the winning space (2) see the winning space filled (3) have the alert proclaim the winner.
What I see and do not like is:
*checkForWinners() runs
winDeclared() runs and the alert "winner" pop up first
Then after the alert is cleared, drawboard() runs, adding the winning piece to the gameboard.
This does not happen as badly in Firefox. The piece is added at the same time the alert pops up.
Then, in winDeclared(), I also change the display in the top right to also indicate the winner. But swapTurns() seems to execute before winDeclared().
Is that because winDeclared() is two functions deep into checkForWinners()? Is there a way to delay it?
Thanks!
let gameboard = [
[1,2,3,4,5,6,7],
[8,9,10,11,12,13,14],
[15,16,17,18,19,20,21],
[22,23,24,25,26,27,28],
[29,30,31,32,33,34,35],
[36,37,38,39,40,41,42]
];
let playerOne
let playerTwo
let indexPick
let availableSpots
let gameType
let playerOneTurn = true
document.getElementsByName("announcements")[0].innerHTML = "Current Player: " + whosPlaying() + " "
let itsAOnePlayerGame = true
let isThereAWinner = false
let mainDiv = document.createElement("div");
mainDiv.setAttribute('class', 'mainDiv')
document.body.append(mainDiv);
let selectorHolder = document.createElement("div")
selectorHolder.setAttribute('class', 'selectorHolder')
selectorHolder.setAttribute('id', 'selectorHolder')
mainDiv.append(selectorHolder)
let selectorTable = document.createElement("table")
selectorTable.setAttribute('class', 'selectorTable')
selectorTable.setAttribute('id', 'selectorTable')
selectorHolder.append(selectorTable)
function drawSelector() {
let selectorRow = document.createElement("tr")
selectorRow.setAttribute('class', 'selectorRow')
selectorTable.append(selectorRow)
for (i=0; i<7; i++){
let selectorCell = document.createElement("td")
selectorCell.setAttribute('class', 'selectorCell')
let innerSelectorCell = document.createElement("div")
innerSelectorCell.setAttribute('class', 'innerSelectorCell')
innerSelectorCell.setAttribute('id', [i])
selectorCell.append(innerSelectorCell)
innerSelectorCell.addEventListener("mouseover", function(event) {
if (playerOneTurn == true) {
innerSelectorCell.classList.add('yellowBG')}
else {innerSelectorCell.classList.add('redBG')
}
})
innerSelectorCell.addEventListener("mouseout", function(event) {
if (playerOneTurn == true) {
innerSelectorCell.classList.remove('yellowBG')}
else {innerSelectorCell.classList.remove('redBG')
}
})
innerSelectorCell.onclick = function(){
if (isThereAWinner == true){return}
else {
indexPick = parseInt(this.id)
console.log(indexPick)
claimSpot()
}
}
selectorRow.append(selectorCell)
}
};
drawSelector()
// Draw Main Gameboard
let mainTable = document.createElement("table");
mainTable.setAttribute('class', 'mainTable')
mainDiv.append(mainTable)
function drawBoard() {
for (i=0; i<gameboard.length; i++){
let row = document.createElement("tr")
mainTable.append(row)
for (j=0; j<gameboard[i].length; j++){
let outerCell = document.createElement('td')
outerCell.setAttribute('class', 'outerCell')
row.append(outerCell)
let innerCell = document.createElement('div')
innerCell.setAttribute('class', 'innerCell')
innerCell.classList.add(gameboard[i][j])
innerCell.setAttribute('innerHTML', gameboard[i][j])
outerCell.append(innerCell)
}
}
};
drawBoard()
function validateRadio() {
let ele = document.getElementsByName('gameType');
for(i = 0; i < ele.length; i++) {
if(ele[i].checked){
gameType = (ele[i].value)
beginGame()
}
}
};
function beginGame() {
if (gameType == "1PEasy"){
itsAOnePlayerGame = true
resetBoard()
onePlayerPickSides()
play1PGame()
}
else if (gameType == "1PHard"){
itsAOnePlayerGame = true
resetBoard()
onePlayerPickSides()
play1PGame()
}
else if (gameType == "2P"){
itsAOnePlayerGame = false
resetBoard()
twoPlayerPickSides()
play2PGame()
}
};
function resetBoard() {
playerOneTurn = true
isThereAWinner = false
gameboard = [
[1,2,3,4,5,6,7],
[8,9,10,11,12,13,14],
[15,16,17,18,19,20,21],
[22,23,24,25,26,27,28],
[29,30,31,32,33,34,35],
[36,37,38,39,40,41,42]
];
}
function swapTurns() {
selectorTable.innerHTML = ""
drawSelector()
playerOneTurn = !playerOneTurn
document.getElementsByName("announcements")[0].innerHTML = "Current Player: " + whosPlaying() + " "
};
// GAMEPLAY
function playerSelects2P() {
findAvailableSpots()
// put an eventListener here?
columnPick = prompt(whosPlaying() + ', choose which column 1-7')
if (availableSpots.includes(parseInt(columnPick)))
{console.log(columnPick)}
else {
alert("not available")
playerSelects2P()}
};
function playerSelects1P() {
if (whosPlaying() == playerTwo) {
findAvailableSpots()
columnPick = availableSpots[Math.floor(Math.random() * availableSpots.length)]
return
}
else {playerSelects2P()}
};
function whosPlaying() {
if (playerOneTurn) {
return "Yellow"
} else {
return "Red"
}
};
// starts from the bottom row and claims spot when there it is a number (unoccupied)
function claimSpot(){
findAvailableSpots()
if (availableSpots.includes(indexPick+1)) {
let i;
for (i = 5; i > -1; i--)
{if (Number.isInteger(gameboard[i][indexPick])) {
gameboard[i].splice((indexPick), 1, whosPlaying())
mainTable.innerHTML = ""
drawBoard()
checkForWinners()
// do I need to put some sort of delay here for it not to go to swap turns right away?
swapTurns()
return
}
}
}
else {
console.log(availableSpots)
alert("Forbidden")
}
};
// if there is a string in row[0], that column is no longer available.
// the cells are numbered from 1 to 7, not per index so you need to add one to indexPick to identify
function findAvailableSpots() {
availableSpots = gameboard[0].filter(x => Number.isInteger(x) == true)
};
function checkForWinners() {
horizontalCheck()
verticalCheck()
downrightCheck()
uprightCheck()
}
// WIN CHECKERS
// a forloop evaluates a section of the matrix, moving through it and seeing if the 3 ahead match.
// it stops before going out of bounds
function findFour(w,x,y,z) {
// Checks first cell against current player and all cells match that player
return ((w == whosPlaying()) && (w === x) && (w === y) && (w === z));
};
function winDeclared() {
isThereAWinner = true
alert("winner")
document.getElementsByName("announcements")[0].innerHTML = whosPlaying() + " wins! "
// this does not show, it snaps to swap places
};
function uprightCheck() {
for (r=5; r>2; r--) {
for (c=0; c<4; c++){
if (findFour(gameboard[r][c], gameboard[r-1][c+1], gameboard[r-2][c+2], gameboard[r-3][c+3])) {
winDeclared()
return
}
}
}
};
function downrightCheck() {
for (r=0; r<3; r++) {
for (c=0; c<4; c++){
if (findFour(gameboard[r][c], gameboard[r+1][c+1], gameboard[r+2][c+2], gameboard[r+3][c+3])) {
winDeclared()
return
}
}
}
};
function verticalCheck() {
for (r=5; r>2; r--) {
for (c=0; c<7; c++){
if (findFour(gameboard[r][c], gameboard[r-1][c], gameboard[r-2][c], gameboard[r-3][c])) {
winDeclared()
return
}
}
}
};
function horizontalCheck() {
for (r=0; r<6; r++) {
for (c=0; c<4; c++){
if (findFour(gameboard[r][c], gameboard[r][c+1], gameboard[r][c+2], gameboard[r][c+3])) {
winDeclared()
return
}
}
}
};
When you manipulate the DOM, the operation itself is syncrhonous but the browser decides when the user will actually see the changes. Sometimes, the broswer will not have time to redraw before the prompt appears. To get around this, you can wrap the alert in a setTimeout() to delay the alert.
setTimeout(
function() {
alert("winner")
}, 10)

Trouble preventing duplicate letter entry in pure JS hangman

So, I'm coding out a hangman style game as is the custom when learning how to user JS. I have it working pretty well, but I cannot seem to figure out how to properly identify and prevent duplicate wrong guesses.
Example: if you press "f" and it is not in the word, display and decrement the remaining scores once, and if you press "f" again, ignore the input. Here is my code thus far. I have my existing code in the function but it doesn't currently do anything. Any advice is appreciated.
// words to guess will be selected from here
var wordSelectionArray = [
"adventure", "bandit", "battleaxe", "battlefield", "beholder", "bugbear", "castle", "catapult", "cleric", "dragon", "dungeon",
"fairy", "fireball", "giant", "goblin", "knight", "magic", "monster", "necromancer", "owlbear", "paladin", "potion", "quarterstaff",
"sorcerer", "spells", "sword", "vampire", "warlock", "wizard", "zombie"]
//variable to hold the randomly selected word
var selectedWord = "";
//variable to store number of blanks
var blanks = 0;
//var to hold the split out array of the randomly selected word.
var letters = [];
//var to store blanks with the correct word
var blankAndCorrect = [];
//var to hold the incorrect letter guesses when input by the user.
var wrongGuess = [];
// start the game with a score of zero
var wins = 0;
var losses = 0;
//variable to hold number of guesses remaining
var guessesRemaining = 12;
//array for guessed letters
var alreadyGuessedLetters = [];
//variable to hold the background music
var myMusic = document.getElementById("background-music");
function game() {
selectedWord = wordSelectionArray[Math.floor(Math.random() * wordSelectionArray.length)];
letters = selectedWord.split("");
blanks = selectedWord.length;
for (var i = 0; i < blanks; i++) {
blankAndCorrect.push("_");
}
document.getElementById("current-word").innerHTML = " " + blankAndCorrect.join(" ");
myMusic.play();
// console.log(selectedWord);
// console.log(letters);
// console.log(blanks);
// console.log(blankAndCorrect);
};
//function to start the game over
function reset() {
guessesRemaining = 12;
wrongGuess = [];
blankAndCorrect = [];
game();
};
//check and compare function
function checkInput(letter) {
letterInWord = false;
for (var i = 0; i < blanks; i++) {
if (selectedWord[i] == letter) {
letterInWord = true;
}
} if (letterInWord) {
for (var i = 0; i < blanks; i++) {
if (selectedWord[i] == letter) {
blankAndCorrect[i] = letter;
}
}
} if (letterInWord) {
for (var i = 0; i < alreadyGuessedLetters.length; i++) {
if (selectedWord[i] !== letter) {
alreadyGuessedLetters[i] = letter;
return;
}
}
} else {
wrongGuess.push(letter);
alreadyGuessedLetters.push(letter);
guessesRemaining--;
console.log(alreadyGuessedLetters);
}
};
//function to check wins/losses
function completeGame () {
if (letters.toString() == blankAndCorrect.toString()) {
wins++;
document.getElementById("correct-answer-message").innerHTML = "Great Job! " + selectedWord.toLocaleUpperCase() + " was the word!" ;
reset();
document.getElementById("wins").innerHTML = " " + wins;
} else if (guessesRemaining === 0) {
losses++;
reset();
document.getElementById("losses").innerHTML = " " + losses;
}
document.getElementById("current-word").innerHTML = " " + blankAndCorrect.join(" ");
document.getElementById("guesses-remaining").innerHTML = " " + guessesRemaining;
};
game();
document.onkeyup = function (event) {
var guesses = String.fromCharCode(event.keyCode).toLowerCase();
checkInput(guesses);
completeGame();
// console.log(guesses);
document.getElementById("letters-guessed").innerHTML = " " + wrongGuess.join(" ");
}
See how I'm ignoring duplicates in checkInput.
// words to guess will be selected from here
var wordSelectionArray = [
"adventure", "bandit", "battleaxe", "battlefield", "beholder", "bugbear", "castle", "catapult", "cleric", "dragon", "dungeon",
"fairy", "fireball", "giant", "goblin", "knight", "magic", "monster", "necromancer", "owlbear", "paladin", "potion", "quarterstaff",
"sorcerer", "spells", "sword", "vampire", "warlock", "wizard", "zombie"]
//variable to hold the randomly selected word
var selectedWord = "";
//variable to store number of blanks
var blanks = 0;
//var to hold the split out array of the randomly selected word.
var letters = [];
//var to store blanks with the correct word
var blankAndCorrect = [];
//var to hold the incorrect letter guesses when input by the user.
var wrongGuess = [];
// start the game with a score of zero
var wins = 0;
var losses = 0;
//variable to hold number of guesses remaining
var guessesRemaining = 12;
//array for guessed letters
var alreadyGuessedLetters = [];
//variable to hold the background music
var myMusic = document.getElementById("background-music");
function game() {
selectedWord = wordSelectionArray[Math.floor(Math.random() * wordSelectionArray.length)];
letters = selectedWord.split("");
blanks = selectedWord.length;
for (var i = 0; i < blanks; i++) {
blankAndCorrect.push("_");
}
document.getElementById("current-word").innerHTML = " " + blankAndCorrect.join(" ");
// myMusic.play();
// console.log(selectedWord);
// console.log(letters);
// console.log(blanks);
// console.log(blankAndCorrect);
};
//function to start the game over
function reset() {
guessesRemaining = 12;
wrongGuess = [];
blankAndCorrect = [];
game();
};
//check and compare function
function checkInput(letter) {
if (wrongGuess.includes(letter)) { // <--- HERE
// do anything else you wanna do
return;
}
letterInWord = false;
for (var i = 0; i < blanks; i++) {
if (selectedWord[i] == letter) {
letterInWord = true;
}
} if (letterInWord) {
for (var i = 0; i < blanks; i++) {
if (selectedWord[i] == letter) {
blankAndCorrect[i] = letter;
}
}
} if (letterInWord) {
for (var i = 0; i < alreadyGuessedLetters.length; i++) {
if (selectedWord[i] !== letter) {
alreadyGuessedLetters[i] = letter;
return;
}
}
} else {
wrongGuess.push(letter);
alreadyGuessedLetters.push(letter);
guessesRemaining--;
console.log(alreadyGuessedLetters);
}
};
//function to check wins/losses
function completeGame () {
if (letters.toString() == blankAndCorrect.toString()) {
wins++;
document.getElementById("correct-answer-message").innerHTML = "Great Job! " + selectedWord.toLocaleUpperCase() + " was the word!" ;
reset();
document.getElementById("wins").innerHTML = " " + wins;
} else if (guessesRemaining === 0) {
losses++;
reset();
document.getElementById("losses").innerHTML = " " + losses;
}
document.getElementById("current-word").innerHTML = " " + blankAndCorrect.join(" ");
document.getElementById("guesses-remaining").innerHTML = " " + guessesRemaining;
};
game();
document.onkeyup = function (event) {
var guesses = String.fromCharCode(event.keyCode).toLowerCase();
checkInput(guesses);
completeGame();
// console.log(guesses);
document.getElementById("letters-guessed").innerHTML = " " + wrongGuess.join(" ");
}
<div id="current-word"></div>
<div id="guesses-remaining"></div>
<div id="letters-guessed"></div>

create a typing and deleting effect animation of placeholder in an input box

I'm trying to create an animated placeholder for an input box which looks like the placeholder is typed and deleted again (like in https://www.squarespace.com/templates ).
I tried for a single string but failed to do so for an array, I have commented the part I tried to change. I am new to Javascript:
const words = [
"Injection",
"Broken Authentication",
"Sensitive Data Exposure",
"XML External Entities (XXE)",
"Broken Access Control",
"Security Misconfiguration",
"Cross-Site Scripting (XSS)",
"Insecure Deserialization",
"Using Components with Known Vulnerabilities",
"Insufficient Logging&Monitoring"
];
let i = 0;
let timer;
function typingEffect() {
let word = words[i].split("");
var loopTyping = function () {
if (word.length > 0) {
document.getElementById('word').innerHTML += word.shift();
} else {
deletingEffect();
return false;
};
timer = setTimeout(loopTyping, 200);
};
loopTyping();
};
function deletingEffect() {
let word = words[i].split("");
var loopDeleting = function () {
if (word.length > 0) {
word.pop();
// document.getElementById('word').style.font = inherit;
document.getElementById('word').innerHTML = word.join("");
} else {
if (words.length > (i + 1)) {
i++;
} else {
i = 0;
};
typingEffect();
return false;
};
timer = setTimeout(loopDeleting, 100);
};
loopDeleting();
};
typingEffect();
I wanted this to happen in the placeholder of the input tag. Can you help me please?
You can use .setAttribute() and .getAttribute() to set and append to the placeholder text:
typingEffect():
let elem = document.getElementById('typer');
elem.setAttribute('placeholder', elem.getAttribute('placeholder') + word.shift());
deletingEffect():
document.getElementById('typer').setAttribute('placeholder', word.join(""));
See example below:
const words = ["Injection",
"Broken Authentication",
"Sensitive Data Exposure",
"XML External Entities (XXE)",
"Broken Access Control",
"Security Misconfiguration",
"Cross-Site Scripting (XSS)",
"Insecure Deserialization",
"Using Components with Known Vulnerabilities",
"Insufficient Logging&Monitoring"
];
let i = 0;
let timer;
function typingEffect() {
let word = words[i].split("");
var loopTyping = function() {
if (word.length > 0) {
let elem = document.getElementById('typer');
elem.setAttribute('placeholder', elem.getAttribute('placeholder') + word.shift());
} else {
deletingEffect();
return false;
};
timer = setTimeout(loopTyping, 200);
};
loopTyping();
};
function deletingEffect() {
let word = words[i].split("");
var loopDeleting = function() {
if (word.length > 0) {
word.pop();
document.getElementById('typer').setAttribute('placeholder', word.join(""));
} else {
if (words.length > (i + 1)) {
i++;
} else {
i = 0;
};
typingEffect();
return false;
};
timer = setTimeout(loopDeleting, 100);
};
loopDeleting();
};
typingEffect();
#typer {
font-size: 30px;
}
<input type="text" id="typer" placeholder=""></div>

JavaScript Algorithm not filtering correctly

Pretty new to JavaScript. I am writing a program that shows quotes at random. The issue is that I also want the same quote to never be repeated unless the entire array of quotes have been used.
The program does generate the quotes but they are not unique each time. Here is the script:
// Random Quote Generator - Justin Duncan
// Create the array of quote objects and name it quotes
var quotes = [{
quote: "I love you the more in that I believe you had" +
" liked me for my own sake and for nothing else.",
source: "John Keats",
categorization: "love",
year: ""
},
{
quote: "But man is not made for defeat. A man can be destroyed but not defeated.",
source: "Ernest Hemingway",
categorization: "philosophy",
year: ""
},
{
quote: "When you reach the end of your rope, tie a knot in it and hang on.",
source: "Franklin D. Roosevelt",
categorization: "motivation",
year: ""
},
{
quote: "There is nothing permanent except change.",
source: "Heraclitus",
categorization: "philosophy",
year: ""
},
{
quote: "You cannot shake hands with a clenched fist",
source: "Indira Gandhi",
categorization: "philosophy",
year: "1971"
},
{
quote: "Learning never exhausts the mind",
source: " Leonardo da Vinci",
categorization: "philosophy",
year: ""
},
{
quote: "There is no charm equal to tenderness of heart.",
source: "Jane Austen",
categorization: "motivation",
year: ""
},
];
//To track quotes that have been shown and remove them from random pool
var shownQuotes = [];
// grabs copy of quotes to manipulate
var notShownQuotes = Object.create(quotes);
// Create the getRandomQuote function and name it getRandomQuote
function getRandomQuote() {
if (shownQuotes.length !== 0) {
//checks if a shown quote is in the notShownQuotes arry and removes it
shownQuotes.forEach(function(shownQuote) {
for (var i = 0; i < notShownQuotes.length; i++) {
if (shownQuote.quote == notShownQuotes[i].quote) {
notShownQuotes.splice(i, 1);
}
}
})
}
//resets the array if all have been shown
if (notShownQuotes.length === 0) {
notShownQuotes = Object.create(quotes);
}
// generates random number according to array length
var returnVal = Math.round(Math.random() * notShownQuotes.length - 1);
// in case returnval is an invalid number because of small array size
if (returnVal <= 0) {
return notShownQuotes[0];
} else {
return notShownQuotes[returnVal]
}
}
// Create the printQuote funtion and name it printQuote
function printQuote() {
var tempQuote = getRandomQuote();
var str = '<p class="quote">' +
` ${tempQuote.quote}.</p>` +
`<p class="source">${tempQuote.source}`;
if (tempQuote.year.length !== 0) {
str +=
`<span class="year">${tempQuote.year}</span></p>`
} else {
str += '</p>'
}
//this portion prints to the document
document.getElementById('quote-box').innerHTML = str;
//change background color
document.body.style.backgroundColor = ran3Color();
//change button as well :)
document.getElementById('loadQuote').style.backgroundColor = ran3Color();
//clears timer
clearInterval(timer);
// resets timer
timer = setInterval(printQuote, 5000);
}
//random color generator
function ran3Color() {
var r = Math.round(Math.random() * 360);
var g = Math.round(Math.random() * 360);
var b = Math.round(Math.random() * 360);
return `rgb(${r},${g},${b})`;
}
//set interval for timer
var timer = setInterval(printQuote, 50);
// This event listener will respond to "Show another quote" button clicks
// when user clicks anywhere on the button, the "printQuote" function is called
document.getElementById('loadQuote').addEventListener("click", printQuote, false);
I hope it is something small. This is not homework, I am wanting to ensure I understand how to manipulate Js and I obviously am doing something wrong here. Thanks in advance.
Edit: Added the entire app.js in case that helps understand what I have done and the direction I am looking at.
Your code have 2 problems
1- When you remove one item of array, decrease counter
notShownQuotes.splice(i, 1);
i --;
2- Update your notShownQuotes array everytime you show an item
if (returnVal <= 0) {
notShownQuotes.splice(0, 1);
return notShownQuotes[0];
} else {
notShownQuotes.splice(returnVal, 1);
return notShownQuotes[returnVal];
}
Full code will be
// Random Quote Generator - Justin Duncan
// Create the array of quote objects and name it quotes
var quotes = [{
quote: "I love you the more in that I believe you had" + " liked me for my own sake and for nothing else.",
source: "John Keats",
categorization: "love",
year: ""
}, {
quote: "But man is not made for defeat. A man can be destroyed but not defeated.",
source: "Ernest Hemingway",
categorization: "philosophy",
year: ""
}, {
quote: "When you reach the end of your rope, tie a knot in it and hang on.",
source: "Franklin D. Roosevelt",
categorization: "motivation",
year: ""
}, {
quote: "There is nothing permanent except change.",
source: "Heraclitus",
categorization: "philosophy",
year: ""
}, {
quote: "You cannot shake hands with a clenched fist",
source: "Indira Gandhi",
categorization: "philosophy",
year: "1971"
}, {
quote: "Learning never exhausts the mind",
source: " Leonardo da Vinci",
categorization: "philosophy",
year: ""
}, {
quote: "There is no charm equal to tenderness of heart.",
source: "Jane Austen",
categorization: "motivation",
year: ""
}, ];
//To track quotes that have been shown and remove them from random pool
var shownQuotes = [];
// grabs copy of quotes to manipulate
var notShownQuotes = Object.create(quotes);
// Create the getRandomQuote function and name it getRandomQuote
function getRandomQuote() {
if (shownQuotes.length !== 0) {
console.log(shownQuotes)
console.log(notShownQuotes)
//checks if a shown quote is in the notShownQuotes arry and removes it
shownQuotes.forEach(function(shownQuote) {
for (var i = 0; i < notShownQuotes.length; i++) {
if (shownQuote.quote == notShownQuotes[i].quote) {
notShownQuotes.splice(i, 1);
i--;
}
}
});
}
//resets the array if all have been shown
if (notShownQuotes.length === 0) {
notShownQuotes = Object.create(quotes);
}
// generates random number according to array length
var returnVal = Math.round(Math.random() * notShownQuotes.length - 1);
// in case returnval is an invalid number because of small array size
if (returnVal <= 0) {
notShownQuotes.splice(0, 1);
return notShownQuotes[0];
} else {
notShownQuotes.splice(returnVal, 1);
return notShownQuotes[returnVal];
}
}
// Create the printQuote funtion and name it printQuote
function printQuote() {
var tempQuote = getRandomQuote();
var str =
'<p class="quote">' +
` ${tempQuote.quote}.</p>` +
`<p class="source">${tempQuote.source}`;
if (tempQuote.year.length !== 0) {
str += `<span class="year">${tempQuote.year}</span></p>`;
} else {
str += "</p>";
}
//this portion prints to the document
document.getElementById("quote-box").innerHTML = str;
//change background color
document.body.style.backgroundColor = ran3Color();
//change button as well :)
document.getElementById("loadQuote").style.backgroundColor = ran3Color();
//clears timer
clearInterval(timer);
// resets timer
timer = setInterval(printQuote, 5000);
}
//random color generator
function ran3Color() {
var r = Math.round(Math.random() * 360);
var g = Math.round(Math.random() * 360);
var b = Math.round(Math.random() * 360);
return `rgb(${r},${g},${b})`;
}
//set interval for timer
var timer = setInterval(printQuote, 50);
// This event listener will respond to "Show another quote" button clicks
// when user clicks anywhere on the button, the "printQuote" function is called
document
.getElementById("loadQuote")
.addEventListener("click", printQuote, false);
<button id="loadQuote">click</button>
<p id="quote-box"></p>
Another solution is: Store just showed quotes and after all, empty array again
// Random Quote Generator - Justin Duncan
// Create the array of quote objects and name it quotes
var quotes = [{
quote: "I love you the more in that I believe you had" + " liked me for my own sake and for nothing else.",
source: "John Keats",
categorization: "love",
year: ""
}, {
quote: "But man is not made for defeat. A man can be destroyed but not defeated.",
source: "Ernest Hemingway",
categorization: "philosophy",
year: ""
}, {
quote: "When you reach the end of your rope, tie a knot in it and hang on.",
source: "Franklin D. Roosevelt",
categorization: "motivation",
year: ""
}, {
quote: "There is nothing permanent except change.",
source: "Heraclitus",
categorization: "philosophy",
year: ""
}, {
quote: "You cannot shake hands with a clenched fist",
source: "Indira Gandhi",
categorization: "philosophy",
year: "1971"
}, {
quote: "Learning never exhausts the mind",
source: " Leonardo da Vinci",
categorization: "philosophy",
year: ""
}, {
quote: "There is no charm equal to tenderness of heart.",
source: "Jane Austen",
categorization: "motivation",
year: ""
}, ];
//To track quotes that have been shown and remove them from random pool
var shownQuotes = [];
// Create the getRandomQuote function and name it getRandomQuote
function getRandomQuote() {
if (shownQuotes.length == quotes.length) {
shownQuotes = [];
}
// generates random number according to array length
var returnVal = Math.floor(Math.random() * quotes.length - 1) + 1;
// in case returnval is an invalid number because of small array size
while (shownQuotes.indexOf(returnVal) != -1) {
returnVal = Math.floor(Math.random() * quotes.length - 1) + 1;
}
if (returnVal <= 0) {
shownQuotes.push(0)
return quotes[0];
} else {
shownQuotes.push(returnVal)
return quotes[returnVal];
}
}
// Create the printQuote funtion and name it printQuote
function printQuote() {
var tempQuote = getRandomQuote();
var str =
'<p class="quote">' +
` ${tempQuote.quote}.</p>` +
`<p class="source">${tempQuote.source}`;
if (tempQuote.year.length !== 0) {
str += `<span class="year">${tempQuote.year}</span></p>`;
} else {
str += "</p>";
}
//this portion prints to the document
document.getElementById("quote-box").innerHTML = str;
//change background color
document.body.style.backgroundColor = ran3Color();
//change button as well :)
document.getElementById("loadQuote").style.backgroundColor = ran3Color();
//clears timer
clearInterval(timer);
// resets timer
timer = setInterval(printQuote, 5000);
}
//random color generator
function ran3Color() {
var r = Math.round(Math.random() * 360);
var g = Math.round(Math.random() * 360);
var b = Math.round(Math.random() * 360);
return `rgb(${r},${g},${b})`;
}
//set interval for timer
var timer = setInterval(printQuote, 50);
// This event listener will respond to "Show another quote" button clicks
// when user clicks anywhere on the button, the "printQuote" function is called
document
.getElementById("loadQuote")
.addEventListener("click", printQuote, false);
<button id="loadQuote">click</button>
<p id="quote-box"></p>
I think the part with forEach and then splice to create a new array is a bit confusing. You could use a simple Array.filter to get rid of already used quotes, for example:
let shownQuotes = [];
function getRandomQuote () {
// if all quotes were used reset shownQuotes so all quotes can be shown again
if (shownQuotes.length === quotes.length) {
shownQuotes = [];
}
// filter all quotes that have been used
const unusedQuotes = quotes.filter(quote => !shownQuotes.includes(quote));
// get a random index between [0, unusedQuotes.length)
const randomIndex = Math.floor(Math.random() * unusedQuotes.length);
const randomQuote = unusedQuotes[randomIndex];
// add the element to the quotes already shown.
shownQuotes.push(randomQuote);
return randomQuote;
}
I try to replace your code but i don't have inputs.
I use simple inputs.
so i removed ".quote"s from
if (shownQuote.quote === notShownQuotes[i].quote) {
and then you have one mistake:
I think you try to splice array to remove duplicate objects. be careful when use "splice" in "for" you must don't use "i++" and must use "i--" !
If you use "i++" maybe after splice skipped one object and don't compare! see whats happen to "3" in my example inputs.
please run below scripts. compare outputs and see last code (Changed code) maybe help you.
comment: first code doesn't work and reset because inputs don't have "quote"s
in upper if checked (undefined === undefined)
Your code:
console.log('Your code:')
var notShownQuotes = [1,2,3,3,4,5,4,6], shownQuotes = [1,2,3,6], quotes = [1,2,3,4,6];
function getRandomQuote() {
if (shownQuotes.length !== 0) {
//checks if a shown quote is in the notShownQuotes arry and removes it
shownQuotes.forEach(function(shownQuote) {
for (var i = 0; i < notShownQuotes.length; i++) {
if (shownQuote.quote === notShownQuotes[i].quote) {
notShownQuotes.splice(i, 1);
console.log('remove ' + shownQuote);
}
}
})
}
//resets the array if all have been shown
if (notShownQuotes.length === 0) {
notShownQuotes = quotes;
console.log('reseted');
}
// generates random number according to array length
var returnVal = Math.round(Math.random() * notShownQuotes.length - 1);
// in case returnval is an invalid number because of small array size
if (returnVal <= 0) {
return notShownQuotes[0];
} else {
return notShownQuotes[returnVal]
}
}
console.log('getRandomQuote: ' + getRandomQuote());
Your code Without ".quote"s:
console.log('Your code Without ".quote"s:')
var notShownQuotes = [1,2,3,3,4,5,4,6], shownQuotes = [1,2,3,6], quotes = [1,2,3,4,6];
function getRandomQuote2() {
if (shownQuotes.length !== 0) {
//checks if a shown quote is in the notShownQuotes arry and removes it
shownQuotes.forEach(function(shownQuote) {
for (var i = 0; i < notShownQuotes.length; i++) {
if (shownQuote === notShownQuotes[i]) {
notShownQuotes.splice(i, 1);
console.log('remove ' + shownQuote);
}
}
})
}
//resets the array if all have been shown
if (notShownQuotes.length === 0) {
notShownQuotes = quotes;
console.log('reseted');
}
// generates random number according to array length
var returnVal = Math.round(Math.random() * notShownQuotes.length - 1);
// in case returnval is an invalid number because of small array size
if (returnVal <= 0) {
return notShownQuotes[0];
} else {
return notShownQuotes[returnVal]
}
}
console.log('getRandomQuote: ' + getRandomQuote2());
Changed code: #####################
console.log('Changed code: #####################')
var notShownQuotes = [1,2,3,3,4,5,4,6], shownQuotes = [1,2,3,6], quotes = [1,2,3,4,6];
function getRandomQuote3() {
if (shownQuotes.length !== 0) {
//checks if a shown quote is in the notShownQuotes arry and removes it
shownQuotes.forEach(function(shownQuote) {
for (var i = notShownQuotes.length - 1; i >= 0; i--) {
if (shownQuote === notShownQuotes[i]) {
notShownQuotes.splice(i, 1);
console.log('remove ' + shownQuote);
}
}
})
}
//resets the array if all have been shown
if (notShownQuotes.length === 0) {
notShownQuotes = quotes;
console.log('reseted');
}
// generates random number according to array length
var returnVal = Math.round(Math.random() * notShownQuotes.length - 1);
// in case returnval is an invalid number because of small array size
if (returnVal <= 0) {
return notShownQuotes[0];
} else {
return notShownQuotes[returnVal]
}
}
console.log('getRandomQuote: ' + getRandomQuote3());
in end. sorry for my bad english :)
TRY
var temp_qoute="";
function getRandomQuote() {
if (shownQuotes.length !== 0) {
//checks if a shown quote is in the notShownQuotes arry and removes it
shownQuotes.forEach(function(shownQuote) {
for (var i = 0; i < notShownQuotes.length; i++) {
if (shownQuote.quote === notShownQuotes[i].quote && notShownQuotes[i].quote!=temp_qoute) {
temp_qoute= notShownQuotes[i].quote
notShownQuotes.splice(i, 1);
}
}
})
}
//resets the array if all have been shown
if (notShownQuotes.length === 0) {
notShownQuotes = quotes;
}
// generates random number according to array length
var returnVal = Math.round(Math.random() * notShownQuotes.length - 1);
// in case returnval is an invalid number because of small array size
if (returnVal <= 0) {
return notShownQuotes[0];
} else {
return notShownQuotes[returnVal]
}
}

Clearing multiple timeouts properly

I've been struggling for the past day trying to solve this one.
I'm trying to get some funky text effects going, basically a glorified string creation.
It writes a line a bit like a billboard and to do it I've used setTimeout.
Thing is I would like to put it in a function so I can reuse it and call it multiple times on different elements.
The problem is that I then need to update the text maybe halfway to a new text.
To do so I clear the timeout, but unless the timer variable is outside the scope it doesn't clear.
I can't really have it outside the function because of practicality;
I'm not sure how many times it is going to be called and it just feels wrong to declare 20 time variables outside the function.
Here's the code working CORRECTLY on one item
(click multiple times to interrupt and restart)
var t;
function writeStats(str,dest) {
var options = {
"step" : 8, // How many times should the letters be changed
"fps" : 25, // Frames Per Second
"text" : "" // Use this text instead of the contents
}
function randomChar(type){
var pool = "";
if (type == "lowerLetter"){
pool = "abcdefghijklmnopqrstuvwxyz0123456789";
}
else if (type == "upperLetter"){
pool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
}
else if (type == "symbol"){
pool = ",.?/\\(^)![]{}*&^%$#'\"";
}
var arr = pool.split('');
return arr[Math.floor(Math.random()*arr.length)];
}
str = str.split('');
var types = [],
letters = [];
for(var i=0;i<str.length;i++){
var ch = str[i];
if(ch == " "){
types[i] = "space";
continue;
}
else if(/[a-z]/.test(ch)){
types[i] = "lowerLetter";
}
else if(/[A-Z]/.test(ch)){
types[i] = "upperLetter";
}
else {
types[i] = "symbol";
}
letters.push(i);
}
clearTimeout(t);
(function shuffle(start){
// This code is run options.fps times per second
// and updates the contents of the page element
var i,
len = letters.length,
strCopy = str.slice(0); // Fresh copy of the string
if(start>len){
return;
}
// All the work gets done here
for(i=Math.max(start,0); i < len; i++){
// The start argument and options.step limit
// the characters we will be working on at once
if( i < start+options.step){
// Generate a random character at this position
strCopy[letters[i]] = randomChar(types[letters[i]]);
}
else {
strCopy[letters[i]] = "";
}
}
//el.text(strCopy.join(""));
el = strCopy.join("");
//console.log(el);
$('.'+dest).text(el);
t = setTimeout(function(){
shuffle(start+1);
},500/options.fps);
})(-options.step);
}
$(document).ready(function(){
$(document).click(function(){
writeStats('this sentence is a great one','t1');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="t1"></div>
<div class="t2"></div>
and a Fiddle script: https://jsfiddle.net/phjzfw15/
If I bring the t variable inside the function like so it doesn't work as before:
function writeStats(str,dest) {
var t;
var options = {
"step" : 8, // How many times should the letters be changed
"fps" : 25, // Frames Per Second
"text" : "" // Use this text instead of the contents
}
function randomChar(type){
var pool = "";
if (type == "lowerLetter"){
pool = "abcdefghijklmnopqrstuvwxyz0123456789";
}
else if (type == "upperLetter"){
pool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
}
else if (type == "symbol"){
pool = ",.?/\\(^)![]{}*&^%$#'\"";
}
var arr = pool.split('');
return arr[Math.floor(Math.random()*arr.length)];
}
str = str.split('');
var types = [],
letters = [];
for(var i=0;i<str.length;i++){
var ch = str[i];
if(ch == " "){
types[i] = "space";
continue;
}
else if(/[a-z]/.test(ch)){
types[i] = "lowerLetter";
}
else if(/[A-Z]/.test(ch)){
types[i] = "upperLetter";
}
else {
types[i] = "symbol";
}
letters.push(i);
}
clearTimeout(t);
(function shuffle(start){
// This code is run options.fps times per second
// and updates the contents of the page element
var i,
len = letters.length,
strCopy = str.slice(0); // Fresh copy of the string
if(start>len){
return;
}
// All the work gets done here
for(i=Math.max(start,0); i < len; i++){
// The start argument and options.step limit
// the characters we will be working on at once
if( i < start+options.step){
// Generate a random character at this position
strCopy[letters[i]] = randomChar(types[letters[i]]);
}
else {
strCopy[letters[i]] = "";
}
}
//el.text(strCopy.join(""));
el = strCopy.join("");
//console.log(el);
$('.'+dest).text(el);
t = setTimeout(function(){
shuffle(start+1);
},500/options.fps);
})(-options.step);
}
$(document).ready(function(){
$(document).click(function(){
writeStats('this sentence is a great one','t1');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="t1"></div>
<div class="t2"></div>
... and the relative fiddle if you prefer it there: https://jsfiddle.net/phjzfw15/1/
If you run the snippet you'll see it doesn't work properly anymore.
Clicking repeatedly will show you that the old sentence is still there and it gets overwritten.
How can I get this working clearing the timeout correctly inside the function?
I thought the "t" variable was local to each function and a separate instance of it would have been created?
Thanks!
Ok, here's a dumbed down version (maybe the amount of code was too much before)
CORRECT VERSION
var starr = [
'bloop the boop',
'cammy the shadow',
'i like cauliflower',
'bro, i kick u hard',
'like measels? I dont.',
'eat fish and pie'
];
var timer;
function writeStats(str, dest) {
$('.'+dest).text('');
var options = {
"step" : 8, // How many times should the letters be changed
"fps" : 25, // Frames Per Second
"text" : "" // Use this text instead of the contents
}
str = str.split('');
clearTimeout(timer);
var ll = '';
(function shuffle(start){
// This code is run options.fps times per second
// and updates the contents of the page element
var i, len = str.length, el;
if(start>=len){
return;
}
ll = ll + str[start];
$('.'+dest).text(ll);
timer = setTimeout(function(){
shuffle(start+1);
},1500/options.fps);
})(0);
}
$(document).ready(function(){
var index = 0;
$(document).click(function(){
writeStats(starr[index],'t1');
if (index == 5) index = 0; else index++;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
Correct version
<div class="t1">Click anywhere multiple times</div>
NOT WORKING VERSION
var starr = [
'bloop the boop',
'cammy the shadow',
'i like cauliflower',
'bro, i kick u hard',
'like measels? I dont.',
'eat fish and pie'
];
function writeStats(str, dest) {
var timer;
$('.'+dest).text('');
var options = {
"step" : 8, // How many times should the letters be changed
"fps" : 25, // Frames Per Second
"text" : "" // Use this text instead of the contents
}
str = str.split('');
clearTimeout(timer);
var ll = '';
(function shuffle(start){
// This code is run options.fps times per second
// and updates the contents of the page element
var i, len = str.length, el;
if(start>=len){
return;
}
ll = ll + str[start];
$('.'+dest).text(ll);
timer = setTimeout(function(){
shuffle(start+1);
},1500/options.fps);
})(0);
}
$(document).ready(function(){
var index = 0;
$(document).click(function(){
writeStats(starr[index],'t1');
if (index == 5) index = 0; else index++;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
NOT WORKING VERSION (please note I just moved the timer declaration inside the function)
<div class="t1">Click anywhere multiple times fast</div>
Finally made it to work.
For posterities...
var starr = [
'bloop the boop',
'cammy the shadow',
'i like cauliflower',
'bro, i kick u hard',
'like measels? I dont.',
'eat fish and pie'
];
var writer = function(){
var timer;
this.writeStat = function(str,dest) {
var options = { "step" : 8, "fps" : 25, "text" : "" }
str = str.split('');
clearTimeout(timer);
var ll = '';
(function shuffle(start){
// This code is run options.fps times per second
// and updates the contents of the page element
var i, len = str.length, el;
if(start>=len){
return;
}
ll = ll + str[start];
$('.'+dest).text(ll);
timer = setTimeout(function(){
shuffle(start+1);
},1500/options.fps);
})(0);
}
}
$(document).ready(function(){
var index = 0;
w = new writer;
y = new writer;
$(document).click(function(){
w.writeStat(starr[index],'t1');
y.writeStat(starr[index],'t2');
if (index == 5) index = 0; else index++;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<div class="t1"></div>
<div class="t2"></div>

Categories

Resources