DIR : /home/kozerus/public_html/dgs/include/classlib_goban.php
/home/kozerus/public_html/dgs/include
<?php
/*
Dragon Go Server
Copyright (C) 2001- Erik Ouchterlony, 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/>.
*/
require_once 'include/coords.php';
require_once 'include/goban_handler_sl.php';
require_once 'include/goban_handler_gfx.php';
/* Author: Jens-Uwe Gaspar */
/*!
* \file classlib_goban.php
*
* \brief Classes to support formatting of DGS go-boards.
*/
/*!
* \class MarkupHandlerGoban
* \brief Static helper class managing parsing and formatting of <igoban...>-tag.
*/
class MarkupHandlerGoban
{
// register of all known GobanHandlers, each must implement the interface-methods
// - constructor( $args_map )
// - Goban read_goban( $text )
// - String write_goban( Goban )
private static $ARR_GOBAN_HANDLERS_READER = array(
// uppercase(igoban_type) => GobanHandler-class
'SL1' => 'GobanHandlerSL1',
);
private static $ARR_GOBAN_HANDLERS_WRITER = array(
// uppercase(Goban-Writer-type) => GobanHandler-class
'GFX' => 'GobanHandlerGfxBoard',
);
// ---------- Static Class functions ----------------------------
/*! \brief Replaces igoban-tags with graphical-board. */
public static function replace_igoban_tags( $text )
{
$repl_text = self::replace_igoban_tags_collect_goban( $text );
return $repl_text[0];
}
/*!
* \brief Replaces igoban-tags with graphical-board but also collecting parsed Goban-objects.
* \return array( replaced tag-string, array of parsed Goban-objects )
*/
public static function replace_igoban_tags_collect_goban( $text )
{
$arr_gobans = array();
//<igoban [t|type=]type [...]> => show inline-goban
$repl_text = preg_replace( "/<igoban +([\w\s=]+) *>(.*?)<\/igoban *>/ise",
"MarkupHandlerGoban::parse_igoban('\\0','\\1','\\2',\$arr_gobans,'GFX')", $text );
return array( $repl_text, $arr_gobans );
}//replace_igoban_tags_collect_goban
/*!
* \brief Returns true, if given text contains a Go-diagram (igoban-tag);
* then ConfigBoard must be loaded for woodcolor/stone-size etc.
*/
public static function contains_goban( $text )
{
return preg_match( "/<igoban +([\w\s=]+) *>(.*?)<\/igoban *>/ise", $text );
}
/*!
* \brief Parses <igoban $attbs>..</igoban> tags into display of a go-board using DGS-images.
* \param $rawtext original igoban-tag-match
* \param $attbs attribute-string of <igoban>-tag with syntax, separated by spaces: (k=v|v)*
* \param $text payload between start- and end-igoban-tags, can include LFs
*/
private static function parse_igoban( $rawtext, $attbs, $text, array &$arr_gobans, $writer_type = 'GFX' )
{
// attributes: key=value
$map_args = self::attribute_split( $attbs );
// parse igoban-type (1st attribute); no type-default
$igoban_type = self::extract_igoban_type( $map_args );
if ( is_null($igoban_type) )
return $rawtext;
// NOTE: displaying text with bad igoban-tag must not redirect to error-page by throwing error() here
$readerClass = @self::$ARR_GOBAN_HANDLERS_READER[strtoupper($igoban_type)];
$writerClass = @self::$ARR_GOBAN_HANDLERS_WRITER[strtoupper($writer_type)];
if ( !$readerClass || !$writerClass )
return $rawtext;
$goban_reader = new $readerClass( $map_args );
$goban = $goban_reader->read_goban( $text );
$arr_gobans[] = $goban;
$goban_writer = new $writerClass( $rawtext );
$result = $goban_writer->write_goban( $goban );
return $result;
}//parse_igoban
// Extracts type from: <igoban [t|type=]...>
private static function extract_igoban_type( array $args )
{
if ( isset($args['#1']) ) // first arg
return $args['#1'];
elseif ( isset($args['type']) )
return $args['type'];
elseif ( isset($args['t']) )
return $args['t'];
else
return null;
}//extract_igoban_type
/*!
* \brief Returns attribute-string parsed into map( key => value ).
* \note: map additionally contains entries: '#n' => value, with '#n' arg-pos in attbs starting with 1
*/
public static function attribute_split( $attbs )
{
$arr = preg_split( "/\s+/", trim($attbs) );
$result = array();
$arg_idx = 0;
foreach ( $arr as $part )
{
if ( $part === '' ) continue;
$arg_idx++;
if ( preg_match( "/^([^=]+)=(\S*)$/", $part, $matches) )
$result["#$arg_idx"] = $result[$matches[1]] = $matches[2];
else
$result["#$arg_idx"] = $part;
}
return $result;
}//attribute_split
} //end 'MarkupHandlerGoban'
// Goban (4 bits, single bits):
// board-lines at board-position (x/y), empty=0, MID=N|S|W|E
define('GOBB_BITMASK', 0x000F);
define('GOBB_EMPTY', 0x0000); // empty layer = empty intersection
define('GOBB_NORTH', 0x0001); // north-line
define('GOBB_SOUTH', 0x0002); // south-line
define('GOBB_WEST', 0x0004); // west-line
define('GOBB_EAST', 0x0008); // east-line
define('GOBB_MID', (GOBB_NORTH|GOBB_SOUTH|GOBB_WEST|GOBB_EAST)); // middle board-position
// Goban (special other layer)
define('GOBO_HOSHI', 0x0080); // hoshi-bit
// Goban (3 bits, combined bitset): stone-color
define('GOBS_BITMASK', 0x0070);
define('GOBS_EMPTY', 0x0000); // empty layer = no stone
define('GOBS_BLACK', 0x0010); // black
define('GOBS_WHITE', 0x0020); // white
//define('GOBS_..', 0x0030..0x0070); // reserved
// Goban (4 bits, single + combined bitset): markers
define('GOBM_BITMASK', 0x0F00);
define('GOBM_EMPTY', 0x0000); // empty layer = no marker
// - mutual exclusive markers (no bitmask)
define('GOBM_NUMBER', 0x0100); // 1..n
define('GOBM_LETTER', 0x0200); // a..z
define('GOBM_MARK', 0x0300); // double circle
define('GOBM_CIRCLE', 0x0400);
define('GOBM_SQUARE', 0x0500); // unfilled
define('GOBM_TRIANGLE', 0x0600); //
define('GOBM_CROSS', 0x0700); //
define('GOBM_TERR_W', 0x0800); // white (filled) box = white territory
define('GOBM_TERR_B', 0x0900); // black (filled) box = black territory
define('GOBM_TERR_NEUTRAL', 0x0A00); // green (filled) box = neutral territory
define('GOBM_TERR_DAME', 0x0B00); // red (filled) box = dame territory
//define('GOBM_..', 0x0B00..0x0F00); // reserved
// internal
define('GOBMATRIX_VALUE', 'V' ); // layer-value
define('GOBMATRIX_LABEL', 'L' ); // label for GOBM_NUMBER|LETTER
/*!
* \class Goban
* \brief Container for storing a board used for go-diagrams.
*
* \note A position on a goban consists of four "layers":
* 1. the background (image or nothing), global config
* 2. the board lines
* 3. stone or no-stone (colored)
* 4. markers
* The layers can be independently used.
* Rendering a Goban-object need to choose a "representation" for all the combined layers.
* This can result in a mapping where not all combinations make sense or
* can be mapped to something, and so can lead to an loss of information.
*/
class Goban implements StoneReader
{
/*! \brief bitmask using GOBB_NORTH|SOUTH|WEST|EAST enabling coordinates on that side of the go-board. */
public $opts_coords = 0;
public $show_coords = true; // true to show coordinates
/*!
* \brief Board matrix
* \note Format (x/y=1..):
* [y][x] = int-bitmask |
* = array( layer => int-bitmask, label => number|letter )
*/
private $matrix = array();
public $max_x = 1; // 1..max_x/y really HAS something on board, can be partial board of size_x/y
public $max_y = 1;
public $size_x = 1;
public $size_y = 1; // for starting y-coordinates-label
public $BoardTitle = null;
public $BoardText = null;
public $BoardTextInline = true; // true = float text on right of diagram, false = text below diagram
public $BoardLinks = array(); // [ label => link, ... ]
/*! \brief Returns String-representation of most important fields of this object. */
public function to_string()
{
$out = array();
$out[] = sprintf("opts_coords=0x%x", $this->opts_coords);
for ($y=1; $y <= $this->max_y; $y++)
{
for ($x=1; $x <= $this->max_x; $x++)
{
$arr = $this->getValue( $x, $y );
$val_text = self::value_to_string( $arr[GOBMATRIX_VALUE] );
$out[] = sprintf("[%2d,%2d]: V=%03x L=%1s ( %s )", $x,$y, $arr[GOBMATRIX_VALUE], $arr[GOBMATRIX_LABEL], $val_text );
}
}
return "Goban(max={$this->max_x}x{$this->max_y},size={$this->size_x}x{$this->size_y})={\n ".implode("\n ", $out)." }\n\n";
}//to_string
public function setOptionsCoords( $coords, $showCoords )
{
$this->opts_coords = ($coords & GOBB_BITMASK);
$this->show_coords = (bool)$showCoords;
}
public function getOptionsCoords()
{
return ($this->show_coords) ? $this->opts_coords : 0;
}
public function setSize( $size_x, $size_y )
{
$this->size_x = $size_x;
$this->size_y = $size_y;
}
// overwriting layer-value
public function setValue( $x, $y, $value, $label=NULL )
{
$this->max_x = max( $x, $this->max_x );
$this->max_y = max( $y, $this->max_y );
//error_log("Goban.setValue($x,$y,$value,$label): (".(is_array($value) ? self::value_to_string($value[GOBMATRIX_VALUE]) : '').") max_x/y={$this->max_x}/{$this->max_y}");
if ( is_array($value) )
$this->matrix[$y][$x] = $value;
elseif ( !is_numeric($value) )
error('invalid_args', "Goban.setValue($x,$y,$value)");
elseif ( is_null($label) )
$this->matrix[$y][$x] = $value; // optimization to avoid too many object-instances
else
$this->matrix[$y][$x] = array( GOBMATRIX_VALUE => $value, GOBMATRIX_LABEL => $label );
}//setValue
public function hasValue( $x, $y )
{
return isset($this->matrix[$y][$x]);
}
// internal, always return non-null layer-value-array
public function getValue( $x, $y )
{
$arrval = @$this->matrix[$y][$x];
if ( is_array($arrval) )
$result = $arrval;
elseif ( is_numeric($arrval) )
$result = array( GOBMATRIX_VALUE => $arrval, GOBMATRIX_LABEL => '' );
else
$result = array( GOBMATRIX_VALUE => 0, GOBMATRIX_LABEL => '' );
return $result;
}//getValue
public function addLink( $label, $href )
{
$this->BoardLinks[$label] = $href;
}
public function getLink( $label )
{
return @$this->BoardLinks[$label];
}
public function setBoardLines( $x, $y, $board_value, $hoshi_value=null )
{
$upd_arr = $this->getValue($x,$y);
$upd_arr[GOBMATRIX_VALUE] = ($upd_arr[GOBMATRIX_VALUE] & ~GOBB_BITMASK) | ($board_value & GOBB_BITMASK);
if ( !is_null($hoshi_value) )
$upd_arr[GOBMATRIX_VALUE] = ($upd_arr[GOBMATRIX_VALUE] & ~GOBO_HOSHI) | ($hoshi_value ? GOBB_BITMASK : 0);
$this->setValue( $x, $y, $upd_arr );
}//setBoardLines
public function getBoardLines( $x, $y )
{
$current_arr = $this->getValue($x,$y);
return ($current_arr[GOBMATRIX_VALUE] & GOBB_BITMASK);
}
public function eraseBoardPoint( $x, $y )
{
$this->setBoardLines( $x, $y, 0, /*hoshi*/false );
// adjust neighbour points
if ( $x > 1 )
$this->clearBoardLinesBit( $x-1, $y, GOBB_EAST );
if ( $this->hasValue( $x+1, $y ) )
$this->clearBoardLinesBit( $x+1, $y, GOBB_WEST );
if ( $y > 1 )
$this->clearBoardLinesBit( $x, $y-1, GOBB_SOUTH );
if ( $this->hasValue( $x, $y+1 ) )
$this->clearBoardLinesBit( $x, $y+1, GOBB_NORTH );
}//eraseBoardPoint
public function clearBoardLinesBit( $x, $y, $value )
{
$value &= GOBB_BITMASK;
$upd_arr = $this->getValue($x,$y);
$upd_arr[GOBMATRIX_VALUE] &= ~$value;
$this->setValue( $x, $y, $upd_arr );
}//clearBoardLinesBit
public function setHoshi( $x, $y, $hoshi_set, $check=false )
{
if ( $check && ( $x < 1 || $x > $this->max_x || $y < 1 || $y > $this->max_y ) )
return;
$upd_arr = $this->getValue($x,$y);
$upd_arr[GOBMATRIX_VALUE] = ($upd_arr[GOBMATRIX_VALUE] & ~GOBO_HOSHI) | ( (bool)$hoshi_set ? GOBO_HOSHI : 0 );
$this->setValue( $x, $y, $upd_arr );
}//setHoshi
// bool getHoshi(x,y)
public function getHoshi( $x, $y )
{
$current_arr = $this->getValue($x,$y);
return (bool)($current_arr[GOBMATRIX_VALUE] & GOBO_HOSHI);
}
public function setStone( $x, $y, $stone_value )
{
$upd_arr = $this->getValue($x,$y);
$upd_arr[GOBMATRIX_VALUE] = ($upd_arr[GOBMATRIX_VALUE] & ~GOBS_BITMASK) | ($stone_value & GOBS_BITMASK);
$this->setValue( $x, $y, $upd_arr );
}
public function getStone( $x, $y )
{
$current_arr = $this->getValue($x,$y);
return ($current_arr[GOBMATRIX_VALUE] & GOBS_BITMASK);
}
/*! \brief Switches BLACK/WHITE stone-color. */
public function switchStoneColor( $x, $y )
{
$upd_arr = $this->getValue($x,$y);
$stone = ( $upd_arr[GOBMATRIX_VALUE] & GOBS_BITMASK );
if ( $stone == GOBS_BLACK )
$stone = GOBS_WHITE;
elseif ( $stone == GOBS_WHITE )
$stone = GOBS_BLACK;
else
return;
$upd_arr[GOBMATRIX_VALUE] = ($upd_arr[GOBMATRIX_VALUE] & ~GOBS_BITMASK) | $stone;
$this->setValue( $x, $y, $upd_arr );
}//switchStoneColor
public function setMarker( $x, $y, $marker_value, $label=NULL )
{
$marker_value &= GOBM_BITMASK;
$upd_arr = $this->getValue($x,$y);
$upd_arr[GOBMATRIX_VALUE] =
($upd_arr[GOBMATRIX_VALUE] & ~GOBM_BITMASK) | $marker_value;
$upd_arr[GOBMATRIX_LABEL] =
(is_null($label)) ? '' : $label;
$this->setValue( $x, $y, $upd_arr );
}//setMarker
public function getMarker( $x, $y, $with_label=false )
{
$current_arr = $this->getValue($x,$y);
$value = ($current_arr[GOBMATRIX_VALUE] & GOBM_BITMASK);
return ( $with_label ) ? array( $value, $current_arr[GOBMATRIX_LABEL] ) : $value;
}
public function getMarkerLabel( $x, $y )
{
$current_arr = $this->getValue($x,$y);
return $current_arr[GOBMATRIX_LABEL];
}
public function makeBoard( $width, $height, $withHoshi=true )
{
if ( $width < 2 || $height < 2 )
error('invalid_args', "Goban.makeBoard.check($width,$height)");
for ( $y=1; $y <= $height; $y++)
{
$board_lines = GOBB_MID;
if ( $y == 1 )
$board_lines &= ~GOBB_NORTH;
elseif ( $y == $height )
$board_lines &= ~GOBB_SOUTH;
for ( $x=1; $x <= $width; $x++)
{
$val = $board_lines;
if ( $x == 1 )
$val &= ~GOBB_WEST;
elseif ( $x == $width )
$val &= ~GOBB_EAST;
if ( $withHoshi && is_hoshi($x-1, $y-1, $width, $height) )
$val |= GOBO_HOSHI;
$this->matrix[$y][$x] = $val;
}
}
$this->max_x = $width;
$this->max_y = $height;
}//makeBoard
/*!
* \brief StoneReader-interface-method on Goban-class for GameSnapshot-class.
* \return 0/1/2/3 for given goban-pos x/y=0..size-1, 0=empty, 1=black, 2=white, 3=dead
* \see GameSnapshot#make_game_snapshot()
*/
public function read_stone_value( $x, $y, $with_dead=true )
{
static $VAL_MAP = array(
GOBS_BLACK => 1, // 01 (black)
GOBS_WHITE => 2, // 10 (white)
GOBM_TERR_W => 3, // 11 (dead-stone)
GOBM_TERR_B => 3, // 11 (dead-stone)
);
$arrval = $this->getValue($x+1,$y+1);
$value = $arrval[GOBMATRIX_VALUE];
$marker = (int)@$VAL_MAP[ $value & GOBM_BITMASK ];
if ( $marker )
return ($with_dead) ? $marker : 0;
return (int)@$VAL_MAP[ $value & GOBS_BITMASK ]; // undef = 00 (empty-field)
}//read_stone_value
public function switch_colors()
{
for ( $y = 1; $y <= $this->max_y; $y++ )
{
for ( $x = 1; $x <= $this->max_x; $x++ )
$this->switchStoneColor( $x, $y );
}
}//switch_colors
/*!
* \brief Flattens Goban for shape-game: quadratic board, remove hoshi (use auto-hoshi),
* no irregular boards, only full (not part) boards, remove all markup.
*/
public function flatten_for_shape_game()
{
// no part-board, no irregular board
$size = max( $this->size_x, $this->size_y, $this->max_x, $this->max_y );
$this->size_x = $this->size_y = $this->max_x = $this->max_y;
// reset board-lines, no irregular board
for ( $y = 1; $y <= $size; $y++ )
{
$board_lines = GOBB_MID;
if ( $y == 1 )
$board_lines &= ~GOBB_NORTH;
elseif ( $y == $size )
$board_lines &= ~GOBB_SOUTH;
for ( $x = 1; $x <= $size; $x++ )
{
$val = $board_lines;
if ( $x == 1 )
$val &= ~GOBB_WEST;
elseif ( $x == $size )
$val &= ~GOBB_EAST;
$arrval = $this->getValue($x,$y);
$this->matrix[$y][$x] = ($arrval[GOBMATRIX_VALUE] & GOBS_BITMASK) | $board_lines;
}
}
}//flatten_for_shape_game
// ------------ static functions ----------------------------
/*!
* \brief Creates Goban from Games-Snapshot, prepared with GameSnapshot::parse_stones_snapshot().
* \param $arr_pos_xy array of [ GOBS_BLACK|WHITE, x, y ] with x/y=0..n
*/
public static function create_goban_from_stones_snapshot( $size, array $arr_pos_xy )
{
$goban = new Goban();
$goban->setOptionsCoords( GOBB_MID, true );
$goban->setSize( $size, $size );
$goban->makeBoard( $size, $size, /*withHoshi*/true );
foreach ( $arr_pos_xy as $arr )
{
list( $stone, $x, $y ) = $arr;
$goban->setStone( $x + 1, $y + 1, $stone );
}
return $goban;
}//create_goban_from_stones_snapshot
// static, see also to_string()-func
private static function value_to_string( $val )
{
$out = array();
if ( $val & GOBB_BITMASK )
{
$v = $val & GOBB_BITMASK;
if ( $v == GOBB_EMPTY )
$out[] = '.';
elseif ( $v == GOBB_MID )
$out[] = '+';
else
{
if ( $v & GOBB_NORTH ) $out[] = 'N';
if ( $v & GOBB_SOUTH ) $out[] = 'S';
if ( $v & GOBB_WEST ) $out[] = 'W';
if ( $v & GOBB_EAST ) $out[] = 'E';
}
}
if ( $val & GOBO_HOSHI )
$out[] = 'Ho';
if ( $val & GOBS_BITMASK )
{
$v = $val & GOBS_BITMASK;
if ( $v == GOBS_EMPTY )
$out[] = '-';
elseif ( $v == GOBS_BLACK )
$out[] = 'b';
elseif ( $v == GOBS_WHITE )
$out[] = 'w';
}
if ( $val & GOBM_BITMASK )
{
$v = $val & GOBM_BITMASK;
if ( $v == GOBM_EMPTY ) $out[] = ':';
if ( $v == GOBM_NUMBER ) $out[] = '9';
if ( $v == GOBM_LETTER ) $out[] = 'L';
if ( $v == GOBM_MARK ) $out[] = 'M';
if ( $v == GOBM_CIRCLE ) $out[] = 'C';
if ( $v == GOBM_SQUARE ) $out[] = 'S';
if ( $v == GOBM_TRIANGLE ) $out[] = 'T';
if ( $v == GOBM_CROSS ) $out[] = 'X';
if ( $v == GOBM_TERR_W ) $out[] = 'wT';
if ( $v == GOBM_TERR_B ) $out[] = 'bT';
if ( $v == GOBM_TERR_NEUTRAL ) $out[] = 'nT';
if ( $v == GOBM_TERR_DAME ) $out[] = 'dT';
}
return implode(' ', $out);
}//value_to_string
} //end 'Goban'
?>
//
koh5_pano
Drag mouse to navigate.
Navigation
- Left/Right Mouse drag: Changes camera heading.
- Up/Sown Mouse drag: Changes camera pitch.
- Scroll wheel: Changes camera field of view.
- I-Key: Displays Info panel with canvas size, image size and FPS.
17.Aug.2010, Martin Wengenmayer