manual start of private races - for #9276

requires lila-ws update
pull/9218/head^2
Thibault Duplessis 2021-06-28 12:31:24 +02:00
parent 5da1d4575e
commit 14904c0776
10 changed files with 63 additions and 9 deletions

View File

@ -71,6 +71,7 @@ object racer {
s.raceComplete,
s.spectating,
s.joinTheRace,
s.startTheRace,
s.yourRankX,
s.waitForRematch,
s.nextRace,

View File

@ -2128,6 +2128,7 @@ val `waitingForMorePlayers` = new I18nKey("storm:waitingForMorePlayers")
val `raceComplete` = new I18nKey("storm:raceComplete")
val `spectating` = new I18nKey("storm:spectating")
val `joinTheRace` = new I18nKey("storm:joinTheRace")
val `startTheRace` = new I18nKey("storm:startTheRace")
val `yourRankX` = new I18nKey("storm:yourRankX")
val `waitForRematch` = new I18nKey("storm:waitForRematch")
val `nextRace` = new I18nKey("storm:nextRace")

View File

@ -39,7 +39,7 @@ final class RacerApi(colls: RacerColls, selector: StormSelector, userRepo: UserR
.make(
owner = player,
puzzles = puzzles.grouped(2).flatMap(_.headOption).toList,
countdownSeconds = 10
countdownSeconds = 5
)
store.put(race.id, race)
lila.mon.racer.race(lobby = race.isLobby).increment()
@ -69,18 +69,23 @@ final class RacerApi(colls: RacerColls, selector: StormSelector, userRepo: UserR
def join(id: RacerRace.Id, player: RacerPlayer.Id): Option[RacerRace] =
get(id).flatMap(_ join player) map { r =>
val race = start(r) | r
val race = (r.isLobby ?? doStart(r)) | r
saveAndPublish(race)
race
}
private def start(race: RacerRace): Option[RacerRace] = race.startCountdown.map { starting =>
system.scheduler.scheduleOnce(RacerRace.duration.seconds + race.countdownSeconds.seconds + 50.millis) {
finish(race.id)
}
starting
private[racer] def manualStart(race: RacerRace): Unit = !race.isLobby ?? {
doStart(race) foreach saveAndPublish
}
private def doStart(race: RacerRace): Option[RacerRace] =
race.startCountdown.map { starting =>
system.scheduler.scheduleOnce(RacerRace.duration.seconds + race.countdownSeconds.seconds + 50.millis) {
finish(race.id)
}
starting
}
private def finish(id: RacerRace.Id): Unit =
get(id) foreach { race =>
lila.mon.racer.players(lobby = race.isLobby).record(race.players.size)

View File

@ -27,7 +27,8 @@ final class RacerJson(stormJson: StormJson, sign: StormSign, lightUserSync: Ligh
.add("lobby", race.isLobby),
"player" -> player,
"puzzles" -> race.puzzles
) ++ state(race)
)
.add("owner", race.owner == player.id) ++ state(race)
// socket updates
def state(race: RacerRace) = Json

View File

@ -1,5 +1,6 @@
package lila.racer
import lila.room.RoomSocket.{ Protocol => RP, _ }
import lila.socket.RemoteSocket.{ Protocol => P, _ }
import play.api.libs.json.{ JsObject, Json }
@ -28,6 +29,12 @@ final private class RacerSocket(
api.join(raceId, playerId).unit
case Protocol.In.PlayerScore(raceId, playerId, score) =>
api.registerPlayerScore(raceId, playerId, score)
case Protocol.In.RaceStart(raceId, playerId) =>
api
.get(raceId)
.filter(_.startsAt.isEmpty)
.filter(_.owner == playerId)
.foreach(api.manualStart)
}
remoteSocketApi.subscribe("racer-in", Protocol.In.reader)(
@ -45,6 +52,7 @@ object RacerSocket {
case class PlayerJoin(race: RacerRace.Id, player: RacerPlayer.Id) extends P.In
case class PlayerScore(race: RacerRace.Id, player: RacerPlayer.Id, score: Int) extends P.In
case class RaceStart(race: RacerRace.Id, player: RacerPlayer.Id) extends P.In
val reader: P.In.Reader = raw => raceReader(raw) orElse RP.In.reader(raw)
@ -58,6 +66,10 @@ object RacerSocket {
raw.get(3) { case Array(raceId, playerId, scoreStr) =>
scoreStr.toIntOption map { PlayerScore(RacerRace.Id(raceId), RacerPlayer.Id(playerId), _) }
}
case "racer/start" =>
raw.get(2) { case Array(raceId, playerId) =>
RaceStart(RacerRace.Id(raceId), RacerPlayer.Id(playerId)).some
}
case _ => none
}
}

View File

@ -38,6 +38,7 @@
<string name="raceComplete">Race complete!</string>
<string name="spectating">Spectating</string>
<string name="joinTheRace">Join the race!</string>
<string name="startTheRace">Start the race</string>
<string name="yourRankX">Your rank: %s</string>
<string name="waitForRematch">Wait for rematch</string>
<string name="nextRace">Next race</string>

View File

@ -54,6 +54,10 @@
}
}
.puz-side__start {
margin-top: 1em;
}
.puz-clock {
flex-flow: row wrap;
justify-content: space-between;

View File

@ -95,6 +95,8 @@ export default class StormCtrl {
isRacing = () => this.status() == 'racing';
isOwner = () => this.data.owner;
myScore = (): number | undefined => {
const p = this.data.players.find(p => p.name == this.data.player.name);
return p?.score;
@ -104,6 +106,10 @@ export default class StormCtrl {
if (!this.isPlayer()) this.socketSend('racerJoin');
});
start = throttle(1000, () => {
if (this.isOwner()) this.socketSend('racerStart');
});
countdownSeconds = (): number | undefined =>
this.status() == 'pre' && this.vm.startsAt && this.vm.startsAt > new Date()
? Math.min(9, Math.ceil((this.vm.startsAt.getTime() - Date.now()) / 1000))

View File

@ -5,6 +5,8 @@ export type RaceStatus = 'pre' | 'racing' | 'post';
export type WithGround = <A>(f: (g: CgApi) => A) => A | false;
export type PlayerId = string;
export interface RacerOpts {
data: RacerData;
pref: RacerPrefs;
@ -22,6 +24,7 @@ export interface RacerData extends UpdatableData {
race: Race;
puzzles: Puzzle[];
player: Player;
owner?: boolean;
}
export interface Race {

View File

@ -45,7 +45,7 @@ const selectScreen = (ctrl: RacerCtrl): MaybeVNodes => {
: [
waitingToStart(noarg),
h('div.racer__pre__message', [
ctrl.raceFull() ? undefined : ctrl.isPlayer() ? renderLink(ctrl) : renderJoin(ctrl),
...(ctrl.raceFull() ? [] : ctrl.isPlayer() ? [renderLink(ctrl), renderStart(ctrl)] : [renderJoin(ctrl)]),
povMsg,
]),
comboZone(ctrl),
@ -150,6 +150,26 @@ const renderLink = (ctrl: RacerCtrl) =>
]),
]);
const renderStart = (ctrl: RacerCtrl) =>
ctrl.isOwner() && !ctrl.vm.startsAt
? h(
'div.puz-side__start',
h(
'button.button.button-fat',
{
class: {
disabled: ctrl.players().length < 2,
},
hook: bind('click', ctrl.start),
attrs: {
disabled: ctrl.players().length < 2,
},
},
ctrl.trans.noarg('startTheRace')
)
)
: null;
const renderJoin = (ctrl: RacerCtrl) =>
h(
'div.puz-side__join',