#!/usr/bin/perl
$VER='v1.41+text';# BeholderBoard Virtual Chess-set: www.beholder.co.uk 28-Aug-1999

# cgi: produces list of available chessboards or else the requested board
# v1.4:  
# +text  SUPPORTS "TEXT ONLY" for the Lynx folks etc.

# please see the on-line FAQ at http://www.beholder.co.uk/chess/faq.html

#------------------------------------------------------------------------------
#  Note that this is the NOT the standard BeholderBoard version
#       ...this is the TEXT-ONLY option "special" version
#
# *** see the readme which accompanies this script for detailed information ***
# *** and also the FAQ maintained beneath http://www.beholder.co.uk         ***

{
 ##------------------------------------------------------------------------------
 ##  LOCAL CONFIGURATION... edit these!    be careful: no trailing "/"s in paths!
 ##------------------------------------------------------------------------------

 $URL        = 'http://YOUR_DOMAIN';      # url of your domain
 $GRPATH     = 'chess';                   # path for graphics (from server root)
 $CGIPATH    = 'cgi-bin';                 # path to this script (from server root)
 $SCRIPT     = 'boardtxt.pl';             # name of this script
 $DATAPATH   = '';                        # path to data (relative to this script)
 $DATAFNAME  = 'board.txt';               # base data file name - must have one '.'

 $EMAILPROG  = '';                        # your mailer program...
                                          # e.g. $EMAILPROG ='/usr/lib/sendmail';
                                          # Note: to DISABLE email notification
                                          # of moves, set $EMAILPROG = '';
 
 ##------------------------------------------------------------------------------
 ##  LOCAL CONFIGURATION... that's all 
 ##------------------------------------------------------------------------------



 $BACKCOLOUR = '#115511';                 
 $BRDRCOLOUR = '#ff3333';                 # used for border and behind error message
 $TEXTCOLOUR = '#ffffff';
 $LINKCOLOUR = '#ffffff';                 # text must be visible over...
 $ALNKCOLOUR = '#ff0000';                 # ...$BACK- and $BRDR- colours

 $MAX_XY     = 26;                        # limit of deviance
 $PLAY       = 1;
 $PROBE      = 0;
 %PIECE      = qw(P pawn R rook N knight B bishop Q queen K king);
 %COLOUR     = qw(b black w white);
 %PASSWD     = ('b','','w','');
 %EMAIL      = ('b','','w','');

 $plyr       ='w';                        # current player
 $err        ='';                         # error (possibly benign) message
 $captives   ='';                         # list of captives (in order taken)
 $timestamp  = 0;
 $title      ='';
 $hidden     = 0;
 $syserr     = 0;
 $xX         = 7;                         # edges of... 
 $yY         = 7;                         # ...board
 $|          = 1;

 print "Content-Type: text/html\n\n<html>\n";
 use integer;
 foreach (qw(   fr to pp bd hl hc hm sz pw dj tx   )){$form{$_}=0} # initialise

 &getCGIvars;          # loads global %form and sets $err if there's a problem
 
 if( $form{'fr'}=~/^\s*(\w\d)\s*$/    ){$fr=lc($1)}else{$fr=''} # move from...
 if( $form{'to'}=~/^\s*(\w\d)\s*$/    ){$to=lc($1)}else{$to=''} # ...to
 if( $form{'pp'}=~/^\s*(\w)\s*$/      ){$pp=uc($1)}else{$pp=''} # pawn promotion piece 
 if( $form{'bd'}=~/^\s*(\d{1,3})\s*$/ ){$bd=$1}    else{$bd=0 } # board number
 if( $form{'hl'}                      ){$hl=1}     else{$hl=0 } # Hide Labels  (0=no, 1=yes [default=not hidden])
 if( $form{'hc'}                      ){$hc=1}     else{$hc=0 } # Hide Captives
 if( $form{'hm'}                      ){$hm=1}     else{$hm=0 } # Hide Moves
 if( $form{'sz'}=~/^\s*([1-9]\d{0,2})\s*$/){$sz=$1}    else{$sz=50} # square size
 if( $form{'pw'}=~/^\s*(\w+)\s*$/     ){$pw=$1}    else{$pw=''} # password
 if( $form{'dj'}                      ){$dj=1}     else{$dj=0 } # disable JavaScript (0=no, 1=yes)
 if( $form{'tx'}=~/^\s*(1|0)\s*$/     ){$tx=$1}    else{$tx=1 } # text-only mode (0=no, 1=yes)

 if (not $err){
  if ($bd){
   if (getdata($bd)){
    if ($fr or $to){
     if (allowed($pw,$plyr)){
      ($x,$y)=an2sub($fr);
      ($x2,$y2)=an2sub($to);
      if   ( $x<0){$err="Couldn't understand <i>from</i> part of Move"}
      elsif($x2<0){$err="Couldn't understand <i>to</i> part of Move"}
      elsif (occupied($x,$y) and (colour($x,$y) eq $plyr)){
       $pc=type($x,$y);
       ($dx,$dy)=($x2-$x,$y2-$y);
       if ($dx or $dy){
        if(validMove($x,$y, $dx, $dy, $plyr, $PLAY)){
         if (not collision($x,$y, $x2,$y2, $plyr)){
          state(1);
          $capture=makeMove($fr, $to);
          if (not inCheck($plyr)){
           $mv=recordMove($pc, $fr, $to, $capture);
           if ($pw and not $PASSWD{$plyr}){setpassword($pw,$plyr)}
           if (putdata($bd)){
            notify(other($plyr), $bd, $moveno, $mv, $title);
            $fr=''; $to=''; $pp=''; $plyr=other($plyr)
           }else{$err="System problem! <font size=2>Write to data-file failed ($!)</font>"; state(0)}
          }else{$err="Can't move in check"; state(0)}
         }else{$err="Move obstructed"}
        }else{$err||="Illegal move for ".$PIECE{uc(type($x,$y))}}
       }else{$err="No move made"}
      }else{$err="No $COLOUR{$plyr} piece at $fr"}  
     }else{$err="Sorry, not that password"}
    } # else do nothing, just lookin'
   }else{$err="Board not available right now <font size=2>($!)</font>"; $syserr=1}
  } # else bd=0, show list
 }else{$err="Sorry, can't play chess with you...<br>$err"; $syserr=1}
 printBoard($bd);
 exit
}


#---------------------------------------------------
# datafile returns data filename for this board
#---------------------------------------------------
sub datafile{
 my $bd=shift;
 my $datname=$DATAFNAME;
 $datname=~/(.*)(\.\w+)$/;
 if ($DATAPATH){$datname= $DATAPATH.'/'.$1.substr($bd+1000,-3).$2}
 else {$datname= $1.substr($bd+1000,-3).$2}
}


#---------------------------------------------------
# getdata
# reads the data file and sets everything up
# side-effect: initialises all the global variables
# returns true if everything was OK
#---------------------------------------------------
sub getdata{
 my $bd = shift;
 my ($x,$y,@raw);
 my $datname=datafile($bd);
 open(DAT, "<$datname") or return 0;
 while(<DAT>){
  if   (/^(\w)\w* to move/i){$plyr=lc($1)}
  elsif(/^(b|w|-)/){unshift @raw, [split]}
  elsif(/^captives:(.*)/i){$captives=$1}
  elsif(/^passwd:\s*(b|w)\w*\s+(\S+)/i){$PASSWD{$1}=$2}
  elsif(/^timestamp:\s*(\d+)/i){$timestamp=$1}
  elsif(/^title:(.*)/i){
   $title=$1; $title=~s/^\s*(.*?)\s*$/$1/;
   if ($title=~/^\((.*)\)$/){$hidden++; $title=$1}
  }
  elsif(/^e\-?mail:\s*(b|w)\w*\s([-~\w.]+(\@\w+[-\w.]+\w)?)\s*$/i){$EMAIL{$1}=$2}
  elsif(/^\d+\.\s/){chop; push @moves,$_}
 }
 $moveno=$#moves+1;
 if (not @raw){while (<DATA>){if(/^(b|w|-)/){unshift @raw, [split]}}}
 if(( $xX = $#{$raw[0]}) > $MAX_XY ){$xX=$MAX_XY};
 if(( $yY = $#raw      ) > $MAX_XY ){$yY=$MAX_XY};
 for $x (0..$yY){
  for $y (0..$xX){
   $_=$raw[$x][$y];
   if (/^((--)|(-\+)|((b|w)[prnbqk]))$/i){$board[$y][$x]=$_}
   else {$board[$y][$x]='--'}
  }
 }
 close DAT; 1
}

#---------------------------------------------------
# an2sub
# converts algebraic notation to subscripts e.g. a1 -> (0,0)
# cheekily allows boards up to 16 x 16 (!)
# returns -ve x coord if either are bad
#---------------------------------------------------
sub an2sub{
 my $n = shift;
 my ($x,$y);
 $n=~/^(\w)(\w)$/;
 if(not ($1 and $2)){return (-1,-1)}
 else{
  $_=lc($1); $x=-ord('a')+ord;
  $_=lc($2); $y=/^\d/?$_-1:10-ord('a')+ord;
 }
 if ( $x<0 or $x>$xX or $y<0 or $y>$yY){return (-1,-1)}
 else {return ($x,$y)}
}


#---------------------------------------------------
# allowed
# checks password against the Right Word
# returns true if OK
#---------------------------------------------------
sub allowed{
 my($pw, $plyr)=@_;
 not $PASSWD{$plyr}
 or (crypt($pw, 'bw') eq $PASSWD{$plyr})
}


#---------------------------------------------------
# setpassword
# sets password provided for given player
#---------------------------------------------------
sub setpassword{
 my ($pw, $plyr)=@_;
 $PASSWD{$plyr}=crypt($pw, 'bw');
 if ($err){$err.='<br>'}
 $err.="Remember that password for $COLOUR{$plyr} from now on!"
}


#---------------------------------------------------
# board
# returns contents of square eg wKb
#---------------------------------------------------
sub board{
 my ($x,$y)=@_;
 if ($x<0 or $x>$xX or $y<0 or $y>$yY){return '--'}
 $board[$x][$y]
}


#--------------------------------------------------
# other returns other colour of argument
#--------------------------------------------------
sub other{ $_[0]eq'b'?'w':'b' }

#--------------------------------------------------
# sqcol  returns colour of given square
#--------------------------------------------------
sub sqcol{my ($x,$y)=@_; (($x+$y)%2-1?'b':'w')}

#--------------------------------------------------
# type  returns type of piece at x,y  eg wB -> B
#--------------------------------------------------
sub type{my ($x, $y)=@_; my $p=board($x,$y); $p=~/^.(.)/; $1}

#--------------------------------------------------
# colour  returns colour of piece at x,y eg wB -> w
#--------------------------------------------------
sub colour{my ($x, $y)=@_; my $p=board($x,$y); $p=~/^(.)./; $1}

#--------------------------------------------------
# occupied returns true if there is a piece at x,y
#--------------------------------------------------
sub occupied{ my ($x, $y)=@_; type($x,$y)=~/\w/}

#--------------------------------------------------
# pl  returns 's' if plural, else nothing
#--------------------------------------------------
sub pl{$_[0]==1?'':'s'}

#---------------------------------------------------
# biggest  returns max of absolute pair
#---------------------------------------------------
sub biggest{my($a,$b)=@_;(($a=abs($a))>($b=abs($b)))?$a:$b}

#---------------------------------------------------
# smallest  returns max of absolute pair
#---------------------------------------------------
sub smallest{my($a,$b)=@_;(($a=abs($a))<($b=abs($b)))?$a:$b}

#---------------------------------------------------
# html
# strips out leading tabs (\t) and prints it
# (just to make Perl source a bit more readable)
#---------------------------------------------------
sub html{ $_=shift; s/\t//gm; print}

#---------------------------------------------------
# validMove
# checks proposed move against type of piece
# consider "special" moves only if this is a player's move
# returns true if OK
#---------------------------------------------------
sub validMove{
 my ($x,$y,$dx,$dy,$plyr,$playmv) = @_;
 my $x2=$x+$dx;
 my $y2=$y+$dy;
 my $Pc=type($x,$y);
 my $pc=lc($Pc);
 if ($Pc eq'K' and (abs($dx)==2) and not $dy){
  return unless castling(@_)
 }
 elsif ($pc eq 'k'){
  return unless (biggest($dx,$dy)==1)
 }
 elsif ($pc eq 'q'){
  return unless (not $dx or not $dy or abs($dx)==abs($dy))
 }
 elsif ($pc eq 'b'){
  return unless (abs($dx)==abs($dy))
 }
 elsif ($pc eq 'n'){
  return unless ((abs($dx)==2 and abs($dy)==1)
             or  (abs($dx)==1 and abs($dy)==2))
 }
 elsif ($pc eq 'r'){
  return unless (not($dx and $dy))
 }
 elsif ($pc eq 'p'){
  return unless ((($plyr eq 'w') and ($dy>0))
             or  (($plyr eq 'b') and ($dy<0))); # advance only
  if (($Pc eq 'P') and (abs($dy)==2)){
   return unless pawnFirstMove(@_);
  }else{
   return unless ((biggest($dx,$dy)==1));
   if ($dx){ # must capture
    return unless (occupied($x2,$y2) or enpassant(@_))
   }else{ # mustn't capture
    return if (colour($x2,$y2) eq other($plyr))
   }
   if ($playmv and $y2==($plyr eq 'b'?0:$yY)){return 0 unless promotion(@_)}
  }
 }
 1 # ...a valid move
}


#---------------------------------------------------
# collision
# walks between (x,y) and (x2,y2) seeking collision
#  -checks intermediate squares for any
#  -checks the end square for friendly
# returns true if hit something
#---------------------------------------------------
sub collision{
 my($x,$y, $x2,$y2, $plyr)=@_;
 my $dx=($x2-$x); $dx=$dx?$dx/abs($dx):0;
 my $dy=($y2-$y); $dy=$dy?$dy/abs($dy):0;
 my $hit=0;
 if (lc(type($x,$y)) eq 'n'){ # knights don't collide
  $x=$x2; $y=$y2
 }else{
  $x+=$dx; $y+=$dy;
  while(not ($x==$x2 and $y==$y2)){
   if ($hit=occupied($x,$y)){last}
   $x+=$dx; $y+=$dy;
  }
 }
 if (not $hit and occupied($x,$y)){ # target square
  $hit=(colour($x,$y) eq $plyr)
 }
 return $hit
}


#---------------------------------------------------
# sub castling
# returns true if castling move was OK
# side-effect: loads the @special array
#---------------------------------------------------
sub castling{
 my ($x,$y,$dx,$dy,$plyr,$playmv) = @_;
 return if not $playmv;
 my $x2=$x+$dx;
 my $y2=$y+$dy;
 my $rx=$dx<0?0:$xX;
 my $obstructed=0;
 my $xx;
 $dx=$rx?1:-1;
 if ((type($rx,$y) eq 'R')
 and (colour($rx,$y) eq $plyr)){
  for (smallest($rx-$dx, $x2)..biggest($rx-$dx, $x2)){
   if (occupied($_,$y2)){$obstructed=1; last}
  }
  if (not $obstructed){
   if (inCheck($plyr)){$err="Can't castle out of check";  return}
   state(1);
   $board[$x+$dx][$y]=lc($board[$x][$y]);
   $board[$x][$y]='--';
   if (inCheck($plyr)){$err="Can't castle through check"; state(0); return}
   state(0);
   # other collison/checks carried out as normal
   @special=($rx?'O-O':'O-O-O', 0, $x2-$dx,$y,$plyr.'r', $x2,$y2,$plyr.'k', $rx,$y,'--', $x,$y,'--');
   return 1
  }else{$err="Castling obstructed"}
 }else{$err="Can't castle: need an unmoved rook"}
 0 # castling illegal
}


#----------------------------------------------------
# pawnFirstMove
# returns true if 2-square pawn move was OK
# side-effect: sets @special (marks en passant target with *)
#----------------------------------------------------
sub pawnFirstMove{
 my ($x,$y,$dx,$dy,$plyr,$playmv) = @_;
 return if not $playmv;
 if (not $dx and not (colour($x2,$y2) eq other($plyr))){
  @special=(' ', 0, $x, $y+($dy/2), '-*');
  return 1
 }
 0 # pawn 2-sq move illegal
}


#----------------------------------------------------
# promotion
# returns true if OK (ie we have a promote-to type)
# side effect: set $err (to request promote-to)
#              or load @special array with new piece
# NB! this uses global $pp
#----------------------------------------------------
sub promotion{
 my ($x,$y,$dx,$dy,$plyr,$playmv) = @_;
 return if not $playmv;
 if ($pp!~/^([RNBQ])$/){
  $err=qq|Pawn promotion: select<br>&nbsp;R<input name='pp' type=radio value='R'>&nbsp;N<input name='pp' type=radio value='N'>&nbsp;B<input name='pp' type=radio value='B'>&nbsp;Q<input name='pp' type=radio value='Q' checked>|;
  return 0
 }else{@special=("+=$1", 0, $x+$dx, $y+$dy, $plyr.$1)}
 1 # promotion OK
}


#----------------------------------------------------
# enpassant
# returns true if en passant move was OK
# side-effect: sets @special
#----------------------------------------------------
sub enpassant{
 my ($x,$y,$dx,$dy,$plyr,$playmv) = @_;
 return if not $playmv;
 if ($board[$x+$dx][$y+$dy]=~/\+/){ # +sign marks ep target
  @special=('+ e.p.', 'p', $x+$dx, $y, '--');
  return 1
 }
 0 # en passant illegal
}

#---------------------------------------------------
# inCheck
# returns true if player's king is threatenned
# (actually tests *all* player's kings!)
#---------------------------------------------------
sub inCheck{
 my ($plyr)=shift;
 my ($x, $y);
 my @kings=findPiece($plyr, 'k');
 while (@kings){
  $x=shift(@kings);
  $y=shift(@kings);
  if (inCheckTest($plyr, $x, $y)){return 1}
 }
 0 # not in check
}

#---------------------------------------------------
# inCheckTest
# returns true if player's king is threatenned
# Looks in all straight directions for a collision,
# and sees if that collision is enemy and can take
# If that doesn't work, test for attacking knights
#---------------------------------------------------
sub inCheckTest{
 my ($plyr, $x, $y)=@_;
 my ($dx,$dy,$dz, $x1,$y1);
 for $dy (-1,0,1){
  for $dx (-1,0,1){
   ($x1,$y1)=($x,$y);
   if ($dx or $dy){
    while( $x1>=0 and $x1<=$xX and $y1>=0 and $y1<=$yY ){
     $x1+=$dx; $y1+=$dy;
     if (occupied($x1,$y1)){
      if ((colour($x1,$y1) eq other($plyr))
      and validMove($x1, $y1, $x-$x1, $y-$y1, other($plyr),$PROBE))
       {return 1} # check!
      last
     }
    }
   }
  }
 }
 # test for Knights - look for a knight everywhere it could take from
 for $dy (-1,1){
  for $dx (-1,1){
   for $dz (2,3){
    $x1=$x+$dx*(4-$dz);
    $y1=$y+$dy*($dz-1);
    if ((lc(type($x1,$y1)) eq 'n')
    and (colour($x1,$y1) eq other($plyr)))
     {return 1} # check!
   }
  }
 }
 0 # not in check
}


#----------------------------------------------------
# findPiece
# returns ([x,y , [x1, y1, [x2, y2, [...]]]]) of all
# pieces matching specified colour and type
#----------------------------------------------------
sub findPiece{
 my $pc=lc($_[0].$_[1]);
 my @ret;
 my($x,$y);
 for $y (0..$yY){
  for $x (0..$xX){
   if (lc(board($x,$y)) eq $pc){push @ret, ($x,$y)}
  }
 }
 return @ret
}


#---------------------------------------------------
# state   saves board state (snapshot)
# argument 1 = store, 0 = restore (i.e. revert)
#---------------------------------------------------
sub state{
 my ($x,$y);
 for $y (0..$yY){
  for $x (0..$xX){
   if ($_[0]){$snapshot[$x][$y]=$board[$x][$y]}
   else      {$board[$x][$y]=$snapshot[$x][$y]}
  }
 }
}


#---------------------------------------------------
# makeMove
# makes the move in board data structure from $fr to $to
# returns type of piece taken, or null if no capture
#---------------------------------------------------
sub makeMove {
 my($from,$to)=@_;
 my($x,$y)=an2sub($from);
 my($xt,$yt)=an2sub($to);
 my ($sq, $ret, $capt);
 $ret = occupied($xt,$yt)?lc(type($xt,$yt)):'';
 $board[$xt][$yt]=lc($board[$x][$y]);
 $board[$x][$y]='--';
 if (@special){
  $_=shift @special;
  if ($capt = shift @special){$ret=$capt}
  while(@special){
   ($x,$y,$sq)=splice(@special,0,3);
   $board[$x][$y]=$sq
  }
  @special=$_; # put special move back
 }
 $timestamp=time;
 $ret
}


#---------------------------------------------------
# recordMove  update the list of moves
# use SAN short-form notation
# returns the move string
#---------------------------------------------------
sub recordMove{
 my ($pc, $from, $to, $took)=@_;
 my ($mv, $x1, $y1);
 my ($ambig, $same)=(0,0);
 my $sp=@special?$special[0]:'';
 if ($took){$captives.=' '.other($plyr).$took}
 if ($sp=~/O-O/){$mv=$sp}
 else{ # SAN comliance: short-form ambiguity detector
  my ($x,$y)=an2sub($from);
  my ($x2,$y2)=an2sub($to);
  if ($pc=~/P/i){
   if ($took){ $from=~s/\d//; $from.='x' } else { $from='' }
  }else{ 
   my @similar=findPiece($plyr, $pc);
   while (@similar) {
    $x1=shift @similar;
    $y1=shift @similar;
    if(($x1!=$x2 or $y1!=$y2) # ignore piece itself
    and validMove($x1, $y1, $x2-$x1, $y2-$y1, $plyr, $PROBE)
    and not collision($x1,$y1, $x2,$y2, other($plyr))){
     $ambig++;
     if($x1==$x){ $same++; last }
    }
   }
   $from=~/^(.)(.)/;
   $from=uc($pc);
   if ($ambig){$from.=$1 }
   if ($same){ $from.=$2 }
   if ($took){ $from.='x'} elsif ($ambig){ $from.='-' }
  }
  $mv = "$from$to";
  if ($sp=~/^\+(.*)/){$mv.=$1}
 }
 if (inCheck(other($plyr))){$mv.='+'}
 if ($plyr eq 'b'){
  if (not @moves){push(@moves, ++$moveno.".\t ...")}
  $moves[$#moves].="\t $mv"
 }
 else{push(@moves, ++$moveno.".\t $mv")}
 return $mv
}


#---------------------------------------------------
# putdata
# writes the data to file ready for next move
# returns true if write was OK
#---------------------------------------------------
sub putdata{
 my $bd=shift;
 my $datname=datafile($bd);
 if (not open(DAT, ">$datname")){return 0}
 else{
  print DAT $COLOUR{other($plyr)}." to move\n";
  for $y(0..$yY){
   for $x(0..$xX){
    $_=board($x,$yY-$y); tr/*+/+-/;
    print DAT "$_ "
   }
   print DAT "\n"
  }
  print DAT "\n";
  foreach $_ (@moves){print DAT $_."\n"};
  print DAT "\n";
  if ($hidden){ $title="($title)" }
  print DAT "title: $title\n" if $title;
  print DAT "captives: $captives\n" if $captives;
  print DAT "passwd: w $PASSWD{'w'} \n" if $PASSWD{'w'};
  print DAT "passwd: b $PASSWD{'b'} \n" if $PASSWD{'b'};
  print DAT "email: w $EMAIL{'w'} \n" if $EMAIL{'w'};
  print DAT "email: b $EMAIL{'b'} \n" if $EMAIL{'b'};
  print DAT "timestamp: ".time."\n";
  close DAT;
  return 1 # write OK
 }
}


#---------------------------------------------------
# timelapse
# returns time since last move ("Last move...ago")
# blank if no timestamp or just seconds ago
#---------------------------------------------------
sub timelapse{
 my $timestamp = shift;
 my $timelapse='';
 if ($timestamp and ($t=int((time-$timestamp)/60))){
  $timelapse="<font size=2>Last move ";
  if ($_=int($t/1440)){$timelapse.=" $_ day".pl($_);$t-=($_*1440)};
  if ($_=int($t/60))  {$timelapse.=" $_ hour".pl($_);$t-=($_*60)};
  $_=$t; $timelapse.=" $_ minute".pl($_)." ago</font>"
 }
 $timelapse
}


#---------------------------------------------------
# notify
# sends email notice to the other player
# note: does nothing if $EMAILPROG is null (disabled)
#---------------------------------------------------
sub notify{
 my ($plyr, $bd, $mvno, $mv, $title)=@_;
 if ($EMAILPROG and $EMAIL{$plyr}){
  if ($plyr eq 'w'){$mv=" ... $mv"}
  $_=$title; s/<[^>]*>/ /g; s/\s+/ /g; s/(^ | $)//g; # strip out html
  $title=$_?qq! "$_"!:'';
  if (open (MAIL, "|$EMAILPROG $EMAIL{$plyr}")){
   print MAIL "Subject: [$bd]Chess: $mvno. $mv\n";
   print MAIL "Your opponent (".$COLOUR{other($plyr)}.") just moved on board $bd$title:\n\n";
   foreach (@moves){ print MAIL "$_\n" }
   print MAIL "\n\n$URL/$CGIPATH/$SCRIPT?bd=$bd\n";
   print MAIL "\n-----------------------------------------\nYou have been notified automatically by\nthe BeholderBoard Virtual Chess-set $VER\n-----------------------------------------\n";
   close (MAIL);
   $err="Your move has been e-mailed to $COLOUR{$plyr}. $err"
  }
  else{ $err="Warning: had problems e-mailing $COLOUR{$plyr}. $err" }
 }
}


#---------------------------------------------------------
# getCGIvars
# gets the variables passed over the web
# two important side-effects:
#   loads up the global %form
#   sets $err if there was a problem
#---------------------------------------------------------
sub getCGIvars {
 my ($in, $name, $value);
 if (($ENV{'REQUEST_METHOD'} eq 'GET')
  or ($ENV{'REQUEST_METHOD'} eq 'HEAD') ){
   $in=$ENV{'QUERY_STRING'}
 }
 elsif ($ENV{'REQUEST_METHOD'} eq 'POST'){
  if ($ENV{'CONTENT_TYPE'}=~ m#^application/x-www-form-urlencoded$#i){
   if(length($ENV{'CONTENT_LENGTH'})){
    read(STDIN, $in, $ENV{'CONTENT_LENGTH'})
   }else{$err='POST request'}
  }else{$err='Content-Type: '.$ENV{'CONTENT_TYPE'}}
 }else{$err='REQUEST_METHOD'}
 if ($err){$err="<font size=2>Your browser sent a bad $err</font>"; return}
 foreach (split('&', $in)) {
  s/\+/ /g ;
  ($name, $value)= split('=', $_, 2) ;
  $name=~ s/%(..)/chr(hex($1))/ge ;
  $value=~ s/%(..)/chr(hex($1))/ge ;
  $form{$name}=$value ;
 }
}


#---------------------------------------------------
# printListOfBoards
# produces the html output for the list of available boards
# print summary move info; don't print hidden boards
#---------------------------------------------------
sub printListOfBoards{
 my @boards=();
 my ($board, $bd, $plyr, $timestamp, $title, $graphicL, $graphicR);
 my $here=$DATAPATH?$DATAPATH:'.';
 $DATAFNAME=~/^(.*)(\.\w+)$/;
 my ($prefix,$suffix)=($1,$2);

 if ($tx) {$graphicL='<<<'} else {$graphicL=qq!<img src="/$GRPATH/wqb.gif">!}
 if ($tx) {$graphicR='>>>'} else {$graphicR=qq!<img src="/$GRPATH/bqb.gif">!}
 
 if (opendir DATADIR, $here){
  @boards = sort grep/^$prefix\d\d\d$suffix$/, readdir DATADIR;
  closedir DATADIR
 }
 if (not @boards){push @moves, "No boards are currently set up."}
 else{
  foreach $board (@boards){
   $board=~/^$prefix(\d\d\d)$suffix$/; $bd=$1+0;
   if ($bd){ 
    if (open DAT, "$here/$board"){
     $plyr='w'; $timestamp=$title='';
     while(<DAT>){
      if (/^(b|w)\w* to move/i){$plyr=lc($1)}
      elsif (/^timestamp:\s*(.*)/i){$timestamp=$1}
      elsif(/^title:\s*(.*)/i){$title=$1}
     }
     close DAT;
     if ($title=~/^\(/){next} # hidden board; skip it
     $timelapse=timelapse($timestamp);
     push @moves, qq!<a href="$URL/$CGIPATH/$SCRIPT?bd=$bd&dj=$dj&tx=$tx">Board $bd</a></td><td>$title</td><td>$COLOUR{$plyr} to move<br>$timelapse!;
    }
    else{push @moves, "</td><td colspan=2>Problem <font size=2>$here/$board: $!</font>"}
   }
  }
 }
 html(<<HTML
	<head>
	<title>Chess Boards</title>
	</head>
	<body bgcolor="$BACKCOLOUR" text="$TEXTCOLOUR" link="$LINKCOLOUR" vlink="$LINKCOLOUR" alink="$ALNKCOLOUR">
	<center>

	<table border=0 cellpadding=10>
	<tr>
	 <td valign=top align=right>$graphicL</td>
	 <td valign=top align=center><h3>Current Games</h3><p>Choose your board...</p></td>
	 <td valign=top align=left>$graphicR</td>
	</tr>
	</table>
	
	<table border=1 cellpadding=4 cellspacing=3>
HTML
);
 foreach (@moves){
  print "<tr><td>\n$_\n</td></tr>\n"
 }
 print "</table>\n</center>\n";
}


#---------------------------------------------------
# printBoard
# produces the html output for the board
#---------------------------------------------------
sub printBoard{
 my $bd=shift;
 if (not ($bd or $syserr)){printListOfBoards}
 else{
  my ($y,$x,$isz,$timelapse,$t,$hlch,$hcch,$hmch);
  my $onUnload='';
  if (not $tx){
   if ($err){$err="<tr><td colspan=3 bgcolor=\"$BRDRCOLOUR\">".$err."</td></tr>"}
   else {$err=''}
  };
  $timelapse=timelapse($timestamp);
  $hlch=$hl?'checked':'';
  $hcch=$hc?'checked':'';
  $hmch=$hm?'checked':'';
  $isz=$sz<=50?$sz:50;  # images shrink but never grow > 50 

  print "<head>\n<title>Chess Board</title>\n";

  if (not ($syserr or $dj or $tx)){
   $onUnload=qq!onUnload="killw()"!;
   $err=~s/'/\\'/g; ##########$err=~s/'/'+"'"+'/g;
 html(<<HTML
	<script language="javascript">
	<!--

	 window.name="chessboard";

	 var w = window.open("", "w", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,width=220,height=270")
	 w.document.open()
	 var m = '<html><head><title>Chess Options</title></head>\\n<body bgcolor="$BACKCOLOUR" text="$TEXTCOLOUR">\\n'
	 m+='<form method="post" action="$URL/$CGIPATH/$SCRIPT" target="chessboard">\\n'
	 m+=' <input name="bd" value="$bd" type=hidden>\\n<center>\\n<table border=0 cellpadding=2 cellspacing=2 width=210>\\n'
	 m+=' <tr><td align=right>hide labels</td>\\n <td><input name="hl" type="checkbox" value="1" $hlch>\\n</td><td rowspan=3 align=right>\\n<input type="submit" value="Update"></td></tr>\\n'
	 m+=' <tr><td align=right>hide captives</td>\\n <td><input name="hc" type="checkbox" value="1" $hcch></td>\\n</tr>\\n'
	 m+=' <tr><td align=right>hide moves</td>\\n <td><input name="hm" type="checkbox" value="1" $hmch></td>\\n</tr>\\n'
	 m+=' <tr><td colspan=2 align=right>square size</td>\\n <td><input name="sz" value=$sz size=3 maxlength=3></td>\\n</tr>\\n'
	 m+=' <tr><td colspan=3><hr></td></tr>\\n'
	 m+=' $err\\n'
	 m+=' <tr><td align=right>$COLOUR{$plyr} to move<br><font size=2>e.g. e2-e4</font></td>\\n <td colspan=2><input name="fr"  value="$fr" size=2 maxlength=2> - <input name="to" value="$to" size=2 maxlength=2></td></tr>\\n'
	 m+=' <tr><td align=right><font size=2>move requires password</font></td>\\n <td colspan=2><input name="pw" type="password" size=8 maxlength=16></td>\\n</tr>\\n'
	 m+=' <tr><td colspan=3>$timelapse</td></tr>\\n'
	 m+='</table>\\n</center>\\n</form>\\n'
	 m+='</body>\\n</html>\\n'
	 w.document.write(m)
	 w.document.close()
	 w.focus()

	function killw() {
	 if (window.w){
	  w = window.open("", "w", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,width=220,height=270")
	  w.document.open()
	  w.document.write("<html><head></head><body bgcolor='$BACKCOLOUR' onLoad='self.blur();setTimeout(\\"self.close()\\",500)'></body></html>")
	  w.document.close()
	 } 
	}
	//-->
	</script>
HTML
)}

 if ($tx) { printTextOnlyBoard($bd) }
 else{
 	
  html(<<HTML
	</head>
	<!-- HTML autogenerated by  board.pl $VER (c) Beholder 1999 -->
	<body bgcolor="$BACKCOLOUR" text="$TEXTCOLOUR" link="$LINKCOLOUR" vlink="$LINKCOLOUR" alink="$ALNKCOLOUR" $onUnload>
	<h3>$title</h3></b>
	<pre>


	</pre>
	<center>
	<table cellpadding=0 cellspacing=0 border=0>
	<tr>
	 <td valign=top align=center>

	<table cellpadding=0 cellspacing=0 border=0>
	<tr>
	 <td valign=center align=center>
HTML
   );
    if (not $hl){
    html(<<HTML
	  <table bgcolor=#115511 border=0 cellspacing=0 cellpadding=2>
	   <tr>
	    <td valign=center align=center>
	     <table border=0 cellspacing=1 cellpadding=0>
	      <tr><td width=$sz height=3></td></tr>
HTML
   );
   for ($y=$yY; $y>=0; $y--){$_=$y<9?$y+1:chr(ord('a')+$y-9); print  qq!      <tr><td align=center valign=center width=$sz height=$sz>$_</td></tr>\n!}
    html(<<HTML
	      <tr><td width=$sz height=3></td></tr>
	     </table>
	    </td>
	   </tr>
	  </table>
HTML
   )}
   html(<<HTML
	 </td>
	 <td>
	  <table bgcolor="$BRDRCOLOUR" border=2 cellspacing=0 cellpadding=2>
	   <tr>
	    <td valign=center align=center>
	     <table border=1 cellspacing=0 cellpadding=0>
HTML
   );
   for $y (0..$yY){
    print  "      <tr>\n";
    for $x (0..$xX){
     $sq=sqcol($x,$yY-$y);
     print  "       <td width=$sz height=$sz bgcolor=\"#", ($sq eq'b'?'5':'f')x 6, '"';
     $_=$syserr?'':lc(board($x,$yY-$y));
     if (/\w/){print qq!align=center valign=center><img src="/$GRPATH/$_$sq.gif" width=$isz height=$isz>!}
     else{print '><font size=1>&nbsp;</font>'}
     print  "</td>\n"
    }
    print  "      </tr>\n";
   }
   html(<<HTML
	     </table>
	    </td>
	   </tr>
	  </table>
	 </td>
	 <td width=$sz>&nbsp;</td>
	</tr>
	<tr>
	<td></td>
	<td>
HTML
   );
   if (not $hl){
    html(<<HTML
	 <table bgcolor=#115511 border=0 cellspacing=0 cellpadding=2>
	  <tr>
	   <td valign=center align=center>
	    <table border=0 cellspacing=2 cellpadding=0>
	     <tr>
	      <td width=2 height=$sz></td>
HTML
   ); for $x (0..$xX){$_=chr(ord('a')+$x); print qq!      <td align=center valign=center width=$sz height=$sz>$_</td>\n!}
   html(<<HTML
	      <td width=2 height=$sz></td>
	     </tr>
	    </table>
	   </td>
	  </tr>
	 </table>
HTML
   )}

  html(<<HTML
	</td>
	</tr>
	<tr>
	 <td colspan=3 align=left>
HTML
  );
  if (not $hc){
   print "  <br><br>\n";
   while ( $captives=~/\s+w(\w)/g ){
    print  qq!  <img src="/$GRPATH/w$1b.gif" width=$sz height=$sz>\n!
   }
  }
  html(<<HTML
	 </td>
	</tr>
	<tr>
	 <td colspan=3 align=left>
HTML
  );
  if (not $hc){
   while ( $captives=~/\s+b(\w)/g ){
    print  qq!  <img src="/$GRPATH/b$1b.gif" width=$sz height=$sz>\n!
   }
  }
  html(<<HTML
	 </td>
	</tr>
	</table>
	</td>
	<td valign=top align=center>
HTML
  );
  if ((not $hm) and $moves[0]){
   print  " <table cellpadding=5 cellspacing=0 border=1>\n";
   foreach $_ (@moves){
    /(\d+.)\s+([^\t]+)*(?:\Z|\t(.*))/;
    print  "  <tr><td>$1</td><td>$2</td><td>$3</td></tr>\n"
   }
   print  " </table>\n"
  }
  print "</td>\n</tr>\n</table>\n</center>\n";
  $err=~s/colspan=3/colspan=5/i;
  if ($dj or $syserr){
   html(<<HTML
	<br><br>
	<center>

	<table  border=1 cellspacing=0 cellpadding=4>
	 <tr>
	  <td valign=center align=center>
	   <table border=0 cellpadding=2 cellspacing=4 >
	    $err
HTML
   );
   if (not $syserr){ # only offer form if the board is OK
    html(<<HTML_Form
	    <tr>
	     <td rowspan=2 align=right valign=bottom>
	      <form method="post" action="$URL/$CGIPATH/$SCRIPT">
	      <input name="bd" value="$bd" type=hidden>
	      <input name="dj" value="$dj" type=hidden>
      	      <input name="tx" value="$tx" type=hidden>
	      $COLOUR{$plyr} to move<br><font size=2>e.g. e2-e4</font>
	     </td>
	     <td rowspan=2 valign=bottom><input name="fr"  value="$fr" size=2 maxlength=2> - <input name="to" value="$to" size=2 maxlength=2></td>
	     <td align=right>hide&nbsp;labels</td>
	     <td><input name="hl" type="checkbox" value="1" $hlch></td>
	     <td rowspan=4 align=center><input type="submit" value="Update"></td>
	    </tr>
	    <tr>
	     <td align=right>hide&nbsp;captives</td>
	     <td><input name="hc" type="checkbox" value="1" $hcch></td>
	    </tr>
	    <tr>
	     <td rowspan=2 align=right valign=top><font size=2>move requires<br>password</font></td>
	     <td rowspan=2 valign=top><input name="pw" type="password" size=8 maxlength=16></td>
	     <td align=right>hide&nbsp;moves</td>
	     <td><input name="hm" type="checkbox" value="1" $hmch></td>
	    </tr>
	    <tr>
	     <td align=right valign=top>square&nbsp;size</td>
	     <td valign=top><input name="sz" value=$sz size=3 maxlength=3></form></td>
	    </tr>
	    <tr>
	     <td colspan=5 align=center>$timelapse</td>
	    </tr>
HTML_Form
   )}
   html(<<HTML
	   </table>
	  </td>
	 </tr>
	</table>
	</center>
HTML
    )
   }
  }
 }
  &printFooter
}

#---------------------------------------------------
# printFooter
# slaps HTML footer onto bottom of page
#---------------------------------------------------
sub printFooter{
 my $listOption=$bd?qq!| <a href="$URL/$CGIPATH/$SCRIPT?bd=0&dj=$dj&tx=$tx">List Boards</a>!:'';
 my $javaOption;
 my ($js, $ts);
 if ($dj){$js='0">Enable'}
 else    {$js='1">Disable'}
 if ($tx){$ts='0">Show Graphics'}
 else    {$ts='1">Text Only'}
 $javaOption=$tx?'':qq!| <a href="$URL/$CGIPATH/$SCRIPT?bd=$bd&hm=$hm&hc=$hc&hl=$hl&sz=$sz&tx=0&dj=$js Javascript</a>!;

 html(<<HTML
	<pre>\n\n\n\n\n</pre>\n
	<font size=2>
	<a href="$URL/$CGIPATH/$SCRIPT?bd=$bd&hm=$hm&hc=$hc&hl=$hl&sz=$sz&dj=$dj&tx=$ts</a>
        $javaOption
	$listOption
	<br>
	<hr>
	<b>The BeholderBoard Virtual Chess-set</b><br>
	bug reports or grunts of approval to <a href="mailto:cartoons\@beholder.co.uk">cartoons\@beholder.co.uk</a><br>
	<a href="http://www.beholder.co.uk">www.beholder.co.uk</a><br>
	</font>
	</body>
	</html>
HTML
)
}



#---------------------------------------------------
# printTextOnlyBoard
# text only, for those Lynx people out there
#---------------------------------------------------
sub printTextOnlyBoard{
 my $bd = shift;
 my ($x,$y);
 my ($timelapse, $hlch, $hcch, $hmch);
  $timelapse=timelapse($timestamp);
  $hlch=$hl?'checked':'';
  $hcch=$hc?'checked':'';
  $hmch=$hm?'checked':'';
 print"<body>\n<tt><i>BeholderBoard</i> Text-Only output:\n<h3>$title</h3>\n";
 if ($syserr){print $err}
 else{
  print "<pre>\n\n";
  foreach $y (reverse 0..$yY){
   print $hl?"   ":substr("  ".($y<9?$y+1:chr(ord('a')+$y-9))."|",-3);
   foreach $x (0..$xX){
    print ' ', occupied($x,$y)? lc(colour($x,$y)).uc(type($x,$y)):'--';
   }
   print "\n";
  }
  if (not $hl){
   print   "   "; foreach $x (0..$xX){print "___";}   	
   print "\n   "; foreach $x (0..$xX){print substr("  ".chr(ord('a')+$x),-3);}
  }
  print "\n\n";
  print"</pre>\n";
  if (not $syserr){
   html(<<HTML_FORM
	<hr>
	$err<br>
	<form method="post" action="$URL/$CGIPATH/$SCRIPT">
	<input name="bd" value="$bd" type=hidden>
	<input name="dj" value="$dj" type=hidden>
	<input name="tx" value="$tx" type=hidden>
	<pre>
	              $COLOUR{$plyr} to move
	              $timelapse
	         from <input name="fr"  value="$fr" size=2 maxlength=2> to <input name="to" value="$to" size=2 maxlength=2> <font size=2>(e.g. e2 - e4)</font>
	     password <input name="pw" type="password" size=8 maxlength=16> <font size=2>(required for moves)</font>
	  hide labels <input name="hl" type="checkbox" value="1" $hlch>
	   hide moves <input name="hm" type="checkbox" value="1" $hmch>
	hide captives <input name="hc" type="checkbox" value="1" $hcch>
	              <input type="submit" value="Update">
	</pre></form>
	<hr>
HTML_FORM
   );
    if (not $hc){
   print "<br>\nWhite has captured: "; while ( $captives=~/\s+b(\w)/g ){print uc(" $1")};
   print "<br>\nBlack has captured: "; while ( $captives=~/\s+w(\w)/g ){print uc(" $1")};
  }
   if (not $hm){print "<pre>\n\nMove List:\n\n"; foreach (@moves){print "$_\n"} print"</pre>"}
   print"</tt>"
  }
 }
}

# start position data follows:

__END__
bR bN bB bQ bK bB bN bR
bP bP bP bP bP bP bP bP
-- -- -- -- -- -- -- --
-- -- -- -- -- -- -- --
-- -- -- -- -- -- -- --
-- -- -- -- -- -- -- --
wP wP wP wP wP wP wP wP
wR wN wB wQ wK wB wN wR

