summaryrefslogtreecommitdiff
path: root/rules.js
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-06-05 13:31:15 +0200
committerTor Andersson <tor@ccxvii.net>2023-07-16 13:06:00 +0200
commit51f8e9aefed01f36127246b15563d7e9cc1801b8 (patch)
tree28d0c16b9247d1f33f49d99a874514114b6637d8 /rules.js
parentfa3fc7fd0184fc8500cbe21cee7440433d1d1c4c (diff)
downloadfield-cloth-gold-51f8e9aefed01f36127246b15563d7e9cc1801b8.tar.gz
All in one.
Diffstat (limited to 'rules.js')
-rw-r--r--rules.js395
1 files changed, 340 insertions, 55 deletions
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) {