[LIB-9] Separate chesshog-format module
[chesshog.git] / chesshog-format / src / main / java / org / hedgecode / chess / tcd / TCDParser.java
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/tcd/TCDParser.java b/chesshog-format/src/main/java/org/hedgecode/chess/tcd/TCDParser.java
new file mode 100644 (file)
index 0000000..bcf1989
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2018. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess.tcd;
+
+import org.hedgecode.chess.position.Castle;
+import org.hedgecode.chess.position.Color;
+import org.hedgecode.chess.position.ParseException;
+import org.hedgecode.chess.position.Parser;
+import org.hedgecode.chess.position.Position;
+import org.hedgecode.chess.position.Positions;
+import org.hedgecode.chess.position.Square;
+
+/**
+ * Tiny Chess Diagram (TCD) parser.
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public final class TCDParser implements Parser {
+
+    private static Parser _instance = new TCDParser();
+
+    private TCDParser() {
+    }
+
+    private final class TCDIterator {
+
+        private byte[] bytes;
+        private int pos;
+        private boolean isHighPart;
+
+        TCDIterator(byte[] bytes) {
+            this.bytes = bytes;
+            this.pos = 0;
+            this.isHighPart = true;
+        }
+
+        boolean hasNext() {
+            return (bytes.length > pos + 1) || (isHighPart && bytes.length > pos);
+        }
+
+        boolean isLast() {
+            return !isHighPart && (bytes.length - 1 == pos);
+        }
+
+        byte next() throws ParseException {
+            isHighPart = !isHighPart;
+            if (isHighPart)
+                pos++;
+            return current();
+        }
+
+        byte current() throws ParseException {
+            if (bytes.length <= pos)
+                throw new ParseException("parse.tcd.index.out.of.bounds");
+            byte frame = bytes[pos];
+            if (isHighPart)
+                frame = (byte) (frame >> TCD.FRAME_LENGTH);
+            return (byte) (frame & TCD.FRAME_MASK);
+        }
+    }
+
+    @Override
+    public Position parse(String string) throws ParseException {
+        return parse(
+                string.getBytes()
+        );
+    }
+
+    public Position parse(byte[] bytes) throws ParseException {
+        TCD.replaceFrom(bytes);
+        if (!TCD.isValid(bytes))
+            throw new ParseException("parse.tcd.invalid.bytes");
+        TCDIterator iterator = new TCDIterator(bytes);
+        return _parse(iterator);
+    }
+
+    private Position _parse(TCDIterator iterator) throws ParseException {
+        Position position;
+
+        byte curr = iterator.current();
+        byte next = iterator.next();
+
+        if (TCD.isBlackWhite(curr, next)) {
+            position = Positions.INITIAL.getPosition();
+            curr = iterator.next();
+            next = iterator.next();
+        } else {
+            position = Positions.EMPTY.getPosition();
+            boolean isWhiteAssigned = false, isBlackAssigned = false;
+            while (!isWhiteAssigned || !isBlackAssigned) {
+                switch (curr) {
+                    case TCD.WHITE:
+                        _parsePieces(Color.WHITE, position, iterator);
+                        isWhiteAssigned = true;
+                        break;
+                    case TCD.BLACK:
+                        _parsePieces(Color.BLACK, position, iterator);
+                        isBlackAssigned = true;
+                        break;
+                    default:
+                        throw new ParseException("parse.tcd.incorrect.color");
+                }
+                curr = iterator.current();
+                next = iterator.next();
+            }
+        }
+
+        if (!TCD.isWhiteBlack(curr, next))
+            throw new ParseException("parse.tcd.incorrect.end");
+
+        _parseAddition(position, iterator);
+
+        return position;
+    }
+
+    private void _parsePieces(Color color, Position position, TCDIterator iterator) throws ParseException {
+        byte piece = iterator.current();
+        while (TCD.isNotColor(piece)) {
+            int pieceCount = iterator.next() + 1;
+            for (int i = 0; i < pieceCount; ++i) {
+                Square square = _parseSquare(iterator);
+                switch (piece) {
+                    case TCD.PAWN:
+                        position.setPawn(color, square);
+                        break;
+                    case TCD.KNIGHT:
+                        position.setKnight(color, square);
+                        break;
+                    case TCD.BISHOP:
+                        position.setBishop(color, square);
+                        break;
+                    case TCD.ROOK:
+                        position.setRook(color, square);
+                        break;
+                    case TCD.QUEEN:
+                        position.setQueen(color, square);
+                        break;
+                    case TCD.KING:
+                        position.setKing(color, square);
+                        break;
+                    default:
+                        throw new ParseException("parse.tcd.incorrect.piece");
+                }
+            }
+            piece = iterator.next();
+        }
+    }
+
+    private void _parseAddition(Position position, TCDIterator iterator) throws ParseException {
+        if (iterator.hasNext()) {
+            byte frame = iterator.next();
+            if (iterator.isLast() && frame != TCD.END) {
+                throw new ParseException("parse.tcd.incorrect.end");
+            } else {
+                boolean isEndPosition = false;
+                while (!isEndPosition) {
+                    switch (frame) {
+                        case TCD.MOVE:
+                            position.setMove(
+                                    _parseMove(iterator)
+                            );
+                            break;
+                        case TCD.CASTLE_WHITE:
+                            position.setCastle(
+                                    Color.WHITE, _parseCastle(iterator)
+                            );
+                            break;
+                        case TCD.CASTLE_BLACK:
+                            position.setCastle(
+                                    Color.BLACK, _parseCastle(iterator)
+                            );
+                            break;
+                        case TCD.EN_PASSANT:
+                            position.setEnPassant(
+                                    _parseSquare(iterator)
+                            );
+                            break;
+                        case TCD.HALFMOVE:
+                            position.setHalfMove(
+                                    _parseNumber(iterator)
+                            );
+                            break;
+                        case TCD.FULLMOVE:
+                            position.setFullMove(
+                                    _parseNumber(iterator)
+                            );
+                            break;
+                        default:
+                            throw new ParseException("parse.tcd.incorrect.addition");
+                    }
+                    isEndPosition = true;
+                    if (iterator.hasNext()) {
+                        frame = iterator.next();
+                        if (iterator.isLast() && frame != TCD.END) {
+                            throw new ParseException("parse.tcd.incorrect.end");
+                        }
+                        isEndPosition = iterator.isLast();
+                    }
+                }
+            }
+        }
+    }
+
+    private Square _parseSquare(TCDIterator iterator) throws ParseException {
+        byte vl = iterator.next();
+        byte hl = iterator.next();
+        return Square.getSquare(vl, hl);
+    }
+
+    private Color _parseMove(TCDIterator iterator) throws ParseException {
+        byte frame = iterator.next();
+        if (TCD.isNotColor(frame))
+            throw new ParseException("parse.tcd.incorrect.move.color");
+        return frame == TCD.WHITE ? Color.WHITE : Color.BLACK;
+    }
+
+    private Castle _parseCastle(TCDIterator iterator) throws ParseException {
+        byte frame = iterator.next();
+        if (TCD.isNotCastle(frame))
+            throw new ParseException("parse.tcd.incorrect.castle");
+        Castle castle = Castle.NONE;
+        switch (frame) {
+            case TCD.CASTLE_KING:
+                castle = Castle.KING;
+                break;
+            case TCD.CASTLE_QUEEN:
+                castle = Castle.QUEEN;
+                break;
+            case TCD.CASTLE_BOTH:
+                castle = Castle.BOTH;
+                break;
+        }
+        return castle;
+    }
+
+    private int _parseNumber(TCDIterator iterator) throws ParseException {
+        return iterator.next() << (2 * TCD.FRAME_LENGTH)
+                | iterator.next() << TCD.FRAME_LENGTH
+                | iterator.next();
+    }
+
+    public static Parser getInstance() {
+        return _instance;
+    }
+
+
+    public static void main(String[] args) throws ParseException {
+        //Position position = getInstance().parse("A7ONMILHJJIFCJEDO0ECFD0OvFV+4fnNQwOYoWaG-h5pgG");
+        //Position position = getInstance().parse("xG");
+        Position position = getInstance().parse("A7ONMILHJJIFCJEDO0ECFDGOvFV+4fnNQwOYoWaG-h5pgG");
+        System.out.println(position);
+    }
+
+}