worldmap v2 poc
This commit is contained in:
parent
97272e2fd5
commit
88efb2222e
|
@ -79,6 +79,7 @@ final class Env(
|
|||
Env.video,
|
||||
Env.shutup, // required to load the actor
|
||||
Env.insight, // required to load the actor
|
||||
Env.worldMap, // required to load the actor
|
||||
Env.push // required to load the actor
|
||||
)
|
||||
play.api.Logger("boot").info("Preloading complete")
|
||||
|
|
|
@ -13,9 +13,11 @@ object WorldMap extends LilaController {
|
|||
Ok(views.html.site.worldMap())
|
||||
}
|
||||
|
||||
def stream = Action {
|
||||
Ok.chunked(
|
||||
Env.worldMap.stream.producer &> EventSource()
|
||||
) as "text/event-stream"
|
||||
def stream = Action.async {
|
||||
Env.worldMap.getStream map { stream =>
|
||||
Ok.chunked(
|
||||
stream &> EventSource()
|
||||
) as "text/event-stream"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,22 +12,9 @@
|
|||
<body>
|
||||
<h1><a href="http://lichess.org">lichess<span class="extension">.org</span></a> network</h1>
|
||||
<div id="worldmap"></div>
|
||||
<div id="stats">
|
||||
<div class="wrapL">
|
||||
<div id="time">time: <span></span></div>
|
||||
<div id="moves">moves: <span></span></div>
|
||||
<div id="countries">countries: <span></span></div>
|
||||
</div>
|
||||
<div class="wrapL">
|
||||
<div id="topCountries"></div>
|
||||
</div>
|
||||
</div>
|
||||
@jQueryTag
|
||||
@jsAt("worldMap/MIDIUtils.js")
|
||||
@jsAt("worldMap/raphael-min.js")
|
||||
@jsAt("worldMap/world.js")
|
||||
@jsAt("worldMap/time.js")
|
||||
@jsAt("worldMap/app.js")
|
||||
@jsAt("worldMap/stats.js")
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -268,7 +268,6 @@ playban {
|
|||
}
|
||||
worldMap {
|
||||
geoip = ${geoip}
|
||||
players.cache_size = 8192
|
||||
}
|
||||
push {
|
||||
collection.device = push_device
|
||||
|
|
|
@ -13,8 +13,6 @@ trait ActorMap extends Actor {
|
|||
|
||||
def mkActor(id: String): Actor
|
||||
|
||||
def withEvents = false
|
||||
|
||||
def actorMapReceive: Receive = {
|
||||
|
||||
case Get(id) => sender ! getOrMake(id)
|
||||
|
@ -32,9 +30,7 @@ trait ActorMap extends Actor {
|
|||
case Terminated(actor) =>
|
||||
context unwatch actor
|
||||
actors foreach {
|
||||
case (id, a) if (a == actor) =>
|
||||
actors -= id
|
||||
if (withEvents) self ! ActorMap.Remove(id, a)
|
||||
case (id, a) if (a == actor) => actors -= id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,16 +40,12 @@ trait ActorMap extends Actor {
|
|||
context.actorOf(Props(mkActor(id)), name = id) ~ { actor =>
|
||||
actors += (id -> actor)
|
||||
context watch actor
|
||||
if (withEvents) self ! ActorMap.Add(id, actor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ActorMap {
|
||||
|
||||
case class Add(id: String, actor: ActorRef)
|
||||
case class Remove(id: String, actor: ActorRef)
|
||||
|
||||
def apply(make: String => Actor) = new ActorMap {
|
||||
def mkActor(id: String) = make(id)
|
||||
def receive = actorMapReceive
|
||||
|
|
|
@ -3,7 +3,6 @@ package actorApi
|
|||
|
||||
import lila.common.LightUser
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import play.api.libs.json._
|
||||
import play.twirl.api.Html
|
||||
|
||||
|
@ -194,9 +193,11 @@ case class NbRounds(nb: Int)
|
|||
case class Abort(gameId: String, byColor: String)
|
||||
case class Berserk(gameId: String, userId: String)
|
||||
case class IsOnGame(color: chess.Color)
|
||||
sealed trait DoorEvent
|
||||
case class Open(gameId: String) extends DoorEvent
|
||||
case class Close(gameId: String) extends DoorEvent
|
||||
sealed trait SocketEvent
|
||||
object SocketEvent {
|
||||
case class OwnerJoin(gameId: String, color: chess.Color, ip: String) extends SocketEvent
|
||||
case class Stop(gameId: String) extends SocketEvent
|
||||
}
|
||||
}
|
||||
|
||||
package evaluation {
|
||||
|
|
|
@ -54,7 +54,6 @@ final class Env(
|
|||
lazy val eventHistory = History(db(CollectionHistory)) _
|
||||
|
||||
val roundMap = system.actorOf(Props(new lila.hub.ActorMap {
|
||||
override val withEvents = true
|
||||
def mkActor(id: String) = new Round(
|
||||
gameId = id,
|
||||
messenger = messenger,
|
||||
|
@ -72,10 +71,6 @@ final class Env(
|
|||
case actorApi.GetNbRounds =>
|
||||
nbRounds = size
|
||||
hub.socket.lobby ! lila.hub.actorApi.round.NbRounds(nbRounds)
|
||||
case lila.hub.ActorMap.Add(id, _) =>
|
||||
system.lilaBus.publish(lila.hub.actorApi.round.Open(id), 'roundDoor)
|
||||
case lila.hub.ActorMap.Remove(id, _) =>
|
||||
system.lilaBus.publish(lila.hub.actorApi.round.Close(id), 'roundDoor)
|
||||
}: Receive) orElse actorMapReceive
|
||||
}), name = ActorMapName)
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ private[round] final class Socket(
|
|||
override def postStop() {
|
||||
super.postStop()
|
||||
lilaBus.unsubscribe(self)
|
||||
lilaBus.publish(lila.hub.actorApi.round.SocketEvent.Stop(gameId), 'roundDoor)
|
||||
}
|
||||
|
||||
private def refreshSubscriptions {
|
||||
|
@ -146,6 +147,9 @@ private[round] final class Socket(
|
|||
playerDo(color, _.ping)
|
||||
sender ! Connected(enumerator, member)
|
||||
if (member.userTv.isDefined) refreshSubscriptions
|
||||
if (member.owner) lilaBus.publish(
|
||||
lila.hub.actorApi.round.SocketEvent.OwnerJoin(gameId, color, ip),
|
||||
'roundDoor)
|
||||
|
||||
case Nil =>
|
||||
case eventList: EventList => notify(eventList.events)
|
||||
|
|
|
@ -2,6 +2,7 @@ package lila.worldMap
|
|||
|
||||
import com.typesafe.config.Config
|
||||
|
||||
import akka.actor._
|
||||
import com.sanoma.cda.geoip.MaxMindIpGeo
|
||||
import lila.common.PimpedConfig._
|
||||
|
||||
|
@ -11,15 +12,19 @@ final class Env(
|
|||
|
||||
private val GeoIPFile = config getString "geoip.file"
|
||||
private val GeoIPCacheTtl = config duration "geoip.cache_ttl"
|
||||
private val PlayersCacheSize = config getInt "players.cache_size"
|
||||
|
||||
lazy val players = new Players(PlayersCacheSize)
|
||||
private val stream = system.actorOf(
|
||||
Props(new Stream(
|
||||
geoIp = MaxMindIpGeo(GeoIPFile, 0),
|
||||
geoIpCacheTtl = GeoIPCacheTtl)))
|
||||
|
||||
lazy val stream = new Stream(
|
||||
system = system,
|
||||
players = players,
|
||||
geoIp = MaxMindIpGeo(GeoIPFile, 0),
|
||||
geoIpCacheTtl = GeoIPCacheTtl)
|
||||
def getStream = {
|
||||
import play.api.libs.iteratee._
|
||||
import play.api.libs.json._
|
||||
import akka.pattern.ask
|
||||
import makeTimeout.short
|
||||
stream ? Stream.Get mapTo manifest[Enumerator[JsValue]]
|
||||
}
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
package lila.worldMap
|
||||
|
||||
import lila.memo.Builder
|
||||
|
||||
private[worldMap] final class Players(cacheSize: Int) {
|
||||
|
||||
// to each game ID, associate list of player Locations
|
||||
// there can be 0, 1 or 2 players per game ID,
|
||||
// but this constraint is not expressed by the cache type :(
|
||||
private val cache = Builder.size[String, List[Location]](cacheSize)
|
||||
|
||||
def getOpponentLocation(gameId: String, myLocation: Location): Option[Location] =
|
||||
|
||||
Option(cache getIfPresent gameId) getOrElse Nil match {
|
||||
|
||||
// new game ID, store player location
|
||||
case Nil =>
|
||||
cache.put(gameId, List(myLocation)); None
|
||||
|
||||
// only my location is known
|
||||
case List(loc) if loc == myLocation => None
|
||||
|
||||
// only opponent location is known. Store mine
|
||||
case List(loc) =>
|
||||
cache.put(gameId, List(loc, myLocation)); Some(loc)
|
||||
|
||||
// both locations are known
|
||||
case List(l1, l2) if l1 == myLocation => Some(l2)
|
||||
|
||||
// both locations are known
|
||||
case List(l1, l2) if l2 == myLocation => Some(l1)
|
||||
|
||||
// both are unknown
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -81,7 +81,7 @@ object ApplicationBuild extends Build {
|
|||
libraryDependencies ++= provided(play.api, RM, PRM)
|
||||
)
|
||||
|
||||
lazy val worldMap = project("worldMap", Seq(common, hub, memo)).settings(
|
||||
lazy val worldMap = project("worldMap", Seq(common, hub, memo, rating)).settings(
|
||||
libraryDependencies ++= provided(play.api, maxmind)
|
||||
)
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
(function() {
|
||||
|
||||
var noteMap = {};
|
||||
var noteNumberMap = [];
|
||||
var notes = [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ];
|
||||
|
||||
|
||||
for(var i = 0; i < 127; i++) {
|
||||
|
||||
var index = i,
|
||||
key = notes[index % 12],
|
||||
octave = ((index / 12) | 0) - 1; // MIDI scale starts at octave = -1
|
||||
|
||||
if(key.length === 1) {
|
||||
key = key + '-';
|
||||
}
|
||||
|
||||
key += octave;
|
||||
|
||||
noteMap[key] = i;
|
||||
noteNumberMap[i] = key;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getBaseLog(value, base) {
|
||||
return Math.log(value) / Math.log(base);
|
||||
}
|
||||
|
||||
|
||||
var MIDIUtils = {
|
||||
|
||||
noteNameToNoteNumber: function(name) {
|
||||
return noteMap[name];
|
||||
},
|
||||
|
||||
noteNumberToFrequency: function(note) {
|
||||
return 440.0 * Math.pow(2, (note - 69.0) / 12.0);
|
||||
},
|
||||
|
||||
noteNumberToName: function(note) {
|
||||
return noteNumberMap[note];
|
||||
},
|
||||
|
||||
frequencyToNoteNumber: function(f) {
|
||||
return Math.round(12.0 * getBaseLog(f / 440.0, 2) + 69);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Make it compatible for require.js/AMD loader(s)
|
||||
if(typeof define === 'function' && define.amd) {
|
||||
define(function() { return MIDIUtils; });
|
||||
} else if(typeof module !== 'undefined' && module.exports) {
|
||||
// And for npm/node.js
|
||||
module.exports = MIDIUtils;
|
||||
} else {
|
||||
this.MIDIUtils = MIDIUtils;
|
||||
}
|
||||
|
||||
|
||||
}).call(this);
|
||||
|
|
@ -1,67 +1,3 @@
|
|||
var stats = {
|
||||
nMoves: 0,
|
||||
countries: {}
|
||||
};
|
||||
|
||||
var context, gain_node, filter, oscillator, sound = {};
|
||||
var noteIt = 0;
|
||||
|
||||
sound.stop = function() {
|
||||
try {
|
||||
oscillator.disconnect();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
sound.play = function(freq) {
|
||||
oscillator = context.createOscillator();
|
||||
oscillator.type = 'sawtooth';
|
||||
oscillator.connect(filter);
|
||||
oscillator.frequency.value = freq;
|
||||
oscillator.start(0);
|
||||
};
|
||||
|
||||
(function init(g) {
|
||||
try {
|
||||
context = new(g.AudioContext || g.webkitAudioContext);
|
||||
gain_node = context.createGain();
|
||||
gain_node.connect(context.destination);
|
||||
gain_node.gain.value = 0.1;
|
||||
filter = context.createBiquadFilter();
|
||||
filter.type = 'lowpass';
|
||||
filter.connect(gain_node);
|
||||
} catch (e) {
|
||||
console.log('No web audio oscillator support in this browser');
|
||||
}
|
||||
}(window));
|
||||
|
||||
var soundEnabled = false;
|
||||
var prevMoves;
|
||||
var maxPrevMoves = 10;
|
||||
setInterval(function() {
|
||||
if (!soundEnabled) return;
|
||||
sound.stop();
|
||||
if (stats.nMoves > 0) {
|
||||
noteIt++;
|
||||
if (prevMoves) {
|
||||
var note = Math.min(
|
||||
11 + 3 * (stats.nMoves - prevMoves),
|
||||
79);
|
||||
var freq = MIDIUtils.noteNumberToFrequency(note);
|
||||
sound.play(freq);
|
||||
}
|
||||
filter.frequency.value = 2400 + Math.sin(noteIt / 22) * 2000;
|
||||
filter.Q.value = 6 + Math.sin(noteIt / 9) * 12;
|
||||
gain_node.gain.value = 0.3 + -0.08 * Math.sin(noteIt / 22);
|
||||
prevMoves = stats.nMoves;
|
||||
}
|
||||
}, 100);
|
||||
|
||||
$('body').prepend($('<button id="sound">').text('SOUND').click(function() {
|
||||
soundEnabled = !soundEnabled;
|
||||
prevMoves = null;
|
||||
if (!soundEnabled) sound.stop();
|
||||
}));
|
||||
|
||||
$(function() {
|
||||
var worldWith = window.innerWidth - 30,
|
||||
mapRatio = 0.4,
|
||||
|
@ -89,94 +25,66 @@ $(function() {
|
|||
};
|
||||
};
|
||||
|
||||
// position legend and stats
|
||||
var worldOffset = $('#worldmap').offset();
|
||||
$('#stats').offset({
|
||||
top: worldOffset.top + 250 * scale,
|
||||
left: worldOffset.left + 7.6 * scale
|
||||
}).attr({
|
||||
width: 195 * scale,
|
||||
height: 172 * scale
|
||||
});
|
||||
var randomPoint = function() {
|
||||
return [
|
||||
Math.random() * 100 - 50,
|
||||
Math.random() * 200 - 100
|
||||
];
|
||||
};
|
||||
|
||||
if (!!window.EventSource) {
|
||||
var source = new EventSource("/network/stream");
|
||||
var point2pos = function(point) {
|
||||
// point = randomPoint();
|
||||
return world.getXY(
|
||||
point[0] + Math.random() - 0.5,
|
||||
point[1] + Math.random() - 0.5);
|
||||
};
|
||||
|
||||
source.addEventListener('message', function(e) {
|
||||
var raw = e.data.split('|');
|
||||
var data = {
|
||||
country: raw[0],
|
||||
lat: parseFloat(raw[1]),
|
||||
lon: parseFloat(raw[2]),
|
||||
oLat: parseFloat(raw[3]),
|
||||
oLon: parseFloat(raw[4])
|
||||
// move: raw[5],
|
||||
// piece: raw[6]
|
||||
};
|
||||
data.lat += Math.random() - 0.5;
|
||||
data.lon += Math.random() - 0.5;
|
||||
var orig = world.getXY(data.lat, data.lon);
|
||||
var dot = paper.circle().attr({
|
||||
fill: "#FE7727",
|
||||
r: 1.2,
|
||||
'stroke-width': 0
|
||||
});
|
||||
dot.attr(orig);
|
||||
setTimeout(function() {
|
||||
dot.remove();
|
||||
}, 5000);
|
||||
if (data.oLat) {
|
||||
var dest = world.getXY(data.oLat, data.oLon);
|
||||
dest.lat += Math.random() - 0.5;
|
||||
dest.lon += Math.random() - 0.5;
|
||||
var str = "M" + orig.cx + "," + orig.cy + "T" + dest.cx + "," + dest.cy;
|
||||
var lightning = paper.path(str);
|
||||
lightning.attr({
|
||||
opacity: 0.2,
|
||||
stroke: "#fff",
|
||||
'stroke-width': 0.5
|
||||
});
|
||||
setTimeout(function() {
|
||||
lightning.remove();
|
||||
var line = paper.path(str);
|
||||
line.attr({
|
||||
opacity: 0.35,
|
||||
stroke: "#FE7727",
|
||||
'stroke-width': 0.5,
|
||||
'arrow-end': 'oval-wide-long'
|
||||
});
|
||||
setTimeout(function() {
|
||||
line.remove();
|
||||
var drag = paper.path(str);
|
||||
drag.attr({
|
||||
opacity: 0.2,
|
||||
stroke: "#FE7727",
|
||||
'stroke-width': 0.5,
|
||||
'arrow-end': 'oval-wide-long'
|
||||
});
|
||||
setTimeout(function() {
|
||||
drag.remove();
|
||||
}, 500);
|
||||
}, 500);
|
||||
}, 50);
|
||||
}
|
||||
var source = new EventSource("/network/stream");
|
||||
|
||||
// moves
|
||||
stats.nMoves++;
|
||||
$('#moves > span').text(stats.nMoves);
|
||||
// top countries
|
||||
if (data.country in stats.countries) stats.countries[data.country]++;
|
||||
else stats.countries[data.country] = 1;
|
||||
}, false);
|
||||
// source.addEventListener('open', function(e) {
|
||||
// // Connection was opened.
|
||||
// // console.log("connection opened");
|
||||
// }, false);
|
||||
// source.addEventListener('error', function(e) {
|
||||
// if (e.readyState == EventSource.CLOSED) {
|
||||
// // Connection was closed.
|
||||
// // console.log("connection closed");
|
||||
// }
|
||||
// }, false);
|
||||
var removeFunctions = {};
|
||||
|
||||
var drawPoint = function(pos) {
|
||||
var dot = paper.circle().attr({
|
||||
opacity: 0.5,
|
||||
fill: "#fff",
|
||||
stroke: "#FE7727",
|
||||
r: 1.5,
|
||||
'stroke-width': 1
|
||||
}).attr(pos);
|
||||
var shadow = paper.circle().attr({
|
||||
opacity: 0.03,
|
||||
fill: "#fff",
|
||||
r: 6
|
||||
}).attr(pos);
|
||||
return function() {
|
||||
dot.remove();
|
||||
shadow.remove();
|
||||
};
|
||||
};
|
||||
|
||||
var drawLine = function(pos) {
|
||||
var str = "M" + pos[0].cx + "," + pos[0].cy + "T" + pos[1].cx + "," + pos[1].cy;
|
||||
var line = paper.path(str).attr({
|
||||
opacity: 0.5,
|
||||
stroke: "#FE7727",
|
||||
'stroke-width': 1
|
||||
});
|
||||
return function() {
|
||||
line.remove();
|
||||
};
|
||||
}
|
||||
|
||||
source.addEventListener('message', function(e) {
|
||||
var data = JSON.parse(e.data);
|
||||
if (removeFunctions[data.id]) {
|
||||
removeFunctions[data.id].forEach(function(f) {
|
||||
f();
|
||||
});
|
||||
delete removeFunctions[data.id];
|
||||
}
|
||||
if (!data.ps) return;
|
||||
var pos = data.ps.map(point2pos);
|
||||
removeFunctions[data.id] = pos.map(drawPoint);
|
||||
if (data.ps[1]) removeFunctions[data.id].push(drawLine(pos));
|
||||
}, false);
|
||||
});
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
$(function() {
|
||||
var since = Date.now();
|
||||
function startTime()
|
||||
{
|
||||
var now = Date.now(),
|
||||
elapsed = new Date(now - since),
|
||||
h=formatTime(elapsed.getUTCHours()),
|
||||
m=formatTime(elapsed.getUTCMinutes()),
|
||||
s=formatTime(elapsed.getUTCSeconds());
|
||||
$('#time > span').text(h+":"+m+":"+s);
|
||||
setTimeout(function(){startTime();},500);
|
||||
}
|
||||
|
||||
function formatTime(i)
|
||||
{
|
||||
if (i < 10) {
|
||||
i="0" + i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
startTime();
|
||||
});
|
Loading…
Reference in a new issue