Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 146
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
MoveBuilder
0.00% covered (danger)
0.00%
0 / 146
0.00% covered (danger)
0.00%
0 / 10
1406
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addMoves
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 addMove
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 isChessMove
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 addCommentBeforeFirstMove
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 addComment
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
56
 getActions
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 1
306
 startVariation
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 endVariation
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getMoves
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This file is a part of ChessBrowser.
4 *
5 * ChessBrowser is free software: you can redistribute it and/or modify
6 *  it under the terms of the GNU General Public License as published by
7 *  the Free Software Foundation, either version 3 of the License, or
8 *  (at your option) any later version.
9 *
10 *  This program is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 *  GNU General Public License for more details.
14 *
15 *  You should have received a copy of the GNU General Public License
16 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * This file is a part of PgnParser
19 *
20 * PgnParser is free software: you can redistribute it and/or modify
21 *  it under the terms of the GNU Lesser General Public License as published by
22 *  the Free Software Foundation, either version 3 of the License, or
23 *  (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
27 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28 *  GNU Lesser General Public License for more details.
29 *
30 *  You should have received a copy of the GNU Lesser General Public License
31 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
32 *
33 * @file MoveBuilder
34 * @ingroup ChessBrowser
35 * @author Alf Magne Kalleland
36 */
37
38namespace MediaWiki\Extension\ChessBrowser\PgnParser;
39
40class MoveBuilder {
41
42    private const PGN_KEY_ACTION_ARROW = "ar";
43    private const PGN_KEY_ACTION_HIGHLIGHT = "sq";
44    private const PGN_KEY_ACTION_CLR_HIGHLIGHT = "csl";
45    private const PGN_KEY_ACTION_CLR_ARROW = "cal";
46
47    private $moves = [];
48    private $moveReferences = [];
49    private $pointer = 0;
50    private $currentIndex = 0;
51
52    public function __construct() {
53        $this->moveReferences[0] = &$this->moves;
54    }
55
56    /**
57     * Add moves, separated by spaces
58     *
59     * @param string $moveString
60     */
61    public function addMoves( $moveString ) {
62        $moves = explode( " ", $moveString );
63        foreach ( $moves as $move ) {
64            $this->addMove( $move );
65        }
66    }
67
68    /**
69     * Add a single move
70     *
71     * @param string $move
72     */
73    private function addMove( $move ) {
74        if ( !$this->isChessMove( $move ) ) {
75            return;
76        }
77        $move = preg_replace( "/^([a-h])([18])([QRNB])$/", "$1$2=$3", $move );
78        $this->moveReferences[$this->pointer][] = [ ChessJson::MOVE_NOTATION => $move ];
79        $this->currentIndex++;
80    }
81
82    /**
83     * Check if a string is a valid chess move
84     *
85     * @param string $move
86     * @return bool
87     */
88    private function isChessMove( $move ) {
89        if ( $move == '--' ) {
90            return true;
91        }
92        $regex = "/([PNBRQK]?[a-h]?[1-8]?x?[a-h][1-8](?:\=[PNBRQK])?|O(-?O){1,2})[\+#]?(\s*[\!\?]+)?/s";
93        return preg_match( $regex, $move );
94    }
95
96    /**
97     * Insert a comment before the first move
98     *
99     * @param string $comment
100     */
101    public function addCommentBeforeFirstMove( $comment ) {
102        $comment = trim( $comment );
103        if ( !strlen( $comment ) ) {
104            return;
105        }
106        $this->moveReferences[$this->pointer][] = [];
107        $this->addComment( $comment );
108    }
109
110    /**
111     * Insert a comment at the current location
112     *
113     * @param string $comment
114     */
115    public function addComment( $comment ) {
116        $comment = trim( $comment );
117        if ( !strlen( $comment ) ) {
118            return;
119        }
120        # $index = max(0,count($this->moveReferences[$this->pointer])-1);
121        $index = count( $this->moveReferences[$this->pointer] ) - 1;
122
123        if ( strstr( $comment, '[%clk' ) ) {
124            $clk = preg_replace( '/\[%clk\D*?([\d\:]+?)[\]]/si', '$1', $comment );
125            $comment = str_replace( '[%clk ' . $clk . ']', '', $comment );
126            $this->moveReferences[$this->pointer][$index][ChessJson::MOVE_CLOCK] = $clk;
127        }
128
129        $actions = $this->getActions( $comment );
130        if ( $actions ) {
131            foreach ( $actions as $action ) {
132                $this->moveReferences[$this->pointer][$index][ChessJson::MOVE_ACTIONS][] = $action;
133            }
134        }
135
136        $comment = preg_replace(
137            '/\[%'
138            . self::PGN_KEY_ACTION_ARROW
139            . '[^\]]+?\]/si', '', $comment
140        );
141        $comment = preg_replace(
142            '/\[%'
143            . self::PGN_KEY_ACTION_CLR_ARROW
144            . '[^\]]+?\]/si', '', $comment
145        );
146        $comment = preg_replace(
147            '/\[%'
148            . self::PGN_KEY_ACTION_HIGHLIGHT
149            . '[^\]]+?\]/si', '', $comment
150        );
151        $comment = preg_replace(
152            '/\[%'
153            . self::PGN_KEY_ACTION_CLR_HIGHLIGHT
154            . '[^\]]+?\]/si', '', $comment
155        );
156        $comment = trim( $comment );
157
158        if ( $comment === '' ) {
159            return;
160        }
161
162        if ( $index === -1 ) {
163            $index = 0;
164            $this->moveReferences[$this->pointer][$index][ChessJson::MOVE_COMMENT] = $comment;
165            $this->currentIndex++;
166        } else {
167            $this->moveReferences[$this->pointer][$index][ChessJson::MOVE_COMMENT] = $comment;
168        }
169    }
170
171    /**
172     * getActions
173     *
174     * TODO document
175     *
176     * @param string $comment
177     * @return array
178     */
179    private function getActions( $comment ) {
180        $ret = [];
181        if ( strstr( $comment, '[%' . self::PGN_KEY_ACTION_ARROW ) ) {
182            $arrow = preg_replace(
183                '/.*?\[%'
184                . self::PGN_KEY_ACTION_ARROW
185                . ' ([^\]]+?)\].*/si', '$1', $comment
186            );
187            $arrows = explode( ",", $arrow );
188
189            foreach ( $arrows as $arrow ) {
190                $tokens = explode( ";", $arrow );
191                if ( strlen( $tokens[0] ) == 4 ) {
192                    $action = [
193                        'from' => substr( $arrow, 0, 2 ),
194                        'to' => substr( $arrow, 2, 2 ),
195                        'type' => 'arrow'
196                    ];
197                    if ( isset( $tokens[1] ) ) {
198                        $action['color'] = $tokens[1];
199                    }
200                    $ret[] = $action;
201                }
202            }
203        }
204
205        if ( strstr( $comment, '[%' . self::PGN_KEY_ACTION_CLR_ARROW ) ) {
206            $arrow = preg_replace(
207                '/.*?\[%'
208                . self::PGN_KEY_ACTION_CLR_ARROW
209                . ' ([^\]]+?)\].*/si', '$1', $comment
210            );
211            $arrows = explode( ",", $arrow );
212
213            foreach ( $arrows as $arrow ) {
214                $len = strlen( $arrow );
215                $color = "G";
216                if ( $len === 5 ) {
217                    $color = substr( $arrow, 0, 1 );
218                    $arrow = substr( $arrow, 1 );
219
220                }
221
222                if ( strlen( $arrow ) === 4 ) {
223                    $action = [
224                        'from' => substr( $arrow, 0, 2 ),
225                        'to' => substr( $arrow, 2, 2 ),
226                        'color' => $color,
227                        'type' => 'arrow',
228                    ];
229                    $ret[] = $action;
230                }
231            }
232        }
233
234        if ( strstr( $comment, '[%' . self::PGN_KEY_ACTION_HIGHLIGHT ) ) {
235            $arrow = preg_replace(
236                '/.*?\[%'
237                . self::PGN_KEY_ACTION_HIGHLIGHT
238                . ' ([^\]]+?)\].*/si', '$1', $comment
239            );
240            $arrows = explode( ",", $arrow );
241
242            foreach ( $arrows as $arrow ) {
243                $tokens = explode( ";", $arrow );
244                if ( strlen( $tokens[0] ) == 2 ) {
245                    $action = [
246                        'square' => substr( $arrow, 0, 2 ),
247                        'type' => 'highlight',
248                    ];
249                    if ( isset( $tokens[1] ) ) {
250                        $action["color"] = $tokens[1];
251                    }
252                    $ret[] = $action;
253                }
254            }
255        }
256
257        if ( strstr( $comment, '[%' . self::PGN_KEY_ACTION_CLR_HIGHLIGHT ) ) {
258            $arrow = preg_replace(
259                '/.*?\[%'
260                . self::PGN_KEY_ACTION_CLR_HIGHLIGHT
261                . ' ([^\]]+?)\].*/si', '$1', $comment
262            );
263            $arrows = explode( ",", $arrow );
264
265            foreach ( $arrows as $arrow ) {
266                $color = "G";
267                if ( strlen( $arrow ) === 3 ) {
268                    $color = substr( $arrow, 0, 1 );
269                    $arrow = substr( $arrow, 1 );
270                }
271
272                if ( strlen( $arrow ) === 2 ) {
273                    $action = [
274                        'square' => substr( $arrow, 0, 2 ),
275                        'color' => $color,
276                        'type' => 'highlight',
277                    ];
278                    $ret[] = $action;
279                }
280            }
281        }
282
283        return $ret;
284    }
285
286    /**
287     * Begin a variation at the current index
288     */
289    public function startVariation() {
290        $index = count( $this->moveReferences[$this->pointer] ) - 1;
291        if ( !isset( $this->moveReferences[$this->pointer][$index][ChessJson::MOVE_VARIATIONS] ) ) {
292            $this->moveReferences[$this->pointer][$index][ChessJson::MOVE_VARIATIONS] = [];
293        }
294        $moveVar = ChessJson::MOVE_VARIATIONS;
295        $countVars = count( $this->moveReferences[$this->pointer][$index][$moveVar] );
296        $this->moveReferences[$this->pointer][$index][$moveVar][$countVars] = [];
297        // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
298        $this->moveReferences[] =& $this->moveReferences[$this->pointer][$index][$moveVar][$countVars];
299        $this->pointer++;
300    }
301
302    /**
303     * End a variation
304     */
305    public function endVariation() {
306        array_pop( $this->moveReferences );
307        $this->pointer--;
308    }
309
310    /**
311     * Get the moves
312     *
313     * @return array
314     */
315    public function getMoves() {
316        return $this->moveReferences;
317    }
318}