From 5b665a840bd3193c9be7b21fbe6f66e119e92d74 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Tue, 23 Jul 2024 21:18:08 +0200 Subject: New time control. --- public/docs/tips.html | 43 +++++++++++++++++++--------- public/join.js | 13 +++++---- schema.sql | 24 ++++------------ server.js | 79 +++++++++++++++++++++++++++++++++------------------ views/create.pug | 24 ++++------------ views/head.pug | 8 +++--- 6 files changed, 103 insertions(+), 88 deletions(-) diff --git a/public/docs/tips.html b/public/docs/tips.html index 1faa5eb..8e331c7 100644 --- a/public/docs/tips.html +++ b/public/docs/tips.html @@ -82,27 +82,44 @@ Use this if you need to check something on the map that is obscured. For everyone's enjoyment, please respect the pace requests! If someone wants a fast game, don't join if you can only play one move per day. +

+Each player has a clock that ticks down while it's their turn. +When the clock runs out, they forfeit the game. +Each time a player takes a move, they get some time back on the clock, but there's a limit to how +much time you can accumulate. +

-
Live + +
No time control
-Let your opponents know if you're going away! -If you need to resume the game another day, arrange a time when you can all continue, -or agree to continue playing at a different pace. +For friendly games without an enforced pace. -
Fast +
+Live / Blitz (7+ moves per day) +
+24h +4h/move up to 72h
-Turn on notifications so you can take your turns promptly. -If you see that your opponent is online (the dot next to their name is filled in) -then stay in the game for a while and perhaps you can play live for a bit. -
Slow +
+Fast (3+ moves per day) +
+48h +12h/move up to 120h
-If you create a game and know you can not play more than one move per day, -please pick this option to set the appropriate expectations. -
Any +
+Slow +(1+ move per day)
-Anything goes. Read the game notice and adapt! +72h +36h/move up to 240h
+

+When playing live, let your opponents know if you're going away! +If you need to resume a live game another day, schedule a time when you can continue. + +

+Turn on notifications so you can take your turns promptly. +If you see that your opponent is online (the dot next to their name is filled in) +then stay in the game for a while and perhaps you can play live for a bit. + diff --git a/public/join.js b/public/join.js index 08e56fb..81fc665 100644 --- a/public/join.js +++ b/public/join.js @@ -2,11 +2,11 @@ /* global game, roles, players, blacklist, user_id */ -const pace_text = [ - "", - "Live!", - "Fast \u2013 many moves per day", - "Slow \u2013 one move per day", +const PACE_TEXT = [ + "No time control", + "7+ moves per day", + "3+ moves per day", + "1+ moves per day", ] let start_status = 0 @@ -321,7 +321,8 @@ function create_game_list() { if (game.scenario !== "Standard") create_game_list_item(list, "Scenario", game.scenario) create_game_list_item(list, "Options", format_options(game.options)) - create_game_list_item(list, "Pace", pace_text[game.pace]) + if (game.pace > 0) + create_game_list_item(list, "Pace", PACE_TEXT[game.pace]) create_game_list_item(list, "Notice", game.notice) if (game.owner_id) diff --git a/schema.sql b/schema.sql index 1d3641c..9df5b93 100644 --- a/schema.sql +++ b/schema.sql @@ -452,8 +452,7 @@ create table if not exists players ( role text, user_id integer, is_invite integer, - time_used real, - time_added real, + clock real, score integer, primary key (game_id, role) ) without rowid; @@ -505,14 +504,10 @@ create view player_view as end ) as is_active, ( - case when pace = 0 then - 21.0 - (julianday() - julianday(mtime)) + case when active in ( 'Both', role ) then + clock - (julianday() - julianday(mtime)) else - case when active in ( 'Both', role ) then - 10.0 - ((julianday() - julianday(mtime)) + time_used - time_added) - else - 10.0 - (time_used - time_added) - end + clock end ) as time_left, atime @@ -527,15 +522,7 @@ drop view if exists time_control_view; create view time_control_view as select game_id, - user_id, role, - ( - case when pace = 0 then - 21.0 - (julianday() - julianday(mtime)) - else - 10.0 - ((julianday() - julianday(mtime)) + time_used - time_added) - end - ) as time_left, is_opposed from games @@ -543,6 +530,7 @@ create view time_control_view as where status = 1 and active in ( 'Both', role ) + and clock - (julianday() - julianday(mtime)) < 0 ; drop view if exists your_turn_reminder; @@ -689,7 +677,7 @@ drop trigger if exists trigger_time_used_update; create trigger trigger_time_used_update before update of active on games begin update players - set time_used = time_used + (julianday() - julianday(old.mtime)) + set clock = clock - (julianday() - julianday(old.mtime)) where players.game_id = old.game_id and players.role in ( 'Both', old.active ); end; diff --git a/server.js b/server.js index 82adb93..07b9827 100644 --- a/server.js +++ b/server.js @@ -178,15 +178,19 @@ app.locals.ENABLE_FORUM = process.env.FORUM | 0 app.locals.EMOJI_PRIVATE = "\u{1F512}" // or 512 app.locals.EMOJI_MATCH = "\u{1f3c6}" -app.locals.EMOJI_LIVE = "\u{1f465}" -app.locals.EMOJI_FAST = "\u{1f3c1}" -app.locals.EMOJI_SLOW = "\u{1f40c}" - -const PACE_ICON = [ - "", - app.locals.EMOJI_LIVE, - app.locals.EMOJI_FAST, - app.locals.EMOJI_SLOW + +app.locals.PACE_ICON = [ + "", // none + "\u{26a1}", // blitz + "\u{1f3c1}", // fast + "\u{1f40c}", // slow +] + +app.locals.PACE_TEXT = [ + "No time control", + "7+ moves per day", + "3+ moves per day", + "1+ moves per day", ] app.set("x-powered-by", false) @@ -1130,13 +1134,6 @@ const STATUS_ACTIVE = 1 const STATUS_FINISHED = 2 const STATUS_ARCHIVED = 3 -const PACE_ANY = 0 -const PACE_LIVE = 1 -const PACE_FAST = 2 -const PACE_SLOW = 3 - -const PACE_NAME = [ "Any", "Live", "Fast", "Slow" ] - const PARSE_OPTIONS_CACHE = {} const HUMAN_OPTIONS_CACHE = { @@ -1321,13 +1318,6 @@ const SQL_SELECT_GAME_NOTE = SQL("SELECT note FROM game_notes WHERE game_id=? AN const SQL_UPDATE_GAME_NOTE = SQL("INSERT OR REPLACE INTO game_notes (game_id,role,note) VALUES (?,?,?)") const SQL_DELETE_GAME_NOTE = SQL("DELETE FROM game_notes WHERE game_id=? AND role=?") -const SQL_UPDATE_PLAYERS_ADD_TIME = SQL(` - update players - set time_added = min(time_used, time_added + 1.5) - where - players.game_id = ? and players.role = ? -`) - const SQL_INSERT_REPLAY = SQL("insert into game_replay (game_id,replay_id,role,action,arguments) values (?, (select coalesce(max(replay_id), 0) + 1 from game_replay where game_id=?) ,?,?,?) returning replay_id").pluck() const SQL_INSERT_SNAP = SQL("insert into game_snap (game_id,snap_id,replay_id,state) values (?, (select coalesce(max(snap_id), 0) + 1 from game_snap where game_id=?), ?, ?) returning snap_id").pluck() @@ -1384,7 +1374,7 @@ const SQL_UPDATE_PLAYER_ACCEPT = SQL("UPDATE players SET is_invite=0 WHERE game_ const SQL_UPDATE_PLAYER_ROLE = SQL("UPDATE players SET role=? WHERE game_id=? AND role=? AND user_id=?") const SQL_SELECT_PLAYER_ROLE = SQL("SELECT role FROM players WHERE game_id=? AND user_id=?").pluck() const SQL_SELECT_PLAYER_NAME = SQL("SELECT name FROM players JOIN users using(user_id) WHERE game_id=? AND role=?").pluck() -const SQL_INSERT_PLAYER_ROLE = SQL("INSERT OR IGNORE INTO players (game_id,role,user_id,is_invite,time_used,time_added) VALUES (?,?,?,?,0,0)") +const SQL_INSERT_PLAYER_ROLE = SQL("INSERT OR IGNORE INTO players (game_id,role,user_id,is_invite) VALUES (?,?,?,?)") const SQL_DELETE_PLAYER_ROLE = SQL("DELETE FROM players WHERE game_id=? AND role=?") const SQL_SELECT_PLAYER_VIEW = SQL("select * from player_view where game_id = ?") @@ -1930,7 +1920,7 @@ app.get("/join/:game_id", function (req, res) { if (game.is_match) icon += app.locals.EMOJI_MATCH if (game.pace) - icon += PACE_ICON[game.pace] + icon += app.locals.PACE_ICON[game.pace] res.render("join.pug", { user: req.user, @@ -2121,6 +2111,8 @@ function start_game(game) { put_snap(game.game_id, replay_id, state) SQL_INSERT_GAME_STATE.run(game.game_id, JSON.stringify(state)) + SQL_UPDATE_PLAYERS_INIT_TIME.run(game.game_id) + SQL_COMMIT.run() } finally { if (db.inTransaction) @@ -2617,14 +2609,45 @@ setTimeout(purge_game_ticker, 89 * 1000) * TIME CONTROL */ -const QUERY_LIST_TIME_CONTROL = SQL("select * from time_control_view join users using(user_id) where time_left < 0") +const SQL_UPDATE_PLAYERS_INIT_TIME = SQL(` + update players + set clock = ( + case (select pace from games where games.game_id = players.game_id) + when 1 then 1 + when 2 then 2 + when 3 then 3 + else 14 + end + ) + where + players.game_id = ? +`) + +const SQL_UPDATE_PLAYERS_ADD_TIME = SQL(` + update players + set clock = ( + case (select pace from games where games.game_id = players.game_id) + when 1 then min(clock + ${4 / 24}, 3) + when 2 then min(clock + ${12 / 24}, 5) + when 3 then min(clock + ${36 / 24}, 10) + else 14 + end + ) + where + players.game_id = ? and players.role = ? +`) + +// SQL_UPDATE_PLAYERS_USE_TIME is handled by trigger + +const SQL_SELECT_TIME_CONTROL = SQL("select * from time_control_view") function time_control_ticker() { - for (let item of QUERY_LIST_TIME_CONTROL.all()) { + for (let item of SQL_SELECT_TIME_CONTROL.all()) { if (item.is_opposed) { - console.log("TIMED OUT GAME:", item.game_id, item.name, item.time_left) + console.log("TIMED OUT GAME:", item.game_id, item.role) do_resign(item.game_id, item.role, "timed out") } else { + console.log("TIMED OUT GAME:", item.game_id, item.role, "(solo)") SQL_DELETE_GAME.run(item.game_id) } } diff --git a/views/create.pug b/views/create.pug index 912156c..d45903e 100644 --- a/views/create.pug +++ b/views/create.pug @@ -49,25 +49,11 @@ html input(type="text" autocomplete="off" name="notice" size=45 placeholder="What are you looking for?") p Pace: - br - label - input(type="radio" name="pace" value="0" checked) - | Any - - br - label - input(type="radio" name="pace" value="1") - | #{EMOJI_LIVE} Live - - br - label - input(type="radio" name="pace" value="2") - | #{EMOJI_FAST} Fast – many moves per day - - br - label - input(type="radio" name="pace" value="3") - | #{EMOJI_SLOW} Slow – one move per day + each text, pace in PACE_TEXT + br + label + input(type="radio" name="pace" value=pace checked=pace===0) + | #{PACE_ICON[pace]} #{text} p label diff --git a/views/head.pug b/views/head.pug index e4ff857..00e0ada 100644 --- a/views/head.pug +++ b/views/head.pug @@ -62,10 +62,10 @@ mixin gamelist(list,hide_title=0) else if (item.status === 3) className += " archived" if (item.is_unread) chat_icon = "\u{1f4dd}" if (item.is_private) pace_icon += EMOJI_PRIVATE - if (item.is_match) pace_icon += EMOJI_MATCH - else if (item.pace === 1) pace_icon += EMOJI_LIVE, pace_text = "Live!" - else if (item.pace === 2) pace_icon += EMOJI_FAST, pace_text = "Fast - many moves per day" - else if (item.pace === 3) pace_icon += EMOJI_SLOW, pace_text = "Slow - one move per day" + if (item.is_match) + pace_icon += EMOJI_MATCH + else if (item.pace > 0) + pace_icon += PACE_ICON[item.pace], pace_text = PACE_TEXT[item.pace] div(class=className) div.game_head -- cgit v1.2.3