more work on user perf stats
parent
cfa894dbfe
commit
5b40f990f3
|
@ -23,7 +23,6 @@ evenMoreJs = moreJs,
|
|||
evenMoreCss = moreCss) {
|
||||
<div class="content_box no_padding" id="perfStat">
|
||||
<div class="content_box_top">
|
||||
<span class="righty"><strong>@u.perfs(perfType).nb.localize</strong> @trans.games()</span>
|
||||
<h1 class="lichess_title">
|
||||
<a href="@routes.User.show(u.username)" data-icon="I" class="text">
|
||||
@u.username <span>@perfType.name stats</span>
|
||||
|
|
|
@ -99,6 +99,8 @@ case class Game(
|
|||
|
||||
def updatedAtOrCreatedAt = updatedAt | createdAt
|
||||
|
||||
def durationSeconds = (updatedAtOrCreatedAt.getSeconds - createdAt.getSeconds).toInt
|
||||
|
||||
def lastMoveTimeInSeconds: Option[Int] = lastMoveTime.map(x => (x / 10).toInt)
|
||||
|
||||
// in tenths of seconds
|
||||
|
|
|
@ -42,7 +42,7 @@ object PerfStat {
|
|||
lowest = none,
|
||||
bestWins = Results(Nil),
|
||||
worstLosses = Results(Nil),
|
||||
count = Count(all = 0, rated = 0, win = 0, loss = 0, draw = 0, tour = 0, berserk = 0, opAvg = Avg(0, 0)),
|
||||
count = Count.init,
|
||||
resultStreak = ResultStreak(win = Streaks.init, loss = Streaks.init),
|
||||
playStreak = PlayStreak(nb = Streaks.init, time = Streaks.init, lastDate = none)
|
||||
)
|
||||
|
@ -56,8 +56,8 @@ case class ResultStreak(win: Streaks, loss: Streaks) {
|
|||
|
||||
case class PlayStreak(nb: Streaks, time: Streaks, lastDate: Option[DateTime]) {
|
||||
def agg(pov: Pov) = {
|
||||
val cont = isContinued(pov.game.createdAt)
|
||||
val seconds = (pov.game.updatedAtOrCreatedAt.getSeconds - pov.game.createdAt.getSeconds).toInt
|
||||
val seconds = pov.game.durationSeconds
|
||||
val cont = seconds < 3 * 60 * 60 && isContinued(pov.game.createdAt)
|
||||
copy(
|
||||
nb = nb(cont, pov)(1),
|
||||
time = time(cont, pov)(seconds),
|
||||
|
@ -105,7 +105,9 @@ case class Count(
|
|||
draw: Int,
|
||||
tour: Int,
|
||||
berserk: Int,
|
||||
opAvg: Avg) {
|
||||
opAvg: Avg,
|
||||
seconds: Int,
|
||||
disconnects: Int) {
|
||||
def apply(pov: Pov) = copy(
|
||||
all = all + 1,
|
||||
rated = rated + pov.game.rated.fold(1, 0),
|
||||
|
@ -114,7 +116,17 @@ case class Count(
|
|||
draw = draw + pov.win.isEmpty.fold(1, 0),
|
||||
tour = tour + pov.game.isTournament.fold(1, 0),
|
||||
berserk = berserk + pov.player.berserk.fold(1, 0),
|
||||
opAvg = pov.opponent.stableRating.fold(opAvg)(opAvg.agg))
|
||||
opAvg = pov.opponent.stableRating.fold(opAvg)(opAvg.agg),
|
||||
seconds = seconds + (pov.game.durationSeconds match {
|
||||
case s if s > 3 * 60 * 60 => 0
|
||||
case s => s
|
||||
}),
|
||||
disconnects = disconnects + {
|
||||
~pov.loss && pov.game.status == chess.Status.Timeout
|
||||
}.fold(1, 0))
|
||||
}
|
||||
object Count {
|
||||
val init = Count(all = 0, rated = 0, win = 0, loss = 0, draw = 0, tour = 0, berserk = 0, opAvg = Avg(0, 0), seconds = 0, disconnects = 0)
|
||||
}
|
||||
|
||||
case class Avg(avg: Double, pop: Int) {
|
||||
|
|
|
@ -14,12 +14,6 @@
|
|||
text-transform: uppercase;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#perfStat .content_box_top .righty {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
line-height: 30px;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
#perfStat h2 {
|
||||
font-size: 1.5em;
|
||||
text-transform: uppercase;
|
||||
|
@ -37,6 +31,12 @@
|
|||
#perfStat time {
|
||||
display: inline;
|
||||
}
|
||||
#perfStat green {
|
||||
color: #759900;
|
||||
}
|
||||
#perfStat red {
|
||||
color: #dc322f;
|
||||
}
|
||||
#perfStat .content_box_content a {
|
||||
color: #3893E8;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@
|
|||
}
|
||||
#perfStat .result th,
|
||||
#perfStat .result td {
|
||||
padding: 12px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
#perfStat .result td:first-child {
|
||||
text-indent: 20px;
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
var m = require('mithril');
|
||||
var util = require('./util');
|
||||
|
||||
function percent(x, y) {
|
||||
if (y == 0) return 'N/A';
|
||||
return Math.round(x * 100 / y) + '%';
|
||||
if (y === 0) return 0;
|
||||
return Math.round(x * 100 / y);
|
||||
}
|
||||
|
||||
module.exports = function(d) {
|
||||
var c = d.stat.count;
|
||||
var per = function(x, y) {
|
||||
return percent(x, y || c.all);
|
||||
return percent(x, y || c.all) + '%';
|
||||
};
|
||||
return [
|
||||
m('div.half', m('table', m('tbody', [
|
||||
|
@ -31,7 +32,11 @@ module.exports = function(d) {
|
|||
m('th', 'Berserked games'),
|
||||
m('td', c.berserk),
|
||||
m('td', per(c.berserk, c.tour)),
|
||||
])
|
||||
]),
|
||||
c.seconds ? m('tr.full', [
|
||||
m('th', 'Time spent playing'),
|
||||
m('td[colspan=2]', util.formatSeconds(c.seconds, 'short'))
|
||||
]) : null
|
||||
]))),
|
||||
m('div.half', m('table', m('tbody', [
|
||||
m('tr', [
|
||||
|
@ -41,8 +46,8 @@ module.exports = function(d) {
|
|||
]),
|
||||
m('tr.full', [
|
||||
m('th', 'Victories'),
|
||||
m('td', c.win),
|
||||
m('td', per(c.win))
|
||||
m('td', util.green(c.win)),
|
||||
m('td', util.green(per(c.win)))
|
||||
]),
|
||||
m('tr.full', [
|
||||
m('th', 'Draws'),
|
||||
|
@ -51,9 +56,16 @@ module.exports = function(d) {
|
|||
]),
|
||||
m('tr.full', [
|
||||
m('th', 'Defeats'),
|
||||
m('td', c.loss),
|
||||
m('td', per(c.loss))
|
||||
])
|
||||
m('td', util.red(c.loss)),
|
||||
m('td', util.red(per(c.loss)))
|
||||
]),
|
||||
m('tr.full', (function(color) {
|
||||
return [
|
||||
m('th', 'Disconnections'),
|
||||
m('td', color(c.disconnects)),
|
||||
m('td', color(per(c.disconnects, c.loss)))
|
||||
];
|
||||
})(percent(c.disconnects, c.loss) >= 15 ? util.red : util.identity))
|
||||
])))
|
||||
];
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
var m = require('mithril');
|
||||
var util = require('./util');
|
||||
|
||||
function ratingAt(title, opt) {
|
||||
function ratingAt(title, opt, color) {
|
||||
return util.fMap(opt, function(r) {
|
||||
return [
|
||||
m('h2', [title + ': ', m('strong', r.int)]),
|
||||
m('h2', [title + ': ', m('strong', color(r.int))]),
|
||||
util.gameLink(r.gameId, ['reached ', util.date(r.at)])
|
||||
];
|
||||
}, [
|
||||
|
@ -15,7 +15,7 @@ function ratingAt(title, opt) {
|
|||
|
||||
module.exports = function(d) {
|
||||
return [
|
||||
m('div.half', ratingAt('Highest rating', d.stat.highest)),
|
||||
m('div.half', ratingAt('Lowest rating', d.stat.lowest))
|
||||
m('div.half', ratingAt('Highest rating', d.stat.highest, util.green)),
|
||||
m('div.half', ratingAt('Lowest rating', d.stat.lowest, util.red))
|
||||
];
|
||||
};
|
||||
|
|
|
@ -18,12 +18,6 @@ function streaks(s, display) {
|
|||
]);
|
||||
}
|
||||
|
||||
function formatSeconds(s) {
|
||||
var d = moment.duration(s, 'seconds');
|
||||
var hours = d.days() * 24 + d.hours();
|
||||
return hours + ' hours, ' + d.minutes() + ' minutes';
|
||||
}
|
||||
|
||||
var lessThan = 'Less than one hour between games.';
|
||||
|
||||
module.exports = {
|
||||
|
@ -34,10 +28,10 @@ module.exports = {
|
|||
title: lessThan
|
||||
}, 'Games played in a row')),
|
||||
streaks(s, function(v) {
|
||||
return [
|
||||
return v ? [
|
||||
m('strong', v),
|
||||
' game' + (v > 1 ? 's' : '')
|
||||
];
|
||||
] : 'none';
|
||||
})
|
||||
];
|
||||
});
|
||||
|
@ -48,26 +42,8 @@ module.exports = {
|
|||
m('h2', m('span', {
|
||||
title: 'less than one hour between games'
|
||||
}, 'Max time spent playing')),
|
||||
streaks(s, formatSeconds)
|
||||
streaks(s, util.formatSeconds)
|
||||
];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// fMap(d.stat.playStreak.nb, function(s) {
|
||||
// return m('div', [
|
||||
// m('h2', 'Games played in a row (less than one hour between games)'),
|
||||
// streaks(s)
|
||||
// ]);
|
||||
// }),
|
||||
// fMap(d.stat.playStreak.time, function(s) {
|
||||
// return m('div', [
|
||||
// m('h2', 'Max time spent playing (less than one hour between games)'),
|
||||
// streaks(s, function(s) {
|
||||
// return [
|
||||
// m('strong', formatSeconds(s.v)),
|
||||
// fromTo(s)
|
||||
// ];
|
||||
// })
|
||||
// ]);
|
||||
// })
|
||||
|
|
|
@ -1,33 +1,37 @@
|
|||
var m = require('mithril');
|
||||
var util = require('./util');
|
||||
|
||||
function streak(s, title) {
|
||||
function streak(s, title, color) {
|
||||
return m('div.streak', [
|
||||
m('h3', [
|
||||
title + ': ',
|
||||
m('strong', s.v),
|
||||
' game' + (s.v > 1 ? 's' : '')
|
||||
s.v > 0 ? color([
|
||||
m('strong', s.v),
|
||||
' game' + (s.v > 1 ? 's' : '')
|
||||
]) : 'none'
|
||||
]),
|
||||
util.fromTo(s)
|
||||
]);
|
||||
}
|
||||
|
||||
function streaks(s) {
|
||||
return [
|
||||
streak(s.max, 'Longest'),
|
||||
streak(s.cur, 'Current')
|
||||
];
|
||||
function streaks(color) {
|
||||
return function(s) {
|
||||
return [
|
||||
streak(s.max, 'Longest', color),
|
||||
streak(s.cur, 'Current', color)
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function(d) {
|
||||
return [
|
||||
m('div.half', [
|
||||
m('h2', 'Winning streaks'),
|
||||
util.fMap(d.stat.resultStreak.win, streaks, util.noData)
|
||||
m('h2', 'Winning streak'),
|
||||
util.fMap(d.stat.resultStreak.win, streaks(util.green), util.noData)
|
||||
]),
|
||||
m('div.half', [
|
||||
m('h2', 'Losing streaks'),
|
||||
util.fMap(d.stat.resultStreak.loss, streaks, util.noData)
|
||||
m('h2', 'Losing streak'),
|
||||
util.fMap(d.stat.resultStreak.loss, streaks(util.red), util.noData)
|
||||
])
|
||||
];
|
||||
};
|
||||
|
|
|
@ -53,5 +53,20 @@ module.exports = {
|
|||
m('h3', 'Current streak'), (f || streak)(s.cur)
|
||||
])
|
||||
];
|
||||
},
|
||||
green: function(v) {
|
||||
return m('green', v);
|
||||
},
|
||||
red: function(v) {
|
||||
return m('red', v);
|
||||
},
|
||||
identity: function(v) {
|
||||
return v;
|
||||
},
|
||||
formatSeconds: function(s, format) {
|
||||
var d = moment.duration(s, 'seconds');
|
||||
var hours = d.days() * 24 + d.hours();
|
||||
if (format === 'short') return hours + 'h, ' + d.minutes() + 'm';
|
||||
return hours + ' hours, ' + d.minutes() + ' minutes';
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue