Skip to content

Commit

Permalink
minimax, bfs,
Browse files Browse the repository at this point in the history
  • Loading branch information
Entkenntnis committed Oct 30, 2024
1 parent 20f5a23 commit 3d318e9
Showing 1 changed file with 281 additions and 19 deletions.
300 changes: 281 additions & 19 deletions public/worms/wormer.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class Wormer {
const newRedDir = this.actRed(
74,
42,
this.board,
JSON.parse(JSON.stringify(this.board)),
this.redX,
this.redY,
this.redDir,
Expand Down Expand Up @@ -159,7 +159,7 @@ class Wormer {
const newGreenDir = this.actGreen(
74,
42,
this.board,
JSON.parse(JSON.stringify(this.board)),
this.greenX,
this.greenY,
this.greenDir,
Expand Down Expand Up @@ -238,26 +238,288 @@ function createDemoBot() {
[0, 1],
[-1, 0],
]

function AI(dx, dy, board, x, y, dir, enemyX, enemyY) {
// where would we be going with current direction?
let nx = x + offsets[dir][0]
let ny = y + offsets[dir][1]
if (nx < 0 || ny < 0 || nx >= dx || ny >= dy || board[nx][ny] != 0) {
// oops, that would be hitting a wall!
// let's try to turn -- choose a new direction
if (Math.random() < 0.5) dir = (dir + 1) % 4
else dir = (dir + 3) % 4
let cnt = 0
// check this new direction is safe -- if not, try others
while (++cnt < 4) {
nx = x + offsets[dir][0]
ny = y + offsets[dir][1]
if (nx >= 0 && ny >= 0 && nx < dx && ny < dy && board[nx][ny] == 0)
break
dir = (dir + 1) % 4
console.time('demo bot')

function evaluatePosition(position) {
const { board, myX, myY, redX, redY } = position
const myDistances = JSON.parse(JSON.stringify(board)) // make copy because I will override it
const redDistances = JSON.parse(JSON.stringify(board)) // make copy because I will override it

function bfs(board, x, y) {
const queue = [{ x, y, dist: -2 }]
let reachableCells = 0
// Start flood-fill using BFS
while (queue.length > 0) {
const { x, y, dist } = queue.shift()

// Skip if the cell is out of bounds or blocked
if (board[x][y] !== 0 && dist < -2) continue

// Mark the cell with the current distance
board[x][y] = dist
reachableCells++

// Enqueue all neighboring cells with incremented distance
for (const [dx, dy] of offsets) {
queue.push({ x: x + dx, y: y + dy, dist: dist - 1 })
}
}
return reachableCells
}

const myCells = bfs(myDistances, myX, myY)
const redCells = bfs(redDistances, redX, redY)

// console.log(myDistances, redDistances)

let meCloser = 0
let redCloser = 0
let tie = 0

for (let x = 0; x < dx; x++) {
for (let y = 0; y < dy; y++) {
if (myDistances[x][y] < -2 && redDistances[x][y] < -2) {
if (myDistances[x][y] == redDistances[x][y]) {
tie++
} else if (
Math.abs(myDistances[x][y]) > Math.abs(redDistances[x][y])
) {
redCloser++
} else {
meCloser++
}
} else if (myDistances[x][y] < -2) {
meCloser++
} else if (redDistances[x][y]) {
redCloser++
}
}
}

//console.log(meCloser, redCloser, tie)

// return 0

/*let equalSpace = false
function countConnectedZeros(board, x, y, countForRed) {
if (countForRed) {
if (x == myX && y == myY) {
equalSpace = true
return 0
}
}
if (!countForRed) {
if (x == redX && y == redY) {
equalSpace = true
return 0
}
}
// Base case: if out of bounds or not on a free cell (0), return 0.
if (board[x][y] !== 0) {
return 0
}
// Temporarily mark the cell as visited by setting it to a non-zero value.
board[x][y] = -1
// Count this cell, and recursively count all 4 adjacent cells.
return (
1 +
countConnectedZeros(board, x + 1, y, countForRed) + // Down
countConnectedZeros(board, x - 1, y, countForRed) + // Up
countConnectedZeros(board, x, y + 1, countForRed) + // Right
countConnectedZeros(board, x, y - 1, countForRed)
) // Left
}
const mySpace =
countConnectedZeros(floodFillBoard, myX + 1, myY, false) + // Down
countConnectedZeros(floodFillBoard, myX - 1, myY, false) + // Up
countConnectedZeros(floodFillBoard, myX, myY + 1, false) + // Right
countConnectedZeros(floodFillBoard, myX, myY - 1, false)
let redSpace = mySpace
if (!equalSpace) {
redSpace =
countConnectedZeros(floodFillBoard, redX + 1, redY, true) + // Down
countConnectedZeros(floodFillBoard, redX - 1, redY, true) + // Up
countConnectedZeros(floodFillBoard, redX, redY + 1, true) + // Right
countConnectedZeros(floodFillBoard, redX, redY - 1, true)
}*/

const spaceADV = meCloser - redCloser

// tie breaker
//let tieBreaker = position.myLastDir == position.myPreviousDir ? 0.1 : 0

return spaceADV //+ tieBreaker
}

function availableMoves(board, x, y) {
return [0, 1, 2, 3].filter(
(dir) => board[x + offsets[dir][0]][y + offsets[dir][1]] == 0
)
}

function minimax(position, depth, isMyTurn, top) {
if (depth == 0) {
return [evaluatePosition(position), -1]
}
if (isMyTurn) {
const dirs = availableMoves(position.board, position.myX, position.myY)
if (dirs.length == 0) {
return [-Infinity, -1]
}
const evals = dirs.map((dir) => {
const newPosition = JSON.parse(JSON.stringify(position))
const nx = position.myX + offsets[dir][0]
const ny = position.myY + offsets[dir][1]
newPosition.board[nx][ny] = 2
newPosition.myX = nx
newPosition.myY = ny
newPosition.myPreviousDir = position.myLastDir
newPosition.myLastDir = dir
return [
minimax(newPosition, depth - 1, !isMyTurn)[0] +
(top && position.myLastDir == dir ? 0.1 : 0),
dir,
] // only care about eval
})
evals.sort((a, b) => b[0] - a[0])

// console.log('evals of red', evals)
return evals[0]
} else {
const dirs = availableMoves(
position.board,
position.redX,
position.redY
)
if (dirs.length == 0) {
return [Infinity, -1]
}
const evals = dirs.map((dir) => {
const newPosition = JSON.parse(JSON.stringify(position))
const nx = position.redX + offsets[dir][0]
const ny = position.redY + offsets[dir][1]
newPosition.board[nx][ny] = 1
newPosition.redX = nx
newPosition.redY = ny
return [minimax(newPosition, depth - 1, !isMyTurn)[0], dir] // only care about eval
})
evals.sort((a, b) => a[0] - b[0])
// console.log('evals of green', evals)
return evals[0]
}
}
return dir

const bestMove = minimax(
{ board, myX: x, myY: y, redX: enemyX, redY: enemyY, myLastDir: dir },
3,
true,
true
)
console.log(bestMove[0])

/*function countConnectedZeros(board, x, y) {
// Base case: if out of bounds or not on a free cell (0), return 0.
if (board[x][y] !== 0) {
return 0
}
// Temporarily mark the cell as visited by setting it to a non-zero value.
board[x][y] = -1
// Count this cell, and recursively count all 4 adjacent cells.
return (
1 +
countConnectedZeros(board, x + 1, y) + // Down
countConnectedZeros(board, x - 1, y) + // Up
countConnectedZeros(board, x, y + 1) + // Right
countConnectedZeros(board, x, y - 1)
) // Left
}*/

/*function countConnectedZerosAround(board, x, y) {
if (board[x][y] < 0) return 0 // wall
return (
countConnectedZeros(board, x + 1, y) + // Down
countConnectedZeros(board, x - 1, y) + // Up
countConnectedZeros(board, x, y + 1) + // Right
countConnectedZeros(board, x, y - 1)
) // Left
}*/

/*function findBestForGreen(board, x, y, greenLastDir) {
const counts = [0, 1, 2, 3].map((i) => {
const boardCopy = JSON.parse(JSON.stringify(board))
const nx = x + offsets[i][0]
const ny = y + offsets[i][1]
if (boardCopy[nx][ny] != 0) {
return -Infinity
} else {
boardCopy[nx][ny] = 2 // mark
return evaluatePosition(boardCopy, nx, ny)
}
})
console.log(counts)
const best = Math.max(...counts)
const bestDir = counts[dir] == best ? dir : counts.indexOf(best)
return [bestDir, best]
}
function fromRedPerspective(board, x, y) {
const counts = [0, 1, 2, 3].map((i) => {
const boardCopy = JSON.parse(JSON.stringify(board))
const nx = x + offsets[i][0]
const ny = y + offsets[i][1]
if (boardCopy[nx][ny] != 0) {
return Infinity
} else {
boardCopy[nx][ny] = 3 // mark
return evaluatePosition(boardCopy, nx, ny)
}
})
console.log(counts)
const best = Math.min(...counts)
const bestDir = counts[dir] == best ? dir : counts.indexOf(best)
}
const [newDir, best] = findBestForGreen(board, x, y, dir)
console.log(dir, best)
dir = newDir
/*const frontFree = countConnectedZeros(
JSON.parse(JSON.stringify(board)),
x + offsets[dir][0],
y + offsets[dir][1]
)
const leftFree = countConnectedZeros(
JSON.parse(JSON.stringify(board)),
x + offsets[(dir + 3) % 4][0],
y + offsets[(dir + 3) % 4][1]
)
const rightFree = countConnectedZeros(
JSON.parse(JSON.stringify(board)),
x + offsets[(dir + 1) % 4][0],
y + offsets[(dir + 1) % 4][1]
)
const best = Math.max(frontFree, leftFree, rightFree)
if (frontFree !== best) {
if (best == leftFree) {
dir = (dir + 3) % 4
} else if (best == rightFree) {
dir = (dir + 1) % 4
}
}*/

console.timeEnd('demo bot')
return bestMove[1]
}

return AI
Expand Down

0 comments on commit 3d318e9

Please sign in to comment.