From 6451060eeb7f0f445621a0b71b21085a829301c4 Mon Sep 17 00:00:00 2001 From: afuersch Date: Thu, 24 Sep 2015 19:16:04 +0200 Subject: [PATCH 1/9] #1 Add action, model and view for simple players list. --- .../Controllers/PlayersController.cs | 44 +++++++++++++++++++ .../ViewModels/Players/IndexViewModel.cs | 12 +++++ src/Wuzlstats/Views/Players/Index.cshtml | 20 +++++++++ 3 files changed, 76 insertions(+) create mode 100644 src/Wuzlstats/Controllers/PlayersController.cs create mode 100644 src/Wuzlstats/ViewModels/Players/IndexViewModel.cs create mode 100644 src/Wuzlstats/Views/Players/Index.cshtml diff --git a/src/Wuzlstats/Controllers/PlayersController.cs b/src/Wuzlstats/Controllers/PlayersController.cs new file mode 100644 index 0000000..6dcaa5f --- /dev/null +++ b/src/Wuzlstats/Controllers/PlayersController.cs @@ -0,0 +1,44 @@ +using System.Linq; +using Microsoft.AspNet.Mvc; +using Wuzlstats.Models; +using Wuzlstats.ViewModels.Players; +using System; + +namespace Wuzlstats.Controllers +{ + public class PlayersController : Controller + { + private readonly Db _db; + private readonly AppSettings _settings; + + public PlayersController(Db db, AppSettings settings) + { + _settings = settings; + _db = db; + } + + [Route("~/League/{league}/Players")] + public IActionResult Index(string league) + { + var leagueEntity = _db.Leagues.FirstOrDefault(x => x.Name.ToLower() == league.ToLower()); + if (leagueEntity == null) + { + return RedirectToAction("Index", "Leagues"); + } + ViewBag.CurrentLeague = leagueEntity.Name; + var players = _db.Players.Where(x => x.LeagueId == leagueEntity.Id).Select(x => new + { + Id = x.Id, + Name = x.Name, + Image = x.Image + }).ToList(); + + return View(players.Select(player => new IndexViewModel + { + PlayerId = player.Id, + Name = player.Name, + Image = player.Image == null || player.Image.Length <= 0 ? EmptyAvatar.Base64 : Convert.ToBase64String(player.Image) + })); + } + } +} diff --git a/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs b/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs new file mode 100644 index 0000000..e4fd5ec --- /dev/null +++ b/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs @@ -0,0 +1,12 @@ +using System; + +namespace Wuzlstats.ViewModels.Players +{ + public class IndexViewModel + { + public int PlayerId { get; set; } + public string Name { get; set; } + public string Image { get; set; } + public DateTime LastGamePlayedOn { get; set; } + } +} diff --git a/src/Wuzlstats/Views/Players/Index.cshtml b/src/Wuzlstats/Views/Players/Index.cshtml new file mode 100644 index 0000000..3cf54b8 --- /dev/null +++ b/src/Wuzlstats/Views/Players/Index.cshtml @@ -0,0 +1,20 @@ +@model IEnumerable + +

These are all the players. Ever.
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.

+ + From d7d88578af9d18c4f47ac4f927c42fd619ebf0f0 Mon Sep 17 00:00:00 2001 From: afuersch Date: Sat, 10 Oct 2015 17:00:56 +0200 Subject: [PATCH 2/9] #1 Add service for retrieving players --- src/Wuzlstats/Services/PlayersService.cs | 119 +++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/Wuzlstats/Services/PlayersService.cs diff --git a/src/Wuzlstats/Services/PlayersService.cs b/src/Wuzlstats/Services/PlayersService.cs new file mode 100644 index 0000000..e736c74 --- /dev/null +++ b/src/Wuzlstats/Services/PlayersService.cs @@ -0,0 +1,119 @@ +using Microsoft.Data.Entity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Wuzlstats.Models; + +namespace Wuzlstats.Services +{ + public class PlayersService + { + private readonly Db _db; + + public PlayersService(Db db) + { + _db = db; + } + + public async Task> FindPlayersOfLeague(int leagueId, int? daysForStatistics) + { + var gamesQuery = _db.Games.AsNoTracking().Where(x => x.LeagueId == leagueId); + if (daysForStatistics.HasValue) + { + var date = DateTime.UtcNow.Date.AddDays(-daysForStatistics.Value); + gamesQuery = gamesQuery.Where(x => x.Date >= date); + } + + // EF7 beta4 does not support navigation properties in queries yet + // this complicates the code a lot, because we need joins :( + + var players = new List(); + + 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 + select new + { + position.Position, + Player = player + }).ToListAsync(); + // player stats + foreach (var position in positions) + { + var player = players.FirstOrDefault(x => x.Equals(position.Player)); + if (player == null) + { + player = PlayerDto.Create(position.Player); + players.Add(player); + } + // calculate count of single or team games + if (position.Position == PlayerPositionTypes.Blue || position.Position == PlayerPositionTypes.Red) + { + player.SingleGames++; + } + else + { + player.TeamGames++; + } + + // don't count ties + if (game.BlueWins && (position.Position == PlayerPositionTypes.Blue || position.Position == PlayerPositionTypes.BlueDefense || position.Position == PlayerPositionTypes.BlueOffense)) + { + player.Wins++; + } + else if (game.RedWins && (position.Position == PlayerPositionTypes.Red || position.Position == PlayerPositionTypes.RedDefense || position.Position == PlayerPositionTypes.RedOffense)) + { + player.Wins++; + } + else if (game.RedWins && + (position.Position == PlayerPositionTypes.Blue || position.Position == PlayerPositionTypes.BlueDefense || position.Position == PlayerPositionTypes.BlueOffense)) + { + player.Losses++; + } + else if (game.BlueWins && + (position.Position == PlayerPositionTypes.Red || position.Position == PlayerPositionTypes.RedDefense || position.Position == PlayerPositionTypes.RedOffense)) + { + player.Losses++; + } + if (game.Date > player.LatestGame) + { + player.LatestGame = game.Date; + } + } + } + return players.OrderByDescending(x => x.LatestGame); + } + } + + public class PlayerDto + { + public int Id { get; set; } + public string Name { get; set; } + public byte[] Image { get; set; } + public int Wins { get; set; } + public int Losses { get; set; } + public int SingleGames { get; set; } + public int TeamGames { get; set; } + public DateTime LatestGame { get; set; } + + public double Rank => Losses == 0 ? Wins : (Wins == 0 ? 0.1d / Losses : (double)Wins / Losses); + + public bool Equals(Player p) + { + return Id == p.Id; + } + + public static PlayerDto Create(Player p) + { + return new PlayerDto + { + Id = p.Id, + Name = p.Name, + Image = p.Image + }; + } + } +} From d4c2dac9455612c9a47d1a8a954822152f3c3774 Mon Sep 17 00:00:00 2001 From: afuersch Date: Sat, 10 Oct 2015 17:01:35 +0200 Subject: [PATCH 3/9] #1 Modify players action, view model and view. --- .../Controllers/PlayersController.cs | 21 ++++--- .../ViewModels/Players/IndexViewModel.cs | 4 ++ src/Wuzlstats/Views/Players/Index.cshtml | 60 ++++++++++++++----- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/Wuzlstats/Controllers/PlayersController.cs b/src/Wuzlstats/Controllers/PlayersController.cs index 6dcaa5f..d12bd61 100644 --- a/src/Wuzlstats/Controllers/PlayersController.cs +++ b/src/Wuzlstats/Controllers/PlayersController.cs @@ -3,6 +3,8 @@ using Wuzlstats.Models; using Wuzlstats.ViewModels.Players; using System; +using System.Threading.Tasks; +using Wuzlstats.Services; namespace Wuzlstats.Controllers { @@ -10,15 +12,17 @@ public class PlayersController : Controller { private readonly Db _db; private readonly AppSettings _settings; + private readonly PlayersService _statisticsService; public PlayersController(Db db, AppSettings settings) { _settings = settings; _db = db; + _statisticsService = new PlayersService(_db); } [Route("~/League/{league}/Players")] - public IActionResult Index(string league) + public async Task Index(string league) { var leagueEntity = _db.Leagues.FirstOrDefault(x => x.Name.ToLower() == league.ToLower()); if (leagueEntity == null) @@ -26,19 +30,20 @@ public IActionResult Index(string league) return RedirectToAction("Index", "Leagues"); } ViewBag.CurrentLeague = leagueEntity.Name; - var players = _db.Players.Where(x => x.LeagueId == leagueEntity.Id).Select(x => new - { - Id = x.Id, - Name = x.Name, - Image = x.Image - }).ToList(); + var players = await _statisticsService.FindPlayersOfLeague(leagueEntity.Id, /*_settings.DaysForStatistics*/ null); return View(players.Select(player => new IndexViewModel { PlayerId = player.Id, Name = player.Name, - Image = player.Image == null || player.Image.Length <= 0 ? EmptyAvatar.Base64 : Convert.ToBase64String(player.Image) + Image = player.Image == null || player.Image.Length <= 0 ? EmptyAvatar.Base64 : Convert.ToBase64String(player.Image), + Wins = player.Wins, + Losses = player.Losses, + SingleGames = player.SingleGames, + TeamGames = player.TeamGames, + LastGamePlayedOn = player.LatestGame })); } + } } diff --git a/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs b/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs index e4fd5ec..40286d3 100644 --- a/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs +++ b/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs @@ -7,6 +7,10 @@ public class IndexViewModel public int PlayerId { get; set; } public string Name { get; set; } public string Image { get; set; } + public int Wins { get; set; } + public int Losses { get; set; } public DateTime LastGamePlayedOn { get; set; } + public int SingleGames { get; set; } + public int TeamGames { get; set; } } } diff --git a/src/Wuzlstats/Views/Players/Index.cshtml b/src/Wuzlstats/Views/Players/Index.cshtml index 3cf54b8..3e40186 100644 --- a/src/Wuzlstats/Views/Players/Index.cshtml +++ b/src/Wuzlstats/Views/Players/Index.cshtml @@ -2,19 +2,47 @@

These are all the players. Ever.
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.

-
    - @foreach (var player in Model) - { -
  • - - @player.Name - -
    @player.Name
    -
    - #@player.PlayerId - @*3 - 0*@ -
    -
  • - } -
+ + + + + + + + + + + + + @{var rank = 1; } + @foreach (var player in Model) + { + + + + + + + + + } + +
PlayerScoreLast game# 1 vs. 1# 2 vs. 2
+ @(rank++) + + + @player.Name + +
@player.Name
+
+
+ @player.Wins + @player.Losses +
+
+ @player.LastGamePlayedOn.ToShortDateString() + + @player.SingleGames + + @player.TeamGames +
From 1171ae251d042a4efe18f193f7d222379a92152a Mon Sep 17 00:00:00 2001 From: afuersch Date: Sat, 10 Oct 2015 17:01:56 +0200 Subject: [PATCH 4/9] #1 Add styles for table players list. --- src/Wuzlstats/wwwroot/css/site.css | 7 +++++++ src/Wuzlstats/wwwroot/css/site.scss | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Wuzlstats/wwwroot/css/site.css b/src/Wuzlstats/wwwroot/css/site.css index 1354425..acf822b 100644 --- a/src/Wuzlstats/wwwroot/css/site.css +++ b/src/Wuzlstats/wwwroot/css/site.css @@ -135,3 +135,10 @@ footer { @media (max-width: 768px) { footer { margin-top: 50px; } } + +.players .player-name { + font-weight: bold; + display: inline; } + +.players .player-score { + color: #999; } diff --git a/src/Wuzlstats/wwwroot/css/site.scss b/src/Wuzlstats/wwwroot/css/site.scss index e2672d8..c3b6b2a 100644 --- a/src/Wuzlstats/wwwroot/css/site.scss +++ b/src/Wuzlstats/wwwroot/css/site.scss @@ -204,3 +204,15 @@ footer { margin-top: 50px; } } + +.players { + + .player-name { + font-weight: bold; + display: inline; + } + + .player-score { + color: #999; + } +} From 983b42475ad7d23afa5078ffcd2714282d02a6f1 Mon Sep 17 00:00:00 2001 From: afuersch Date: Sun, 11 Oct 2015 12:16:26 +0200 Subject: [PATCH 5/9] #1 Make players sortable via querystring. --- src/Wuzlstats/Controllers/PlayersController.cs | 17 ++++++++++++++++- src/Wuzlstats/Views/Players/Index.cshtml | 6 ++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Wuzlstats/Controllers/PlayersController.cs b/src/Wuzlstats/Controllers/PlayersController.cs index d12bd61..49e8599 100644 --- a/src/Wuzlstats/Controllers/PlayersController.cs +++ b/src/Wuzlstats/Controllers/PlayersController.cs @@ -22,7 +22,7 @@ public PlayersController(Db db, AppSettings settings) } [Route("~/League/{league}/Players")] - public async Task Index(string league) + public async Task Index(string league, string sort) { var leagueEntity = _db.Leagues.FirstOrDefault(x => x.Name.ToLower() == league.ToLower()); if (leagueEntity == null) @@ -32,6 +32,21 @@ public async Task Index(string league) ViewBag.CurrentLeague = leagueEntity.Name; var players = await _statisticsService.FindPlayersOfLeague(leagueEntity.Id, /*_settings.DaysForStatistics*/ null); + switch (sort) + { + case "best": + players = players.OrderByDescending(x => x.Rank); + break; + case "worst": + players = players.OrderBy(x => x.Rank); + break; + case "activity": + players = players.OrderByDescending(x => x.Wins + x.Losses); + break; + default: + break; + } + return View(players.Select(player => new IndexViewModel { PlayerId = player.Id, diff --git a/src/Wuzlstats/Views/Players/Index.cshtml b/src/Wuzlstats/Views/Players/Index.cshtml index 3e40186..81fec62 100644 --- a/src/Wuzlstats/Views/Players/Index.cshtml +++ b/src/Wuzlstats/Views/Players/Index.cshtml @@ -2,6 +2,12 @@

These are all the players. Ever.
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.

+ + From fce876fffa767f3ea508cc925886434117dd370f Mon Sep 17 00:00:00 2001 From: afuersch Date: Thu, 15 Oct 2015 21:21:20 +0200 Subject: [PATCH 6/9] #1 Change index VM, add players VM, support filters. --- .../Controllers/PlayersController.cs | 29 +++++++++++-------- .../ViewModels/Players/IndexViewModel.cs | 13 +++------ .../ViewModels/Players/PlayerViewModel.cs | 16 ++++++++++ src/Wuzlstats/Views/Players/Index.cshtml | 13 +++++---- 4 files changed, 44 insertions(+), 27 deletions(-) create mode 100644 src/Wuzlstats/ViewModels/Players/PlayerViewModel.cs diff --git a/src/Wuzlstats/Controllers/PlayersController.cs b/src/Wuzlstats/Controllers/PlayersController.cs index 49e8599..38690cc 100644 --- a/src/Wuzlstats/Controllers/PlayersController.cs +++ b/src/Wuzlstats/Controllers/PlayersController.cs @@ -22,7 +22,7 @@ public PlayersController(Db db, AppSettings settings) } [Route("~/League/{league}/Players")] - public async Task Index(string league, string sort) + public async Task Index(string league, string sort, bool recent) { var leagueEntity = _db.Leagues.FirstOrDefault(x => x.Name.ToLower() == league.ToLower()); if (leagueEntity == null) @@ -30,7 +30,7 @@ public async Task Index(string league, string sort) return RedirectToAction("Index", "Leagues"); } ViewBag.CurrentLeague = leagueEntity.Name; - var players = await _statisticsService.FindPlayersOfLeague(leagueEntity.Id, /*_settings.DaysForStatistics*/ null); + var players = await _statisticsService.FindPlayersOfLeague(leagueEntity.Id, recent ?_settings.DaysForStatistics : default(int?)); switch (sort) { @@ -47,17 +47,22 @@ public async Task Index(string league, string sort) break; } - return View(players.Select(player => new IndexViewModel + return View(new IndexViewModel { - PlayerId = player.Id, - Name = player.Name, - Image = player.Image == null || player.Image.Length <= 0 ? EmptyAvatar.Base64 : Convert.ToBase64String(player.Image), - Wins = player.Wins, - Losses = player.Losses, - SingleGames = player.SingleGames, - TeamGames = player.TeamGames, - LastGamePlayedOn = player.LatestGame - })); + ActiveFilter = sort, + Recent = recent, + Players = players.Select(player => new PlayerViewModel + { + PlayerId = player.Id, + Name = player.Name, + Image = player.Image == null || player.Image.Length <= 0 ? EmptyAvatar.Base64 : Convert.ToBase64String(player.Image), + Wins = player.Wins, + Losses = player.Losses, + SingleGames = player.SingleGames, + TeamGames = player.TeamGames, + LastGamePlayedOn = player.LatestGame + }) + }); } } diff --git a/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs b/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs index 40286d3..d843f84 100644 --- a/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs +++ b/src/Wuzlstats/ViewModels/Players/IndexViewModel.cs @@ -1,16 +1,11 @@ -using System; +using System.Collections.Generic; namespace Wuzlstats.ViewModels.Players { public class IndexViewModel { - public int PlayerId { get; set; } - public string Name { get; set; } - public string Image { get; set; } - public int Wins { get; set; } - public int Losses { get; set; } - public DateTime LastGamePlayedOn { get; set; } - public int SingleGames { get; set; } - public int TeamGames { get; set; } + public string ActiveFilter { get; set; } + public bool Recent { get; set; } + public IEnumerable Players { get; set; } } } diff --git a/src/Wuzlstats/ViewModels/Players/PlayerViewModel.cs b/src/Wuzlstats/ViewModels/Players/PlayerViewModel.cs new file mode 100644 index 0000000..891680e --- /dev/null +++ b/src/Wuzlstats/ViewModels/Players/PlayerViewModel.cs @@ -0,0 +1,16 @@ +using System; + +namespace Wuzlstats.ViewModels.Players +{ + public class PlayerViewModel + { + public int PlayerId { get; set; } + public string Name { get; set; } + public string Image { get; set; } + public int Wins { get; set; } + public int Losses { get; set; } + public DateTime LastGamePlayedOn { get; set; } + public int SingleGames { get; set; } + public int TeamGames { get; set; } + } +} diff --git a/src/Wuzlstats/Views/Players/Index.cshtml b/src/Wuzlstats/Views/Players/Index.cshtml index 81fec62..c0a4e7f 100644 --- a/src/Wuzlstats/Views/Players/Index.cshtml +++ b/src/Wuzlstats/Views/Players/Index.cshtml @@ -1,11 +1,12 @@ -@model IEnumerable +@model Wuzlstats.ViewModels.Players.IndexViewModel -

These are all the players. Ever.
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.

+

These are all the players. Ever.
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.

@@ -21,7 +22,7 @@ @{var rank = 1; } - @foreach (var player in Model) + @foreach (var player in Model.Players) {
From 2a644147c29650cb14078305d8b035deebf3f629 Mon Sep 17 00:00:00 2001 From: afuersch Date: Thu, 15 Oct 2015 21:21:29 +0200 Subject: [PATCH 7/9] #1 Style filters --- src/Wuzlstats/wwwroot/css/site.css | 3 +++ src/Wuzlstats/wwwroot/css/site.scss | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/Wuzlstats/wwwroot/css/site.css b/src/Wuzlstats/wwwroot/css/site.css index acf822b..6081a46 100644 --- a/src/Wuzlstats/wwwroot/css/site.css +++ b/src/Wuzlstats/wwwroot/css/site.css @@ -136,6 +136,9 @@ footer { footer { margin-top: 50px; } } +.filter li.active { + font-weight: bold; } + .players .player-name { font-weight: bold; display: inline; } diff --git a/src/Wuzlstats/wwwroot/css/site.scss b/src/Wuzlstats/wwwroot/css/site.scss index c3b6b2a..4496461 100644 --- a/src/Wuzlstats/wwwroot/css/site.scss +++ b/src/Wuzlstats/wwwroot/css/site.scss @@ -205,6 +205,13 @@ footer { } } +//Players page +.filter { + li.active { + font-weight: bold; + } +} + .players { .player-name { From 9c5c367d2664f8bba5ef96a39376c8ef57e1d1e8 Mon Sep 17 00:00:00 2001 From: afuersch Date: Thu, 15 Oct 2015 21:21:47 +0200 Subject: [PATCH 8/9] #1 Link to players site from home. --- src/Wuzlstats/Views/Home/Index.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Wuzlstats/Views/Home/Index.cshtml b/src/Wuzlstats/Views/Home/Index.cshtml index 8f0c221..9acb4f6 100644 --- a/src/Wuzlstats/Views/Home/Index.cshtml +++ b/src/Wuzlstats/Views/Home/Index.cshtml @@ -31,7 +31,7 @@
-

The best players

+

The best players

  • @@ -39,7 +39,7 @@
-

The worst players

+

The worst players

  • From 95fbad68b9191e342e683bfdcb0840cca170aa3f Mon Sep 17 00:00:00 2001 From: afuersch Date: Thu, 15 Oct 2015 21:30:07 +0200 Subject: [PATCH 9/9] #1 Remove text-decoration on player picture when hovering. --- src/Wuzlstats/wwwroot/css/site.css | 3 +++ src/Wuzlstats/wwwroot/css/site.scss | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/Wuzlstats/wwwroot/css/site.css b/src/Wuzlstats/wwwroot/css/site.css index 6081a46..f558cc7 100644 --- a/src/Wuzlstats/wwwroot/css/site.css +++ b/src/Wuzlstats/wwwroot/css/site.css @@ -139,6 +139,9 @@ footer { .filter li.active { font-weight: bold; } +.players td > a:focus, .players td > a:hover { + text-decoration: none; } + .players .player-name { font-weight: bold; display: inline; } diff --git a/src/Wuzlstats/wwwroot/css/site.scss b/src/Wuzlstats/wwwroot/css/site.scss index 4496461..965078e 100644 --- a/src/Wuzlstats/wwwroot/css/site.scss +++ b/src/Wuzlstats/wwwroot/css/site.scss @@ -213,6 +213,11 @@ footer { } .players { + td > a { + &:focus, &:hover { + text-decoration: none; + } + } .player-name { font-weight: bold;