summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.html52
-rw-r--r--play.js47
-rw-r--r--rules.js453
3 files changed, 328 insertions, 224 deletions
diff --git a/play.html b/play.html
index 8bce1a6..063d039 100644
--- a/play.html
+++ b/play.html
@@ -41,6 +41,11 @@ body.Opposition header.your_turn { background-color: hsl(16 85% 75%);; }
#log .h1 { background-color: silver; }
#log .h2 { background-color: gainsboro; }
+#log .suf { background-color: hsl(273 39% 85%); }
+#log .opp { background-color: hsl(16 85% 85%); }
+#log .tip { cursor: pointer; }
+#log .tip:hover { text-decoration: underline; }
+
#log > .p { padding-left: 20px; text-align: right; font-style: italic; opacity: 75%; }
#log > .i { padding-left: 20px; }
@@ -55,10 +60,10 @@ body.Opposition header.your_turn { background-color: hsl(16 85% 75%);; }
gap: 20px;
}
-#hand, #discard, #states_draw, #strategy_draw, #set_aside { min-height: 350px; }
-#hand_panel, #states_draw { min-width: 1100px; }
-#strategy_draw, #set_aside_panel { min-width: 830px; }
-#discard_panel { min-width: 290px; }
+#hand, #discard, #support_claimed, #support_discard, #opposition_claimed, #opposition_discard, #states_draw, #strategy_draw, #out_of_play { min-height: 350px; }
+#hand_panel, #states_draw, #support_discard_panel, #opposition_discard_panel, #out_of_play { min-width: 1100px; }
+#strategy_draw { min-width: 790px; }
+#support_claimed, #opposition_claimed { min-width: 290px; }
.panel {
background-color: #555;
@@ -886,45 +891,58 @@ c5 3 13 7 17 8 8 2 9 3 11 12 1 5 5 12 8 16 5 8 5 8 3 22 l-3 14 -30 -1 c-35
</div>
<div class="panel_grid">
-
<div id="hand_panel" class="panel hide">
<div id="hand_header" class="panel_header">Hand</div>
<div id="hand" class="panel_body"></div>
</div>
+ </div>
- <div id="discard_panel" class="panel">
- <div id="discard_header" class="panel_header">Discard</div>
- <div id="discard" class="panel_body"></div>
+ <div class="panel_grid">
+ <div id="support_claimed_panel" class="panel">
+ <div id="support_claimed_header" class="panel_header">Suffragist Claimed</div>
+ <div id="support_claimed" class="panel_body"></div>
</div>
+ <div id="opposition_claimed_panel" class="panel">
+ <div id="opposition_claimed_header" class="panel_header">Opposition Claimed</div>
+ <div id="opposition_claimed" class="panel_body"></div>
+ </div>
</div>
<div class="panel_grid">
-
<div id="strategy_draw_panel" class="panel">
- <div id="strategy_draw_header" class="panel_header">Strategy cards</div>
+ <div id="strategy_draw_header" class="panel_header">Available Strategy cards</div>
<div id="strategy_draw" class="panel_body"></div>
</div>
</div>
<div class="panel_grid">
-
<div id="states_draw_panel" class="panel">
- <div id="states_draw_header" class="panel_header">States cards</div>
+ <div id="states_draw_header" class="panel_header">Available States cards</div>
<div id="states_draw" class="panel_body"></div>
</div>
-
</div>
-
<div class="panel_grid">
+ <div id="support_discard_panel" class="panel">
+ <div id="support_discard_header" class="panel_header">Suffragist Discard</div>
+ <div id="support_discard" class="panel_body"></div>
+ </div>
+ </div>
- <div id="set_aside_panel" class="panel hide">
- <div id="set_aside_header" class="panel_header">Set-aside cards</div>
- <div id="set_aside" class="panel_body"></div>
+ <div class="panel_grid">
+ <div id="opposition_discard_panel" class="panel">
+ <div id="opposition_discard_header" class="panel_header">Opposition Discard</div>
+ <div id="opposition_discard" class="panel_body"></div>
</div>
+ </div>
+ <div class="panel_grid">
+ <div id="out_of_play_panel" class="panel">
+ <div id="out_of_play_header" class="panel_header">Out of Play</div>
+ <div id="out_of_play" class="panel_body"></div>
+ </div>
</div>
<br><br><br>
diff --git a/play.js b/play.js
index 2b8ae7e..10b1db9 100644
--- a/play.js
+++ b/play.js
@@ -274,13 +274,19 @@ function on_log(text) { // eslint-disable-line no-unused-vars
text = text.substring(4)
p.className = 'h1'
}
-
- if (text.match(/^\.h2/)) {
+ else if (text.match(/^\.h2/)) {
text = text.substring(4)
p.className = 'h2'
}
-
- if (text.match(/^\.h3/)) {
+ else if (text.match(/^\.h3.suf/)) {
+ text = text.substring(8)
+ p.className = 'h3 suf'
+ }
+ else if (text.match(/^\.h3.opp/)) {
+ text = text.substring(8)
+ p.className = 'h3 opp'
+ }
+ else if (text.match(/^\.h3/)) {
text = text.substring(4)
p.className = 'h3'
}
@@ -289,16 +295,15 @@ function on_log(text) { // eslint-disable-line no-unused-vars
return p
}
+const pluralize = (count, noun, suffix = 's') =>
+ `${count} ${noun}${count !== 1 ? suffix : ''}`;
+
function support_info() {
- if (view.support_hand === 1)
- return "1 card in hand";
- return view.support_hand + " cards in hand";
+ return `${pluralize(view.support_buttons, 'button')}, ${pluralize(view.support_hand, 'card')} in hand`
}
function opposition_info() {
- if (view.opposition_hand === 1)
- return "1 card in hand";
- return view.opposition_hand + " cards in hand";
+ return `${pluralize(view.opposition_buttons, 'button')}, ${pluralize(view.opposition_hand, 'card')} in hand`
}
function on_update() { // eslint-disable-line no-unused-vars
@@ -311,8 +316,13 @@ function on_update() { // eslint-disable-line no-unused-vars
document.getElementById("opposition_info").textContent = opposition_info()
document.getElementById("hand").replaceChildren()
+ document.getElementById("support_claimed").replaceChildren()
+ document.getElementById("support_discard").replaceChildren()
+ document.getElementById("opposition_claimed").replaceChildren()
+ document.getElementById("opposition_discard").replaceChildren()
document.getElementById("states_draw").replaceChildren()
document.getElementById("strategy_draw").replaceChildren()
+ document.getElementById("out_of_play").replaceChildren()
if (view.hand) {
document.getElementById("hand_panel").classList.remove("hide")
@@ -322,11 +332,28 @@ function on_update() { // eslint-disable-line no-unused-vars
document.getElementById("hand_panel").classList.add("hide")
}
+ for (let c of view.support_claimed)
+ document.getElementById("support_claimed").appendChild(ui.cards[c])
+ for (let c of view.support_discard)
+ document.getElementById("support_discard").appendChild(ui.cards[c])
+ for (let c of view.opposition_claimed)
+ document.getElementById("opposition_claimed").appendChild(ui.cards[c])
+ for (let c of view.opposition_discard)
+ document.getElementById("opposition_discard").appendChild(ui.cards[c])
+
for (let c of view.states_draw)
document.getElementById("states_draw").appendChild(ui.cards[c])
for (let c of view.strategy_draw)
document.getElementById("strategy_draw").appendChild(ui.cards[c])
+ for (let c of view.out_of_play)
+ document.getElementById("out_of_play").appendChild(ui.cards[c])
+
+ action_button("commit_1_button", "+1 Button")
+ action_button("defer", "Defer")
+ action_button("match", "Match")
+ action_button("supersede", "Supersede")
+
action_button("draw", "Draw")
action_button("skip", "Skip")
diff --git a/rules.js b/rules.js
index d583b52..f99e3d1 100644
--- a/rules.js
+++ b/rules.js
@@ -18,9 +18,111 @@ const last_strategy_card = 116
const first_states_card = 117
const last_states_card = 128
+// #region CARD & HAND FUNCTIONS
+
+function is_support_card(c) {
+ return c >= first_support_card && c <= last_support_card
+}
+
+function is_opposition_card(c) {
+ return c >= first_opposition_card && c <= last_opposition_card
+}
+
+function is_strategy_card(c) {
+ return c >= first_strategy_card && c <= last_strategy_card
+}
+
+function is_states_card(c) {
+ return c >= first_states_card && c <= last_states_card
+}
+
+function draw_card(deck) {
+ if (deck.length === 0)
+ throw Error("can't draw from empty deck")
+ return deck.pop()
+}
+
+function player_hand() {
+ if (game.active === SUF) {
+ return game.support_hand
+ } else if (game.active === OPP) {
+ return game.opposition_hand
+ }
+ return []
+}
+
+function player_claimed() {
+ if (game.active === SUF) {
+ return game.support_claimed
+ } else {
+ return game.opposition_claimed
+ }
+}
+
+function player_discard() {
+ if (game.active === SUF) {
+ return game.support_discard
+ } else {
+ return game.opposition_discard
+ }
+}
+
+function is_player_claimed_card(c) {
+ return player_claimed().includes(c)
+}
+
+// #region PUBLIC FUNCTIONS
+
exports.scenarios = [ "Standard" ]
exports.roles = [ SUF, OPP ]
+function gen_action(action, argument) {
+ if (argument === undefined) {
+ view.actions[action] = 1
+ } else {
+ if (!(action in view.actions))
+ view.actions[action] = []
+ view.actions[action].push(argument)
+ }
+}
+
+exports.action = function (state, player, action, arg) {
+ game = state
+ if (states[game.state] && action in states[game.state]) {
+ states[game.state][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 === SUF)
+ goto_game_over(OPP, "Suffragist resigned.")
+ if (player === OPP)
+ goto_game_over(SUF, "Opposition 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)
+}
+
+// #endregion
+
+// #region SETUP
+
exports.setup = function (seed, _scenario, _options) {
game = {
seed: seed,
@@ -56,6 +158,8 @@ exports.setup = function (seed, _scenario, _options) {
opposition_claimed: [],
opposition_campaigner: [0, 0],
opposition_buttons: 0,
+
+ out_of_play: []
}
log_h1("Votes for Women")
@@ -81,7 +185,7 @@ function setup_game() {
shuffle(game.states_draw)
shuffle(game.strategy_deck)
- game.states_draw.splice(-3) // 3 states card aren't used
+ game.out_of_play.push(...game.states_draw.splice(-3)) // 3 states card aren't used
log_h2("States Cards")
for (let c of game.states_draw)
log(`C${c}`)
@@ -109,12 +213,88 @@ function init_player_cards(first_card) {
return [].concat(late, middle, early)
}
-function draw_card(deck) {
- if (deck.length === 0)
- throw Error("can't draw from empty deck")
- return deck.pop()
+// #endregion
+
+// #region VIEW
+
+exports.view = function(state, player) {
+ game = state
+
+ view = {
+ log: game.log,
+ active: game.active,
+ prompt: null,
+ actions: null,
+
+ turn: game.turn,
+ round: game.round,
+ congress: game.congress,
+ states: game.states,
+
+ strategy_deck: game.strategy_deck.length,
+ strategy_draw: game.strategy_draw,
+ states_draw: game.states_draw,
+
+ persisted_turn: game.persisted_turn,
+ persisted_game: game.persisted_game,
+ persisted_ballot: game.persisted_ballot,
+
+ support_deck: game.support_deck.length,
+ support_discard: game.support_discard, // top_discard?
+ support_hand: game.support_hand.length,
+ support_claimed: game.support_claimed,
+ purple_campaigner: game.purple_campaigner,
+ yellow_campaigner: game.yellow_campaigner,
+ support_buttons: game.support_buttons,
+
+ opposition_deck: game.opposition_deck.length,
+ opposition_discard: game.opposition_discard, // top_discard?
+ opposition_hand: game.opposition_hand.length,
+ opposition_claimed: game.opposition_claimed,
+ opposition_campaigner: game.opposition_campaigner,
+ opposition_buttons: game.opposition_buttons,
+
+ out_of_play: game.out_of_play,
+
+ hand: 0,
+ }
+
+ if (player === SUF) {
+ view.hand = game.support_hand
+ } else if (player === OPP) {
+ view.hand = game.opposition_hand
+ }
+
+ if (game.state === "game_over") {
+ view.prompt = game.victory
+ } else if (player === "Observer" || (game.active !== player && game.active !== "Both")) {
+ if (states[game.state]) {
+ let inactive = states[game.state].inactive
+ view.prompt = `Waiting for ${game.active} to ${inactive}...`
+ } else {
+ view.prompt = "Unknown state: " + game.state
+ }
+ } else {
+ view.actions = {}
+ if (states[game.state])
+ states[game.state].prompt(player)
+ else
+ view.prompt = "Unknown state: " + game.state
+ if (view.actions.undo === undefined) {
+ if (game.undo && game.undo.length > 0)
+ view.actions.undo = 1
+ else
+ view.actions.undo = 0
+ }
+ }
+
+ return view
}
+// #endregion
+
+// #region FLOW OF PLAY
+
function start_turn() {
game.turn += 1
log_h1("Turn " + game.turn)
@@ -129,7 +309,7 @@ function goto_planning_phase() {
}
states.planning_phase = {
- inactive: "to do Planning.",
+ inactive: "do Planning.",
prompt() {
view.prompt = "Planning."
gen_action("draw")
@@ -167,12 +347,12 @@ function end_planning_phase() {
}
function goto_strategy_phase() {
- log_h2("Strategy Phase")
+ log_h2("Strategy")
game.state = "strategy_phase"
}
states.strategy_phase = {
- inactive: "to do Strategy.",
+ inactive: "do Strategy.",
prompt() {
view.prompt = "Strategy: TODO"
gen_action("done")
@@ -183,41 +363,13 @@ states.strategy_phase = {
}
function goto_operations_phase() {
- log_h2("Operations Phase")
+ log_h2("Operations")
game.state = "operations_phase"
game.active = SUF
game.round = 1
begin_player_round()
}
-function player_hand() {
- if (game.active === SUF) {
- return game.support_hand
- } else if (game.active === OPP) {
- return game.opposition_hand
- }
- return []
-}
-
-function player_claimed() {
- if (game.active === SUF) {
- return game.support_claimed
- } else {
- return game.opposition_claimed
- }
-}
-
-function player_discard() {
- if (game.active === SUF) {
- return game.support_discard
- } else {
- return game.opposition_discard
- }
-}
-
-function is_player_claimed_card(c) {
- return player_claimed().includes(c)
-}
function count_player_active_campaigners() {
if (game.active === SUF) {
@@ -240,6 +392,7 @@ function after_play_card(c) {
game.has_played_claimed = 1
// remove from game
array_remove_item(player_claimed(), c)
+ game.out_of_play.push(c)
} else {
game.has_played_hand = 1
// move to discard
@@ -248,30 +401,27 @@ function after_play_card(c) {
}
}
-function gen_card_actions(c) {
- gen_action("card_event", c)
- if (has_player_active_campaigners()) {
- gen_action("card_campaigning", c)
- gen_action("card_organizing", c)
- gen_action("card_lobbying", c)
- }
-}
-
states.operations_phase = {
- inactive: "to do Operations Phase",
+ inactive: "Play a Card.",
prompt() {
- view.prompt = "Operations: Play a Card"
+ view.prompt = "Operations: Play a Card."
if (!game.has_played_hand) {
for (let c of player_hand()) {
- gen_card_actions(c)
+ gen_action("card_event", c)
+ if (has_player_active_campaigners()) {
+ gen_action("card_campaigning", c)
+ gen_action("card_organizing", c)
+ gen_action("card_lobbying", c)
+ }
}
}
if (!game.has_played_claimed) {
// only one claimed can be played per turn
for (let c of player_claimed()) {
- gen_card_actions(c)
+ // TODO is this the right type of event?
+ gen_action("card_event", c)
}
}
if (game.has_played_hand)
@@ -279,22 +429,25 @@ states.operations_phase = {
},
card_event(c) {
push_undo()
- log(`Playing C${c} as Event`)
+ log(`Played C${c} as Event`)
after_play_card(c)
+
+ // XXX auto-done to speed-up testing
+ end_player_round()
},
card_campaigning(c) {
push_undo()
- log(`Playing C${c} for Campaigning Action`)
+ log(`Played C${c} for Campaigning Action`)
after_play_card(c)
},
card_organizing(c) {
push_undo()
- log(`Playing C${c} for Organizing Action`)
+ log(`Played C${c} for Organizing Action`)
after_play_card(c)
},
card_lobbying(c) {
push_undo()
- log(`Playing C${c} for Lobbying Action`)
+ log(`Played C${c} for Lobbying Action`)
after_play_card(c)
},
done() {
@@ -303,7 +456,7 @@ states.operations_phase = {
}
function begin_player_round() {
- log_h3(`${game.active} Round ${game.round}`)
+ log_round(`Round ${game.round}`)
}
function end_player_round() {
@@ -326,14 +479,14 @@ function end_player_round() {
}
function goto_cleanup_phase() {
- log_h2("Cleanup Phase")
+ log_h2("Cleanup")
game.state = "cleanup_phase"
}
states.cleanup_phase = {
- inactive: "to do Cleanup.",
+ inactive: "do Cleanup.",
prompt() {
- view.prompt = "Cleanup: TODO"
+ view.prompt = "Cleanup."
gen_action("done")
},
done() {
@@ -341,14 +494,32 @@ states.cleanup_phase = {
}
}
+function cleanup_persistent_turn_cards() {
+ // any cards in the “Cards in Effect for the Rest of the Turn box” are placed in the appropriate discard pile.
+ for (let c of game.persisted_turn) {
+ if (is_support_card(c)) {
+ game.support_discard.push(c)
+ } else if (is_opposition_card(c)) {
+ game.opposition_discard.push(c)
+ } else {
+ throw Error(`Unexpected card ${c} on persisted_turn`)
+ }
+ }
+ game.persisted_turn = []
+}
+
function end_cleanup_phase() {
- // TODO
- // At the end of Turns 1-5, any cards in the “Cards
- // in Effect for the Rest of the Turn box” are placed
- // in the appropriate discard pile. Each player should
- // have one card in their hand to carry over to the next
- // Turn. The Turn marker is advanced and the
- // next Turn begins.
+ if (game.turn < 6) {
+ cleanup_persistent_turn_cards()
+
+ if (game.support_hand.length !== 1)
+ throw Error("ASSERT game.support_hand.length === 1")
+ if (game.opposition_hand.length !== 1)
+ throw Error("ASSERT game.opposition_hand.length === 1")
+
+ start_turn()
+ return
+ }
// TODO
// At the end of Turn 6, if the Nineteenth
@@ -362,139 +533,12 @@ function end_cleanup_phase() {
// Effect for the Rest of the Game box” are placed in
// the appropriate discard pile.
- if (game.support_hand.length !== 1)
- throw Error("ASSERT game.support_hand.length === 1")
- if (game.opposition_hand.length !== 1)
- throw Error("ASSERT game.opposition_hand.length === 1")
-
- if (game.turn < 6) {
- start_turn()
- } else {
- goto_game_over(OPP, "Opposition wins.")
- }
+ goto_game_over(OPP, "Opposition wins.")
}
-// public functions
+// #endregion
-function gen_action(action, argument) {
- if (argument === undefined) {
- view.actions[action] = 1
- } else {
- if (!(action in view.actions))
- view.actions[action] = []
- view.actions[action].push(argument)
- }
-}
-
-exports.action = function (state, player, action, arg) {
- game = state
- if (states[game.state] && action in states[game.state]) {
- states[game.state][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 === SUF)
- goto_game_over(OPP, "Suffragist resigned.")
- if (player === OPP)
- goto_game_over(SUF, "Opposition 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)
-}
-
-// === VIEW ===
-
-exports.view = function(state, player) {
- game = state
-
- view = {
- log: game.log,
- active: game.active,
- prompt: null,
- actions: null,
-
- turn: game.turn,
- round: game.round,
- congress: game.congress,
- states: game.states,
-
- strategy_deck: game.strategy_deck.length,
- strategy_draw: game.strategy_draw,
- states_draw: game.states_draw,
-
- persisted_turn: game.persisted_turn,
- persisted_game: game.persisted_game,
- persisted_ballot: game.persisted_ballot,
-
- support_deck: game.support_deck.length,
- support_discard: game.support_discard, // top_discard?
- support_hand: game.support_hand.length,
- support_claimed: game.support_claimed,
- purple_campaigner: game.purple_campaigner,
- yellow_campaigner: game.yellow_campaigner,
- support_buttons: game.support_buttons,
-
- opposition_deck: game.opposition_deck.length,
- opposition_discard: game.opposition_discard, // top_discard?
- opposition_hand: game.opposition_hand.length,
- opposition_claimed: game.opposition_claimed,
- opposition_campaigner: game.opposition_campaigner,
- opposition_buttons: game.opposition_buttons,
-
- hand: 0,
- }
-
- if (player === SUF) {
- view.hand = game.support_hand
- } else if (player === OPP) {
- view.hand = game.opposition_hand
- }
-
- if (game.state === "game_over") {
- view.prompt = game.victory
- } else if (player === "Observer" || (game.active !== player && game.active !== "Both")) {
- if (states[game.state]) {
- let inactive = states[game.state].inactive
- view.prompt = `Waiting for ${game.active} to ${inactive}...`
- } else {
- view.prompt = "Unknown state: " + game.state
- }
- } else {
- view.actions = {}
- if (states[game.state])
- states[game.state].prompt(player)
- else
- view.prompt = "Unknown state: " + game.state
- if (view.actions.undo === undefined) {
- if (game.undo && game.undo.length > 0)
- view.actions.undo = 1
- else
- view.actions.undo = 0
- }
- }
-
- return view
-}
-
-
-// === LOGGING ===
+// #region LOGGING
function log(msg) {
game.log.push(msg)
@@ -526,11 +570,22 @@ function log_h3(msg) {
log(".h3 " + msg)
}
+function log_round(msg) {
+ log_br()
+ if (game.active === SUF)
+ log(".h3.suf " + msg)
+ else
+ log(".h3.opp " + msg)
+ log_br()
+}
+
function log_sep() {
log(".hr")
}
-// === COMMON LIBRARY ===
+// #endregion
+
+// #region COMMON LIBRARY
function clear_undo() {
if (game.undo.length > 0)
@@ -620,4 +675,8 @@ function array_remove(array, index) {
array.length = n - 1
}
-// === GENERATED EVENT CODE ===
+// #endregion
+
+// #region GENERATED EVENT CODE
+
+// #endregion