I have a javascript array, mediaSizes as follows: ['xs','md','lg'].
I'm using a variable media to refer to the specific index in the array. So media can either be 0, 1, or 2. So mediaSizes[media] should either output xs, md, or lg.
I have up and down arrows for user to cycle through this array. However, when the user clicks the up arrow, and the media value is 2, I want the user to cycle back down to xs. Similarly, if the user presses the down arrow on xs I want them to cycle through to large.
I have the following if else statement, but it doesn't seem to be working:
var mediaSizes = ['xs','md','lg'];
var media = 0;
$scope.mediaDisplay = mediaSizes[media];
$scope.changeMedia = function(direction) {
if (media > 2 || media < 0) {
media = 0;
} else {
if (direction == 'up') {
media ++;
$scope.mediaDisplay = mediaSizes[media];
} else {
media --;
$scope.mediaDisplay = mediaSizes[media];
}
}
}
Right now, I settled for setting the media value to 0 for the sake of getting it to work. There has to be an easier way to cycle through 0, 1, 2...
The usual trick for incrementing a value that you want to wrap around is to use the % operator. For decrementing, you just have to brute force it and check for negative values:
var mediaSizes = ['xs','md','lg'];
var media = 0;
$scope.mediaDisplay = mediaSizes[media];
var changeMedia = function(direction) {
if (direction === 'up') {
media = ++media % mediaSizes.length;
} else {
if (--media < 0) media += mediaSizes.length;
}
$scope.mediaDisplay = mediaSizes[media];
}
The % operator is handy when incrementing because it will guarentee a value between 0 and operand - 1. So media % mediaSizes.length will always be a legal index of the array and it will work for wrapping around as you increment media.
Unfortunately, the % operator doesn't do what you want for negative numbers so if we find a negative number, we have to do something different. In this case, I just chose to add mediaSizes.length which will cause -1 to wrap around to the last index.
FYI, note I'm using mediaSizes.length rather than any hard-coded constants so this code would continue to work flawlessly even if you added more mediaSizes to the array. This is one of the principles of more robust code that automatically handles changes elsewhere.
For code cleanliness, this might be a nice place to capture some variables in a closure to keep them out of the global scope:
var changeMedia = (function() {
var mediaSizes = ['xs','md','lg'];
var media = 0;
$scope.mediaDisplay = mediaSizes[media];
return function(direction) {
if (direction === 'up') {
media = ++media % mediaSizes.length;
} else {
if (--media < 0) media += mediaSizes.length;
}
$scope.mediaDisplay = mediaSizes[media];
}
})();
How about:
if (direction == 'up') {
media ++;
if(media > 2){
media = 0;
}
var mediaDisplay = mediaSizes[media];
} else {
media --;
if(media < 0){
media = 2;
}
var mediaDisplay = mediaSizes[media];
}
Your code is incorrect in several ways.
It does not cycle down
You must chance mediaDisplay property of scope, not some global mediaDisplay object. UPD: already fixed
Incorrect incrementation and decrementation. For example if current media == 2 and user are making up direction, then you are incrementing that value (media++) and then trying to get mediaSizes[3] which does not exist of course.
Use this code instead:
var mediaSizes = ['xs','md','lg'];
var media = 0;
$scope.mediaDisplay = mediaSizes[media];
var changeMedia = function(direction) {
switch (direction) {
case 'up':
media = media === 2 ? 0 : media+1;
break;
case 'down':
media = media === 0 ? 2 : media-1;
break;
}
$scope.mediaDisplay = mediaSizes[media];
};
UPD
What about media = media === 2 ? 0 : media+1 - here we use ternary opertator.
First check if media === 2.
If true - ternary operator return 0 (we must set media to zero, as there is no media with index == 3)
If false - ternary operator return media+1 value.
Almost the same logic for decrementation, but in that case we check on media === 0 instead of media === 2.
Related
I am trying to make a maze generator, and almost everything is working so far. I have been able to set my position to a random pos, and then I repeat the standard() function. In the function, I add pos to posList, and then I choose a random direction. Next, I check if the cell has been visited by running through all of the posList vectors in reverse. I haven't executed the code that backtracks yet. If visited = false then I move to the square and execute the yet-to-be-made path() function. However, for some reason, the mover just doesn't detect if a cell has been visited or not. I am using p5.js. What am I doing wrong?
var posList = [];
var pos;
var tempo;
var boole = false;
var direc;
var mka = 0;
function setup() {
createCanvas(400, 400);
//Set up position
pos = createVector(floor(random(3)), floor(random(3)));
frameRate(1)
}
//Choose a direction
function direct(dire) {
if(dire === 0) {
return(createVector(0, -1));
} else if(dire === 1) {
return(createVector(1, 0));
} else if(dire === 2) {
return(createVector(0, 1));
} else {
return(createVector(-1, 0));
}
}
/foLo stands fo forLoop
function foLo() {
//If we have checked less than three directions and know there is a possibility for moving
if(mka < 4) {
//tempoRARY, this is what we use to see if the cell has been visited
tempo = createVector(pos.x + direct(direc).x, pos.y + direct(direc).y);
//Go through posList backwards
for(var i = posList.length - 1; i >= 0; i --) {
//If the cell has been visited or the cell is off of the screen
if(tempo === posList[i]) {
//Change the direction
direc ++;
//Roll over direction value
if(direc === 4) {
direc = 0;
}
//Re-execute on next frame
foLo();
//The cell has been visited
boole = false;
//Debugging
console.log(direc)
mka++;
} else if(tempo.x < 0 || tempo.x > 2 || tempo.y < 0 || tempo.y > 2) {
direc ++;
if(direc === 4) {
direc = 0;
}
foLo();
boole = false;
console.log(direc)
mka++;
}
}
//If it wasn't visited (Happens every time for some reason)
if(boole === true) {
//position is now the temporary value
pos = tempo;
console.log("works")
mka = 0;
}
}
}
function standard() {
//Add pos to posList
posList.push(pos);
//Random direction
direc = floor(random(4));
//Convert to vector
direct(direc);
foLo();
//Tracks pos
fill(255, 255, 0);
rect(pos.x*100+50, pos.y*100+50, 50, 50)
}
function draw() {
background(255);
fill(0);
noStroke();
//draw grid
for(var i = 0; i < 4; i ++) {
rect(i*100,0,50,350);
rect(0, i*100, 350, 50);
}
standard();
boole = true;
console.log(pos)
console.log(posList);
}
Your issue is on the line where you compare two vectors if(tempo === posList[i]) {: This will never be true.
You can verify that with the following code (in setup() for example):
const v1 = new p5.Vector(1, 0);
const v2 = new p5.Vector(1, 0);
const v3 = new p5.Vector(1, 1);
console.log(v1 === v2) // false
console.log(v1 === v3) // false
This is because despite having the same value v1 and v2 are referencing two different objects.
What you could do is using the p5.Vector.equals function. The doc has the following example:
let v1 = createVector(10.0, 20.0, 30.0);
let v2 = createVector(10.0, 20.0, 30.0);
let v3 = createVector(0.0, 0.0, 0.0);
print(v1.equals(v2)); // true
print(v1.equals(v3)); // false
This might not give you a working algorithm because I suspect you have other logical errors (but I could be wrong or you will debug them later on) but at least this part of the code will do what you expect.
Another solution is to use a Set instead of your list of positions. The cons of this solution is that you will have to adapt your code to handle the "out of grid" position situation. However when you want to keep track of visited items a Set is usually a great solution because the access time is constant. That this means is that to define is a position has already been visited it will always take the same time (you'll do something like visitedSet.has(positionToCheck), whereas with your solution where you iterate through a list the more cells you have visited to longer it will take to check if the cell is in the list.
The Set solution will require that you transform your vectors before adding them to the set though sine, has I explained before you cannot simply compare vectors. So you could check for their string representation with something like this:
const visitedCells = new Set();
const vectorToString = (v) => `${v.x},{$v.y}` // function to get the vector representation
// ...
visitedCells.add(vectorToString(oneCell)); // Mark the cell as visited
visited = visitedCells.has(vectorToString(anotherCell))
Also has a general advice you should pay attention to your variables and functions name. For example
// foLo stands fo forLoop
function foLo() {
is a big smell: Your function name should be descriptive, when you see your function call foLo(); having to find the comment next to the function declaration makes the code less readable. You could call it generateMaze() and this way you'll know what it's doing without having to look at the function code.
Same for
//tempoRARY, this is what we use to see if the cell has been visited
tempo = createVector(pos.x + direct(direc).x, pos.y + direct(direc).y);
You could simply rename tempo to cellToVisit for example.
Or boole: naming a boolean boole doesn't convey a lot of information.
That could look like some minor details when you just wrote the code but when your code will be several hundred lines or when you read it again after taking several days of break, you'll thank past you for taking care of that.
I want to make a function that modifies a variable based on the given argument.
The function checks a variable and the number in that string. Then via the argument, I specify either increase or decrease the number by 1 (++1).
There is an array as well, that if the number is equal to the length of the array, then it turns to 1 and if the number is less than 1 then it is equal the size of the array. This is to make sure the number of the string does not get less than 1 or more than the length of the array.
the string with the number is Music1. So the circle would be like:
...., Music1, Music2, Music3, Music4, Music1, Music2, Music3, ....
var MyArray = ["Music1", "Music2", "Music3", "Music4"];
var currentMusic = "Music1";
$(".increase").on('click tap', nextMusic);
$(".decrease").on('click tap', previousMusic);
function nextMusic() {
unaryChange('plus')
}
function previousMusic() {
unaryChange('minus')
}
function unaryChange(operation) {
if (currentMusic === "Music4") {
currentMusic = "Music1"
} else if (currentMusic === "Music0") {
currentMusic = "Music4"
}
if (operation === "plus") {
currentMusic = currentMusic.replace(/\d+$/, function(n) {
return ++n
});
} else {
currentMusic = currentMusic.replace(/\d+$/, function(n) {
return --n
});
}
console.log(currentMusic);
$(".text").text(currentMusic);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="increase">increase</button>
<button class="decrease">decrease</button>
<p class="text">value</p>
The above method almost does the job, however I am looking for an easier and more professional solution. It does not look efficient. For example, there must be a better way to specify the argument operation instead of a string like plus, or the conditions.
I need this function to be rewritten in a better way, more professionally and works as described.
Thanks in advance.
It is better to work with array index instead of the values
function unaryChange(operation) {
var currentIndex = MyArray.findIndex(function(item) {
return item === currentMusic;
});
if(operation === 'plus') {
newIndex = currentIndex < MyArray.length - 1 && currentIndex + 1 || 0;
} else {
newIndex = currentIndex > 0 ? currentIndex -1 : MyArray.length -1;
}
currentMusic = MyArray[newIndex]
$(".text").text(currentMusic);
}
In this case whatever the size of the array it will work.
A working example https://jsbin.com/rahomorupa/4/edit?html,js,console,output
Building on Joe's answer I'd suggest you define constants for plus and minus as +1 and -1 respectively to simplify the increment/decrement logic, along with the modulus operator to handle the array wrap-around:
const PLUS = 1;
const MINUS = -1;
function unaryChange(operation) {
var currentIndex = MyArray.findIndex(function(item) {
return item === currentMusic;
});
// If it's invoked as unaryChange(PLUS) or unaryChange(MINUS)
// we don't need any conditional logic to handle the increment,
// and with the % operator we don't need additional bounds overflow
// logic. (This latter bit is complicated somewhat by the need to
// handle a minus step from index 0.)
const {length} = MyArray;
const newIndex = ((currentIndex + operation) % length + length) % length;
currentMusic = MyArray[newIndex]
$(".text").text(currentMusic);
}
The % operator returns the remainder of a division, which conveniently loops back around to 0 when used with an array index against the array length:
const array = ['first', 'second', 'third'];
for (let i = 0; i < 20; i++) {
console.log(array[i % array.length]);
}
You can pass a Boolean for plus, use an arrow function, and a ternary operator:
var MyArray = ["Music1", "Music2", "Music3", "Music4"];
var currentMusic = "Music1";
$(".increase").on('click tap', nextMusic);
$(".decrease").on('click tap', previousMusic);
function nextMusic() {
unaryChange(true)
}
function previousMusic() {
unaryChange(false)
}
function unaryChange(plus) {
currentMusic = currentMusic == "Music4" ? "Music1" : (currentMusic == "Music0" ? "Music4" : currentMusic);
currentMusic = currentMusic.replace(/\d+$/, n => plus ? ++n : --n);
console.log(currentMusic);
$(".text").text(currentMusic);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="increase">increase</button>
<button class="decrease">decrease</button>
<p class="text">value</p>
Since you have an array of music, it's better to use that instead. There's no need to operate from the text, you just need to update the array index to the next value and pass it to the function, and let it get the song name directly.
Since we want to be between the boundaries of 0 and the array length, here's what is used to do this:
Get the next song: (currentTrackIndex + 1) % tracks.length. That will get the next index value and apply modulo to it so it will round back if it exceedes the array length.
Get the previous song: (currentTrackIndex - 1 + tracks.length) % tracks.length. It's pretty much the same as getting the next song, save for the case when the index it's already at zero. If you apply modulo to a negative number, you will get a negative result and will mess up your array index. So instead of using a conditional clause ("if (currentTrackIndex === 0 ...)"), let's add the array length. Why? Because since 0 % n == 0 and n % n == 0, adding the array length will not change the modulo result, while keeping your index as a positive number.
(I changed the name from MyArray to tracks and unaryChange to changeTrack, to give it better meaning clarity)
var tracks = ["Music1", "Music2", "Music3", "Music4"];
var currentTrackIndex = 0;
$(".increase").on('click tap', nextMusic);
$(".decrease").on('click tap', previousMusic);
function nextMusic() {
//It will move to the next track. If it's over the array length, it will reset to 0
changeTrack((currentTrackIndex + 1) % tracks.length)
}
function previousMusic() {
//It will move to the previous song. If it's below zero, it will reset to the last track index
changeTrack((currentTrackIndex + tracks.length - 1) % tracks.length)
}
function changeTrack(newTrackIndex) {
currentTrackIndex = newTrackIndex;
var currentTrack = tracks[currentTrackIndex];
console.log(currentTrackIndex);
$(".text").text(currentTrack);
}
Here's how I'd do it. Since it seems that the word Music is just a prefix used to designate a particular unit, I wont store it over and over again in a array.
As for jQuery? Yeah, nah.
"use strict";
function byId(id){return document.getElementById(id)}
window.addEventListener('load', onLoaded, false);
function onLoaded(evt)
{
let prefix = 'Music';
let count = 4, index=0;
byId('increase').addEventListener('click', function(evt){index++; index %= count; update();}, false);
byId('decrease').addEventListener('click', function(evt){index--; if (index<0) index=count-1; update();}, false);
function update()
{
byId('status').textContent = `${prefix}${index+1}`;
}
}
<span id='status'>Music1</span><br>
<button id='increase'>+</button><button id='decrease'>-</button>
I think this is a good start. Accessing the indices of the array versus the values feels a lot cleaner. Using ternaries cleans up a lot of logic into one line as well.
var MyArray = ["Music1", "Music2", "Music3", "Music4"];
var currentMusic = 0;
$(".increase").on('click tap', unaryChange);
$(".decrease").on('click tap', unaryChange);
function unaryChange() {
if (event.target.className === "increase") {
currentMusic = (currentMusic < 3 ? currentMusic + 1 : 0)
} else {
currentMusic = (currentMusic > 0 ? currentMusic -= 1 : 3)
}
console.log(MyArray[currentMusic]);
$(".text").text(MyArray[currentMusic]);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="increase">increase</button>
<button class="decrease">decrease</button>
<p class="text">value</p>
As a practice project I made a Tic-Tac-Toe game on JSFiddle (because there aren't enough already, right?) and I progressed into adding an unbeatable AI. For the most part it works, but there are some combinations (e.g. setting X into fields 5, 9, 3 or into fields 3, 7, 9) that lead to the computer not calculating the optimal move properly.
The project on JSFiddle: https://jsfiddle.net/jd8x0vjz/
And the relevant function starting at line 63:
function evaluateMove(move, player, depth) {
var gameStatus = evaluateGameStatus(move); //get status of current board
if (gameStatus < 2 && player)
return -1; //if human won, return -1
if (gameStatus < 2 && !player)
return 1; //if human lost, return 1
var returnValue = 0 //value to be returned later
for (var z = 0; z < 3; z++) { //loop for row
for (var s = 0; s < 3; s++) { //loop for column
if (move[z][s]) //if current slot has an x or o,
continue; //skip it
var nextMove = cloneGameStatus(move); //create temporary array with base of current grid
nextMove[z][s] = !player ? "x" : "o"; //assign first free field the appropriate symbol
var value = evaluateMove(nextMove, !player, depth+1); //recursion but with switched player, to add the correct icon afterwards
if ((value > returnValue) && player)
returnValue = value;
if ((value < returnValue) && !player)
returnValue = value;
}
}
return returnValue; //return value of current simulation
}
I think the last two if-clauses are causing these problems, since the computer does calculate the proper values (as observable in the debugger), but they're sometimes overwritten, but I am not sure if this really is the root of the problem. Any help or tips would be appreciated!
EDIT: Problem solved! Look for my answer below in case it is not the first one.
I can't say for sure this is the source of the problem but there is most definitely a bug in your code that will produce strange results. The line:
var returnValue = 0 //value to be returned later
is incorrect. Aside from the fact that you are missing a semi-colon, the proper code should be:
var returnValue = -1;
if(!player){
returnValue = 1;
}
You want the default value for the maximum player to be negative so that he takes the best move, and for the minimizing player positive so he takes the worst move. The way you did it, if the maximizing player was faced only with options that valued -1, since -1 is less than 0, and returnValue was initialized to 0, 0 would be returned although the correct value to be returned is -1.
The idea of the default value of returnValue being wrong definitely sent me down the right path; it didn't make everything magically work (would've been too nice if it did), but it did give me the right nudge. Since we don't want to return any value if nothing is calculated, I adjusted the evaluateMove function as following:
function evaluateMove(move, player, depth) {
var gameStatus = evaluateGameStatus(move); //get status of current board
if (gameStatus != 2)
return gameStatus; //if the game is not running anymore, return result
var returnValue; //value to be returned later
for (var z = 0; z < 3; z++) { //loop for row
for (var s = 0; s < 3; s++) { //loop for column
if (move[z][s]) //if current slot has an x or o,
continue; //skip it
var nextMove = cloneGameStatus(move); //create temporary array with base of current grid
nextMove[z][s] = !player ? "x" : "o"; //assign first free field the appropriate symbol
var value = evaluateMove(nextMove, !player, depth+1); //recursion but with switched player, to add the correct icon afterwards
if ((value > returnValue || returnValue == null) && player)
returnValue = value;
if ((value < returnValue || returnValue == null) && !player)
returnValue = value;
}
}
return returnValue; //return value of current simulation
}
Now the default is null and as such should not throw the calculations off. What it did throw off however was the first block of checks, so I adjusted it to simply return the current status should the game have ended, instead of doing any elaborate checks. However that threw off the results because I'm using inversed default values in the two methods, so I had to adjust evaluateGameStatus too. Now if the human won it returns -1 instead of 1, and if the computer won it returns 1 instead of -1:
function evaluateGameStatus(gameStatus) { //a clusterfuck of winning combinations
if(
X Checks
)
return -1; //there's a successful combination of x's
else if(
O Checks
)
return 1; //there's a successful combination of o's
else {
for (var z = 0; z < 3; z++) {
for (var s = 0; s < 3; s++) {
if (!gameStatus[z][s])
return 2; //if there is an empty field neither has won, continue playing
}
}
return 0; //there's no successful combination and max moves have been reached. it's a draw
}
}
I had to do the same adjustmends for the checkGameEnd function, obviously.
You'll notice that I changed the check for draws too. That is because, for some reason, the old check for count == maxMoves did not work anymore, so I changed to a loop which simply checks whether there is any empty field at all, and to return 2 if there is, and 0 if there is not (it returns 0 here because at this point it has run through all the checks: X hasn't won, O hasn't won, and there are no open slots left, so the game must be a draw).
Working project can now be found here:
https://jsfiddle.net/h5zwzkm7/
I am wondering if there is a better way to handle this, or one that uses less text, rather than using a bunch of if/else statements.
Currently I have this handling picture changes to be made during the animation:
link.animate({
top: '0'
}, {
duration: 3000*(Math.random()+1),
easing: 'easeOutBounce',
step: function(now, tween) {
if (now < -300){
$("#"+ link.data("shadow")).attr('src', '');
} else if (now < -250) {
$("#"+ link.data("shadow")).attr('src', ...);
} else if (now < -200) {
$("#"+ link.data("shadow")).attr('src', ...);
} ... etc.
If I can use a switch statement, ternary operators, or an object for the "step" function instead of the if/else statements, could somebody provide examples on how to do that? Thanks in advance.
From what I see, you need a different image base on a 50px difference. 50 is the key here. I'd suggest to use an array of source where the lower pxis the first in array :
var arr = ['srcFor0px', 'srcFor50px', 'srcFor100px', 'srcFor150px', 'srcFor200px'...];
Then, know what's your lower value (in this example -300 is the lower). You will then need to add the lowest value to the current value:
var comparison = now - -300
After that, you'll need to do a math ceil on the result divided by 50.
var index = Math.ceil(comparison / 50);
Then the only thing remaining is to see if the index if inbound. Basically, just check if the index is below 0 or higher than the array length :
if(index < 0) index = 0;
else if(index >= arr.length) index = arr.length-1;
Then you get the good source in the array. Final code would look like that :
step : function(now){
var arr = ['srcFor-300px', 'srcFor-250px', 'srcFor-200px', 'srcFor-150px', 'srcFor-100px'];
var comparison = now - -300
var index = Math.ceil(comparison / 50);
if(index < 0) index = 0;
else if(index >= arr.length) index = arr.length-1;
$("#"+ link.data("shadow")).attr('src', arr[index]);
}
It is not the answer you are specifically looking for, but what about reducing the amount of code you're putting in the if statements instead?
step: function(now, tween) {
var src;
if (now < -300){
src = 'a';
} else if (now < -250) {
src = 'b';
} else if (now < -200) {
src = 'c';
} ... etc.
$("#"+ link.data("shadow")).attr('src', src);
Although other control statements do exist, the ultimate goal towards elegant code is to reduce the amount of code while increasing the ability to understand it.
Only assigning the src variable inside the control flow statements eliminates duplicate code.
You can simplify the repetitive comparisons by a bit of maths:
var num = Math.ceil(now/-50)-1;
if (num > 5)
var src = "";
else {
var src = 'images/home/shadow-pngs/';
if (num > 0)
src += 'shadow'+num;
else
src += 'splat-shadow';
src += '.png';
}
$("#"+ link.data("shadow")).attr('src', src);
I have created a working javascript function to check an array of 5 numbers for a small straight, in a Yahtzee game I'm making. I've tested it to no end and I'm confident it works 100% of the time, but it is also probably the worst algorithm of all time in terms of being efficient. Here is what it looks like:
function calcSmstraight() {
var sum = 0;
var r = new Array();
var r2 = new Array();
var counter = 0;
var temp;
var bool = false;
var bool2 = false;
r[0] = document.getElementById('setKeep1').value;
r[1] = document.getElementById('setKeep2').value;
r[2] = document.getElementById('setKeep3').value;
r[3] = document.getElementById('setKeep4').value;
r[4] = document.getElementById('setKeep5').value;
// Move non-duplicates to new array
r2[0] = r[0];
for(var i=0; i<r.length; i++) {
for(var j=0; j<r2.length; j++) {
if(r[i] == r2[j]) {
bool2 = true; // Already in new list
}
}
// Add to new list if not already in it
if(!bool2) {
r2.push(r[i]);
}
bool2 = false;
}
// Make sure list has at least 4 different numbers
if(r2.length >= 4) {
// Sort dice from least to greatest
while(counter < r2.length) {
if(r2[counter] > r2[counter+1]) {
temp = r2[counter];
r2[counter] = r2[counter+1];
r2[counter+1] = temp;
counter = 0;
} else {
counter++;
}
}
// Check if the dice are in order
if(((r2[0] == (r2[1]-1)) && (r2[1] == (r2[2]-1)) && (r2[2] == (r2[3]-1)))
|| ((r2[1] == (r2[2]-1)) && (r2[2] == (r2[3]-1)) && (r2[3] == (r2[4]-1)))) {
bool = true;
}
}
if(bool) {
// If small straight give 30 points
sum = 30;
}
return sum;
}
My strategy is to:
1) Remove duplicates by adding numbers to a new array as they occur
2) Make sure the new array is at least 4 in length (4 different numbers)
3) Sort the array from least to greatest
4) Check if the first 4 OR last 4 (if 5 in length) numbers are in order
My question:
Does anyone know a way that I can improve this method? It seems ridiculously terrible to me but I can't think of a better way to do this and it at least works.
Given that you're implementing a Yahtzee game you presumably need to test for other patterns beyond just small straights, so it would be better to create the array of values before calling the function so that you can use them in all tests, rather than getting the values from the DOM elements inside the small straight test.
Anyway, here's the first way that came to my mind to test for a small straight within an array representing the values of five six-sided dice:
// assume r is an array with the values from the dice
r.sort();
if (/1234|2345|3456/.test(r.join("").replace(/(.)\1/,"$1") {
// is a small straight
}
Note that you can sort an array of numbers using this code:
r2.sort(function(a,b){return a-b;});
...but in your case the values in the array are strings because they came from the .value attribute of DOM elements, so a default string sort will work with r2.sort(). Either way you don't need your own sort routine, because JavaScript provides one.
EDIT: If you assume that you can just put the five values as a string as above you can implement tests for all possible combinations as a big if/else like this:
r.sort();
r = r.join("");
if (/(.)\1{4}/.test(r)) {
alert("Five of a Kind");
} else if (/(.)\1{3}/.test(r)) {
alert("Four of a Kind");
} else if (/(.)\1{2}(.)\2|(.)\3(.)\4{2}/.test(r)) {
alert("Full House");
} else if (/(.)\1{2}/.test(r)) {
alert("Three of a Kind");
} else if (/1234|2345|3456/.test( r.replace(/(.)\1/,"$1") ) {
alert("Small Straight");
} // etc.
Demo: http://jsfiddle.net/4Qzfw/
Why don't you just have a six-element array of booleans indicating whether a number is present, then check 1-4, 2-5, and 3-6 for being all true? In pseudocode:
numFlags = array(6);
foreach(dice)
numFlags[die.value-1] = true;
if(numFlags[0] && numFlags[1] && numFlags[2] && numFlags[3]) return true
//Repeat for 1-4 and 2-5
return false
This wouldn't be a useful algorithm if you were using million-sided dice, but for six-siders there are only three possible small straights to check for, so it's simple and straightforward.
I do not play Yahtzee, but I do play cards, and it would appear the algorithm might be similar. This routine, written in ActionScript (my JavaScript is a bit rusty) has been compiled but not tested. It should accept 5 cards for input, and return a message for either straights greater than 3 cards or pairs or higher.
private function checkCards(card1:int,card2:int,card3:int,card4:int,card5:int):String
{
// Assumes that the 5 cards have a value between 0-12 (Ace-King)
//The key to the routine is using the card values as pointers into an array of possible card values.
var aryCardValues:Array = new Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
aryCardValues[card1] += 1;
aryCardValues[card1] += 1;
aryCardValues[card1] += 1;
aryCardValues[card1] += 1;
aryCardValues[card1] += 1;
var aryCardNames:Array = new Array("Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King");
var strOutMessage:String;
var intCardCount:int = 0;
var strSeperator:String;
var strHighCard:String;
for (var i:int = 0;i < aryCardValues.length;i++)
{
//Check for runs of three of a kind or greater.
if (aryCardValues[i] >= 2)
{
strOutMessage = strOutMessage + strSeperator + i + "-" + aryCardNames[i] + "s";
strSeperator = " & ";
}
//Check for values in a straight.
if (aryCardValues[i] > 0)
{
intCardCount++;
if (intCardCount > 3)strHighCard = aryCardNames[i];
}
else
{
if (intCardCount < 3)intCardCount = 0;
}
}
if (intCardCount > 3) strOutMessage = intCardCount + " run " + strHighCard + " High."
return strOutMessage;
}
It may not be as concise as the regular expressions used above, but it might be more readable and easily modified. One change that could be made is to pass in an array of cards rather than discrete variables for each card.