summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2023-06-05 02:16:37 +0200
committerTor Andersson <tor@ccxvii.net>2023-07-16 13:06:00 +0200
commitfa3fc7fd0184fc8500cbe21cee7440433d1d1c4c (patch)
tree073404683cb482a9797a09e87480293550f60907
parent64b8441e2650f8e527c6834ced4aeb2bde7de77b (diff)
downloadfield-cloth-gold-fa3fc7fd0184fc8500cbe21cee7440433d1d1c4c.tar.gz
Start rules.
-rw-r--r--about.html39
-rw-r--r--play.html45
-rw-r--r--rules.js274
3 files changed, 335 insertions, 23 deletions
diff --git a/about.html b/about.html
new file mode 100644
index 0000000..3825c25
--- /dev/null
+++ b/about.html
@@ -0,0 +1,39 @@
+<p>
+In June 1520, at Balinghem, there met the young and glorious Kings of England
+and of France in a grand and splendid celebration of their mutual friendship
+and admiration. There were great feasts and opulent amusements, conspicuous
+expressions of deepest and sincerest piety, and fierce tournaments. Each King
+outshone the other in turn, so that the world might be astounded at the wealth
+and power of each kingdom. So many tents and raiments were made of precious
+cloth of gold, that the field is so named.
+
+<p>
+This game was created to commemorate the five hundredth anniversary of the
+world’s most famous three-week party, in which King Henry VIII of England, and
+Francis I of France, spent ridiculous amounts of money and resources to peacock
+at each other. Through smiles made of gritted teeth, the game’s two players
+will express friendship: each action they take will give their opponent a tile
+that they might use to score. Scoring usually entails removing the tile from
+the game, so each player must take care to maximize their scoring opportunities
+while either denying them to their rival, or forcing their rival to score
+before they’re ready. Once the game is underway, players are generally given
+only two options, and both of those options are usually painful - even and
+sometimes especially when they score you points.
+
+<p>
+A sort of subversion of the point salad paradigm, The Field of the Cloth of
+Gold harkens back to the feel of the classic German games of the 1990s: simple,
+elegant, austere, and politely vicious.
+
+<br clear="left">
+
+<p>
+Game Design: Amabel Holland.<br>
+
+<p>
+Copyright &copy; 2023
+<a href="https://hollandspiele.com/products/the-field-of-the-cloth-of-gold">Hollandspiele</a>
+
+<ul>
+<li><a href="/field-cloth-gold/info/rules.html">Laws of Play</a>
+</ul>
diff --git a/play.html b/play.html
index dcdefed..bc33c84 100644
--- a/play.html
+++ b/play.html
@@ -18,15 +18,22 @@ main { background-color: dimgray }
#role_Blue .role_name { background-color: skyblue; }
#mapwrap {
- width: 1100px;
- height: 850px;
+ width: 1104px;
+ height: 854px;
box-shadow: 0px 1px 10px #0008;
+ margin: 12px auto;
}
#map {
width: 1100px;
height: 850px;
- background-image: url(map100.png);
+ background-size: 1100px 850px;
+ background-image: url(map100.jpg);
+ border: 2px solid #333;
+}
+
+@media (min-resolution: 97dpi) {
+ #map { background-image: url(map200.jpg) }
}
#map div {
@@ -72,10 +79,18 @@ main { background-color: dimgray }
.panel {
max-width: 1100px;
- margin: 36px auto;
+ margin: 24px auto;
background-color: #555;
+ border: 2px solid #333;
+ box-shadow: 0 1px 10px #0008;
}
+#red_court_header { background-color: #644 }
+#red_court { background-color: #655 }
+
+#blue_court_header { background-color: #446 }
+#blue_court { background-color: #556 }
+
.panel_header {
background-color: #444;
color: white;
@@ -89,8 +104,9 @@ main { background-color: dimgray }
display: flex;
justify-content: start;
flex-wrap: wrap;
- padding: 20px;
- gap: 20px;
+ padding: 16px;
+ gap: 16px;
+ min-height: 80px;
}
</style>
@@ -150,16 +166,19 @@ main { background-color: dimgray }
<div id="hand_panel" class="panel">
<div id="hand_header" class="panel_header">Hand</div>
<div id="hand" class="panel_body">
-
-<div class="tile red" style="left:198px;top:300px"></div>
-<div class="tile blue" style="left:338px;top:300px"></div>
-<div class="tile green" style="left:478px;top:300px"></div>
-<div class="tile white" style="left:618px;top:300px"></div>
-<div class="tile gold" style="left:758px;top:300px"></div>
-
</div>
</div>
+<div id="red_court_panel" class="panel">
+<div id="red_court_header" class="panel_header">Red Court</div>
+<div id="red_court" class="panel_body">
+</div></div>
+
+<div id="blue_court_panel" class="panel">
+<div id="blue_court_header" class="panel_header">Blue Court</div>
+<div id="blue_court" class="panel_body">
+</div></div>
+
</main>
<footer id="status"></footer>
diff --git a/rules.js b/rules.js
index 6229427..e6461e1 100644
--- a/rules.js
+++ b/rules.js
@@ -3,20 +3,54 @@
const RED = "Red"
const BLUE = "Blue"
-var game, view, states
+const states = {}
+
+var game, view
exports.scenarios = [ "Standard" ]
exports.roles = [ RED, BLUE ]
-exports.setup = function (seed, scenario, options) {
- game = {
- seed: seed,
- state: null,
- log: [],
- undo: [],
- }
- return game
+const TOKEN_DRAGON = 0
+const TOKEN_RED_1 = 1
+const TOKEN_RED_2 = 2
+const TOKEN_BLUE_1 = 3
+const TOKEN_BLUE_2 = 4
+
+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 TILE_NONE = 0
+const FIRST_TILE = 1
+const TILE_BLUE = 1
+const TILE_RED = 13
+const TILE_GOLD = 25
+const TILE_WHITE = 37
+const TILE_GREEN = 49
+const LAST_TILE = 55
+
+function gen_action(action, argument) {
+ if (!(action in view.actions))
+ view.actions[action] = []
+ view.actions[action].push(argument)
+}
+
+function gen_action_token(token) {
+ gen_action("token", token)
+}
+
+function gen_action_tile(tile) {
+ gen_action("tile", tile)
+}
+
+function gen_action_space(space) {
+ gen_action("space", space)
}
exports.action = function (state, player, action, arg) {
@@ -43,11 +77,26 @@ exports.resign = function (state, player) {
exports.view = function(state, player) {
game = state
- let view = {
+ view = {
log: game.log,
prompt: null,
+ tokens: game.tokens,
+ squares: game.squares,
+ red_score: game.red_score,
+ blue_score: game.blue_score,
+ red_court: game.red_court,
+ blue_court: game.blue_court,
+
+ selected_token: -1,
+ selected_tile: 0,
}
+ if (player === RED)
+ view.hand = game.red_hand
+
+ if (player === BLUE)
+ view.hand = game.blue_hand
+
if (game.state === "game_over") {
view.prompt = game.victory
} else if (player !== game.active) {
@@ -64,3 +113,208 @@ exports.view = function(state, player) {
return view;
}
+
+exports.setup = function (seed, scenario, options) {
+ game = {
+ seed: seed,
+ state: null,
+ log: [],
+ undo: [],
+
+ 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 ],
+ darkness: [],
+ red_court: [],
+ blue_court: [],
+ red_hand: [],
+ blue_hand: [],
+ }
+
+ for (let i = FIRST_TILE; i < TILE_GREEN; ++i)
+ game.darkness.push(i)
+
+ shuffle(game.darkness)
+
+ game.red_hand.push(game.darkness.pop())
+ game.red_hand.push(game.darkness.pop())
+
+ game.blue_hand.push(game.darkness.pop())
+ game.blue_hand.push(game.darkness.pop())
+
+ for (let i = TILE_GREEN; i < TILE_GREEN + 6; ++i)
+ game.darkness.push(i)
+
+ shuffle(game.darkness)
+
+ for (let i = 0; i < 6; ++i)
+ game.squares[i] = game.darkness.pop()
+
+ if (random(2) === 0)
+ game.active = RED
+ else
+ game.active = BLUE
+
+ game.state = "move_token"
+
+ return game
+}
+
+// === 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) {
+ gen_action_token(TOKEN_RED_1)
+ } else if (game.tokens[TOKEN_RED_2] === S_OFF_BOARD) {
+ 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) {
+ gen_action_token(TOKEN_BLUE_1)
+ } else if (game.tokens[TOKEN_BLUE_2] === S_OFF_BOARD) {
+ gen_action_token(TOKEN_BLUE_2)
+ } else {
+ gen_action_token(TOKEN_BLUE_1)
+ gen_action_token(TOKEN_BLUE_2)
+ }
+ }
+ },
+ token(token) {
+ push_undo()
+ game.selected_token = token
+ game.state = "move_token_to"
+ },
+}
+
+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)
+ if (is_oval_space_empty(i))
+ gen_action_space(i)
+ },
+ space(space) {
+ game.tokens[game.selected_token] = space
+ // take action!
+ }
+}
+
+// === THE ACTIONS: DRAGON ===
+// === THE ACTIONS: SECRECY ===
+// === THE ACTIONS: CLOTH OF GOLD ===
+// === THE ACTIONS: BANQUETS AND FEASTS ===
+// === THE ACTIONS: GODLINESS AND PIETY ===
+// === THE ACTIONS: TOURNAMENTS ===
+// === THE ACTIONS: COLLECTIONS ===
+// === END OF THE CONTEST ===
+
+// === COMMON LIBRARY ===
+
+function clear_undo() {
+ if (game.undo.length > 0)
+ game.undo = []
+}
+
+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
+ }
+ 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
+}
+
+function random(range) {
+ // An MLCG using integer arithmetic with doubles.
+ // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf
+ // m = 2**35 − 31
+ return (game.seed = game.seed * 200105 % 34359738337) % range
+}
+
+function shuffle(list) {
+ // Fisher-Yates shuffle
+ for (let i = list.length - 1; i > 0; --i) {
+ let j = random(i + 1)
+ let tmp = list[j]
+ list[j] = list[i]
+ list[i] = tmp
+ }
+}
+
+// Fast deep copy for objects without cycles
+function object_copy(original) {
+ if (Array.isArray(original)) {
+ let n = original.length
+ let copy = new Array(n)
+ for (let i = 0; i < n; ++i) {
+ let v = original[i]
+ if (typeof v === "object" && v !== null)
+ copy[i] = object_copy(v)
+ else
+ copy[i] = v
+ }
+ return copy
+ } else {
+ let copy = {}
+ for (let i in original) {
+ let v = original[i]
+ if (typeof v === "object" && v !== null)
+ copy[i] = object_copy(v)
+ else
+ copy[i] = v
+ }
+ return copy
+ }
+}
+
+// Array remove and insert (faster than splice)
+
+function array_remove(array, index) {
+ let n = array.length
+ for (let i = index + 1; i < n; ++i)
+ array[i - 1] = array[i]
+ array.length = n - 1
+}
+
+function array_remove_item(array, item) {
+ let n = array.length
+ for (let i = 0; i < n; ++i)
+ if (array[i] === item)
+ return array_remove(array, i)
+}
+
+function array_insert(array, index, item) {
+ for (let i = array.length; i > index; --i)
+ array[i] = array[i - 1]
+ array[index] = item
+}