summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-07-15 15:08:55 +0200
committerTor Andersson <tor@ccxvii.net>2023-07-16 13:06:00 +0200
commit5bedb1a547dbd70240d8a59ec4a84e57517d4918 (patch)
tree5e0d5aa0ef8565b49e9e4d752c6a3f3550d36472
parent51f8e9aefed01f36127246b15563d7e9cc1801b8 (diff)
downloadfield-cloth-gold-5bedb1a547dbd70240d8a59ec4a84e57517d4918.tar.gz
Lotsa stuff.
-rw-r--r--create.html0
-rw-r--r--favicon.svg3
-rw-r--r--play.html95
-rw-r--r--play.js140
-rw-r--r--rules.js817
-rw-r--r--tools/color.js4
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>
diff --git a/play.html b/play.html
index 5b114c6..2311ab0 100644
--- a/play.html
+++ b/play.html
@@ -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>
diff --git a/play.js b/play.js
index 0cced7e..c4eae91 100644
--- a/play.js
+++ b/play.js
@@ -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")
diff --git a/rules.js b/rules.js
index c809044..8659e2d 100644
--- a/rules.js
+++ b/rules.js
@@ -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; }`)
}