summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2024-07-23 21:18:08 +0200
committerTor Andersson <tor@ccxvii.net>2024-08-20 13:56:22 +0200
commit5b665a840bd3193c9be7b21fbe6f66e119e92d74 (patch)
treedb77f95672db121ee608b9ddcd57a2f7434ce5b4
parentcb73d0710b0eb84d4f788ff9b1d36ed0b02bf970 (diff)
downloadserver-5b665a840bd3193c9be7b21fbe6f66e119e92d74.tar.gz
New time control.
-rw-r--r--public/docs/tips.html43
-rw-r--r--public/join.js13
-rw-r--r--schema.sql24
-rw-r--r--server.js79
-rw-r--r--views/create.pug24
-rw-r--r--views/head.pug8
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.
+<p>
+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.
+
<dl>
-<dt> Live
+
+<dt> No time control
<dd>
-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.
-<dt> Fast
+<dt>
+Live / Blitz (7+ moves per day)
+<dd>
+24h +4h/move up to 72h
<dd>
-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.
-<dt> Slow
+<dt>
+Fast (3+ moves per day)
+<dd>
+48h +12h/move up to 120h
<dd>
-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.
-<dt> Any
+<dt>
+Slow
+(1+ move per day)
<dd>
-Anything goes. Read the game notice and adapt!
+72h +36h/move up to 240h
</dl>
+<p>
+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.
+
+<p>
+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 <i>&ndash; many moves per day</i>
-
- br
- label
- input(type="radio" name="pace" value="3")
- | #{EMOJI_SLOW} Slow <i>&ndash; one move per day</i>
+ 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