All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codingame.gameengine.module.entities.Serializer Maven / Gradle / Ivy

package com.codingame.gameengine.module.entities;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.codingame.gameengine.module.entities.Entity.Type;
import com.google.inject.Singleton;

@Singleton
class Serializer {
    public Map commands, keys, separators;
    Map types;
    Map curves;
    private static DecimalFormat decimalFormat;
    static {
        DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols();
        otherSymbols.setDecimalSeparator('.');
        decimalFormat = new DecimalFormat("0.######");
        decimalFormat.setGroupingUsed(false);
        decimalFormat.setDecimalFormatSymbols(otherSymbols);
    }

    Serializer() {
        keys = new HashMap<>();
        keys.put("rotation", "r");
        keys.put("radius", "R");
        keys.put("x2", "X");
        keys.put("y2", "Y");
        keys.put("width", "w");
        keys.put("height", "h");
        keys.put("tint", "t");
        keys.put("fillColor", "f");
        keys.put("fillAlpha", "F");
        keys.put("lineColor", "c");
        keys.put("lineWidth", "W");
        keys.put("lineAlpha", "A");
        keys.put("alpha", "a");
        keys.put("image", "i");
        keys.put("strokeThickness", "S");
        keys.put("strokeColor", "sc");
        keys.put("fontFamily", "ff");
        keys.put("fontSize", "s");
        keys.put("fontWeight", "fw");
        keys.put("text", "T");
        keys.put("textAlign", "ta");
        keys.put("children", "ch");
        keys.put("scaleX", "sx");
        keys.put("scaleY", "sy");
        keys.put("anchorX", "ax");
        keys.put("anchorY", "ay");
        keys.put("visible", "v");
        keys.put("zIndex", "z");
        keys.put("blendMode", "b");
        keys.put("images", "I");
        keys.put("restarted", "rs");
        keys.put("playing", "p");
        keys.put("loop", "l");
        keys.put("duration", "d");
        keys.put("baseWidth", "bw");
        keys.put("baseHeight", "bh");
        keys.put("points", "ps");
        keys.put("skewX", "kx");
        keys.put("skewY", "ky");
        keys.put("tileX", "tx");
        keys.put("tileY", "ty");
        keys.put("tileScaleX", "tsx");
        keys.put("tileScaleY", "tsy");
        keys.put("scaleMode", "sm");

        commands = new HashMap<>();
        commands.put("CREATE", "C");
        commands.put("UPDATE", "U");
        commands.put("LOADSPRITESHEET", "L");

        separators = new HashMap<>();
        separators.put("COMMAND", ";");
        separators.put("COMMAND_ARGUMENT", " ");
        separators.put("ARGUMENT_DETAILS", ",");
        separators.put("COMMAND_TYPE", "\n");

        curves = new HashMap<>();
        curves.put(Curve.NONE, "_");
        curves.put(Curve.IMMEDIATE, "Γ");
        curves.put(Curve.LINEAR, "/");
        curves.put(Curve.EASE_IN, "🙖");
        curves.put(Curve.EASE_OUT, "🙒");
        curves.put(Curve.EASE_IN_AND_OUT, "∫");
        curves.put(Curve.ELASTIC, "~");

        types = new HashMap<>();
        types.put(Type.RECTANGLE, "R");
        types.put(Type.ROUNDED_RECTANGLE, "K");
        types.put(Type.CIRCLE, "C");
        types.put(Type.GROUP, "G");
        types.put(Type.BUFFERED_GROUP, "B");
        types.put(Type.LINE, "L");
        types.put(Type.SPRITE, "S");
        types.put(Type.TEXT, "T");
        types.put(Type.BITMAPTEXT, "X");
        types.put(Type.SPRITEANIMATION, "A");
        types.put(Type.POLYGON, "P");
        types.put(Type.TILING_SPRITE, "D");

        if (keys.values().stream().distinct().count() != keys.values().size()) {
            throw new RuntimeException("Duplicate keys");
        }
        if (commands.values().stream().distinct().count() != commands.values().size()) {
            throw new RuntimeException("Duplicate commands");
        }
        if (separators.values().stream().distinct().count() != separators.values().size()) {
            throw new RuntimeException("Duplicate separators");
        }
        if (types.values().stream().distinct().count() != types.values().size()) {
            throw new RuntimeException("Duplicate types");
        }
        if (curves.values().stream().distinct().count() != curves.values().size()) {
            throw new RuntimeException("Duplicate curves");
        }
        if (keys.values().stream().anyMatch(character -> curves.containsValue(character))) {
            throw new RuntimeException("Same string used for a curve and a property");
        }
        if (separators.values().stream().anyMatch(
            character -> curves.containsValue(character) ||
                keys.containsValue(character) ||
                types.containsValue(character) ||
                commands.containsValue(character)
        )) {
            throw new RuntimeException("String already used as separator");
        }

    }

    /**
     * Join multiple object into a space separated string
     */
    static private String join(Object... args) {
        return Stream.of(args).map(String::valueOf).collect(Collectors.joining(" "));
    }

    static String formatFrameTime(double t) {
        return decimalFormat.format(t);
    }

    static String escape(String text) {
        String escaped = text.replaceAll("\\'", "\\\\'");
        if (escaped.contains(" ") || escaped.contains(";") || escaped.contains("\n")) {
            return "'" + escaped + "'";
        }
        return escaped;
    }

    private String serializeEntitiesStateDiff(Entity entity, EntityState diff, String frameInstant) {
        String meta = join(
            entity.getId(),
            frameInstant
        );
        if (diff.isEmpty()) {
            return meta;
        }
        return join(
            meta,
            minifyDiff(diff)
        );
    }

    private String minifyParam(String key, EntityState.Param param) {
        String result;

        if (key.equals("rotation") || key.equals("skewX") || key.equals("skewY")) {
            result = String.valueOf((int) Math.toDegrees((double) param.value));
        } else if (param.value instanceof Double) {
            result = decimalFormat.format(param.value);
        } else if (param.value instanceof Boolean) {
            result = (boolean) param.value ? "1" : "0";
        } else {
            result = escape(param.value.toString());
        }

        // We don't send the default curve, it will be implied.
        if (param.curve.equals(Curve.DEFAULT)) {
            return result;
        }
        return join(result, curves.get(param.curve));
    }

    private String minifyKey(String key) {
        return keys.getOrDefault(key, key);
    }

    private String minifyDiff(EntityState diff) {
        return diff.entrySet().stream()
            .map((entry) -> join(minifyKey(entry.getKey()), minifyParam(entry.getKey(), entry.getValue())))
            .collect(Collectors.joining(separators.get("COMMAND_ARGUMENT")));
    }

    private String serializeCreateEntity(Entity e) {
        return join(
            types.get(e.getType())
        );
    }

    public Optional serializeCreateEntities(List> entities) {
        if (entities.isEmpty()) {
            return Optional.empty();
        } else {
            return Optional.of(
                commands.get("CREATE") + entities.stream()
                    .map(e -> serializeCreateEntity(e))
                    .collect(Collectors.joining(separators.get("COMMAND")))
            );
        }
    }

    private String serializeLoadSpriteSheet(SpriteSheetSplitter spriteSheet) {
        return join(
            spriteSheet.getName(), spriteSheet.getSourceImage(), spriteSheet.getWidth(), spriteSheet.getHeight(),
            spriteSheet.getOrigRow(), spriteSheet.getOrigCol(), spriteSheet.getImageCount(), spriteSheet.getImagesPerRow()
        );
    }

    public Optional serializeLoadSpriteSheets(List spriteSheets) {
        if (spriteSheets.isEmpty()) {
            return Optional.empty();
        } else {
            return Optional.of(
                commands.get("LOADSPRITESHEET") + spriteSheets.stream()
                    .map(e -> serializeLoadSpriteSheet(e))
                    .collect(Collectors.joining(separators.get("COMMAND")))
            );
        }
    }

    public Optional serializeWorldDiff(List diffs) {
        if (diffs.isEmpty()) {
            return Optional.empty();
        } else {
            List serialized = diffs.stream()
                .map(worldDiff -> {
                    Optional result;
                    Set, EntityState>> diff = worldDiff.getEntityStateMap().entrySet();
                    if (diff.isEmpty()) {
                        result = Optional.empty();
                    } else {
                        result = Optional.of(
                            diff
                                .stream()
                                .map(e -> {
                                    return serializeEntitiesStateDiff(e.getKey(), e.getValue(), worldDiff.getFrameTime());
                                })
                                .collect(Collectors.joining(separators.get("COMMAND")))
                        );
                    }
                    return result;
                })
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.toList());
            if (serialized.isEmpty()) {
                return Optional.empty();
            } else {
                return Optional.of(
                    commands.get("UPDATE") + serialized
                        .stream()
                        .collect(Collectors.joining(separators.get("COMMAND")))
                );
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy