DIR : /home/kozerus/public_html/go/dgs/include/move.php

/home/kozerus/public_html/go/dgs/include

<?php
/*
Dragon Go Server
Copyright (C) 2001-  Erik Ouchterlony, Rod Ival, Jens-Uwe Gaspar

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

//$TranslateGroups[] = "Game";

require_once 'include/game_functions.php';



/*!
 * \class GameCheckMove
 *
 * \brief helper-class to handle moving of game.
 * \note main-purpose was to avoid globals.
 */
class GameCheckMove
{
   private $board;

   private $black_prisoners;
   private $white_prisoners;
   public $prisoners;
   public $nr_prisoners;
   public $colnr;
   public $rownr;

   private $replay_last_move = ''; // SGF-coords (used for replaying)
   private $replay_game_flags = 0; // GAMEFLAGS_KO

   // tracking of last-color/move for handling of cond-moves
   private $replay_last_color = BLACK; // BLACK | WHITE
   private $replay_last_sgf_move = -1; // SGF-coords


   public function __construct( Board &$board )
   {
      $this->board = $board;
   }

   public function get_replay_last_color()
   {
      return $this->replay_last_color;
   }

   public function get_replay_last_sgf_move()
   {
      return $this->replay_last_sgf_move;
   }

   /*! Take over board-array in this GameCheckMove-instance into passed Board-instance. */
   public function assign_board_array( Board &$board )
   {
      $board->array = $this->board->array;
   }

   /*!
    * \brief Checks move on given board adjusting black/white-prisoners.
    * \param $coord can be arr(x,y) or one sgf-coord (e.g. 'aa')
    * \param $last_move sgf-coord of last-move (e.g. 'aa')
    * \return '' (=no error); the following only if $error_exit is false:
    *       'illegal_position' (=invalid-coord), 'ko', 'suicide'
    *
    * \note adjusted globals: $Black_Prisoners, $White_Prisoners
    * \note sets $this->prisoners with array of the captured stones of play (or suicided stones if, a day, $suicide_allowed==true)
    * \note sets $this->nr_prisoners, colnr/rownr (of given $coord)
    */
   public function check_move( $coord, $to_move, $last_move, $game_flags, $error_exit=true )
   {
      $this->black_prisoners = $this->white_prisoners = $this->nr_prisoners = 0;
      $this->prisoners = array();

      $Size = $this->board->size;
      $array = &$this->board->array;

      list($colnr,$rownr) = (is_array($coord)) ? $coord : sgf2number_coords($coord, $Size);
      $this->colnr = $colnr;
      $this->rownr = $rownr;

      if ( !isset($rownr) || !isset($colnr) || @$array[$colnr][$rownr] != NONE )
      {
         if ( $error_exit )
            error('illegal_position', "GCM.check_move({$this->board->gid})");
         else
            return 'illegal_position';
      }
      $array[$colnr][$rownr] = $to_move;

      $this->board->check_prisoners( $colnr, $rownr, WHITE+BLACK-$to_move, $this->prisoners );

      $this->nr_prisoners = count($this->prisoners);
      if ( $this->nr_prisoners == 0 )
      {
         // Check for suicide
         $suicide_allowed = false;
         if ( !$this->board->has_liberty_check( $colnr, $rownr, $this->prisoners, $suicide_allowed) )
         {
            if ( !$suicide_allowed )
            {
               if ( $error_exit )
                  error('suicide', "GCM.check_move({$this->board->gid})");
               else
                  return 'suicide';
            }
         }
         return ''; // Ok, all tests passed.
      }

      // note: $game_flags (Games.Flags) has set Ko-flag if last move has taken a single stone
      if ( $this->nr_prisoners == 1 && ($game_flags & GAMEFLAGS_KO) )
      {
         // Check for ko
         list($x,$y) = $this->prisoners[0];
         if ( $last_move == number2sgf_coords( $x, $y, $Size) )
         {
            if ( $error_exit )
               error('ko', "GCM.check_move({$this->board->gid})");
            else
               return 'ko';
         }
      }

      if ( $to_move == BLACK )
         $this->black_prisoners = $this->nr_prisoners;
      else
         $this->white_prisoners = $this->nr_prisoners;

      return ''; // Ok, all tests passed.
   }//check_move

   /*! \brief Updates black/white prisoners with diff generated by check_move()-func. */
   public function update_prisoners( &$black_pr, &$white_pr )
   {
      $black_pr += $this->black_prisoners;
      $white_pr += $this->white_prisoners;
   }//update_prisoners


   /*!
    * \brief Replays moves on board up to given move-position.
    * \param $arr_moves arr( [stone,x,y], ... ) like Board->moves
    * \param $last_move_nr last-move that should be played
    * \note ignore prisoners-counting
    */
   public function replay_moves( array $arr_board_moves, $last_move_nr )
   {
      $this->init_replay_moves();

      foreach ( $arr_board_moves as $move_nr => $arr_move )
      {
         if ( !is_numeric($move_nr) )
            continue;
         if ( $move_nr > $last_move_nr )
            break;

         $err = $this->replay_move( $arr_move );
      }
   }//replay_moves

   public function init_replay_moves()
   {
      $this->replay_last_move = '';
      $this->replay_game_flags = 0;

      $this->replay_last_color = BLACK;
      $this->replay_last_sgf_move = -1;
   }

   /*!
    * \brief Replays single move on board (PASS-move is skipped).
    * \param $move array( BLACK|WHITE, x, y ) with x=POSX_PASS for PASS-move;
    *       or sgf-coord "<COL><SGF_COORD|''>" e.g. 'Baa', 'W'
    * \return error-string from this->check_move(); or else '' on successfully played move
    */
   public function replay_move( $move )
   {
      static $MAP_COLORS = array( 'B' => BLACK, 'W' => WHITE );
      if ( is_array($move) )
      {
         list( $stone, $x, $y ) = $move;
         $sgf_move = ( $x <= POSX_PASS ) ? '' : number2sgf_coords( $x, $y, $this->board->size );
      }
      else
      {
         $stone = @$MAP_COLORS[$move[0]];
         $sgf_move = substr($move, 1);
         if ( $sgf_move )
            list( $x, $y ) = sgf2number_coords( $sgf_move, $this->board->size );
         else
            $x = POSX_PASS;
      }

      if ( $x < POSX_PASS || ($stone != BLACK && $stone != WHITE) )
         $err = ''; // keep last-move/game-flags for non-move
      elseif ( $x == POSX_PASS ) // PASS
      {
         $err = ''; // keep last-move/game-flags on PASS-move for ko-check
         $this->replay_last_color = $stone;
         $this->replay_last_sgf_move = '';
      }
      else // move
      {
         $err = $this->check_move( array($x,$y), $stone, $this->replay_last_move, $this->replay_game_flags, /*exit*/false );
         if ( !$err )
         {
            if ( $this->nr_prisoners == 1 )
               $this->replay_game_flags |= GAMEFLAGS_KO;
            else
               $this->replay_game_flags &= ~GAMEFLAGS_KO;

            $this->replay_last_move = $sgf_move;

            $this->replay_last_color = $stone;
            $this->replay_last_sgf_move = $sgf_move;
         }
      }

      return $err;
   }//replay_move

   public function __clone()
   {
      $this->board = clone $this->board;
   }


   // ------------ static functions ----------------------------

   /*! \brief Prepares a new GameCheckMove-instance with given board-size and initial shape-setup. */
   public static function prepare_game_check_move_board_start( $gid, $size, $shape_snapshot )
   {
      $board = new Board( $gid, $size ); // need board to really "move" (with capturing stones)
      $board->init_board();

      if ( $shape_snapshot )
      {
         $arr_xy = GameSnapshot::parse_stones_snapshot( $size, $shape_snapshot, BLACK, WHITE );
         if ( count($arr_xy) )
         {
            foreach ( $arr_xy as $arr_setup )
            {
               list( $Stone, $PosX, $PosY ) = $arr_setup;
               $board->array[$PosX][$PosY] = $Stone;
            }
         }
      }

      $gchkmove = new GameCheckMove( $board );
      return $gchkmove;
   }//prepare_game_check_move_board_start

} // end 'GameCheckMove'



/*!
 * \brief Places handicap stones adjusted board-array.
 * \param $board Board-object, is modified with setting black handicap stones
 * \param $stonestring coordinate-list of handicap-stones to place
 * \param $coord if != false contains SGF-coord to add black-stone to stone-string
 * \return new list of strone-string with coords of stones
 */
function check_handicap( Board &$board, $stonestring, $coord = false )
{
   $Size= $board->size;
   $array= &$board->array;

   // add handicap stones to array
   $l = strlen( $stonestring );
   for ( $i=0; $i < $l; $i += 2 )
   {
      list($colnr,$rownr) = sgf2number_coords(substr($stonestring, $i, 2), $Size);
      if ( !isset($rownr) || !isset($colnr) || @$array[$colnr][$rownr] != NONE )
         error('illegal_position', "check_handicap.1({$board->gid},$colnr,$rownr,$stonestring)");

      $array[$colnr][$rownr] = BLACK;
   }

   if ( $coord )
   {
      list($colnr,$rownr) = sgf2number_coords($coord, $Size);
      if ( !isset($rownr) || !isset($colnr) || @$array[$colnr][$rownr] != NONE )
         error('illegal_position', "check_handicap.2({$board->gid},$colnr,$rownr,$stonestring)");

      $array[$colnr][$rownr] = BLACK;
      $stonestring .= $coord;
   }

   return $stonestring;
}//check_handicap




/*!
 * \class GameCheckScore
 *
 * \brief helper-class to handle scoring of game.
 * \note main-purpose was to avoid globals.
 */
class GameCheckScore
{
   private $board;
   private $stonestring;
   private $handicap;
   private $komi;
   private $black_prisoners;
   private $white_prisoners;
   private $toggle_unique = false;

   public function __construct( Board &$board, $stonestring, $handicap, $komi, $black_prisoners, $white_prisoners )
   {
      $this->board = $board;
      $this->stonestring = (@$stonestring) ? $stonestring : '';
      $this->handicap = $handicap;
      $this->komi = $komi;
      $this->black_prisoners = $black_prisoners;
      $this->white_prisoners = $white_prisoners;
   }

   public function set_toggle_unique()
   {
      $this->toggle_unique = true;
   }

   /*!
    * \brief Checks removal of stones by toggling stones.
    * \param $ruleset RULESET_...
    * \param $coord false to just treat stonestring; single sgf-coord or array of sgf-coords to toggle
    * \return GameScore-object
    */
   public function check_remove( $ruleset, $coords=false, $with_board_status=false )
   {
      $Size = $this->board->size;
      $array =& $this->board->array;

      // toggle marked stones and marked dame to array

      // $stonearray is used to cancel out duplicates, in order to make $stonestring_loc shorter.
      $stonearray = array();

      $l = strlen($this->stonestring);
      for ( $i=0; $i < $l; $i += 2 )
      {
         list($colnr,$rownr) = sgf2number_coords(substr($this->stonestring, $i, 2), $Size);
         if ( !isset($rownr) || !isset($colnr) )
            error('illegal_position', "GCS.check_remove.move4($colnr,$rownr)");

         $stone = isset($array[$colnr][$rownr]) ? $array[$colnr][$rownr] : NONE ;
         if ( $stone == BLACK || $stone == WHITE || $stone == NONE ) //NONE for MARKED_DAME
            $array[$colnr][$rownr] = $stone + OFFSET_MARKED;
         else if ( $stone == BLACK_DEAD || $stone == WHITE_DEAD || $stone == MARKED_DAME )
            $array[$colnr][$rownr] = $stone - OFFSET_MARKED;

         if ( !isset($stonearray[$colnr][$rownr]) )
            $stonearray[$colnr][$rownr] = true;
         else
            unset( $stonearray[$colnr][$rownr] );
      }

      if ( $coords )
      {
         if ( !is_array($coords) )
            $coords = array( $coords );

         $toggled = ( $this->toggle_unique ) ? array() : NULL;
         foreach ( $coords as $coord )
         {
            list($colnr,$rownr) = sgf2number_coords($coord, $Size);
            if ( !isset($rownr) || !isset($colnr) )
               error('illegal_position', "GCS.check_remove.move5($colnr,$rownr)");

            $stone = isset($array[$colnr][$rownr]) ? $array[$colnr][$rownr] : NONE ;
            if ( MAX_SEKI_MARK<=0 || ($stone!=NONE && $stone!=MARKED_DAME) )
            {
               if ( $stone!=BLACK && $stone!=WHITE && $stone!=BLACK_DEAD && $stone!=WHITE_DEAD )
                  error('illegal_position', "GCS.check_remove.move6($colnr,$rownr,$stone)");
            }

            $marked = array();
            $this->board->toggle_marked_area( $colnr, $rownr, $marked, $toggled );

            foreach ( $marked as $sub )
            {
               list($colnr,$rownr) = $sub;
               if ( !isset( $stonearray[$colnr][$rownr] ) )
                  $stonearray[$colnr][$rownr] = true;
               else
                  unset( $stonearray[$colnr][$rownr] );
            }
         }
      }

      $this->stonestring = '';
      foreach ( $stonearray as $colnr => $sub )
      {
         foreach ( $sub as $rownr => $dummy )
            $this->stonestring .= number2sgf_coords($colnr, $rownr, $Size);
      }

      $game_score = new GameScore( $ruleset, $this->handicap, $this->komi );
      $game_score->set_prisoners_all( $this->black_prisoners, $this->white_prisoners );
      $this->board->fill_game_score( $game_score, /*coords*/false, $with_board_status );

      return $game_score;
   }//check_remove

   /*! \brief Update stonestring after check_remove()-call. */
   public function update_stonestring( &$stonestr )
   {
      $stonestr = $this->stonestring;
   }

} // end 'GameCheckScore'

?>
//


koh5_pano



Your browser does not support the HTML5 canvas element.


Drag mouse to navigate.

Navigation





17.Aug.2010, Martin Wengenmayer