Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name := "scalashogi"

version := "2.0.0"
version := "2.1.0"

ThisBuild / scalaVersion := "2.13.8"
ThisBuild / githubWorkflowPublishTargetBranches := Seq() // Don't publish anywhere
Expand All @@ -12,11 +12,13 @@ ThisBuild / versionPolicyIntention := Compatibility.None

libraryDependencies ++= List(
"org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2",
"org.specs2" %% "specs2-core" % "4.15.0" % Test,
"org.specs2" %% "specs2-cats" % "4.15.0" % Test,
"com.github.ornicar" %% "scalalib" % "7.0.2",
"joda-time" % "joda-time" % "2.10.14",
"org.typelevel" %% "cats-core" % "2.7.0"
"org.typelevel" %% "cats-parse" % "0.3.8",
"org.specs2" %% "specs2-core" % "4.16.1" % Test,
"org.specs2" %% "specs2-cats" % "4.16.1" % Test,
"com.github.ornicar" %% "scalalib" % "7.1.0",
"joda-time" % "joda-time" % "2.11.1",
"org.typelevel" %% "cats-core" % "2.8.0",
"org.typelevel" %% "alleycats-core" % "2.8.0"
)

resolvers ++= Seq(
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/Centis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package shogi

import scala.concurrent.duration._

import alleycats.Zero
import cats.Monoid
import ornicar.scalalib.Zero

// maximum centis = Int.MaxValue / 100 / 60 / 60 / 24 = 248 days
final case class Centis(centis: Int) extends AnyVal with Ordered[Centis] {
Expand Down Expand Up @@ -38,7 +38,7 @@ final case class Centis(centis: Int) extends AnyVal with Ordered[Centis] {
}

object Centis {
implicit final val zeroInstance = Zero.instance(Centis(0))
implicit final val zeroInstance = Zero(Centis(0))
implicit val CentisMonoid = new Monoid[Centis] {
def combine(c1: Centis, c2: Centis) = c1 + c2
final val empty = Centis(0)
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/Clock.scala
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ object Clock {
else if (hasIncrement) baseString
else s"${baseString}|0"

override def toString = s"${limitSeconds}.${incrementSeconds}.${byoyomiSeconds}.${periodsTotal}"
override def toString = s"${limitSeconds}.${incrementSeconds}.${byoyomiSeconds}.${periods}"
}

def parseJPTime(str: String): Option[Int] = {
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/LagTracker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ final case class LagTracker(
quota: Centis,
quotaMax: Centis,
lagEstimator: DecayingRecorder,
uncompStats: Stats = EmptyStats,
lagStats: Stats = EmptyStats,
uncompStats: Stats = Stats.empty,
lagStats: Stats = Stats.empty,
// We can remove compEst fields after tuning estimate.
compEstSqErr: Int = 0,
compEstOvers: Centis = Centis(0),
Expand Down
70 changes: 12 additions & 58 deletions src/main/scala/Stats.scala
Original file line number Diff line number Diff line change
@@ -1,82 +1,36 @@
package shogi

// Welford's numerically stable online variance.
sealed trait Stats {
def samples: Int
def mean: Float
def variance: Option[Float]
def record(value: Float): Stats
def +(o: Stats): Stats

def record[T](values: Iterable[T])(implicit n: Numeric[T]): Stats =
values.foldLeft(this) { (s, v) =>
s record n.toFloat(v)
}

def stdDev = variance.map { math.sqrt(_).toFloat }

def total = samples * mean
}

final protected case class StatHolder(
samples: Int,
mean: Float,
sn: Float
) extends Stats {
def variance = (samples > 1) option sn / (samples - 1)
//
final case class Stats(samples: Int, mean: Float, sn: Float) {

def record(value: Float) = {
val newSamples = samples + 1
val delta = value - mean
val newMean = mean + delta / newSamples
val newSN = sn + delta * (value - newMean)

StatHolder(
Stats(
samples = newSamples,
mean = newMean,
sn = newSN
)
}

def +(o: Stats) =
o match {
case EmptyStats => this
case StatHolder(oSamples, oMean, oSN) => {
val invTotal = 1f / (samples + oSamples)
val combMean = {
if (samples == oSamples) (mean + oMean) * 0.5f
else (mean * samples + oMean * oSamples) * invTotal
}

val meanDiff = mean - oMean

StatHolder(
samples = samples + oSamples,
mean = combMean,
sn = sn + oSN + meanDiff * meanDiff * samples * oSamples * invTotal
)
}
def record[T](values: Iterable[T])(implicit n: Numeric[T]): Stats =
values.foldLeft(this) { (s, v) =>
s record n.toFloat(v)
}
}

protected object EmptyStats extends Stats {
val samples = 0
val mean = 0f
val variance = None
def variance = (samples > 1) option sn / (samples - 1)

def record(value: Float) =
StatHolder(
samples = 1,
mean = value,
sn = 0f
)
def stdDev = variance.map { Math.sqrt(_).toFloat }

def +(o: Stats) = o
def total = samples * mean
}

object Stats {
val empty = EmptyStats

def apply(value: Float) = empty.record(value)
def apply[T: Numeric](values: Iterable[T]) = empty.record(values)
val empty: Stats = Stats(0, 0, 0)
def apply(value: Float): Stats = empty record value
def apply[T: Numeric](values: Iterable[T]): Stats = empty record values
}
10 changes: 1 addition & 9 deletions src/test/scala/StatsTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ class StatsTest extends Specification {
Stats.empty.samples must_== 0
}

"convert to StatHolder" in {
Stats(5) must beLike(StatHolder(0, 0f, 0f).record(5))
"make Stats" in {

"with good stats" in {
Stats(5).samples must_== 1
Expand All @@ -60,12 +59,5 @@ class StatsTest extends Specification {
statsN.variance.get must beApprox(realVar(data))
statsN.samples must_== 400
}
"match concat" in {
statsN must_== (Stats.empty + statsN)
statsN must_== (statsN + Stats.empty)
statsN must beLike(Stats(data take 1) + Stats(data drop 1))
statsN must beLike(Stats(data take 100) + Stats(data drop 100))
statsN must beLike(Stats(data take 200) + Stats(data drop 200))
}
}
}