From: gotty Date: Fri, 17 Jan 2020 16:01:41 +0000 (+0300) Subject: [LIB-13] Add PGN format classes X-Git-Url: https://git.hedgecode.org/?p=chesshog-scanner.git;a=commitdiff_plain;h=d1936e2e61d422117f733e9fffec0976356121ea [LIB-13] Add PGN format classes --- diff --git a/src/main/java/org/hedgecode/chess/scanner/format/AbstractPGNFormat.java b/src/main/java/org/hedgecode/chess/scanner/format/AbstractPGNFormat.java new file mode 100644 index 0000000..3aee762 --- /dev/null +++ b/src/main/java/org/hedgecode/chess/scanner/format/AbstractPGNFormat.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019-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.scanner.format; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * AbstractPGNFormat + * + * @author Dmitry Samoshin aka gotty + */ +public abstract class AbstractPGNFormat implements PGNFormat { + + private static final String MOVES_FORMAT = "%s %s"; + + 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; + + @Override + public void addTag(PGNTag tag, String value) { + pgnTags.put(tag, value); + } + + public String getTag(PGNTag tag) { + return pgnTags.get(tag); + } + + @Override + public void addMoves(String moves) { + pgnMoves = moves; + } + + @Override + public String formatDate(Date date) { + return dateFormat.format(date); + } + + @Override + public String formatTime(Date time) { + return timeFormat.format(time); + } + + protected String formatMoves() { + return String.format( + MOVES_FORMAT, moves(), result() + ); + } + + protected String formatTagValue(String value) { + return value.replaceAll("([\\\\\"])", "\\\\$1"); + } + + private String moves() { + return pgnMoves != null ? pgnMoves : PGNTag.EMPTY; + } + + private String result() { + String result = pgnTags.get(PGNTag.RESULT); + return result != null ? result : PGNTag.RESULT.defaultValue(); + } + +} diff --git a/src/main/java/org/hedgecode/chess/scanner/format/GameData.java b/src/main/java/org/hedgecode/chess/scanner/format/GameData.java index bb265bd..6abc0ed 100644 --- a/src/main/java/org/hedgecode/chess/scanner/format/GameData.java +++ b/src/main/java/org/hedgecode/chess/scanner/format/GameData.java @@ -29,6 +29,4 @@ public interface GameData { Move[] moves(); - String formatMoves(); - } diff --git a/src/main/java/org/hedgecode/chess/scanner/format/Move.java b/src/main/java/org/hedgecode/chess/scanner/format/Move.java index 8b9a02e..a4bc9af 100644 --- a/src/main/java/org/hedgecode/chess/scanner/format/Move.java +++ b/src/main/java/org/hedgecode/chess/scanner/format/Move.java @@ -21,10 +21,15 @@ package org.hedgecode.chess.scanner.format; * * @author Dmitry Samoshin aka gotty */ -public interface Move { +public interface Move extends Comparable { - int number(); + int ply(); String move(); + @Override + default int compareTo(Move move) { + return this.ply() - move.ply(); + } + } diff --git a/src/main/java/org/hedgecode/chess/scanner/format/MovesFormat.java b/src/main/java/org/hedgecode/chess/scanner/format/MovesFormat.java new file mode 100644 index 0000000..5317efa --- /dev/null +++ b/src/main/java/org/hedgecode/chess/scanner/format/MovesFormat.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019-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.scanner.format; + +/** + * MovesFormat + * + * @author Dmitry Samoshin aka gotty + */ +public interface MovesFormat { + + String format(Move[] moves); + +} diff --git a/src/main/java/org/hedgecode/chess/scanner/format/PGNConstants.java b/src/main/java/org/hedgecode/chess/scanner/format/PGNConstants.java new file mode 100644 index 0000000..02b8bca --- /dev/null +++ b/src/main/java/org/hedgecode/chess/scanner/format/PGNConstants.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-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.scanner.format; + +/** + * 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_CRLF = "\n"; + + public static final String WHITE_MOVE_FORMAT = "%d. %s "; + public static final String BLACK_MOVE_FORMAT = "%s "; + + + private PGNConstants() { + throw new AssertionError( + String.format("No %s instances!", getClass().getName()) + ); + } + +} diff --git a/src/main/java/org/hedgecode/chess/scanner/format/PGNFormat.java b/src/main/java/org/hedgecode/chess/scanner/format/PGNFormat.java new file mode 100644 index 0000000..fe7d4ab --- /dev/null +++ b/src/main/java/org/hedgecode/chess/scanner/format/PGNFormat.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-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.scanner.format; + +import java.util.Date; + +/** + * PGNFormat + * + * @author Dmitry Samoshin aka gotty + */ +public interface PGNFormat { + + void addTag(PGNTag tag, String value); + + void addMoves(String moves); + + String format(); + + String formatDate(Date date); + + String formatTime(Date time); + +} diff --git a/src/main/java/org/hedgecode/chess/scanner/entity/PGNTag.java b/src/main/java/org/hedgecode/chess/scanner/format/PGNTag.java similarity index 85% rename from src/main/java/org/hedgecode/chess/scanner/entity/PGNTag.java rename to src/main/java/org/hedgecode/chess/scanner/format/PGNTag.java index 4d2fb29..10d6fec 100644 --- a/src/main/java/org/hedgecode/chess/scanner/entity/PGNTag.java +++ b/src/main/java/org/hedgecode/chess/scanner/format/PGNTag.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package org.hedgecode.chess.scanner.entity; +package org.hedgecode.chess.scanner.format; + +import java.util.Arrays; +import java.util.Comparator; /** * PGNTag @@ -26,10 +29,10 @@ public enum PGNTag { EVENT ( "Event", true, PGNTag.QUESTION ), SITE ( "Site", true, PGNTag.QUESTION ), DATE ( "Date", true, PGNTag.QUESTION_DATE ), - TIME ( "Time", false, PGNTag.QUESTION_TIME ), ROUND ( "Round", true, PGNTag.QUESTION ), WHITE ( "White", true, PGNTag.QUESTION ), BLACK ( "Black", true, PGNTag.QUESTION ), + RESULT ( "Result", true, PGNTag.NON_RESULT ), WHITE_TITLE ( "WhiteTitle", false, PGNTag.HYPHEN ), BLACK_TITLE ( "BlackTitle", false, PGNTag.HYPHEN ), WHITE_ELO ( "WhiteElo", false, PGNTag.HYPHEN ), @@ -50,16 +53,16 @@ public enum PGNTag { SUBVARIATION ( "SubVariation", false, PGNTag.EMPTY ), ECO ( "ECO", false, PGNTag.EMPTY ), NIC ( "NIC", false, PGNTag.EMPTY ), - UTC_DATE ( "UTCDate", false, PGNTag.QUESTION_DATE ), + TIME ( "Time", false, PGNTag.QUESTION_TIME ), UTC_TIME ( "UTCTime", false, PGNTag.QUESTION_TIME ), + UTC_DATE ( "UTCDate", false, PGNTag.QUESTION_DATE ), TIME_CONTROL ( "TimeControl", false, PGNTag.QUESTION ), SETUP ( "SetUp", false, PGNTag.ZERO ), FEN ( "FEN", false, PGNTag.EMPTY ), TERMINATION ( "Termination", false, PGNTag.EMPTY ), ANNOTATOR ( "Annotator", false, PGNTag.EMPTY ), MODE ( "Mode", false, PGNTag.EMPTY ), - PLY_COUNT ( "PlyCount", false, PGNTag.EMPTY ), - RESULT ( "Result", true, PGNTag.NON_RESULT ); + PLY_COUNT ( "PlyCount", false, PGNTag.EMPTY ); public static final String EMPTY = ""; public static final String HYPHEN = "-"; @@ -93,4 +96,27 @@ public enum PGNTag { return defaultValue; } + public static PGNTag[] tags() { + PGNTag[] tags = values(); + Arrays.sort( + tags, new TagComparator() + ); + return tags; + } + + static class TagComparator implements Comparator { + + @Override + public int compare(PGNTag tag1, PGNTag 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/src/main/java/org/hedgecode/chess/scanner/format/ReducePGNFormat.java b/src/main/java/org/hedgecode/chess/scanner/format/ReducePGNFormat.java new file mode 100644 index 0000000..f595dd7 --- /dev/null +++ b/src/main/java/org/hedgecode/chess/scanner/format/ReducePGNFormat.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-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.scanner.format; + +import static org.hedgecode.chess.scanner.format.PGNConstants.*; + +/** + * ReducePGNFormat + * + * @author Dmitry Samoshin aka gotty + */ +public class ReducePGNFormat extends AbstractPGNFormat { + + @Override + public String format() { + StringBuilder sb = new StringBuilder(); + for (PGNTag tag : PGNTag.tags()) { + String tagValue = getTag(tag); + if (tag.isRequired() || tagValue != null) { + sb.append( + String.format( + PGNTag.TAG_FORMAT, + tag.getName(), + tagValue != null ? formatTagValue(tagValue) : tag.defaultValue() + ) + ).append(PGN_CRLF); + } + } + sb.append(PGN_CRLF).append( + formatMoves() + ).append(PGN_CRLF); + return sb.toString(); + } + +} diff --git a/src/main/java/org/hedgecode/chess/scanner/format/TypeMovesFormat.java b/src/main/java/org/hedgecode/chess/scanner/format/TypeMovesFormat.java new file mode 100644 index 0000000..da2bfbc --- /dev/null +++ b/src/main/java/org/hedgecode/chess/scanner/format/TypeMovesFormat.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019-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.scanner.format; + +import java.util.Arrays; + +import static org.hedgecode.chess.scanner.format.PGNConstants.*; + +/** + * TypeMovesFormat + * + * @author Dmitry Samoshin aka gotty + */ +public enum TypeMovesFormat { + + LINE ( new LineMovesFormat() ), + WRAP ( new WrapMovesFormat() ); + + private MovesFormat movesFormat; + + TypeMovesFormat(MovesFormat movesFormat) { + this.movesFormat = movesFormat; + } + + public String format(Move[] moves) { + return movesFormat.format(moves); + } + + static class LineMovesFormat extends WrapMovesFormat { + + LineMovesFormat() { + super(PGN_MAX_LINE_LENGTH); + } + + } + + static class WrapMovesFormat implements MovesFormat { + + private int lineLength; + + WrapMovesFormat() { + this(PGN_DEF_LINE_LENGTH); + } + + WrapMovesFormat(int length) { + lineLength = length; + } + + @Override + public String format(Move[] moves) { + Arrays.sort(moves); + int maxLength = lineLength - PGN_CRLF.length(); + int length = 0; + StringBuilder sb = new StringBuilder(); + for (Move move : moves) { + String nextMove = move.ply() % 2 != 0 + ? String.format(WHITE_MOVE_FORMAT, move.ply() / 2 + 1, move.move()) + : String.format(BLACK_MOVE_FORMAT, move.move()); + if (length + nextMove.length() > maxLength) { + sb.append(PGN_CRLF); + length = 0; + } + sb.append(nextMove); + length += nextMove.length(); + } + return sb.toString().trim(); + } + + } + +} diff --git a/src/main/java/org/hedgecode/chess/scanner/json/format/AbstractPGNFormat.java b/src/main/java/org/hedgecode/chess/scanner/json/format/AbstractPGNFormat.java deleted file mode 100644 index d0a3b84..0000000 --- a/src/main/java/org/hedgecode/chess/scanner/json/format/AbstractPGNFormat.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2019-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.scanner.json.format; - -import java.util.HashMap; -import java.util.Map; - -import org.hedgecode.chess.scanner.entity.PGNEntity; -import org.hedgecode.chess.scanner.entity.PGNTag; - -import static org.hedgecode.chess.scanner.ChessHogScannerConstants.*; - -/** - * AbstractPGNFormat - * - * @author Dmitry Samoshin aka gotty - */ -public abstract class AbstractPGNFormat extends AbstractBaseFormat implements PGNEntity { - - private static final String EMPTY = ""; - - private static final String MOVES_FORMAT = "%s %s"; - - private final Map pgnTags = new HashMap<>(); - private String pgnMoves; - - protected abstract void assignPGN(); - - protected void addTag(PGNTag tag, String value) { - pgnTags.put(tag, value); - } - - protected void addMoves(String moves) { - pgnMoves = moves; - } - - private String moves() { - return pgnMoves != null ? pgnMoves : EMPTY; - } - - private String result() { - String result = pgnTags.get(PGNTag.RESULT); - return result != null ? result : PGNTag.RESULT.defaultValue(); - } - - @Override - public String pgn() { - assignPGN(); - StringBuilder sb = new StringBuilder(); - for (PGNTag tag : PGNTag.values()) { - String tagValue = pgnTags.get(tag); - if (tag.isRequired() || tagValue != null) { - sb.append( - String.format( - PGNTag.TAG_FORMAT, - tag.getName(), - tagValue != null ? tagValue : tag.defaultValue() - ) - ).append(CRLF); - } - } - sb.append(CRLF).append( - String.format(MOVES_FORMAT, moves(), result()) - ).append(CRLF); - return sb.toString(); - } - -} diff --git a/src/main/java/org/hedgecode/chess/scanner/json/format/JSONGameData.java b/src/main/java/org/hedgecode/chess/scanner/json/format/JSONGameData.java index 9761b01..33091a8 100644 --- a/src/main/java/org/hedgecode/chess/scanner/json/format/JSONGameData.java +++ b/src/main/java/org/hedgecode/chess/scanner/json/format/JSONGameData.java @@ -30,9 +30,6 @@ import org.hedgecode.chess.scanner.format.Room; */ public class JSONGameData implements GameData { - private static final String WHITE_MOVE_FORMAT = "%d. %s "; - private static final String BLACK_MOVE_FORMAT = "%s "; - @SerializedName("game") private JSONGame game; @@ -60,21 +57,4 @@ public class JSONGameData implements GameData { return moves; } - @Override - public String formatMoves() { - StringBuilder sb = new StringBuilder(); - for (Move move : moves) { - if (move.number() % 2 == 0) { - sb.append( - String.format(WHITE_MOVE_FORMAT, move.number() / 2 + 1, move.move()) - ); - } else { - sb.append( - String.format(BLACK_MOVE_FORMAT, move.move()) - ); - } - } - return sb.toString(); - } - } diff --git a/src/main/java/org/hedgecode/chess/scanner/json/format/JSONGameFormat.java b/src/main/java/org/hedgecode/chess/scanner/json/format/JSONGameFormat.java index 2092d01..0db8233 100644 --- a/src/main/java/org/hedgecode/chess/scanner/json/format/JSONGameFormat.java +++ b/src/main/java/org/hedgecode/chess/scanner/json/format/JSONGameFormat.java @@ -16,24 +16,21 @@ package org.hedgecode.chess.scanner.json.format; -import java.text.DateFormat; -import java.text.SimpleDateFormat; - import com.google.gson.annotations.SerializedName; -import org.hedgecode.chess.scanner.entity.PGNTag; +import org.hedgecode.chess.scanner.format.PGNFormat; +import org.hedgecode.chess.scanner.format.PGNTag; import org.hedgecode.chess.scanner.format.GameData; import org.hedgecode.chess.scanner.format.GameFormat; +import org.hedgecode.chess.scanner.format.TypeMovesFormat; +import org.hedgecode.chess.scanner.spi.ServiceRegistry; /** * JSONGameFormat * * @author Dmitry Samoshin aka gotty */ -public class JSONGameFormat extends AbstractPGNFormat implements GameFormat { - - private final DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd"); - private final DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); +public class JSONGameFormat extends AbstractBaseFormat implements GameFormat { @SerializedName("gameData") private JSONGameData gameData; @@ -53,25 +50,31 @@ public class JSONGameFormat extends AbstractPGNFormat implements GameFormat { return gameData; } + @Override - protected void assignPGN() { - addTag(PGNTag.EVENT, gameData.room().name()); - addTag(PGNTag.DATE, dateFormat.format(gameData.game().startAt())); - addTag(PGNTag.TIME, timeFormat.format(gameData.game().startAt())); - addTag(PGNTag.ROUND, gameData.game().roundSlug()); - addTag(PGNTag.WHITE, gameData.game().white().name()); - addTag(PGNTag.BLACK, gameData.game().black().name()); - addTag(PGNTag.WHITE_TITLE, gameData.game().white().title()); - addTag(PGNTag.BLACK_TITLE, gameData.game().black().title()); - addTag(PGNTag.WHITE_ELO, Integer.toString(gameData.game().whiteElo())); - addTag(PGNTag.BLACK_ELO, Integer.toString(gameData.game().blackElo())); - addTag(PGNTag.EVENT_DATE, dateFormat.format(gameData.room().startAt())); - addTag(PGNTag.RESULT, gameData.game().result()); + public String pgn() { + PGNFormat pgnFormat = ServiceRegistry.singleProvider( + PGNFormat.class + ); + pgnFormat.addTag(PGNTag.EVENT, gameData.room().name()); + pgnFormat.addTag(PGNTag.DATE, pgnFormat.formatDate(gameData.game().startAt())); + pgnFormat.addTag(PGNTag.ROUND, gameData.game().roundSlug()); + pgnFormat.addTag(PGNTag.WHITE, gameData.game().white().name()); + pgnFormat.addTag(PGNTag.BLACK, gameData.game().black().name()); + pgnFormat.addTag(PGNTag.WHITE_TITLE, gameData.game().white().title()); + pgnFormat.addTag(PGNTag.BLACK_TITLE, gameData.game().black().title()); + pgnFormat.addTag(PGNTag.WHITE_ELO, Integer.toString(gameData.game().whiteElo())); + pgnFormat.addTag(PGNTag.BLACK_ELO, Integer.toString(gameData.game().blackElo())); + pgnFormat.addTag(PGNTag.EVENT_DATE, pgnFormat.formatDate(gameData.room().startAt())); + pgnFormat.addTag(PGNTag.TIME, pgnFormat.formatTime(gameData.game().startAt())); + pgnFormat.addTag(PGNTag.RESULT, gameData.game().result()); if (gameData.moves().length > 0) { - addTag(PGNTag.PLY_COUNT, Integer.toString(gameData.moves().length)); + pgnFormat.addTag(PGNTag.PLY_COUNT, Integer.toString(gameData.moves().length)); } - - addMoves(gameData.formatMoves()); + pgnFormat.addMoves( + TypeMovesFormat.WRAP.format(gameData.moves()) + ); + return pgnFormat.format(); } } diff --git a/src/main/java/org/hedgecode/chess/scanner/json/format/JSONMove.java b/src/main/java/org/hedgecode/chess/scanner/json/format/JSONMove.java index 6a65e40..0b65d36 100644 --- a/src/main/java/org/hedgecode/chess/scanner/json/format/JSONMove.java +++ b/src/main/java/org/hedgecode/chess/scanner/json/format/JSONMove.java @@ -39,8 +39,8 @@ public class JSONMove implements Move { } @Override - public int number() { - return ply; + public int ply() { + return ply + 1; } @Override diff --git a/src/main/resources/META-INF/services/org.hedgecode.chess.scanner.format.PGNFormat b/src/main/resources/META-INF/services/org.hedgecode.chess.scanner.format.PGNFormat new file mode 100644 index 0000000..e1c3f84 --- /dev/null +++ b/src/main/resources/META-INF/services/org.hedgecode.chess.scanner.format.PGNFormat @@ -0,0 +1 @@ +org.hedgecode.chess.scanner.format.ReducePGNFormat \ No newline at end of file