Rewriting the lobby with new db and websockets

This commit is contained in:
Thibault Duplessis 2012-04-05 10:31:18 +02:00
parent b1fe7ccb05
commit f065d1df57
11 changed files with 115 additions and 145 deletions

View file

@ -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)

View file

@ -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()
)
}

View file

@ -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)

View 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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -6,4 +6,8 @@ import com.novus.salat.annotations.Key
case class Message(
username: String,
text: String) {
def render = Map(
"txt" -> text,
"u" -> username)
}

2
todo
View file

@ -2,3 +2,5 @@ move times
blurs
ie9 testing (frozen sync/clock) // on play or chat against human
ping nbm
http://en.lichess.org/@/noworkingmen elo graph missing
need ability to cancel rematch