Blame


1 8866f3d0 2023-05-06 jrmu <?php if (!defined('PmWiki')) exit();
2 8866f3d0 2023-05-06 jrmu /* Copyright 2005-2022 Patrick R. Michaud (pmichaud@pobox.com)
3 8866f3d0 2023-05-06 jrmu This file is chess.php; you can redistribute it and/or modify
4 8866f3d0 2023-05-06 jrmu it under the terms of the GNU General Public License as published
5 8866f3d0 2023-05-06 jrmu by the Free Software Foundation; either version 2 of the License, or
6 8866f3d0 2023-05-06 jrmu (at your option) any later version.
7 8866f3d0 2023-05-06 jrmu
8 8866f3d0 2023-05-06 jrmu This recipe enables PGN (Portable Game Notation) chess markup
9 8866f3d0 2023-05-06 jrmu in PmWiki. Strings in the markup text matching the PGN move format
10 8866f3d0 2023-05-06 jrmu (e.g., things like "5. O-O Nf6" and "4...exd4") are internally
11 8866f3d0 2023-05-06 jrmu recorded as moves in a "game", and then the {FEN} markup can be
12 8866f3d0 2023-05-06 jrmu used to obtain the current board position in Forsyth-Edwards
13 8866f3d0 2023-05-06 jrmu Notation. The markup also allows lines of play to be explored,
14 8866f3d0 2023-05-06 jrmu as (re-)specifying an earlier move automatically undoes any
15 8866f3d0 2023-05-06 jrmu previously recorded later moves for the current game.
16 8866f3d0 2023-05-06 jrmu
17 8866f3d0 2023-05-06 jrmu The recipe also defines a "Chessboard:" InterMap shortcut,
18 8866f3d0 2023-05-06 jrmu which can be used in combination with {FEN} to draw a graphical
19 8866f3d0 2023-05-06 jrmu representation of the current board position. Normally this is
20 8866f3d0 2023-05-06 jrmu done with either the "(:chessboard:") markup, which generates
21 8866f3d0 2023-05-06 jrmu a table, or the "Chessboard:{FEN}&t=.gif" markup, which calls
22 8866f3d0 2023-05-06 jrmu the chessboard.php application. An administrator can override
23 8866f3d0 2023-05-06 jrmu the Chessboard: InterMap shortcut to change the default board display
24 8866f3d0 2023-05-06 jrmu settings or use a different script entirely:
25 8866f3d0 2023-05-06 jrmu
26 8866f3d0 2023-05-06 jrmu # (in local/localmap.txt)
27 8866f3d0 2023-05-06 jrmu # redefine "Chessboard:" to use a green+tan board
28 8866f3d0 2023-05-06 jrmu Chessboard $PubDirUrl/chess/chessboard.php?light=ffffcc&dark=00bb00&fen=$1
29 8866f3d0 2023-05-06 jrmu
30 8866f3d0 2023-05-06 jrmu # define "CB:" as a small 160x160 board
31 8866f3d0 2023-05-06 jrmu CB $PubDirUrl/chess/chessboard.php?w=160&h=160&fen=$1
32 8866f3d0 2023-05-06 jrmu
33 8866f3d0 2023-05-06 jrmu Script maintained by Petko Yotov https://www.pmwiki.org/petko
34 8866f3d0 2023-05-06 jrmu */
35 8866f3d0 2023-05-06 jrmu
36 8866f3d0 2023-05-06 jrmu SDV($RecipeInfo['ChessMarkup']['Version'], '20220103');
37 8866f3d0 2023-05-06 jrmu SDVA($CustomSyntax, array('ChessMarkup'=>'InterMap Chessboard:\{FEN\}'));
38 8866f3d0 2023-05-06 jrmu
39 8866f3d0 2023-05-06 jrmu ## First, we define the Chessboard: InterMap shortcut
40 8866f3d0 2023-05-06 jrmu SDV($LinkFunctions['Chessboard:'], 'LinkIMap');
41 8866f3d0 2023-05-06 jrmu SDV($IMap['Chessboard:'], "$PubDirUrl/chess/chessboard.php?fen=\$1");
42 8866f3d0 2023-05-06 jrmu
43 8866f3d0 2023-05-06 jrmu ## Define the pgn and {FEN} markups:
44 8866f3d0 2023-05-06 jrmu $PGNMovePattern = "[KQRBNP]?[a-h]?[1-8]?x?[a-h][1-8](?:=[KQRBNP])?|O-O-O|O-O";
45 8866f3d0 2023-05-06 jrmu Markup('pgn', 'directives',
46 8866f3d0 2023-05-06 jrmu "/(\\d+)\\.\\s*($PGNMovePattern|\\.+)(?:[\\s+#?!]*($PGNMovePattern))?/",
47 8866f3d0 2023-05-06 jrmu "PGNMove");
48 8866f3d0 2023-05-06 jrmu Markup('{FEN}', '>pgn', '/\\{FEN\\}/', 'FENBoard');
49 8866f3d0 2023-05-06 jrmu
50 8866f3d0 2023-05-06 jrmu ## The (:chessboard:) markup by default generates a table, since
51 8866f3d0 2023-05-06 jrmu ## some PHP installations don't have GD support compiled in.
52 8866f3d0 2023-05-06 jrmu Markup('chessboard', '>{FEN}',
53 8866f3d0 2023-05-06 jrmu '/\\(:chessboard(\\s.*?)?:\\)/i',
54 8866f3d0 2023-05-06 jrmu "ChessTable");
55 8866f3d0 2023-05-06 jrmu
56 8866f3d0 2023-05-06 jrmu
57 8866f3d0 2023-05-06 jrmu ## $PGNMoves is an array of all moves (in PGN notation). The
58 8866f3d0 2023-05-06 jrmu ## PGNMove($num, $white, $black) function adds moves for white and
59 8866f3d0 2023-05-06 jrmu ## black into the array, removing any later moves that might be
60 8866f3d0 2023-05-06 jrmu ## already present.
61 8866f3d0 2023-05-06 jrmu $PGNMoves = array();
62 8866f3d0 2023-05-06 jrmu function PGNMove($m) {
63 8866f3d0 2023-05-06 jrmu $out = array_shift($m);
64 8866f3d0 2023-05-06 jrmu @list($num, $white, $black) = $m;
65 8866f3d0 2023-05-06 jrmu global $PGNMoves;
66 8866f3d0 2023-05-06 jrmu if ($white[0] != '.') {
67 8866f3d0 2023-05-06 jrmu $PGNMoves[$num*2-2] = $white;
68 8866f3d0 2023-05-06 jrmu array_splice($PGNMoves, $num*2-1);
69 8866f3d0 2023-05-06 jrmu }
70 8866f3d0 2023-05-06 jrmu if ($black) {
71 8866f3d0 2023-05-06 jrmu $PGNMoves[$num*2-1] = $black;
72 8866f3d0 2023-05-06 jrmu array_splice($PGNMoves, $num*2);
73 8866f3d0 2023-05-06 jrmu }
74 8866f3d0 2023-05-06 jrmu return Keep($out);
75 8866f3d0 2023-05-06 jrmu }
76 8866f3d0 2023-05-06 jrmu
77 8866f3d0 2023-05-06 jrmu
78 8866f3d0 2023-05-06 jrmu ## FENBoard() plays out the moves given in $PGNMoves and returns
79 8866f3d0 2023-05-06 jrmu ## the resulting position as a FEN record. Internally $board
80 8866f3d0 2023-05-06 jrmu ## is maintained as a 10x10 character string, with $board{11}
81 8866f3d0 2023-05-06 jrmu ## corresponding to a8 and $board{88} corresponding to h1.
82 8866f3d0 2023-05-06 jrmu ## Since PGN generally gives us only the name of the piece being
83 8866f3d0 2023-05-06 jrmu ## moved and the square it moved to, we have to figure out where
84 8866f3d0 2023-05-06 jrmu ## that piece came from. Castling is handled by directly manipulating
85 8866f3d0 2023-05-06 jrmu ## the $board to the result of the castle. The from position
86 8866f3d0 2023-05-06 jrmu ## for pawn moves is computed directly by inspection, while all
87 8866f3d0 2023-05-06 jrmu ## other pieces use the $legalmoves array to determine the relative
88 8866f3d0 2023-05-06 jrmu ## square offsets where pieces could've come from. Once we
89 8866f3d0 2023-05-06 jrmu ## figure out where the piece came from, we put the piece in its
90 8866f3d0 2023-05-06 jrmu ## new position ($to) and change the old position ($from) to an
91 8866f3d0 2023-05-06 jrmu ## empty square.
92 8866f3d0 2023-05-06 jrmu
93 8866f3d0 2023-05-06 jrmu function FENBoard() {
94 8866f3d0 2023-05-06 jrmu global $PGNMoves;
95 8866f3d0 2023-05-06 jrmu $num = count($PGNMoves) * 2;
96 8866f3d0 2023-05-06 jrmu # initialize the board and our allowed moves
97 8866f3d0 2023-05-06 jrmu $board = "-----------rnbqkbnr--pppppppp--........--........-"
98 8866f3d0 2023-05-06 jrmu . "-........--........--PPPPPPPP--RNBQKBNR-----------";
99 8866f3d0 2023-05-06 jrmu $legalmoves = array(
100 8866f3d0 2023-05-06 jrmu 'K' => array(-11, -10, -9, -1, 1, 9, 10, 11),
101 8866f3d0 2023-05-06 jrmu 'Q' => array(-11, -10, -9, -1, 1, 9, 10, 11),
102 8866f3d0 2023-05-06 jrmu 'B' => array(-11, -9, 9, 11),
103 8866f3d0 2023-05-06 jrmu 'R' => array(-10, -1, 1, 10),
104 8866f3d0 2023-05-06 jrmu 'N' => array(-21, -19, -12, -8, 8, 12, 19, 21));
105 8866f3d0 2023-05-06 jrmu
106 8866f3d0 2023-05-06 jrmu # now, walk through each move and update the board accordingly
107 8866f3d0 2023-05-06 jrmu for($i=0; $i<$num; $i++) {
108 8866f3d0 2023-05-06 jrmu $move = @$PGNMoves[$i]; # odd numbered
109 8866f3d0 2023-05-06 jrmu $isblack = $i % 2; # moves are black
110 8866f3d0 2023-05-06 jrmu if ($move == 'O-O') { # kingside castling
111 8866f3d0 2023-05-06 jrmu $board = ($isblack)
112 8866f3d0 2023-05-06 jrmu ? substr_replace($board, '.rk.', 15, 4)
113 8866f3d0 2023-05-06 jrmu : substr_replace($board, '.RK.', 85, 4);
114 8866f3d0 2023-05-06 jrmu continue;
115 8866f3d0 2023-05-06 jrmu }
116 8866f3d0 2023-05-06 jrmu if ($move == 'O-O-O') { # queenside castling
117 8866f3d0 2023-05-06 jrmu $board = ($isblack)
118 8866f3d0 2023-05-06 jrmu ? substr_replace($board, '..kr.', 11, 5)
119 8866f3d0 2023-05-06 jrmu : substr_replace($board, '..KR.', 81, 5);
120 8866f3d0 2023-05-06 jrmu continue;
121 8866f3d0 2023-05-06 jrmu }
122 8866f3d0 2023-05-06 jrmu if (preg_match( # all other moves
123 8866f3d0 2023-05-06 jrmu "/^([KQRBNP]?)([a-h]?)([1-8]?)(x?)([a-h])([1-8])(=[KQRBNP])?/",
124 8866f3d0 2023-05-06 jrmu $move, $match)) {
125 8866f3d0 2023-05-06 jrmu @list($m, $piece, $ff, $fr, $cap, $tf, $tr, $promotion) = $match;
126 8866f3d0 2023-05-06 jrmu $tf = strpos("abcdefgh", $tf)+1;
127 8866f3d0 2023-05-06 jrmu $ff = ($ff) ? strpos("abcdefgh", $ff)+1 : 0;
128 8866f3d0 2023-05-06 jrmu $to = (9-$tr)*10 + $tf;
129 8866f3d0 2023-05-06 jrmu if (!$piece) $piece = "P";
130 8866f3d0 2023-05-06 jrmu $pp = ($isblack) ? strtolower($piece) : $piece;
131 8866f3d0 2023-05-06 jrmu if ($pp == 'P') { # white's pawn move
132 8866f3d0 2023-05-06 jrmu if ($cap) { # capture
133 8866f3d0 2023-05-06 jrmu $from = (9-$tr)*10+10+$ff;
134 8866f3d0 2023-05-06 jrmu if ($board[$to]=='.') $board[$to+10]='.'; # en passant
135 8866f3d0 2023-05-06 jrmu }
136 8866f3d0 2023-05-06 jrmu elseif ($board[$to+10]==$pp) $from=$to+10; # move
137 8866f3d0 2023-05-06 jrmu elseif ($tr==4 && $board[$to+20]==$pp) $from=$to+20; # first move
138 8866f3d0 2023-05-06 jrmu } elseif ($pp == 'p') { # black's pawn
139 8866f3d0 2023-05-06 jrmu if ($cap) { # capture
140 8866f3d0 2023-05-06 jrmu $from = (9-$tr)*10-10+$ff;
141 8866f3d0 2023-05-06 jrmu if ($board[$to]=='.') $board[$to-10]='.'; # en passant
142 8866f3d0 2023-05-06 jrmu }
143 8866f3d0 2023-05-06 jrmu elseif ($board[$to-10]==$pp) $from=$to-10; # move
144 8866f3d0 2023-05-06 jrmu elseif ($tr==5 && $board[$to-20]==$pp) $from=$to-20; # first move
145 8866f3d0 2023-05-06 jrmu } else {
146 8866f3d0 2023-05-06 jrmu # Here we look at squares along the lines for the piece
147 8866f3d0 2023-05-06 jrmu # being moved. $n contains an offset for each square along
148 8866f3d0 2023-05-06 jrmu # a valid line for the current piece.
149 8866f3d0 2023-05-06 jrmu foreach($legalmoves[$piece] as $n) {
150 8866f3d0 2023-05-06 jrmu for($from=$to+$n; $from>10 && $from<89; $from+=$n) {
151 8866f3d0 2023-05-06 jrmu # if we find the piece we're looking for, we're done
152 8866f3d0 2023-05-06 jrmu if ($board[$from] == $pp
153 8866f3d0 2023-05-06 jrmu && (!$ff || ($from%10==$ff))
154 8866f3d0 2023-05-06 jrmu && (!$fr || ((int)($from/10)==(9-$fr)))) break 2;
155 8866f3d0 2023-05-06 jrmu # if we find anything but an empty square, try another line
156 8866f3d0 2023-05-06 jrmu if ($board[$from] != '.') continue 2;
157 8866f3d0 2023-05-06 jrmu
158 8866f3d0 2023-05-06 jrmu # kings and knights don't repeat offsets
159 8866f3d0 2023-05-06 jrmu if ($piece == 'K' || $piece == 'N') continue 2;
160 8866f3d0 2023-05-06 jrmu }
161 8866f3d0 2023-05-06 jrmu }
162 8866f3d0 2023-05-06 jrmu }
163 8866f3d0 2023-05-06 jrmu
164 8866f3d0 2023-05-06 jrmu # pawn promotions
165 8866f3d0 2023-05-06 jrmu if ($promotion)
166 8866f3d0 2023-05-06 jrmu $pp = ($isblack) ? strtolower($promotion[1]) : $promotion[1];
167 8866f3d0 2023-05-06 jrmu
168 8866f3d0 2023-05-06 jrmu # move the piece
169 8866f3d0 2023-05-06 jrmu $board[$to] = $pp; $board[$from] = '.';
170 8866f3d0 2023-05-06 jrmu }
171 8866f3d0 2023-05-06 jrmu }
172 8866f3d0 2023-05-06 jrmu
173 8866f3d0 2023-05-06 jrmu # now, convert the board to a FEN record
174 8866f3d0 2023-05-06 jrmu $board = PPRA(array('/-+/' =>'/',
175 8866f3d0 2023-05-06 jrmu '/\\.+/'=> 'chess_cb_length'
176 8866f3d0 2023-05-06 jrmu ),
177 8866f3d0 2023-05-06 jrmu substr($board, 11, 78));
178 8866f3d0 2023-05-06 jrmu
179 8866f3d0 2023-05-06 jrmu # and return it
180 8866f3d0 2023-05-06 jrmu return $board;
181 8866f3d0 2023-05-06 jrmu }
182 8866f3d0 2023-05-06 jrmu
183 8866f3d0 2023-05-06 jrmu function chess_cb_length($m) { return strlen($m[0]); }
184 8866f3d0 2023-05-06 jrmu
185 8866f3d0 2023-05-06 jrmu ## The ChessTable function takes a FEN string (or the current
186 8866f3d0 2023-05-06 jrmu ## game position as returned by FENBoard() above) and creates an
187 8866f3d0 2023-05-06 jrmu ## HTML table representing the current game position.
188 8866f3d0 2023-05-06 jrmu $ChessPieces = array(
189 8866f3d0 2023-05-06 jrmu 'k' => 'kd-60.png', 'q' => 'qd-60.png', 'r' => 'rd-60.png',
190 8866f3d0 2023-05-06 jrmu 'b' => 'bd-60.png', 'n' => 'nd-60.png', 'p' => 'pd-60.png',
191 8866f3d0 2023-05-06 jrmu 'K' => 'kl-60.png', 'Q' => 'ql-60.png', 'R' => 'rl-60.png',
192 8866f3d0 2023-05-06 jrmu 'B' => 'bl-60.png', 'N' => 'nl-60.png', 'P' => 'pl-60.png',
193 8866f3d0 2023-05-06 jrmu '.' => 'blank-60.png');
194 8866f3d0 2023-05-06 jrmu $ChessChars = array(
195 8866f3d0 2023-05-06 jrmu 'k' => '&#x265A;', 'q' => '&#x265B;', 'r' => '&#x265C;',
196 8866f3d0 2023-05-06 jrmu 'b' => '&#x265D;', 'n' => '&#x265E;', 'p' => '&#x265F;',
197 8866f3d0 2023-05-06 jrmu 'K' => '&#x2654;', 'Q' => '&#x2655;', 'R' => '&#x2656;',
198 8866f3d0 2023-05-06 jrmu 'B' => '&#x2657;', 'N' => '&#x2658;', 'P' => '&#x2659;',
199 8866f3d0 2023-05-06 jrmu '.' => '&nbsp;');
200 8866f3d0 2023-05-06 jrmu SDV($HTMLStylesFmt['chess'], "
201 8866f3d0 2023-05-06 jrmu table.chesstable td.square1 { background-color: #cccccc; }
202 8866f3d0 2023-05-06 jrmu table.chesstable { border:1px solid #666666; line-height: 0; } ");
203 8866f3d0 2023-05-06 jrmu
204 8866f3d0 2023-05-06 jrmu
205 8866f3d0 2023-05-06 jrmu function ChessTable($m) {
206 8866f3d0 2023-05-06 jrmu global $ChessPieces, $ChessChars, $ChessPubDirUrlFmt, $FmtV;
207 8866f3d0 2023-05-06 jrmu
208 8866f3d0 2023-05-06 jrmu extract($GLOBALS['MarkupToHTML']);
209 8866f3d0 2023-05-06 jrmu $args = ParseArgs($m[1]);
210 8866f3d0 2023-05-06 jrmu
211 8866f3d0 2023-05-06 jrmu
212 8866f3d0 2023-05-06 jrmu # Some day: SDV($ChessSquareFmt, "<td>\$PieceChar</td>");
213 8866f3d0 2023-05-06 jrmu SDV($ChessSquareFmt,
214 8866f3d0 2023-05-06 jrmu "<td class='square\$SquareColor'><img
215 8866f3d0 2023-05-06 jrmu src='\$FarmPubDirUrl/chess/\$PieceImg' alt='\$PieceName' title='\$PieceName'
216 8866f3d0 2023-05-06 jrmu width='\$SquareWidth' height='\$SquareHeight' /></td>");
217 8866f3d0 2023-05-06 jrmu SDV($ChessDefaults, array(
218 8866f3d0 2023-05-06 jrmu 'width' => 240, 'class' => 'chesstable',
219 8866f3d0 2023-05-06 jrmu 'cellspacing' => 0, 'cellpadding' => 0));
220 8866f3d0 2023-05-06 jrmu $args = array_merge($ChessDefaults, $args);
221 8866f3d0 2023-05-06 jrmu $fen = (@$args['']) ? $args[''][0] : FENBoard();
222 8866f3d0 2023-05-06 jrmu $width = $args['width'];
223 8866f3d0 2023-05-06 jrmu $height = (@$args['height'] > 0) ? $args['height'] : $width;
224 8866f3d0 2023-05-06 jrmu
225 8866f3d0 2023-05-06 jrmu $tableargs = "";
226 8866f3d0 2023-05-06 jrmu foreach(array('class', 'cellspacing', 'cellpadding', 'align', 'style') as $a)
227 8866f3d0 2023-05-06 jrmu if (isset($args[$a]))
228 8866f3d0 2023-05-06 jrmu $tableargs .= " $a='".str_replace("'", "&#039;", $args[$a])."'";
229 8866f3d0 2023-05-06 jrmu
230 8866f3d0 2023-05-06 jrmu ## build the 8x8 board from the FEN record
231 8866f3d0 2023-05-06 jrmu $board = str_repeat('.', 64);
232 8866f3d0 2023-05-06 jrmu $file = 0; $rank = 0;
233 8866f3d0 2023-05-06 jrmu $len = strlen($fen);
234 8866f3d0 2023-05-06 jrmu for($i=0; ($i<$len) && ($rank+$file<64); $i++) {
235 8866f3d0 2023-05-06 jrmu $k = $fen[$i];
236 8866f3d0 2023-05-06 jrmu if (is_numeric($k) && $k > 0) { $file += $k; continue; }
237 8866f3d0 2023-05-06 jrmu if ($k == '/') { $rank += 8; $file = 0; continue; }
238 8866f3d0 2023-05-06 jrmu if ($ChessPieces[$k]) { $board[$rank+$file] = $k; $file++; }
239 8866f3d0 2023-05-06 jrmu }
240 8866f3d0 2023-05-06 jrmu
241 8866f3d0 2023-05-06 jrmu ## Now generate the table from the 8x8 board
242 8866f3d0 2023-05-06 jrmu $FmtV['$SquareWidth'] = $width / 8;
243 8866f3d0 2023-05-06 jrmu $FmtV['$SquareHeight'] = $height / 8;
244 8866f3d0 2023-05-06 jrmu for($i=0; $i<64; $i++) {
245 8866f3d0 2023-05-06 jrmu if ($i%8 == 0) $out[] = "<tr>";
246 8866f3d0 2023-05-06 jrmu $FmtV['$PieceImg'] = $ChessPieces[$board[$i]];
247 8866f3d0 2023-05-06 jrmu $FmtV['$PieceChar'] = $ChessChars[$board[$i]];
248 8866f3d0 2023-05-06 jrmu $FmtV['$PieceName'] = $board[$i];
249 8866f3d0 2023-05-06 jrmu $FmtV['$SquareColor'] = ($i + (int)($i/8)) % 2;
250 8866f3d0 2023-05-06 jrmu $out[] = FmtPageName($ChessSquareFmt, $pagename);
251 8866f3d0 2023-05-06 jrmu if ($i%8 == 7) $out[] = "</tr>";
252 8866f3d0 2023-05-06 jrmu }
253 8866f3d0 2023-05-06 jrmu return "<table $tableargs>".Keep(implode('', $out))."</table>";
254 8866f3d0 2023-05-06 jrmu }
255 8866f3d0 2023-05-06 jrmu