On May 1, 1999, an air-conditioned room deep within the University of Missouri Kansas City's Computing Services department was transformed for a brief eight hours into the mission control center for what may very well have been the first ever programming contest of its kind: the Solitaire 500, described in TPJ #13
As you might recall, all programs entered in the Solitaire 500 ran simultaneously on the same computer. They had to make network connections to a server running on a different computer and solve an identical series of five hundred problems presented by the server. The first to complete the series of problems won
Nineteen entries reached the contest's e-mail inbox before the morning of May first.
ID | Name | |
---|---|---|
1 | Theo Van Dinter | |
2 | Joseph A. Diverdi | |
3 | Steven W. McDougall | |
4 | Eero Pajarre | |
5 | Mark Kvale | |
6 | Dave Shield | |
7 | Andreas Gross | |
8 | James P. Williams | |
9 | David Brandt | |
10 | Stephen M. Moraco | |
11 | John Whitmore | |
12 | Edward M. Perry | |
13 | Jack Applin | |
14 | Robert Au | |
15 | Ira Joseph Woodhead | |
16 | Erle Schuyler | |
17 | Yanick Champoux | |
18 | Jeff Norden | |
19 | Peter J. Jones |
Seventeen qualified for the competition.
The winner was #4, Eero Pajarre's entry, which averaged only 9.7 rounds per deck in the qualifying trial and earned Eero $100 plus a two year extension on his TPJ subscription. His entry re-optimized for speed in the main contest, and won with an average time per deck of slightly more than three seconds. Developing with precompiled binaries of ActiveState Perl and GNU Emacs under Microsoft Windows 95, Pajarre optimized pickup order for most discards available next round with the help of a precompiled table of eight and twelve card situations. Then with the help of the Benchmark module, he realized that switching to string representations would save time
His canonic() subroutine converts decks represented as strings of card rank letters into patterns for the pre-solved table:
sub canonic ($) { my ($d) = @_; my ($res) = ""; my ($ind) = ""; my ($c, $i); for ($i = 0; $i < length($d); $i++){ $c="substr($d," $i, 1); if ( 0 > index($ind,$c) ) { $ind .= $c; } $res .= chr( ord('0') + index($ind, $c) ); } return $res; }
Steven W. McDougall's entry #3, a completely commentless six page program that nevertheless reads like a chess match, came in second, taking the $50 prize and a one-year extension to TPJ. By using arrays and array references for his internal representations, McDougall's program was able to generate workable long solutions faster than the next four finishers could generate short solutions. His Partition() subroutine uses bitmasks to determine if the lengths of arrays are divisible by four, to quickly choose good restack orders.
sub Partition { my ($stacks, $block4) = @_; my $s1 = scalar @{$Tab[$stacks->[1]]}; my $s2 = scalar @{$Tab[$stacks->[2]]}; my $s3 = scalar @{$Tab[$stacks->[3]]}; $block4 & 3 or return @$stacks; ($block4 + $s1) & 3 or return @$stacks[1, 0, 2, 3]; ($block4 + $s2) & 3 or return @$stacks[2, 0, 1, 3]; ($block4 + $s1 + $s2) & 3 or return @$stacks[1, 2, 0, 3]; ($block4 + $s3) & 3 or return @$stacks[3, 0, 1, 2]; ($block4 + $s1 + $s3) & 3 or return @$stacks[1, 3, 0, 2]; ($block4 + $s2 + $s3) & 3 or return @$stacks[2, 3, 0, 1]; ($block4 + $s1 + $s2 + $s3) & 3 or return @$stacks[1, 2, 3, 0]; }
The Wheels game is based on a game of solitaire known as "Perpetual Motion", and some years ago, McDougall wrote a C program to play Perpetual Motion with an animated display of the game state. His second place client was based on this earlier work.
After debugging the loop avoidance strategy, McDougall's program solves approximately eighteen decks per second on his 266 MHz Pentium II PC/NT.
With an algorithm that appeared to win every hand, McDougall focused on minimizing the socket I/O by tacking the instruction to receive the next deck onto the tail of the instruction to win the current deck. At that point his program could process about three hands per second in concert with the demonstration server program.
Jim Williams's third place Expert.pm, #8, was the only entry to have more than one file of program code, and was also the only entry to have full pod documentation. After he settled on an algorithm, Williams made extensive use of the Benchmark and Devel::DProf modules to guide his creation, inlining several pieces of code that had been separate subroutines. His %permutations hash, a hash of arrays of arrays (a HoLoL) to hold the dealt cards, allowed him to quickly construct the decks so as to choose one that would provide at least one discard the following round. Using "early pickups" to prevent missed discards, Williams's entry has the second lowest total number of moves in the contest, after Norden's #18.
Honorable Mentions: Dave Shield's #6 entry made extensive use of regular expressions. Mark Kvale's #5, the winner of the qualifying round, used a two-ply search with an external database of twelve-card situations. Kvale's Simulate() subroutine provides the same functionality as Williams's array-reference based @{$Permutations{$piles}} with this verbose slice:
# create the deck after pickup @order = split //, $perm; @deck = ( @{$pile[$order[0]]}, @{$pile[$order[1]]}, @{$pile[$order[2]]}, @{$pile[$order[3]]} );
Jeff Norden's "RACE CAR" (#18), also using a full two-ply heuristic search, achieved the best round count of the contest. However, Norden spent more CPU time than any other entry:
foreach $order (@Pickups) { @deck = map ( @{$_, @stack[@$pickup_list{$order}]} ); play_and_score(); }
Theo Van Dinter's #1 entry was the last of the entries that supplied multiple move commands at a time.
From Ira Joseph Woodhead (#15), we learned that $ENV{'USER'} is a more portable construction than 'whoami', and from Stephen M. Moraco (#10) we see the advantage of BEGIN { $Exporter::Verbose = 1; } as a package debugging technique.
Joseph A. DiVerdi (#2) used heuristic search to achieve the second-best move count, but his subroutine sent frequent instructions and therefore used a lot of time waiting for the server to respond.
One entry froze, and a spelling error prevented another entry from qualifying. The other seventeen entries successfully completed the qualifying round, earning their creators Perl Mongers T-shirts. The competition results:
> grep 'OK FI' competition_log
925611991 | entry4 | aai | OK | FI | NI | SH | ED | time | 1527 | round | 6653 | move | 81951 |
925612196 | entry3 | aac | OK | FI | NI | SH | ED | time | 1732 | round | 9345 | move | 108969 |
925612235 | entry8 | aaf | OK | FI | NI | SH | ED | time | 1771 | round | 8376 | move | 72820 |
925612518 | entry11 | aab | OK | FI | NI | SH | ED | time | 2054 | round | 8721 | move | 84869 |
925612702 | entry6 | aal | OK | FI | NI | SH | ED | time | 2238 | round | 8105 | move | 96577 |
925612904 | entry7 | aaj | OK | FI | NI | SH | ED | time | 2440 | round | 8861 | move | 101499 |
925612999 | entry14 | aak | OK | FI | NI | SH | ED | time | 2535 | round | 10268 | move | 141576 |
925614674 | entry19 | aac | OK | FI | NI | SH | ED | time | 4210 | round | 27908 | move | 312841 |
925614893 | entry5 | aap | OK | FI | NI | SH | ED | time | 4429 | round | 7208 | move | 84750 |
925615025 | entry12 | aai | OK | FI | NI | SH | ED | time | 4561 | round | 6725 | move | 79026 |
925615396 | entry18 | aaa | OK | FI | NI | SH | ED | time | 4931 | round | 5566 | move | 64495 |
925615434 | entry1 | aah | OK | FI | NI | SH | ED | time | 4970 | round | 5948 | move | 77961 |
925633669 | entry15 | aal | OK | FI | NI | SH | ED | time | 23205 | round | 10871 | move | 91375 |
925634034 | entry10 | aab | OK | FI | NI | SH | ED | time | 23570 | round | 9353 | move | 114787 |
925635423 | entry2 | aan | OK | FI | NI | SH | ED | time | 24959 | round | 8932 | move | 78288 |
925641828 | entry9 | aan | OK | FI | NI | SH | ED | time | 31364 | round | 6727 | move | 93842 |
925650130 | entry13 | aar | OK | FI | NI | SH | ED | time | 39666 | round | 41316 | move | 317819 |
This is the final ps listing before Eero finished, having used the least CPU time of all the entries.
Sat May 1 21:25:00 CDT 1999
USER | PID | %CPU | %MEM | SIZE | RSS | TTY | STAT | START | TIME | COMMAND |
---|---|---|---|---|---|---|---|---|---|---|
entry1 | 17264 | 6.6 | 1.8 | 2416 | 1804 | p3 | R | 20:54 | 2:03 | perl entry.pl |
entry2 | 17253 | 0.6 | 2.0 | 2560 | 1936 | pd | S | 20:54 | 0:12 | perl entry.pl |
entry3 | 17235 | 4.5 | 1.9 | 2464 | 1852 | q1 | R | 20:53 | 1:26 | perl entry.pl |
entry4 | 17237 | 4.4 | 2.0 | 2600 | 1984 | pf | R | 20:53 | 1:24 | perl entry.pl |
entry5 | 17247 | 6.4 | 2.4 | 2916 | 2320 | pe | R | 20:53 | 2:02 | perl entry.pl |
entry6 | 17245 | 4.8 | 1.9 | 2500 | 1884 | q0 | R | 20:53 | 1:32 | perl entry.pl |
entry7 | 17241 | 5.5 | 2.3 | 2824 | 2216 | q3 | R | 20:53 | 1:45 | perl entry.pl |
entry8 | 17239 | 4.7 | 1.9 | 2480 | 1860 | q2 | R | 20:53 | 1:30 | perl entry.pl |
entry9 | 17255 | 4.1 | 2.1 | 2632 | 2024 | q4 | R | 20:54 | 1:16 | perl entry.pl |
entry10 | 17266 | 0.6 | 2.4 | 2968 | 2360 | p4 | S | 0:54 | 0:12 | perl entry.pl |
entry11 | 17262 | 5.1 | 1.9 | 2424 | 1820 | p5 | R | 20:54 | 1:35 | perl entry.pl |
entry12 | 17251 | 6.5 | 1.9 | 2488 | 1872 | pa | S | 20:53 | 2:02 | perl entry.pl |
entry13 | 17259 | 0.1 | 1.9 | 2508 | 1892 | p7 | S | 20:54 | 0:03 | perl entry.pl |
entry14 | 17257 | 5.3 | 2.0 | 2556 | 1944 | pb | R | 20:54 | 1:40 | perl entry.pl |
entry15 | 17261 | 0.3 | 2.0 | 2528 | 1920 | p6 | R | 20:54 | 0:06 | perl entry.pl |
entry18 | 17249 | 7.0 | 3.2 | 3684 | 3072 | p9 | R | 20:53 | 2:13 | perl entry.pl |
entry19 | 17243 | 4.3 | 1.8 | 2404 | 1792 | pc | R | 20:53 | 1:23 | perl entry.pl |
__END__
David Nicol (gratuitoushyphens@tipjar.com) no longer wonders what grading papers at the end of the semester must be like.