Merge branch 'master' into yarn

This commit is contained in:
Isaac Levy 2017-05-19 19:04:19 -04:00 committed by GitHub
commit f8eea774eb
18 changed files with 143 additions and 104 deletions

View file

@ -49,6 +49,10 @@ jobs:
- stage: test
language: node_js
node_js: "4"
addons:
apt:
packages:
- parallel
before_install: yarn global add gulp-cli
script: ./ui/build prod
after_success:

View file

@ -34,7 +34,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
val speedAndClock =
if (game.imported) "imported"
else game.clock.fold(chess.Speed.Correspondence.name) { c =>
s"${chess.Speed(c.config).name} (${c.show})"
s"${chess.Speed(c.config).name} (${c.config.show})"
}
val mode = game.mode.name
val variant = if (game.variant == chess.variant.FromPosition) "position setup chess"
@ -174,7 +174,7 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
private def gameTitle(game: Game, color: Color): String = {
val u1 = playerText(game player color, withRating = true)
val u2 = playerText(game opponent color, withRating = true)
val clock = game.clock ?? { c => " • " + c.show }
val clock = game.clock ?? { c => " • " + c.config.show }
val variant = game.variant.exotic ?? s"${game.variant.name}"
s"$u1 vs $u2$clock$variant"
}

View file

@ -17,7 +17,7 @@
@game.variant.name.toUpperCase
}
} else {
@game.clock.map(_.show).getOrElse {
@game.clock.map(_.config.show).getOrElse {
@game.daysPerTurn.map { days =>
<span data-hint="@trans.correspondence()" class="hint--top">@{(days == 1).fold(trans.oneDay(), trans.nbDays(days))}</span>
}.getOrElse {

View file

@ -48,7 +48,7 @@
@g.variant.name.toUpperCase
}
} else {
@g.clock.map(_.show).getOrElse {
@g.clock.map(_.config.show).getOrElse {
@g.daysPerTurn.map { days =>
<span data-hint="@trans.correspondence()" class="hint--top">@if(days == 1) {@trans.oneDay()} else {@trans.nbDays(days)}</span>
}.getOrElse {

View file

@ -9,7 +9,7 @@
@setup.select(form("timeMode"), translatedTimeModeChoices)
</div>
<div class="time_choice slider">
@trans.minutesPerSide(): <span>@(chess.Clock.showLimit(~form("time").value.map(x => (x.toDouble * 60).toInt)))</span>
@trans.minutesPerSide(): <span>@(chess.Clock.Config(~form("time").value.map(x => (x.toDouble * 60).toInt), 0).limitString)</span>
@setup.input(form("time"))
</div>
<div class="increment_choice slider">

View file

@ -31,7 +31,7 @@
<div class="game_legend">
@playerText(pov.opponent, withRating = true)
@pov.game.clock.map { c =>
• @c.show
• @c.config.show
}
</div>
}

@ -1 +1 @@
Subproject commit 5ab69132aaa4203f6280a896a03ec187e566c753
Subproject commit 03014d9b3a2d4d861492c3dbe95f72bf71c156fc

View file

@ -79,60 +79,58 @@ object BinaryFormat {
}.take(turns).map(Centis.apply)(breakOut)
}
case class clock(since: DateTime) {
case class clock(start: Timestamp) {
def write(clock: Clock): ByteArray = {
Array(writeClockLimit(clock.limitSeconds), clock.incrementSeconds.toByte) ++
writeSignedInt24(clock.whiteTime.centis) ++
writeSignedInt24(clock.blackTime.centis) ++
writeTimer(clock.timerOption.fold(0l)(_.value / 10l))
clock.timerOption.fold(Array.empty[Byte])(writeTimer)
}
def read(ba: ByteArray, whiteBerserk: Boolean, blackBerserk: Boolean): Color => Clock = color => ba.value map toInt match {
case Array(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12) =>
readTimer(b9, b10, b11, b12) match {
case 0 => PausedClock(
config = Clock.Config(readClockLimit(b1), b2),
color = color,
whiteTime = Centis(readSignedInt24(b3, b4, b5)),
blackTime = Centis(readSignedInt24(b6, b7, b8)),
whiteBerserk = whiteBerserk,
blackBerserk = blackBerserk
)
case timer => RunningClock(
config = Clock.Config(readClockLimit(b1), b2),
color = color,
whiteTime = Centis(readSignedInt24(b3, b4, b5)),
blackTime = Centis(readSignedInt24(b6, b7, b8)),
whiteBerserk = whiteBerserk,
blackBerserk = blackBerserk,
timer = Timestamp(timer * 10l)
)
def read(ba: ByteArray, whiteBerserk: Boolean, blackBerserk: Boolean): Color => Clock = color => {
val ia = ba.value map toInt
// ba.size might be greater than 12 with 5 bytes timers
// ba.size might be 8 if there was no timer.
// #TODO remove 5 byte timer case! But fix the DB first!
val timer = {
if (ia.size == 12) readTimer(readInt(ia(8), ia(9), ia(10), ia(11)))
else None
}
ia match {
case Array(b1, b2, b3, b4, b5, b6, b7, b8, _*) => {
val config = Clock.Config(readClockLimit(b1), b2)
val whiteTime = Centis(readSignedInt24(b3, b4, b5))
val blackTime = Centis(readSignedInt24(b6, b7, b8))
timer.fold[Clock](
PausedClock(
config = config,
color = color,
whiteTime = whiteTime,
blackTime = blackTime,
whiteBerserk = whiteBerserk,
blackBerserk = blackBerserk
)
)(t =>
RunningClock(
config = config,
color = color,
whiteTime = whiteTime,
blackTime = blackTime,
whiteBerserk = whiteBerserk,
blackBerserk = blackBerserk,
timer = t
))
}
// compatibility with 5 bytes timers
// #TODO remove me! But fix the DB first!
case Array(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, _) =>
PausedClock(
config = Clock.Config(readClockLimit(b1), b2),
color = color,
whiteTime = Centis(readSignedInt24(b3, b4, b5)),
blackTime = Centis(readSignedInt24(b6, b7, b8)),
whiteBerserk = whiteBerserk,
blackBerserk = blackBerserk
)
case _ => sys error s"BinaryFormat.clock.read invalid bytes: ${ba.showBytes}"
case _ => sys error s"BinaryFormat.clock.read invalid bytes: ${ba.showBytes}"
}
}
private def decay = (since.getMillis / 10) - 10
private def writeTimer(timer: Timestamp) = writeInt((timer - start).centis)
private def writeTimer(long: Long) = {
writeInt(math.max(0l, long - decay).toInt)
}
private def readTimer(b1: Int, b2: Int, b3: Int, b4: Int) = {
val l = readInt(b1, b2, b3, b4)
if (l == 0) 0 else l + decay
}
private def readTimer(l: Int) =
if (l != 0) Some(start + Centis(l)) else None
private def writeClockLimit(limit: Int): Byte = {
// The database expects a byte for a limit, and this is limit / 60.
@ -149,6 +147,10 @@ object BinaryFormat {
}
}
object clock {
def apply(start: DateTime) = new clock(Timestamp(start.getMillis))
}
object castleLastMoveTime {
def write(clmt: CastleLastMoveTime): ByteArray = {

View file

@ -220,7 +220,7 @@ case class Game(
binaryMoveTimes.?? { t =>
BinaryFormat.moveTime.read(t, playedTurns)
} :+ {
(Centis(nowCentis - movedAt.getCentis) - ~lag) nonNeg
Centis(nowCentis - movedAt.getCentis) nonNeg
}
}
},
@ -365,7 +365,7 @@ case class Game(
def goBerserk(color: Color) =
clock.ifTrue(berserkable && !player(color).berserk).map { c =>
val newClock = c berserk color
val newClock = c goBerserk color
Progress(this, copy(
clock = Some(newClock),
clockHistory = clockHistory.map(history => {
@ -456,7 +456,7 @@ case class Game(
}
private def outoftimeCorrespondence: Boolean =
playableCorrespondenceClock ?? { _ outoftime player.color }
playableCorrespondenceClock ?? { _ outoftime turnColor }
def isCorrespondence = speed == chess.Speed.Correspondence

View file

@ -2,7 +2,7 @@ package lila.game
import scala.concurrent.duration._
import chess.{ Centis, Clock }
import chess.{ Centis, Clock, White, Black }
import org.specs2.mutable._
import org.specs2.specification._
@ -12,57 +12,82 @@ class BinaryClockTest extends Specification {
val _0_ = "00000000"
val since = org.joda.time.DateTime.now.minusHours(1)
def write(c: Clock): List[String] =
(BinaryFormat.clock(since) write c).showBytes.split(',').toList
def read(bytes: List[String]): Clock =
(BinaryFormat.clock(since).read(ByteArray.parseBytes(bytes), false, false))(chess.White)
def isomorphism(c: Clock): Clock =
(BinaryFormat.clock(since).read(BinaryFormat.clock(since) write c, false, false))(chess.White)
def writeBytes(c: Clock) = BinaryFormat.clock(since) write c
def readBytes(bytes: ByteArray, berserk: Boolean = false): Clock =
(BinaryFormat.clock(since).read(bytes, berserk, false))(White)
def isomorphism(c: Clock): Clock = readBytes(writeBytes(c))
def write(c: Clock): List[String] = writeBytes(c).showBytes.split(',').toList
def read(bytes: List[String]) = readBytes(ByteArray.parseBytes(bytes))
"binary Clock" should {
val clock = Clock(120, 2)
val bits22 = List("00000010", "00000010")
"write" in {
write(clock) must_== {
bits22 ::: List.fill(10)(_0_)
bits22 ::: List.fill(6)(_0_)
}
write(clock.giveTime(chess.White, Centis(3))) must_== {
bits22 ::: List("10000000", "00000000", "00000011") ::: List.fill(7)(_0_)
write(clock.giveTime(White, Centis(3))) must_== {
bits22 ::: List("10000000", "00000000", "00000011") ::: List.fill(3)(_0_)
}
write(clock.giveTime(chess.White, Centis(-3))) must_== {
bits22 ::: List("00000000", "00000000", "00000011") ::: List.fill(7)(_0_)
write(clock.giveTime(White, Centis(-3))) must_== {
bits22 ::: List("00000000", "00000000", "00000011") ::: List.fill(3)(_0_)
}
write(Clock(0, 3)) must_== {
List("00000000", "00000011", "10000000", "00000001", "00101100", "10000000", "00000001", "00101100") ::: List.fill(4)(_0_)
List("00000000", "00000011", "10000000", "00000001", "00101100", "10000000", "00000001", "00101100")
}
}
"read" in {
read(bits22 ::: List.fill(11)(_0_)) must_== {
clock
"with timer" in {
read(bits22 ::: List.fill(11)(_0_)) must_== {
clock
}
read(bits22 ::: List("10000000", "00000000", "00000011") ::: List.fill(8)(_0_)) must_== {
clock.giveTime(White, Centis(3))
}
read(bits22 ::: List("00000000", "00000000", "00000011") ::: List.fill(8)(_0_)) must_== {
clock.giveTime(White, Centis(-3))
}
}
read(bits22 ::: List("10000000", "00000000", "00000011") ::: List.fill(8)(_0_)) must_== {
clock.giveTime(chess.White, Centis(3))
}
read(bits22 ::: List("00000000", "00000000", "00000011") ::: List.fill(8)(_0_)) must_== {
clock.giveTime(chess.White, Centis(-3))
"without timer bytes" in {
read(bits22 ::: List.fill(7)(_0_)) must_== {
clock
}
read(bits22 ::: List("10000000", "00000000", "00000011") ::: List.fill(4)(_0_)) must_== {
clock.giveTime(White, Centis(3))
}
read(bits22 ::: List("00000000", "00000000", "00000011") ::: List.fill(4)(_0_)) must_== {
clock.giveTime(White, Centis(-3))
}
}
}
"isomorphism" in {
isomorphism(clock) must_== clock
val c2 = clock.giveTime(chess.White, Centis.ofSeconds(15))
val c2 = clock.giveTime(White, Centis.ofSeconds(15))
isomorphism(c2) must_== c2
val c3 = clock.giveTime(chess.Black, Centis.ofSeconds(5))
isomorphism(c3) must_== c3
val c4 = clock.start
isomorphism(c4).timerOption.get.value must beCloseTo(c4.timerOption.get.value, 10)
isomorphism(c4).timerOption.get.value must beCloseTo(c4.timer.value, 10)
Clock(120, 60) |> { c =>
isomorphism(c) must_== c
}
"with berserk" in {
val b1 = clock.goBerserk(White)
readBytes(writeBytes(b1), true) must_== b1
val b2 = clock.giveTime(White, Centis(15)).goBerserk(White)
readBytes(writeBytes(b2), true) must_== b2
val b3 = Clock(60, 2).goBerserk(White)
readBytes(writeBytes(b3), true) must_== b3
}
}
}
}

View file

@ -31,7 +31,7 @@ private final class GameJson(
tree = TreeBuilder(game, plies)
} yield Json.obj(
"id" -> game.id,
"clock" -> game.clock.map(_.show),
"clock" -> game.clock.map(_.config.show),
"perf" -> Json.obj(
"icon" -> perfType.iconChar.toString,
"name" -> perfType.name

View file

@ -355,7 +355,7 @@ object JsonView {
"increment" -> c.incrementSeconds,
"white" -> c.remainingTime(Color.White).toSeconds,
"black" -> c.remainingTime(Color.Black).toSeconds,
"emerg" -> c.emergTime
"emerg" -> c.config.emergSeconds
)
}

View file

@ -2,7 +2,7 @@ package lila.round
import chess.format.Forsyth
import chess.variant._
import chess.{ Game => ChessGame, Board, Color => ChessColor, Castles }
import chess.{ Clock, Game => ChessGame, Board, Color => ChessColor, Castles }
import ChessColor.{ White, Black }
import lila.game.{ GameRepo, Game, Event, Progress, Pov, Source, AnonCookie, PerfPicker }
@ -77,7 +77,7 @@ private[round] final class Rematcher(
board = Board(pieces, variant = pov.game.variant).withCastles {
situation.fold(Castles.init)(_.situation.board.history.castles)
},
clock = pov.game.clock map (_.reset),
clock = pov.game.clock map { c => Clock(c.config) },
turns = situation ?? (_.turns),
startedAtTurn = situation ?? (_.turns)
),

View file

@ -45,7 +45,7 @@ object DataForm {
val clockTimesPrivate: Seq[Double] = clockTimes ++ (10d to 30d by 5d) ++ (40d to 60d by 10d)
val clockTimeDefault = 2d
private def formatLimit(l: Double) =
chess.Clock.showLimit(l * 60 toInt) + {
chess.Clock.Config(l * 60 toInt, 0).limitString + {
if (l <= 1) " minute" else " minutes"
}
val clockTimeChoices = optionsDouble(clockTimes, formatLimit)

View file

@ -1,16 +1,15 @@
#!/bin/sh -e
. bin/lilarc
#!/bin/bash -ea
target=${1-dev}
mkdir -p public/compiled
ts_apps="common chess ceval game tree"
ts_apps1="common chess"
ts_apps2="ceval game tree"
apps="site chat2 challenge2 notify2 learn insight editor puzzle round2 analyse lobby tournament tournamentSchedule simul perfStat dasher"
prll_sh=${PRLLSH-/etc/profile.d/prll.sh}
build_ts() {
echo "build_ts" "$@"
set -ex
cd ui/$1
rm -rf node_modules/types
rm -rf node_modules/common
@ -21,8 +20,9 @@ build_ts() {
}
build() {
echo "build" "$@"
set -ex
app=$1
echo "Building $app"
cd ui/$app
rm -rf node_modules/types
rm -rf node_modules/common
@ -35,13 +35,15 @@ build() {
cd -
}
if [ -f $prll_sh ]; then # parallel execution!
export PRLL_NRJOBS="${PRLL_NRJOBS-10}"
echo "Building up to $PRLL_NRJOBS in parallel!"
. $prll_sh
prll build_ts $ts_apps
prll build $apps
if command -v parallel >/dev/null; then # parallel execution!
if [ -z "$P_OPTS" -a ! -e ~/.parallel/config ]; then
P_OPTS="-j+4"
[ "$TRAVIS" = "true" ] || P_OPTS+=" --bar"
fi
parallel --gnu $P_OPTS build_ts ::: $ts_apps1
parallel --gnu $P_OPTS build_ts ::: $ts_apps2
parallel --gnu $P_OPTS build ::: $apps
else # sequential execution
for app in $ts_apps; do build_ts $app; done
for app in $ts_apps1 $ts_apps2; do build_ts $app; done
for app in $apps; do build $app; done
fi

View file

@ -126,8 +126,14 @@ function renderLine(ctrl: Ctrl, line: Line) {
]);
var userNode = thunk('a', line.u, userLink, [line.u]);
return h('li', ctrl.moderation ? [
lineAction(() => ctrl.moderation && line.u && ctrl.moderation.open(line.u.split(' ')[0])),
return h('li', {
hook: ctrl.moderation ? bind('click', (e: Event) => {
const target = e.target as HTMLElement;
if (ctrl.moderation && target.classList.contains('mod'))
ctrl.moderation.open((target.getAttribute('data-username') as string).split(' ')[0]);
}) : {}
}, ctrl.moderation ? [
line.u ? lineAction(line.u) : null,
userNode,
textNode
] : [userNode, textNode]);

View file

@ -52,11 +52,11 @@ export function moderationCtrl(opts: ModerationOpts): ModerationCtrl {
};
}
export function lineAction(onClick: (e: Event) => void) {
export function lineAction(username: string) {
return h('i.mod', {
hook: bind('click', onClick),
attrs: {
'data-icon': '',
'data-username': username,
title: 'Moderation'
}
});
@ -65,10 +65,12 @@ export function lineAction(onClick: (e: Event) => void) {
export function moderationView(ctrl?: ModerationCtrl): VNode[] | undefined {
if (!ctrl) return;
if (ctrl.loading()) return [h('div.loading', spinner())];
var data = ctrl.data();
const data = ctrl.data();
if (!data) return;
return [
h('div.top', [
h('div.top', {
key: 'mod-' + data.id,
}, [
h('span.text', {
attrs: {'data-icon': '' },
}, [userLink(data.username)]),

View file

@ -3,9 +3,7 @@ var makeAckable = require('./ackable');
// versioned events, acks, retries, resync
lichess.StrongSocket = function(url, version, settings) {
var now = function() {
return Date.now();
};
var now = Date.now;
var settings = $.extend(true, {}, lichess.StrongSocket.defaults, settings);
var url = url;