Skip to content

Commit

Permalink
feat: init debug ai
Browse files Browse the repository at this point in the history
  • Loading branch information
kenrick95 committed Apr 21, 2023
1 parent 4ab0154 commit cb379e2
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 21 deletions.
119 changes: 119 additions & 0 deletions core/src/__tests__/__snapshots__/humanVsAi.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`PlayerHuman vs PlayerAi > Issue #3 1`] = `
[
[
0,
0,
0,
0,
0,
0,
0,
],
[
0,
0,
0,
1,
0,
0,
0,
],
[
0,
0,
0,
1,
0,
1,
0,
],
[
0,
0,
0,
2,
1,
2,
1,
],
[
0,
0,
0,
1,
2,
2,
2,
],
[
0,
0,
0,
1,
2,
2,
1,
],
]
`;

exports[`PlayerHuman vs PlayerAi > Issue #3 2`] = `
[
[
0,
0,
0,
0,
0,
0,
0,
],
[
0,
0,
0,
1,
0,
0,
0,
],
[
0,
0,
0,
1,
0,
1,
2,
],
[
0,
0,
0,
2,
1,
2,
1,
],
[
0,
0,
0,
1,
2,
2,
2,
],
[
0,
0,
0,
1,
2,
2,
1,
],
]
`;
98 changes: 98 additions & 0 deletions core/src/__tests__/humanVsAi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { TestGame } from '../__testHelpers/test-game'
import { BoardPiece, BoardBase } from '../board'
import { TestPlayer } from '../__testHelpers/test-player'
import { clone } from '../utils'
import { describe, test, expect } from 'vitest'
import { PlayerAi } from '../player'

describe('PlayerHuman vs PlayerAi', () => {
const testPlayer = new TestPlayer(BoardPiece.PLAYER_1)
const aiPlayer = new PlayerAi(BoardPiece.PLAYER_2)
const players = [testPlayer, aiPlayer]
test('Issue #3', async () => {
const board = new BoardBase()
const game = new TestGame(players, board)

/*
Next move is Player 2 (AI)
Current board:
0 0 0 0 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 1 0
0 0 0 2 1 2 1
0 0 0 1 2 2 2
0 0 0 1 2 2 1
Should probably choose column 2 to block immediate Player 1 from winning
*/
board.map = [
[
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
],
[
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.PLAYER_1,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
],
[
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.PLAYER_1,
BoardPiece.EMPTY,
BoardPiece.PLAYER_1,
BoardPiece.EMPTY,
],
[
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.PLAYER_2,
BoardPiece.PLAYER_1,
BoardPiece.PLAYER_2,
BoardPiece.PLAYER_1,
],
[
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.PLAYER_1,
BoardPiece.PLAYER_2,
BoardPiece.PLAYER_2,
BoardPiece.PLAYER_2,
],
[
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.EMPTY,
BoardPiece.PLAYER_1,
BoardPiece.PLAYER_2,
BoardPiece.PLAYER_2,
BoardPiece.PLAYER_1,
],
]

board.debug()
expect(board.map).toMatchSnapshot()
game.currentPlayerId = 1
game.start()
board.debug()
console.log()
await game.afterMovePromise

expect(board.map).toMatchSnapshot()
board.debug()
})
})
32 changes: 15 additions & 17 deletions core/src/player/player-ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ export class PlayerAi extends Player {
): number {
const isWon = winnerBoardPiece === this.boardPiece
const isLost = winnerBoardPiece === this.enemyBoardPiece

// value is slightly higher than BIG_NEGATIVE_NUMBER & lower than BIG_POSITIVE_NUMBER
// so that minState(...) and maxState(...) could "catch"" this value and AI take this move
// This is just my hypothesis, I haven't tested without it yet.
// My point is that this AI implementation is basically a heuristic function :P
if (isWon) {
returnValue = BIG_POSITIVE_NUMBER - 100
} else if (isLost) {
Expand Down Expand Up @@ -169,16 +164,17 @@ export class PlayerAi extends Player {
}

// alpha-beta pruning
if (value > beta) {
return {
value: value,
move: choose(moveQueue),
}
}
// if (value > beta) {
// return {
// value: value,
// move: choose(moveQueue),
// }
// }
alpha = Math.max(alpha, value)
}
}

console.log('[maxState] depth', depth, value, moveQueue)
return {
value: value,
move: choose(moveQueue),
Expand Down Expand Up @@ -211,15 +207,17 @@ export class PlayerAi extends Player {
}

// alpha-beta pruning
if (value < alpha) {
return {
value: value,
move: choose(moveQueue),
}
}
// if (value < alpha) {
// return {
// value: value,
// move: choose(moveQueue),
// }
// }
beta = Math.min(beta, value)
}
}

console.log('[minState] depth', depth, value, moveQueue)
return {
value: value,
move: choose(moveQueue),
Expand Down
1 change: 1 addition & 0 deletions core/src/player/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BoardBase, BoardPiece } from '../board'

export abstract class Player {
boardPiece: BoardPiece
/** @return {number} column number (0-index) */
abstract getAction(board: BoardBase): Promise<number>
constructor(boardPiece: BoardPiece) {
this.boardPiece = boardPiece
Expand Down
8 changes: 4 additions & 4 deletions core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ export function getRandomColumnNumber(): number {
return Math.floor(Math.random() * BoardBase.COLUMNS)
}

export function choose(choice: Array<any>): any {
export function choose<T>(choice: Array<T>): T {
return choice[Math.floor(Math.random() * choice.length)]
}

export function clone(array: Array<Array<any>>): Array<Array<any>> {
const arr: Array<Array<any>> = []
export function clone<T>(array: Array<Array<T>>): Array<Array<T>> {
const arr: Array<Array<T>> = []

for (let i: number = 0; i < array.length; i++) arr[i] = array[i].slice()

Expand All @@ -57,7 +57,7 @@ export function getMockPlayerAction(
success: boolean
map: Array<Array<number>>
} {
const clonedMap: Array<Array<any>> = clone(map)
const clonedMap: Array<Array<number>> = clone(map)

if (
clonedMap[0][column] !== BoardPiece.EMPTY ||
Expand Down

0 comments on commit cb379e2

Please sign in to comment.