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