Hangman game only matches the first character, not all of them - javascript

I’m creating a Hangman game using jQuery. I seem to be having trouble with repeating letters while using indexOf. When I pick a correct letter it only fills in the first slot and won’t fill in the second, even if you click the same letter twice.
Example:
Name: “bob”
Typed character: “b”
Outcome: “b__”
Expected outcome: “b_b”
I tried to convert this over to using a loop, but it become an endless loop.
// Band Names array
var bandNames = ["AJJ", "Bob Dylan", "The Front Bottoms", "The Hotelier"];
var correctLetters = [];
var placeHolder = [];
// creates a random number between 1 and the length of the band array.
var randomNumber = Math.floor(Math.random() * bandNames.length);
// picks a random band based of the length of the randomband array
var selectedBand = bandNames[randomNumber];
// add place holder based on length of band name
for (i = 0; i < selectedBand.length; i++) {
placeHolder.push("<li></li>");
}
$(document).keypress(function(e) {
var letterClicked = String.fromCharCode(e.which);
if (selectedBand.indexOf(letterClicked) > -1) {
// adds correct letter to the array
correctLetters.push(letterClicked);
placeHolder[selectedBand.indexOf(letterClicked)] = letterClicked;
// combines the array
var joinArray = placeHolder.join("");
// appand
$('#current-container').html("").append(joinArray);
if (joinArray == selectedBand) {
alert("you win");
location.reload();
}
}
});
How can I find all the characters?

indexOf will only return the first index found. What you could instead do is use map to map your placeHolder value to the selectedBand and replace all the values which match the key pressed.
Here is a working example.
var bandNames = ["AJJ", "Bob Dylan", "The Front Bottoms", "The Hotelier"];
var randomNumber = Math.floor(Math.random() * bandNames.length);
var selectedBand = bandNames[randomNumber];
var correctLetters = [];
var placeHolder = [];
for (i = 0; i < selectedBand.length; i++) {
selectedBand[i] === " " ? placeHolder.push(" ") : placeHolder.push("_");
}
$('#current-container').html(placeHolder);
$(document).keypress(function(e) {
var letterClicked = String.fromCharCode(e.which);
selectedBand.split("").map((letter, index) => {
if (letter.toLowerCase() === letterClicked) {
placeHolder[index] = selectedBand[index];
}
});
$('#current-container').html(placeHolder);
if (placeHolder.join("") == selectedBand) {
setTimeout(() => {
alert("you win");
});
location.reload();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="current-container">
</div>
Note: I have used setTimeout to display the alert because otherwise the alert displays before the last letter clicked before winning the game is displayed on the screen.

So here's the thing about String#indexOf(), it returns after the first index found. What you can do instead is to use a Array#forEach() to iterate over the entire string instead of just to the first match. By passing a function that interacts with the value and the index you can use a loop per letter to decide when to update the DOM.
Example usage:
Prints out each letter at each index
const letters = 'ababab';
letters
.split('') // convert to array
forEach((letter, index) => {
console.log("letter %s at index %i", letter, index);
});

you can use a map function and get all indexes for each letter that user enter. example:
const getIndexes = (letter, bandName) => bandName.split("").map((currentValue, index) => currentValue.toUpperCase() === letter.toUpperCase() ? index : undefined).filter(x => x !== undefined);
const indexes = getIndexes("e", "Green Day");
console.log("indexes", indexes);

Related

How to get odd and even position characters from a string?

I'm trying to figure out how to remove every second character (starting from the first one) from a string in Javascript.
For example, the string "This is a test!" should become "hsi etTi sats!"
I also want to save every deleted character into another array.
I have tried using replace method and splice method, but wasn't able to get them to work properly. Mostly because replace only replaces the first character.
function encrypt(text, n) {
if (text === "NULL") return n;
if (n <= 0) return text;
var encArr = [];
var newString = text.split("");
var j = 0;
for (var i = 0; i < text.length; i += 2) {
encArr[j++] = text[i];
newString.splice(i, 1); // this line doesn't work properly
}
}
You could reduce the characters of the string and group them to separate arrays using the % operator. Use destructuring to get the 2D array returned to separate variables
let str = "This is a test!";
const [even, odd] = [...str].reduce((r,char,i) => (r[i%2].push(char), r), [[],[]])
console.log(odd.join(''))
console.log(even.join(''))
Using a for loop:
let str = "This is a test!",
odd = [],
even = [];
for (var i = 0; i < str.length; i++) {
i % 2 === 0
? even.push(str[i])
: odd.push(str[i])
}
console.log(odd.join(''))
console.log(even.join(''))
It would probably be easier to use a regular expression and .replace: capture two characters in separate capturing groups, add the first character to a string, and replace with the second character. Then, you'll have first half of the output you need in one string, and the second in another: just concatenate them together and return:
function encrypt(text) {
let removedText = '';
const replacedText1 = text.replace(/(.)(.)?/g, (_, firstChar, secondChar) => {
// in case the match was at the end of the string,
// and the string has an odd number of characters:
if (!secondChar) secondChar = '';
// remove the firstChar from the string, while adding it to removedText:
removedText += firstChar;
return secondChar;
});
return replacedText1 + removedText;
}
console.log(encrypt('This is a test!'));
Pretty simple with .reduce() to create the two arrays you seem to want.
function encrypt(text) {
return text.split("")
.reduce(({odd, even}, c, i) =>
i % 2 ? {odd: [...odd, c], even} : {odd, even: [...even, c]}
, {odd: [], even: []})
}
console.log(encrypt("This is a test!"));
They can be converted to strings by using .join("") if you desire.
I think you were on the right track. What you missed is replace is using either a string or RegExp.
The replace() method returns a new string with some or all matches of a pattern replaced by a replacement. The pattern can be a string or a RegExp, and the replacement can be a string or a function to be called for each match. If pattern is a string, only the first occurrence will be replaced.
Source: String.prototype.replace()
If you are replacing a value (and not a regular expression), only the first instance of the value will be replaced. To replace all occurrences of a specified value, use the global (g) modifier
Source: JavaScript String replace() Method
So my suggestion would be to continue still with replace and pass the right RegExp to the function, I guess you can figure out from this example - this removes every second occurrence for char 't':
let count = 0;
let testString = 'test test test test';
console.log('original', testString);
// global modifier in RegExp
let result = testString.replace(/t/g, function (match) {
count++;
return (count % 2 === 0) ? '' : match;
});
console.log('removed', result);
like this?
var text = "This is a test!"
var result = ""
var rest = ""
for(var i = 0; i < text.length; i++){
if( (i%2) != 0 ){
result += text[i]
} else{
rest += text[i]
}
}
console.log(result+rest)
Maybe with split, filter and join:
const remaining = myString.split('').filter((char, i) => i % 2 !== 0).join('');
const deleted = myString.split('').filter((char, i) => i % 2 === 0).join('');
You could take an array and splice and push each second item to the end of the array.
function encrypt(string) {
var array = [...string],
i = 0,
l = array.length >> 1;
while (i <= l) array.push(array.splice(i++, 1)[0]);
return array.join('');
}
console.log(encrypt("This is a test!"));
function encrypt(text) {
text = text.split("");
var removed = []
var encrypted = text.filter((letter, index) => {
if(index % 2 == 0){
removed.push(letter)
return false;
}
return true
}).join("")
return {
full: encrypted + removed.join(""),
encrypted: encrypted,
removed: removed
}
}
console.log(encrypt("This is a test!"))
Splice does not work, because if you remove an element from an array in for loop indexes most probably will be wrong when removing another element.
I don't know how much you care about performance, but using regex is not very efficient.
Simple test for quite a long string shows that using filter function is on average about 3 times faster, which can make quite a difference when performed on very long strings or on many, many shorts ones.
function test(func, n){
var text = "";
for(var i = 0; i < n; ++i){
text += "a";
}
var start = new Date().getTime();
func(text);
var end = new Date().getTime();
var time = (end-start) / 1000.0;
console.log(func.name, " took ", time, " seconds")
return time;
}
function encryptREGEX(text) {
let removedText = '';
const replacedText1 = text.replace(/(.)(.)?/g, (_, firstChar, secondChar) => {
// in case the match was at the end of the string,
// and the string has an odd number of characters:
if (!secondChar) secondChar = '';
// remove the firstChar from the string, while adding it to removedText:
removedText += firstChar;
return secondChar;
});
return replacedText1 + removedText;
}
function encrypt(text) {
text = text.split("");
var removed = "";
var encrypted = text.filter((letter, index) => {
if(index % 2 == 0){
removed += letter;
return false;
}
return true
}).join("")
return encrypted + removed
}
var timeREGEX = test(encryptREGEX, 10000000);
var timeFilter = test(encrypt, 10000000);
console.log("Using filter is faster ", timeREGEX/timeFilter, " times")
Using actually an array for storing removed letters and then joining them is much more efficient, than using a string and concatenating letters to it.
I changed an array to string in filter solution to make it the same like in regex solution, so they are more comparable.

Js function that returns a matching letter from 2 strings

Function that returns the first letter that is present in both strings that a user submits via
an input type text, the strings are separated with a comma. For example:aaaaa,bbbbba--> the matching letter is 'a'because is present in both strings
Sorry for some italian names but i code in italian
I'm not sure how to continue, i have a for to go throught both strings, but i'm not sure if it's correct
function Ripetizione() {
var rip = document.getElementById("string").value;
if (rip.indexOf(",") == -1) { //check to see if the comma is not present
alert("Non c'è nessuna virgola");
return;
}
var stringa1 = rip.substr(0, rip.indexOf(",")); //this is the string1 before the comma
var stringa2 = rip.substr(rip.indexOf(",") + 1, rip.length - (stringa1.length + 1)); //this is the second string after the comma
for (i = 0; i <= stringa1.length; i++) { //for cycle to count the elements of the first string
}
for (k = 0; i <= stringa2.lenght; k++) { //same for the string2
}
}
Ripetizione()
You need not loop the second string.. Just check for index of the element => 0 , while looping through each element of first string part. And return the value..
Always prefer functional over imperative programming.Use Array#find
function getCommonLetter(str){
const [stringA, stringB]=str.split(',');
return Array.from(stringB).find(val => stringA.includes(val));
}
console.log(getCommonLetter('ab,ba'))
console.log(getCommonLetter('ads,bsd'))
console.log(getCommonLetter('aaa,bbc'))
function Ripetizione() {
var rip=document.getElementById("string").value;
if (rip.indexOf(",")==-1){
alert("Non c'è nessuna virgola");
return;
}
var stringa1=rip.substr(0,rip.indexOf(","));
var stringa2=rip.substr(rip.indexOf(",")+1,rip.length-(stringa1.length+1));
return search(stringa1, stringa2);
}
function search(a, b){
for(var i=0; i<a.length;i++){
for(var j=0;j<b.length;j++){
if(a[i] == b[j]){
return a[i];
}
}
}
}
We can do it using Array#reduce and check for the presence of the matching chars using Array#includes.
The idea is to convert the strings into an array of string using Array#from then use the reduce function to match and accumulate the matched characters.
//returns the matching chars as an array
function Ripetizione(rip) {
//let rip=document.getElementById("string").value;
let strs = rip.split(",");
if (strs.length !== 2){ //check to see if the comma is not present
alert("Non c'è nessuna virgola");
return;
}
//converting strings to array to use reduce
let strOne = Array.from(strs[0]), strTwo = strs[1];
return strOne.reduce((acc,alpha)=> {
return !acc.includes(alpha) && strTwo.includes(alpha)?[alpha,...acc]:acc;
},[]).slice(0,1).toString();
}
console.log(Ripetizione("aaaaaab,bbbbba"));
console.log(Ripetizione("aaaaaa,bbbbba"));
console.log(Ripetizione("acccaaaa,bbbbba"));
console.log(Ripetizione("acccaaaa,bbbbcba"));
console.log(Ripetizione("dddddddd,bbbbba")); //return blank string
console.log(Ripetizione("ab,ba"));
function Ripetizione() {
var rip = document.getElementById("string").value;
if (rip.indexOf(",") == -1) { //check to see if the comma is not present
alert("Non c'è nessuna virgola");
return;
}
var stringa1 = rip.substr(0, rip.indexOf(",")); //this is the string1 before the comma
var stringa2 = rip.substr(rip.indexOf(",") + 1, rip.length - (stringa1.length + 1)); //this is the second string after the comma
if (stringa1.length <= stringa2.length) {
stringa2 = stringa2.split('')
stringa1 = stringa1.split('')
for (i = 0; i <= stringa2.length; i++) { //for cycle to count the elements of the first string
if (stringa1.includes(stringa2[i])) {
console.log(stringa2[i]);
}
}
} else if (stringa1.length >= stringa2.length) {
stringa1 = stringa1.split('')
stringa2 = stringa2.split('')
for (i = 0; i <= stringa1.length; i++) { //for cycle to count the elements of the first string
if (stringa2.includes(stringa1[i])) {
console.log(stringa1[i]);
}
}
}
}
<input id="string" type="text">
|<button id="ok" onclick="Ripetizione()">done</button>
The following demo binds the change event to input. When a user enters text in input then clicks outside of input the function is called. The function uses split() and filter() and displays the result in an output. The function also removes any spaces and reports if there are no matches as well.
Demo
Details are commented in demo
var str = document.getElementById("string");
// This is an Event Handler
function ripZ(e) {
var result = document.getElementById('result');
var rip = this.value;
if (rip.indexOf(",") === -1) {
result.value = "Separate the two strings with a comma (no spaces).";
return;
}
// If there's a space remove it
var rip = rip.replace(/\s/g, '');
// Split the string at the comma making an array of two strings
var array = rip.split(',');
/*
Take each string and split them at each letter.
Now there's two arrays of letters
*/
var first = array[0].split('');
var second = array[1].split('');
/*
Loop through both arrays by using two for...of loops
*/
var match = first.filter(function(f, i) {
return second.indexOf(f) !== -1;
});
// Display results
if (match) {
result.innerHTML = `
The letter "${match[0]}" is the first match between both strings.`;
} else {
result.value = "There was no match."
}
return;
}
/*
This is an onevent property registered to the input
If a user types into the input then clicks outside of input
the Event Handler ripZ() is called.
*/
str.onchange = ripZ;
<input id='string'><button>GO</button><br>
<output id='result'></output>
If the input was "ab,ba", you sad that it should be return b so Code must be above in my openion :
function Ripetizione() {
// var rip=document.getElementById("string").value;
// if (rip.indexOf(",")==-1){
// alert("Non c'è nessuna virgola");
// return;
// }
// var stringa1=rip.substr(0,rip.indexOf(","));
// var stringa2=rip.substr(rip.indexOf(",")+1,rip.length-(stringa1.length+1));
var stringa1="ab";
var stringa2="ba";
for(var i=0; i<stringa2.length;i++){
for(var j=0;j<stringa1.length;j++){
if(stringa2.charAt(i) === stringa1.charAt(j)){
console.log(stringa2.charAt(i));
return;
}
}
}

Capture respective index of same string with multiple occurences using Javascript

input
Lets go to play football.
I love football.
Here, how do I get the respective start and end index of both the first 'football' and the second 'football'?
var start_index = input.indexOf('football');
This only gives me the index of the first occurence of football .
You can use this function that will find every occurence.
const s = 'Lets go to play football.\n' +
'I love football.';
const findIndices = (s, word) => {
let index = -1;
while(1) {
index = s.indexOf(word, index + 1);
if (index === -1) return;
console.log(index);
}
};
findIndices(s, 'football');
One option is using RegExp.prototype.exec function:
var str = "Lets go to play football.\nI love football.";
var reg = /football/g, indices = [];
while (reg.exec(str) !== null) {
indices.push(reg.lastIndex - reg.source.length);
}
// indices:
// Array(2)
// 0: 16
// 1: 33
In the above code str is the input and indices is an array of indices (indexes).
A good place to find out about these kind of things is the documentation indexOf()
As you can see, this function accepts a second parameter that is used to determine form where to start the search.
That means that you can get the index of the second occurrence like this:
var str = "Lets go to play football.\nI love football.";
var firstIndex = str.indexOf("football");
var secondIndex = str.indexOf("football", firstIndex + 1);
console.log(firstIndex, secondIndex);
And to do that for all the occurrences, you can use a loop:
var str = "Lets go to play football.\nI love football.\nI love football.";
var indexes = [];
var latestIndex = -1;
while(true){
latestIndex = str.indexOf("football", latestIndex + 1);
if(latestIndex === -1){
break;
}
indexes.push(latestIndex);
}
console.log(indexes);

Highest occurrence in javascript

Hi my knowledge on javascript is very limited and basic. Basically below is I will prompt a pop-up that displays the answer to the value. The thing is from the coding I found below if I had to insert an array lets say 1,2,3,2 the output would be , since it has the highest occurrence in the array. Is there a way to edit this code so that the answer to the input above would be 2
I have done my fair share of research:
Here are the links:
Highest occurrence in an array or first selected
Get the element with the highest occurrence in an array
https://www.sitepoint.com/community/t/numbers-only-input-values-in-text-box-how/3029
Code:
<script type="text/javascript">
function evaluate() {
var input = prompt("Please enter your input");
var array = new Array();
function mode(array)
{
if(array.length == 0)
return null;
var modeMap = {};
var maxEl = array[0], maxCount = 1;
for(var i = 0; i < array.length; i++)
{
var el = array[i];
if(modeMap[el] == null)
modeMap[el] = 1;
else
modeMap[el]++;
if(modeMap[el] > maxCount)
{
maxEl = el;
maxCount = modeMap[el];
}
}
return maxEl;
}
document.writeln("Your calculation is: ");
document.writeln(mode(input) + " with a starting input string of: " + input);
}
</script>
<script type="text/javascript">
evaluate();
</script>
You want to convert the string '1,2,3,2' into the array [ 1, 2, 3, 2 ] first. this can be done with the split function. You probably also want to trim each element in case someone formats them with a space.
function evaluate() {
const input = prompt("Please enter the array of integers")
.split(',')
.map(item => item.trim());
function mode(items) {
const counts = items
.reduce((counts, item) => {
const currentItemCount = counts.get(item) || 0;
return counts.set(item, currentItemCount + 1);
}, new Map());
const maxEntry = Array.from(counts.entries())
.reduce((maxEntry, entry) => {
return entry[1] > maxEntry[1] ? entry : maxEntry;
});
return maxEntry[0];
}
document.writeln("Your calculation is: ");
document.writeln(mode(input) + " with a starting input string of: " + input);
}
evaluate();
Your issue stems from the fact that you never convert your input (which you receive from prompt as a string) into an actual array.
When mode is called directly on the string, the comma is returned as being the most common because comma is the most common character in the string.
To fix this, you need to convert your string into an actual array so you're operating on the elements of the array instead of the characters of the string.
You can use the split function to split your string ("1,2,3,2") at commas into an array (["1", "2", "3", "2"]) which you can then pass to the mode function:
mode(input.split(","))

Loop through an array to find the index of a user input, then replace the value at the same index of another array

I am creating a Hangman game (with a twist) in JS/jQuery. I have figured out how to identify the index of the array of the word being guessed based on the user input (when they guess a letter). However, I cannot figure out how to take that index and replace the blank (underscoreArray) with the value of the user input at the same index.
I have tried to do this with a a conditional (at the end of the JS code), but I'm not sure how to make this work.
Upon a correct guess, this conditional should change the html from (for example, if the word is "drag" and the user guesses "d") " _ _ _ _ " to "d _ _ _".
Here is the fiddle and here is the JS:
$(document).ready(function() {
var words = ["shade", "she mail", "queen"];
var usedLetters = [];
var wrongLetters = [];
$('.play').click(function() {
var word = words[Math.floor(Math.random() * words.length)];
var wordLength = word.length;
var underscores = "";
for (i = 0; i < wordLength; i++) {
underscores = underscores + "_ ";
}
$('.btn-default').on('click', function() {
var guess = $(this).text();
if (jQuery.inArray(guess, usedLetters) === -1) {
usedLetters.push(guess);
$('.used').html('<p class="used">Letters used:</p><span>' + usedLetters + '</span>');
} else {
alert("You already guessed \"" + guess + "\"!");
}
/*Find out if guess = an index of word, and if it does replce the underscore index with the guess*/
var find = word.indexOf(guess);
var wordArray = [word];
//loop through wordArray and where wordArray === find replace same index of underscores with guess.
for (i = 0; i < wordArray.length; i++) {
var underscoresArray = [underscores];
var index = wordArray.indexOf(guess);
if (index !== -1) {
underscoresArray[index] = guess;
$('#words').after(underscoresArray).toString();
};
}
});
});
});
There are a few problems:
As #Glubus pointed out, [word] doesn't make a character array. Use word.split('').
You set up your underscores string to have spaces in it, making it twice as long as word, so the index needs to be multiplied by two to compensate
You aren't catching every instance of a guessed letter, because indexOf only returns the first one
This should be closer:
var wordArray = word.split('');
var underscoresArray = underscores.split('');
for (var i = 0; i < wordArray.length; i++) {
// Catch every letter, eg. the "p"s in "popcorn"
if (wordArray[i] == guess) {
// Underscored indices are twice the word indices
// "popcorn" -> "p _ p _ _ _ _"
underscoresArray[i * 2] = guess;
};
}
// Only need to do this once after all changes are made
underscores = underscoresArray.join(''); // Collapse back to string
$('#words').after(underscores);
console.log(underscores);
Agreed with #Glubus and #Kristjan,
His solution is good, except you should also use join to bring the array back into a string before adding it into the #words element.
Edit: Kristjan updated his solution with join.
For reference, see:
Split
Join

Categories

Resources