Skip to content

Commit 224b4ef

Browse files
committed
add map ladders, fix #2477
1 parent 9b91ee7 commit 224b4ef

8 files changed

Lines changed: 232 additions & 24 deletions

File tree

Zero-K.info/Controllers/LaddersController.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Web.UI;
1010
using Microsoft.Linq.Translations;
1111
using PlasmaShared;
12+
using Ratings;
1213
using ZkData;
1314

1415
namespace ZeroKWeb.Controllers
@@ -71,5 +72,43 @@ public class LaddersIndexModel
7172

7273
public IQueryable<Indexed<Account>> Data;
7374
}
75+
76+
77+
// GET: /Ladders/Maps
78+
public ActionResult Maps(LaddersMapsModel model)
79+
{
80+
model = model ?? new LaddersMapsModel();
81+
82+
var ret = MapRatings.GetMapRanking().AsQueryable();
83+
84+
if (!string.IsNullOrEmpty(model.Name))
85+
{
86+
var termLower = model.Name.ToLower();
87+
ret = ret.Where(x => x.Map.InternalName.ToLower().Contains(termLower));
88+
}
89+
if (!string.IsNullOrEmpty(model.Author))
90+
{
91+
var termLower = model.Author.ToLower();
92+
ret = ret.Where(x => x.Map.AuthorName.ToLower().Contains(termLower));
93+
}
94+
95+
if (model.SizeFrom.HasValue) ret = ret.Where(x => x.Map.MapWidth >= model.SizeFrom && x.Map.MapHeight >= model.SizeFrom);
96+
if (model.SizeTo.HasValue) ret = ret.Where(x => x.Map.MapWidth <= model.SizeTo && x.Map.MapHeight <= model.SizeTo);
97+
98+
model.Data = ret;
99+
100+
return View("LaddersMaps", model);
101+
}
102+
103+
public class LaddersMapsModel
104+
{
105+
public string Name { get; set; }
106+
public string Author { get; set; }
107+
108+
public int? SizeFrom { get; set; } = 0;
109+
public int? SizeTo { get; set; } = 1000;
110+
111+
public IQueryable<MapRatings.Rating> Data;
112+
}
74113
}
75114
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
@using ZeroKWeb
2+
@using ZeroKWeb.Controllers
3+
@using ZkData
4+
@using Ratings
5+
@using PlasmaShared
6+
@model LaddersController.LaddersMapsModel
7+
@{
8+
Page.Title = "Map ladders";
9+
10+
var grid = new UniGrid<MapRatings.Rating>(Model.Data, "Ladders");
11+
12+
grid.AddCol("Rank", x => string.Format("{0}.",x.Rank + 1));
13+
grid.AddCol("Map", x => Html.PrintMap(x.Map.InternalName));
14+
grid.AddCol("Rating", x => string.Format("{0} ± {1}", Math.Round(x.Elo), Math.Round(x.EloStdev, 1)));
15+
16+
grid.PageSize = 50;
17+
}
18+
19+
20+
<div id="userIndex">
21+
<h1>Detailed Zero-K Ladders</h1> <br />
22+
23+
@using (Ajax.BeginForm("Maps", "Ladders", Global.GetAjaxOptions("userIndex")))
24+
{
25+
<table>
26+
<tr>
27+
<td>Name:</td>
28+
<td>@Html.TextBoxFor(x => x.Name, new { data_autocomplete = Url.Action("Maps", "Autocomplete") })</td>
29+
</tr>
30+
<tr>
31+
<td>Author:</td>
32+
<td>@Html.TextBoxFor(x => x.Author)</td>
33+
</tr>
34+
<tr>
35+
<td>Size:</td>
36+
<td>@Html.TextBoxFor(x => x.SizeFrom) - @Html.TextBoxFor(x => x.SizeTo)</td>
37+
</tr>
38+
</table>
39+
<input type="submit" value="Show" />
40+
41+
@GridHelpers.RenderTable(grid)
42+
}
43+
</div>

Zero-K.info/asp.net.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,9 @@
15891589
<Content Include="Views\PlanetwarsAdmin\PlanetwarsAdminIndex.cshtml" />
15901590
<Content Include="Views\Admin\DynamicConfigDetail.cshtml" />
15911591
<Content Include="Views\Ladders\LaddersFull.cshtml" />
1592+
<Content Include="Views\Ladders\LaddersMaps.cshtml">
1593+
<BrowseToURL>~/Ladders/LaddersMaps</BrowseToURL>
1594+
</Content>
15921595
</ItemGroup>
15931596
<ItemGroup>
15941597
<Content Include="Views\_ViewStart.cshtml" />

ZkData/Ef/WHR/MapRatings.cs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Data.Entity;
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using ZkData;
11+
12+
namespace Ratings
13+
{
14+
public static class MapRatings
15+
{
16+
private static readonly Timer ladderRecalculationTimer = new Timer((t) => { UpdateRatings(); }, null, 15 * 60000, (int)(GlobalConst.LadderUpdatePeriod * 3600 * 1000 + 4242));
17+
18+
private static ConcurrentDictionary<int, Player> maps = new ConcurrentDictionary<int, Player>();
19+
private static ConcurrentDictionary<int, Rating> mapRatings = new ConcurrentDictionary<int, Rating>();
20+
private static List<Rating> mapRanking = new List<Rating>();
21+
private static int lastPollId = -1;
22+
private static readonly Rating defaultRating = new Rating(WholeHistoryRating.RatingOffset, float.PositiveInfinity, null, int.MaxValue, 1);
23+
24+
public static void Init()
25+
{
26+
27+
Task.Factory.StartNew(() =>
28+
{
29+
UpdateRatings(true);
30+
});
31+
}
32+
33+
public static Rating GetMapRating(int ResourceId)
34+
{
35+
Rating rating;
36+
if (mapRatings.TryGetValue(ResourceId, out rating)) return rating;
37+
return defaultRating;
38+
}
39+
40+
public static List<Rating> GetMapRanking()
41+
{
42+
return mapRanking;
43+
}
44+
45+
private static Player GetPlayer(int id)
46+
{
47+
lock (maps)
48+
{
49+
Player player;
50+
if (maps.TryGetValue(id, out player)) return player;
51+
player = new Player(id);
52+
maps.TryAdd(id, player);
53+
return player;
54+
}
55+
}
56+
57+
private static void UpdateRatings(bool init = false)
58+
{
59+
try
60+
{
61+
using (var db = new ZkDataContext())
62+
{
63+
db.MapPollOutcomes.Where(x => x.MapPollID > lastPollId).Include(x => x.MapPollOptions).OrderBy(x => x.MapPollID).AsNoTracking().AsEnumerable().ForEach(poll =>
64+
{
65+
var opts = poll.MapPollOptions.OrderByDescending(x => x.Votes).ToList();
66+
var winners = opts.Where(x => x.Votes == opts[0].Votes);
67+
var losers = opts.Where(x => x.Votes != opts[0].Votes);
68+
if (losers.Count() > 0)
69+
{
70+
var game = new Game(winners.Select(x => GetPlayer(x.ResourceID)).ToList(), losers.Select(x => GetPlayer(x.ResourceID)).ToList(), true, 0, poll.MapPollID);
71+
game.whitePlayers.ForEach(x => x.AddGame(game));
72+
game.blackPlayers.ForEach(x => x.AddGame(game));
73+
}
74+
lastPollId = poll.MapPollID;
75+
});
76+
}
77+
if (init) RunIterations(70);
78+
RunIterations(3);
79+
using (var db = new ZkDataContext())
80+
{
81+
var newRanking = maps.Values.Select(x => x.days[0]).OrderByDescending(x => x.r).ToList();
82+
var ranks = new List<Rating>();
83+
for (int i = 0; i < newRanking.Count; i++)
84+
{
85+
var rating = new Rating(newRanking[i].GetElo() + WholeHistoryRating.RatingOffset, newRanking[i].GetEloStdev(), db.Resources.FirstOrDefault(r => r.ResourceID == newRanking[i].player.id), i, i / (float)newRanking.Count);
86+
ranks.Add(rating);
87+
mapRatings[newRanking[i].player.id] = rating;
88+
}
89+
mapRanking = ranks;
90+
}
91+
}
92+
catch (Exception ex)
93+
{
94+
Trace.TraceError("Error calculating map ratings: " + ex);
95+
}
96+
}
97+
98+
private static void RunIterations(int count)
99+
{
100+
for (int i = 0; i < count - 1; i++)
101+
{
102+
maps.Values.ForEach(x => x.RunOneNewtonIteration(false));
103+
}
104+
maps.Values.ForEach(x => x.RunOneNewtonIteration(true));
105+
}
106+
107+
public class Rating
108+
{
109+
public readonly float Elo, EloStdev, Percentile;
110+
public readonly Resource Map;
111+
public readonly int Rank;
112+
113+
public Rating(float elo, float stdev, Resource map, int rank, float percentile)
114+
{
115+
Elo = elo;
116+
EloStdev = stdev;
117+
Map = map;
118+
Rank = rank;
119+
Percentile = percentile;
120+
}
121+
}
122+
}
123+
}

ZkData/Ef/WHR/Player.cs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public class Player
2727

2828
private static List<float> gradient = new List<float>();
2929

30+
private static readonly object whrLock = new object();
31+
3032
private List<float> sigma2 = new List<float>();
3133

3234
static Player()
@@ -87,22 +89,25 @@ private void UpdateGradient()
8789
gradient[days.Count - 1] = days[days.Count - 1].GetLogLikelyhoodFirstDerivative() - (days[days.Count - 1].r - days[days.Count - 2].r) / sigma2[days.Count - 2];
8890
}
8991

90-
public void RunOneNewtonIteration()
92+
public void RunOneNewtonIteration(bool updateCovariance)
9193
{
92-
foreach (PlayerDay day in days)
94+
lock (whrLock)
9395
{
94-
day.UpdateGameTermsCache();
95-
}
96+
foreach (PlayerDay day in days)
97+
{
98+
day.UpdateGameTermsCache();
99+
}
96100

97-
if (days.Count == 1)
98-
{
99-
days[0].UpdateByOneDimensionalNewton();
100-
}
101-
else if (days.Count > 1)
102-
{
103-
UpdateByNDimNewton();
101+
if (days.Count == 1)
102+
{
103+
days[0].UpdateByOneDimensionalNewton();
104+
}
105+
else if (days.Count > 1)
106+
{
107+
UpdateByNDimNewton();
108+
}
109+
if (updateCovariance) UpdateRatingVariance();
104110
}
105-
UpdateRatingVariance();
106111
}
107112

108113

ZkData/Ef/WHR/WholeHistoryRating.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace Ratings
2222
public class WholeHistoryRating : IRatingSystem
2323
{
2424

25-
const float RatingOffset = 1500;
25+
public const float RatingOffset = 1500;
2626
public static readonly PlayerRating DefaultRating = new PlayerRating(int.MaxValue, 1, RatingOffset, float.PositiveInfinity, GlobalConst.NaturalRatingVariancePerDay(0), 0, 0);
2727

2828
IDictionary<ITopPlayersUpdateListener, int> topPlayersUpdateListeners = new Dictionary<ITopPlayersUpdateListener, int>();
@@ -322,7 +322,7 @@ public void UpdateRatings()
322322
{
323323
Trace.TraceInformation("Updating WHR " + category + " ratings for last Battle: " + latestBattle.SpringBattleID);
324324
IEnumerable<Player> players = latestBattle.SpringBattlePlayers.Where(p => !p.IsSpectator).Select(p => getPlayerById(RatingSystems.GetRatingId(p.AccountID)));
325-
players.ForEach(p => p.RunOneNewtonIteration());
325+
players.ForEach(p => p.RunOneNewtonIteration(true));
326326
UpdateRankings(players);
327327
});
328328
}
@@ -649,10 +649,11 @@ private Game AddGame(Game game)
649649

650650
private void runIterations(int count)
651651
{
652-
for (int i = 0; i < count; i++)
652+
for (int i = 0; i < count - 1; i++)
653653
{
654-
runSingleIteration();
654+
players.Values.ForEach(x => x.RunOneNewtonIteration(false));
655655
}
656+
players.Values.ForEach(x => x.RunOneNewtonIteration(true));
656657
}
657658

658659
private void printStats()
@@ -681,14 +682,6 @@ private void printStats()
681682
Trace.TraceInformation("Amount > 0in " + bigger);
682683
Trace.TraceInformation("Amount < 0in " + (total - bigger));
683684
}
684-
685-
private void runSingleIteration()
686-
{
687-
foreach (Player p in players.Values)
688-
{
689-
p.RunOneNewtonIteration();
690-
}
691-
}
692685
}
693686

694687
public class RatingUpdate : EventArgs

ZkData/ZkData.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
<Compile Include="Ef\SpringBattleBot.cs" />
8787
<Compile Include="Ef\WHR\Game.cs" />
8888
<Compile Include="Ef\WHR\IRatingSystem.cs" />
89+
<Compile Include="Ef\WHR\MapRatings.cs" />
8990
<Compile Include="Ef\WHR\Ranks.cs" />
9091
<Compile Include="Ef\WHR\ITopPlayersUpdateListener.cs" />
9192
<Compile Include="Ef\WHR\Player.cs" />

ZkLobbyServer/ZkLobbyServer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public class ZkLobbyServer
5757
public ZkLobbyServer(string geoIPpath, IPlanetwarsEventCreator creator)
5858
{
5959
RatingSystems.Init();
60+
MapRatings.Init();
6061

6162
PlanetWarsEventCreator = creator;
6263
var entry = Assembly.GetExecutingAssembly();

0 commit comments

Comments
 (0)