I am trying to make a simon game with Javascript for school. I currently have the code set up to create an array sequence of colors on it's own and have also successfully saved the users answers into it's own array, which gets cleared upon a new sequence. I am now trying to see if said color sequence matches the users color sequence, but I am unsure how to check this without any loopholes. The best I have gotten is being able to test the userClicks array length to the gamePattern array length and to ensure that the last click was indeed correct, but can't figure out a good way to test if the entire userClicks array is equivalent to the gamePattern array. I will also run into the problem that the array must be filled in an allotted time, I'll probably use the setTimout() function to achieve this. There must be an easier way to test if a given array is equal to another.
/*************VARIABLES*************/
//store colors
var buttonColors = [
"green", //0
"red", //1
"yellow", //2
"blue" //3
]
//Game Pattern Storage
var gamePattern = [ /*Added From nextSequence*/ ];
var userClicks = [ /* Added from userClickHistory*/ ];
var level = 0;
var gameOn = false;
/******************BASIC FUNCTIONS*********************/
/*AWAIT KEYPRESS TO BEGIN GAME*/
//some document listener to look for keypress changing gameOn to `true`
//display the level to user
//activate nextSequence();
//log user clicks after nextSequence has executed, check the userClicks vs gamePattern
$(`.btn`).click(function () {
var buttonClicked = $(this).attr(`id`);
userClicks.push(buttonClicked);
animate(buttonClicked);
playSound(buttonClicked);
checkAnswer(userClicks);
});
function checkAnswer(userClicksArray) {
//display the current level to user
//NOT SURE WHAT TO DO ANYMORE
}
/************* NEXT SEQUENCE TO PROGRESS GAME *********/
function nextSequence() {
userClickPattern = [];
level++;
console.log(level);
randomNumber = Math.floor(Math.random() * 4)
randomChosenColor = buttonColors[randomNumber];
gamePattern.push(randomChosenColor);
animate(randomChosenColor);
playSound(randomChosenColor);
}
/******************** SOUNDS AND ANIMATIONS*************************************/
//buttons animations
function animate(clickedButton) {
$(`#` + clickedButton).fadeOut(100).fadeIn(100);
};
//Play a sound in corellation to randomChosenColor
function playSound(color) {
var sound = new Audio('sounds/' + color + '.mp3');
sound.play();
};
You should start by comparing the lengths and if they are the same, just loop through the arrays and check that the value at each index is the same.
If the array is "flat" (no nested arrays or objects), this works nicely. If you have nested objects, you'll need something like this: How to compare arrays in JavaScript?
function checkAnswer(gamePattern, userClicks) {
const len = gamePattern.length;
if (len !== userClicks.length) {
// The lengths of the arrays don't match
return false;
}
for (let i = 0; i < len; i++) {
if (gamePattern[i] !== userClicks[i]) {
// The values at the same index don't match
return false;
}
}
return true;
}
console.log(checkAnswer(['red', 'blue', 'green'], ['red', 'blue', 'green']));
console.log(checkAnswer(['red', 'blue', 'green'], ['red', 'yellow', 'green']));
Related
I'm trying to change the following (that currently returns a random number from an array), so that each random number is different from the last one chosen.
function randomize(arr) {
return arr[Math.floor(Math.random()*arr.length)];
}
oracleImg = [];
for (var i=1;i<=6;i++) {
oracleImg.push(i);
}
randOracleImg = randomize(oracleImg);
I tried the following, but it's not always giving me a number different from the last number.
function randomize(arr) {
var arr = Math.floor(Math.random()*arr.length);
if(arr == this.lastSelected) {
randomize();
}
else {
this.lastSelected = arr;
return arr;
}
}
How can I fix this?
Your existing function's recursive randomize() call doesn't make sense because you don't pass it the arr argument and you don't do anything with its return value. That line should be:
return randomize(arr);
...except that by the time it gets to that line you have reassigned arr so that it no longer refers to the original array. Using an additional variable as in the following version should work.
Note that I've also added a test to make sure that if the array has only one element we return that item immediately because in that case it's not possible to select a different item each time. (The function returns undefined if the array is empty.)
function randomize(arr) {
if (arr.length < 2) return arr[0];
var num = Math.floor(Math.random()*arr.length);
if(num == this.lastSelected) {
return randomize(arr);
} else {
this.lastSelected = num;
return arr[num];
}
}
document.querySelector("button").addEventListener("click", function() {
console.log(randomize(["a","b","c","d"]));
});
<button>Test</button>
Note that your original function seemed to be returning a random array index, but the code shown in my answer returns a random array element.
Note also that the way you are calling your function means that within the function this is window - not sure if that's what you intended; it works, but basically lastSelected is a global variable.
Given that I'm not keen on creating global variables needlessly, here's an alternative implementation with no global variables, and without recursion because in my opinion a simple while loop is a more semantic way to implement the concept of "keep trying until x happens":
var randomize = function () {
var lastSelected, num;
return function randomize(arr) {
if (arr.length < 2) return arr[0];
while (lastSelected === (num = Math.floor(Math.random()*arr.length)));
lastSelected = num;
return arr[num];
};
}();
document.querySelector("button").addEventListener("click", function() {
console.log(randomize(["a","b","c","d"]));
});
<button>Test</button>
Below code is just an example, it will generate 99 numbers and all will be unique and random (Range is 0-1000), logic is simple just add random number in a temporary array and compare new random if it is already generated or not.
var tempArray = [];
var i=0;
while (i != 99) {
var random = Math.floor((Math.random() * 999) + 0);
if (tempArray.indexOf(random)==-1) {
tempArray.push(random);
i++;
} else {
continue;
}
}
console.log(tempArray);
here is a version which will ensure a random number that is always different from the last one. additionally you can control the max and min value of the generated random value. defaults are max: 100 and min: 1
var randomize = (function () {
var last;
return function randomize(min, max) {
max = typeof max != 'number' ? 100 : max;
min = typeof min != 'number' ? 1 : min;
var random = Math.floor(Math.random() * (max - min)) + min;
if (random == last) {
return randomize(min, max);
}
last = random;
return random;
};
})();
If you want to ALWAYS return a different number from an array then don't randomize, shuffle instead!*
The simplest fair (truly random) shuffling algorithm is the Fisher-Yates algorithm. Don't make the same mistake Microsoft did and try to abuse .sort() to implement a shuffle. Just implement Fisher-Yates (otherwise known as the Knuth shuffle):
// Fisher-Yates shuffle:
// Note: This function shuffles in-place, if you don't
// want the original array to change then pass a copy
// using [].slice()
function shuffle (theArray) {
var tmp;
for (var i=0; i<theArray.length;i++) {
// Generate random index into the array:
var j = Math.floor(Math.random()*theArray.length);
// Swap current item with random item:
tmp = theArray[i];
theArray[j] = theArray[i];
theArray[i] = tmp;
}
return theArray;
}
So just do:
shuffledOracleImg = shuffle(oracleImg.slice());
var i=0;
randOracleImg = shuffledOracleImg[i++]; // just get the next image
// to get a random image
How you want to handle running out of images is up to you. Media players like iTunes or the music player on iPhones, iPads and iPods give users the option of stop playing or repeat from beginning. Some card game software will reshuffle and start again.
*note: One of my pet-peeves is music player software that randomize instead of shuffle. Randomize is exactly the wrong thing to do because 1. some implementations don't check if the next song is the same as the current song so you get a song played twice (what you seem to want to avoid) and 2. some songs end up NEVER getting played. Shuffling and playing the shuffled playlist from beginning to end avoids both problems. CD player manufacturers got it right. MP3 player developers tend to get it wrong.
I think my title is not correct for this issue but since english is not my mother tongue i couldn't figure out a better way to title my problem.
So what i have is different Players with a few properties:
Player_one.name = "A";
Player_two.name = "B";
Player_three.name = "C";
Player_four.name = "D";
Players also have property:
Player_one.canPlay = ["B","D"];
Player_two.canPlay = ["A","D"];
Player_three.canPlay = ["A","C"];
Player_four.canPlay = ["B","D"]
//note that this is example and may not be accurate right now
Property canPlay shows with who player can play with.
Now players also have property called "placeInTable" which shows their current spot, for example position one, two or three etc.
What i would like to achieve is to check whether it is possible to make combinations from every canPlay array so that every player is able to play with another in the current round. In case there are several possible options, for example, if player one can play with player two and three and games still could be played with everyone then the player with higher "placeInTable" will be selected for an opponent.
To sum my idea up is that i'm trying to create a swiss system table which manages the next games properly. Algorithm should check every canPlay for each player and create combinations which would result in a way that every player is able to play with another and if several options for games are available it will choose the best opponent first according to "placeInTable".
What i've done so far is that i have an algorithm which will start checking the table from the beginning, if player can't play with another the lower one will be selected. Although my current algorithm is faulty since if the length-1 and length-2 players have played with each other the algorithm does not know what to do. So for that i'll add two upper players from which the algorithm crashes, currently length-1 and length-2 so the players length-3 and length-4 will be added and canPlay check will be ran.
I hope my description for this problem was not too misleading and could be understood and really big kudos to someone who is able to help.
If any questions i'd be happy to describe more about this issue.
Edit no. 1:
I forgot to add that in Swiss System two players can't play with each other once they've already played with each other. That's why i have canPlay and in first round the canPlay array length may be longer and in fifth or sixth it may be really small.
About answer which suggested that if A can play with B and B can play with C then A can play with C then no, it's not correct idea. Better understanding could be made with an example.
Let's say there are possible combinations like:
A vs B
C vs D
//and
A vs D
B vs C
Now in such manner every player can play with every other player but there's two options. Now if the table goes that player A has spot 1, player B spot 2, player C spot 3 and player D spot 4 then it should choose the first option because players in higher place in table should be placed together.
There could be of course a third option, player A vs player C && player B vs player D but i left it out right now. IF there would be third option then still the first one would be selected since it places the higher spots together.
Edit no. 2:
function reastaArray(){
var newArr = jQuery.extend(true,[],playerLst); //copy of original players
var original = jQuery.extend(true,[],playerLst);
var inPlay = []; //siia hakkame lisama
var firstRound = true;
var visitedCount = 1;
var i = 0;
var count = 0;
while (newArr.length != 0){
//this goes on until there are players left in playerList
count = i;
hereiam = false;
if (inPlay.length % 2 == 0){ //if the players in play are even amount then the first one gets pushed without a check
inPlay.push(newArr[i]);
newArr.splice(i,1);
}
else{ //now we need to search for opponent
var lastEl = inPlay[inPlay.length-1];
var element = newArr[i];
var played = hasPlayed(element,lastEl); //true/false, can we play with last element in inPlay, if it's true then while cycle begins
while (played == true){
count += 1;
if (count == newArr.length){ //if we've reached at the end of the newArr and still haven't found an opponent
//take in last played games into new array
takeLast(inPlay,newArr);
for (var y = 0; y<visitedCount;y++){
takeLast(inPlay,newArr);
takeLast(inPlay,newArr);
}
canWePlayNow(newArr);
//populize canPlay-s.
//IDEA FROM STACK
var combinations = findCombinations(newArr);
console.log("possible combinations");
combinations.forEach(function(comb) {
console.log(comb);
});
console.log(findBest(combinations,newArr));
visitedCount += 1;
}
else{
element = newArr[count];
played = hasPlayed(element,lastEl);
}
}
if (hereiam == false){
inPlay.push(element);
newArr.splice(count,1);
}
}
}
return inPlay;
}
function canWePlayNow(newArr){
for (var i = 0; i<newArr.length;i++){
var player = newArr[i];
player.canPlay = [];
var hasPlayed = player.playedNames;
for (var j = i+1; j<newArr.length;j++){
playerFromPlayed = newArr[j];
var inArr = isInArray(hasPlayed,playerFromPlayed.name);
if (inArr == false){
player.canPlay.push(playerFromPlayed.name);
}
}
}
}
The combination array could work better, right now as i tested it does:
As seen from image, first round is calculated great, now second round values are entered and third round calculation messes up: It could put together 5vs6 ; 1vs4 and 2vs3, but this algorithm you provided is pretty close already, could you give a further look to check out what's wrong?
Edit no 3:
With further inspection it seems like it's still doing something wrong. As seen from image below the correct result should be 1 & 2 and 5 vs 3 although next games shown are 1vs5 & 3vs2, which shouldn't be result.
The input for the array were Players who've had already one round of free or as in table "V". The algorithm you posted is unchanged.
As seen from console, the other option which i pointed out above is in combinations but it's not selected. Any ideas?
Edit no 4:
Added new image!
function findBest(combinations, players) {
if(combinations.length === 0) throw new Error();
var koht = {};
function score(comb) {
return comb.reduce(function(score, pair) {
//console.log("New calc:");
//console.log(score+koht[pair[0]]*koht[pair[1]]);
return score + koht[pair[0]] * koht[pair[1]];
}, 0);
};
players.forEach(function(p) {
koht[p.name] = p.koht;
});
var best = combinations[0];
combinations.slice(1).forEach(function(comb) {
console.log(score(comb) + " = combs & best = "+score(best));
console.log("Checked combs: ");
console.log(comb);
if(score(comb) > score(best)) {
best = comb;
}
});
console.log("Returned array: (best)");
console.log(best);
return best;
}
Added Log to KOHT
Here is some code that displays all possible combinations in which each player gets to play. It then tries to find the best one by preferring pairings of players in high spots.
function findCombinations(players) {
var combinations = [];
function removePlayer(a, name) {
for(var i = 0; i < a.length; i++) {
if(a[i].name === name) return a.slice(0, i).concat(a.slice(i+1));
}
return a;
}
function find(players, comb) {
if(players.length === 0) {
combinations.push(comb);
return;
};
var player = players[0];
player.canPlay.forEach(function(other) {
var newPlayers = removePlayer(players, other);
if(newPlayers !== players && other !== player.name) {
find(newPlayers.slice(1), comb.concat([[player.name, other]]));
}
});
}
find(players, []);
return combinations;
}
function findBest(combinations, players) {
if(combinations.length === 0) throw new Error();
var placeInTable = {};
function score(comb) {
return comb.reduce(function(score, pair) {
return score + placeInTable[pair[0]] * placeInTable[pair[1]];
}, 0);
};
players.forEach(function(p) {
placeInTable[p.name] = p.placeInTable;
});
var best = combinations[0];
combinations.slice(1).forEach(function(comb) {
if(score(comb) > score(best)) best = comb;
});
return best;
}
var p1 = {name: "A", canPlay: ["B", "D", "C"], placeInTable: 1},
p2 = {name: "B", canPlay: ["A", "D"], placeInTable: 2},
p3 = {name: "C", canPlay: ["A", "C", "D"], placeInTable: 3},
p4 = {name: "D", canPlay: ["B", "D", "C"], placeInTable: 4};
var players = [p1, p2, p3, p4],
combinations = findCombinations(players);
console.log("possible combinations");
combinations.forEach(function(comb) {
console.log(comb);
});
console.log("\nbest:");
console.log(findBest(combinations, players));
I am using Javascript and Openlayers library in order to style a vector feature on the map.
I have written the following script:
var gidS = response[Object.keys(response)[Object.keys(response).length - 1]] // get the data from json obj
// ADD STYLING - DIFFERENT COLORS FOR WHAT IS COMPLETE
var styleContext = {
getColor: function (feature) {
var objectKeys = Object.keys(gidS); // use objectkeys to loop over all the object properties //use it to get the length
for (var i = 0; i < objectKeys.length; i++){
//alert(i);
//////////////////
if(gidS[i][1]=="MT"){
//alert(gidS[i][1]);
return "green";
}
else if(gidS[i][1]=="IRU"){
alert(gidS[i][1]);
return "#780000"; //no images on this line
}
///////////////////////
}
}
};
If I run the script without the if conditions (between the slashes) then I get a correct incremental value of i based on the maximum length of gidS.
But when I include the if statements for some reason the variable i doesn't increment. It remains 0.
EDITED
The getColor function is executed later like this
// define Style
var defaultStyle = new OpenLayers.Style({
fillColor: "${getColor}",
fillOpacity:"1",
strokeColor: "${getColor}",
strokeOpacity: "1",
strokeWidth: 8,
cursor: "pointer",
pointRadius: 8
}, {
context: styleContext
});
What am I doing wrong here?
Thanks a lot.
D.
Capture the color in a variable, for example: color, and return it at the end of the function:
getColor: function (feature) {
var color = '';
var objectKeys = Object.keys(gidS); // use objectkeys to loop over all the object properties //use it to get the length
for (var i = 0; i < objectKeys.length; i++){
if(gidS[i][1]=="MT"){
color = "green";
}
else if(gidS[i][1]=="IRU"){
color = "#780000"; //no images on this line
}
}
return color;
}
By looping through every property of the object, and then returning, you're effectively getting the last possible matching "MT" or "IRU", if any. If you return out of the function as soon as you find a match, then your getting the first possible matching "MT" or "IRU".
For example, given the set: [[435,'IRU'],[34324,'MT'],[343,'MT']] My method will return green, and your method will return #780000.
don't use the return, it immediately stop after used .
try this altough i dont know the gidS.
if(gidS[i][1]=="MT"){
gidS[i]['color']='green';
}
else if(gidS[i][1]=="IRU"){
gidS[i]['color']='#780000';
}
I have an array and a function that picks randomly elements from this array and displays them in a div.
My array:
var testarray = [A, B, C, D, E, F];
Part of the js function:
var new_word = testarray[Math.floor((Math.random()*testarray.length)+1)];
$("#stimuli").text(new_word);
My question is, is there a way I can have them picked randomly in a certain ratio/order?
For example, that if I have my function executed 12 times, that each of the six letters is displayed exactly twice, and that there can never be the same letter displayed twice in a row?
You might want to try a quasi-random sequence. These sequences have the properties you're after. http://en.wikipedia.org/wiki/Low-discrepancy_sequence
Edit:
To your question in the comment: Of course there are hundreds ways to solve a problem. Think about using artificial intelligence, a mathematical algorithm or the answers given by others here. It depends on what you really want to achieve. I just gave a robust solution that is easy to understand and implement..
Here's another (different approach), same result but with the prevention that values displays twice in a row.
Jsfiddle: http://jsfiddle.net/kychan/jJE7F/
Code:
function StructuredRandom(arr, nDisplay)
{
// storage array.
this.mVar = [];
this.previous;
// add it in the storage.
for (var i in arr)
for (var j=0; j<nDisplay; j++)
this.mVar.push(arr[i]);
// shuffle it, making it 'random'.
for(var a, b, c = this.mVar.length; c; a = Math.floor(Math.random() * c), b = this.mVar[--c], this.mVar[c] = this.mVar[a], this.mVar[a] = b);
// call this when you want the next item.
this.next = function()
{
// default value if empty.
if (this.mVar.length==0) return 0;
// if this is the last element...
if (this.mVar.length==1)
{
// we must give it..
return this.mVar.pop();
// or give a default value,
// because we can't 'control' re-occuring values.
return -1;
}
// fetch next element.
var element = this.mVar.pop();
// check if this was already given before.
if (element==this.previous)
{
// put it on top if so.
this.mVar.unshift(element);
// call the function again for next number.
return this.next();
}
// set 'previous' for next call.
this.previous = element;
// give an element if not.
return element;
};
}
NOTE: In this example we can't fully control that the same values are displayed twice.. This is because we can control the first numbers, but when there is only one number left to display, we must either give it or display a default value for it, thus there is a chance that the same value is shown.
Good luck!
Like this?
var arr = [1,2,3,4,5,6,7], // array with random values.
maxDispl = 2, // max display.
arr2 = init(arr) // storage.
;
// create object of given array.
function init(arr)
{
var pop = [];
for (var i in arr)
{
pop.push({value:arr[i], displayed:0});
}
return pop;
}
// show random number using global var arr2.
function showRandom()
{
// return if all numbers has been given.
if (arr2.length<1) return;
var randIndex= Math.floor(Math.random()*arr2.length);
if (arr2[randIndex].displayed<maxDispl)
{
document.getElementById('show').innerHTML+=arr2[randIndex].value + ', ';
arr2[randIndex].displayed++;
}
else
{
// remove from temp array.
arr2.splice(randIndex, 1);
// search for a new random.
showRandom();
}
}
// iterate the function *maxDispl plus random.
var length = (arr.length*maxDispl) + 2;
for (var i=0; i<length; i++)
{
showRandom();
}
jsfiddle: http://jsfiddle.net/kychan/JfV77/3/
I'm having some trouble getting an array to be filled properly. I'm attempting to get a deck of cards to be loaded into an array and then shuffled which does work fine initially, however, after I do a check to see if there are enough cards left, the array does not load properly and everything more or less breaks.
Here's the relevant code. Any help would be greatly appreciated. Thanks!
var deck = {
//base deck before shuffle
baseDeck: ['d02', 'd03', 'd04', 'd05', 'd06', 'd07', 'd08', 'd09', 'd10', 'd11', 'd12', 'd13', 'd14', 'h02', 'h03', 'h04', 'h05', 'h06', 'h07', 'h08', 'h09', 'h10', 'h11', 'h12', 'h13', 'h14', 'c02', 'c03', 'c04', 'c05', 'c06', 'c07', 'c08', 'c09', 'c10', 'c11', 'c12', 'c13', 'c14', 's02', 's03', 's04', 's05', 's06', 's07', 's08', 's09', 's10', 's11', 's12', 's13', 's14'],
//Deck Shoe
shoe: [],
//pull deck #, return to shoe
shuffleDeck: function () {
this.shoe.length = 0;
this.shoe = this.baseDeck;
for (i = 0; i < this.shoe.length; i++) {
var randomPlace = Math.floor(Math.random() * 50) + 1;
var currentPlace = this.shoe[i];
this.shoe[i] = this.shoe[randomPlace];
this.shoe[randomPlace] = currentPlace;
}
}
}
var cardRetrieval = {
//return card vals
getCard: function (curHand) {
var q = curHand.push(deck.shoe.shift());
this.checkValue(curHand);
showCards(curHand, q);
if (deck.shoe.length <= 40) {
deck.shuffleDeck();
}
}
Everything works fine until the if statement at the bottom that checks if there are 40+ cards in the shoe array. But when it attempts to shuffle the deck again, it breaks.
The trouble is with this:
this.shoe.length = 0;
this.shoe = this.baseDeck;
You're not making a copy of the baseDeck into the shoe. Instead you're overwriting the reference to the empty Array you created for shoe, and you're replacing it with a reference to the same Array that baseDeck references.
So it works the first time you shuffle, because this.shoe.length = 0 is not yet affecting the baseDeck. But when you shuffle the second time, you're destroying the baseDeck. (Basically, with that first shuffle, you were using the baseDeck instead of a copy of it.)
Change it to this:
this.shoe.length = 0;
this.shoe = this.baseDeck.slice(0);
This will make a clean copy of baseDeck that is referenced by shoe each time.
There are 2 problems with your random number, 1) it will never be 0 - the first card in the deck, and 2) it may exceed the array size.
Use this instead:
var randomPlace = Math.floor(Math.random() * this.shoe.length);