tournament wip

This commit is contained in:
Thibault Duplessis 2012-09-10 18:34:30 +02:00
parent e08ed0f447
commit e811f36dd8
14 changed files with 136 additions and 24 deletions

View file

@ -48,6 +48,21 @@ object Tournament extends LilaController {
}
}
def withdraw(id: String) = Auth { implicit ctx
implicit me
IOptionIORedirect(repo createdById id) { tour
api.withdraw(tour, me) map { _ routes.Tournament.show(tour.id) }
}
}
def userList(id: String) = Open { implicit ctx
IOptionIOk(repo byId id) { tour
userRepo byIds tour.data.users map { users
html.tournament.userList(users)
}
}
}
def form = Auth { implicit ctx
me
Ok(html.tournament.form(forms.create))

View file

@ -131,6 +131,7 @@ final class Settings(config: Config) {
val ActorLobbyHub = "lobby_hub"
val ActorMonitorHub = "monitor_hub"
val ActorTournamentHubMaster = "tournament_hub_master"
val ActorTournamentOrganizer = "tournament_organizer"
val ModlogCollectionModlog = getString("modlog.collection.modlog")

View file

@ -41,6 +41,8 @@ final class Hub(
notifyVersion("talk", JsString(message.render))
}
case ReloadUserList notifyVersion("users", JsNull)
case GetTournamentVersion(_) sender ! history.version
case Join(uid, user, version) {

View file

@ -30,6 +30,8 @@ final class HubMaster(
case msg @ SendTo(_, _) hubs.values foreach (_ ! msg)
case Forward(id, msg) hubs.get(id).foreach(_ ! msg)
case GetHub(id: String) sender ! {
(hubs get id) | {
mkHub(id) ~ { h hubs = hubs + (id -> h) }

View file

@ -0,0 +1,31 @@
package lila
package tournament
import akka.actor._
import akka.actor.ReceiveTimeout
import akka.util.duration._
import akka.util.Timeout
import akka.pattern.{ ask, pipe }
import akka.dispatch.{ Future, Promise }
import play.api.libs.concurrent._
import play.api.Play.current
final class Organizer(
api: TournamentApi,
repo: TournamentRepo) extends Actor {
implicit val timeout = Timeout(1 second)
implicit val executor = Akka.system.dispatcher
def receive = {
case StartTournament => startTournaments.unsafePerformIO
}
def startTournament = for {
tours <- repo.created
} yield (tours filter (_.readyToStart) map api.start).sequence
}

View file

@ -27,6 +27,10 @@ final class Socket(
private val timeoutDuration = 1 second
implicit private val timeout = Timeout(timeoutDuration)
def reloadUserList(tournamentId: String) {
hubMaster ! Forward(tournamentId, ReloadUserList)
}
def join(
tournamentId: String,
version: Option[Int],

View file

@ -13,19 +13,22 @@ case class Data(
minUsers: Int,
createdAt: DateTime,
createdBy: String,
users: List[String]) {
users: List[String])
sealed trait Tournament {
val id: String
val data: Data
def encode: RawTournament
import data._
lazy val duration = new Duration(minutes * 60 * 1000)
def missingUsers = minUsers - users.size
def contains(username: String) = users contains username
}
sealed trait Tournament {
def id: String
def encode: RawTournament
def contains(username: String): Boolean = users contains username
def contains(user: User): Boolean = contains(user.id)
def showClock = "2 + 0"
}
@ -34,12 +37,23 @@ case class Created(
id: String,
data: Data) extends Tournament {
import data._
def readyToStart = users.size >= minUsers
def encode = RawTournament.created(id, data)
def join(user: User): Valid[Created] = (data contains user.id).fold(
def join(user: User): Valid[Created] = contains(user).fold(
!!("User %s is already part of the tournament" format user.id),
copy(data = data.copy(users = data.users :+ user.id)).success
withUsers(users :+ user.id).success
)
def withdraw(user: User): Valid[Created] = contains(user).fold(
withUsers(users filterNot (user.id ==)).success,
!!("User %s is not part of the tournament" format user.id)
)
private def withUsers(x: List[String]) = copy(data = data.copy(users = x))
}
case class RawTournament(
@ -85,7 +99,7 @@ object Tournament {
val minuteDefault = 10
val minuteChoices = options(minutes, "%d minute{s}")
val minUsers = 5 to 30 by 5
val minUsers = (2 to 4) ++ (5 to 30 by 5)
val minUserDefault = 10
val minUserChoices = options(minUsers, "%d players{s}")
}

View file

@ -8,7 +8,8 @@ import scalaz.effects._
import user.User
final class TournamentApi(
repo: TournamentRepo) {
repo: TournamentRepo,
socket: Socket) {
def createTournament(setup: TournamentSetup, me: User): IO[Created] = {
val tournament = Tournament(
@ -21,7 +22,18 @@ final class TournamentApi(
}
def join(tour: Created, me: User): IO[Unit] = (tour join me).fold(
err => putStrLn(err.shows),
tour2 repo saveIO tour2
err putStrLn(err.shows),
tour2 for {
_ repo saveIO tour2
_ io(socket reloadUserList tour.id)
} yield ()
)
def withdraw(tour: Created, me: User): IO[Unit] = (tour withdraw me).fold(
err putStrLn(err.shows),
tour2 for {
_ repo saveIO tour2
_ io(socket reloadUserList tour.id)
} yield ()
)
}

View file

@ -30,7 +30,8 @@ final class TournamentEnv(
collection = mongodb(TournamentCollectionTournament))
lazy val api = new TournamentApi(
repo = repo)
repo = repo,
socket = socket)
lazy val roomRepo = new RoomRepo(
collection = mongodb(TournamentCollectionRoom)
@ -55,4 +56,9 @@ final class TournamentEnv(
uidTimeout = TournamentUidTimeout,
hubTimeout = TournamentHubTimeout
)), name = ActorTournamentHubMaster)
lazy val organizer = Akka.system.actorOf(Props(new HubMaster(
repo = repo,
api = api
)), name = ActorTournamentOrganizer)
}

View file

@ -33,5 +33,10 @@ case class Talk(u: String, txt: String)
case class GetTournamentVersion(tournamentId: String)
case class CloseTournament(tournamentId: String)
case class GetHub(tournamentId: String)
case class Forward(tournamentId: String, msg: Any)
case object ReloadUserList
case object HubTimeout
case object GetNbHubs
// organizer
case object StartTournament

View file

@ -1,6 +1,6 @@
@(tour: lila.tournament.Created, roomHtml: Html, version: Int, users: List[User])(implicit ctx: Context)
@import tour.data
@import tour._
@title = @{ tour.showClock + " tournament" }
@ -12,17 +12,23 @@ title = title) {
<h1>@title pending</h1>
<div>
Waiting for @data.missingUsers players
Waiting for @missingUsers players
</div>
<div>
Players: @users.map { user =>
@userLink(user)
}
<div class="user_list" data-href="@routes.Tournament.userList(id)">
@tournament.userList(users)
</div>
@ctx.me.map { me =>
<div>
<form action="@routes.Tournament.join(tour.id)" method="POST">
<input type="submit" class="submit button" />
@if(tour contains me) {
<form action="@routes.Tournament.withdraw(tour.id)" method="POST">
<input type="submit" class="submit button" value="Withdraw" />
</form>
} else {
<form action="@routes.Tournament.join(tour.id)" method="POST">
<input type="submit" class="submit button" value="Join tournament" />
</form>
}
</div>
}
}

View file

@ -0,0 +1,6 @@
@(users: List[User])(implicit ctx: Context)
Players: @users.map { user =>
@userLink(user)
}

View file

@ -45,6 +45,8 @@ 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)
POST /tournament/$id<[\w\-]{8}>/join controllers.Tournament.join(id: String)
POST /tournament/$id<[\w\-]{8}>/withdraw controllers.Tournament.withdraw(id: String)
GET /tournament/$id<[\w\-]{8}>/users controllers.Tournament.userList(id: String)
# Analyse
GET /analyse/$gameId<[\w\-]{8}> controllers.Analyse.replay(gameId: String, color: String = "white")

View file

@ -9,6 +9,7 @@ $(function() {
var $chat = $("div.lichess_chat");
var $chatToggle = $chat.find('input.toggle_chat');
var chatExists = $chat.length > 0;
var $userList = $wrap.find("div.user_list");
var socketUrl = $wrap.data("socket-url");
if (chatExists) {
@ -50,9 +51,14 @@ $(function() {
$('body').trigger('lichess.content_loaded');
}
function reloadUserList() {
$userList.load($userList.data("href"));
}
lichess.socket = new $.websocket(lichess.socketUrl + socketUrl, lichess_data.version, $.extend(true, lichess.socketDefaults, {
events: {
talk: function(e) { if (chatExists) addToChat(e); }
talk: function(e) { if (chatExists) addToChat(e); },
users: reloadUserList
},
options: {
name: "tournament"