From d0261cdee3fe77453db490a2424c0bf4a7a3d2a1 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Wed, 12 Mar 2014 20:35:22 +0100 Subject: [PATCH] implement hold alerts --- app/views/analyse/replay.scala.html | 1 + app/views/round/holdAlerts.scala.html | 12 +++++++++++ modules/game/src/main/GameRepo.scala | 13 ++++++++++++ modules/game/src/main/Player.scala | 11 ++++++++++ modules/round/src/main/Round.scala | 7 +++++++ modules/round/src/main/SocketHandler.scala | 5 +++++ modules/round/src/main/actorApi.scala | 1 + public/javascripts/big.js | 24 +++++++++++++++++++++- 8 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 app/views/round/holdAlerts.scala.html diff --git a/app/views/analyse/replay.scala.html b/app/views/analyse/replay.scala.html index cd0c00ee65..20ad5abf17 100644 --- a/app/views/analyse/replay.scala.html +++ b/app/views/analyse/replay.scala.html @@ -72,6 +72,7 @@ themepicker = true) {
@round.blurs(game) + @round.holdAlerts(game) @analysis.filter(_.done).map { a => @for((color, pairs) <- a.summary) { diff --git a/app/views/round/holdAlerts.scala.html b/app/views/round/holdAlerts.scala.html new file mode 100644 index 0000000000..cf67259de4 --- /dev/null +++ b/app/views/round/holdAlerts.scala.html @@ -0,0 +1,12 @@ +@(game: Game)(implicit ctx: Context) + +@if(isGranted(_.ViewBlurs)) { +@game.players.map { p => +@p.holdAlert.map { h => +
+ @playerLink(p, cssClass = p.color.name.some) hold alert
+ (ply: @h.ply, mean: @h.mean ms, SD: @h.sd ms)
+
+} +} +} diff --git a/modules/game/src/main/GameRepo.scala b/modules/game/src/main/GameRepo.scala index 217190b5b5..5390e3ced7 100644 --- a/modules/game/src/main/GameRepo.scala +++ b/modules/game/src/main/GameRepo.scala @@ -106,6 +106,19 @@ trait GameRepo { def incBookmarks(id: ID, value: Int) = $update($select(id), $incBson("bm" -> value)) + def setHoldAlert(pov: Pov, mean: Int, sd: Int) = { + import Player.holdAlertBSONHandler + $update( + $select(pov.gameId), + BSONDocument( + "$set" -> BSONDocument( + s"p${pov.color.fold(0, 1)}.${Player.BSONFields.holdAlert}" -> + Player.HoldAlert(ply = pov.game.turns, mean = mean, sd = sd) + ) + ) + ).void + } + def finish(id: ID, winnerColor: Option[Color], winnerId: Option[String]) = $update( $select(id), BSONDocument( diff --git a/modules/game/src/main/Player.scala b/modules/game/src/main/Player.scala index bc13e8c222..2c1c5b34c6 100644 --- a/modules/game/src/main/Player.scala +++ b/modules/game/src/main/Player.scala @@ -17,6 +17,7 @@ case class Player( rating: Option[Int] = None, ratingDiff: Option[Int] = None, blurs: Int = 0, + holdAlert: Option[Player.HoldAlert] = None, name: Option[String] = None) { def encodePieces(allPieces: Iterable[(Pos, Piece, Boolean)]): String = @@ -43,6 +44,8 @@ case class Player( def wins = isWinner getOrElse false + def hasHoldAlert = holdAlert.isDefined + def finish(winner: Boolean) = copy( isWinner = if (winner) Some(true) else None ) @@ -85,6 +88,11 @@ object Player { def black = make(Color.Black, None) + case class HoldAlert(ply: Int, mean: Int, sd: Int) + + import reactivemongo.bson.Macros + implicit val holdAlertBSONHandler = Macros.handler[HoldAlert] + object BSONFields { val aiLevel = "ai" @@ -95,6 +103,7 @@ object Player { val rating = "e" val ratingDiff = "d" val blurs = "b" + val holdAlert = "h" val name = "na" } @@ -123,6 +132,7 @@ object Player { rating = r intO rating, ratingDiff = r intO ratingDiff, blurs = r intD blurs, + holdAlert = r.getO[HoldAlert](holdAlert), name = r strO name) def writes(w: BSON.Writer, o: Builder) = @@ -136,6 +146,7 @@ object Player { rating -> p.rating, ratingDiff -> p.ratingDiff, blurs -> w.intO(p.blurs), + holdAlert -> p.holdAlert, name -> p.name) } } diff --git a/modules/round/src/main/Round.scala b/modules/round/src/main/Round.scala index 12394692cf..7e206e5981 100644 --- a/modules/round/src/main/Round.scala +++ b/modules/round/src/main/Round.scala @@ -100,6 +100,13 @@ private[round] final class Round( } } + case HoldAlert(playerId, mean, sd) => handle(playerId) { pov => + !pov.player.hasHoldAlert ?? { + play.api.Logger("round").info(s"Hold alert $pov mean: $mean SD: $sd") + GameRepo.setHoldAlert(pov, mean, sd) inject List[Event]() + } + } + case RematchYes(playerRef) => handle(playerRef)(rematcher.yes) case RematchNo(playerRef) => handle(playerRef)(rematcher.no) diff --git a/modules/round/src/main/SocketHandler.scala b/modules/round/src/main/SocketHandler.scala index c3dcab9657..0aaf4cf62c 100644 --- a/modules/round/src/main/SocketHandler.scala +++ b/modules/round/src/main/SocketHandler.scala @@ -75,6 +75,11 @@ private[round] final class SocketHandler( case ("talk", o) => o str "d" foreach { text => messenger.owner(gameId, member, text, socket) } + case ("hold", o) => for { + d ← o obj "d" + mean ← d int "mean" + sd ← d int "sd" + } round(HoldAlert(playerId, mean, sd)) } } } diff --git a/modules/round/src/main/actorApi.scala b/modules/round/src/main/actorApi.scala index 260e4f3b5d..473aabc3bb 100644 --- a/modules/round/src/main/actorApi.scala +++ b/modules/round/src/main/actorApi.scala @@ -102,4 +102,5 @@ package round { case object Outoftime case object Abandon case class Cheat(color: Color) + case class HoldAlert(playerId: String, mean: Int, sd: Int) } diff --git a/public/javascripts/big.js b/public/javascripts/big.js index eae7d9834c..ade6ec49be 100644 --- a/public/javascripts/big.js +++ b/public/javascripts/big.js @@ -854,6 +854,8 @@ var storage = { self.initialTitle = document.title; self.hasMovedOnce = false; self.premove = null; + self.holdStart = null; + self.holds = []; self.options.tableUrl = self.element.data('table-url'); self.options.endUrl = self.element.data('end-url'); self.options.socketUrl = self.element.data('socket-url'); @@ -1306,7 +1308,7 @@ var storage = { if (self.options.game.finished || self.options.player.spectator) { return; } - var draggingKey = null; + var draggingKey; var dropped = false; // init squares self.$board.find("div.lcs").each(function() { @@ -1322,6 +1324,7 @@ var storage = { }, drop: function(ev, ui) { self.dropPiece(ui.draggable, ui.draggable.parent(), $(this)); + self.addHold(); dropped = true; }, hoverClass: 'droppable-hover' @@ -1338,6 +1341,7 @@ var storage = { }, start: function() { draggingKey = $this.hide().parent().attr('id'); + self.holdStart = Date.now(); dropped = false; self.unselect(); }, @@ -1364,6 +1368,7 @@ var storage = { self.unselect(); if (isSelected) return; $square.addClass('selected'); + self.holdStart = Date.now(); }); }); @@ -1394,6 +1399,7 @@ var storage = { $to.removeClass('selectable'); self.dropPiece($piece, $from, $this); } + self.addHold(); }); }); @@ -1401,6 +1407,22 @@ var storage = { * End of code for touch screens */ }, + addHold: function() { + if (this.holdStart) { + var nb = 10; + this.holds.push(Date.now() - this.holdStart); + this.holdStart = null; + if (this.holds.length > nb) { + this.holds.shift(); + var mean = this.holds.reduce(function(a, b) { return a + b; }) / nb; + if (mean < 80) { + var diffs = this.holds.map(function(a) { return Math.pow(a - mean, 2); }); + var sd = Math.sqrt(diffs.reduce(function(a, b) { return a + b; }) / nb); + if (sd < 10) lichess.socket.send('hold', { mean: Math.round(mean), sd: Math.round(sd) }); + } + } + } + }, reloadTable: function(callback) { var self = this; self.get(self.options.tableUrl, {