I've been working on writing a minimax algorithm for my tictactoe game, but it doesn't seem to be choosing the correct move. It is possible to win against the computer, so I must have something mixed up here.
I based my algorithm off this article (I had to alter my minimax code since my code was different from the example in the article)
// Checking if there are any moves remaining on the board. Returns false if there are no moves left to play
const isMovesLeft = function () {
if (gameBoard._moves === 9) {
return false;
} else {
return true;
}
};
// Evaluates if the maximiser or minimiser has won or draw - returns a number if there is a win or draw
const evaluate = function () {
if (gameBoard._roundWon && gameBoard._currentPlayer === gameBoard._player1)
return +10;
if (gameBoard._roundWon && gameBoard._currentPlayer === gameBoard._player2)
return -10;
if (gameBoard._roundTie) return 0;
};
// Minimax function - considers all the possible ways the game can go and returns the value of the board
const minimax = function (board, depth, isMax) {
let score = evaluate();
// If maximiser has won the game - return evaluated score
if (score === 10) return score;
// If minimiser has won the game - return evaluated score
if (score === -10) return score;
// If there are no moves left and there is no winner - then return as a tie
if (isMovesLeft() === false) return 0;
// If it is the maximiser's turn
if (isMax) {
let bestScore = -1000;
let availableTiles = [];
// Looping through all tiles
tile.forEach((tile, index) => {
// Check if cell is empty
if (tile.textContent === '') {
availableTiles.push(index);
}
});
// Make the move
const chosenEmptyTile =
availableTiles[Math.floor(Math.random() * availableTiles.length)];
renderAITile(tile, chosenEmptyTile);
addPlayerMoves(chosenEmptyTile);
// Calling minimax recursively and choose the maximum value
bestScore = Math.max(bestScore, minimax(board, depth + 1, false));
console.log(bestScore);
// Undo the move
undoAIMove(tile, chosenEmptyTile);
return bestScore;
} else {
let bestScore = 1000;
let availableTiles = [];
// Looping through all tiles
tile.forEach((tile, index) => {
// Check if cell is empty
if (tile.textContent === '') {
availableTiles.push(index);
}
});
// Make the move
const chosenEmptyTile =
availableTiles[Math.floor(Math.random() * availableTiles.length)];
renderAITile(tile, chosenEmptyTile);
addPlayerMoves(chosenEmptyTile);
// Calling minimax recursively and choose the maximum value
bestScore = Math.min(bestScore, minimax(board, depth + 1, false));
// Undo the move
undoAIMove(tile, chosenEmptyTile);
return bestScore;
}
};
const undoAIMove = function (tile, chosenEmptyTile) {
tile[chosenEmptyTile].textContent = '';
tile[chosenEmptyTile].classList.remove(`playerX`);
tile[chosenEmptyTile].classList.remove(`playerO`);
gameBoard._board[chosenEmptyTile] = '';
gameBoard._moves -= 1;
};
// Return the best possible move for the AI
const findBestMove = function (board) {
let bestVal = -1000;
let bestMove = [];
let availableTiles = [];
// Traverse all cells, evaluate minimax function for all empty cells. And return the cell with optimal value.
tile.forEach((tile, index) => {
if (tile.textContent === '') {
availableTiles.push(index);
}
});
// Make the move
console.log(`Empty Tile: ${availableTiles}`);
const chosenEmptyTile =
availableTiles[Math.floor(Math.random() * availableTiles.length)];
console.log(`Chosen tile: ${chosenEmptyTile}`);
renderAITile(tile, chosenEmptyTile);
addPlayerMoves(chosenEmptyTile);
console.log(gameBoard._board);
// Compute evaluation function for this move
let moveVal = minimax(board, 0, false);
// Undo the move
undoAIMove(tile, chosenEmptyTile);
// If value of the current move is more than the best value, then update best
if (moveVal > bestVal) {
bestVal = moveVal;
bestMove.push(chosenEmptyTile);
gameBoard._board[chosenEmptyTile] = 'X';
renderAITile(tile, bestMove);
}
return bestMove;
};
const startGameAI = function () {
if (gameBoard._aiTurn && gameBoard._opponent === 'AI') {
// Finding best move
findBestMove(gameBoard._board);
// Store AI moves in data
addPlayerMoves();
console.log(gameBoard._moves);
// Check if game over or tie
checkGameOver();
// Switch players
switchPlayers();
renderScoreBoard();
}
};
// Renders the tile AI selects
const renderAITile = function (tile, randomEmptyTileIndex) {
if (gameBoard._roundWon || gameBoard._roundTie || !gameBoard._isGameActive)
return;
if (gameBoard._aiTurn) {
tile[randomEmptyTileIndex].textContent = 'X';
tile[randomEmptyTileIndex].classList.add(
`player${gameBoard._currentPlayer}`
);
}
};
Full code on Codepen
Related
My goal is to code a somewhat OK chess engine, in the following position it's a mate in 2 which the engine should easily find with its depth of 4-5.
The first move the AI makes is Ra2 to trap the white king, the white king goes to f1 and instead of mating the AI moves the Rook to c2.
var initial_depth = depth;
var bestMove = null;
var nodes = 0;
var ret = await minimax(position, depth, alpha, beta, maximizingPlayer);
console.log("nodes visited: " + nodes);
return ret;
async function minimax(position, depth, alpha, beta, maximizingPlayer) {
nodes++;
if (maximizingPlayer) {
var validMoves = await getValidMoves(position, ArrtoFEN(position) + " w");
} else {
var validMoves = await getValidMoves(position, ArrtoFEN(position) + " b");
}
if (validMoves.length < 1 || depth == 0) {
var eval = await getEval(position);
return [eval, null];
}
if (maximizingPlayer) {
var maxEval = Number.NEGATIVE_INFINITY;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var testbrd = makeMove(move, position) //not the actual code. shortend for Readability
var eval = await minimax(testbrd, depth - 1, alpha, beta, false);
if (eval[0] > maxEval) {
maxEval = eval[0];
if (initial_depth == depth) {
bestMove = move;
console.log("current bestmove: " + bestMove);
}
}
alpha = Math.max(alpha, eval[0]);
if (beta <= alpha) {
break;
}
}
return [maxEval, bestMove];
} else {
var minEval = Number.POSITIVE_INFINITY;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var testbrd = makeMove(move, position)//not the actual code. shortend for Readability
var eval = await minimax(testbrd, depth - 1, alpha, beta, true);
if (eval[0] < minEval) {
minEval = eval[0];
if (initial_depth == depth) {
bestMove = move;
console.log("current bestmove: " + bestMove);
}
}
beta = Math.min(beta, eval[0]);
if (beta <= alpha) {
break;
}
}
return [minEval, bestMove];
}
}
}
This is because it sees that any move will win, and you don't have a condition that tells the engine that it is better to do mate in 1 move than in 5 moves. If at the end of a search find that you have 0 legal moves and you are in check, then you are checkmated. In this case you want to send back a checkmate score (large negative value) and add the ply from this. This way you will make it better to mate in fewer moves than in a larger amount of moves.
I suggest you go for Negamax alogirthm in stead of minimax. It will mean much less code and much easier to debug.
I am trying to capture the Sound Frequency value from the Device Microphone from a Web Browser, utilizing the Web Audio API. The Instructions only covers how to play a sound file and manipulate its output, ect.
I need to listen to a Humming Sound from a person and figure out the Sound frequency, which I will then convert to a musical note. I need to build an array with all the frequencies that are generated from a Single Musical Note that I play from a digital Piano keyboard. I will then get the mode of all the frequencies to get the actual Musical note.
I am able to display the frequency and notes that I detect onto the Console.log(), however, I am not able to build the array, get the mode, and then clear-out the array when I play a new note. My array remains active because the code is on an event listening mode and remains active to listen for the next note.
This is my code:
var arrayList = [];
function getModes(array) {
var frequency = []; // array of frequency.
var maxFreq = 0; // holds the max frequency.
var modes = [];
for (var i in array) {
frequency[array[i]] = (frequency[array[i]] || 0) + 1; // increment frequency.
if (frequency[array[i]] > maxFreq) { // is this frequency > max so far ?
maxFreq = frequency[array[i]]; // update max.
}
}
for (var k in frequency) {
if (frequency[k] == maxFreq) {
modes.push(k);
}
}
return modes;
}
function updatePitch(time) {
var cycles = new Array;
analyser.getFloatTimeDomainData(buf);
var ac = autoCorrelate(buf, audioContext.sampleRate);
// TODO: Paint confidence meter on canvasElem here.
if (DEBUGCANVAS) { // This draws the current waveform, useful for debugging
waveCanvas.clearRect(0, 0, 512, 256);
waveCanvas.strokeStyle = "red";
waveCanvas.beginPath();
waveCanvas.moveTo(0, 0);
waveCanvas.lineTo(0, 256);
waveCanvas.moveTo(128, 0);
waveCanvas.lineTo(128, 256);
waveCanvas.moveTo(256, 0);
waveCanvas.lineTo(256, 256);
waveCanvas.moveTo(384, 0);
waveCanvas.lineTo(384, 256);
waveCanvas.moveTo(512, 0);
waveCanvas.lineTo(512, 256);
waveCanvas.stroke();
waveCanvas.strokeStyle = "black";
waveCanvas.beginPath();
waveCanvas.moveTo(0, buf[0]);
for (var i = 1; i < 512; i++) {
waveCanvas.lineTo(i, 128 + (buf[i] * 128));
}
waveCanvas.stroke();
}
if (ac == -1) {
detectorElem.className = "vague";
pitchElem.innerText = "--";
noteElem.innerText = "-";
detuneElem.className = "";
detuneAmount.innerText = "--";
} else {
detectorElem.className = "confident";
pitch = ac;
pitchElem.innerText = Math.round(pitch);
var note = noteFromPitch(pitch);
// Here is where I am converting the frequency to a note letter
var noteString = noteStrings[note % 12];
console.log(noteString);
// This is where I am building the array range with the notes that I find
// I have a nice array, but it keeps building and I do not know how to clear it for
// the next session.
if (note >=36 && note <= 96) {
if (arrayList) {
arrayList.push(noteString);
}
console.log(noteString);
}
else {
console.log("not note");
var MyNote = getModes(arrayList)
noteElem.innerHTML = MyNote;
arrayList = [];
}
// This function remains active and continues to listen for the next not to
// generate and new note letter
var detune = centsOffFromPitch(pitch, note);
if (detune == 0) {
detuneElem.className = "";
detuneAmount.innerHTML = "--";
} else {
if (detune < 0)
detuneElem.className = "flat";
else
detuneElem.className = "sharp";
detuneAmount.innerHTML = Math.abs(detune);
}
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = window.webkitRequestAnimationFrame;
rafID = window.requestAnimationFrame(updatePitch);
}
How do I clear the array and use a new array when I play a new note?
thank you for the support...
Tried to make a script like this:
let intervals = [],
isClick = [],
isGameOver = false,
countElement = 3,
count = 0,
gameOver = function () {
if (isGameOver) {
return;
}
isGameOver = true;
if (countElement <= count) {
for (var i = 0; i < intervals.legth; ++i) {
clearInterval(intervals[i]);
}
intervals = [];
countElement = 0;
}
},
elm = function (index) {
return function () {
if (isGameOver || isClick[index]) {
return null;
}
isClick[index] = true;
clearInterval(intervals[index]);
intervals[index] = null;
if (!intervals.filter(a => a).length) {
count = countElement;
gameOver();
return;
}
};
};
for (let i = 0; i < 17; ++i) {
setTimeout(() => {
element.on('pointerup', elm(i));
intervals[i] = setInterval(() => {
if (countElement <= count) {
clearInterval(intervals[i]);
gameOver();
return;
}
if (-64 > element.position.y) {
clearInterval(intervals[i]);
intervals[i] = null;
++count;
} else {
element.position.y -= 30;
}
}, pos.speed);
}, pos.startTime * i);
}
It actually works, but for some reason it doesn't always work as it should.
Perhaps I'll tell you right away what is required ..
It is necessary to generate the required number of elements and move along the axis Y.
They must have different speeds.
I tried to solve it like this:
let rnd = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
}, pos = {
speed: Math.floor(rnd(100, rnd(370, 470))),
startTime: Math.floor(rnd(rnd(370, 470), rnd(700, 1000)))
}
In general, I would like to see the elements start flying with different departure and flight speeds, there were attempts that can be missed if the element flew away, when you click on the element, it stops.
Well, in fact, if all the elements flew out - it doesn't matter if they flew away or stopped, the main thing is that all intervals should stop and there would be a kind of exit ...
Connoisseurs help out how this can be done without third-party libraries?
How to do it correctly, please show.
Problem solved...
There was no need to zero out countElement and intervals in gameOver.
Because of this, subsequent intervals were looped, as the indices were violated.
I have an issue where I have an array containing a deck of cards (['A', 2,3,...'J',...])
I want to be able to pick a number of random cards and then get the total sum of them. for example J,4 should give me the total value of 14.
my current problem is that I can't figure out how to change the strings in the array to a number and
then add those together to get the total sum.
my current code is:
blackjackGame={
'you': 0,
'cards': ['A','2','3','4','5','6','7','8','9','10','J','Q','K'],
'cardsMap' : {'A':1, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, '10':10, 'J':10, 'Q':10, 'K':10},
}
let playerCards = 2
let card = [];
const YOU = blackjackGame['you']
// gives me a random card
function randomCard (){
let rand = Math.floor(Math.random()* 13)
return blackjackGame['cards'][rand];
}
// gives me the two starting cards for the player in an array so I can later add more
function start(){
for(let i= 0; i < playerCards; i++){
card.push(randomCard())
}
return card
}
function totalValue (player){
// this is where i have no idea what to do
// let player = card.reduce(function (a,b){
// return a +b
// }, 0)
// return player += blackjackGame['cardsMap'][card[0]]
}
console.log(start())
console.log(showScore(YOU)) ```
PS. I'm trying to create a blackjack game.
Your reduce code is fine. Just add the reference to blackjackGame.cardsMap to retrieve the value that corresponds to card b.
let sum = card.reduce(function(a, b) {
return a + blackjackGame.cardsMap[b];
}, 0);
Note that you cannot return that value via the argument of the function. Instead let the function return it with a return statement:
return sum;
const blackjackGame={
'you': 0,
'cards': ['A','2','3','4','5','6','7','8','9','10','J','Q','K']
}
let playerCards = 2
let card = [];
const YOU = blackjackGame['you']
function getCardValue(card) {
const v = blackjackGame['cards']
if(v.indexOf(card) === -1){
throw Error("not found")
}
// for each card above index 9 (10), always return 10
return v.indexOf(card) > 9 ? 10 : v.indexOf(card) + 1
}
function randomCard (){
let rand = Math.floor(Math.random()* 13)
return blackjackGame['cards'][rand];
}
function deal(){
for(let i= 0; i < playerCards; i++){
card.push(randomCard())
}
return card
}
function calculateValue (cards){
return cards.reduce(function (total, num){
return total + getCardValue(num)
}, 0)
}
document.getElementById('deal').addEventListener('click',(e) => {
const cards = deal()
console.log(cards)
const playerValue = calculateValue(cards)
YOU = playerValue
console.log(playerValue)
})
<html>
<head>
</head>
<body>
<button id="deal">Deal</button>
<span id=cards />
<span id=total />
</body>
</html>
You need a way to map the face to the value. This will work:
function getValueOfCard( face ) {
var cardOrder =" A234567891JQK";
var faceStart = (""+face).substring(0,1);
return Math.min(10, cardOrder.indexOf(faceStart))
}
If you want to get the values of all your cards, simply iterate over them (faster than reduce, and more easy to read).
Your card only needs the face and color, the other values follow.
card = { color: "spades", face : "King" };
getValueOfCard( card.face );
function totalValue ( playerHand ){
// assuming an array of cards is the player hand
var total = 0;
for ( var card in playerHand ) {
total += getValueOfCard( card.face );
}
return total;
}
I also recommend, that you create all your cards in one go, and then shuffle them, by picking two random numbers and switching these two cards. Do this in a loop for a couple of times, and you have a randomized stack of cards, which means you can actually treat it as a stack.
cardColors = ["♠","♥","♦","♣"];
cardFaces = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'];
// create deck of cards
var stackOfCards = [];
for ( var a = 0; a < cardColors.length; a++ ) {
var curColor = cardColors[a];
for ( var i = 0; i < cardFaces.length; i++) {
var curFace = cardFaces[i];
card = { color : curColor, face : curFace };
stackOfCards.push(card);
}
}
// shuffle the deck
// then you can pop cards from the stack to deal...
function start () {
for (let i = 0; i < playerCards; i++) {
cards.push(randomCard())
}
totalValue(cards)
}
function totalValue (cards) {
cards.forEach((card) => {
blackjackGame.you += blackjackGame.cardsMap[card]
})
}
start()
console.log(blackjackGame.you)
You were on the right track with having a map. You can access objects with a variable by using someObj[yourVar]
I created AI for snake in Python. The game works nicely. I started learning Javascript recently and I tried writting the Javascript equivalent of the game.
The game is played on x*y grid (eg. 30*20). In Python I used (x, y) tuples for game position. In JS I use integers which I map ussing:
function map(x, y) {
return x + y * size.width;
}
function unmap(pos) {
return {x: pos % size.width, y: Math.floor(pos/size.width)};
}
My problem is that the search doesn't work. When I try to create path from it enters infinite loop. The search function is:
function search(start, goal) {
var frontier = new PriorityQueue({
comparator: function(a, b) {
return a.score - b.score;
}
});
frontier.queue({value: start, score: 0});
var cameFrom = {};
cameFrom[start] = null;
while (frontier.length !== 0) {
var current = frontier.dequeue().value;
if (current === goal) {
break;
}
var nbs = neighbors(current);
for(var i = 0; i < nbs.length; i++) {
var next = nbs[i];
if (Object.keys(cameFrom).indexOf(next) === -1) {
var priority = heuristic(goal, next);
frontier.queue({value: next, score: priority});
cameFrom[next] = current;
}
}
}
return cameFrom;
}
I use this priority queue.
The search in Python is more OOP but I don't want to include more code - the question is already long. But I'll include the search:
def search(self, grid, start, goal):
frontier = PriorityQueue()
frontier.put(start, 0)
came_from = {}
came_from[start] = None
while not frontier.empty():
current = frontier.get()
if current == goal:
break
for next in grid.neighbors(current):
if next not in came_from:
priority = self.heuristic(goal, next)
frontier.put(next, priority)
came_from[next] = current
return came_from
If anything more is needed please ask. I'm bad at JS.
Problem was with this line:
if (Object.keys(cameFrom).indexOf(next) === -1) {
I was searching for integer but keys are always strings. You can clearly the behavior in this example:
var foo = {0: "fooBar", 1: "bar"};
console.log("keys", Object.keys(foo));
console.log("int", Object.keys(foo).indexOf(0)); // -1
console.log("string", Object.keys(foo).indexOf("0")); // 0
This is more readable, shorter and works:
if (cameFrom[next] === undefined) {
Thanks to IVlad who pointed out the bad line.