]> git.sesse.net Git - wloh/blob - www/index.pl
Add average rating display.
[wloh] / www / index.pl
1 #! /usr/bin/perl
2 use strict;
3 use warnings;
4 no warnings qw(once);
5 use CGI;
6 use CGI::Carp qw( fatalsToBrowser );
7 use DBI;
8 use POSIX;
9 use Devel::Peek;
10 use HTML::Entities;
11 use Encode;
12 use utf8;
13 use locale;
14 require '../config.pm';
15 require '../common.pm';
16
17 my $cgi = CGI->new;
18
19 my $dbh = DBI->connect($config::local_connstr, $config::local_username, $config::local_password)
20         or die "connect: " . $DBI::errstr;
21 $dbh->{AutoCommit} = 0;
22 $dbh->{RaiseError} = 1;
23
24 my $trials = 25_000;
25
26 binmode STDOUT, ':utf8';
27
28 my %players = ();
29 my %ratings = ();
30 my %ratings_stddev = ();
31 my @matches = ();
32 my %parms = ();
33 my $match_stddev;
34
35 sub sanitize {
36         return HTML::Entities::encode_entities(shift);
37 }
38
39 sub color {
40         my $x = shift;
41         return int(255.0 * ($x ** (1.80)));
42 }
43
44 sub make_table {
45         my ($lowest_division, $used_ratings, $used_cov) = @_;
46
47         print <<"EOF";
48
49     <table>
50       <tr>
51         <th></th>
52 EOF
53
54         POSIX::setlocale(&POSIX::LC_ALL, 'C');
55
56         my $tmpnam = POSIX::tmpnam();
57         open MCCALC, ">", $tmpnam
58                 or die "$tmpnam: $!";
59
60         printf MCCALC "%f\n", $match_stddev;
61         printf MCCALC "%d\n", scalar keys %players;
62
63         for my $id (keys %players) {
64                 my $rating = $used_ratings->{$id} // 500.0;
65                 printf MCCALC "%s %f\n", $id, $rating;
66         }
67
68         # covariance matrix
69         for my $id1 (keys %players) {
70                 for my $id2 (keys %players) {
71                         if ($id1 == $id2) {
72                                 printf MCCALC "%f ", ($used_cov->{$id1}{$id2} // $parms{-3});
73                         } else {
74                                 printf MCCALC "%f ", ($used_cov->{$id1}{$id2} // 0.0);
75                         }
76                 }
77                 printf MCCALC "\n";
78         }
79
80         for my $match (@matches) {
81                 printf MCCALC "%s %s %d %d\n", $match->[0], $match->[1], $match->[2], $match->[3];
82         }
83         close MCCALC;
84
85         POSIX::setlocale(&POSIX::LC_ALL, 'nb_NO.UTF-8');
86
87         my %prob = ();
88
89         open MCCALC, "$config::base_dir/mcwordfeud $trials < $tmpnam |"
90                 or die "mccalc: $!";
91         while (<MCCALC>) {
92                 chomp;
93                 my @x = split /\s+/;
94                 my $id = $x[0];
95                 my $player = sprintf "%s (%.0f ± %.0f)", $players{$id}, ($ratings{$id} // 500.0), ($ratings_stddev{$id} // $parms{-3});
96                 $prob{$player} = [ @x[1..$#x] ];
97         }
98         close MCCALC;
99         #unlink $tmpnam;
100
101         my $num_games = scalar keys %prob;
102         for my $i (1..$num_games) {
103                 print "        <th>$i.</th>\n";
104         }
105         print "        <th>NEDRYKK</th>\n" unless ($lowest_division);
106         print "      </tr>\n";
107
108         for my $player (sort { $a cmp $b } keys %prob) {
109                 print "      <tr>\n";
110                 print "        <th>$player</th>\n";
111
112                 for my $i (1..$num_games) {
113                         my $pn = $prob{$player}->[$i - 1] / $trials;
114
115                         my $r = color(1.0 - $pn / 3);
116                         my $g = color(1.0 - $pn / 3);
117                         my $b = color(1.0);
118
119                         if ($i == 1) {
120                                 ($g, $b) = ($b, $g);
121                         } elsif ($i >= $num_games - 1 && !$lowest_division) {
122                                 ($r, $b) = ($b, $r);
123                         }
124
125                         printf "        <td style=\"background-color: rgb($r, $g, $b)\" class=\"num\">%.1f%%</td>\n", $pn * 100.0;
126                 }
127
128                 unless ($lowest_division) {
129                         my $pn = ($prob{$player}->[$num_games - 1] + $prob{$player}->[$num_games - 2]) / $trials;
130
131                         my $r = color(1.0);
132                         my $g = color(1.0 - $pn / 3);
133                         my $b = color(1.0 - $pn / 3);
134                         printf "        <td style=\"background-color: rgb($r, $g, $b)\" class=\"num\">%.1f%%</td>\n", $pn * 100.0;
135                 }
136                 print "      </tr>\n";
137         }
138
139         print << "EOF";
140     </table>
141 EOF
142 }
143
144 # Get auxillary parameters
145 my $q = $dbh->prepare('SELECT * FROM ratings WHERE id < 0');
146 $q->execute;
147
148 while (my $ref = $q->fetchrow_hashref) {
149         $parms{$ref->{'id'}} = $ref->{'rating'};
150 }
151 $match_stddev = $parms{-2} * sqrt(2.0);
152
153 my $season;
154 my $division = $cgi->param('divisjon') // -1;
155 my $subdivision = $cgi->param('avdeling') // -1;
156 my $last_division = 0;
157
158 POSIX::setlocale(&POSIX::LC_ALL, 'nb_NO.UTF-8');
159
160 print $cgi->header(-type=>'text/html; charset=utf-8', -expires=>'now');
161 printf <<"EOF", $match_stddev;
162 <?xml version="1.0" encoding="UTF-8" ?>
163 <!DOCTYPE
164   html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
165   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
166 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="no">
167   <head>
168     <title>WLoH-plasseringsannsynlighetsberegning</title>
169     <link rel="stylesheet" href="/style" type="text/css" />
170   </head>
171   <body>
172     <h1>WLoH-plasseringsannsynlighetsberegning</h1>
173
174     <p><em>Dette er et hobbyprosjekt fra tredjepart, og ikke en offisiell del av
175       <a href="http://wordfeud.aasmul.net/">Wordfeud Leage of Honour</a>.</em></p>
176
177     <p>Beregningen tar ikke hensyn til ujevn spillestyrke, ting som er sagt i forumet e.l.;
178       den antar at samtlige uspilte kamper trekkes fra en normalfordeling med standardavvik
179       %.1f poeng. Sannsynlighetene kan summere til andre tall enn 100%% pga. avrunding.
180       Tallene vil variere litt fra gang til gang fordi utregningen skjer ved randomisering.</p>
181
182     <p>Spillerne er sortert etter nick.</p>
183
184     <form method="get" action="/">
185 EOF
186
187 $q = $dbh->prepare('SELECT MAX(sesong) AS max_sesong FROM fotballserier');
188 $q->execute;
189 my $season;
190 while (my $ref = $q->fetchrow_hashref) {
191         $season = $ref->{'max_sesong'};
192 }
193
194 print <<"EOF";
195      <p>Divisjon:
196         <select name="divisjon" onchange="form.submit();">
197 EOF
198
199 $q = $dbh->prepare('SELECT DISTINCT(divisjon) FROM fotballserier WHERE sesong=? ORDER BY divisjon');
200 $q->execute($season);
201
202 my $found_division = 0;
203 my $max_division;
204
205 while (my $ref = $q->fetchrow_hashref) {
206         my $d = $ref->{'divisjon'};
207         if ($d == $division) {
208                 print "        <option value=\"$d\" selected=\"selected\">$d</option>\n";
209                 $found_division = 1;
210         } else {
211                 print "        <option value=\"$d\">$d</option>\n";
212         }
213         $max_division = $d;
214 }
215
216 $division = 1 if (!$found_division);
217
218 print <<"EOF";
219         </select>
220         Avdeling:
221         <select name="avdeling" onchange="form.submit();">
222 EOF
223
224 $q = $dbh->prepare('SELECT DISTINCT(avdeling) FROM fotballserier WHERE sesong=? AND divisjon=? ORDER BY avdeling');
225 $q->execute($season, $division);
226
227 my $found_subdivision = 0;
228
229 while (my $ref = $q->fetchrow_hashref) {
230         my $sd = $ref->{'avdeling'};
231         if ($sd == $subdivision) {
232                 print "        <option value=\"$sd\" selected=\"selected\">$sd</option>\n";
233                 $found_subdivision = 1;
234         } else {
235                 print "        <option value=\"$sd\">$sd</option>\n";
236         }
237 }
238
239 $subdivision = 1 if (!$found_subdivision);
240
241 print <<"EOF";
242         </select>
243         <input type="submit" value="Vis" />
244       </p>
245     </form>
246 EOF
247
248 # Get players and ratings
249 my $sum_rating = 0.0;
250
251 $q = $dbh->prepare('SELECT fotballdeltagere.id,fotballdeltagere.navn,rating,rating_stddev FROM fotballdeltagere JOIN fotballserier ON fotballdeltagere.serie=fotballserier.nr LEFT JOIN ratings ON fotballdeltagere.id=ratings.id WHERE sesong=? AND divisjon=? AND avdeling=?');
252 $q->execute($season, $division, $subdivision);
253
254 while (my $ref = $q->fetchrow_hashref) {
255         my $id = $ref->{'id'};
256         $players{$id} = sanitize(Encode::decode_utf8($ref->{'navn'}));
257         $ratings{$id} = $ref->{'rating'};
258         $ratings_stddev{$id} = $ref->{'rating_stddev'};
259         $sum_rating += $ref->{'rating'};
260 }
261 $q->finish;
262
263 $q = $dbh->prepare('
264 SELECT
265   d1.id AS p1, d2.id AS p2, maalfor AS score1, maalmot AS score2
266 FROM fotballresultater r
267   JOIN fotballserier s ON r.serie=s.nr
268   JOIN fotballdeltagere d1 ON r.lagrecno=d1.nr AND r.serie=d1.serie
269   JOIN fotballdeltagere d2 ON r.motstander=d2.nr AND r.serie=d2.serie
270 WHERE
271   sesong=? AND divisjon=? AND avdeling=?
272   AND lagrecno > motstander
273 ');
274 $q->execute($season, $division, $subdivision);
275
276 while (my $ref = $q->fetchrow_hashref) {
277         push @matches, [ $ref->{'p1'}, $ref->{'p2'}, $ref->{'score1'}, $ref->{'score2'} ];
278 }
279 $q->finish;
280
281 # Pick up covariance matrix
282 my $player_sql = '{' . join(',', keys %players ) . '}';
283 my $q = $dbh->prepare('SELECT * FROM covariance WHERE player1=ANY(?::smallint[]) AND player2=ANY(?::smallint[])', { pg_prepare_now => 0 });
284 $q->execute($player_sql, $player_sql);
285
286 my $cov = {};
287 while (my $ref = $q->fetchrow_hashref) {
288         $cov->{$ref->{'player1'}}{$ref->{'player2'}} = $ref->{'cov'};
289 }
290
291 my $lowest_division = ($division == $max_division);
292 make_table($lowest_division, {}, {});
293
294 print <<"EOF";
295     <p>Under er en variant som tar relativ spillestyrke med i beregningen;
296       se <a href="/rating">ratingsiden</a>.</p>
297 EOF
298
299 make_table($lowest_division, \%ratings, $cov);
300
301 my $avg_rating = $sum_rating / scalar keys %players;
302 printf "    <p>Gjennomsnittlig rating i denne avdelingen er <strong>%.1f</strong>.</p>\n", $avg_rating;
303
304 wloh_common::output_last_sync($dbh);
305
306 print <<"EOF";
307   </body>
308 </html>
309 EOF