tournaments wip
This commit is contained in:
parent
20abffa655
commit
e08ed0f447
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) ⇒ {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue