From 2f3b4a2f04488308a6ac9d1cf4db299874090e7b Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Wed, 12 Feb 2025 00:43:17 +0100 Subject: Games that time out should result in "None" instead of resign. Clean up resignation handling. Prepare for collectively abandoning games as well. --- schema.sql | 2 ++ server.js | 49 +++++++++++++++++++++++++++++++++---------------- tools/patchgame.js | 36 +++++++++++++++++++++++++++++++++++- views/tm_pool.pug | 30 +++++++++++++++++++++--------- 4 files changed, 91 insertions(+), 26 deletions(-) diff --git a/schema.sql b/schema.sql index 5fd189e..041761b 100644 --- a/schema.sql +++ b/schema.sql @@ -173,6 +173,7 @@ create view rated_games_view as and moves >= player_count * 3 and user_count = player_count and player_count > 1 + and result != 'None' and not exists ( select 1 from players where players.game_id = games.game_id and user_id = 0 ) @@ -720,6 +721,7 @@ begin set score = ( case when new.result is null then null + when new.result = 'None' then null when new.result = role then 2 when new.result = 'Draw' then 1 when instr(new.result, role) then 1 diff --git a/server.js b/server.js index 8b5c0b6..9ae38a0 100644 --- a/server.js +++ b/server.js @@ -1277,10 +1277,11 @@ function format_options(options_json) { function get_game_roles(title_id, scenario, options) { let roles = RULES[title_id].roles - if (typeof options === "string") - options = parse_game_options(options) - if (typeof roles === "function") + if (typeof roles === "function") { + if (typeof options === "string") + options = parse_game_options(options) return roles(scenario, options) + } return roles } @@ -2719,7 +2720,7 @@ function time_control_ticker() { for (let item of SQL_SELECT_TIME_CONTROL.all()) { if (item.is_opposed) { console.log("TIMED OUT GAME:", item.game_id, item.role) - do_resign(item.game_id, item.role, "timed out") + do_timeout(item.game_id, item.role, item.role + " timed out.") if (item.is_match) { console.log("BANNED FROM TOURNAMENTS:", item.user_id) TM_INSERT_TIMEOUT.run(item.user_id, item.game_id) @@ -2903,6 +2904,7 @@ const TM_SELECT_GAMES = SQL(` tm_rounds.*, games.status, games.moves, + games.status > 1 and games.result = 'None' as is_abandoned, json_group_object(role, coalesce(name, 'null')) as role_names, json_group_object(role, score) as role_scores from @@ -3116,7 +3118,7 @@ app.get("/tm/pool/:pool_name", function (req, res) { players = TM_SELECT_PLAYERS_MP.all(pool_id) let games = TM_SELECT_GAMES.all(pool_id) let games_by_round = object_group_by(games, "round") - res.render("tm_pool.pug", { user: req.user, seed, pool, roles, players, games_by_round }) + res.render("tm_pool.pug", { user: req.user, seed, pool, roles, players, games, games_by_round }) }) app.post("/api/tm/register/:seed_id", must_be_logged_in, function (req, res) { @@ -3647,20 +3649,27 @@ function on_action(socket, action, args, cookie) { function on_resign(socket) { SLOG(socket, "RESIGN") try { - do_resign(socket.game_id, socket.role, "resigned") + do_resign(socket.game_id, socket.role) } catch (err) { console.log(err) return send_message(socket, "error", err.toString()) } } -function do_resign(game_id, role, how) { +function do_timeout(game_id, role) { let game = SQL_SELECT_GAME.get(game_id) let state = get_game_state(game_id) let old_active = state.active + state = finish_game_state(game.title_id, state, "None", role + " timed out.") + put_new_state(game.title_id, game_id, state, old_active, role, ".timeout", null) +} - let result = "None" +function do_resign(game_id, role) { + let game = SQL_SELECT_GAME.get(game_id) + let state = get_game_state(game_id) + let old_active = state.active + let result = "None" let roles = get_game_roles(game.title_id, game.scenario, game.options) if (game.player_count === 2) { for (let r of roles) @@ -3670,15 +3679,23 @@ function do_resign(game_id, role, how) { result = roles.filter(r => r !== role).join(", ") } - // TODO: clean up - state.state = "game_over" - state.active = "None" - state.result = result - state.victory = role + " " + how + "." - state.log.push("") - state.log.push(state.victory) + state = finish_game_state(game.title_id, state, result, role + " resigned.") + + put_new_state(game.title_id, game_id, state, old_active, role, ".resign", result) +} - put_new_state(game.title_id, game_id, state, old_active, role, ".resign", null) +function finish_game_state(title_id, state, result, message) { + if (typeof RULES[title_id].finish === "function") { + state = RULES[title_id].finish(state, result, message) + } else { + state.state = "game_over" + state.active = "None" + state.result = result + state.victory = message + state.log.push("") + state.log.push(message) + } + return state } function on_query(socket, q, params) { diff --git a/tools/patchgame.js b/tools/patchgame.js index fef07a0..a28ce1f 100755 --- a/tools/patchgame.js +++ b/tools/patchgame.js @@ -97,6 +97,34 @@ function dont_snap(rules, state, old_active) { return false } +function get_game_roles(rules, scenario, options) { + let roles = rules.roles + if (typeof roles === "function") { + if (typeof options === "string") + options = JSON.parse(options) + return roles(scenario, options) + } + return roles +} + +function get_resign_result(roles, role) { + return roles.filter(r => r !== role).join(", ") +} + +function finish_game(rules, state, result, message) { + if (typeof rules.finish === "function") { + state = RULES[title_id].finish(state, result, message) + } else { + state.state = "game_over" + state.active = "None" + state.result = result + state.victory = message + state.log.push("") + state.log.push(message) + } + return state +} + function patch_game(game_id, {validate_actions=true, save_snaps=true, delete_undo=false, delete_invalid=false}, verbose) { let game = select_game.get(game_id) if (!game) { @@ -106,6 +134,7 @@ function patch_game(game_id, {validate_actions=true, save_snaps=true, delete_und let title_id = game.title_id let rules = require("../public/" + title_id + "/rules.js") + let roles = get_game_roles(rules, game.scenario, game.options) let replay = select_replay.all(game_id) if (replay.length === 0) @@ -129,8 +158,13 @@ function patch_game(game_id, {validate_actions=true, save_snaps=true, delete_und case ".setup": state = rules.setup(...args) break + case ".timeout": + finish_game(rules, state, "None", item.role + " timed out.") + break + case ".abandon": + finish_game(rules, state, "None", item.role + " abandoned the game.") case ".resign": - state = rules.resign(state, item.role) + finish_game(rules, state, get_resign_result(roles, item.role), item.role + " resigned.") break default: if (validate_actions) { diff --git a/views/tm_pool.pug b/views/tm_pool.pug index 1690535..e6c7715 100644 --- a/views/tm_pool.pug +++ b/views/tm_pool.pug @@ -49,9 +49,9 @@ html tr td Started td= human_date(pool.start_date) - tr - td Finished - if pool.finish_date + if pool.finish_date + tr + td Finished td= human_date(pool.finish_date) if seed.player_count === 2 @@ -84,7 +84,10 @@ html if ix > 0 |   if gs[1] === null - a.black(href="/join/" + gs[0]) − + if games.find(game => game.game_id === gs[0]).is_abandoned + a.black(href="/join/" + gs[0]) × + else + a.black(href="/join/" + gs[0]) − else a.black(href="/join/" + gs[0])= gs[1] td.r= row.points @@ -116,7 +119,10 @@ html each gs in result td.c if gs[1] === null - a.black(href="/join/" + gs[0]) − + if games.find(game => game.game_id === gs[0]).is_abandoned + a.black(href="/join/" + gs[0]) × + else + a.black(href="/join/" + gs[0]) − else a.black(href="/join/" + gs[0])= gs[1] td.r= row.points @@ -150,10 +156,16 @@ html if game.status > 1 td.w.r - each role, ix in roles - if ix > 0 - |  :  - | #{role_scores[role]} + if game.is_abandoned + | None + else + each role, ix in roles + if ix > 0 + |  :  + if role_scores[role] === null + | × + else + | #{role_scores[role]} else td.r if game.status > 0 -- cgit v1.2.3