# Ratings Calculation # Barry Harridge 3/04/2004 # Revised 12/01/2007 # # Version 2 on uses provisional ratings # and uses the chess way of calculating first rating # Version 2.3 introduced checking ambiguous names like Joy Smith (a real pain!) # Version 2.31 spits out the extra report # Version 2.33 tries to archive files # Version 10.04 uses year.month version numbering # Version 10.04 radical changes, no bonus nor feedback, aggregated provisional # Version 11.05 copes with player moving between sections # NICKstateNAME 06moonah.tou # AFIS VIC Andrew Fisher 462 1880 20060910 # EOKU NSW Edward Okulicz 1503 1836 20060910 # CMAY NSW Chris May 841 1830 20060910 # NFER VIC Naween Fernando 661 1830 20060910 # Usage: perl bazrat.pl 06LANG.TOU VIC 20060521 # ^ ^ ^ # TOU filename ------+ | | # State (VIC/NSW/etc) ---------+ | # DATE YYYYMMDD ---------------------+ # But if you omit parameters or give bad parameters you will be asked what to do # Input files : RATING.DAT assumed, 06LANG.TOU # Output files : 06LANG.RT2 (can be used as RATING.DAT for next time) # 06LANG.STA (stats file plus explanations) # 06LANG.WHY (explanations) # CHECK.CSV #Version 10.10 removes the archiving parts again $progname = 'PLinrat'; $probrule = 'linear'; $write_to_CSV = FALSE; #@x = localtime(time); # array has (0: $sec, 1: $min, 2: $hour, 3: $mday, 4: $mon, 5: $year, 6: $wday, 7: $yday, 8: $isdst) $version = '11.05'; # May 2011 # ( $toufile, $toustate, $toudate ) = @ARGV; $toustate = uc($toustate); # optional parameters eg -i 06janb.RT2 or -i latest @ARGV = (); # basic parameters $scale = 1200; # reciprocal of slope $proviso = 50; # ratings are provisional if less than x games at the END of the tourney $maxtries = 400; # Will iterate this many times at most $floor = 500; # established ratings will not go below this $currency = 1; # rankings amongst players active in the last year $archiveplace = '/tournaments'; print "$progname version $version ($probrule)\n"; if ( defined($toufile) && ( $toufile =~ /help/i ) ) { print <) { chomp; $line = $_; $line =~ s/\#.+$//; # get rid of comments foreach $st ( split( ' ', $line ) ) { if ( length($st) < 4 ) { push( @states, uc($st) ) } } } close(STATESFILE); } else { @states = (qw/NSW ACT VIC SA QLD TAS WA OS/); } # Forgive leaving off .tou $toufile .= '.tou' if defined($toufile) && index( $toufile, '.' ) < 0; # Present a list of recent TOU files if necessary if ( not defined($toufile) or not -e $toufile ) { opendir( DIR, '.' ); @files = grep( /\.TOU$/i, readdir(DIR) ); # v 2.17 has the dot closedir(DIR); foreach $toufile (@files) { if ( -f $toufile ) { # a file not a directory @bits = split( '\.', $toufile ); $toustub = $bits[0]; $done{$toufile} = -e "$toustub.RT2" ? 'DONE' : 'NO '; @filedata = stat($toufile); $fdate{$toufile} = $filedata[9]; # reinstated v 2.21 as extra field #print "$toufile $fdate{$toufile}\n"; open( TOUFILE, "$toufile" ) or die "Cannot open $toufile\n"; $_ = ; chomp; $line = $_; #*M15.01.2006 Baulkham Hills Kick-Off $title = substr( $line, 2, length($line) - 2 ); if ( $title =~ /(\d{1,2})[\/\.](\d{1,2})[\/\.](\d*)\s*(.*)$/ ) { $tyear = $3 < 100 ? 2000 + $3 : $3; $tdate{$toufile} = sprintf( "%04d%02d%02d", $tyear, $2, $1 ); $tevent{$toufile} = $4; } else { $tdate{$toufile} = '00000000'; $tevent{$toufile} = $title; } close(TOUFILE); } # if -f $toufile } @files = sort { $tdate{$b} == $tdate{$a} ? $fdate{$b} <=> $fdate{$b} : $tdate{$b} <=> $tdate{$a} } @files; @files = @files[ 0 .. 9 ] if @files > 10; if ( @files > 1 ) { print "Recent TOU files\n\n"; printf " %-20s Done? %-35s %s\n", 'TOU file', 'event', 'date'; printf "===========================================================================\n"; for ( $i = 1 ; $i <= @files ; $i++ ) { printf "%2d %-20s %5s %-35s %-8s\n", $i, $files[ $i - 1 ], $done{ $files[ $i - 1 ] }, $tevent{ $files[ $i - 1 ] }, $tdate{ $files[ $i - 1 ] }; } } elsif ( @files == 0 ) { die "Cannot find any TOU files\n" } else { print "Only one TOU file found, so "; } if ( @files > 1 ) { $gask = @files - 1; while ( ( $gask > 1 ) and ( $done{ $files[$gask] } eq '*' ) ) { $gask--; } $gask++; do { print "Which file [$gask] "; $_ = <>; chomp; $ask = $_; $ask = $gask if $ask eq ''; } until ( $ask <= @files ); } $toufile = $files[ $ask - 1 ]; print "using $toufile\n"; } # open TOU file, at this stage just for the date open( TOUFILE, "$toufile" ) or die "Cannot open $toufile\n"; $_ = ; chomp; $line = $_; #*M15.01.2006 Baulkham Hills Kick-Off $title = substr( $line, 2, length($line) - 2 ); if ( $title =~ /(\d{1,2})[\/\.](\d{1,2})[\/\.](\d*)/ ) { $tyear = $3 < 100 ? 2000 + $3 : $3; $titledate = sprintf( "%04d%02d%02d", $tyear, $2, $1 ); } print "$toufile has title $title\n"; close(TOUFILE); while ( not defined($toustate) or ( grep( $toustate eq $_, @states ) == 0 ) ) { do { print "Which state? One of @states : "; $_ = <>; chomp; $toustate = uc($_); } until ( grep( $toustate eq $_, @states ) > 0 ); } if ( not defined($toudate) or ( $toudate < 19910000 ) or ( $toudate > 21000000 ) ) { print "Give the tournament date "; if ( defined($titledate) ) { print "(default is $titledate) : "; } else { $titledate = '' } $_ = <>; chomp; $toudate = $_; $toudate = $titledate if $toudate eq ''; } @bits = split( '\.', $toufile ); $toustub = $bits[0]; opendir( DIR, '.' ); @files = grep( /\.RT2$/i, readdir(DIR) ); closedir(DIR); if ( -e 'RATING.DAT' ) { unshift( @files, 'RATING.DAT' ); } foreach $file (@files) { #@filedata = stat($file); $fdate{$file}=$filedata[9]} open( RATFILE, $file ); $header = ; # header @bits = split( ' ', $header ); $event{$file} = $bits[1]; $lastdate{$file} = $bits[-1]; $lastdate{$file} = '00000000' if $lastdate{$file} !~ /\d{8}$/; close RATFILE; } #@files = grep($toudate >= $lastdate{$_},@files); @files = grep( $_ !~ /$toustub/i, @files ); @files = sort { $lastdate{$b} <=> $lastdate{$a} } @files; @files = @files[ 0 .. 8 ] if @files > 9; if ( @files > 1 ) { print "Files with prior ratings\n\n"; printf " %-20s %-14s %s\n", 'Ratings file', 'last event', 'last date'; printf "=================================================\n"; for ( $i = 0 ; $i <= $#files ; $i++ ) { printf "%1d %-20s %-14s %-8s", $i + 1, $files[$i], $event{ $files[$i] }, $lastdate{ $files[$i] }; if ( $toudate ge $lastdate{ $files[$i] } ) { print "\n"; } else { print " AFTER!\n" } } } elsif ( @files == 0 ) { die "Cannot find any ratings data files\n" } $ask = 1; if ( @files > 1 ) { do { print "Which file [1] "; $_ = <>; chomp; $ask = $_; $ask = 1 if $ask eq ''; } until ( $ask <= @files ); } $ratfile = $files[ $ask - 1 ]; #print "Prior data from $ratfile after $event{$ratfile} on date $lastdate{$ratfile}\n"; $event{$ratfile} =~ s/\..+//; print "Prior data from $ratfile after $event{$ratfile} on date $lastdate{$ratfile}\n"; open( RATFILE, $ratfile ) or die "Cannot find $ratfile"; # newly introduced last item, last tou date # NICKstateNAME 06sydim.tou 20060129 # AFIS VIC Andrew Fisher 315 1994 20060129 # NFER VIC Naween Fernando 484 1969 20060129 $header = ; # header ( $_, $_, $lastdate ) = split( ' ', $header ); if ( $toudate <= $lastdate ) { print "Tournament date is $toudate "; print $toudate < $lastdate ? 'before' : 'same day as '; print "$lastdate from $ratfile\n"; print "Is this okay? "; $ask = <>; chomp $ask; exit if ( uc($ask) ne 'Y' ); } @oldlist = (); # April 2010 , now look for extra data about provisional players while () { chomp; $line = $_; $name = substr( $line, 9, 20 ); # eg Naween Fernando $name =~ s/\s+$//; #$name = &ucbits($name); # eg John Van Der Schoor $realname{ &squished($name) } = $name; # eg JOHNVANDERSCHOOR, LAREINELANG $name = &squished($name); $statemark = substr( $line, 5, 3 ); # eg VIC $statemark =~ s/\s+$//; # trim leading spaces $qname = $name . '^' . $statemark; $rat0{$qname} = substr( $line, 35, 4 ); # eg 1853 $newrat{$qname} = $rat0{$qname} + 0; $ratedgames{$qname} = substr( $line, 30, 4 ); # eg 595 $ratedgames{$qname} =~ s/^\s+//; $lastplayed{$qname} = substr( $line, 40, 8 ); $state{$name} = $statemark; # now look for data for provisional players # RGRO OS Ruth Groffman 49 1242 20100403 P 20 1145.0 if ( $line =~ / P (\d+) (\d+\.\d+)/ ) { $pastwins = $1; $pastsumopr = $2 * $ratedgames{$qname}; printf "%-20s is provisional winning %d/%d games against mean ratings of %s\n", $realname{$name}, $pastwins, $ratedgames{$qname}, $2; $pastperformance{$qname} = $pastsumopr{$qname} + $scale * $pastwins{$qname} - 0.5 * $scale * $ratedgames{$qname}; } elsif ( $ratedgames{$qname} < $proviso ) { # if they cross over it won't be used. No harm. $pastperformance{$qname} = $ratedgames{$qname} * $rat0{$qname}; } $games{$qname} = 0; push( @oldlist, $qname ) # all of them (no time filter yet) } close RATFILE; $lastdate = '19900101' if $lastdate eq ''; #Stage 1 - get ratings of all players # NICKstateNAME 06sydim1.tou # NFER VIC Naween Fernando 595 1853 20060128 # EOKU NSW Edward Okulicz 1346 1797 20060128 # AFIS VIC Andrew Fisher 397 1903 20060128 # DELD VIC David Eldar 413 1858 20060128 # Stage 2 Check for unrated players @newby = (); &checkrat; @qnewby = (); if ( scalar(@newby) > 0 ) { print "Tournament file has unknown players:\n"; print "Be careful they aren't misspellings or players who haven't played for a while.\n"; print "If in doubt, abort this program and fix using AUPAIR with spellcheck.\n"; print "For each unrated player type their state\n"; print "ENTER = default state.\n\n"; foreach $qname (@newby) { ( $name, $st ) = split( '\^', $qname ); print "$touname{$name} "; } print "\n\n"; foreach $name (@newby) { do { printf "%-20s (%s) :", $touname{$name}, $toustate; $_ = <>; chomp; $st = uc($_); $st = $toustate if $st eq ''; $qname = $name . '^' . $st; $state{$name} = $st; push( @qnewby, $qname ); } until ( grep( $st eq $_, @states ) > 0 ); $lastplayed{$qname} = $toudate; $ratedgames{$qname} = 0; $oldrank{$qname} = 0; $oldsrank{$qname} = 0; $rat0{$qname} = 0; $pastperformance{$qname} = 0; } } # Stage 3 $badboys = 0; @who = (); &parsetou; if ( $badboys > 0 ) { print "Forfeits:\n"; foreach $name ( keys %touname ) { print "$touname{$name} in game $forfeits{$name}\n" if defined( $forfeits{$name} ); } print "\n"; } $badspells = 0; foreach $name ( keys %touname ) { if ( $realname{$name} ne $touname{$name} ) { printf "%-20s in RATING.DAT but %-20s in $toufile.\n", $realname{$name}, $touname{$name}; $badspells++; } } if ( $badspells > 0 ) { print "Ratings okay for the above, but please fix $toufile.\n\n"; } @filterlist = (); foreach $qname (@oldlist) { ( $name, $st ) = split( '\^', $qname ); if ( ( $games{$qname} > 0 ) or ( $toudate - $lastplayed{$qname} < $currency * 10000 ) ) { push( @filterlist, $qname ); } } @filterlist = sort { $rat0{$a} == $rat0{$b} ? $ratedgames{$b} <=> $ratedgames{$a} : $rat0{$b} <=> $rat0{$a} } @filterlist; #@filterlist = sort { $rat0{$b} <=> $rat0{$a} } @filterlist ; foreach $state (@states) { $counter{$state} = 0; } $counter = 0; for ( $i = 0 ; $i < scalar(@filterlist) ; $i++ ) { $qname = $filterlist[$i]; ( $name, $st ) = split( '\^', $qname ); if ( $st ne 'OS' ) { $counter++; $oldrank{$qname} = $counter } else { $oldrank{$qname} = 0 } $counter{$st}++; $oldsrank{$qname} = $counter{$st}; # debug # if ($counter<61) { # printf "%-25s r0=%4d N=%4d",$qname,$rat0{$qname},$oldrank{$qname}; # printf " S= %4d",$oldsrank{$qname} if $st eq $toustate; # if ($games{$qname}>0) { print " !" } # print "\n"; # if ($counter % 20 ==0) { <>} # } } if ($write_to_CSV) { open( CSVFILE, '>check.csv' ); print "Write spreadsheet file check.csv\n"; } # new in plin, calculate novices and provisional first for ( $secnum = 1 ; $secnum <= $sections ; $secnum++ ) { if ($write_to_CSV) { print CSVFILE "$secname[$secnum]\n"; print CSVFILE "State,Name,Prior,Games,"; print CSVFILE "Opp," x $maxgames[$secnum]; print CSVFILE "Wins,Final\n"; } $totalupfloor[$secnum] = 0; &calcprov($secnum); &calcearned($secnum); } close CSVFILE if $write_to_CSV; #@newlist = (@oldlist,@qnewby,@oldy); @newlist = ( @filterlist, @qnewby ); # #@newlist = sort { $newrat{$b} <=> $newrat{$a} } @newlist; @newlist = sort { $newrat{$a} == $newrat{$b} ? $ratedgames{$b} <=> $ratedgames{$a} : $newrat{$b} <=> $newrat{$a} } @newlist; foreach $state (@states) { $counter{$state} = 0; } $counter = 0; for ( $i = 0 ; $i < scalar(@newlist) ; $i++ ) { $qname = $newlist[$i]; ( $name, $st ) = split( '\^', $qname ); if ( $st ne 'OS' ) { $counter++; $newrank{$qname} = $counter } else { $newrank{$qname} = 0 } $counter{$st}++; $newsrank{$qname} = $counter{$st}; # debug # if ($counter<61) { # printf "%-25s r =%4d N=%4d",$qname,$newrat{$qname},$newrank{$qname}; # printf " S= %4d",$newsrank{$qname} if $st eq $toustate; # if ($games{$qname}>0) { print " !" } # print "\n"; # if ($counter % 20 ==0) { <>} # } } # Stage 5 : Write the STA file print "Write STA file $toustub.STA\n"; $rule = "+--------------------------+-----------+---------+-----------+-----------------+\n"; open( STAFILE, ">$toustub.STA" ); print STAFILE < 0 ) and ( $newtotgames{$qname} < $proviso ) ) { $marker = '|&'; $n_provs++; } else { $marker = '| ' } # print STAFILE ($ratedgames{$name1}==0) ? '|*' : '| '; printf STAFILE "%2s%-3s %-20s |", $marker, $st, $realname{$name}; if ( $oldrank{$qname} == 0 ) { printf STAFILE " " } else { printf STAFILE "%5d", $oldrank{$qname} } if ( $newrank{$qname} == 0 ) { printf STAFILE " |" } else { printf STAFILE "%5d |", $newrank{$qname} } ; # national ranks if ( $oldsrank{$qname} == 0 ) { printf STAFILE " " } else { printf STAFILE "%4d", $oldsrank{$qname} } if ( $newsrank{$qname} == 0 ) { printf STAFILE " |" } else { printf STAFILE "%4d |", $newsrank{$qname} } ; # state ranks # expected games if ( $newtotgames{$qname} < $proviso ) { print STAFILE " " } elsif ( $ratedgames{$qname} == 0 ) { print STAFILE " " } else { printf STAFILE "%5.1f", $expected{$qname}; } printf STAFILE "%5.1f |", $ratedgames{$qname} == 0 && $wins{$qname} < 1 ? 0 : $wins{$qname}; # prior rating if ( $ratedgames{$qname} == 0 ) { print STAFILE " "; } elsif ( $ratedgames{$qname} < $proviso ) { printf STAFILE "(%4d)", $rat0{$qname}; } else { printf STAFILE " %4d ", $rat0{$qname}; } if ( $newtotgames{$qname} < $proviso ) { print STAFILE " "; } elsif ( $ratedgames{$qname} == 0 ) { print STAFILE " "; } else { printf STAFILE " %+4d", $newrat{$qname} - $rat0{$qname}; } if ( $newtotgames{$qname} < $proviso ) { printf STAFILE "(%4d)|\n", $newrat{$qname}; } else { printf STAFILE " %4d |\n", $newrat{$qname}; } } } # if not a bye print STAFILE "$rule"; } foreach $qname ( sort keys %section ) { if ( $section{$qname} =~ /\+/ ) { ( $name, $st ) = split( '\^', $qname ); print STAFILE "$realname{$name} played in $section{$qname}\n"; } } #printf STAFILE "Games in this tourney = %5d\n",$maxgames; print STAFILE "\n\nNotes:\nTournament placings by wins and "; print STAFILE "aggregate.\n" if $rankmethod eq 'A'; print STAFILE "margins.\n\n" if $rankmethod eq 'M'; print STAFILE "* denotes a previously unrated player\n" if $n_novices > 0; print STAFILE "x denotes a player who forfeited\n" if $n_forfeits > 0; print STAFILE "& denotes a provisional player\n" if $n_provs > 0; for ( $secnum = 1 ; $secnum <= $sections ; $secnum++ ) { print STAFILE "The floor of $floor was imposed in section $secname[$secnum]\n" if $totalupfloor[$secnum] > 0; } # Print warnings about players who played many unrated players for ( $secnum = 1 ; $secnum <= $sections ; $secnum++ ) { foreach $player ( sort byplacing ( 1 .. $secsize[$secnum] ) ) { $qname = $who[$secnum][$player]; if ( $qname !~ /^. Bye\^/ ) { ( $name, $st ) = split( '\^', $qname ); # print "WARNING $name played $rawopps{$qname}/$games{$qname} unrated players.\n" if $$newrat{$qname1} < $floor;{$qname} >0; if ( $rawopps{$qname} > $games{$qname} * 0.5 ) { print STAFILE "$touname{$name} ($rat0{$qname}) played $rawopps{$qname}/$games{$qname} unrated players.\n"; } } } } close STAFILE; print "Write analysis file $toustub.WHY\n"; # Now write another file with more detailed explanations open( WHYFILE, ">$toustub.WHY" ); print WHYFILE "Explanations of $title generated by $progname $version ($probrule probability rule)\n\n"; $explain_p = 0; for ( $secnum = 1 ; $secnum <= $sections ; $secnum++ ) { printf WHYFILE "|%s\n", $secname[$secnum]; printf WHYFILE "|%4s %5s | %-3s %-20s | %2s |", 'Wins', $rankmethod, '', '', 'G'; printf WHYFILE "%7s%5s |", 'Exp', 'Act'; printf WHYFILE " Old Chg New \n"; foreach $player ( sort byplacing ( 1 .. $secsize[$secnum] ) ) { $qname = $who[$secnum][$player]; ( $name, $st ) = split( '\^', $qname ); if ( $name !~ /^. Bye$/ ) { printf WHYFILE "|%4.1f %+5d ", $wins{$qname}, $totscore{$qname}; printf WHYFILE "| %3s", $st; print WHYFILE ( $ratedgames{$qname} == 0 ) ? '*' : $newtotgames{$qname} < $proviso ? '&' : ' '; printf WHYFILE "%-20s | %2d |", $realname{$name}, $games{$qname}; if ( defined( $earned{$qname} ) ) { printf WHYFILE "%7.3f%5.1f |", $expected{$qname}, $wins{$qname}; } else { printf WHYFILE "%7s%5.1f |", '', $wins{$qname}; } if ( $ratedgames{$qname} > 0 ) { printf WHYFILE " %4d ", $rat0{$qname}; } else { printf WHYFILE " " } if ( defined( $earned{$qname} ) ) { printf WHYFILE " %+6.1f", $earned{$qname}; } else { printf WHYFILE " %6s", ''; } printf WHYFILE " %4d ", $newrat{$qname}; if ( $ratedgames{$qname} == 0 ) { printf WHYFILE " Novice", $ratedgames{$qname}, $newtotgames{$qname}; } elsif ( $newtotgames{$qname} < $proviso ) { printf WHYFILE " Prov (g=%d -> %d)", $ratedgames{$qname}, $newtotgames{$qname}; } elsif ( $ratedgames{$qname} < $proviso ) { printf WHYFILE "Xprov (g=%d -> %d)", $ratedgames{$qname}, $newtotgames{$qname}; } printf WHYFILE " %d", $lastplayed{$qname} if $toudate - $lastplayed{$qname} >= $currency * 10000; print WHYFILE "\n"; if ( defined( $explain{$qname} ) ) { $x = eval("( $explain{$qname} ) / $newtotgames{$qname}"); printf WHYFILE "Prov rating %g = ( $explain{$qname} ) / $newtotgames{$qname} \n", $x; if ( $x < $floor ) { $fudge = $games{$qname} * ( $floor - $x ) / $scale; printf WHYFILE "Invoke $floor floor, real wins = %g but purported wins = %5.2f\n", $wins{$qname}, $wins{$qname} + $fudge; } } unless ( $toogood{$qname} eq '' ) { print WHYFILE "HARD GAMES $toogood{$qname}\n"; } unless ( $toobad{$qname} eq '' ) { print WHYFILE "EASY GAMES $toobad{$qname}\n"; } } # if !~ /Bye$/ } # foreach $player print WHYFILE "\n"; printf WHYFILE "%s\n", $secname[$secnum]; print WHYFILE "Total rating change: "; printf WHYFILE "established = %+5.1f", $totalearned[$secnum]; if ( defined( $totalupdated[$secnum] ) ) { $explain_p = 1; printf WHYFILE " provisional = %+5.1f", $totalupdated[$secnum]; } printf WHYFILE " total upfloor = %5.1f", $totalupfloor[$secnum] if $totalupfloor[$secnum] > 0; print WHYFILE "\n\n"; } foreach $qname ( sort keys %section ) { if ( $section{$qname} =~ /\+/ ) { ( $name, $st ) = split( '\^', $qname ); print WHYFILE "$realname{$name} played in $section{$qname}\n"; } } if ($explain_p) { print WHYFILE "Provisional ratings are updated using\n"; # Prov rating 556.3 = ( 7 * 685 + 977+ 786+ 703+ 977+ 786+ 703+1061 + 1200 * 1 - 600 * 7 ) / 14 $halfs = $scale / 2; print WHYFILE "new_rating = (old_ngames * old_rating + new_data )/ new_ngames\n"; print WHYFILE "where new_data = sum_opprats_thistourney + $scale * wins_this_tourney - $halfs * games_played_this_tourney\n"; print WHYFILE "This effectively treats all tournaments to date as one big tournament\n"; print WHYFILE "but preserves any advantage (eg upflooring) used in earlier tournaments.\n"; } close WHYFILE; # Stage 4 : Create new RATING.DAT file # NICKstateNAME 060107ja.tou # EOKU NSW Edward Okulicz 1321 1853 20051203 # NFER VIC Naween Fernando 564 1841 20051228 # DELD VIC David Eldar 389 1836 20051228 #Newlist must be redone need to put back those set aside earlier $report = $toufile; # eg blah.tou; $report =~ s/\..+$/\.txt/; print "Write report $report with placings and rating changes.\n"; open( NICEFILE, ">$report" ) or die "Cannot write to $report\n"; print NICEFILE "Results and ratings: $title\n"; print NICEFILE "following $event{$ratfile} on $lastdate{$ratfile}\n"; for ( $secnum = 1 ; $secnum <= $sections ; $secnum++ ) { if ( $converged[$secnum] ) { print NICEFILE "$secname[$secnum]\n"; printf NICEFILE "\n%8d games\n", $maxgames[$secnum]; $gcap = $maxgames[$secnum]; printf NICEFILE "%3s %4s %5s", '', 'W', $rankmethod; printf NICEFILE "%-22s %s\n", '', 'Old Chg New'; $rank = 0; foreach $player ( sort byplacing ( 1 .. $secsize[$secnum] ) ) { $qname = $who[$secnum][$player]; ( $name, $st ) = split( '\^', $qname ); if ( $name !~ /^. Bye$/ ) { $rank++; if ( $games{$qname} < $gcap ) { printf NICEFILE "\n%8d games\n", $games{$qname}; $gcap = $games{$qname}; $rank = 1; } printf NICEFILE "%3d %4g %+5d", $rank, $wins{$qname}, $totscore{$qname}; print NICEFILE $ratedgames{$qname} == 0 ? '*' : $newtotgames{$qname} < $proviso ? '&' : ' '; printf NICEFILE "%-20s", $realname{$name}; if ( $ratedgames{$qname} == 0 ) { print NICEFILE " ", ''; $p_tell{$qname} = "$realname{$name} was previously unrated"; } elsif ( $ratedgames{$qname} < $proviso ) { printf NICEFILE "(%4d)%4s", $rat0{$qname}, ''; $p_tell{$qname} = "$realname{$name} was previously provisional"; } else { printf NICEFILE " %4d %+4d", $rat0{$qname}, $newrat{$qname} - $rat0{$qname}; } if ( $newtotgames{$qname} < $proviso ) { printf NICEFILE "(%4d)\n", $newrat{$qname}; if ( defined( $p_tell{$qname} ) ) { if ( $p_tell{$qname} =~ /provisional/ ) { $p_tell{$qname} = "$realname{$name} is still provisional ($newtotgames{$qname} games)"; } } } else { printf NICEFILE " %4d\n", $newrat{$qname}; if ( defined( $p_tell{$qname} ) ) { $p_tell{$qname} = "$realname{$name} is no longer provisional ($newtotgames{$qname} games)"; } } } # if !~ /Bye$/ } # foreach $player printf NICEFILE "\nHigh game: %s %s \n", $whohighgame[$secnum], $highgame[$secnum] % 1000; print NICEFILE "High word: $highword[$secnum]\n" unless $highword[$secnum] =~ /\s+0\s+/; print NICEFILE "\n\n"; } else { print NICEFILE "$secname[$secnum] could not be rated\n"; } } #Explain about player who played in more than one section foreach $qname ( sort keys %section ) { if ( $section{$qname} =~ /\+/ ) { ( $name, $st ) = split( '\^', $qname ); print NICEFILE "$realname{$name} played in $section{$qname}\n"; } } #Explain about novices and provisional @x = keys %p_tell; if ( @x > 0 ) { foreach $qname ( sort { $newtotgames{$a} <=> $newtotgames{$b} } @x ) { print NICEFILE "$p_tell{$qname}\n"; } } close NICEFILE; print "Writing updated RATING data file $toustub.RT2\n"; @newlist = sort { $newrat{$a} == $newrat{$b} ? $ratedgames{$b} <=> $ratedgames{$a} : $newrat{$b} <=> $newrat{$a} } ( @oldlist, @qnewby ); open( NEWRATFILE, ">$toustub" . ".RT2" ); printf NEWRATFILE "%-32s%s %s\n", 'NICKstateNAME', lc($toufile), $toudate; for ( $i = 0 ; $i < scalar(@newlist) ; $i++ ) { $qname = $newlist[$i]; # This should not happen I hope if ( $qname eq $newlist[ $i - 1 ] ) { print "Duplicated entry for $qname!\n"; <>; } ( $name, $st ) = split( '\^', $qname ); printf NEWRATFILE "%4s %-4s%-20s", &nickname( $realname{$name} ), $st, $realname{$name}; #print "$name $games{$qname}"; <>; if ( $games{$qname} > 0 ) { printf NEWRATFILE "%5d%5d%9d\n", $ratedgames{$qname} + $games{$qname}, $newrat{$qname}, $toudate; } else { printf NEWRATFILE "%5d%5d%9d\n", $ratedgames{$qname}, $rat0{$qname}, $lastplayed{$qname}; } } # for loop print "\n"; close(NEWRATFILE); sub byplacing { my $qname1 = $who[$secnum][$b]; my $qname2 = $who[$secnum][$a]; if ( $games{$qname1} != $games{$qname2} ) { $games{$qname1} <=> $games{$qname2}; } elsif ( $wins{$qname1} != $wins{$qname2} ) { $wins{$qname1} <=> $wins{$qname2}; } else { $totscore{$qname1} <=> $totscore{$qname2} } } sub checkrat { my ( $line, $name ); open( TOUFILE, "$toufile" ) or die; $_ = ; chomp; $line = $_; # #*M15.01.2006 Baulkham Hills Kick-Off # This part now irrelevant because we opened and closed file earlier to find the date # $title = substr($line,2,length($line)-2); # # if ($title =~ m/(\d{1,2})[\/\.](\d{1,2})[\/\.](\d*)/) { # $tyear=$3<100 ? 2000+$3 : $3; # $titledate = sprintf("%04d%02d%02d",$tyear,$2,$1); # } # # print "$toufile has title $title\n"; $rankmethod = substr( $line, 1, 1 ); # should be A or M while () { chomp; $line = $_; if ( substr( $line, 0, 1 ) eq '*' ) { last if $line eq '*** END OF FILE ***'; ; #highword; } else { $name = substr( $line, 0, 20 ); $name =~ s/@.+$//; # get rid of @R, @12 $name =~ s/\(.+$//; # get rid of (N) etc $name =~ s/\s+$//; # get rid of trailing spaces # Warning only, it can actually cope if a genuine shift. if (defined($touname{&squished($name)})) { print "$name in more than once!!\n"; } $touname{ &squished($name) } = $name; $name = &squished($name); #$qname=$name.'^'.$toustate; ## NO not right # now check for ambiguous names and unrated players @result = grep( $_ =~ /^$name\^[A-Z]*/, keys %rat0 ); $n = @result; if ( ( $n == 0 ) and ( $name !~ /^. Bye$/ ) ) { # n=0 no prior rating from RATING.DAT $realname{$name} = $touname{$name}; push( @newby, $name ) } elsif ( $n == 1 ) { # May be Edward Okulicz^NSW playing in Victoria! $qname = $result[0]; } } } # while TOUFILE close TOUFILE; } # sub checkrat # subroutine creates a number indexed array of players, opponents etc # naughty because it uses and creates global variables sub parsetou { my ( $player, $secnum, $line, $name ); open( TOUFILE, "$toufile" ) or die; $_ = ; chomp; $line = $_; # #*M15.01.2006 Baulkham Hills Kick-Off # This part now irrelevant because we opened and closed file earlier to find the date # $title = substr($line,2,length($line)-2); # # if ($title =~ m/(\d{1,2})[\/\.](\d{1,2})[\/\.](\d*)/) { # $tyear=$3<100 ? 2000+$3 : $3; # $titledate = sprintf("%04d%02d%02d",$tyear,$2,$1); # } # # print "$toufile has title $title\n"; $rankmethod = substr( $line, 1, 1 ); # should be A or M $player = 0; $secnum = 0; while () { chomp; $line = $_; if ( substr( $line, 0, 1 ) eq '*' ) { $secsize[$secnum] = $player; $whohighgame[$secnum] = join( ',', @smarties ) if $secnum > 0; last if $line eq '*** END OF FILE ***'; $secnum++; $secname[$secnum] = substr( $line, 1, 20 ); $secname[$secnum] =~ s/\s+$//; $maxgames[$secnum] = 0; # print "SECTION $secnum is $secname[$secnum]\n"; <>; # debug $highgame[$secnum] = 0; @smarties = (); $h = ; chomp; $h =~ s/\s+/ /g; $highword[$secnum] = $h; # print "SECTION $secnum is $secname[$secnum]\n"; <>; # debug $totalearned[$secnum] = 0; $player = 0; } else { $player++; $name = substr( $line, 0, 20 ); $name =~ s/@.+$//; # get rid of @R, @12 $name =~ s/\(.+$//; # get rid of (N) etc $name =~ s/\s+$//; # get rid of trailing spaces $touname{ &squished($name) } = $name; $name = &squished($name); if ( $name =~ /^. Bye$/ ) { # n=0 no prior rating from RATING.DAT $qname = $name . "^OS"; $rat0{$qname} = 0; $realname{$name} = $name; $games{$qname} = 0; $wins{$qname} = 0; $totscore{$qname} = 0; $who[$secnum][$player] = $qname; } else { # not a bye @result = grep( $_ =~ /^$name\^[A-Z]*/, keys %rat0 ); $n = @result; # number of players named eg Joy Smith in RATING.DAT if ( $n == 0 ) { # n=0 no prior rating from RATING.DAT print "ERROR: No prior rating for $name"; die; } elsif ( $n == 1 ) { # May be Edward Okulicz^NSW playing in Victoria! $qname = $result[0]; } elsif ( $n > 1 ) { # n>1 is ambiguous $ask = 'n'; while ( $ask ne 'Y' ) { print "Which of these $n is $name (type Y)?\n"; foreach $joy (@result) { ( $name, $st ) = split( '\^', $joy ); print "$touname{$name} from $st? "; $_ = <>; chomp; $ask = uc($_); $qname = $joy; last if $ask eq 'Y'; } } } # if $n>1 $who[$secnum][$player] = $qname; # New in 11.04 if ( defined( $section{$qname} ) ) { $section{$qname} .= "+$secname[$secnum]"; } else { $section{$qname} = $secname[$secnum] } undef( $forfeits{$name} ); $newrat{$qname} = $rat0{$qname} + 0; # print "$qname\n"; #debug substr( $line, 0, 20 ) = ' ' x 20; @data = split( ' ', $line ); $game = 0; while ( scalar(@data) ) { $score = shift(@data); $oppnum = shift(@data); if ( $score > 0 ) { # ver 2.19 ignore prospective games } $win = 0.5 * int( $score / 1000 ); # should be 0, 0.5 or 1 $game++; $maxgames[$secnum] = $game if $game > $maxgames[$secnum]; if ( $score > $highgame[$secnum] ) { $highgame[$secnum] = $score; @smarties = ( $realname{$name} ); } elsif ( $score == $highgame[$secnum] ) { push( @smarties, $realname{$name} ); } $score = $score % 1000; $hisscore[$secnum][$player][$game] = $score; $games{$qname} ++ unless $player == $oppnum; if ( $score == 002 ) { # code for forfeited game $forfeits{$name} .= ' ' . $game; $badboys++; $hisscore[$secnum][$player][$game] = 0; } $hisopp[$secnum][$player][$game] = $oppnum; $hiswin[$secnum][$player][$game] = $win; } $lastround[$secnum][$player] = $game; # $games{$qname} = $game; } } #if not A Bye } } # while TOUFILE close TOUFILE; $sections = $secnum; # Version 11.05 at last done here rather than in midst of ratings foreach $secnum (1..$sections) { foreach $player (1..$secsize[$secnum]) { $qname1 = $who[$secnum][$player]; $totscore{$qname1} = 0; foreach $game (1..$maxgames[$secnum]) { $oppnum=$hisopp[$secnum][$player][$game]; $totscore{$qname1} += $hisscore[$secnum][$player][$game]; if ( $rankmethod eq 'M' ) { $totscore{$qname1} -= $hisscore[$secnum][$oppnum][$game]; } } } } } # sub parsetou sub calcprov { # does provisional and novices my ($secnum) = @_; my @newbies = (); $converged[$secnum] = 0; $totalupdated[$secnum] = 0; for ( $player = 1 ; $player <= $secsize[$secnum] ; $player++ ) { $qname1 = $who[$secnum][$player]; if ( $qname1 !~ /^. Bye\^/ ) { if ( $games{$qname1} > 0 ) { if ( $ratedgames{$qname1} + $games{$qname1} < $proviso ) { # Edward's masterstroke decree push( @newbies, $player ); ( $name, $st ) = split( '\^', $qname1 ); $csvline{$qname1} = "$st,$realname{$name},$rat0{$qname1},$ratedgames{$qname1}"; $wins{$qname1} = 0; $g = 0; # $games{$qname1} = 0; $expected{$qname1} = 0; $rawopps{$qname1} = 0; for ( $game = 1 ; $game <= $lastround[$secnum][$player] ; $game++ ) { $rival = $hisopp[$secnum][$player][$game]; $qname2 = $who[$secnum][$rival]; if ( ( $rival == $player ) or $qname2 =~ /^. Bye\^/ ) { # print "$qname1 played $qname2\n"; } else { $g++; # $games{$qname1}++; $wins{$qname1} += $hiswin[$secnum][$player][$game]; $rawopps{$qname1}++ if $ratedgames{$qname2} == 0; } # if $rival!= $player } # for $game # $winbit{$qname1} = 600 * $numerator / $denominator; $foundation{$qname1} = $pastperformance{$qname1} + $scale * $wins{$qname1} - 0.5 * $scale * $g; $newtotgames{$qname1} = $ratedgames{$qname1} + $games{$qname1}; } # if provisional } # if $games{$qname1} >0 } # if not a bye } # for player if ( @newbies > 0 ) { foreach $player (@newbies) { $qname1 = $who[$secnum][$player]; ( $name, $st ) = split( '\^', $qname1 ); $prevrat{$qname1} = $rat0{$qname1}; } # Use iteration to find all newbies ratings $maxgap = 9999; $tries = 0; do { $thisgap = 0; $tries++; foreach $player (@newbies) { $qname1 = $who[$secnum][$player]; $toogood{$qname1} = ''; $toobad{$qname1} = ''; # printf "%2d %8d ", $player, $foundation{$qname1}; $sum = $foundation{$qname1}; $opprats{$qname1} = ''; for ( $game = 1 ; $game <= $lastround[$secnum][$player] ; $game++ ) { $rival = $hisopp[$secnum][$player][$game]; if ( $rival != $player ) { $qname2 = $who[$secnum][$rival]; $sum += $newrat{$qname2}; # printf "+%4d ", $newrat{$qname2}; $opprats{$qname1} .= sprintf( ",%g", $newrat{$qname2} ); checkmismatch(); } else { $opprats{$qname1} .= ","; # A bye } } # printf " = %8d/%d\n", $sum, $newtotgames{$qname1}; $newrat{$qname1} = $sum / $newtotgames{$qname1}; $newrat{$qname1} = $floor if $newrat{$qname1} < $floor; # should this be in tthe loop ? NO!! $gap = abs( $newrat{$qname1} - $prevrat{$qname1} ); $thisgap = $gap if $gap > $thisgap; $prevrat{$qname1} = $newrat{$qname1}; } if ( $thisgap < $maxgap ) { $maxgap = $thisgap } } until ( $maxgap < 0.5 or $tries > $maxtries ); if ( $tries > $maxtries ) { print "===== FAILED TO CONVERGE =====\n"; } else { $converged[$secnum] = 1 } # converged, now upfloor or round foreach $player (@newbies) { $qname1 = $who[$secnum][$player]; $explain{$qname1} = "$ratedgames{$qname1} * $rat0{$qname1} "; @temp = split( ',', $opprats{$qname1} ); @temp = grep( /\d/, @temp ); # get rid of byes $x = join( '+', @temp ); $halfs = 0.5 * $scale; $explain{$qname1} .= "+ $x + $scale * $wins{$qname1} - $halfs * $games{$qname1} "; if ( $newrat{$qname1} < $floor ) { $totalupfloor[$secnum] += $floor - $newrat{$qname1}; $newrat{$qname1} = $floor; } else { $newrat{$qname1} = int( $newrat{$qname1} + 0.5 ); } $totalupdated[$secnum] += $newrat{$qname1} - $rat0{$qname1} unless $ratedgames{$qname1} == 0; $csvline{$qname1} = $csvline{$qname1} . $opprats{$qname1} . ",$wins{$qname1},$newrat{$qname1}"; print CSVFILE "$csvline{$qname1}\n" if $write_to_CSV; } } else { $converged[$secnum] = 1; # print "no newbies.\n"; } } # Calculates earned points # Effectively returns the key information # $totalearned, # and for each $name # returns info about $games{$name}, $wins, $actual, $expected, sub calcearned { # for plin, now filtered to do just established players my ($secnum) = @_; for ( $player = 1 ; $player <= $secsize[$secnum] ; $player++ ) { $qname1 = $who[$secnum][$player]; $hisrat = $rat0{$qname1}; if ( $qname1 !~ /^. Bye\^/ ) { if ( $games{$qname1} > 0 ) { $newtotgames{$qname1} = $ratedgames{$qname1} + $games{$qname1}; if ( $newtotgames{$qname1} >= $proviso ) { # new in plinrat with Edward's bright idea ( $name, $st ) = split( '\^', $qname1 ); $csvline{$qname1} = "$st,$realname{$name},$rat0{$qname1},$ratedgames{$qname1}"; $rawopps{$qname1} = 0; $toogood{$qname1} = ''; $toobad{$qname1} = ''; for ( $game = 1 ; $game <= $lastround[$secnum][$player] ; $game++ ) { $rival = $hisopp[$secnum][$player][$game]; $qname2 = $who[$secnum][$rival]; if ( ( $rival == $player ) or $qname2 =~ /^. Bye\^/ ) { # print "$qname1 played $qname2\n"; $csvline{$qname1} .= ","; # bye } else { # $games{$qname1}++; $opporat = defined( $explain{$qname2} ) ? $newrat{$qname2} : $rat0{$qname2} + 0; # push( @opprats, $opporat ); $rawopps{$qname1}++ if $ratedgames{$qname2} == 0; $exp = &$probrule( $hisrat - $opporat ); $act = $hiswin[$secnum][$player][$game]; $expected{$qname1} += $exp; $wins{$qname1} += $act; $csvline{$qname1} .= ",$opporat"; checkmismatch(); } # if $rival!= $player } # for $game $csvline{$qname1} .= ",$wins{$qname1}"; # $multiplier = ( $hisrat >= 1800 and $use16 > 0 ) ? 16 : 20; $multiplier = 20; $earned{$qname1} = $multiplier * ( $wins{$qname1} - $expected{$qname1} ); $totalearned[$secnum] += $earned{$qname1}; $newrat{$qname1} = int( $rat0{$qname1} + $earned{$qname1} + 0.5 ); # whole number if ( $newrat{$qname1} < $floor ) { $totalupfloor[$secnum] += $floor - $newrat{$qname1}; $newrat{$qname1} = $floor; } $newtotgames{$qname1} = $ratedgames{$qname1} + $games{$qname1}; $csvline{$qname1} .= ",$newrat{$qname1}"; print CSVFILE "$csvline{$qname1}\n" if $write_to_CSV; } else { # print "$qname1 has already been done.\n"; } } # else (nonbye) } } # for $player } #end subroutine calcearned sub checkmismatch { # prepares info about matches between players with disparate ratings my $outcome = $hiswin[$secnum][$player][$game] == 1 ? '>' : $hiswin[$secnum][$player][$game] == 0 ? '<' : '='; my ( $oname, $ost ) = split( '\^', $qname2 ); my $nopp = nickname( $realname{$oname} ); my $r1 = $ratedgames{$qname1} < $proviso ? $newrat{$qname1} : $rat0{$qname1}; my $r2 = $ratedgames{$qname2} < $proviso ? $newrat{$qname2} : $rat0{$qname2}; my $x = $r2 - $r1; $toogood{$qname1} .= sprintf( " %s%s R+%d", $outcome, $nopp, $x ) if $x > 350; $toobad{$qname1} .= sprintf( " %s%s R%d", $outcome, $nopp, $x ) if $x < -350; } sub nickname { my ($name) = @_; my @parts = split( ' ', uc($name) ); if ( @parts < 2 ) { return '' } else { return sprintf( '%-4s', substr( substr( $parts[0], 0, 1 ) . substr( $parts[1], 0, 3 ) . ' ', 0, 4 ) ); } } sub squished { my ($name) = @_; if ( $name =~ /^. Bye$/ ) { return $name } else { $name =~ s/[^A-Za-z]//g; #Strip out nonalphe eg space, hyphen, comma return uc($name); } } sub ucbits { # Changes John van der Schoor to John Van Der Schoor } my $name = $_[0]; return join( ' ', map( ucfirst($_), split( ' ', $name ) ) ); } sub linear { my $x = $_[0]; return 0.05 if $x < -540; return 0.95 if $x > 540; return 0.5 + $x / $scale; } sub logit { return 1 / ( 1 + exp( -$_[0] / 172 ) ) } __END__