package uk.org.squirm3.engine.generator; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import uk.org.squirm3.model.Atom; import uk.org.squirm3.model.Atoms; import uk.org.squirm3.model.Configuration; import uk.org.squirm3.model.type.AtomType; import uk.org.squirm3.model.type.BuilderType; import uk.org.squirm3.springframework.converter.BuilderTypeToAtomTypeConverter; import com.google.common.collect.Lists; public class AtomBuilder { private static final char[] NO_ATOM = "......".toCharArray(); private static final int ATOM_LENGTH = NO_ATOM.length; private static final char MOBILE_ATOM_START = '('; private static final char MOBILE_ATOM_STOP = ')'; private static final char FIXED_ATOM_START = '['; private static final char FIXED_ATOM_STOP = ']'; private static final char NO_BOND = '_'; private static final char HORIZONTAL_BOND = '⇠'; private static final char VERTICAL_BOND = '⇡'; private final ConversionService conversionService; public AtomBuilder(final ConversionService conversionService) { this.conversionService = conversionService; } private float getSize(final String string) { return Atom.getAtomSize() * 2 * Integer.parseInt(string); } public Configuration build(final String levelDescription) throws BuilderException { String randomizerConfiguration = "abcdef"; final BufferedReader descriptionReader = new BufferedReader( new StringReader(levelDescription)); try { String descriptionLine = descriptionReader.readLine(); final Configuration partialConfiguration = parseSizeConfiguration(descriptionLine); descriptionLine = descriptionReader.readLine(); if (descriptionLine != null && descriptionLine.startsWith("#")) { randomizerConfiguration = descriptionLine.substring(1); descriptionLine = descriptionReader.readLine(); } final Collection<Atom> atoms = Lists.newArrayList(); ArrayList<Atom> previousAtomLine = Lists.newArrayList(); ArrayList<Atom> currentAtomLine = Lists.newArrayList(); final Converter<BuilderType, AtomType> atomTypeConverter = new BuilderTypeToAtomTypeConverter( randomizerConfiguration); int x = 0; int y = 0; int maxX = 0; while (descriptionLine != null) { y++; x = 1; int index = 0; while (index < descriptionLine.length()) { final char[] atomDescription = getAtomDescription( descriptionLine, index); if (isAtomStart(atomDescription)) { final char atomStart = atomDescription[0]; final char atomType = atomDescription[2]; final char atomState = atomDescription[3]; final float xCoordinate = 2 * x * Atom.getAtomSize(); final float yCoordinate = 2 * y * Atom.getAtomSize(); final Atom atom = Atoms.createAtom( getAtomType(atomType, atomTypeConverter), getAtomState(atomState), xCoordinate, yCoordinate, atomStart == FIXED_ATOM_START); currentAtomLine.add(atom); if (isVerticalBondActivated(atomDescription)) { atom.bondWith(getUpperAtom(previousAtomLine, x)); } if (isHorizontalBondActivated(atomDescription)) { atom.bondWith(getPreviousAtom(currentAtomLine, x)); } } else { currentAtomLine.add(null); } index += ATOM_LENGTH; x++; } atoms.addAll(currentAtomLine); previousAtomLine = currentAtomLine; currentAtomLine = Lists.newArrayList(); descriptionLine = descriptionReader.readLine(); if (x > maxX) { maxX = x; } } atoms.removeAll(Collections.singleton(null)); checkConfiguration(partialConfiguration, maxX, y); return new Configuration(partialConfiguration.getHeight(), partialConfiguration.getWidth(), atoms); } catch (final IOException e) { throw new BuilderException( "Unexpected IOException while reading from level description.", e); } finally { try { descriptionReader.close(); } catch (final IOException e) { } } } private Configuration parseSizeConfiguration(final String descriptionLine) throws BuilderException { if (descriptionLine == null) { throwIncorrectSizeConfiguration(descriptionLine); } final Matcher matcher = Pattern.compile("#(\\d+)x(\\d+)").matcher( descriptionLine); if (!matcher.matches()) { throwIncorrectSizeConfiguration(descriptionLine); } return new Configuration(getSize(matcher.group(1)), getSize(matcher.group(2))); } private void throwIncorrectSizeConfiguration(final String descriptionLine) throws BuilderException { throw new BuilderException( "First line should indicate the size of the level : " + descriptionLine); } private void checkConfiguration(final Configuration configuration, final int x, final int y) throws BuilderException { final double horizontalSpace = Atom.getAtomSize() * (x * 2 + 1); if (horizontalSpace > configuration.getWidth()) { throw new BuilderException( "Map horizontal space is greater than the configuration's width : " + horizontalSpace + " > " + configuration.getWidth()); } final double verticalSpace = Atom.getAtomSize() * (y * 2 + 1); if (verticalSpace > configuration.getHeight()) { throw new BuilderException( "Map vertical space is greater than the configuration's height : " + verticalSpace + " > " + configuration.getHeight()); } } private boolean isHorizontalBondActivated(final char[] atomDescription) throws BuilderException { if (atomDescription[1] == HORIZONTAL_BOND) { return true; } checkDefaultBondValue(atomDescription[1], atomDescription, "horizontal"); return false; } private void checkDefaultBondValue(final char bond, final char[] atomDescription, final String qualifier) throws BuilderException { if (bond != NO_BOND) { throw new BuilderException("Incorrect setting for " + qualifier + " bond : " + String.valueOf(atomDescription)); } } private boolean isVerticalBondActivated(final char[] atomDescription) throws BuilderException { if (atomDescription[4] == VERTICAL_BOND) { return true; } checkDefaultBondValue(atomDescription[4], atomDescription, "vertical"); return false; } private Atom getUpperAtom(final ArrayList<Atom> previousLine, final int x) throws BuilderException { try { final Atom atom = previousLine.get(x - 1); if (atom == null) { throw new BuilderException( "Incorrect setting for vertical bond : there is no upper atom!"); } return atom; } catch (final IndexOutOfBoundsException e) { throw new BuilderException( "Incorrect setting for vertical bond : there is no upper atom!", e); } } private Atom getPreviousAtom(final List<Atom> currentLine, final int currentIndex) throws BuilderException { try { final Atom atom = currentLine.get(currentIndex - 2); if (atom == null) { throw new BuilderException( "Incorrect setting for horizontal bond : there is no previous atom!"); } return atom; } catch (final IndexOutOfBoundsException e) { throw new BuilderException( "Incorrect setting for horizontal bond : there is no previous atom!", e); } } private AtomType getAtomType(final char atomType, final Converter<BuilderType, AtomType> atomTypeConverter) throws BuilderException { final BuilderType builderType = conversionService.convert(atomType, BuilderType.class); if (builderType == null) { throw new BuilderException("Incorrect BuilderType : " + atomType); } final AtomType type = atomTypeConverter.convert(builderType); if (type == null) { throw new BuilderException("No AtomType for : " + builderType); } return type; } private int getAtomState(final char atomState) throws BuilderException { final int digitAtomState = Character.digit(atomState, 10); if (digitAtomState == -1) { throw new BuilderException("Incorrect state : " + atomState); } return digitAtomState; } private char[] getAtomDescription(final String levelDescription, final int index) throws BuilderException { if (index + ATOM_LENGTH > levelDescription.length()) { throw new BuilderException("Unexpected end of levelDescription : " + levelDescription.substring(index)); } return levelDescription.substring(index, index + ATOM_LENGTH) .toCharArray(); } private boolean isAtomStart(final char[] atomDescription) throws BuilderException { if (atomDescription[0] == MOBILE_ATOM_START) { if (atomDescription[ATOM_LENGTH - 1] != MOBILE_ATOM_STOP) { throw new BuilderException("Illegal end of mobile atom : " + String.valueOf(atomDescription)); } return true; } if (atomDescription[0] == FIXED_ATOM_START) { if (atomDescription[ATOM_LENGTH - 1] != FIXED_ATOM_STOP) { throw new BuilderException("Illegal end of fixed atom : " + String.valueOf(atomDescription)); } return true; } if (atomDescription[0] == NO_ATOM[0]) { if (!Arrays.equals(atomDescription, NO_ATOM)) { throw new BuilderException("Illegal no atom : " + String.valueOf(atomDescription)); } return false; } throw new BuilderException("Illegal description of atom : " + String.valueOf(atomDescription)); } }