Implement game info

This commit is contained in:
Thibault Duplessis 2012-05-08 13:23:49 +02:00
parent fcc7403e54
commit c065d513e7
15 changed files with 686 additions and 519 deletions

View file

@ -21,7 +21,8 @@ final class AppApi(
gameSocket: game.Socket,
messenger: Messenger,
starter: Starter,
eloUpdater: EloUpdater) {
eloUpdater: EloUpdater,
gameInfo: DbGame IO[GameInfo]) {
private implicit val timeout = Timeout(300 millis)
private implicit val executor = Akka.system.dispatcher
@ -131,6 +132,11 @@ final class AppApi(
def gameVersion(gameId: String): Future[Int] = futureVersion(gameId)
def gameInfo(gameId: String): IO[Option[GameInfo]] = for {
gameOption gameRepo game gameId
gameInfo gameOption.fold(gameInfo(_) map some, io(none))
} yield gameInfo
def isConnected(gameId: String, colorName: String): Future[Boolean] =
Color(colorName).fold(
c gameSocket.hubMaster ? IsConnectedOnGame(gameId, c) mapTo manifest[Boolean],

37
app/GameInfo.scala Normal file
View file

@ -0,0 +1,37 @@
package lila
import model.DbGame
import chess.Eco
import chess.format.Forsyth
import scalaz.effects.IO
final class GameInfo private (
val game: DbGame,
val pgn: String,
val fen: String,
val opening: Option[Eco.Opening]) {
def toMap = Map(
"pgn" -> pgn,
"fen" -> fen,
"opening" -> (opening map { o
Map(
"code" -> o.code,
"name" -> o.name
)
})
)
}
object GameInfo {
def apply(pgnDump: PgnDump)(game: DbGame): IO[GameInfo] =
pgnDump >> game map { pgn
new GameInfo(
game = game,
pgn = pgn,
fen = Forsyth >> game.toChess,
opening = Eco openingOf game.pgn)
}
}

60
app/PgnDump.scala Normal file
View file

@ -0,0 +1,60 @@
package lila
import chess.format.Forsyth
import db.{ GameRepo, UserRepo }
import model.{ DbGame, DbPlayer, User }
import org.joda.time.format.DateTimeFormat
import scalaz.effects._
final class PgnDump(gameRepo: GameRepo, userRepo: UserRepo) {
val dateFormat = DateTimeFormat forPattern "yyyy-MM-dd";
def >>(game: DbGame): IO[String] =
header(game) map { headers
"%s\n\n%s %s".format(headers, moves(game), result(game))
}
def header(game: DbGame): IO[String] = for {
whiteUser user(game.whitePlayer)
blackUser user(game.blackPlayer)
initialFen game.variant.standard.fold(io(none), gameRepo initialFen game.id)
} yield List(
"Event" -> game.rated.fold("Rated game", "Casual game"),
"Site" -> ("http://lichess.org/" + game.id),
"Date" -> game.createdAt.fold(dateFormat.print, "?"),
"White" -> player(game.whitePlayer, whiteUser),
"Black" -> player(game.blackPlayer, blackUser),
"WhiteElo" -> elo(game.whitePlayer),
"BlackElo" -> elo(game.blackPlayer),
"Result" -> result(game),
"PlayCount" -> game.turns,
"Variant" -> game.variant.name
) ++ game.variant.standard.fold(Map.empty, Map(
"FEN" -> (initialFen | "?"),
"SetUp" -> "1"
)) map {
case (name, value) """[%s "%s"]""".format(name, value)
} mkString "\n"
def elo(p: DbPlayer) = p.elo.fold(_.toString, "?")
def user(p: DbPlayer): IO[Option[User]] = p.userId.fold(
userRepo.user,
io(none))
def player(p: DbPlayer, u: Option[User]) = p.isAi.fold(
"Crafty level " + p.aiLevel,
u.fold(_.username, "Anonymous"))
def moves(game: DbGame) = (game.pgnList grouped 2).zipWithIndex map {
case (moves, turn) "%d. %s".format((turn + 1), moves.mkString(" "))
} mkString " "
def result(game: DbGame) = game.finished.fold(
game.winnerColor.fold(
color color.white.fold("1-0", "0-1"),
"1/2-1/2"),
"*")
}

View file

@ -18,6 +18,12 @@ final class SystemEnv(application: Application) {
implicit val app = application
val config = app.configuration.underlying
lazy val pgnDump = new PgnDump(
userRepo = userRepo,
gameRepo = gameRepo)
lazy val gameInfo = GameInfo(pgnDump) _
lazy val reporting = Akka.system.actorOf(
Props(new report.Reporting), name = "reporting")
@ -82,7 +88,8 @@ final class SystemEnv(application: Application) {
gameSocket = gameSocket,
messenger = messenger,
starter = starter,
eloUpdater = eloUpdater)
eloUpdater = eloUpdater,
gameInfo = gameInfo)
lazy val lobbyApi = new lobby.Api(
hookRepo = hookRepo,

View file

@ -46,6 +46,13 @@ object AppApiC extends LilaController {
}
}
def gameInfo(gameId: String) = Action {
(api gameInfo gameId).unsafePerformIO.fold(
info JsonOk(info.toMap),
BadRequest("No such game")
)
}
def rematchAccept(gameId: String, color: String, newGameId: String) = Action { implicit request
FormValidIOk[RematchData](rematchForm)(r
api.rematchAccept(gameId, newGameId, color, r._1, r._2, r._3, r._4))
@ -59,7 +66,7 @@ object AppApiC extends LilaController {
env.captcha.create.unsafePerformIO.fold(
err BadRequest(err.shows),
data JsonOk(Map(
"id" -> data._1,
"id" -> data._1,
"fen" -> data._2,
"color" -> data._3.toString
))

View file

@ -107,6 +107,10 @@ class GameRepo(collection: MongoCollection)
)
}
def initialFen(gameId: String): IO[Option[String]] = io {
primitiveProjection[String](DBObject("_id" -> gameId), "initialFen")
}
def cleanupUnplayed: IO[Unit] = io {
remove(("turns" $lt 2) ++ ("createdAt" $lt (DateTime.now - 2.day)))
}

View file

@ -4,6 +4,7 @@ package model
import chess._
import Pos.{ posAt, piotr }
import Role.forsyth
import org.joda.time.DateTime
case class DbGame(
id: String,
@ -20,7 +21,8 @@ case class DbGame(
castles: String = "KQkq",
isRated: Boolean = false,
variant: Variant = Standard,
lastMoveTime: Option[Int] = None) {
lastMoveTime: Option[Int] = None,
createdAt: Option[DateTime] = None) {
val players = List(whitePlayer, blackPlayer)
@ -215,6 +217,8 @@ case class DbGame(
def creator = player(creatorColor)
def invited = player(!creatorColor)
def pgnList = pgn.split(' ').toList
}
object DbGame {

View file

@ -6,6 +6,7 @@ import com.novus.salat.annotations._
import chess._
import Pos.{ posAt, piotr }
import Role.forsyth
import org.joda.time.DateTime
case class RawDbGame(
@Key("_id") id: String,
@ -21,7 +22,8 @@ case class RawDbGame(
castles: String = "KQkq",
isRated: Boolean = false,
v: Int = 1,
lmt: Option[Int] = None) {
lmt: Option[Int] = None,
createdAt: Option[DateTime]) {
def decode: Option[DbGame] = for {
whitePlayer players find (_.c == "white") flatMap (_.decode)
@ -46,7 +48,8 @@ case class RawDbGame(
castles = castles,
isRated = isRated,
variant = trueVariant,
lastMoveTime = lmt
lastMoveTime = lmt,
createdAt = createdAt
)
}
@ -68,7 +71,8 @@ object RawDbGame {
castles = castles,
isRated = isRated,
v = variant.id,
lmt = lastMoveTime
lmt = lastMoveTime,
createdAt = createdAt
)
}
}

View file

@ -4,6 +4,8 @@ package model
sealed abstract class Variant(val id: Int) {
lazy val name = toString.toLowerCase
def standard = this == Standard
}
case object Standard extends Variant(1)

View file

@ -20,6 +20,9 @@ sealed trait Color {
def queen = this - Queen
def king = this - King
def white = this == White
def black = this == Black
override def toString = name
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
package lila.chess
import format.PgnDump
import Eco.Opening
final class GameInfo private (
val game: Game,
val pgn: String,
val opening: Option[Opening]) {
def toMap = Map(
"pgn" -> pgn,
"opening" -> (opening map { o
Map(
"code" -> o.code,
"name" -> o.name
)
})
)
}
object GameInfo {
def apply(game: Game) = new GameInfo(
game = game,
pgn = game.pgnMoves,
opening = Eco openingOf game.pgnMoves)
}

View file

@ -1,11 +1,12 @@
package lila.chess
class EcoTest extends ChessTest {
"Complete game" in {
val game = "d4 Nf6 e4 Nxe4 f3 Nd6 g3"
Eco nameOf game must beSome("Queen's Pawn Game")
Eco openingOf game must beSome.like {
case Eco.Opening(code, name) name must_== "Queen's Pawn Game"
}
}
}

View file

@ -20,6 +20,7 @@ POST /api/rematch-accept/:gameId/:color/:newGameId lila.controllers.AppApiC.re
POST /api/adjust/:username lila.controllers.AppApiC.adjust(username: String)
GET /api/activity/:gameId/:color lila.controllers.AppApiC.activity(gameId: String, color: String)
GET /api/game-version/:gameId lila.controllers.AppApiC.gameVersion(gameId: String)
GET /api/game-info/:gameId lila.controllers.AppApiC.gameInfo(gameId: String)
GET /api/captcha/create lila.controllers.CaptchaC.create
GET /api/captcha/solve/:gameId lila.controllers.CaptchaC.solve(gameId: String)

1
todo
View file

@ -6,7 +6,6 @@ any link in-game
back button to game -> old status
home list of current games not translated
registered user disconnection delay
better spam protection than anon + http
bad visibility of online indicators during game
force resign
post <div> in chat