From 51f8e9aefed01f36127246b15563d7e9cc1801b8 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Mon, 5 Jun 2023 13:31:15 +0200 Subject: All in one. --- rules.js | 395 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 340 insertions(+), 55 deletions(-) (limited to 'rules.js') diff --git a/rules.js b/rules.js index e6461e1..c809044 100644 --- a/rules.js +++ b/rules.js @@ -3,9 +3,9 @@ const RED = "Red" const BLUE = "Blue" -const states = {} - -var game, view +var states = {} +var game = null +var view = null exports.scenarios = [ "Standard" ] @@ -17,23 +17,31 @@ const TOKEN_RED_2 = 2 const TOKEN_BLUE_1 = 3 const TOKEN_BLUE_2 = 4 +const FIRST_SPACE = 0 const S_DARKNESS = 0 const S_GOLD = 1 const S_BLUE = 2 const S_WHITE = 3 const S_RED = 4 const S_PURPLE = 5 -const S_DRAGON = 6 -const S_OFF_BOARD = 7 +const LAST_TILE_SPACE = 5 +const S_DRAGON_1 = 6 +const LAST_OVAL_SPACE = 6 + +const S_DRAGON_2 = 7 +const S_OFF_BOARD_1 = 8 +const S_OFF_BOARD_2 = 9 +const S_OFF_BOARD_3 = 10 +const S_OFF_BOARD_4 = 11 -const TILE_NONE = 0 const FIRST_TILE = 1 +const TILE_NONE = 0 const TILE_BLUE = 1 const TILE_RED = 13 const TILE_GOLD = 25 const TILE_WHITE = 37 const TILE_GREEN = 49 -const LAST_TILE = 55 +const LAST_TILE = 54 function gen_action(action, argument) { if (!(action in view.actions)) @@ -58,22 +66,13 @@ exports.action = function (state, player, action, arg) { let S = states[game.state] if (action in S) S[action](arg, player) + else if (action === "undo" && game.undo && game.undo.length > 0) + pop_undo() else throw new Error("Invalid action: " + action) return game } -exports.resign = function (state, player) { - game = state - if (game.state !== 'game_over') { - if (player === RED) - goto_game_over(BLUE, "Red resigned.") - if (player === BLUE) - goto_game_over(RED, "Blue resigned.") - } - return game -} - exports.view = function(state, player) { game = state @@ -114,6 +113,37 @@ exports.view = function(state, player) { return view; } +exports.resign = function (state, player) { + game = state + if (game.state !== 'game_over') { + if (player === RED) + goto_game_over(BLUE, "Red resigned.") + if (player === BLUE) + goto_game_over(RED, "Blue resigned.") + } + return game +} + +function goto_game_over(result, victory) { + game.state = "game_over" + game.active = "None" + game.result = result + game.victory = victory + log_br() + log(game.victory) +} + +states.game_over = { + get inactive() { + return game.victory + }, + prompt() { + view.prompt = game.victory + }, +} + +// === PREPARATION === + exports.setup = function (seed, scenario, options) { game = { seed: seed, @@ -123,8 +153,8 @@ exports.setup = function (seed, scenario, options) { red_score: 0, blue_score: 0, - tokens: [ S_DRAGON, S_OFF_BOARD, S_OFF_BOARD, S_OFF_BOARD, S_OFF_BOARD ], - squares: [ 0, 0, 0, 0, 0, 0 ], + tokens: [ S_DRAGON_2, S_OFF_BOARD_1, S_OFF_BOARD_2, S_OFF_BOARD_3, S_OFF_BOARD_4 ], + squares: [ TILE_NONE, TILE_NONE, TILE_NONE, TILE_NONE, TILE_NONE, TILE_NONE ], darkness: [], red_court: [], blue_court: [], @@ -148,7 +178,7 @@ exports.setup = function (seed, scenario, options) { shuffle(game.darkness) - for (let i = 0; i < 6; ++i) + for (let i = FIRST_SPACE; i <= LAST_TILE_SPACE; ++i) game.squares[i] = game.darkness.pop() if (random(2) === 0) @@ -161,24 +191,112 @@ exports.setup = function (seed, scenario, options) { return game } +// === HANDS AND COURTS === + +function rival_court() { + if (game.active === RED) + return game.blue_court + return game.red_court +} + +function own_court() { + if (game.active === RED) + return game.red_court + return game.blue_court +} + +function own_hand() { + if (game.active === RED) + return game.red_hand + return game.blue_hand +} + +function own_score() { + if (game.active === RED) + return game.red_score + return game.blue_score +} + +function gift_tile_in_space(to) { + // Gift associated tile to rival. + if (to <= LAST_TILE_SPACE) { + let tile = game.squares[to] + log("Gifted " + tile) + game.squares[to] = TILE_NONE + rival_court().push(tile) + } +} + +function score_own_points(n) { + log("Scored " + n) + if (game.active === RED) + game.red_score += n + if (game.active === BLUE) + game.blue_score += n + return (game.red_score >= 30) || (game.blue_score >= 30) +} + +function count_tiles(list, type) { + let n = 0 + for (let tile of list) + if (tile >= type && tile < type + 12) + ++n + return n +} + +function reveal_tiles_into_court(type) { + let hand = own_hand() + let court = own_court() + for (let i = 0; i < hand.length;) { + let tile = hand[i] + if (tile >= type && tile < type + 12) { + logi("Revealed " + tile) + array_remove(hand, i) + court.push(tile) + } else { + ++i + } + } +} + +function remove_tiles_from_court(type) { + let court = own_court() + for (let i = 0; i < court.length;) { + let tile = court[i] + if (tile >= type && tile < type + 12) { + logi("Removed " + tile) + array_remove(court, i) + } else { + ++i + } + } +} + +function is_oval_space_empty(s) { + for (let i = FIRST_SPACE; i <= LAST_OVAL_SPACE; ++i) + if (game.tokens[i] === s) + return false + return true +} + // === FLOW OF PLAY === states.move_token = { prompt() { view.prompt = "Move one of your tokens to an oval space." if (game.active === RED) { - if (game.tokens[TOKEN_RED_1] === S_OFF_BOARD) { + if (game.tokens[TOKEN_RED_1] >= S_OFF_BOARD_1) { gen_action_token(TOKEN_RED_1) - } else if (game.tokens[TOKEN_RED_2] === S_OFF_BOARD) { + } else if (game.tokens[TOKEN_RED_2] >= S_OFF_BOARD_1) { gen_action_token(TOKEN_RED_2) } else { gen_action_token(TOKEN_RED_1) gen_action_token(TOKEN_RED_2) } } else { - if (game.tokens[TOKEN_BLUE_1] === S_OFF_BOARD) { + if (game.tokens[TOKEN_BLUE_1] >= S_OFF_BOARD_1) { gen_action_token(TOKEN_BLUE_1) - } else if (game.tokens[TOKEN_BLUE_2] === S_OFF_BOARD) { + } else if (game.tokens[TOKEN_BLUE_2] >= S_OFF_BOARD_1) { gen_action_token(TOKEN_BLUE_2) } else { gen_action_token(TOKEN_BLUE_1) @@ -193,65 +311,232 @@ states.move_token = { }, } -function is_oval_space_empty(s) { - for (let i = 0; i < 5; ++i) - if (game.tokens[i] === s) - return false - return true -} - states.move_token_to = { prompt() { view.prompt = "Move your token to an oval space." view.selected_token = game.selected_token - for (let i = 0; i < 7; ++i) + for (let i = FIRST_SPACE; i <= LAST_OVAL_SPACE; ++i) if (is_oval_space_empty(i)) gen_action_space(i) + gen_action_token(game.selected_token) + }, + space(to) { + // Remember whence we came. + game.from = game.tokens[game.selected_token] + + log_h2(game.active + " MOVED TO " + to) + + // Move the token. + game.tokens[game.selected_token] = to + + // Gift associated tile to rival. + gift_tile_in_space(to) + + // Take action. + switch (to) { + case S_DRAGON_1: + return goto_dragon() + case S_DARKNESS: + return goto_secrecy() + case S_GOLD: + return goto_cloth_of_gold() + case S_BLUE: + return goto_banquets_and_feasts() + case S_WHITE: + return goto_godliness_and_piety() + case S_RED: + return goto_tournaments() + case S_PURPLE: + return goto_purple() + } + }, + token(t) { + pop_undo() }, - space(space) { - game.tokens[game.selected_token] = space - // take action! +} + +function end_turn() { + clear_undo() + + // Return Dragon if needed. + if (is_oval_space_empty(S_DRAGON_1)) + game.tokens[TOKEN_DRAGON] = S_DRAGON_2 + + // Refill Tiles if needed. + for (let i = FIRST_SPACE; i <= LAST_TILE_SPACE; ++i) { + if (is_oval_space_empty(i) && game.squares[i] === 0) { + let tile = game.darkness.pop() + game.squares[i] = tile + if (game.darkness.length === 0) + return goto_end_of_the_contest() + } } + + // Play passes to the rival player. + if (game.active === RED) + game.active = BLUE + else + game.active = RED + game.state = "move_token" } // === THE ACTIONS: DRAGON === + +function goto_dragon() { + log("Dragon") + game.state = "dragon" +} + +states.dragon = { + prompt() { + view.selected_token = TOKEN_DRAGON + for (let i = FIRST_SPACE; i <= LAST_OVAL_SPACE; ++i) + if (i !== game.from && is_oval_space_empty(i)) + gen_action_space(i) + }, + space(to) { + game.tokens[TOKEN_DRAGON] = to + gift_tile_in_space(to) + end_turn() + }, +} + // === THE ACTIONS: SECRECY === + +const SECRECY_PER_ROW = [ 1, 2, 2, 3, 0 ] + +function gain_tile_from_darkness() { + let tile = game.darkness.pop() + own_hand().push(tile) + return game.darkness.length === 0 +} + +function goto_secrecy() { + log("Secrecy") + let score = own_score() + let row = (score >> 8) + let n = SECRECY_PER_ROW[row] + for (let i = 0; i < n; ++i) { + log("Gained Tile from Darkness.") + if (gain_tile_from_darkness()) { + return goto_end_of_the_contest() + } + } + end_turn() +} + // === THE ACTIONS: CLOTH OF GOLD === + +function goto_cloth_of_gold() { + log("Cloth of Gold") + reveal_tiles_into_court(TILE_GOLD) + let own = count_tiles(own_court(), TILE_GOLD) + let rival = count_tiles(rival_court(), TILE_GOLD) + if (own > rival) { + if (score_own_points(2)) + return goto_end_of_the_contest() + } + end_turn() +} + // === THE ACTIONS: BANQUETS AND FEASTS === + +function goto_banquets_and_feasts() { + log("Banquets & Feasts") + reveal_tiles_into_court(TILE_BLUE) + let own = count_tiles(own_court(), TILE_BLUE) + let score = 0 + if (own === 1) + score = 1 + else if (own === 2) + score = 3 + else if (own >= 3) + score = 6 + remove_tiles_from_court(TILE_BLUE) + if (score_own_points(score)) + return goto_end_of_the_contest() + end_turn() +} + // === THE ACTIONS: GODLINESS AND PIETY === + +function goto_godliness_and_piety() { + end_turn() +} + // === THE ACTIONS: TOURNAMENTS === + +function goto_tournaments() { + end_turn() +} + // === THE ACTIONS: COLLECTIONS === + +function goto_purple() { + end_turn() +} + // === END OF THE CONTEST === // === COMMON LIBRARY === +function log(msg) { + game.log.push(msg) +} + +function log_br() { + if (game.log.length > 0 && game.log[game.log.length - 1] !== "") + game.log.push("") +} + +function logi(msg) { + game.log.push(">" + msg) +} + +function log_h1(msg) { + log_br() + log(".h1 " + msg) + log_br() +} + +function log_h2(msg) { + log_br() + log(".h2 " + msg) + log_br() +} + function clear_undo() { - if (game.undo.length > 0) - game.undo = [] + if (game.undo) { + game.undo.length = 0 + } } function push_undo() { - let copy = {} - for (let k in game) { - let v = game[k] - if (k === "undo") - continue - else if (k === "log") - v = v.length - else if (typeof v === "object" && v !== null) - v = object_copy(v) - copy[k] = v + if (game.undo) { + let copy = {} + for (let k in game) { + let v = game[k] + if (k === "undo") + continue + else if (k === "log") + v = v.length + else if (typeof v === "object" && v !== null) + v = object_copy(v) + copy[k] = v + } + game.undo.push(copy) } - game.undo.push(copy) } function pop_undo() { - let save_log = game.log - let save_undo = game.undo - game = save_undo.pop() - save_log.length = game.log - game.log = save_log - game.undo = save_undo + if (game.undo) { + let save_log = game.log + let save_undo = game.undo + game = save_undo.pop() + save_log.length = game.log + game.log = save_log + game.undo = save_undo + } } function random(range) { -- cgit v1.2.3