[LIB-9] Separate chesshog-hedgefish module
[chesshog.git] / src / main / java / org / hedgecode / chess / tcd / TCDParser.java
1 /*
2  * Copyright (c) 2018. Developed by Hedgecode.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.hedgecode.chess.tcd;
18
19 import org.hedgecode.chess.position.Castle;
20 import org.hedgecode.chess.position.Color;
21 import org.hedgecode.chess.position.ParseException;
22 import org.hedgecode.chess.position.Parser;
23 import org.hedgecode.chess.position.Position;
24 import org.hedgecode.chess.position.Positions;
25 import org.hedgecode.chess.position.Square;
26
27 /**
28  * Tiny Chess Diagram (TCD) parser.
29  *
30  * @author Dmitry Samoshin aka gotty
31  */
32 public final class TCDParser implements Parser {
33
34     private static Parser _instance = new TCDParser();
35
36     private TCDParser() {
37     }
38
39     private final class TCDIterator {
40
41         private byte[] bytes;
42         private int pos;
43         private boolean isHighPart;
44
45         TCDIterator(byte[] bytes) {
46             this.bytes = bytes;
47             this.pos = 0;
48             this.isHighPart = true;
49         }
50
51         boolean hasNext() {
52             return (bytes.length > pos + 1) || (isHighPart && bytes.length > pos);
53         }
54
55         boolean isLast() {
56             return !isHighPart && (bytes.length - 1 == pos);
57         }
58
59         byte next() throws ParseException {
60             isHighPart = !isHighPart;
61             if (isHighPart)
62                 pos++;
63             return current();
64         }
65
66         byte current() throws ParseException {
67             if (bytes.length <= pos)
68                 throw new ParseException("parse.tcd.index.out.of.bounds");
69             byte frame = bytes[pos];
70             if (isHighPart)
71                 frame = (byte) (frame >> TCD.FRAME_LENGTH);
72             return (byte) (frame & TCD.FRAME_MASK);
73         }
74     }
75
76     @Override
77     public Position parse(String string) throws ParseException {
78         return parse(
79                 string.getBytes()
80         );
81     }
82
83     public Position parse(byte[] bytes) throws ParseException {
84         TCD.replaceFrom(bytes);
85         if (!TCD.isValid(bytes))
86             throw new ParseException("parse.tcd.invalid.bytes");
87         TCDIterator iterator = new TCDIterator(bytes);
88         return _parse(iterator);
89     }
90
91     private Position _parse(TCDIterator iterator) throws ParseException {
92         Position position;
93
94         byte curr = iterator.current();
95         byte next = iterator.next();
96
97         if (TCD.isBlackWhite(curr, next)) {
98             position = Positions.INITIAL.getPosition();
99             curr = iterator.next();
100             next = iterator.next();
101         } else {
102             position = Positions.EMPTY.getPosition();
103             boolean isWhiteAssigned = false, isBlackAssigned = false;
104             while (!isWhiteAssigned || !isBlackAssigned) {
105                 switch (curr) {
106                     case TCD.WHITE:
107                         _parsePieces(Color.WHITE, position, iterator);
108                         isWhiteAssigned = true;
109                         break;
110                     case TCD.BLACK:
111                         _parsePieces(Color.BLACK, position, iterator);
112                         isBlackAssigned = true;
113                         break;
114                     default:
115                         throw new ParseException("parse.tcd.incorrect.color");
116                 }
117                 curr = iterator.current();
118                 next = iterator.next();
119             }
120         }
121
122         if (!TCD.isWhiteBlack(curr, next))
123             throw new ParseException("parse.tcd.incorrect.end");
124
125         _parseAddition(position, iterator);
126
127         return position;
128     }
129
130     private void _parsePieces(Color color, Position position, TCDIterator iterator) throws ParseException {
131         byte piece = iterator.current();
132         while (TCD.isNotColor(piece)) {
133             int pieceCount = iterator.next() + 1;
134             for (int i = 0; i < pieceCount; ++i) {
135                 Square square = _parseSquare(iterator);
136                 switch (piece) {
137                     case TCD.PAWN:
138                         position.setPawn(color, square);
139                         break;
140                     case TCD.KNIGHT:
141                         position.setKnight(color, square);
142                         break;
143                     case TCD.BISHOP:
144                         position.setBishop(color, square);
145                         break;
146                     case TCD.ROOK:
147                         position.setRook(color, square);
148                         break;
149                     case TCD.QUEEN:
150                         position.setQueen(color, square);
151                         break;
152                     case TCD.KING:
153                         position.setKing(color, square);
154                         break;
155                     default:
156                         throw new ParseException("parse.tcd.incorrect.piece");
157                 }
158             }
159             piece = iterator.next();
160         }
161     }
162
163     private void _parseAddition(Position position, TCDIterator iterator) throws ParseException {
164         if (iterator.hasNext()) {
165             byte frame = iterator.next();
166             if (iterator.isLast() && frame != TCD.END) {
167                 throw new ParseException("parse.tcd.incorrect.end");
168             } else {
169                 boolean isEndPosition = false;
170                 while (!isEndPosition) {
171                     switch (frame) {
172                         case TCD.MOVE:
173                             position.setMove(
174                                     _parseMove(iterator)
175                             );
176                             break;
177                         case TCD.CASTLE_WHITE:
178                             position.setCastle(
179                                     Color.WHITE, _parseCastle(iterator)
180                             );
181                             break;
182                         case TCD.CASTLE_BLACK:
183                             position.setCastle(
184                                     Color.BLACK, _parseCastle(iterator)
185                             );
186                             break;
187                         case TCD.EN_PASSANT:
188                             position.setEnPassant(
189                                     _parseSquare(iterator)
190                             );
191                             break;
192                         case TCD.HALFMOVE:
193                             position.setHalfMove(
194                                     _parseNumber(iterator)
195                             );
196                             break;
197                         case TCD.FULLMOVE:
198                             position.setFullMove(
199                                     _parseNumber(iterator)
200                             );
201                             break;
202                         default:
203                             throw new ParseException("parse.tcd.incorrect.addition");
204                     }
205                     isEndPosition = true;
206                     if (iterator.hasNext()) {
207                         frame = iterator.next();
208                         if (iterator.isLast() && frame != TCD.END) {
209                             throw new ParseException("parse.tcd.incorrect.end");
210                         }
211                         isEndPosition = iterator.isLast();
212                     }
213                 }
214             }
215         }
216     }
217
218     private Square _parseSquare(TCDIterator iterator) throws ParseException {
219         byte vl = iterator.next();
220         byte hl = iterator.next();
221         return Square.getSquare(vl, hl);
222     }
223
224     private Color _parseMove(TCDIterator iterator) throws ParseException {
225         byte frame = iterator.next();
226         if (TCD.isNotColor(frame))
227             throw new ParseException("parse.tcd.incorrect.move.color");
228         return frame == TCD.WHITE ? Color.WHITE : Color.BLACK;
229     }
230
231     private Castle _parseCastle(TCDIterator iterator) throws ParseException {
232         byte frame = iterator.next();
233         if (TCD.isNotCastle(frame))
234             throw new ParseException("parse.tcd.incorrect.castle");
235         Castle castle = Castle.NONE;
236         switch (frame) {
237             case TCD.CASTLE_KING:
238                 castle = Castle.KING;
239                 break;
240             case TCD.CASTLE_QUEEN:
241                 castle = Castle.QUEEN;
242                 break;
243             case TCD.CASTLE_BOTH:
244                 castle = Castle.BOTH;
245                 break;
246         }
247         return castle;
248     }
249
250     private int _parseNumber(TCDIterator iterator) throws ParseException {
251         return iterator.next() << (2 * TCD.FRAME_LENGTH)
252                 | iterator.next() << TCD.FRAME_LENGTH
253                 | iterator.next();
254     }
255
256     public static Parser getInstance() {
257         return _instance;
258     }
259
260
261     public static void main(String[] args) throws ParseException {
262         //Position position = getInstance().parse("A7ONMILHJJIFCJEDO0ECFD0OvFV+4fnNQwOYoWaG-h5pgG");
263         //Position position = getInstance().parse("xG");
264         Position position = getInstance().parse("A7ONMILHJJIFCJEDO0ECFDGOvFV+4fnNQwOYoWaG-h5pgG");
265         System.out.println(position);
266     }
267
268 }