From: gotty Date: Fri, 24 Jan 2020 23:55:16 +0000 (+0300) Subject: [LIB-9] Set of entities for PGN parsing and formatting X-Git-Url: https://git.hedgecode.org/?p=chesshog.git;a=commitdiff_plain;h=5d271f23371d6a435a0435f000370f4d9b8c621a [LIB-9] Set of entities for PGN parsing and formatting --- diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNBuilder.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNBuilder.java index 7095a56..5d57dcf 100644 --- a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNBuilder.java +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018. Developed by Hedgecode. + * Copyright (c) 2018-2020. 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. @@ -16,14 +16,31 @@ package org.hedgecode.chess.pgn; +import org.hedgecode.chess.pgn.entity.Game; +import org.hedgecode.chess.pgn.format.PGNFormat; +import org.hedgecode.chess.pgn.format.ReducePGNFormat; + /** - * + * PGNBuilder * * @author Dmitry Samoshin aka gotty */ public class PGNBuilder { + public static String build(Game game) { + return build( + game, new ReducePGNFormat() + ); + } - + public static String build(Game game, PGNFormat pgnFormat) { + pgnFormat.addTags( + game.getTags() + ); + pgnFormat.addMoves( + game.getMoves() + ); + return pgnFormat.format(); + } } diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNConstants.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNConstants.java new file mode 100644 index 0000000..45b2408 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNConstants.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2020. 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.pgn; + +/** + * PGNConstants + * + * @author Dmitry Samoshin aka gotty + */ +public final class PGNConstants { + + public static final int PGN_DEF_LINE_LENGTH = 80; + public static final int PGN_MAX_LINE_LENGTH = 255; + + public static final String PGN_SPACE = " "; + public static final String PGN_CRLF = "\n"; + + public static final String PGN_DETECT_REGEX = "^\\[Event \"[^\"]+\"\\]$"; + + public static final String WHITE_MOVE_FORMAT = "%d.%s"; + public static final String BLACK_MOVE_FORMAT = "%s"; + public static final String BLACK_MOVE_DOT_FORMAT = "%d...%s"; + + public static final String COMMENT_FORMAT = "{%s}"; + public static final String VARIATION_FORMAT = "(%s)"; + + private PGNConstants() { + throw new AssertionError( + String.format("No %s instances!", getClass().getName()) + ); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNParser.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNParser.java index 274bfdf..4f67a2d 100644 --- a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNParser.java +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018. Developed by Hedgecode. + * Copyright (c) 2018-2020. 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. @@ -16,13 +16,25 @@ package org.hedgecode.chess.pgn; +import org.hedgecode.chess.pgn.entity.Game; +import org.hedgecode.chess.pgn.token.Tokenizer; +import org.hedgecode.chess.pgn.entity.DetailGame; +import org.hedgecode.chess.position.ParseException; + /** - * + * PGNParser * * @author Dmitry Samoshin aka gotty */ public class PGNParser { - + public static Game parse(String pgn) throws ParseException { + Game game = new DetailGame(); + Tokenizer pgnTokenizer = new Tokenizer(pgn); + while (pgnTokenizer.hasToken()) { + pgnTokenizer.token(game); + } + return game; + } } diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNUtils.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNUtils.java new file mode 100644 index 0000000..822c31f --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-2020. 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.pgn; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * PGNUtils + * + * @author Dmitry Samoshin aka gotty + */ +public final class PGNUtils { + + private static final char BACKSLASH = '\\'; + private static final String SHIELD_REGEX = "\\\\"; + + private static final String CRLF = "\\r?\\n"; + private static final String SPACE = " "; + + public static String match(String source, String regex) { + Matcher matcher = Pattern.compile( + regex, + Pattern.MULTILINE + ).matcher(source); + if (matcher.find()) { + return matcher.groupCount() > 0 + ? matcher.group(1) + : matcher.group(); + } + return null; + } + + public static boolean isPgn(String source) { + return match( + source, + PGNConstants.PGN_DETECT_REGEX + ) != null; + } + + public static String shield(String source, char[] shields) { + for (char shield : shields) { + if (source.indexOf(shield) >= 0) { + String regexShield = + shield == BACKSLASH + ? SHIELD_REGEX + : String.valueOf(shield); + source = source.replaceAll( + String.format("([%s])", regexShield), + SHIELD_REGEX.concat("$1") + ); + } + } + return source; + } + + public static String stripCrlf(String pgn) { + return pgn.replaceAll(CRLF, SPACE); + } + + private PGNUtils() { + throw new AssertionError( + String.format("No %s instances!", getClass().getName()) + ); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailGame.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailGame.java new file mode 100644 index 0000000..7486dc0 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailGame.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.entity; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * DetailGame + * + * @author Dmitry Samoshin aka gotty + */ +public class DetailGame implements Game, Moves { + + private final DetailMove NULL_MOVE = new DetailMove(0, null); + + private DetailMove currentMove; + private final List moves = new ArrayList<>(); + + private final Map tags = new HashMap<>(); + + public DetailGame() { + currentMove = NULL_MOVE; + } + + @Override + public void addMove(DetailMove move) { + moves.add(move); + currentMove = move; + } + + @Override + public List getMoves() { + return moves; + } + + @Override + public DetailMove nullMove() { + return NULL_MOVE; + } + + @Override + public DetailMove currentMove() { + return currentMove; + } + + @Override + public void addTag(String name, String value) { + tags.put(name, value); + } + + @Override + public String getTag(String name) { + return tags.get(name); + } + + @Override + public Map getTags() { + return tags; + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailMove.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailMove.java new file mode 100644 index 0000000..2b37204 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailMove.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.entity; + +import java.util.ArrayList; +import java.util.List; + +/** + * DetailMove + * + * @author Dmitry Samoshin aka gotty + */ +public class DetailMove implements Move { + + private int ply; + private String move; + + private List comments = new ArrayList<>(); + private List variations = new ArrayList<>(); + + public DetailMove(int ply, String move) { + this.ply = ply; + this.move = move; + } + + @Override + public int ply() { + return ply; + } + + @Override + public String move() { + return move; + } + + public void addComment(String comment) { + comments.add(comment); + } + + public void addVariation(Variation variation) { + variations.add(variation); + } + + public List comments() { + return comments; + } + + public List variations() { + return variations; + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/img/ImageBuilder.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Game.java similarity index 60% rename from chesshog-format/src/main/java/org/hedgecode/chess/img/ImageBuilder.java rename to chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Game.java index fd79aa0..59814c1 100644 --- a/chesshog-format/src/main/java/org/hedgecode/chess/img/ImageBuilder.java +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Game.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018. Developed by Hedgecode. + * Copyright (c) 2018-2020. 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. @@ -14,22 +14,21 @@ * limitations under the License. */ -package org.hedgecode.chess.img; +package org.hedgecode.chess.pgn.entity; -import java.awt.image.RenderedImage; - -import org.hedgecode.chess.position.Builder; -import org.hedgecode.chess.position.Position; +import java.util.Map; /** - * + * Game * * @author Dmitry Samoshin aka gotty */ -public interface ImageBuilder extends Builder { +public interface Game extends Moves { + + void addTag(String name, String value); - String build(Position position); + String getTag(String name); - RenderedImage build(Position position, String boardType, String pieceType) throws ImageException; + Map getTags(); } diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Move.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Move.java new file mode 100644 index 0000000..2b56f10 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Move.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.entity; + +/** + * Move + * + * @author Dmitry Samoshin aka gotty + */ +public interface Move extends Comparable { + + int ply(); + + String move(); + + @Override + default int compareTo(Move move) { + return this.ply() - move.ply(); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Moves.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Moves.java new file mode 100644 index 0000000..c751132 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Moves.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.entity; + +import java.util.List; + +/** + * Moves + * + * @author Dmitry Samoshin aka gotty + */ +public interface Moves { + + void addMove(DetailMove move); + + List getMoves(); + + DetailMove nullMove(); + + DetailMove currentMove(); + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Tag.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Tag.java new file mode 100644 index 0000000..2c0a380 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Tag.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.entity; + +import java.util.Arrays; +import java.util.Comparator; + +import org.hedgecode.chess.pgn.PGNUtils; + +/** + * Tag + * + * @author Dmitry Samoshin aka gotty + */ +public enum Tag { + + EVENT ( "Event", true, Tag.QUESTION ), + SITE ( "Site", true, Tag.QUESTION ), + DATE ( "Date", true, Tag.QUESTION_DATE ), + ROUND ( "Round", true, Tag.QUESTION ), + WHITE ( "White", true, Tag.QUESTION ), + BLACK ( "Black", true, Tag.QUESTION ), + RESULT ( "Result", true, Tag.NON_RESULT ), + WHITE_TITLE ( "WhiteTitle", false, Tag.HYPHEN ), + BLACK_TITLE ( "BlackTitle", false, Tag.HYPHEN ), + WHITE_ELO ( "WhiteElo", false, Tag.HYPHEN ), + BLACK_ELO ( "BlackElo", false, Tag.HYPHEN ), + WHITE_USCF ( "WhiteUSCF", false, Tag.HYPHEN ), + BLACK_USCF ( "BlackUSCF", false, Tag.HYPHEN ), + WHITE_NA ( "WhiteNA", false, Tag.HYPHEN ), + BLACK_NA ( "BlackNA", false, Tag.HYPHEN ), + WHITE_TYPE ( "WhiteType", false, Tag.HYPHEN ), + BLACK_TYPE ( "BlackType", false, Tag.HYPHEN ), + EVENT_DATE ( "EventDate", false, Tag.QUESTION_DATE ), + EVENT_SPONSOR ( "EventSponsor", false, Tag.EMPTY ), + SECTION ( "Section", false, Tag.EMPTY ), + STAGE ( "Stage", false, Tag.EMPTY ), + BOARD ( "Board", false, Tag.EMPTY ), + OPENING ( "Opening", false, Tag.EMPTY ), + VARIATION ( "Variation", false, Tag.EMPTY ), + SUBVARIATION ( "SubVariation", false, Tag.EMPTY ), + ECO ( "ECO", false, Tag.EMPTY ), + NIC ( "NIC", false, Tag.EMPTY ), + TIME ( "Time", false, Tag.QUESTION_TIME ), + UTC_TIME ( "UTCTime", false, Tag.QUESTION_TIME ), + UTC_DATE ( "UTCDate", false, Tag.QUESTION_DATE ), + TIME_CONTROL ( "TimeControl", false, Tag.QUESTION ), + SETUP ( "SetUp", false, Tag.ZERO ), + FEN ( "FEN", false, Tag.EMPTY ), + TERMINATION ( "Termination", false, Tag.EMPTY ), + ANNOTATOR ( "Annotator", false, Tag.EMPTY ), + MODE ( "Mode", false, Tag.EMPTY ), + PLY_COUNT ( "PlyCount", false, Tag.EMPTY ); + + // todo: -> PGNConstants + public static final String EMPTY = ""; + public static final String HYPHEN = "-"; + public static final String ZERO = "0"; + public static final String QUESTION = "?"; + public static final String QUESTION_DATE = "????.??.??"; + public static final String QUESTION_TIME = "??:??:??"; + public static final String NON_RESULT = "*"; + + public static final String TAG_FORMAT = "[%s \"%s\"]"; + + public static final char[] TAG_SHIELD_CHARS = { '\\', '"' }; + + private String tagName; + private boolean isRequired; + private String defaultValue; + + Tag(String name, boolean required, String defValue) { + tagName = name; + isRequired = required; + defaultValue = defValue; + } + + public String getName() { + return tagName; + } + + public boolean isRequired() { + return isRequired; + } + + public String defaultValue() { + return defaultValue; + } + + public static String formatTagValue(String tagValue) { + return PGNUtils.shield( + tagValue, TAG_SHIELD_CHARS + ); + } + + public static Tag[] tags() { + Tag[] tags = values(); + Arrays.sort( + tags, new TagComparator() + ); + return tags; + } + + static class TagComparator implements Comparator { + + @Override + public int compare(Tag tag1, Tag tag2) { + if (tag1.isRequired && tag2.isRequired) { + return tag1.ordinal() - tag2.ordinal(); + } else { + return tag1.isRequired ? -1 + : tag2.isRequired ? 1 + : tag1.name().compareTo(tag2.name()); + } + } + + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Variation.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Variation.java new file mode 100644 index 0000000..3c290e8 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Variation.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.entity; + +import java.util.ArrayList; +import java.util.List; + +/** + * Variation + * + * @author Dmitry Samoshin aka gotty + */ +public class Variation implements Moves { + + private final DetailMove NULL_MOVE = new DetailMove(0, null); + + private DetailMove currentMove; + private final List moves = new ArrayList<>(); + + public Variation() { + currentMove = NULL_MOVE; + } + + @Override + public void addMove(DetailMove move) { + moves.add(move); + currentMove = move; + } + + @Override + public List getMoves() { + return moves; + } + + @Override + public DetailMove nullMove() { + return NULL_MOVE; + } + + @Override + public DetailMove currentMove() { + return currentMove; + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractMovesFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractMovesFormat.java new file mode 100644 index 0000000..0240472 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractMovesFormat.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format; + +import java.util.Arrays; + +import org.hedgecode.chess.pgn.entity.Move; + +/** + * AbstractMovesFormat + * + * @author Dmitry Samoshin aka gotty + */ +public abstract class AbstractMovesFormat implements MovesFormat { + + @Override + public String format(Move[] moves) { + return format( + Arrays.asList(moves) + ); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractPGNFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractPGNFormat.java new file mode 100644 index 0000000..b5b0e47 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractPGNFormat.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.hedgecode.chess.pgn.PGNUtils; +import org.hedgecode.chess.pgn.entity.Move; +import org.hedgecode.chess.pgn.entity.Tag; +import org.hedgecode.chess.pgn.format.wrap.ExportWrapper; +import org.hedgecode.chess.pgn.format.wrap.MovesWrapper; + +import static org.hedgecode.chess.pgn.PGNConstants.*; + +/** + * AbstractPGNFormat + * + * @author Dmitry Samoshin aka gotty + */ +public abstract class AbstractPGNFormat implements PGNFormat { + + private final DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd"); + private final DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); + + private final Map pgnTags = new HashMap<>(); + + private String pgnMoves; + private List moves; + + private MovesWrapper wrapper; + + public AbstractPGNFormat() { + this.wrapper = new ExportWrapper(); + } + + public AbstractPGNFormat(MovesWrapper wrapper) { + this.wrapper = wrapper; + } + + @Override + public void addTag(Tag tag, String value) { + pgnTags.put(tag, value); + } + + @Override + public void addTags(Map tags) { + for (Tag tag : Tag.tags()) { + String value = tags.get(tag.getName()); + if (value != null) { + pgnTags.put(tag, value); + } + } + } + + @Override + public String getTag(Tag tag) { + return pgnTags.get(tag); + } + + @Override + public void addMoves(String moves) { + pgnMoves = moves; + } + + @Override + public void addMoves(Move[] moves) { + this.moves = Arrays.asList(moves); + } + + @Override + public void addMoves(List moves) { + this.moves = moves; + } + + @Override + public String formatDate(Date date) { + return dateFormat.format(date); + } + + @Override + public String formatTime(Date time) { + return timeFormat.format(time); + } + + protected String formatMoves() { + if (pgnMoves == null) { + pgnMoves = movesFormat().format(moves); + } else { + pgnMoves = PGNUtils.stripCrlf(pgnMoves); + } + return wrapper.wrap( + pgnMoves.concat( + PGN_SPACE + ).concat( + result() + ) + ); + } + + private String result() { + String result = pgnTags.get(Tag.RESULT); + return result != null ? result : Tag.RESULT.defaultValue(); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportMovesFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportMovesFormat.java new file mode 100644 index 0000000..4de3b41 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportMovesFormat.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format; + +import java.util.Collections; +import java.util.List; + +import org.hedgecode.chess.pgn.entity.DetailMove; +import org.hedgecode.chess.pgn.entity.Move; +import org.hedgecode.chess.pgn.entity.Moves; +import org.hedgecode.chess.pgn.entity.Variation; + +import static org.hedgecode.chess.pgn.PGNConstants.*; + +/** + * ExportMovesFormat + * + * @author Dmitry Samoshin aka gotty + */ +public class ExportMovesFormat extends AbstractMovesFormat { + + @Override + public String format(List moves) { + Collections.sort(moves); + StringBuilder sb = new StringBuilder(); + Move prevMove = null; + for (Move move : moves) { + sb.append( + formatSan(move, prevMove) + ).append( + PGN_SPACE + ); + if (move instanceof DetailMove) { + DetailMove detailMove = (DetailMove) move; + sb.append( + formatAddition(detailMove) + ); + } + prevMove = move; + } + return sb.toString().trim(); + } + + @Override + public String format(Moves moves) { + Collections.sort(moves.getMoves()); + StringBuilder sb = new StringBuilder(); + sb.append( + formatComment(moves.nullMove()) + ); + DetailMove prevMove = null; + for (DetailMove move : moves.getMoves()) { + sb.append( + formatSan(move, prevMove) + ).append( + PGN_SPACE + ).append( + formatAddition(move) + ); + prevMove = move; + } + return sb.toString().trim(); + } + + private String formatSan(Move move, Move prevMove) { + return move.ply() % 2 != 0 + ? whiteFormat(move) + : blackFormat(move, prevMove); + } + + private String whiteFormat(Move move) { + return String.format( + WHITE_MOVE_FORMAT, move.ply() / 2 + 1, move.move() + ); + } + + private String blackFormat(Move move, Move prevMove) { + if (prevMove != null && prevMove instanceof DetailMove) { + DetailMove detailMove = (DetailMove) prevMove; + if (detailMove.comments().size() > 0 || detailMove.variations().size() > 0) { + return String.format( + BLACK_MOVE_DOT_FORMAT, move.ply() / 2, move.move() + ); + } + } + return String.format(BLACK_MOVE_FORMAT, move.move()); + } + + private String formatAddition(DetailMove move) { + StringBuilder sb = new StringBuilder(); + sb.append( + formatComment(move) + ).append( + formatVariation(move) + ); + return sb.toString(); + } + + private String formatComment(DetailMove move) { + StringBuilder sb = new StringBuilder(); + for (String comment : move.comments()) { + sb.append( + String.format(COMMENT_FORMAT, comment) + ).append(PGN_SPACE); + } + return sb.toString(); + } + + private String formatVariation(DetailMove move) { + StringBuilder sb = new StringBuilder(); + for (Variation variation : move.variations()) { + sb.append( + String.format(VARIATION_FORMAT, format(variation)) + ).append(PGN_SPACE); + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportPGNFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportPGNFormat.java new file mode 100644 index 0000000..68e4a02 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportPGNFormat.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format; + +import org.hedgecode.chess.pgn.entity.Tag; + +import static org.hedgecode.chess.pgn.PGNConstants.*; + +/** + * ExportPGNFormat + * + * @author Dmitry Samoshin aka gotty + */ +public class ExportPGNFormat extends AbstractPGNFormat { + + @Override + public String format() { + StringBuilder sb = new StringBuilder(); + for (Tag tag : Tag.tags()) { + String tagValue = getTag(tag); + if (tag.isRequired() || tagValue != null) { + sb.append( + String.format( + Tag.TAG_FORMAT, + tag.getName(), + tagValue != null + ? Tag.formatTagValue(tagValue) + : tag.defaultValue() + ) + ).append(PGN_CRLF); + } + } + sb.append(PGN_CRLF).append( + formatMoves() + ).append(PGN_CRLF); + return sb.toString(); + } + + @Override + public ExportMovesFormat movesFormat() { + return new ExportMovesFormat(); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/MovesFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/MovesFormat.java new file mode 100644 index 0000000..fbcc266 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/MovesFormat.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format; + +import java.util.List; + +import org.hedgecode.chess.pgn.entity.Move; +import org.hedgecode.chess.pgn.entity.Moves; + +/** + * MovesFormat + * + * @author Dmitry Samoshin aka gotty + */ +public interface MovesFormat { + + String format(Move[] moves); + + String format(List moves); + + String format(Moves moves); + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/PGNFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/PGNFormat.java new file mode 100644 index 0000000..16e1de0 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/PGNFormat.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.hedgecode.chess.pgn.entity.Move; +import org.hedgecode.chess.pgn.entity.Tag; + +/** + * PGNFormat + * + * @author Dmitry Samoshin aka gotty + */ +public interface PGNFormat { + + String format(); + + void addTag(Tag tag, String value); + + void addTags(Map tags); + + String getTag(Tag tag); + + void addMoves(String moves); + + void addMoves(Move[] moves); + + void addMoves(List moves); + + Format movesFormat(); + + String formatDate(Date date); + + String formatTime(Date time); + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReduceMovesFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReduceMovesFormat.java new file mode 100644 index 0000000..283dddd --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReduceMovesFormat.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format; + +import java.util.Collections; +import java.util.List; + +import org.hedgecode.chess.pgn.entity.Move; +import org.hedgecode.chess.pgn.entity.Moves; + +import static org.hedgecode.chess.pgn.PGNConstants.*; + +/** + * ReduceMovesFormat + * + * @author Dmitry Samoshin aka gotty + */ +public class ReduceMovesFormat extends AbstractMovesFormat { + + @Override + public String format(List moves) { + Collections.sort(moves); + StringBuilder sb = new StringBuilder(); + for (Move move : moves) { + sb.append( + formatSan(move) + ).append( + PGN_SPACE + ); + } + return sb.toString().trim(); + } + + @Override + public String format(Moves moves) { + return format( + moves.getMoves().toArray( + new Move[moves.getMoves().size()] + ) + ); + } + + private String formatSan(Move move) { + return move.ply() % 2 != 0 + ? String.format(WHITE_MOVE_FORMAT, move.ply() / 2 + 1, move.move()) + : String.format(BLACK_MOVE_FORMAT, move.move()); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReducePGNFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReducePGNFormat.java new file mode 100644 index 0000000..e5ef938 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReducePGNFormat.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format; + +import org.hedgecode.chess.pgn.entity.Tag; + +import static org.hedgecode.chess.pgn.PGNConstants.*; + +/** + * ReducePGNFormat + * + * @author Dmitry Samoshin aka gotty + */ +public class ReducePGNFormat extends AbstractPGNFormat { + + @Override + public String format() { + StringBuilder sb = new StringBuilder(); + for (Tag tag : Tag.tags()) { + String tagValue = getTag(tag); + if (tag.isRequired()) { + sb.append( + String.format( + Tag.TAG_FORMAT, + tag.getName(), + tagValue != null + ? Tag.formatTagValue(tagValue) + : tag.defaultValue() + ) + ).append(PGN_CRLF); + } + } + sb.append(PGN_CRLF).append( + formatMoves() + ).append(PGN_CRLF); + return sb.toString(); + } + + @Override + public ReduceMovesFormat movesFormat() { + return new ReduceMovesFormat(); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/AbstractWrapper.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/AbstractWrapper.java new file mode 100644 index 0000000..d39172b --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/AbstractWrapper.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format.wrap; + +import static org.hedgecode.chess.pgn.PGNConstants.*; + +/** + * AbstractWrapper + * + * @author Dmitry Samoshin aka gotty + */ +public abstract class AbstractWrapper implements MovesWrapper { + + protected abstract int length(); + + @Override + public String wrap(String moves) { + StringBuilder sb = new StringBuilder(); + int startIndex = 0, endIndex = 0; + moves = moves.concat(PGN_SPACE); + int index = moves.indexOf(PGN_SPACE); + while (index >= 0) { + if (index - startIndex >= length()) { + sb.append( + moves.substring(startIndex, endIndex) + ).append(PGN_CRLF); + startIndex = endIndex + 1; + } + endIndex = index; + index = moves.indexOf(PGN_SPACE, index + 1); + } + sb.append( + moves.substring(startIndex) + ).append(PGN_CRLF); + return sb.toString(); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/ExportWrapper.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/ExportWrapper.java new file mode 100644 index 0000000..67667a7 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/ExportWrapper.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format.wrap; + +import org.hedgecode.chess.pgn.PGNConstants; + +/** + * ExportWrapper + * + * @author Dmitry Samoshin aka gotty + */ +public class ExportWrapper extends AbstractWrapper { + + @Override + protected int length() { + return PGNConstants.PGN_DEF_LINE_LENGTH; + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/LineWrapper.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/LineWrapper.java new file mode 100644 index 0000000..80cb3e7 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/LineWrapper.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format.wrap; + +import org.hedgecode.chess.pgn.PGNConstants; + +/** + * LineWrapper + * + * @author Dmitry Samoshin aka gotty + */ +public class LineWrapper extends AbstractWrapper { + + @Override + protected int length() { + return PGNConstants.PGN_MAX_LINE_LENGTH; + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/MovesWrapper.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/MovesWrapper.java new file mode 100644 index 0000000..809d5cb --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/MovesWrapper.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.format.wrap; + +/** + * MovesWrapper + * + * @author Dmitry Samoshin aka gotty + */ +public interface MovesWrapper { + + String wrap(String moves); + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/CommentToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/CommentToken.java new file mode 100644 index 0000000..660475a --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/CommentToken.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.token; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hedgecode.chess.pgn.entity.Moves; +import org.hedgecode.chess.position.ParseException; + +/** + * CommentToken + * + * @author Dmitry Samoshin aka gotty + */ +public class CommentToken implements Token { + + private static final String COMMENT_REGEX = "^\\s*\\{([^}]+)\\}"; + private static final Pattern COMMENT_PATTERN = Pattern.compile(COMMENT_REGEX); + private static final int COMMENT_GROUP = 1; + + @Override + public int token(Moves moves, String pgn) throws ParseException { + Matcher matcher = COMMENT_PATTERN.matcher(pgn); + if (!matcher.find()) { + throw new ParseException("parse.pgn.incorrect.comment", pgn); + } else { + String comment = matcher.group(COMMENT_GROUP); + moves.currentMove().addComment(comment); + } + return matcher.group().length(); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/EmptyToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/EmptyToken.java new file mode 100644 index 0000000..838d020 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/EmptyToken.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.token; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hedgecode.chess.pgn.PGNConstants; +import org.hedgecode.chess.pgn.entity.Game; +import org.hedgecode.chess.position.ParseException; + +/** + * EmptyToken + * + * @author Dmitry Samoshin aka gotty + */ +public class EmptyToken implements Token { + + private static final String EMPTY_REGEX = "^\\s*$"; + private static final Pattern EMPTY_PATTERN = Pattern.compile(EMPTY_REGEX, Pattern.MULTILINE); + + @Override + public int token(Game game, String pgn) throws ParseException { + Matcher matcher = EMPTY_PATTERN.matcher(pgn); + if (!matcher.find()) { + throw new ParseException("parse.pgn.incorrect.empty", pgn); + } + return pgn.indexOf(PGNConstants.PGN_CRLF) + 1; + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MoveToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MoveToken.java new file mode 100644 index 0000000..e87bf9b --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MoveToken.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.token; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hedgecode.chess.pgn.entity.DetailMove; +import org.hedgecode.chess.pgn.entity.Moves; +import org.hedgecode.chess.position.ParseException; + +/** + * MoveToken + * + * @author Dmitry Samoshin aka gotty + */ +public class MoveToken implements Token { + + private static final String MOVE_REGEX = "^\\s*(([0-9]+)\\s*(\\.|\\.{3})\\s*)*([PNBRQK]?[a-hx1-8\\-O]+(=[NBRQ])?([+#])?)"; + private static final Pattern MOVE_PATTERN = Pattern.compile(MOVE_REGEX); + private static final int PLY_GROUP = 2, DOT_GROUP = 3, MOVE_GROUP = 4; + private static final String DOT_BLACK = "..."; + + @Override + public int token(Moves moves, String pgn) throws ParseException { + Matcher matcher = MOVE_PATTERN.matcher(pgn); + if (!matcher.find()) { + throw new ParseException("parse.pgn.incorrect.move", pgn); + } else { + boolean isBlackMove = isBlackMove(matcher.group(DOT_GROUP)); + String plyStr = matcher.group(PLY_GROUP); + int ply = plyStr != null + ? Integer.parseInt(plyStr) * 2 - (isBlackMove ? 0 : 1) + : moves.currentMove().ply() + 1; + String move = matcher.group(MOVE_GROUP); + moves.addMove( + new DetailMove(ply, move) + ); + } + return matcher.group().length(); + } + + private boolean isBlackMove(String dotGroup) { + return dotGroup == null || dotGroup.equals(DOT_BLACK); + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MovesToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MovesToken.java new file mode 100644 index 0000000..6667a27 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MovesToken.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.token; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hedgecode.chess.pgn.PGNUtils; +import org.hedgecode.chess.pgn.entity.Moves; +import org.hedgecode.chess.position.ParseException; + +/** + * MovesToken + * + * @author Dmitry Samoshin aka gotty + */ +public class MovesToken implements Token { + + enum Type { + + MOVE ( new MoveToken() ), + COMMENT ( new CommentToken() ), + VARIATION ( new VariationToken() ); + + private Token token; + + public Token token() { + return token; + } + + Type(Token token) { + this.token = token; + } + } + + private static final String MOVES_REGEX = "^\\s*([^\\s])"; + private static final Pattern MOVES_PATTERN = Pattern.compile(MOVES_REGEX); + + private static final String COMMENT = "{"; + private static final String VARIATION = "("; + + @Override + public int token(Entity entity, String pgn) throws ParseException { + int pgnLength = pgn.length(); + pgn = PGNUtils.stripCrlf(pgn); + Token token = assignToken(pgn); + while (token != null) { + int index = token.token(entity, pgn); + pgn = pgn.substring(index); + token = assignToken(pgn); + } + return pgnLength; + } + + private Token assignToken(String pgn) { + Token token = null; + Matcher moveMatcher = MOVES_PATTERN.matcher(pgn); + if (moveMatcher.find()) { + switch (moveMatcher.group(1)) { + case COMMENT: + token = Type.COMMENT.token(); + break; + case VARIATION: + token = Type.VARIATION.token(); + break; + default: + token = Type.MOVE.token(); + } + } + return token; + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/TagToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/TagToken.java new file mode 100644 index 0000000..91aa8f8 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/TagToken.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.token; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.hedgecode.chess.pgn.PGNConstants; +import org.hedgecode.chess.pgn.entity.Game; +import org.hedgecode.chess.position.ParseException; + +/** + * TagToken + * + * @author Dmitry Samoshin aka gotty + */ +public class TagToken implements Token { + + private static final String TAG_REGEX = "^\\[([^\"]+)\\s\"([^\"]+)\"\\]$"; + private static final Pattern TAG_PATTERN = Pattern.compile(TAG_REGEX, Pattern.MULTILINE); + private static final int NAME_GROUP = 1, VALUE_GROUP = 2; + + @Override + public int token(Game game, String pgn) throws ParseException { + Matcher matcher = TAG_PATTERN.matcher(pgn); + if (!matcher.find()) { + throw new ParseException("parse.pgn.incorrect.tag", pgn); + } else { + String name = matcher.group(NAME_GROUP); + String value = matcher.group(VALUE_GROUP); + game.addTag(name, value); + } + return pgn.indexOf(PGNConstants.PGN_CRLF) + 1; + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Token.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Token.java new file mode 100644 index 0000000..a0aa9f3 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Token.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.token; + +import org.hedgecode.chess.position.ParseException; + +/** + * Token + * + * @author Dmitry Samoshin aka gotty + */ +public interface Token { + + int token(Entity entity, String pgn) throws ParseException; + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Tokenizer.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Tokenizer.java new file mode 100644 index 0000000..3421259 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Tokenizer.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.token; + +import java.util.regex.Pattern; + +import org.hedgecode.chess.pgn.entity.Game; +import org.hedgecode.chess.position.ParseException; + +/** + * Tokenizer + * + * @author Dmitry Samoshin aka gotty + */ +public class Tokenizer { + + enum Type { + + TAG ( new TagToken() ), + EMPTY ( new EmptyToken() ), + MOVES ( new MovesToken() ); + + private Token token; + + public Token token() { + return token; + } + + Type(Token token) { + this.token = token; + } + } + + private static final Pattern TAG_PATTERN = Pattern.compile("^\\[[^]]+\\]$", Pattern.MULTILINE); + private static final Pattern EMPTY_PATTERN = Pattern.compile("^\\s*$", Pattern.MULTILINE); + private static final Pattern MOVES_PATTERN = Pattern.compile("^\\s*([^\\s])", Pattern.MULTILINE); + + private final String pgn; + + private Token token; + private String tokenPgn; + + public Tokenizer(String pgn) { + this.pgn = pgn; + this.tokenPgn = pgn; + } + + public boolean hasToken() { + assignToken(); + return token != null; + } + + public void token(Game game) throws ParseException { + if (token == null) { + throw new ParseException("parse.pgn.null.token"); + } + int index = token.token(game, tokenPgn); + tokenPgn = tokenPgn.substring(index); + token = null; + } + + public String sourcePgn() { + return pgn; + } + + private void assignToken() { + token = null; + if (!tokenPgn.isEmpty()) { + if (TAG_PATTERN.matcher(tokenPgn).find()) { + token = Type.TAG.token(); + } else if (EMPTY_PATTERN.matcher(tokenPgn).find()) { + token = Type.EMPTY.token(); + } else if (MOVES_PATTERN.matcher(tokenPgn).find()) { + token = Type.MOVES.token(); + } + } + } + +} diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/VariationToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/VariationToken.java new file mode 100644 index 0000000..377df13 --- /dev/null +++ b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/VariationToken.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2020. 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.pgn.token; + +import java.util.regex.Pattern; + +import org.hedgecode.chess.pgn.entity.Moves; +import org.hedgecode.chess.pgn.entity.Variation; +import org.hedgecode.chess.position.ParseException; + +/** + * VariationToken + * + * @author Dmitry Samoshin aka gotty + */ +public class VariationToken implements Token { + + private static final char OPEN_VARIATION = '('; + private static final char CLOSE_VARIATION = ')'; + + private static final String VARIATION_REGEX = "^\\s*\\(([^)]+)\\)"; + private static final Pattern VARIATION_PATTERN = Pattern.compile(VARIATION_REGEX); + + @Override + public int token(Moves moves, String pgn) throws ParseException { + int startToken = pgn.indexOf(OPEN_VARIATION); + int endToken = endToken(pgn, startToken); + Variation variation = new Variation(); + Token variationToken = new MovesToken<>(); + variationToken.token( + variation, pgn.substring(startToken + 1, endToken) + ); + moves.currentMove().addVariation(variation); + return endToken + 1; + } + + private int endToken(String pgn, int startToken) throws ParseException { + int open = pgn.indexOf(OPEN_VARIATION, startToken + 1); + int close = pgn.indexOf(CLOSE_VARIATION, startToken + 1); + while (open >= 0 && open < close) { + open = pgn.indexOf(OPEN_VARIATION, open + 1); + close = pgn.indexOf(CLOSE_VARIATION, close + 1); + } + if (close == -1) { + throw new ParseException("parse.pgn.incorrect.variation", pgn); + } + return close; + } + +} diff --git a/chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings.properties b/chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings.properties index 66a5a66..67f3df1 100644 --- a/chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings.properties +++ b/chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings.properties @@ -1,4 +1,4 @@ -# Copyright (c) 2018. Developed by Hedgecode. +# Copyright (c) 2018-2020. 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. @@ -19,6 +19,14 @@ parse.null.input.string=Null input string parse.ascii.incorrect.board=Incorrect board parse.fen.invalid.string=Invalid input parse.fen.incorrect.board=Incorrect board + +parse.pgn.null.token=Unable to define token for PGN +parse.pgn.incorrect.tag=Incorrect PGN tag +parse.pgn.incorrect.empty=Missed blank line in PGN +parse.pgn.incorrect.move=Incorrect PGN move +parse.pgn.incorrect.comment=Incorrect PGN comment +parse.pgn.incorrect.variation=Incorrect PGN variation + parse.tcd.index.out.of.bounds=Array Index Out Of Bounds parse.tcd.invalid.bytes=Invalid input bytes parse.tcd.incorrect.color=Incorrect color diff --git a/chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings_ru.properties b/chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings_ru.properties index 05dad64..ce7ffa7 100644 --- a/chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings_ru.properties +++ b/chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings_ru.properties @@ -1,4 +1,4 @@ -# Copyright (c) 2018. Developed by Hedgecode. +# Copyright (c) 2018-2020. 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. @@ -18,6 +18,14 @@ parse.null.input.string= parse.ascii.incorrect.board= parse.fen.invalid.string= parse.fen.incorrect.board= + +parse.pgn.null.token= +parse.pgn.incorrect.tag= +parse.pgn.incorrect.empty= +parse.pgn.incorrect.move= +parse.pgn.incorrect.comment= +parse.pgn.incorrect.variation= + parse.tcd.index.out.of.bounds= parse.tcd.invalid.bytes= parse.tcd.incorrect.color=