267 lines
9.5 KiB
Java
267 lines
9.5 KiB
Java
package io.github.applecommander.bastools.api.shapes;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Queue;
|
|
import java.util.function.Function;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
public class VectorShape implements Shape {
|
|
public static VectorShape from(ByteBuffer buf) {
|
|
Objects.requireNonNull(buf);
|
|
VectorShape shape = new VectorShape();
|
|
|
|
VectorCommand[] commands = VectorCommand.values();
|
|
while (buf.hasRemaining()) {
|
|
int code = Byte.toUnsignedInt(buf.get());
|
|
if (code == 0) break;
|
|
|
|
int vector1 = code & 0b111;
|
|
int vector2 = (code >> 3) & 0b111;
|
|
int vector3 = (code >> 6) & 0b011; // Cannot plot
|
|
|
|
shape.vectors.add(commands[vector1]);
|
|
|
|
if (vector2 != 0 || vector3 != 0) {
|
|
shape.vectors.add(commands[vector2]);
|
|
|
|
if (vector3 != 0) {
|
|
shape.vectors.add(commands[vector3]);
|
|
}
|
|
}
|
|
}
|
|
return shape;
|
|
}
|
|
|
|
public final String label;
|
|
public final List<VectorCommand> vectors = new ArrayList<>();
|
|
|
|
public VectorShape() {
|
|
this.label = null;
|
|
}
|
|
public VectorShape(String label) {
|
|
this.label = label;
|
|
}
|
|
|
|
public VectorShape moveUp() { return append(VectorCommand.MOVE_UP); }
|
|
public VectorShape moveRight() { return append(VectorCommand.MOVE_RIGHT); }
|
|
public VectorShape moveDown() { return append(VectorCommand.MOVE_DOWN); }
|
|
public VectorShape moveLeft() { return append(VectorCommand.MOVE_LEFT); }
|
|
public VectorShape plotUp() { return append(VectorCommand.PLOT_UP); }
|
|
public VectorShape plotRight() { return append(VectorCommand.PLOT_RIGHT); }
|
|
public VectorShape plotDown() { return append(VectorCommand.PLOT_DOWN); }
|
|
public VectorShape plotLeft() { return append(VectorCommand.PLOT_LEFT); }
|
|
|
|
public VectorShape append(VectorCommand vectorCommand) {
|
|
this.vectors.add(vectorCommand);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Optimize the vectors by removing useless vectors or replacing a series with a shorter series.
|
|
* At this point, everything is based off of a regex with a potential modification.
|
|
*/
|
|
public VectorShape optimize() {
|
|
String commands = toShortCommands();
|
|
Function<String,String> opts =
|
|
// Unused moves (left followed by a right with no plotting in between, for instance).
|
|
VectorRegexOptimization.of("l([ud]*)r")
|
|
.andThen(VectorRegexOptimization.of("r([ud]*)l"))
|
|
.andThen(VectorRegexOptimization.of("u([rl]*)d"))
|
|
.andThen(VectorRegexOptimization.of("d([rl]*)u"))
|
|
// These are plot/move combinations, such as LEFT>up>right that can be replaced by just UP.
|
|
.andThen(VectorRegexOptimization.of("L([ud])r", String::toUpperCase))
|
|
.andThen(VectorRegexOptimization.of("R([ud])l", String::toUpperCase))
|
|
.andThen(VectorRegexOptimization.of("U([rl])d", String::toUpperCase))
|
|
.andThen(VectorRegexOptimization.of("D([rl])u", String::toUpperCase))
|
|
// Base assumption is that any tail moves can be removed as they don't lead to a plot.
|
|
.andThen(VectorRegexOptimization.of("()[udlr]+$"));
|
|
|
|
String oldCommands = null;
|
|
do {
|
|
oldCommands = commands;
|
|
commands = opts.apply(commands);
|
|
} while (!oldCommands.equals(commands));
|
|
|
|
VectorShape newShape = new VectorShape();
|
|
newShape.appendShortCommands(commands);
|
|
return newShape;
|
|
}
|
|
|
|
/**
|
|
* A vector optimization based on regex. Transformation is optional to (for instance) change a {@code move}
|
|
* to a {@code plot} command. Note that the regex requires a matcher group; also be aware that an empty group
|
|
* "{@code ()}" is a viable solution.
|
|
*/
|
|
public static class VectorRegexOptimization implements Function<String,String> {
|
|
public static Function<String,String> of(String regex, Function<String,String> transformation) {
|
|
VectorRegexOptimization opt = new VectorRegexOptimization();
|
|
opt.pattern = Pattern.compile(regex);
|
|
opt.fn = transformation;
|
|
return opt;
|
|
}
|
|
public static Function<String,String> of(String regex) {
|
|
return of(regex, (s) -> s);
|
|
}
|
|
|
|
private Pattern pattern;
|
|
private Function<String,String> fn;
|
|
|
|
private VectorRegexOptimization() { /* Prevent construction */ }
|
|
|
|
@Override
|
|
public String apply(String shortCommands) {
|
|
Matcher matcher = pattern.matcher(shortCommands);
|
|
StringBuffer sb = new StringBuffer();
|
|
while (matcher.find()) {
|
|
matcher.appendReplacement(sb, fn.apply(matcher.group(1)));
|
|
}
|
|
matcher.appendTail(sb);
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
public String toShortCommands() {
|
|
StringBuilder sb = new StringBuilder();
|
|
vectors.stream().map(v -> v.shortCommand).forEach(sb::append);
|
|
return sb.toString();
|
|
}
|
|
|
|
public void appendShortCommands(String line) {
|
|
for (char cmd : line.trim().toCharArray()) {
|
|
appendShortCommand(cmd);
|
|
}
|
|
}
|
|
public void appendShortCommand(char cmd) {
|
|
switch (cmd) {
|
|
case 'u': moveUp(); break;
|
|
case 'd': moveDown(); break;
|
|
case 'l': moveLeft(); break;
|
|
case 'r': moveRight(); break;
|
|
case 'U': plotUp(); break;
|
|
case 'D': plotDown(); break;
|
|
case 'L': plotLeft(); break;
|
|
case 'R': plotRight(); break;
|
|
default:
|
|
// whitespace is allowed
|
|
if (!Character.isWhitespace(cmd)) {
|
|
throw new RuntimeException("Unknown command: " + cmd);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void appendLongCommands(String line) {
|
|
Queue<String> tokens = new LinkedList<>(Arrays.asList(line.split("\\s+")));
|
|
while (!tokens.isEmpty()) {
|
|
String command = tokens.remove();
|
|
int count = 1;
|
|
String checkNumber = tokens.peek();
|
|
if (checkNumber != null && checkNumber.matches("\\d+")) count = Integer.parseInt(tokens.remove());
|
|
|
|
for (int i=0; i<count; i++) {
|
|
switch (command.toLowerCase()) {
|
|
case "moveup": moveUp(); break;
|
|
case "movedown": moveDown(); break;
|
|
case "moveleft": moveLeft(); break;
|
|
case "moveright": moveRight(); break;
|
|
case "plotup": plotUp(); break;
|
|
case "plotdown": plotDown(); break;
|
|
case "plotleft": plotLeft(); break;
|
|
case "plotright": plotRight(); break;
|
|
default:
|
|
throw new RuntimeException("Unknown command: " + command);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public byte[] toBytes() {
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
LinkedList<VectorCommand> work = new LinkedList<>(vectors);
|
|
while (!work.isEmpty()) {
|
|
VectorCommand vector1 = work.remove();
|
|
int section1 = vector1.ordinal();
|
|
VectorCommand vector2 = work.poll();
|
|
int section2 = Optional.ofNullable(vector2).map(VectorCommand::ordinal).orElse(0);
|
|
VectorCommand vector3 = work.poll();
|
|
// Remove all invalid encodings 100, 101, 110, 111, and 000.
|
|
if (vector3 != null && (vector3.plot || vector3 == VectorCommand.MOVE_UP)) {
|
|
work.addFirst(vector3);
|
|
vector3 = null;
|
|
}
|
|
int section3 = Optional.ofNullable(vector3).map(VectorCommand::ordinal).orElse(0);
|
|
if (section3 == 0 && section2 == 0 && !work.isEmpty()) {
|
|
vector3 = VectorCommand.MOVE_LEFT;
|
|
section3 = vector3.ordinal();
|
|
// If we have a series of MOVE_UP, we'll end up with a "uul", "rul", "rul", etc.
|
|
// It can be compressed a bit by stretching that right out a bit to get "uul", "uur", "uul, "uur", etc.
|
|
int moveUpCount = 0;
|
|
for (VectorCommand test : work) {
|
|
if (test == VectorCommand.MOVE_UP) {
|
|
moveUpCount += 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
work.add(Math.min(moveUpCount,2), VectorCommand.MOVE_RIGHT);
|
|
}
|
|
outputStream.write(section3 << 6 | section2 << 3 | section1);
|
|
}
|
|
outputStream.write(0);
|
|
return outputStream.toByteArray();
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return vectors.isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public String getLabel() {
|
|
return label;
|
|
}
|
|
|
|
@Override
|
|
public BitmapShape toBitmap() {
|
|
BitmapShape shape = new BitmapShape();
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
for (VectorCommand command : vectors) {
|
|
if (command.plot) {
|
|
while (y < 0) {
|
|
shape.insertRow();
|
|
y += 1;
|
|
}
|
|
while (y >= shape.getHeight()) {
|
|
shape.addRow();
|
|
}
|
|
while (x < 0) {
|
|
shape.insertColumn();
|
|
x += 1;
|
|
}
|
|
while (x >= shape.getWidth()) {
|
|
shape.addColumn();
|
|
}
|
|
shape.plot(x,y);
|
|
}
|
|
x += command.xmove;
|
|
y += command.ymove;
|
|
}
|
|
|
|
return shape;
|
|
}
|
|
|
|
@Override
|
|
public VectorShape toVector() {
|
|
return this;
|
|
}
|
|
}
|