Rewriting the lobby with new db and websockets
This commit is contained in:
parent
b1fe7ccb05
commit
f065d1df57
|
@ -4,9 +4,6 @@ import model._
|
|||
import memo._
|
||||
import db._
|
||||
import scalaz.effects._
|
||||
import scalaz.NonEmptyList
|
||||
import scala.annotation.tailrec
|
||||
import scala.math.max
|
||||
|
||||
final class LobbyPreloader(
|
||||
hookRepo: HookRepo,
|
||||
|
@ -50,31 +47,10 @@ final class LobbyPreloader(
|
|||
entries ← entryRepo.recent
|
||||
} yield Map(
|
||||
"pool" -> renderHooks(hooks, myHookId),
|
||||
"chat" -> messages.toNel.fold(renderMessages, Nil),
|
||||
"timeline" -> entries.toNel.fold(renderEntries, Nil)
|
||||
"chat" -> (messages.reverse map (_.render)),
|
||||
"timeline" -> (entries.reverse map (_.render))
|
||||
)
|
||||
|
||||
private def renderMessages(messages: NonEmptyList[Message]) =
|
||||
messages.list.reverse map { message ⇒
|
||||
Map(
|
||||
"txt" -> message.text,
|
||||
"u" -> message.username)
|
||||
}
|
||||
|
||||
private def renderEntries(entries: NonEmptyList[Entry]) =
|
||||
entries.list.reverse map { entry ⇒
|
||||
"<td>%s</td><td>%s</td><td class='trans_me'>%s</td><td class='trans_me'>%s</td><td class='trans_me'>%s</td>".format(
|
||||
"<a class='watch' href='/%s'></a>" format entry.data.id,
|
||||
entry.data.players map { p ⇒
|
||||
p.u.fold(
|
||||
username ⇒ "<a class='user_link' href='/@/%s'>%s</a>".format(username, p.ue),
|
||||
p.ue)
|
||||
} mkString " vs ",
|
||||
entry.data.variant,
|
||||
entry.data.rated ? "Rated" | "Casual",
|
||||
entry.data.clock | "Unlimited")
|
||||
}
|
||||
|
||||
private def renderHooks(hooks: List[Hook], myHookId: Option[String]) = hooks map { h ⇒
|
||||
if (myHookId == Some(h.ownerId))
|
||||
h.render ++ Map("action" -> "cancel", "ownerId" -> myHookId)
|
||||
|
|
|
@ -9,21 +9,14 @@ final class Starter(
|
|||
val gameRepo: GameRepo,
|
||||
entryRepo: EntryRepo,
|
||||
val versionMemo: VersionMemo,
|
||||
entryMemo: EntryMemo,
|
||||
ai: Ai) extends IOTools {
|
||||
|
||||
def start(game: DbGame, entryData: String): IO[DbGame] = for {
|
||||
_ ← if (game.variant == Standard) io() else gameRepo saveInitialFen game
|
||||
_ ← addEntry(game, entryData)
|
||||
_ ← Entry(game, entryData).fold(entryRepo.add, io())
|
||||
g2 ← if (game.player.isHuman) io(game) else for {
|
||||
aiResult ← ai(game) map (_.err)
|
||||
(newChessGame, move) = aiResult
|
||||
} yield game.update(newChessGame, move)
|
||||
} yield g2
|
||||
|
||||
private def addEntry(game: DbGame, data: String): IO[Unit] =
|
||||
Entry.build(game, data).fold(
|
||||
f ⇒ (entryMemo++) map (id ⇒ entryRepo insert f(id)),
|
||||
io()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -81,8 +81,7 @@ final class SystemEnv(config: Config) {
|
|||
gameRepo = gameRepo,
|
||||
entryRepo = entryRepo,
|
||||
ai = ai,
|
||||
versionMemo = versionMemo,
|
||||
entryMemo = entryMemo)
|
||||
versionMemo = versionMemo)
|
||||
|
||||
lazy val ai: Ai = craftyAi
|
||||
|
||||
|
@ -145,9 +144,6 @@ final class SystemEnv(config: Config) {
|
|||
lazy val hookMemo = new HookMemo(
|
||||
timeout = getMilliseconds("memo.hook.timeout"))
|
||||
|
||||
lazy val entryMemo = new EntryMemo(
|
||||
getId = entryRepo.lastId)
|
||||
|
||||
lazy val gameFinishCommand = new GameFinishCommand(
|
||||
gameRepo = gameRepo,
|
||||
finisher = finisher)
|
||||
|
|
22
system/src/main/scala/db/CappedRepo.scala
Normal file
22
system/src/main/scala/db/CappedRepo.scala
Normal file
|
@ -0,0 +1,22 @@
|
|||
package lila.system
|
||||
package db
|
||||
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
abstract class CappedRepo[A](collection: MongoCollection, max: Int) {
|
||||
|
||||
val naturalOrder = DBObject("$natural" -> -1)
|
||||
|
||||
val recent: IO[List[A]] = io {
|
||||
collection.find(DBObject())
|
||||
.sort(naturalOrder)
|
||||
.limit(max)
|
||||
.toList.map(decode).flatten
|
||||
}
|
||||
|
||||
def decode(obj: DBObject): Option[A]
|
||||
|
||||
def encode(obj: A): DBObject
|
||||
}
|
|
@ -4,6 +4,42 @@ package db
|
|||
import model.Entry
|
||||
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
class EntryRepo(collection: MongoCollection, val max: Int)
|
||||
extends TimelineRepo[Entry](collection, max)
|
||||
final class EntryRepo(collection: MongoCollection, max: Int)
|
||||
extends CappedRepo[Entry](collection, max) {
|
||||
|
||||
def add(entry: Entry): IO[Unit] = io {
|
||||
collection += DBObject(
|
||||
"gameId" -> entry.gameId,
|
||||
"whiteName" -> entry.whiteName,
|
||||
"blackName" -> entry.blackName,
|
||||
"whiteId" -> entry.whiteId,
|
||||
"blackId" -> entry.blackId,
|
||||
"variant" -> entry.variant,
|
||||
"rated" -> entry.rated,
|
||||
"clock" -> entry.clock)
|
||||
}
|
||||
|
||||
def decode(obj: DBObject): Option[Entry] = for {
|
||||
gameId ← obj.getAs[String]("gameId")
|
||||
whiteName ← obj.getAs[String]("whiteName")
|
||||
blackName ← obj.getAs[String]("blackName")
|
||||
whiteId = obj.getAs[String]("whiteId")
|
||||
blackId = obj.getAs[String]("blackId")
|
||||
variant ← obj.getAs[String]("variant")
|
||||
rated ← obj.getAs[Boolean]("rated")
|
||||
clock = obj.getAs[String]("clock")
|
||||
} yield Entry(gameId, whiteName, blackName, whiteId, blackId, variant, rated, clock)
|
||||
|
||||
def encode(obj: Entry): DBObject = DBObject(
|
||||
"gameId" -> obj.gameId,
|
||||
"whiteName" -> obj.whiteName,
|
||||
"blackName" -> obj.blackName,
|
||||
"whiteId" -> obj.whiteId,
|
||||
"blackId" -> obj.blackId,
|
||||
"variant" -> obj.variant,
|
||||
"rated" -> obj.rated,
|
||||
"clock" -> obj.clock)
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import com.mongodb.casbah.Imports._
|
|||
import scalaz.effects._
|
||||
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
|
||||
|
||||
class MessageRepo(collection: MongoCollection, val max: Int) {
|
||||
final class MessageRepo(collection: MongoCollection, max: Int)
|
||||
extends CappedRepo[Message](collection, max) {
|
||||
|
||||
private val naturalOrder = DBObject("$natural" -> -1)
|
||||
private val urlRegex = """lichess\.org/([\w-]{8})[\w-]{4}""".r
|
||||
|
||||
def add(text: String, username: String): Valid[IO[Message]] =
|
||||
|
@ -23,22 +23,19 @@ class MessageRepo(collection: MongoCollection, val max: Int) {
|
|||
text.trim.take(140),
|
||||
m ⇒ "lichess.org/" + (m group 1))
|
||||
io {
|
||||
collection += DBObject(
|
||||
"u" -> username,
|
||||
"t" -> t)
|
||||
Message(username, t)
|
||||
Message(username, t) ~ { collection += encode(_) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val recent: IO[List[Message]] = io {
|
||||
collection.find(DBObject()).sort(naturalOrder).limit(max).toList map { obj ⇒
|
||||
for {
|
||||
u ← obj.getAs[String]("u")
|
||||
t ← obj.getAs[String]("t")
|
||||
} yield Message(u, t)
|
||||
} flatten
|
||||
}
|
||||
def decode(obj: DBObject): Option[Message] = for {
|
||||
u ← obj.getAs[String]("u")
|
||||
t ← obj.getAs[String]("t")
|
||||
} yield Message(u, t)
|
||||
|
||||
def encode(obj: Message): DBObject = DBObject(
|
||||
"u" -> obj.username,
|
||||
"t" -> obj.text)
|
||||
|
||||
private def !!(msg: String) = failure(msg.wrapNel)
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package lila.system
|
||||
package db
|
||||
|
||||
import model.Message
|
||||
|
||||
import com.novus.salat._
|
||||
import com.novus.salat.dao._
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
abstract class TimelineRepo[A <: AnyRef](collection: MongoCollection, max: Int)(implicit m: Manifest[A]) extends SalatDAO[A, Int](collection) {
|
||||
|
||||
val idSelector = DBObject("_id" -> true)
|
||||
val idSorter = DBObject("_id" -> -1)
|
||||
|
||||
val lastId: () ⇒ IO[Option[Int]] = () ⇒ io {
|
||||
collection.find(DBObject(), idSelector)
|
||||
.sort(idSorter)
|
||||
.limit(1)
|
||||
.next()
|
||||
.getAs[Int]("_id")
|
||||
}
|
||||
|
||||
val recent = io {
|
||||
find(DBObject()).sort(idSorter).limit(max).toList
|
||||
}
|
||||
|
||||
def since(id: Int): IO[List[A]] = io {
|
||||
find("_id" $gt id).sort(idSorter).toList
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package lila.system
|
||||
package memo
|
||||
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
final class EntryMemo(getId: () ⇒ IO[Option[Int]]) {
|
||||
|
||||
private var privateId: Int = _
|
||||
|
||||
refresh.unsafePerformIO
|
||||
|
||||
def refresh = for {
|
||||
idOption ← getId()
|
||||
} yield {
|
||||
privateId = idOption err "No last entry found"
|
||||
}
|
||||
|
||||
def ++ : IO[Int] = io {
|
||||
privateId = privateId + 1
|
||||
privateId
|
||||
}
|
||||
|
||||
def id: Int = privateId
|
||||
}
|
|
@ -5,44 +5,46 @@ import com.novus.salat.annotations._
|
|||
import com.mongodb.BasicDBList
|
||||
|
||||
case class Entry(
|
||||
@Key("_id") id: Int,
|
||||
data: EntryGame) {
|
||||
gameId: String,
|
||||
whiteName: String,
|
||||
blackName: String,
|
||||
whiteId: Option[String],
|
||||
blackId: Option[String],
|
||||
variant: String,
|
||||
rated: Boolean,
|
||||
clock: Option[String]) {
|
||||
|
||||
def players: List[(String, Option[String])] = List(
|
||||
whiteName -> whiteId,
|
||||
blackName -> blackId)
|
||||
|
||||
def render =
|
||||
"<td>%s</td><td>%s</td><td class='trans_me'>%s</td><td class='trans_me'>%s</td><td class='trans_me'>%s</td>".format(
|
||||
"<a class='watch' href='/%s'></a>" format gameId,
|
||||
players map {
|
||||
case (name, None) ⇒ name
|
||||
case (name, Some(id)) ⇒
|
||||
"<a class='user_link' href='/@/%s'>%s</a>".format(id, name)
|
||||
} mkString " vs ",
|
||||
variant,
|
||||
rated ? "Rated" | "Casual",
|
||||
clock | "Unlimited")
|
||||
}
|
||||
|
||||
case class EntryPlayer(u: Option[String], ue: String)
|
||||
|
||||
case class EntryGame(
|
||||
id: String,
|
||||
players: List[EntryPlayer],
|
||||
variant: String,
|
||||
rated: Boolean,
|
||||
clock: Option[String])
|
||||
|
||||
object Entry {
|
||||
|
||||
def build(game: DbGame, encodedData: String): Option[Int ⇒ Entry] =
|
||||
def apply(game: DbGame, encodedData: String): Option[Entry] =
|
||||
encodedData.split('$').toList match {
|
||||
case wu :: wue :: bu :: bue :: Nil ⇒ Some(
|
||||
(id: Int) ⇒
|
||||
new Entry(
|
||||
id = id,
|
||||
EntryGame(
|
||||
id = game.id,
|
||||
players = List(
|
||||
EntryPlayer(
|
||||
u = wu.some filterNot (_.isEmpty),
|
||||
ue = wue
|
||||
),
|
||||
EntryPlayer(
|
||||
u = bu.some filterNot (_.isEmpty),
|
||||
ue = bue
|
||||
)
|
||||
),
|
||||
variant = game.variant.name,
|
||||
rated = game.isRated,
|
||||
clock = game.clock map (_.show)
|
||||
)
|
||||
)
|
||||
new Entry(
|
||||
gameId = game.id,
|
||||
whiteName = wue,
|
||||
blackName = bue,
|
||||
whiteId = Some(wu) filter (_.nonEmpty),
|
||||
blackId = Some(bu) filter (_.nonEmpty),
|
||||
variant = game.variant.name,
|
||||
rated = game.isRated,
|
||||
clock = game.clock map (_.show))
|
||||
)
|
||||
case _ ⇒ None
|
||||
}
|
||||
|
|
|
@ -6,4 +6,8 @@ import com.novus.salat.annotations.Key
|
|||
case class Message(
|
||||
username: String,
|
||||
text: String) {
|
||||
|
||||
def render = Map(
|
||||
"txt" -> text,
|
||||
"u" -> username)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue