diff options
author | Tor Andersson <tor@ccxvii.net> | 2023-07-15 15:08:55 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2023-07-16 13:06:00 +0200 |
commit | 5bedb1a547dbd70240d8a59ec4a84e57517d4918 (patch) | |
tree | 5e0d5aa0ef8565b49e9e4d752c6a3f3550d36472 | |
parent | 51f8e9aefed01f36127246b15563d7e9cc1801b8 (diff) | |
download | field-cloth-gold-5bedb1a547dbd70240d8a59ec4a84e57517d4918.tar.gz |
Lotsa stuff.
-rw-r--r-- | create.html | 0 | ||||
-rw-r--r-- | favicon.svg | 3 | ||||
-rw-r--r-- | play.html | 95 | ||||
-rw-r--r-- | play.js | 140 | ||||
-rw-r--r-- | rules.js | 817 | ||||
-rw-r--r-- | tools/color.js | 4 |
6 files changed, 859 insertions, 200 deletions
diff --git a/create.html b/create.html new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/create.html diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..f3b2c0a --- /dev/null +++ b/favicon.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1.0"> +<rect x="2" y="2" width="28" height="28" stroke-width="4" stroke="hsl(50,80%,30%)" fill="hsl(50,80%,70%)" /> +</svg> @@ -5,28 +5,59 @@ <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1"> <meta charset="UTF-8"> <title>THE FIELD OF THE CLOTH OF GOLD</title> -<link rel="icon" href="favicon.png"> +<link rel="icon" href="favicon.svg"> <link rel="stylesheet" href="/fonts/fonts.css"> <link rel="stylesheet" href="/common/play.css"> <script defer src="/common/play.js"></script> <script defer src="play.js"></script> <style> +@font-face{ + font-family: "Albert Text"; + src: url('fonts/AlbertText-Bold.woff2') format('woff2'); +} + main { + user-select: none; background-color: #666; background-image: url(background.png); box-shadow: inset 0 0 8px #0008; } -#log { - background-color: oldlace; +body.Observer #hand { + display: none; +} + +.court, #hand { + font-family: "Albert Text"; + padding: 6px 10px; + text-align: right; + font-size: 20px; + color: #0004; +} + +#log { background-color: oldlace; } +#log .h1 { + margin: 6px 0; + padding: 2px 8px; + border-top: 1px solid black; + border-bottom: 1px solid black; + background-color: hsl(0,0%,85%); } +#log .h1.x { background-color: hsl(0,0%,85%); } +#log .h1.gold { background-color: hsl(50,81%,79%); } +#log .h1.blue { background-color: hsl(201,80%,67%); } +#log .h1.white { background-color: hsl(0,0%,100%); } +#log .h1.red { background-color: hsl(0,90%,69%); } +#log .h1.purple { background-color: hsl(296,29%,65%); } +#log .h1.r{background-image:linear-gradient(90deg,transparent 180px,#000 180px,#000 181px, hsl(359,85%,60%) 181px); } +#log .h1.b{background-image:linear-gradient(90deg,transparent 180px,#000 180px,#000 181px, hsl(220,85%,60%) 181px); } #role_Red .role_name { background-color: hsl(359,100%,75%); } #role_Blue .role_name { background-color: hsl(220,100%,80%); } #mapwrap { - width: 827px; + width: 825px; margin: 0px auto; } @@ -42,7 +73,6 @@ main { border: 2px solid #0004; border-color: #fff4 #0004 #0004 #fff4; box-shadow: 0 0 0 1px #222, 1px 1px 8px -2px #000; - border-radius: 8px; } #hand, .court { @@ -57,6 +87,10 @@ main { border-color: #fff8 #2228 #2228 #fff8; } +@media (min-resolution: 97dpi) { + #board { background-image: url(map150.jpg) } +} + #court1 { top: 12px } #board { top: calc(12px + 80px + 12px) } #court2 { top: calc(12px + 80px + 12px + 638px + 12px) } @@ -65,15 +99,7 @@ main { .red.court { background-color: hsl(359,35%,45%) } .blue.court { background-color: hsl(220,35%,45%) } - -X#hand { background-color: hsl(46, 63%, 56%) } -#hand { background-color: hsl(46, 35%, 45%) } -X#hand { background-image: url(bg_gold3.png); background-size: 256px 256px; } -X#hand { background-color: #666 } - -@media (min-resolution: 97dpi) { - #board { background-image: url(map150.jpg) } -} +#hand { background-color: #666 } #map div { position: absolute; @@ -110,8 +136,9 @@ X#hand { background-color: #666 } } .tile { - width: 52px; - height: 52px; + box-sizing: border-box; + width: 56px; + height: 56px; background-repeat: no-repeat; background-size: 38px 38px; background-position: center; @@ -130,29 +157,32 @@ X#hand { background-color: #666 } .space { box-sizing: border-box; - border-radius: 50%; border: 3px solid transparent; } +.space.oval { + border-radius: 50%; +} + .space.action { border: 3px solid white; + box-shadow: 0 0 4px white; } .token.white { background-image: url(images/token_white.svg) } .token.red { background-image: url(images/token_red.svg) } .token.blue { background-image: url(images/token_blue.svg) } -.tile.white { background-image: url(images/white.png) } -.tile.red { background-image: url(images/red.png) } -.tile.blue { background-image: url(images/blue.png) } -.tile.gold { background-image: url(images/gold.png) } -.tile.green { background-image: url(images/green.png) } +.tile.gold { background-image: url(images/gold.png); background-color: hsl(50,81%,59%); } +.tile.blue { background-image: url(images/blue.png); background-color: hsl(201,80%,47%); } +.tile.white { background-image: url(images/white.png); background-color: hsl(0,0%,94%); } +.tile.red { background-image: url(images/red.png); background-color: hsl(0,90%,49%); } +.tile.green { background-image: url(images/green.png); background-color: hsl(125,21%,33%); } -.tile.red { background-color: hsl(0,90%,49%); border-color: hsl(0,90%,59%) hsl(0,90%,39%) hsl(0,90%,39%) hsl(0,90%,59%); } -.tile.white { background-color: hsl(0,0%,94%); border-color: hsl(0,0%,100%) hsl(0,0%,84%) hsl(0,0%,84%) hsl(0,0%,100%); } -.tile.blue { background-color: hsl(201,80%,47%); border-color: hsl(201,80%,57%) hsl(201,80%,37%) hsl(201,80%,37%) hsl(201,80%,57%); } -.tile.gold { background-color: hsl(50,81%,59%); border-color: hsl(50,81%,69%) hsl(50,81%,49%) hsl(50,81%,49%) hsl(50,81%,69%); } -.tile.green { background-color: hsl(125,21%,33%); border-color: hsl(125,21%,43%) hsl(125,21%,23%) hsl(125,21%,23%) hsl(125,21%,43%); } +.tile { border-color: #fff5 #0003 #0003 #fff5; } +.tile.gold { border-color: #fff6 #0003 #0003 #fff6 } +.tile.white { border-color: #ddd #bbb #bbb #ddd } +.tile.green { border-color: #fff3 #0003 #0003 #fff3 } </style> </head> @@ -169,6 +199,7 @@ X#hand { background-color: #666 } </div> </div> <div class="icon_button" onclick="toggle_zoom()"><img src="/images/magnifying-glass.svg"></div> + <div class="icon_button" onclick="toggle_log()"><img src="/images/scroll-quill.svg"></div> </div> <div id="prompt"></div> <div id="actions"></div> @@ -194,14 +225,12 @@ X#hand { background-color: #666 } <main> -<div id="mapwrap"> +<div id="mapwrap" class="fit"> <div id="map"> - -<div id="court1" class="red court"></div> +<div id="court1" class="red court">Rival Court</div> <div id="board"></div> -<div id="court2" class="blue court"></div> -<div id="hand" class="blue hand"></div> - +<div id="court2" class="blue court">Court</div> +<div id="hand" class="blue hand">Hand</div> </div> </div> @@ -33,40 +33,40 @@ const TILE_DX = 3 const TILE_DY = 3 const LAYOUT_OVAL = [ - [156,165,45,45], - [262,165,45,45], - [369,165,45,45], - [475,165,45,45], - [582,165,45,45], - [688,165,45,45], - [66,164,45,45], + [156,165,46,46], + [262,165,46,46], + [369,165,46,46], + [475,165,46,46], + [582,165,46,46], + [688,165,46,46], + [66,164,46,46], // dragon at home - [66,105,46,45], + [67,106,46,46], // off-board - [75,400,62,62], - [175,400,62,62], - [50,500,62,62], - [150,500,62,62], + [10,638-50], + [110,638-50], + [60,638-50], + [160,638-50], ] const LAYOUT_SQUARE = [ - [147,224,61,61], - [253,224,61,61], - [360,225,61,61], - [466,225,61,61], - [573,225,61,61], - [679,225,61,61], + [147,224,62,62], + [253,224,62,62], + [360,225,62,62], + [466,225,62,62], + [573,225,62,62], + [679,225,62,62], ] -const LAYOUT_SCORE_0 = [358,332,45,45] -const LAYOUT_SCORE_39 = [748,547,45,45] +const LAYOUT_SCORE_0 = [358,332,46,46] +const LAYOUT_SCORE_39 = [748,547,46,46] const SCORE_X0 = LAYOUT_SCORE_0[0] + TOKEN_DX + BOARD_X const SCORE_Y0 = LAYOUT_SCORE_0[1] + TOKEN_DY + BOARD_Y -const SCORE_DX = (LAYOUT_SCORE_39[0] - LAYOUT_SCORE_0[0]) / 7 -const SCORE_DY = (LAYOUT_SCORE_39[1] - LAYOUT_SCORE_0[1]) / 4 +const SCORE_DX = (LAYOUT_SCORE_39[0] - LAYOUT_SCORE_0[0] + 4) / 7 +const SCORE_DY = (LAYOUT_SCORE_39[1] - LAYOUT_SCORE_0[1] + 0) / 4 let ui = { board: document.getElementById("map"), @@ -75,6 +75,7 @@ let ui = { red_score: null, blue_score: null, oval_spaces: [], + square_spaces: [], tokens: [], tiles: [ null ], } @@ -127,7 +128,7 @@ function on_init() { return on_init_once = true - for (let i = 0; i < 7; ++i) { + for (let i = 0; i < 8; ++i) { ui.oval_spaces[i] = create("div", { className: "oval space", my_action: "space", my_id: i }) ui.oval_spaces[i].style.left = (BOARD_X + LAYOUT_OVAL[i][0]) + "px" ui.oval_spaces[i].style.top = (BOARD_Y + LAYOUT_OVAL[i][1]) + "px" @@ -136,8 +137,17 @@ function on_init() { ui.board.append(ui.oval_spaces[i]) } - ui.red_score = create_token({ className: "token red" }) - ui.blue_score = create_token({ className: "token blue" }) + for (let i = 0; i < 6; ++i) { + ui.square_spaces[i] = create("div", { className: "square space", my_action: "square", my_id: i }) + ui.square_spaces[i].style.left = (BOARD_X + LAYOUT_SQUARE[i][0]) + "px" + ui.square_spaces[i].style.top = (BOARD_Y + LAYOUT_SQUARE[i][1]) + "px" + ui.square_spaces[i].style.width = LAYOUT_SQUARE[i][2] + "px" + ui.square_spaces[i].style.height = LAYOUT_SQUARE[i][3] + "px" + ui.board.append(ui.square_spaces[i]) + } + + ui.red_score = create_token({ className: "token red", my_action: "score", my_id: 1 }) + ui.blue_score = create_token({ className: "token blue", my_action: "score", my_id: 2 }) ui.tokens[0] = create_token({ className: "token white", my_action: "token", my_id: 0 }) ui.tokens[1] = create_token({ className: "token red", my_action: "token", my_id: 1 }) @@ -171,7 +181,7 @@ function on_update() { on_init() for (let i = 1; i <= 54; ++i) { - if (view.hand.includes(i) || view.red_court.includes(i) || view.blue_court.includes(i) || view.squares.includes(i)) + if ((view.hand && view.hand.includes(i)) || view.red_court.includes(i) || view.blue_court.includes(i) || view.squares.includes(i)) show(ui.tiles[i]) else hide(ui.tiles[i]) @@ -196,9 +206,22 @@ function on_update() { ui.tokens[i].classList.toggle("selected", i === view.selected_token) } - let tile_space = calc_tile_spacing(view.red_court.length) - for (let i = 0; i < view.red_court.length; ++i) { - let k = view.red_court[i] + let court1, court2 + if (player !== "Blue") { + ui.court1.className = "blue court" + ui.court2.className = "red court" + court1 = view.blue_court + court2 = view.red_court + } else { + ui.court1.className = "red court" + ui.court2.className = "blue court" + court1 = view.red_court + court2 = view.blue_court + } + + let tile_space = calc_tile_spacing(court1.length) + for (let i = 0; i < court1.length; ++i) { + let k = court1[i] let x = COURT1_X + tile_space * i let y = COURT1_Y ui.tiles[k].style.left = x + "px" @@ -206,9 +229,9 @@ function on_update() { ui.tiles[k].style.zIndex = i } - tile_space = calc_tile_spacing(view.blue_court.length) - for (let i = 0; i < view.blue_court.length; ++i) { - let k = view.blue_court[i] + tile_space = calc_tile_spacing(court2.length) + for (let i = 0; i < court2.length; ++i) { + let k = court2[i] let x = COURT2_X + tile_space * i let y = COURT2_Y ui.tiles[k].style.left = x + "px" @@ -216,14 +239,16 @@ function on_update() { ui.tiles[k].style.zIndex = i } - tile_space = calc_tile_spacing(view.hand.length) - for (let i = 0; i < view.hand.length; ++i) { - let k = view.hand[i] - let x = HAND_X + tile_space * i - let y = HAND_Y - ui.tiles[k].style.left = x + "px" - ui.tiles[k].style.top = y + "px" - ui.tiles[k].style.zIndex = i + if (view.hand) { + tile_space = calc_tile_spacing(view.hand.length) + for (let i = 0; i < view.hand.length; ++i) { + let k = view.hand[i] + let x = HAND_X + tile_space * i + let y = HAND_Y + ui.tiles[k].style.left = x + "px" + ui.tiles[k].style.top = y + "px" + ui.tiles[k].style.zIndex = i + } } let rs = view.red_score @@ -246,8 +271,43 @@ function on_update() { for (let e of action_register) e.classList.toggle("action", is_action(e.my_action, e.my_id)) + action_button("darkness", "Darkness") action_button("done", "Done") action_button("undo", "Undo") } +function on_log(text) { + let p = document.createElement("div") + if (text.match(/^\.r /)) { + text = text.substring(3) + p.className = 'h1 r' + } + else if (text.match(/^\.b /)) { + text = text.substring(3) + p.className = 'h1 b' + } + else if (text.match(/^\.x /)) { + text = text.substring(3) + p.className = 'h1 x' + } + + if (text.match(/^Dragon$/)) + p.classList.add("dragon") + if (text.match(/^Secrecy$/)) + p.classList.add("secrecy") + if (text.match(/^Cloth of Gold$/)) + p.classList.add("gold") + if (text.match(/^Banquets/)) + p.classList.add("blue") + if (text.match(/^Godliness/)) + p.classList.add("white") + if (text.match(/^Tournaments$/)) + p.classList.add("red") + if (text.match(/^Collections$/)) + p.classList.add("purple") + + p.textContent = text + return p +} + scroll_with_middle_mouse("main") @@ -11,6 +11,9 @@ exports.scenarios = [ "Standard" ] exports.roles = [ RED, BLUE ] +const SECRECY_PER_ROW = [ 1, 2, 2, 3, 0 ] +const GOLD_PER_ROW = [ 3, 3, 2, 1, 1 ] + const TOKEN_DRAGON = 0 const TOKEN_RED_1 = 1 const TOKEN_RED_2 = 2 @@ -43,6 +46,59 @@ const TILE_WHITE = 37 const TILE_GREEN = 49 const LAST_TILE = 54 +const COUNT_NAME = [ + "no", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "ten", + "eleven", + "twelve", +] + +function count_name(n) { + if (n > 12) + return String(n) + return COUNT_NAME[n] +} + +function space_name(s) { + switch (s) { + case S_DRAGON_1: return "Dragon" + case S_DARKNESS: return "Secrecy" + case S_GOLD: return "Cloth of Gold" + case S_BLUE: return "Banquets & Feasts" + case S_WHITE: return "Godliness & Piety" + case S_RED: return "Tournaments" + case S_PURPLE: return "Collections" + default: return "off board" + } +} + +function tile_name(tile) { + if (tile >= TILE_BLUE && tile < TILE_BLUE + 12) return "Blue" + if (tile >= TILE_RED && tile < TILE_RED + 12) return "Red" + if (tile >= TILE_GOLD && tile < TILE_GOLD + 12) return "Gold" + if (tile >= TILE_WHITE && tile < TILE_WHITE + 12) return "White" + if (tile >= TILE_GREEN && tile < TILE_GREEN + 12) return "Green" + return "None" +} + +function tile_type_name(tile) { + if (tile >= TILE_BLUE && tile < TILE_BLUE + 12) return "Beasts" + if (tile >= TILE_RED && tile < TILE_RED + 12) return "Tournaments" + if (tile >= TILE_GOLD && tile < TILE_GOLD + 12) return "Cloth of Gold" + if (tile >= TILE_WHITE && tile < TILE_WHITE + 12) return "Piety" + if (tile >= TILE_GREEN && tile < TILE_GREEN + 12) return "Jewels" + return "None" +} + function gen_action(action, argument) { if (!(action in view.actions)) view.actions[action] = [] @@ -61,6 +117,33 @@ function gen_action_space(space) { gen_action("space", space) } +function gen_action_square(space) { + gen_action("square", space) +} + +function gen_action_score() { + if (game.active === RED) + gen_action("score", 1) + else + gen_action("score", 2) +} + +function gen_action_score_rival() { + if (game.active !== RED) + gen_action("score", 1) + else + gen_action("score", 2) +} + +function prompt_score(space, score, tail = ".") { + if (score === 0) + view.prompt = space + ": Score no points" + tail + else if (score === 1) + view.prompt = space + ": Score one point" + tail + else + view.prompt = space + ": Score " + score + " points" + tail +} + exports.action = function (state, player, action, arg) { game = state let S = states[game.state] @@ -100,7 +183,7 @@ exports.view = function(state, player) { view.prompt = game.victory } else if (player !== game.active) { let inactive = states[game.state].inactive || game.state - view.prompt = `Waiting for ${game.active} \u2014 ${inactive}...` + view.prompt = `Waiting for ${game.active} \u2014 ${inactive}.` } else { view.actions = {} states[game.state].prompt() @@ -129,14 +212,12 @@ function goto_game_over(result, victory) { game.active = "None" game.result = result game.victory = victory - log_br() + log("") log(game.victory) + return false } states.game_over = { - get inactive() { - return game.victory - }, prompt() { view.prompt = game.victory }, @@ -205,35 +286,71 @@ function own_court() { return game.blue_court } +function rival_hand() { + if (game.active === RED) + return game.blue_hand + return game.red_hand +} + function own_hand() { if (game.active === RED) return game.red_hand return game.blue_hand } +function rival_score() { + if (game.active === RED) + return game.blue_score + return game.red_score +} + function own_score() { if (game.active === RED) return game.red_score return game.blue_score } +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 +} + function gift_tile_in_space(to) { // Gift associated tile to rival. if (to <= LAST_TILE_SPACE) { let tile = game.squares[to] - log("Gifted " + tile) + log("Gifted " + tile_name(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) + if (n > 0) { + if (n === 1) + log("Scored " + count_name(n) + " point.") + else + log("Scored " + count_name(n) + " points.") + if (game.active === RED) + game.red_score += n + if (game.active === BLUE) + game.blue_score += n + } +} + +function score_rival_points(n) { + if (n > 0) { + if (n === 1) + log("Rival scored " + count_name(n) + " point.") + else + log("Rival scored " + count_name(n) + " points.") + if (game.active === RED) + game.blue_score += n + if (game.active === BLUE) + game.red_score += n + } } function count_tiles(list, type) { @@ -244,44 +361,78 @@ function count_tiles(list, type) { 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 has_tile_in_list(type, list) { + for (let tile of list) + if (tile >= type && tile < type + 12) + return true + return false } -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 gen_tile_in_list(type, list) { + for (let tile of list) + if (tile >= type && tile < type + 12) + gen_action_tile(tile) } -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 +function log_reveal_tiles_into_court(type) { + let n = count_tiles(own_hand(), type) + if (n > 0) + log("Revealed " + count_name(n) + " " + tile_name(type) + ".") +} + +function log_remove_tiles_from_court(type) { + let n = count_tiles(own_court(), type) + if (n > 0) + log("Removed " + count_name(n) + " " + tile_name(type) + ".") +} + +function log_remove_tiles_from_rival_court(type) { + let n = count_tiles(rival_court(), type) + if (n > 0) + log("Rival removed " + count_name(n) + " " + tile_name(type) + ".") +} + +function can_reveal_tiles_into_court(type) { + return has_tile_in_list(type, own_hand()) +} + +function gen_reveal_tiles_into_court(type) { + gen_tile_in_list(type, own_hand()) +} + +function can_remove_tiles_from_court(type) { + return has_tile_in_list(type, own_court()) +} + +function gen_remove_tiles_from_court(type) { + gen_tile_in_list(type, own_court()) +} + +function can_remove_tiles_from_rival_court(type) { + return has_tile_in_list(type, rival_court()) +} + +function gen_remove_tiles_from_rival_courts(type) { + gen_tile_in_list(type, rival_court()) +} + +function reveal_tile_into_court(tile) { + array_remove_item(own_hand(), tile) + own_court().push(tile) +} + +function remove_tile_from_court(tile) { + array_remove_item(own_court(), tile) +} + +function remove_tile_from_rival_court(tile) { + array_remove_item(rival_court(), tile) } // === FLOW OF PLAY === states.move_token = { + inactive: "to move one of their tokens", prompt() { view.prompt = "Move one of your tokens to an oval space." if (game.active === RED) { @@ -312,8 +463,9 @@ states.move_token = { } states.move_token_to = { + inactive: "to move one of their tokens", prompt() { - view.prompt = "Move your token to an oval space." + view.prompt = "Move one of your tokens to an oval space." view.selected_token = game.selected_token for (let i = FIRST_SPACE; i <= LAST_OVAL_SPACE; ++i) if (is_oval_space_empty(i)) @@ -324,7 +476,12 @@ states.move_token_to = { // Remember whence we came. game.from = game.tokens[game.selected_token] - log_h2(game.active + " MOVED TO " + to) + if (game.active === RED) + log(".r " + space_name(to)) + else + log(".b " + space_name(to)) + + log("From " + space_name(game.from) + ".") // Move the token. game.tokens[game.selected_token] = to @@ -334,20 +491,20 @@ states.move_token_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() + 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_collections() } }, token(t) { @@ -355,23 +512,80 @@ states.move_token_to = { }, } +function is_end_of_the_contest() { + if (game.darkness.length === 0) + return true + if ((game.red_score >= 30) || (game.blue_score >= 30)) + return true + return false +} + +// TODO: manual return dragon +// TODO: manual refill + function end_turn() { clear_undo() + // Game End triggered? + if (is_end_of_the_contest()) + return goto_end_of_the_contest() + // Return Dragon if needed. - if (is_oval_space_empty(S_DRAGON_1)) - game.tokens[TOKEN_DRAGON] = S_DRAGON_2 + if (is_oval_space_empty(S_DRAGON_1) && game.tokens[TOKEN_DRAGON] !== S_DRAGON_2) { + game.state = "return_dragon" + return + } - // 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() - } + goto_refill_tiles() +} + +states.return_dragon = { + inactive: "End Turn", + prompt() { + view.prompt = "End Turn: Move the Dragon back to its space." + view.selected_token = TOKEN_DRAGON + gen_action_space(S_DRAGON_2) + }, + space(_) { + log("Returned Dragon.") + game.tokens[TOKEN_DRAGON] = S_DRAGON_2 + goto_refill_tiles() } +} + +function must_refill_tiles() { + for (let i = FIRST_SPACE; i <= LAST_TILE_SPACE; ++i) + if (is_oval_space_empty(i) && game.squares[i] === 0) + return true + return false +} + +function goto_refill_tiles() { + if (must_refill_tiles()) + game.state = "refill_tiles" + else + pass_play_to_rival() +} +states.refill_tiles = { + inactive: "End Turn", + prompt() { + view.prompt = "End Turn: Place a new tile." + for (let i = FIRST_SPACE; i <= LAST_TILE_SPACE; ++i) + if (is_oval_space_empty(i) && game.squares[i] === 0) + gen_action_square(i) + }, + square(i) { + let tile = game.darkness.pop() + log("Placed " + tile_name(tile) + " at " + space_name(i) + ".") + game.squares[i] = tile + if (game.darkness.length === 0) + return goto_end_of_the_contest() + goto_refill_tiles() + }, +} + +function pass_play_to_rival() { // Play passes to the rival player. if (game.active === RED) game.active = BLUE @@ -383,18 +597,20 @@ function end_turn() { // === THE ACTIONS: DRAGON === function goto_dragon() { - log("Dragon") game.state = "dragon" } states.dragon = { + inactive: "Dragon", prompt() { + view.prompt = "Dragon: Move the Dragon to an empty oval space." 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) { + log("Dragon to " + space_name(to) + ".") game.tokens[TOKEN_DRAGON] = to gift_tile_in_space(to) end_turn() @@ -403,106 +619,457 @@ states.dragon = { // === THE ACTIONS: SECRECY === -const SECRECY_PER_ROW = [ 1, 2, 2, 3, 0 ] +function gain_tiles_from_darkness(n) { + for (let i = 0; i < n; ++i) + own_hand().push(game.darkness.pop()) +} + +function gain_tiles_from_darkness_rival(n) { + for (let i = 0; i < n; ++i) + rival_hand().push(game.darkness.pop()) +} + +function gain_secrecy_tiles() { + let n = Math.min(SECRECY_PER_ROW[own_score() / 8 | 0], game.darkness.length) + log("Gained " + count_name(n) + ".") + gain_tiles_from_darkness(n) +} -function gain_tile_from_darkness() { - let tile = game.darkness.pop() - own_hand().push(tile) - return game.darkness.length === 0 +function gain_secrecy_tiles_rival() { + let n = Math.min(SECRECY_PER_ROW[rival_score() / 8 | 0], game.darkness.length) + log("Rival gained " + count_name(n) + ".") + gain_tiles_from_darkness_rival(n) } 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() + game.state = "secrecy" +} + +states.secrecy = { + inactive: "Secrecy", + prompt() { + view.prompt = "Secrecy: Gain tiles from the Darkness to your Hand." + view.actions.darkness = 1 + }, + darkness() { + gain_secrecy_tiles() + end_turn() + }, } // === THE ACTIONS: CLOTH OF GOLD === -function goto_cloth_of_gold() { - log("Cloth of Gold") - reveal_tiles_into_court(TILE_GOLD) +function calc_cloth_of_gold_score() { 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() + if (own > rival) + return 2 + return 0 +} + +function goto_cloth_of_gold() { + log_reveal_tiles_into_court(TILE_GOLD) + if (can_reveal_tiles_into_court(TILE_GOLD)) + game.state = "cloth_of_gold_reveal" + else + game.state = "cloth_of_gold_score" +} + +states.cloth_of_gold_reveal = { + inactive: "Cloth of Gold", + prompt() { + view.prompt = "Cloth of Gold: Reveal all Gold tiles from your Hand." + gen_reveal_tiles_into_court(TILE_GOLD) + }, + tile(tile) { + reveal_tile_into_court(tile) + if (!can_reveal_tiles_into_court(TILE_GOLD)) + game.state = "cloth_of_gold_score" + }, +} + +states.cloth_of_gold_score = { + inactive: "Cloth of Gold", + prompt() { + prompt_score("Cloth of Gold", calc_cloth_of_gold_score()) + gen_action_score() + }, + score() { + score_own_points(calc_cloth_of_gold_score()) + end_turn() } - end_turn() } // === THE ACTIONS: BANQUETS AND FEASTS === +function calc_banquets_and_feasts_score() { + let n = count_tiles(own_court(), TILE_BLUE) + if (n === 0) + return 0 + if (n === 1) + return 1 + if (n === 2) + return 3 + return 6 +} + 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() + log_reveal_tiles_into_court(TILE_BLUE) + if (can_reveal_tiles_into_court(TILE_BLUE)) + game.state = "banquets_and_feasts_reveal" + else + game.state = "banquets_and_feasts_score" +} + +states.banquets_and_feasts_reveal = { + inactive: "Banquets & Feasts", + prompt() { + view.prompt = "Banquets & Feasts: Reveal all Blue tiles from your Hand." + gen_reveal_tiles_into_court(TILE_BLUE) + }, + tile(tile) { + reveal_tile_into_court(tile) + if (!can_reveal_tiles_into_court(TILE_BLUE)) + game.state = "banquets_and_feasts_score" + }, +} + +states.banquets_and_feasts_score = { + inactive: "Banquets & Feasts", + prompt() { + prompt_score("Banquets & Feasts", calc_banquets_and_feasts_score()) + gen_action_score() + }, + score() { + score_own_points(calc_banquets_and_feasts_score()) + if (is_end_of_the_contest()) + return goto_end_of_the_contest() + log_remove_tiles_from_court(TILE_BLUE) + if (can_remove_tiles_from_court(TILE_BLUE)) + game.state = "banquets_and_feasts_remove" + else + end_turn() + }, +} + +states.banquets_and_feasts_remove = { + inactive: "Banquets & Feasts", + prompt() { + view.prompt = "Banquets & Feasts: Remove all Blue tiles from your Court." + gen_remove_tiles_from_court(TILE_BLUE) + }, + tile(tile) { + remove_tile_from_court(tile) + if (!can_remove_tiles_from_court(TILE_BLUE)) + end_turn() + }, } // === THE ACTIONS: GODLINESS AND PIETY === +function calc_godliness_and_piety_score() { + return count_tiles(own_court(), TILE_WHITE) +} + function goto_godliness_and_piety() { - end_turn() + log_reveal_tiles_into_court(TILE_WHITE) + if (can_reveal_tiles_into_court(TILE_WHITE)) + game.state = "godliness_and_piety_reveal" + else + game.state = "godliness_and_piety_score" +} + +states.godliness_and_piety_reveal = { + inactive: "Godliness & Piety", + prompt() { + view.prompt = "Godliness & Piety: Reveal all White tiles from your Hand." + gen_reveal_tiles_into_court(TILE_WHITE) + }, + tile(tile) { + reveal_tile_into_court(tile) + if (!can_reveal_tiles_into_court(TILE_WHITE)) + game.state = "godliness_and_piety_score" + }, +} + +states.godliness_and_piety_score = { + inactive: "Godliness & Piety", + prompt() { + view.prompt = "Godliness & Piety: Score " + calc_godliness_and_piety_score() + " points." + prompt_score("Godliness & Piety", calc_godliness_and_piety_score()) + gen_action_score() + }, + score() { + score_own_points(calc_godliness_and_piety_score()) + if (is_end_of_the_contest()) + return goto_end_of_the_contest() + log_remove_tiles_from_court(TILE_WHITE) + if (can_remove_tiles_from_court(TILE_WHITE)) + game.state = "godliness_and_piety_remove" + else + end_turn() + }, +} + +states.godliness_and_piety_remove = { + inactive: "Godliness & Piety", + prompt() { + view.prompt = "Godliness & Piety: Remove all White tiles from your Court." + gen_remove_tiles_from_court(TILE_WHITE) + }, + tile(tile) { + remove_tile_from_court(tile) + if (!can_remove_tiles_from_court(TILE_WHITE)) + end_turn() + }, } // === THE ACTIONS: TOURNAMENTS === +function calc_tournaments_score() { + return count_tiles(own_court(), TILE_RED) +} + +function calc_tournaments_rival_score() { + return count_tiles(rival_court(), TILE_RED) +} + function goto_tournaments() { - end_turn() + goto_tournaments_reveal() +} + +function goto_tournaments_reveal() { + log_reveal_tiles_into_court(TILE_RED) + if (can_reveal_tiles_into_court(TILE_RED)) + game.state = "tournaments_reveal" + else + game.state = "tournaments_score_own" +} + +states.tournaments_reveal = { + inactive: "Tournaments", + prompt() { + view.prompt = "Tournaments: Reveal all Red tiles from your Hand." + gen_reveal_tiles_into_court(TILE_RED) + }, + tile(tile) { + reveal_tile_into_court(tile) + goto_tournaments_reveal() + }, +} + +states.tournaments_score_own = { + inactive: "Tournaments", + prompt() { + view.prompt = "Tournaments: Score " + calc_tournaments_score() + " points." + prompt_score("Tournaments", calc_tournaments_score()) + gen_action_score() + }, + score() { + score_own_points(calc_tournaments_score()) + if (is_end_of_the_contest()) + return goto_end_of_the_contest() + game.state = "tournaments_score_rival" + }, +} + +states.tournaments_score_rival = { + inactive: "Tournaments", + prompt() { + prompt_score("Tournaments", calc_tournaments_rival_score(), " for your rival.") + gen_action_score_rival() + }, + score() { + score_rival_points(calc_tournaments_rival_score()) + if (is_end_of_the_contest()) + return goto_end_of_the_contest() + log_remove_tiles_from_court(TILE_RED) + goto_tournaments_remove_own() + }, +} + +function goto_tournaments_remove_own() { + if (can_remove_tiles_from_court(TILE_RED)) + game.state = "tournaments_remove_own" + else { + log_remove_tiles_from_rival_court(TILE_RED) + goto_tournaments_remove_rival() + } +} + +states.tournaments_remove_own = { + inactive: "Tournaments", + prompt() { + view.prompt = "Tournaments: Remove all Red tiles from your Court." + gen_remove_tiles_from_court(TILE_RED) + }, + tile(tile) { + remove_tile_from_court(tile) + goto_tournaments_remove_own() + }, +} + +function goto_tournaments_remove_rival() { + if (can_remove_tiles_from_rival_court(TILE_RED)) + game.state = "tournaments_remove_rival" + else + game.state = "tournaments_secrecy" +} + +states.tournaments_remove_rival = { + inactive: "Tournaments", + prompt() { + view.prompt = "Tournaments: Remove all Red tiles from rival Court." + gen_remove_tiles_from_rival_courts(TILE_RED) + }, + tile(tile) { + remove_tile_from_rival_court(tile) + goto_tournaments_remove_rival() + }, +} + +states.tournaments_secrecy = { + inactive: "Tournaments", + prompt() { + view.prompt = "Tournaments: Gain tiles from the Darkness." + view.actions.darkness = 1 + }, + darkness() { + gain_secrecy_tiles() + gain_secrecy_tiles_rival() + end_turn() + }, } // === THE ACTIONS: COLLECTIONS === -function goto_purple() { - end_turn() +function calc_collections_score() { + let x1 = count_tiles(own_court(), TILE_GOLD) + let x2 = count_tiles(own_court(), TILE_BLUE) + let x3 = count_tiles(own_court(), TILE_WHITE) + let x4 = count_tiles(own_court(), TILE_RED) + return Math.min(x1, x2, x3, x4) * 2 +} + +function goto_collections() { + log_reveal_tiles_into_court(TILE_GOLD) + log_reveal_tiles_into_court(TILE_BLUE) + log_reveal_tiles_into_court(TILE_WHITE) + log_reveal_tiles_into_court(TILE_RED) + log_reveal_tiles_into_court(TILE_GREEN) + if (own_hand().length > 0) + game.state = "collections_reveal" + else + game.state = "collections_score" +} + +states.collections_reveal = { + inactive: "Collections", + prompt() { + view.prompt = "Collections: Reveal all tiles from your Hand." + for (let tile of own_hand()) + gen_action_tile(tile) + }, + tile(tile) { + reveal_tile_into_court(tile) + if (own_hand().length === 0) + game.state = "collections_score" + }, +} + +states.collections_score = { + inactive: "Collections", + prompt() { + view.prompt = "Collections: Score " + calc_collections_score() + " points." + prompt_score("Collections", calc_collections_score()) + gen_action_score() + }, + score() { + score_own_points(calc_collections_score()) + end_turn() + } } // === END OF THE CONTEST === -// === COMMON LIBRARY === +function calc_jewel_score(court) { + let n = count_tiles(court, TILE_GREEN) + return n * n +} -function log(msg) { - game.log.push(msg) +function calc_gold_score(score, court) { + let n = count_tiles(court, TILE_GOLD) + return n * GOLD_PER_ROW[score / 8 | 0] +} + +function goto_end_of_the_contest() { + log(".x End of the Contest") + game.state = "end_of_the_contest_jewels" +} + +states.end_of_the_contest_jewels = { + prompt() { + prompt_score("End of the Contest", calc_jewel_score(own_court()), " for Jewels.") + gen_action_score() + }, + score() { + score_own_points(calc_jewel_score(own_court())) + game.state = "end_of_the_contest_jewels_rival" + }, +} + +states.end_of_the_contest_jewels_rival = { + prompt() { + prompt_score("End of the Contest", calc_jewel_score(rival_court()), " to your rival for Jewels.") + gen_action_score_rival() + }, + score() { + score_rival_points(calc_jewel_score(rival_court())) + game.state = "end_of_the_contest_gold" + }, } -function log_br() { - if (game.log.length > 0 && game.log[game.log.length - 1] !== "") - game.log.push("") +states.end_of_the_contest_gold = { + prompt() { + prompt_score("End of the Contest", calc_gold_score(own_score(), own_court()), " for Gold.") + gen_action_score() + }, + score() { + score_own_points(calc_gold_score(own_score(), own_court())) + game.state = "end_of_the_contest_gold_rival" + }, } -function logi(msg) { - game.log.push(">" + msg) +states.end_of_the_contest_gold_rival = { + prompt() { + prompt_score("End of the Contest", calc_gold_score(rival_score(), rival_court()), " to your rival for Gold.") + gen_action_score_rival() + }, + score() { + score_rival_points(calc_gold_score(rival_score(), rival_court())) + goto_victory() + }, +} + +function victory_check(red, blue) { + if (red > blue) + return goto_game_over(RED, "Red is the winner!") + if (blue > red) + return goto_game_over(BLUE, "Blue is the winner!") + return true } -function log_h1(msg) { - log_br() - log(".h1 " + msg) - log_br() +function goto_victory() { + if (victory_check(game.red_score, game.blue_score)) + if (victory_check(count_tiles(game.red_court, TILE_WHITE), count_tiles(game.blue_court, TILE_WHITE))) + if (victory_check(game.red_court.length), game.blue_court.length) + goto_game_over("Shared", "The two majesties do share victory.") } -function log_h2(msg) { - log_br() - log(".h2 " + msg) - log_br() +// === COMMON LIBRARY === + +function log(msg) { + game.log.push(msg) } function clear_undo() { diff --git a/tools/color.js b/tools/color.js index d645df7..82b9db7 100644 --- a/tools/color.js +++ b/tools/color.js @@ -41,8 +41,8 @@ function rgb_to_hsl(rgb, add=0, scale=1) { function foo(sel, rgb) { let bg = rgb_to_hsl(rgb, 0.00, 1.00) - let hi = rgb_to_hsl(rgb, 0.10, 1.00) - let sh = rgb_to_hsl(rgb, -0.10, 1.00) + let hi = rgb_to_hsl(rgb, 0.15, 1.00) + let sh = rgb_to_hsl(rgb, -0.15, 1.00) let bd = rgb_to_hsl(rgb, 0.00, 0.33) console.log(sel + ` { background-color: ${bg}; border-color: ${hi} ${sh} ${sh} ${hi}; box-shadow: 0 0 0 1px ${bd}, 0px 1px 4px #0008; }`) } |