tournaments wip

This commit is contained in:
Thibault Duplessis 2012-09-10 15:28:03 +02:00
parent 20abffa655
commit e08ed0f447
17 changed files with 129 additions and 54 deletions

View file

@ -9,6 +9,7 @@ import scalaz.effects._
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.iteratee._
import play.api.templates.Html
object Tournament extends LilaController {
@ -16,6 +17,8 @@ object Tournament extends LilaController {
private def forms = env.tournament.forms
private def api = env.tournament.api
private def socket = env.tournament.socket
private def messenger = env.tournament.messenger
private def userRepo = env.user.userRepo
val home = Open { implicit ctx
IOk(repo.created map { tournaments
@ -24,12 +27,27 @@ object Tournament extends LilaController {
}
def show(id: String) = Open { implicit ctx
IOptionOk(repo byId id) {
case t: Created html.tournament.show.created(t)
case _ throw new Exception("oups")
IOptionIOk(repo byId id) {
case tour: Created for {
roomHtml messenger render tour
users userRepo byIds tour.data.users
} yield html.tournament.show.created(
tour = tour,
roomHtml = Html(roomHtml),
version = version(tour.id),
users = users
)
case _ throw new Exception("oups")
}
}
def join(id: String) = Auth { implicit ctx
implicit me
IOptionIORedirect(repo createdById id) { tour
api.join(tour, me) map { _ routes.Tournament.show(tour.id) }
}
}
def form = Auth { implicit ctx
me
Ok(html.tournament.form(forms.create))
@ -51,4 +69,6 @@ object Tournament extends LilaController {
implicit val ctx = reqToCtx(req)
socket.join(id, getInt("version"), get("uid"), ctx.me).unsafePerformIO
}
private def version(tournamentId: String): Int = socket blockingVersion tournamentId
}

View file

@ -134,7 +134,7 @@ final class CoreEnv private (application: Application, val settings: Settings) {
gameRepo = game.gameRepo)
lazy val metaHub = new lila.socket.MetaHub(
List(site.hub, lobby.hub, round.hubMaster))
List(site.hub, lobby.hub, round.hubMaster, tournament.hubMaster))
lazy val notificationApi = new lila.notification.Api(
metaHub = metaHub)

View file

@ -13,6 +13,7 @@ import scalaz.effects._
final class Hub(
tournamentId: String,
val history: History,
messenger: Messenger,
uidTimeout: Int,
hubTimeout: Int) extends HubActor[Member](uidTimeout) with Historical[Member] {
@ -35,6 +36,11 @@ final class Hub(
}
}
case Talk(u, txt)
messenger.userMessage(tournamentId, u, txt).unsafePerformIO foreach { message
notifyVersion("talk", JsString(message.render))
}
case GetTournamentVersion(_) sender ! history.version
case Join(uid, user, version) {

View file

@ -15,6 +15,7 @@ import play.api.Play.current
final class HubMaster(
makeHistory: () History,
messenger: Messenger,
uidTimeout: Int,
hubTimeout: Int) extends Actor {
@ -61,6 +62,7 @@ final class HubMaster(
private def mkHub(tournamentId: String): ActorRef =
context.actorOf(Props(new Hub(
tournamentId = tournamentId,
messenger = messenger,
history = makeHistory(),
uidTimeout = uidTimeout,
hubTimeout = hubTimeout

View file

@ -7,28 +7,35 @@ import user.User
final class Messenger(
roomRepo: RoomRepo,
getUser: String => IO[Option[User]]) extends core.Room {
getTournament: String IO[Option[Tournament]],
getUser: String IO[Option[User]]) extends core.Room {
import Room._
def init(tour: Created): IO[List[Message]] = for {
userOption getUser(tour.data.createdBy)
username = userOption.fold(_.username, tour.data.createdBy)
message systemMessage(tour, "%s creates the tournament" format username)
message systemMessage(tour, "%s creates the tournament" format username)
} yield List(message)
def userMessage(tour: Tournament, text: String, username: String): IO[Valid[Message]] = for {
def userMessage(tournamentId: String, username: String, text: String): IO[Valid[Message]] = for {
userOption getUser(username)
tourOption getTournament(tournamentId)
message = for {
user userOption filter (_.canChat) toValid "This user cannot chat"
_ tourOption toValid "No such tournament"
msg createMessage(user, text)
(author, text) = msg
} yield Message(author.some, text)
_ message.fold(_ io(), msg roomRepo.addMessage(tour.id, msg))
_ message.fold(_ io(), msg roomRepo.addMessage(tournamentId, msg))
} yield message
def systemMessage(tour: Tournament, text: String): IO[Message] =
Message(none, text) |> { message
roomRepo.addMessage(tour.id, message) map (_ message)
}
def render(tour: Tournament): IO[String] = render(tour.id)
def render(roomId: String): IO[String] = roomRepo room roomId map (_.render)
}

View file

@ -10,14 +10,26 @@ case class Room(
messages: List[String]) {
def render: String =
messages map ((Room.render _) compose Room.decode) mkString ""
messages map (((m: Room.Message) m.render) compose Room.decode) mkString ""
def nonEmpty = messages.nonEmpty
}
object Room {
case class Message(author: Option[String], text: String)
case class Message(author: Option[String], text: String) {
def render: String =
"""<li><span>%s</span>%s</li>""".format(
author.fold(
u """<a class="user_link" href="%s">%s</a>""".format(
userRoute(u), u take 12
),
"""<span class="system"></span>"""
),
escapeXml(text)
)
}
def encode(msg: Message): String = (msg.author | "_") + " " + msg.text
@ -25,15 +37,4 @@ object Room {
case "_" Message(none, encoded.drop(2))
case user Message(user.some, encoded.drop(user.size + 1))
}
def render(msg: Message): String =
"""<li><span>%s</span>%s</li>""".format(
msg.author.fold(
u """<a class="user_link" href="%s">%s</a>""".format(
userRoute(u), u take 12
),
"""<span class="system"></span>"""
),
escapeXml(msg.text)
)
}

View file

@ -5,6 +5,7 @@ import akka.actor._
import akka.pattern.ask
import akka.util.duration._
import akka.util.Timeout
import akka.dispatch.Await
import play.api.libs.json._
import play.api.libs.iteratee._
import play.api.libs.concurrent._
@ -57,17 +58,20 @@ final class Socket(
hub: ActorRef,
uid: String,
member: Member,
tournamentId: String): JsValue Unit =
(e: JsValue) e str "t" match {
case Some("p") e int "v" foreach { v
hub ! PingVersion(uid, v)
}
case Some("talk") for {
username member.username
data e obj "d"
txt data str "txt"
if flood.allowMessage(uid, txt)
} hub ! Talk(username, txt)
case _
tournamentId: String): JsValue Unit = e e str "t" match {
case Some("p") e int "v" foreach { v
hub ! PingVersion(uid, v)
}
case Some("talk") for {
username member.username
data e obj "d"
txt data str "txt"
if flood.allowMessage(uid, txt)
} hub ! Talk(username, txt)
case _
}
def blockingVersion(tournamentId: String): Int = Await.result(
hubMaster ? GetTournamentVersion(tournamentId) mapTo manifest[Int],
timeoutDuration)
}

View file

@ -6,6 +6,8 @@ import org.scala_tools.time.Imports._
import com.novus.salat.annotations.Key
import ornicar.scalalib.OrnicarRandom
import user.User
case class Data(
minutes: Int,
minUsers: Int,
@ -33,6 +35,11 @@ case class Created(
data: Data) extends Tournament {
def encode = RawTournament.created(id, data)
def join(user: User): Valid[Created] = (data contains user.id).fold(
!!("User %s is already part of the tournament" format user.id),
copy(data = data.copy(users = data.users :+ user.id)).success
)
}
case class RawTournament(

View file

@ -19,4 +19,9 @@ final class TournamentApi(
_ repo saveIO tournament
} yield tournament
}
def join(tour: Created, me: User): IO[Unit] = (tour join me).fold(
err => putStrLn(err.shows),
tour2 repo saveIO tour2
)
}

View file

@ -36,7 +36,10 @@ final class TournamentEnv(
collection = mongodb(TournamentCollectionRoom)
)
lazy val messenger = new Messenger(roomRepo, getUser)
lazy val messenger = new Messenger(
roomRepo = roomRepo,
getTournament = repo.byId,
getUser = getUser)
lazy val socket = new Socket(
getTournament = repo.byId,
@ -48,6 +51,7 @@ final class TournamentEnv(
lazy val hubMaster = Akka.system.actorOf(Props(new HubMaster(
makeHistory = history,
messenger = messenger,
uidTimeout = TournamentUidTimeout,
hubTimeout = TournamentHubTimeout
)), name = ActorTournamentHubMaster)

View file

@ -12,8 +12,12 @@ import org.scala_tools.time.Imports._
class TournamentRepo(collection: MongoCollection)
extends SalatDAO[RawTournament, String](collection) {
def byId(id: String): IO[Option[Tournament]] = io {
findOneById(id) flatMap (_.any)
def byId(id: String): IO[Option[Tournament]] = byIdAs(id, _.any)
def createdById(id: String): IO[Option[Created]] = byIdAs(id, _.created)
private def byIdAs[A](id: String, as: RawTournament => Option[A]): IO[Option[A]] = io {
findOneById(id) flatMap as
}
def created: IO[List[Created]] = io {
@ -22,6 +26,11 @@ class TournamentRepo(collection: MongoCollection)
.toList.map(_.created).flatten
}
def setUsers(tourId: String, userIds: List[String]): IO[Unit] = io {
update(idSelector(tourId), $set("data.users" -> userIds.distinct))
}
def saveIO(tournament: Tournament): IO[Unit] = io {
save(tournament.encode)
}

View file

@ -29,6 +29,13 @@ class UserRepo(collection: MongoCollection)
.toList
}
def byOrderedIds(ids: Iterable[String]): IO[List[User]] = io {
find("_id" $in ids.map(normalize)).toList
} map { us
val usMap = us.map(u u.id -> u).toMap
ids.map(usMap.get).flatten.toList
}
def username(userId: String): IO[Option[String]] = io {
primitiveProjection[String](byIdQuery(userId), "username")
}

View file

@ -1,20 +1,28 @@
@(tour: lila.tournament.Created)(implicit ctx: Context)
@(tour: lila.tournament.Created, roomHtml: Html, version: Int, users: List[User])(implicit ctx: Context)
@import tour.data._
@import tour.data
@title = @{ tour.showClock + " tournament" }
@tournament.show.layout(
tour = tour,
roomHtml = roomHtml,
version = version,
title = title) {
<h1>@title pending</h1>
<div>
Waiting for @missingUsers players
Waiting for @data.missingUsers players
</div>
<div>
Players: @users.map { user =>
@userIdLink(user.some)
@userLink(user)
}
</div>
<div>
<form action="@routes.Tournament.join(tour.id)" method="POST">
<input type="submit" class="submit button" />
</form>
</div>
}

View file

@ -1,10 +1,10 @@
@(tour: lila.tournament.Tournament, title: String)(body: Html)(implicit ctx: Context)
@(tour: lila.tournament.Tournament, title: String, roomHtml: Html, version: Int)(body: Html)(implicit ctx: Context)
@chat = {
@for(m <- ctx.me; if m.canChat) {
<div class="lichess_chat tournament_chat">
<div class="lichess_chat anon_chat tournament_chat">
<div class="lichess_chat_top">
<span class="title">@trans.chatRoom()</span>
<span class="title">Tournament room</span>
<input
data-href="@routes.Setting.set("chat")"
data-enabled="@setting.chat.fold("true", "false")"
@ -13,7 +13,7 @@
type="checkbox" />
</div>
<div class="lichess_chat_inner">
<ol class="lichess_messages"></ol>
<ol class="lichess_messages">@roomHtml</ol>
<form action="#">
<input class="lichess_say lichess_hint" value="@trans.talkInChat()" />
<a class="send"></a>
@ -34,6 +34,6 @@ chat = chat.some) {
</div>
</div>
<script type="text/javascript">
var lichess_data = @Html(tournamentJsData(tour, 0, ctx.me))
var lichess_data = @Html(tournamentJsData(tour, version, ctx.me))
</script>
}

View file

@ -43,7 +43,8 @@ GET /tournament controllers.Tournament.home
GET /tournament/new controllers.Tournament.form
POST /tournament/new controllers.Tournament.create
GET /tournament/$id<[\w\-]{8}> controllers.Tournament.show(id: String)
GET /tournament/$id<[\w\-]{8}>/socket controllers.Tournament.websocket(id: String)
GET /tournament/$id<[\w\-]{8}>/socket controllers.Tournament.websocket(id: String)
POST /tournament/$id<[\w\-]{8}>/join controllers.Tournament.join(id: String)
# Analyse
GET /analyse/$gameId<[\w\-]{8}> controllers.Analyse.replay(gameId: String, color: String = "white")

View file

@ -40,7 +40,7 @@ var lichess = {
onProduction: /.+\.lichess\.org/.test(document.domain),
socketUrl: document.domain + ":9000"
};
//lichess.socketDefaults.options.debug = !lichess.onProduction;
lichess.socketDefaults.options.debug = !lichess.onProduction;
$(function() {

View file

@ -49,16 +49,10 @@ $(function() {
$chat.find('.lichess_messages').append(html)[0].scrollTop = 9999999;
$('body').trigger('lichess.content_loaded');
}
function buildChatMessage(txt, username) {
var html = '<li><span>'
html += '<a class="user_link" href="/@/'+username+'">'+username.substr(0, 12) + '</a>';
html += '</span>' + urlToLink(txt) + '</li>';
return html;
}
lichess.socket = new $.websocket(lichess.socketUrl + socketUrl, lichess_data.version, $.extend(true, lichess.socketDefaults, {
events: {
talk: function(e) { if (chatExists && e.txt) addToChat(buildChatMessage(e.txt, e.u)); }
talk: function(e) { if (chatExists) addToChat(e); }
},
options: {
name: "tournament"