From f74396f257264431e7a1f0b0cdd34759b30bb1df Mon Sep 17 00:00:00 2001 From: yukirij Date: Mon, 19 Aug 2024 19:03:50 -0700 Subject: [PATCH] Add simple auto move to practice. --- www/js/game.js | 10 +-- www/js/interface.js | 172 +++++++++++++++++++++++++++++++++++--------- www/js/scene.js | 1 + 3 files changed, 144 insertions(+), 39 deletions(-) diff --git a/www/js/game.js b/www/js/game.js index 4061927..d94d90a 100644 --- a/www/js/game.js +++ b/www/js/game.js @@ -102,14 +102,14 @@ GAME.Tile = class { constructor(index) { this.piece = null; - this.threaten = [false, false]; + this.threaten = [0, 0]; this.checking = false; this.hex = HEX.tile_to_hex(index); } reset() { - this.threaten = [false, false]; + this.threaten = [0, 0]; this.checking = false; } }; @@ -236,7 +236,7 @@ GAME.Game = class { // Get threatened tiles. for(let movement of this.movement_tiles(piece, piece.tile)) { if(movement.threat) { - this.board.tiles[movement.tile].threaten[piece.player] = true; + this.board.tiles[movement.tile].threaten[piece.player] += 1; } if(movement.check) { this.board.tiles[piece.tile].checking = true; @@ -408,7 +408,7 @@ GAME.Game = class { } // King may not move onto threatened tile. - if(piece.piece == GAME.Const.PieceId.Omen && tile_data.threaten[+(!piece.player)]) { + if(piece.piece == GAME.Const.PieceId.Omen && tile_data.threaten[+(!piece.player)] > 0) { result = false; } @@ -490,7 +490,7 @@ GAME.Game = class { // King cannot swap onto tile that is threatened. // This case should also cover blocking. if(target.piece == GAME.Const.PieceId.Omen - && (this.board.tiles[tile].threaten[+(!target.player)] || piece.blocking != 0)) { + && (this.board.tiles[tile].threaten[+(!target.player)] > 0 || piece.blocking != 0)) { return false; } return ((moves.direction & mask) != 0 && (range == 1 || (moves.stride & mask) != 0)); diff --git a/www/js/interface.js b/www/js/interface.js index 88c4cc8..b662bd9 100644 --- a/www/js/interface.js +++ b/www/js/interface.js @@ -87,7 +87,7 @@ const INTERFACE = { if(movement.valid) { // Show valid/threat hints if piece belongs to player and is player turn. if(INTERFACE_DATA.player == 2 || (player == INTERFACE_DATA.player && (GAME_DATA.turn & 1) == player)) { - if(GAME_DATA.board.tiles[movement.tile].threaten[+(!player)]) { + if(GAME_DATA.board.tiles[movement.tile].threaten[+(!player)] > 0) { INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Threat; } else { INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Valid; @@ -200,39 +200,8 @@ const INTERFACE = { // Handle player action. if(is_valid) { - - // Send message to server for online game. - switch(INTERFACE_DATA.mode) { - - // Apply action and change turn for local game. - case INTERFACE.Mode.Local: { - let play = new GAME.Play(INTERFACE_DATA.select.source, INTERFACE_DATA.select.tile, INTERFACE_DATA.hover.tile); - INTERFACE_DATA.play = play; - GAME_DATA.process(play); - - INTERFACE_DATA.player = +(!INTERFACE_DATA.player); - INTERFACE_DATA.rotate = +(!INTERFACE_DATA.rotate); - - INTERFACE.draw(); - } break; - - // Send action to server for validation. - case INTERFACE.Mode.Online: { - let move_data = INTERFACE_DATA.select.source | (INTERFACE_DATA.select.tile << 1) | (INTERFACE_DATA.hover.tile << 7); - MESSAGE_COMPOSE([ - PACK.u16(OpCode.GamePlay), - PACK.u16(0), - PACK.u16(GAME_DATA.turn), - PACK.u16(move_data), - ]); - } break; - - // Branch into local game from here. - case INTERFACE.Mode.Replay: { - - } break; - } - + let play = new GAME.Play(INTERFACE_DATA.select.source, INTERFACE_DATA.select.tile, INTERFACE_DATA.hover.tile); + INTERFACE.process(play); INTERFACE_DATA.select = null; } @@ -860,6 +829,38 @@ const INTERFACE = { } }, + process(play) { + // Send message to server for online game. + switch(INTERFACE_DATA.mode) { + // Apply action and change turn for local game. + case INTERFACE.Mode.Local: { + INTERFACE_DATA.play = play; + GAME_DATA.process(play); + + INTERFACE_DATA.player = +(!INTERFACE_DATA.player); + INTERFACE_DATA.rotate = +(!INTERFACE_DATA.rotate); + + INTERFACE.draw(); + } break; + + // Send action to server for validation. + case INTERFACE.Mode.Online: { + let move_data = play.source | (play.from << 1) | (play.to << 7); + MESSAGE_COMPOSE([ + PACK.u16(OpCode.GamePlay), + PACK.u16(0), + PACK.u16(GAME_DATA.turn), + PACK.u16(move_data), + ]); + } break; + + // Branch into local game from here. + case INTERFACE.Mode.Replay: { + + } break; + } + }, + rotate() { INTERFACE_DATA.rotate ^= 1; INTERFACE.draw(); @@ -909,6 +910,109 @@ const INTERFACE = { } } }, + + auto() { + let moves = [ ]; + let player = GAME_DATA.turn & 1; + let opponent = +(!player); + + // Get available placement moves. + for(let p = 0; p < 8; ++p) { + if(GAME_DATA.pools[player].pieces[p] > 0) { + for(let move of GAME_DATA.placement_tiles(p, player)) { + if(move.valid) { + let hex = HEX.tile_to_hex(move.tile); + + let score = GAME_DATA.board.tiles[move.tile].threaten[player] - GAME_DATA.board.tiles[move.tile].threaten[opponent]; + if(player == 0) { + score += 2 * Math.max(0, GAME_DATA.board.columns[hex.x].extent[player] - hex.y); + } else { + score += 2 * Math.max(0, hex.y - GAME_DATA.board.columns[hex.x].extent[player]); + } + + let moves_from = GAME_DATA.movement_tiles(new GAME.Piece(p, player), move.tile); + for(let next_move of moves_from) { + if(next_move.valid) { + // Add score for taking. + let target_id = GAME_DATA.board.tiles[next_move.tile].piece; + if(target_id !== null) { + let target = GAME_DATA.board.pieces[target_id]; + if(target.player == opponent) { score += 1 + target.piece; } + } + + if(next_move.check) { score += 2; } + } + } + + moves.push({ + score: score, + play: new GAME.Play(1, p, move.tile), + }); + } + } + } + } + + // Get available piece moves. + for(let i = 0; i < GAME_DATA.board.pieces.length; ++i) { + let piece = GAME_DATA.board.pieces[i]; + if(piece !== null) { + if(piece.player == player) { + let current_hex = HEX.tile_to_hex(piece.tile); + + for(let move of GAME_DATA.movement_tiles(piece, piece.tile)) { + if(move.valid) { + let hex = HEX.tile_to_hex(move.tile); + + // Calculate base score. + let score = ((piece.piece + 1) * GAME_DATA.board.tiles[piece.tile].threaten[opponent]) + + GAME_DATA.board.tiles[move.tile].threaten[player] + - ((piece.piece + 1) * GAME_DATA.board.tiles[move.tile].threaten[opponent]); + + // Add score for taking. + let target_id = GAME_DATA.board.tiles[move.tile].piece; + if(target_id !== null) { + let target = GAME_DATA.board.pieces[target_id]; + if(target.player == opponent) { score += 2 * (1 + target.piece); } + else { score -= 1; } + } + + score += +((-MATH.sign_branch(player) * (hex.y - current_hex.y)) > 0); + + // Add score for check. + if(move.check) { score += 2; } + + let moves_from = GAME_DATA.movement_tiles(piece, move.tile); + for(let next_move of moves_from) { + if(next_move.valid) { + // Add score for taking. + let target_id = GAME_DATA.board.tiles[next_move.tile].piece; + if(target_id !== null) { + let target = GAME_DATA.board.pieces[target_id]; + if(target.player == opponent) { score += 1 + target.piece; } + } + + if(next_move.check) { score += 2; } + } + } + + moves.push({ + score: score, + play: new GAME.Play(0, piece.tile, move.tile), + }); + } + } + } + } + } + + if(moves.length > 0) { + moves = moves.sort((a, b) => { return b.score - a.score; }); + + let select = Math.floor(Math.random() * Math.min(moves.length, 3)); + INTERFACE.process(moves[select].play); + } + }, }; INTERFACE.Radius = 2.0 / Math.sqrt(3.0); diff --git a/www/js/scene.js b/www/js/scene.js index ce86557..c785467 100644 --- a/www/js/scene.js +++ b/www/js/scene.js @@ -564,6 +564,7 @@ const SCENES = { UI.nav([ UI.button("Rotate", () => { INTERFACE.rotate(); }), UI.button("Mirror", () => { INTERFACE.mirror(); }), + UI.button("Auto", () => { INTERFACE.auto(); }), ], buttons_bottom); let canvas = document.createElement("canvas");