Skip to content

Commit

Permalink
Merge pull request #7 from afuersch/teams-ranking
Browse files Browse the repository at this point in the history
Close #5 Teams ranking.
  • Loading branch information
afuersch authored Feb 2, 2017
2 parents fe236fa + 987deff commit dcac735
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 13 deletions.
59 changes: 59 additions & 0 deletions src/Wuzlstats/Controllers/TeamsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Wuzlstats.Models;
using Wuzlstats.Services;
using Wuzlstats.ViewModels.Teams;

namespace Wuzlstats.Controllers
{
public class TeamsController : Controller
{
private readonly Db _db;
private readonly AppSettings _settings;
private readonly TeamStatisticsService _statisticsService;

public TeamsController(Db db, AppSettings settings)
{
_settings = settings;
_db = db;
_statisticsService = new TeamStatisticsService(_db);
}

[Route("~/League/{league}/Teams")]
public async Task<IActionResult> Index(string league, string sort, bool recent)
{
var leagueEntity = _db.Leagues.FirstOrDefault(x => x.Name.ToLowerInvariant() == league.ToLowerInvariant());
if (leagueEntity == null)
{
return RedirectToAction("Index", "Leagues");
}
ViewBag.CurrentLeague = leagueEntity.Name;
var teams = await _statisticsService.FindTeamsOfLeagueAsync(leagueEntity.Id, recent ? _settings.DaysForStatistics : default(int?));

switch (sort)
{
case "best":
teams = teams.OrderByDescending(x => x.Rank);
break;
case "worst":
teams = teams.OrderBy(x => x.Rank);
break;
case "activity":
teams = teams.OrderByDescending(x => x.Wins + x.Losses);
break;
default:
teams = teams.OrderByDescending(x => x.LastGamePlayedOn);
break;
}

return View(new IndexViewModel
{
ActiveFilter = sort,
Recent = recent,
Days = _settings.DaysForStatistics,
Teams = teams
});
}
}
}
16 changes: 12 additions & 4 deletions src/Wuzlstats/Services/PlayersService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@ public PlayersService(Db db)
_db = db;
}

public async Task<IEnumerable<PlayerViewModel>> FindPlayersOfLeague(int leagueId, int? daysForStatistics)

private IQueryable<Game> FetchGames(int leagueId, int? daysForStatistics)
{
var gamesQuery = _db.Games.AsNoTracking().Where(x => x.LeagueId == leagueId);
if (daysForStatistics.HasValue)
if (!daysForStatistics.HasValue)
{
var date = DateTime.UtcNow.Date.AddDays(-daysForStatistics.Value);
gamesQuery = gamesQuery.Where(x => x.Date >= date);
return gamesQuery;
}
var date = DateTime.UtcNow.Date.AddDays(-daysForStatistics.Value);
return gamesQuery.Where(x => x.Date >= date);
}

public async Task<IEnumerable<PlayerViewModel>> FindPlayersOfLeague(int leagueId, int? daysForStatistics)
{
var gamesQuery = FetchGames(leagueId, daysForStatistics);

// EF7 beta4 does not support navigation properties in queries yet
// this complicates the code a lot, because we need joins :(
Expand Down Expand Up @@ -95,5 +102,6 @@ join player in _db.Players.AsNoTracking() on position.PlayerId equals player.Id
}
return players.OrderByDescending(x => x.LastGamePlayedOn);
}

}
}
127 changes: 127 additions & 0 deletions src/Wuzlstats/Services/TeamStatisticsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Wuzlstats.Models;
using Wuzlstats.ViewModels.Teams;

namespace Wuzlstats.Services
{
public class TeamStatisticsService
{
private readonly Db _db;

public TeamStatisticsService(Db db)
{
_db = db;
}

public async Task<IEnumerable<TeamViewModel>> FindTeamsOfLeagueAsync(int leagueId, int? daysForStatistics)
{
var gamesQuery = FetchGames(leagueId, daysForStatistics);

var teams = new List<TeamViewModel>();

var players2 = await _db.Players.Where(x => x.LeagueId == leagueId).ToListAsync();

var players = players2.Select(player => new PlayerViewModel
{
Id = player.Id,
Name = player.Name,
Image = player.Image == null || player.Image.Length <= 0 ? EmptyAvatar.Base64 : Convert.ToBase64String(player.Image)
}).ToList();

foreach (var game in await gamesQuery.ToListAsync())
{
var positions = await (from position in _db.PlayerPositions.AsNoTracking()
join player in _db.Players.AsNoTracking() on position.PlayerId equals player.Id
where position.GameId == game.Id
where position.Position != PlayerPositionTypes.Blue || position.Position != PlayerPositionTypes.Red
select new
{
position.Position,
Player = player
}).ToListAsync();

// team stats
if (positions.Count(x => x.Position == PlayerPositionTypes.BlueDefense) == 1
&& positions.Count(x => x.Position == PlayerPositionTypes.BlueOffense) == 1
&& positions.Count(x => x.Position == PlayerPositionTypes.RedDefense) == 1
&& positions.Count(x => x.Position == PlayerPositionTypes.RedOffense) == 1)
{
var redOffense = players.Single(x => x.Id == positions.Single(y => y.Position == PlayerPositionTypes.RedOffense).Player.Id);
var redDefense = players.Single(x => x.Id == positions.Single(y => y.Position == PlayerPositionTypes.RedDefense).Player.Id);
var blueOffense = players.Single(x => x.Id == positions.Single(y => y.Position == PlayerPositionTypes.BlueOffense).Player.Id);
var blueDefense = players.Single(x => x.Id == positions.Single(y => y.Position == PlayerPositionTypes.BlueDefense).Player.Id);

var redTeam = teams.FirstOrDefault(x => x.Equals(redOffense, redDefense));
if (redTeam == null)
{
redTeam = CreateTeam(redOffense, redDefense);
teams.Add(redTeam);
}
var blueTeam = teams.FirstOrDefault(x => x.Equals(blueOffense, blueDefense));
if (blueTeam == null)
{
blueTeam = CreateTeam(blueOffense, blueDefense);
teams.Add(blueTeam);
}

if (game.BlueWins)
{
redTeam.Losses++;
blueTeam.Wins++;
}
else if (game.RedWins)
{
redTeam.Wins++;
blueTeam.Losses++;
}
//Resolve date of last played game
ResolveLastGamePlayedOn(redTeam, game.Date);
ResolveLastGamePlayedOn(blueTeam, game.Date);
}
}

return teams;
}


private void ResolveLastGamePlayedOn(TeamViewModel team, DateTime date)
{
if (team.LastGamePlayedOn < date)
{
team.LastGamePlayedOn = date;
}
}

public static TeamViewModel CreateTeam(PlayerViewModel p1, PlayerViewModel p2)
{
if (p1.Id <= p2.Id)
{
return new TeamViewModel
{
Player1 = p1,
Player2 = p2
};
}
return new TeamViewModel
{
Player1 = p2,
Player2 = p1
};
}

private IQueryable<Game> FetchGames(int leagueId, int? daysForStatistics)
{
var gamesQuery = _db.Games.AsNoTracking().Where(x => x.LeagueId == leagueId);
if (!daysForStatistics.HasValue)
{
return gamesQuery;
}
var date = DateTime.UtcNow.Date.AddDays(-daysForStatistics.Value);
return gamesQuery.Where(x => x.Date >= date);
}
}
}
12 changes: 12 additions & 0 deletions src/Wuzlstats/ViewModels/Teams/IndexViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;

namespace Wuzlstats.ViewModels.Teams
{
public class IndexViewModel
{
public string ActiveFilter { get; set; }
public bool Recent { get; set; }
public int Days { get; set; }
public IEnumerable<TeamViewModel> Teams { get; set; }
}
}
11 changes: 11 additions & 0 deletions src/Wuzlstats/ViewModels/Teams/PlayerViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Wuzlstats.ViewModels.Teams
{
public class PlayerViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Image { get; set; }
}
}
30 changes: 30 additions & 0 deletions src/Wuzlstats/ViewModels/Teams/TeamViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;

namespace Wuzlstats.ViewModels.Teams
{
public class TeamViewModel
{
public PlayerViewModel Player1 { get; set; }
public PlayerViewModel Player2 { get; set; }
public int Wins { get; set; }
public int Losses { get; set; }
public DateTime LastGamePlayedOn { get; set; }
public int GamesCount => Wins + Losses;

// ReSharper disable once PossibleLossOfFraction
public double Rank => Losses == 0 ? Wins : (Wins == 0 ? 0.1d / Losses : (double)Wins / Losses);

public bool Equals(PlayerViewModel p1, PlayerViewModel p2)
{
if (Player1 == null || Player2 == null)
{
return false;
}
if (p1.Id <= p2.Id)
{
return Player1.Id == p1.Id && Player2.Id == p2.Id;
}
return Player2.Id == p1.Id && Player1.Id == p2.Id;
}
}
}
4 changes: 2 additions & 2 deletions src/Wuzlstats/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@
</div>
<div class="row">
<div class="col-sm-6">
<h3 class="text-center">The best teams</h3>
<h3 class="text-center"><a href="@Url.Action("Index", "Teams", new { league = Model.Name, sort = "best", recent = true })">The best teams</a></h3>
<ul id="bestTeamRanking">
<li class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%"></div>
</li>
</ul>
</div>
<div class="col-sm-6">
<h3 class="text-center">The worst teams</h3>
<h3 class="text-center"><a href="@Url.Action("Index", "Teams", new { league = Model.Name, sort = "worst", recent = true })">The worst teams</a></h3>
<ul id="worstTeamRanking">
<li class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%"></div>
Expand Down
65 changes: 65 additions & 0 deletions src/Wuzlstats/Views/Teams/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
@model Wuzlstats.ViewModels.Teams.IndexViewModel

@if (Model.Recent)
{
<p class="help-block">
These are the teams of the last @Model.Days days. All of them.<br />This is pretty sweet, but it gets better: There's also
<a href="@Url.Action("Index", "Teams", new { sort = Model.ActiveFilter, recent = false })">this very same statistic, but for all of the time</a>. All of it. Holy crap!
</p>
}
else
{
<p class="help-block">
These are all the teams. Ever.<br />No one will be forgotten. Shame and fame will last till the very end of days, or at least until a database crash with no backups. <br />
There's the same statistic, <a href="@Url.Action("Index", "Teams", new { sort = Model.ActiveFilter, recent = true })">but only for the last @Model.Days days</a>, too.
</p>
}

<div class="input-group">
Order By
<div class="btn-group">
<a href="@Url.Action("Index", "Teams", new { recent = Model.Recent })" class="btn btn-default btn-sm @(string.IsNullOrEmpty(Model.ActiveFilter) ? "active" : "")">Last played</a>
<a href="@Url.Action("Index", "Teams", new { sort = "best", recent = Model.Recent })" class="btn btn-default btn-sm @(Model.ActiveFilter == "best" ? "active" : "")">Best</a>
<a href="@Url.Action("Index", "Teams", new { sort = "worst", recent = Model.Recent })" class="btn btn-default btn-sm @(Model.ActiveFilter == "worst" ? "active" : "")">Worst</a>
<a href="@Url.Action("Index", "Teams", new { sort = "activity", recent = Model.Recent })" class="btn btn-default btn-sm @(Model.ActiveFilter == "activity" ? "active" : "")">Most active</a>
</div>
</div>

<table class="table table-striped ranking teams">
<thead>
<tr>
<th></th>
<th>Team</th>
<th>Score</th>
<th>Last game</th>
<th>Count</th>
</tr>
</thead>
<tbody>
@{var rank = 1; }
@foreach (var team in Model.Teams)
{
<tr>
<td>
@(rank++)
</td>
<td class="team">
@Html.Partial("_Player", team.Player1)
@Html.Partial("_Player", team.Player2)
</td>
<td>
<div class="player-score">
<span class="ranking-wins">@team.Wins<span class="glyphicon glyphicon-thumbs-up"></span></span>
<span class="ranking-losses">@team.Losses<span class="glyphicon glyphicon-thumbs-down"></span></span>
</div>
</td>
<td>
@team.LastGamePlayedOn.ToShortDateString()
</td>
<td>
@team.GamesCount
</td>
</tr>
}
</tbody>
</table>
8 changes: 8 additions & 0 deletions src/Wuzlstats/Views/Teams/_Player.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@model Wuzlstats.ViewModels.Teams.PlayerViewModel

<div class="player">
<a href="@Url.Action("Index", "Player", new { id = Model.Id })">
<img src="data:image/png;base64,@Model.Image" alt="@Model.Name" class="ranking-image" />
</a>
<div class="player-name">@Model.Name</div>
</div>
11 changes: 8 additions & 3 deletions src/Wuzlstats/wwwroot/css/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,21 @@ footer {
footer {
margin-top: 50px; } }

.players td > a:focus, .players td > a:hover {
.players td a:focus, .players td a:hover, .teams td a:focus, .teams td a:hover {
text-decoration: none; }

.players .player-name {
.players .player-name, .teams .player-name {
font-weight: bold;
display: inline; }

.players .player-score {
.players .player-score, .teams .player-score {
color: #999; }

.teams .player {
display: inline-block; }
.teams .player:first-child {
margin-right: 1em; }

footer .nav > li {
padding-bottom: 15px;
padding-top: 15px;
Expand Down
Loading

0 comments on commit dcac735

Please sign in to comment.