diff --git a/src/components/Controls.vue b/src/components/Controls.vue index 567797f..e4dfc9f 100644 --- a/src/components/Controls.vue +++ b/src/components/Controls.vue @@ -75,6 +75,8 @@ min="1" max="8" thumb-label="always" + ticks="always" + tick-size="4" persistent-hint :hint="difficultHint" > @@ -122,9 +124,9 @@ import { Watch } from "vue-property-decorator"; 7: "tougher", 8: "tougher", }; - return `AI will test ${ - this.$parent.manager.minimax.depth - } plays in the future. (${ + return `AI ${ + this.$parent.manager.state.finished ? "was testing" : "will test" + } ${this.$parent.manager.minimax.depth} plays in the future. (${ difficult[this.$parent.manager.minimax.depth] })`; }, diff --git a/src/engine/GameManager.ts b/src/engine/GameManager.ts index 1cb3356..9f2c1a0 100644 --- a/src/engine/GameManager.ts +++ b/src/engine/GameManager.ts @@ -69,11 +69,13 @@ export default class GameManager { }); if (color === "RED") { - const best = this.minimax.getBestMove(); + const move = this.minimax.getBestMove(); + + this.dropping = false; setTimeout(() => { - this.drop(best.move.x); - }, 500); + this.drop(move[0] || (move as unknown as number)); + }, 100); } setTimeout(() => { diff --git a/src/engine/Minimax.ts b/src/engine/Minimax.ts index 96cbced..49eaac1 100644 --- a/src/engine/Minimax.ts +++ b/src/engine/Minimax.ts @@ -11,60 +11,47 @@ export class Minimax { this.depth = 5; } - public getBestMove() { - const { moves, bestScore } = this.getMoves(); - - const bestMoves = moves.filter((m) => m.score === bestScore); - - return bestMoves[(bestMoves.length * Math.random()) | 0]; - } + const [moves, bestScore] = this.getMoves(); - private getMoves(depth: number = this.depth) { - const board = [...this.manager.board.grid]; - let bestScore = -Infinity; - const moves: { score: number; move: GridSlot }[] = []; + const bestMoves = (moves as number[][]).filter( + (move) => move[1] === bestScore + ); - const validMoves = this.manager.board.allValidLocations(board); + if (bestMoves.length === 1) return bestMoves[0]; - for (const { row, col, x, z } of validMoves) { - board[row][col].disc = { - dropped: false, - color: "YELLOW", - id: -1, - x, - z, - }; + const randomBestMove = + bestMoves[Math.floor(Math.random() * bestMoves.length)]; - const score = this.minimax(board, depth, false); + return randomBestMove; + } - board[row][col].disc = null; + public getMoves(depth: number = this.depth) { + const board = [...this.manager.board.grid]; - moves.push({ score, move: { row, col, x, z, disc: null } }); + const moves = this.minimax(board, depth, -Infinity, Infinity, true); - if (score > bestScore) { - bestScore = score; - } - } - - return { moves, bestScore }; + return moves; } private minimax( board: GridSlot[][], depth: number, + alpha: number, + beta: number, playing: boolean - ): number { + ): (number | number[][])[] { const res = this.manager.check(board); if (res.result != null || depth === 0) { - if (res.result === "RED") return -1; - else if (res.result === "YELLOW") return 1; - return 0; + if (res.result === "RED") return [[], -1]; + else if (res.result === "YELLOW") return [[], 1]; + return [[], 0]; } if (playing) { - let bestScore = -Infinity; + const max: (number | number[][])[] = [[], alpha]; const validMoves = this.manager.board.allValidLocations(board); + for (const { row, col, x, z } of validMoves) { board[row][col].disc = { dropped: false, @@ -73,16 +60,21 @@ export class Minimax { x, z, }; - const score = this.minimax(board, depth - 1, false); + const nextMove = this.minimax(board, depth - 1, alpha, beta, false); board[row][col].disc = null; - if (score > bestScore) { - bestScore = score; + (max[0] as (number | number[])[]).push([x, nextMove[1] as number]); + if ((nextMove as number[])[1] > (max as number[])[1]) { + max[1] = nextMove[1]; + alpha = nextMove[1] as number; } + if ((alpha as number) >= (beta as number)) return max; } - return bestScore; - } else if (!playing) { - let bestScore = Infinity; + + return max; + } else { + const min: (number | number[][])[] = [[], beta]; const validMoves = this.manager.board.allValidLocations(board); + for (const { row, col, x, z } of validMoves) { board[row][col].disc = { dropped: false, @@ -91,15 +83,16 @@ export class Minimax { x, z, }; - const score = this.minimax(board, depth - 1, true); + const nextMove = this.minimax(board, depth - 1, alpha, beta, true); board[row][col].disc = null; - if (score < bestScore) { - bestScore = score; + (min[0] as (number | number[])[]).push([x, nextMove[1] as number]); + if ((nextMove as number[])[1] < (min as number[])[1]) { + min[1] = nextMove[1]; + beta = nextMove[1] as number; } } - return bestScore; - } - return 0; + return min; + } } }