Download the raw code.
use v6;
use Test;
# borrowed from List::Utils
sub sliding-window-wrapped(@a, $n) is export(:DEFAULT) {
my $a-list = @a.iterator.list;
my @values;
gather {
while defined(my $a = $a-list.shift) {
@values.push($a);
@values.shift if +@values > $n;
take @values if +@values == $n;
}
for ^($n-1) {
@values.push(@a[$_]);
@values.shift if +@values > $n;
take @values if +@values == $n;
}
}
}
class Board {
has @.stones;
multi method new(Numeric $N) {
self.bless(*, :stones(Bool::True xx $N));
}
multi method new(Str $board) {
self.bless(*, :stones($board.comb(/ '#' | ' ' /).map({ $_ eq '#' })));
}
method Str {
"[" ~ @.stones.map({ $_ ?? '#' !! ' ' }).join ~ "]";
}
method any-moves-available() {
[||] sliding-window-wrapped(@.stones, 2).flat.map(* && *);
}
method play($n) {
if @.stones[$n] && @.stones[($n + 1) % *] {
@.stones[$n] = Bool::False;
@.stones[($n + 1) % *] = Bool::False;
Bool::True;
} else {
Bool::False; # not a legal move
}
}
method play-available($n) {
if @.stones[$n] && @.stones[($n + 1) % *] {
Bool::True;
} else {
Bool::False;
}
}
method runs() {
my @runs;
my $starting-stone = 0;
my $stone-count = 0;
for @.stones.kv -> $i, $stone {
if !$stone && $stone-count > 0 {
@runs.push($starting-stone => $stone-count);
$stone-count = 0;
}
if $stone {
$starting-stone = $i unless $stone-count > 0;
$stone-count++;
}
}
if $stone-count > 0 {
if ?@runs && @runs[0].key == 0 {
@runs[0] = $starting-stone => @runs[0].value + $stone-count;
} else {
@runs.push($starting-stone => $stone-count);
}
}
@runs.grep(*.value > 1);
}
method long-runs() {
self.runs.grep({ $_.value == 4 || $_.value > 5 }).sort(-*.value);
}
method short-runs() {
my @runs = self.runs;
my @shorts = @runs.grep({ $_.value ~~ 2..3 });
# a run of 5 is basically equivalent to two runs of 2,
# so register it that way to simplify cases
for @runs.grep({ $_.value == 5 }) -> $five {
@shorts.push($five.key => 2, $five.key + 3 => 2);
}
@shorts;
}
}
sub is-sure-win-my-turn($board) {
my @shorts = $board.short-runs;
my @longs = $board.long-runs;
given +@longs {
when 2..* {
given @longs[0].value {
when 10 {
if +@longs == 2 {
given @longs[1].value {
when 4 { +@shorts %% 2; }
Bool::False;
}
} else {
Bool::False;
}
}
when 9 {
if +@longs == 2 {
given @longs[1].value {
when 9 { +@shorts % 2 == 1; }
when 7 | 8 { +@shorts %% 2; }
when 4 | 6 { Bool::True; }
Bool::False;
}
} else {
Bool::False;
}
}
when 7 | 8 {
if +@longs == 2 {
given @longs[1].value {
when 7 | 8 { +@shorts % 2 == 1; }
when 4 | 6 { Bool::True; }
}
} else {
Bool::False;
}
}
when 6 {
if +@longs == 2 {
if @longs[1].value == 4 {
+@shorts %% 2;
} else {
+@shorts % 2 == 1;
}
} elsif +@longs == 3 && @longs[1].value == 4 {
Bool::True;
} else {
Bool::False;
}
}
when 4 {
if +@longs % 2 == 1 {
Bool::True;
} elsif ?@shorts {
+@shorts % 2 == 1; # if even number of 4s, we want an odd number of @shorts
} else {
Bool::False;
}
}
default { Bool::False; }
}
}
when 1 {
given @longs[0].value {
when 4 | 6 | 10 | 11 | 12 | 13 | 14 { Bool::True; }
# 5 should be treated as two 2s
when 7 | 8 { +@shorts %% 2; }
when 9 | 15 { +@shorts % 2 == 1; }
default { Bool::False; } # this might be improved?
}
}
when 0 {
+@shorts % 2 == 1; # odd number, we win; even number, they do
}
}
}
sub blind-search($board, $default-play?) {
my $dumb-play = $default-play;
for ^$board.stones -> $move {
next unless $board.play-available($move);
$dumb-play //= $move;
my $new-board = Board.new(~$board);
$new-board.play($move);
return $move unless is-sure-win-my-turn($new-board);
}
$dumb-play;
}
sub find-move($board) {
sub find-move-inner() {
my @shorts = $board.short-runs;
my @longs = $board.long-runs;
given +@longs {
when 2..* {
given @longs[0].value {
when 10 {
if +@longs == 2 {
given @longs[1].value {
when 4 { @longs[0].key + 2; } # either good or desperate
proceed;
}
} else {
proceed;
}
}
when 9 {
if +@longs == 2 {
given @longs[1].value {
when 7 | 8 | 9 { @longs[0].key; } # either good or desperate
when 6 { +@shorts %% 2 ?? @longs[1].key + 2 !! @longs[1].key + 1; }
when 4 { +@shorts %% 2 ?? @longs[1].key + 1 !! @longs[1].key; }
proceed;
}
} else {
proceed;
}
}
when 8 {
if +@longs == 2 {
given @longs[1].value {
when 7 | 8 { @longs[0].key + 1; } # either good or desperate
when 6 { +@shorts %% 2 ?? @longs[1].key + 1 !! @longs[1].key + 2; }
when 4 { +@shorts %% 2 ?? @longs[1].key + 0 !! @longs[0].key + 2; }
proceed;
}
} else {
proceed;
}
}
when 7 {
given @longs[1].value {
when 7 { @longs[0].key + 2; } # either good or desperate
when 6 { +@shorts %% 2 ?? @longs[1].key + 1 !! @longs[1].key + 2; }
when 4 { +@shorts %% 2 ?? @longs[1].key !! @longs[1].key + 1; }
}
}
when 6 { @longs[0].key; } # either this is right or we're desperate
when 4 {
# pattern here is +@longs 4s, plus maybe 2s and 3s
if +@longs % 2 == 1 {
+@shorts %% 2 ?? @longs[0].key + 1 !! @longs[0].key;
} elsif ?@shorts {
@shorts[0].key; # either this is right or we're desperate
} else {
@longs[0].key; # desperate
}
}
default { blind-search($board, @longs[0].key + 4); }
}
}
when 1 {
given @longs[0].value {
when 4 { +@shorts %% 2 ?? @longs[0].key + 1 !! @longs[0].key; }
# 5 should be treated as two 2s
when 6 { +@shorts %% 2 ?? @longs[0].key + 2 !! @longs[0].key + 1; }
when 7 | 8 { @longs[0].key + 3; } # either this is best, or it won't make any difference
when 9 { @longs[0].key; } # either this is best or desperate
when 10 { +@shorts %% 2 ?? @longs[0].key + 4 !! @longs[0].key + 3; }
when 11 { +@shorts %% 2 ?? @longs[0].key !! @longs[0].key + 1; }
when 12 { +@shorts %% 2 ?? @longs[0].key + 1 !! @longs[0].key + 4; }
when 13 { +@shorts %% 2 ?? @longs[0].key + 3 !! @longs[0].key + 2; }
when 14 { +@shorts %% 2 ?? @longs[0].key + 6 !! @longs[0].key + 3; }
when 15 { +@shorts %% 2 ?? @longs[0].key + 6 !! @longs[0].key + 5; }
default { blind-search($board, @longs[0].key + 4); } # this needs to be improved
}
}
when 0 {
@shorts[0].key; # play can't make any difference
}
}
}
find-move-inner() % +$board.stones;
}
multi MAIN() {
my $number-stones = +($*IN.get // 0);
my $first-player = $*IN.get // "";
unless $number-stones > 1 && $first-player eq "human" | "computer" {
say "aborted";
return;
}
my $board = Board.new($number-stones);
my $first-turn = Bool::True;
loop {
if !$first-turn || $first-player eq "computer" {
my $move = find-move($board);
my $second = ($move + 1) % +$board.stones;
say "computer takes $move,$second";
$board.play($move) || die "Computer failed to play a legal move";
}
if !$board.any-moves-available {
say "computer wins";
last;
}
say "human to move: " ~ $board.stones.pairs.grep(*.value).map(*.key).join(',');
my $human-move = prompt("");
# while !$human-move.match(rx/^\s* (\d+) \s* , \s* (\d+)/) {
# $human-move = prompt("unrecognized format, try again: ");
# }
$board.play(+$human-move) || die "Human failed to play a legal move";
if !$board.any-moves-available {
say "human wins";
last;
}
$first-turn = Bool::False;
}
}
multi MAIN($long-run where { $long-run ~~ /^ [\d+] ["-" \d+]* $/ }) {
my @patterns;
if $long-run ~~ /^ \d+ $/ {
for ^4 -> $tail {
@patterns.push: "[" ~ "#" x +$long-run ~ " ##" x $tail ~ " ]";
}
} else {
@patterns.push: "[" ~ $long-run.split("-").map({ "#" x +$_ ~ " " }).join ~ "]";
}
for @patterns -> $pattern {
my @promising = gather for ^($pattern.chars - 2) {
my $board = Board.new($pattern);
if $board.play($_) && !is-sure-win-my-turn($board) {
my $temp = $_;
take $temp;
}
}
say "{ $pattern }: " ~ (?@promising ?? @promising.join(", ") !! "No winning moves");
}
}
multi MAIN("test") {
plan *;
for "[## # # ]", "[#### #### ## ]" -> $string {
my $board = Board.new($string);
isa_ok $board, Board, "Made a board object";
is +$board.stones, $string.chars - 2, "Board has right number of stones";
is ~$board, $string, "Board has correct pattern";
}
{
my $board = Board.new("[ # ]");
nok $board.any-moves-available, "No moves available";
}
{
my $board = Board.new(4);
isa_ok $board, Board, "Made a board object";
is +$board.stones, 4, "Board has 4 stones";
ok (all $board.stones), "All stones are true";
ok $board.play(1), "Play 1 & 2";
nok (all $board.stones), "Not all stones are true";
ok $board.any-moves-available, "There is still a move available";
nok $board.play(1), "Can no longer play 1 & 2";
nok $board.play(0), "Can't play 0 & 1";
nok $board.play(2), "Can't play 2 & 3";
ok $board.stones[0], "Stone 0 still there";
nok $board.stones[1], "Stone 1 gone";
nok $board.stones[2], "Stone 2 gone";
ok $board.stones[3], "Stone 3 still there";
ok $board.play(3), "Play 3 & 0";
ok (none $board.stones), "No stones are true";
nok $board.any-moves-available, "There are no moves available";
}
{
my $board = Board.new(5);
isa_ok $board, Board, "Made a board object";
is +$board.stones, 5, "Board has 4 stones";
ok (all $board.stones), "All stones are true";
ok $board.play(1), "Play 1 & 2";
nok (all $board.stones), "Not all stones are true";
ok $board.any-moves-available, "There is still a move available";
nok $board.play(1), "Can no longer play 1 & 2";
nok $board.play(0), "Can't play 0 & 1";
nok $board.play(2), "Can't play 2 & 3";
ok $board.stones[0], "Stone 0 still there";
nok $board.stones[1], "Stone 1 gone";
nok $board.stones[2], "Stone 2 gone";
ok $board.stones[3], "Stone 3 still there";
ok $board.stones[4], "Stone 4 still there";
ok $board.play(3), "Play 3 & 4";
ok (one $board.stones), "One stone is true";
nok $board.any-moves-available, "There are no moves available";
}
{
my $board = Board.new(10);
is ~$board.runs, ~[0 => 10], "runs works for complete board";
ok $board.play(1), "Can play 1 & 2";
is ~$board.runs, ~[3 => 8], "runs works wraparound";
ok $board.play(5), "Can play 1 & 2";
is ~$board.runs, ~[7 => 4, 3 => 2], "runs works with two holes";
ok $board.play(8), "Can play 8 & 9";
is ~$board.runs, ~[3 => 2], "runs skips runs of length 1";
}
{
my $board = Board.new(11);
is ~$board.runs, ~[0 => 11], "runs works for complete board";
ok $board.play(0), "Can play 0 & 1";
is ~$board.runs, ~[2 => 9], "runs works with hole at beginning";
ok $board.play(9), "Can play 9 & 10";
is ~$board.runs, ~[2 => 7], "runs works with holes at beginning and end";
}
{
my $board = Board.new("[###### ## ## ]");
my @longs = $board.long-runs;
my @shorts = $board.short-runs;
is +@longs, 1, "Found one long";
is @longs[0].value, 6, "and it has the correct length";
is +@shorts, 2, "Found two shorts";
is @shorts[0].value, 2, "and the first has the correct length";
is @shorts[1].value, 2, "and the second has the correct length";
}
{
my $board = Board.new("[##### ### #### ## ]");
my @longs = $board.long-runs;
my @shorts = $board.short-runs;
is +@longs, 1, "Found one long";
is @longs[0].value, 4, "and it has the correct length";
is +@shorts, 4, "Found four shorts";
is +@shorts.grep(*.value == 2), 3, "with three length 2s";
is +@shorts.grep(*.value == 3), 1, "and one length 3";
}
# ok is-sure-win-my-turn([2 => 2]), "One run of size 2 is a sure win";
# ok is-sure-win-my-turn([2 => 3]), "One run of size 3 is a sure win";
# ok is-sure-win-my-turn([2 => 4]), "One run of size 4 is a sure win";
# nok is-sure-win-my-turn([2 => 5]), "One run of size 5 is a sure loss";
# ok is-sure-win-my-turn([2 => 6]), "One run of size 6 is a sure win";
# ok is-sure-win-my-turn([2 => 7]), "One run of size 7 is a sure win";
# ok is-sure-win-my-turn([2 => 8]), "One run of size 8 is a sure win";
#
# nok is-sure-win-my-turn([10 => 2, 2 => 2]), "2+2 is a sure loss";
# nok is-sure-win-my-turn([10 => 2, 2 => 3]), "2+3 is a sure loss";
# ok is-sure-win-my-turn([10 => 2, 2 => 4]), "2+4 is a sure win";
# ok is-sure-win-my-turn([10 => 2, 2 => 5]), "2+5 is a sure win";
# nok is-sure-win-my-turn([10 => 2, 2 => 6]), "2+6 is a sure loss";
# nok is-sure-win-my-turn([10 => 2, 2 => 7]), "2+7 is a sure loss";
# nok is-sure-win-my-turn([10 => 2, 2 => 8]), "2+8 is a sure loss";
#
# ok is-sure-win-my-turn([10 => 2, 14 => 3, 2 => 2]), "2+3+2 is a sure win";
# ok is-sure-win-my-turn([10 => 2, 14 => 3, 2 => 3]), "2+3+3 is a sure win";
# ok is-sure-win-my-turn([10 => 2, 14 => 3, 2 => 4]), "2+3+4 is a sure win";
# nok is-sure-win-my-turn([10 => 2, 14 => 3, 2 => 5]), "2+3+5 is a sure loss";
# ok is-sure-win-my-turn([10 => 2, 14 => 3, 2 => 6]), "2+3+6 is a sure win";
# ok is-sure-win-my-turn([10 => 2, 14 => 3, 2 => 7]), "2+3+7 is a sure win";
# ok is-sure-win-my-turn([10 => 2, 14 => 3, 2 => 8]), "2+3+8 is a sure win";
{
my $board = Board.new(5);
my $move = find-move($board);
ok $move ~~ 0..5, "First move is any valid move";
ok $board.play($move), "First move worked";
$move = find-move($board);
ok $board.play($move), "Second move worked";
nok $board.any-moves-available, "Board is done, second player won";
}
{
my $board = Board.new(6);
my $move = find-move($board);
ok $move ~~ 0..6, "First move is any valid move";
ok $board.play($move), "First move worked";
$move = find-move($board);
ok $board.play($move), "Second move worked";
nok $board.any-moves-available, "Board is done, second player won";
}
{
my $board = Board.new(7);
my $move = find-move($board);
ok $board.play($move), "First move worked";
$move = find-move($board);
ok $board.play($move), "Second move worked";
$move = find-move($board);
ok $board.play($move), "Third move worked";
nok $board.any-moves-available, "Board is done, first player won";
}
for 8..10 -> $size {
my $board = Board.new($size);
my $move = find-move($board);
ok $board.play($move), "First move worked (size $size)";
$move = find-move($board);
ok $board.play($move), "Second move worked (size $size)";
$move = find-move($board);
ok $board.play($move), "Third move worked (size $size)";
$move = find-move($board);
ok $board.play($move), "Fourth move worked (size $size)";
nok $board.any-moves-available, "Board is done, second player won (size $size)";
}
for 2..9 -> $second-move {
my $board = Board.new(11);
ok $board.play(0), "First move worked (size 12-$second-move)";
ok $board.play($second-move), "Second move worked (size 12-$second-move)";
my $move = find-move($board);
ok $board.play($move), "Third move worked (size 11-$second-move)";
$move = find-move($board);
ok $board.play($move), "Fourth move worked (size 11-$second-move)";
$move = find-move($board);
ok $board.play($move), "Fifth move worked (size 11-$second-move)";
nok $board.any-moves-available, "Board is done, first player won (size 11-$second-move)";
}
for 2..5, 7..10 -> $second-move {
my $board = Board.new(12);
ok $board.play(0), "First move worked (size 12-$second-move)";
ok $board.play($second-move), "Second move worked (size 12-$second-move)";
my $move = find-move($board);
ok $board.play($move), "Third move worked (size 12-$second-move)";
$move = find-move($board);
ok $board.play($move), "Fourth move worked (size 12-$second-move)";
$move = find-move($board);
ok $board.play($move), "Fifth move worked (size 12-$second-move)";
nok $board.any-moves-available, "Board is done, first player won (size 12-$second-move)";
}
for 3, 5..8, 10 -> $second-move {
my $board = Board.new(13);
ok $board.play(0), "First move worked (size 13-$second-move)";
ok $board.play($second-move), "Second move worked (size 13-$second-move)";
my $move = find-move($board);
ok $board.play($move), "Third move worked (size 13-$second-move)";
$move = find-move($board);
ok $board.play($move), "Fourth move worked (size 13-$second-move)";
$move = find-move($board);
ok $board.play($move), "Fifth move worked (size 13-$second-move)";
nok $board.any-moves-available, "Board is done, first player won (size 13-$second-move)";
}
{
my $board = Board.new("[#### ## ]");
my $move = find-move($board);
ok $board.play($move), "Third move worked (4-2 pattern)";
$move = find-move($board);
ok $board.play($move), "Fourth move worked (4-2 pattern)";
$move = find-move($board);
ok $board.play($move), "Fifth move worked (4-2 pattern)";
nok $board.any-moves-available, "Board is done, first player won (4-2 pattern)";
}
my @winning-patterns = "[##]",
"[###]",
"[#### ]",
"[###### ]",
"[####### ]",
"[######## ]",
"[########## ]",
"[########### ]",
"[#### ##]",
"[#### ## ]",
"[##### ## ]",
"[###### ## ]",
"[#### ## ## ]",
"[#### ## ## ## ]",
"[###### ## ## ]",
"[###### ## ## ## ]",
"[######### ## ]",
"[######### ## ## ## ]",
"[########## ## ]",
"[########## ## ## ]",
"[########## ## ## ## ]",
"[########### ]",
"[########### ## ]",
"[########### ## ## ]",
"[########### ## ## ## ]",
"[############ ]",
"[############ ## ]",
"[############ ## ## ]",
"[############ ## ## ## ]",
"[############# ]",
"[############# ## ]",
"[############# ## ## ]",
"[############# ## ## ## ]",
"[############## ]",
"[############## ## ]",
"[############## ## ## ]",
"[############## ## ## ## ]",
"[############### ## ]",
"[############### ## ## ## ]",
# "[################ ]",
# "[################ ## ## ]",
"[## ## ## ]",
"[#### #### ## ]",
"[#### #### ## ## ## ]",
"[##### #### ]",
"[##### #### ## ]",
"[##### #### ## ## ]",
"[#### #### #### ]",
"[#### #### #### ## ]",
"[#### #### #### ## ## ]",
"[#### #### #### ## ## ## ]",
"[#### #### #### #### ## ]",
"[#### #### #### #### ## ## ## ]",
"[#### #### #### #### #### ]",
"[#### #### #### #### #### ## ]",
"[#### #### #### #### #### ## ## ]",
"[#### #### #### #### #### #### ## ]",
"[####### #### ]",
"[####### #### ## ]",
"[####### #### ## ## ]",
"[####### #### ## ## ## ]",
"[####### ###### ]",
"[####### ###### ## ]",
"[####### ###### ## ## ]",
"[####### ###### ## ## ## ]",
"[####### ####### ## ]",
"[####### ####### ## ## ## ]",
"[######## #### ]",
"[######## #### ## ]",
"[######## #### ## ## ]",
"[######## #### ## ## ## ]",
"[######## ###### ]",
"[######## ###### ## ]",
"[######## ###### ## ## ]",
"[######## ###### ## ## ## ]",
"[######## ####### ## ]",
"[######## ####### ## ## ## ]",
"[######## ######## ## ]",
"[######## ######## ## ## ## ]",
"[######### #### ]",
"[######### #### ## ]",
"[######### #### ## ## ]",
"[######### #### ## ## ## ]",
"[######### ###### ]",
"[######### ###### ## ]",
"[######### ###### ## ## ]",
"[######### ###### ## ## ## ]",
"[######### ####### ]",
"[######### ####### ## ## ]",
"[######### ######## ]",
"[######### ######## ## ## ]",
"[######### ######### ## ]",
"[######### ######### ## ## ## ]",
"[########## #### ]",
"[########## #### ## ## ]",
;
for @winning-patterns -> $pattern {
my $board = Board.new($pattern);
my $name = "pattern " ~ $board.runs.map(*.value).join('-');
ok is-sure-win-my-turn($board), "is-sure-win-my-turn is right about $name";
my $i = 1;
loop {
my $move = find-move($board);
ok $board.play($move), "Move #{ $i++ } worked ($name)";
# say $board;
last unless $board.any-moves-available;
$move = find-move($board);
ok $board.play($move), "Move #{ $i++ } worked ($name)";
ok $board.any-moves-available, "Board is not yet done ($name)";
last unless $board.any-moves-available; # whoops!
last if $i > +$board.stones; # something very wrong, bail out
}
nok $board.any-moves-available, "Board is done, first player won ($name)";
}
for 12..16, 18..32 -> $size {
my $board = Board.new($size);
$board.play(0);
my $move = blind-search($board);
# say $board, $move;
ok $board.play($move), "Can make move (size $size)";
nok is-sure-win-my-turn($board), "Didn't lead us astray";
}
{
my $board = Board.new(11);
$board.play(0);
my $move = blind-search($board, 9);
is $move, 9, "No way to win in this circumstance, so return default";
}
done;
}
The code is laid out much as I would lay it out. Indentation sometimes goes eight levels deep, though. I'd probably have factored something out before that point.
Some code is inexplicably commented out.
The method .play-available
is only called once. The rest of the time, .play
is called for the same effect. (And it can, because it returns True
/False
depending on the availability of the move, just like .play-available
.)
I think either the query method should be removed and .play
given a name
that better reflected its dual nature, or .play
should be made into a
pure command method with no return value.
(A nagging suspicion tells me that Board
could have been more pleasant
to use if it were immutable and if .play
, its only command method, returned
a fresh Board
instead of mutating self
. I'm not going to press on
that, because it's an engineering tradeoff, and I haven't tried coding it
the other way. But the gather
starting on line 330 could probably have
been a smaller map
in such a codebase.)
This is where I complain about things not being as clear as they could have been with Sprague-Grundy. I'll try to be similarly unfair to all contestants.
The subroutine is-sure-win-my-turn
is a 76 lines of nested special
cases. Even if it were all correct (it isn't) and would never have to be
maintained, its structure doesn't invite understanding. I'm not faulting
the author with not seeing the patterns of the game so much as not writing
down the patterns he sees in a more systematic way.
Same complaint about find-move
.
I should put in a good word about the tests, though. It's obvious that this
has been developed in a very test-centric way; more than half of the linecount
in the source file is dedicated to testing. Granted, things like the contents
of the @winning-patterns
variable starting on line 565, are another example
of the confusion emerging from not knowing about nimbers. But still, it's nice
with tests.
I see no obvious slowdowns or extra loops. Most of the code is special cases anyway. The algorithm isn't perfect, and has been proven not perfect, but given what it is, it's not terribly inefficient.
I think sliding-window-wrapped
is becoming an antipattern for me. As other
solutions have shown, Perl 6's Z
and .rotate
cover the same semantic
ground, and they're built in.
Nevertheless, there are also some nice Perl 6 patterns in here, such as the
[||]
on line 39, and the for
loop over an array's .kv
on line 64.
Some people like the extra code that if $bool { True } else { False }
produces, such as in play-available
. I don't, particularly; I prefer just
$bool
.
This code is tall and wide.