I am working on a tic-tac-toe algo and am using regexes to solve for the win conditions.
the 9 squares are given the 0-8 values:
[0][1][2]
[3][4][5]
[6][7][8]
Each time player 1 or 2 clicks a square the value is pushed to an array and after 3 values are collected the regex starts testing to see if a player has won.
the problem.. for example the regex test to see if any order of 012 102 exist but it can't match 03142.
How can I fix my Regex to look for the 3 numbers even if separated by other numbers?
Let regexWin = /(?:^|\W)[012][012][012](?:$|\W)/gm,
You could keep track of the board state, so you don't need to rely on the moves list: define board as an array of length 9, initialised with all 0 (indicating each cell is empty). Then when a move is played set the corresponding slot to either "1" or "2", depending on which player plays that move.
var board = Array(9).fill(0); // initial board
var moves = []; // initial move list
var turn = 1; // first player to move (other player is identified as 2)
// ...
function play(move) { // move is an index in board (0..8)
board[move] = turn; // turn is 1 or 2
moves.push(move); // this is the list you already have
// Use regular expression to detect any 3-in-a-row
let isWin = /^(?:...)*([12])\1\1|^.?.?([12])..\2..\2|^([12])...\3...\3|^..([12]).\4.\4/.test(board.join(""));
console.log("is winning move?", isWin);
turn = 3 - turn; // toggle the player that is to move
}
This way you also can use board to update the display.
For a full implementation, with rendering, and a minimax algorithm for generating a "best" move, see this answer.
Assuming the squares occupied are pushed into the object moves, which contains an array for each player, p1 and p2, just sort the arrays after each push. That way, can guarantee the indices are compared in ascending order.
let moves = {p1: [], p2: []};
function move(player, spaceIndex) {
moves[player].push(spaceIndex);
moves[player].sort();
if (/012|345|678|036|147|258|048|246/.test(moves[player].join('')) {
//win!
}
}
From the details given in your question it doesn't sound like you've realised that you need two arrays, not one, otherwise the win algo won't know which player occupies which space, and will report a win merely for three spaces in a row being occupied, even if they're shared between different players.
I did not find the Regex that worked, so I switched to a different idea.
Based on the article by Alvaro Saburido I learned how to take the winArray and the 1&2 playerArray for an intersection and test for a win.
The article is great and i'll keep searching for a regex solution just for coding fun.
https://medium.com/#alvaro.saburido/set-theory-for-arrays-in-es6-eb2f20a61848
let win = ["012", "345", "678", "036", "147", "258", "048", "246"];
function endGameEvaluation() {
if (joueur1Turn) {
resultMessage.innerHTML = `<div id="resultMessage">Player 1 Wins!! End of the game</div>`;
gameWon = true;
playerTurnMsg.innerHTML = "";
} else if (joueur2Turn) {
resultMessage.innerHTML = `<div id="resultMessage">Player 2 Wins!! End of the game</div>`;
gameWon = true;
playerTurnMsg.innerHTML = "";
}
}
function winner(player) {
for (var i = 0; i < 8; i++) {
let won = win[i]
let inCommon = player.filter(x => won.includes(x));
if (inCommon.length == 3) {
endGameEvaluation();
}
}
}
tdClickArea.forEach(item => {
item.addEventListener('click', e => {
let btnArea = e.target;
let player;
if (btnArea.innerHTML == "X" || btnArea.innerHTML == "O") {
alert("tricheur! Choisi une autre case")
} else {
if (joueur1Turn) {
btnArea.innerHTML = "X";
joueur1Sq.push(btnArea.getAttribute("value"));
player = joueur1Sq;
} else if (joueur2Turn) {
btnArea.innerHTML = "O";
joueur2Sq.push(btnArea.getAttribute("value"));
player = joueur2Sq;
}
}
if (joueur1Sq.length >= 3 || joueur2Sq.length >= 3) {
winner(player);
}
counter++;
changeTurn();
// Here we end the game if nobody won until the last posibble move
if (counter == 9 && gameWon == false) {
resultMessage.innerHTML = `<div id="resultMessage">End of the game</div>`;
}
})
});
Related
I have scanned through the extensive list of questions asking similar things, but none of them have been able to solve this problem in this context.
Essentially, I'm attempting to build a blackjack command for a bot in discord.js, and so far, the flow looks like this: it takes the names and id's of all of the players and then passes them into the function that handles each game. First, three functions are defined. One that finds the value of a hand (the total of the cards) and one that adds a new card. A RichEmbed is created that will be edited many times, and the first time it is created in a game, it will give each person their hand, evaluate it, and then put it in the embed.
The problem is: the variables storing players' hands and hand values come back as undefined during initialization, even though I have tested that they can be output from the same place in the code, and that the functions that give them their data also work. What is causing them to be undefined, and why is it only those two variables? The others work just fine.
My code:
async function bj(players_id, players_names) {
var pc = players_id.length; //player count
var turn; //whose turn is it? based on id
var disp; //text to be shown in embed; game status.
var hands = []; //each player's hand
var handValues = []; //the sum of the cards in a hand
var cards = ["A", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K"]; //all possible cards
var ing = []; //is player in game? / have they busted?
turn = players_id[0];
var disp_dir = "React with �� to hit and ✋ to stand.";
disp = `Currently ${message.member.guild.members.get(turn).displayName}'s turn.\n${disp_dir}`;
function addCard() {return(cards[Math.floor(Math.random() * cards.length)])}; //simply grabs a card from the list of cards
function findHandValue(handValue, currentCards) { //takes two inputs, one is the current hand value, the other is a list of card values. finds the total value of the hand and returns it.
var val = 0;
for (i = 0; i < currentCards.length; i++) {
card = currentCards[i];
if (card == "A") {
if (handValue > 21) {val += 1;} else {val += 11;};
} else if (card == "J" || card == "Q" || card == "K") {
val += 10;
} else {val += card;};
};
return(val);
};
function makeEmbed(first) { //constructs the embed that will be used to show the game. param. first tells whether it is the first time the embed is being constrcuted in a game.
var bjg = new Discord.RichEmbed()
.setTitle("Blackjack Game")
.setDescription(`Initiated by ${players_names[0]}`)
.addField("Status", `${disp}`)
.setThumbnail("https://cdn.discordapp.com/attachments/563578266984775681/657323254813425674/cards.jpg")
.setColor("DC134C")
.setFooter("Valkyrie", client.avatarURL)
.setTimestamp();
if (first == true) {
for (i = 0; i < pc; i++) { //should be creating each player's hand
ing.push(true); //this one works
hands.push([addCard(), addCard()]); //but for some reason, this one returns undefined
handValues.push(findHandValue(0, hands[i])); //and this one
bjg.addField(`${players_names[i]}'s Hand`, `${hands[i]}\n\nValue: ${handValues[i]}`); //spits out undefined in almost every place, including the player_name[i]
var bjs = message.channel.send(bjg);
return(bjs);
};
};
};
var bjs = await makeEmbed(true); //and this stuff works fine
await bjs.react("��");
await bjs.react("✋");
};
if (args[0] == "quick") {
message.reply("You've started a game of solo blackjack against a bot!");
await bj([message.author.id], [message.member.displayName]);
Apologies that that is lengthy, but all of it is necessary to show what it's doing and to help pinpoint the error's origin.
Finally, this is the result
Extra Note
After extremely extensive debugging, I've found that the problem is coming from the line
handValues.push(findHandValue(0, hands[i]));, and the problem is that it changes i by +2, which only raises more questions. A small band-aid fix was to set another variable to i at the start and then reset i to the other one after the line.
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'm having a problem with a statement that gets skipped, and also an infinite loop which I can't figure out why it's infinite since it appears to end when the correct input has been given.
Here's a fiddle so you can understand what's happening easier: http://jsfiddle.net/z03ffn2j/
In the else statement I'm running a while loop that runs as long as the current player tries to ask himself the question in the askPlayer function. So if a player is named "chris" and that player enters "chris", the prompt comes up again.
All good and well, but.. When I don't type "chris" the first time and instead type, let's say "tommy", it skips the var card statement which asks for a value. However, when I enter "chris" the first time, it re-prompts cuz it's myself, and then I enter "tommy", it doesn't skip the var code statement. So my question is, why does it skip it when I enter a correct name the first time, but not if I first enter a false name and then a correct one?
var playerArray = []; //Contains player objects
var turn = 0; //Keeps track of whose turn it is
function askPlayer() {
var askedPlayer = prompt('Which player do you want to ask?');
return askedPlayer;
}
//While the pairCount is less than 13(maximum possible pairs)
while (pairCount < maxPairs) {
if (playerArray[turn].totalCards == 0) {
drawCard();
}
else {
var wrongInput = true;
var player = askPlayer();
while (wrongInput) {
if (player === playerArray[turn].name) {
player = askPlayer();
if (player != playerArray[turn].name) {
wrongInput = false;
}
}
}
var card = askCard(player); //<--- This statement gets skipped
}
pairCount = 13; //Prevents infinite loop for now
turn++;
if (turn == playerNum - 1) { //If everyone has made their turn, reset to player1 again
turn = 0;
}
}
When you enter a player name that isn't equal to the current turn's player name, you'll enter an infinite loop.
You have:
var wrongInput = true;
var player = askPlayer();
while (wrongInput) {
if (player === playerArray[turn].name) {
player = askPlayer();
if (player != playerArray[turn].name) {
wrongInput = false;
}
}
}
var card = askCard(player);
As you can see, the 1st time the loop is entered, wrongInput will be true. Assuming the player's names are a and b respectively and you answered b to askPlayer(), the 1st if statement will be false (player is b and playerArray[0].name is a).
Since you have no other code inside the loop, it'll jump right to the while (wrongInput) evaluation... and now you're stuck in a never-ending loop.
So, it seems you forgot one closing bracket after player = askPlayer().
To fix it, instead of:
if (player === playerArray[turn].name) {
player = askPlayer();
if (player != playerArray[turn].name) {
wrongInput = false;
}
}
... write:
if (player === playerArray[turn].name) {
player = askPlayer();
}
if (player != playerArray[turn].name) {
wrongInput = false;
}
Or, better yet:
if (player === playerArray[turn].name) {
player = askPlayer();
} else {
wrongInput = false;
}
Hello I am trying to implement a simple flood fill type algorithm in javascript. Basically I have a 3x3 board which I represent as a 1 dimensional array. I want to append the index for every equal value that is "touching" to a separate array. So for instance this board:
[1][1][0]
[3][1][3]
[0][0][0]
Would be represented as a 1D array ie [1,1,0,3,1,3,0,0,0]. And after running the floodFill on one of the [1] it would result with an array that looks like this [4, 1, 0] because those are the indexes in the 1d array that are touching, which have the same value.
Here is the code:
var boardArray = new Array(1,1,0,3,1,3,0,0,0);
var comboArray = new Array();
function floodFill(n, diceVal) {
if(boardArray[n] != diceVal) {
return;
}
comboArray.push(n);
if (n >0 && n < 8) {
// right
if(!(n%3==2)) {
floodFill(n+1, diceVal);
}
// left
if(!(n%3==0)) {
floodFill(n-1, diceVal);
}
// up
if(n>2) {
floodFill(n-3, diceVal);
}
// down
if(n<5) {
floodFill(n+3, diceVal);
}
} else {
return;
}
}
floodFill(4,1);
Can anyone tell me why this is getting stuck in an infinite loop?
In your "up" case, the first time through, you'll call floodFill(1,1);. That call, in its "down" case, will call floodFill(4,1);, which will soon call floodFill(1,1)
You're already keeping track of the matching squares - the only ones that will really cause any trouble. Just confirm that you're not checking the same square again:
function floodFill(n, diceVal) {
if(boardArray[n] != diceVal) {
return;
}
// have we been here before?
if (comboArray.indexOf(n) >= 0)
return;
comboArray.push(n);
// ...
}