--- /dev/null
+/*
+ * 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);
+ }
+
+}