Created
July 30, 2014 18:16
-
-
Save LyndonArmitage/660c80f94c7a1a60cbb8 to your computer and use it in GitHub Desktop.
Reddit DailyProgrammer Challenge 173: Langtons Ant
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.lyndonarmitage.daily.langton; | |
import javax.imageio.ImageIO; | |
import javax.swing.*; | |
import javax.swing.filechooser.FileFilter; | |
import java.awt.*; | |
import java.awt.image.BufferedImage; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.Random; | |
/** | |
* Grid class containing all data on the Ants world | |
* @author Lyndon Armitage | |
*/ | |
public class Grid { | |
private Map<String,Point> points; | |
private ArrayList<Color> colours; | |
/** | |
* Create an empty grid | |
* @param maxColours the maximum amount of different colours that a point can be | |
*/ | |
public Grid(int maxColours) { | |
points = new HashMap<String, Point>(); | |
colours = getDefaultColours(maxColours); | |
} | |
/** | |
* Preallocate points to the grid | |
* @param width width of the grid | |
* @param height height of the grid | |
* @param maxColours the maximum amount of different colours that a point can be | |
*/ | |
public Grid(int width, int height, int maxColours) { | |
points = new HashMap<String, Point>(width * height); | |
for(int x = 0; x < width; x ++) { | |
for(int y = 0; y < height; y ++) { | |
Point p = new Point(x, y, (byte) 0); | |
points.put(x + "," + y, p); | |
} | |
} | |
colours = getDefaultColours(maxColours); | |
} | |
public static ArrayList<Color>getDefaultColours(int maxColours) { | |
// an array of preselected colours | |
final Color colourArray[] = { | |
Color.black, | |
Color.white, | |
Color.red, | |
Color.green, | |
Color.blue, | |
Color.yellow, | |
Color.pink, | |
}; | |
ArrayList<Color> colours = new ArrayList<Color>(maxColours); | |
int i; | |
for(i = 0; i < maxColours && i < colourArray.length; i ++) { | |
colours.add(colourArray[i]); | |
} | |
if(i < maxColours) { | |
// Fill extra colours with random colours | |
// Doesn't check for duplicates or similar shades | |
Random rnd = new Random(); | |
while (i < maxColours) { | |
colours.add(new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256))); | |
i ++; | |
} | |
} | |
return colours; | |
} | |
public ArrayList<Color> getColours() { | |
return colours; | |
} | |
public void flipPoint(int x, int y, boolean reverse) { | |
Point p = getPoint(x, y); | |
addColour(p, reverse); | |
} | |
private void addColour(Point point, boolean subtract) { | |
byte newColour = (byte) (subtract ? point.colour-1 : point.colour+1); | |
if(newColour >= colours.size()) { | |
point.colour = 0; | |
} | |
else if(newColour < 0){ | |
point.colour = (byte) (colours.size()-1); | |
} | |
else { | |
point.colour = newColour; | |
} | |
// point.traversed = true; | |
} | |
public Point getPoint(int x, int y) { | |
Point point = points.get(x+","+y); | |
if(point != null) { | |
return point; | |
} | |
else { | |
Point newPoint = new Point(x, y, (byte) 0); | |
points.put(x+","+y, newPoint); | |
return newPoint; | |
} | |
} | |
public Point getLowestPoint() { | |
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE; | |
for(String s : points.keySet()) { | |
Point p = points.get(s); | |
if(p.x < minX) { | |
minX = p.x; | |
} | |
if(p.y < minY) { | |
minY = p.y; | |
} | |
} | |
return getPoint(minX, minY); | |
} | |
public Point getHighestPoint() { | |
int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE; | |
for(String s : points.keySet()) { | |
Point p = points.get(s); | |
if(p.x > maxX) { | |
maxX = p.x; | |
} | |
if(p.y > maxY) { | |
maxY = p.y; | |
} | |
} | |
return getPoint(maxX, maxY); | |
} | |
public void saveGridImage() { | |
// Saving the grid requires us to align the points so they are all positive | |
Point lowest = getLowestPoint(); | |
Point highest = getHighestPoint(); | |
int width, height; | |
int adjustX, adjustY; | |
if(lowest.getX() < 0) { | |
adjustX = Math.abs(lowest.getX()); | |
} | |
else { | |
adjustX = -lowest.getX(); | |
} | |
if(lowest.getY() < 0) { | |
adjustY = Math.abs(lowest.getY()); | |
} | |
else { | |
adjustY = -lowest.getY(); | |
} | |
// the width and height of the resulting image | |
width = highest.getX() + adjustX + 1; | |
height = highest.getY() + adjustY + 1; | |
// Get the colours the grid has been told to use | |
ArrayList<Color> colours = getColours(); | |
// Create an image ready to be drawn on | |
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); | |
// Fills the image with the default colour | |
Graphics2D g2 = image.createGraphics(); | |
g2.setColor(colours.get(0)); | |
g2.fillRect(0,0, width-1, height-1); | |
g2.dispose(); | |
// Set the pixel data for each point | |
for(String s : points.keySet()) { | |
Point p = points.get(s); | |
int x, y; | |
x = p.getX() + adjustX; | |
y = p.getY() + adjustY; | |
image.setRGB(x, y, colours.get(p.getColour()).getRGB()); | |
} | |
// Saving image logic below | |
JFileChooser chooser = new JFileChooser("."); // make a chooser in the current directory | |
// setup an appropriate file filter | |
chooser.setFileFilter(new FileFilter() { | |
@Override | |
public boolean accept(File f) { | |
return f.isDirectory() || f.getName().toLowerCase().endsWith(".png"); | |
} | |
@Override | |
public String getDescription() { | |
return "Save PNG"; | |
} | |
}); | |
// Display the dialog | |
if(chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { | |
File saveFile = chooser.getSelectedFile(); | |
// Check to make sure the file has png at the end of it's name and if not append it | |
if(!saveFile.getName().toLowerCase().endsWith(".png")) { | |
saveFile = new File(saveFile.getParent(), saveFile.getName() + ".png"); | |
} | |
// Try to save the image out | |
try { | |
ImageIO.write(image, "png", saveFile); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
/** | |
* Inner class to represent a point on the grid | |
*/ | |
public class Point { | |
private int x; | |
private int y; | |
private byte colour; | |
/** | |
* Create a new Point | |
* @param x the x position on the grid | |
* @param y the y position on the grid | |
* @param colour the colour index of the new point | |
*/ | |
public Point(int x, int y, byte colour) { | |
this.x = x; | |
this.y = y; | |
this.colour = colour; | |
} | |
public int getX() { | |
return x; | |
} | |
public int getY() { | |
return y; | |
} | |
public byte getColour() { | |
return colour; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.lyndonarmitage.daily.langton; | |
import java.util.ArrayList; | |
/** | |
* Langton's technicolor ant | |
* This contains all the ants code, and the main method to run the example with. | |
* @author Lyndon Armitage | |
*/ | |
public class LangtonsAnt { | |
public static final int GRID_INITIAL_WIDTH = 100, GRID_INITIAL_HEIGHT = 100; | |
public static final int DEFAULT_ITERATIONS = 250000; | |
public static final String DEFAULT_RULES = "LRLRR"; | |
private Grid grid; | |
private int x; | |
private int y; | |
private Direction dir = Direction.EAST; | |
private ArrayList<Boolean> rules; | |
/** | |
* The main run code | |
* @param args first argument should be the rules as a string of Ls and Rs, | |
* second argument should be an amount of iterations | |
* @throws UnknownRuleException | |
*/ | |
public static void main(String args[]) throws UnknownRuleException { | |
String rules; | |
int iterations; | |
if(args.length > 0) { | |
rules = args[0]; | |
if(args.length > 1) { | |
iterations = Integer.parseInt(args[1]); | |
} | |
else { | |
iterations = DEFAULT_ITERATIONS; | |
} | |
} | |
else { | |
rules = DEFAULT_RULES; | |
iterations = DEFAULT_ITERATIONS; | |
} | |
System.out.println("Running langtons ant with rules: " + rules + " for " + iterations + " iterations"); | |
Grid grid = new Grid(GRID_INITIAL_WIDTH, GRID_INITIAL_HEIGHT, rules.length()); | |
LangtonsAnt ant = new LangtonsAnt(grid, GRID_INITIAL_WIDTH / 2, GRID_INITIAL_HEIGHT / 2, rules); | |
long startTime = System.currentTimeMillis(); | |
for(int i = 0; i < iterations; i ++) { | |
// Technically speaking I could have multiple ants running on the same grid one after the other with ease. | |
ant.iterate(); | |
} | |
long endTime = System.currentTimeMillis(); | |
float secondsTaken = (float)(endTime - startTime) / 1000 ; | |
System.out.println("Took " + secondsTaken + " seconds"); | |
grid.saveGridImage(); | |
} | |
/** | |
* Constructor for the ant | |
* @param grid the grid the ant will occupy | |
* @param startX the x position on the grid where the ant will start | |
* @param startY the y position on the grid where the ant will start | |
* @param rules a set of rules as a string for each different colour e.g. "LLRR" | |
* @throws UnknownRuleException | |
*/ | |
public LangtonsAnt(Grid grid, int startX, int startY, String rules) throws UnknownRuleException { | |
this.grid = grid; | |
this.x = startX; | |
this.y = startY; | |
this.rules = new ArrayList<Boolean>(Math.min(rules.length(), grid.getColours().size())); | |
rules = rules.toLowerCase(); | |
for(int i = 0; i < rules.length() && i < grid.getColours().size(); i ++) { | |
char rule = rules.charAt(i); | |
if(rule == 'l') { | |
this.rules.add(true); | |
} | |
else if(rule == 'r') { | |
this.rules.add(false); | |
} | |
else { | |
throw new UnknownRuleException("Unknown rule: " + rule); | |
} | |
} | |
} | |
/** | |
* Perform a step | |
*/ | |
public void iterate() { | |
Grid.Point current = grid.getPoint(x, y); | |
boolean counterClockwise = rules.get(current.getColour()); | |
turn(counterClockwise); | |
grid.flipPoint(x, y, false); | |
moveForward(); | |
} | |
/** | |
* Move the ant forward in its current direction | |
*/ | |
private void moveForward() { | |
switch (dir) { | |
case NORTH: | |
y ++; | |
break; | |
case EAST: | |
x ++; | |
break; | |
case SOUTH: | |
y --; | |
break; | |
case WEST: | |
x --; | |
break; | |
} | |
} | |
/** | |
* Turn the ant | |
* @param counterClockwise whether we should turn the ant counterclockwise/anticlockwise or clockwise | |
*/ | |
private void turn(boolean counterClockwise) { | |
int newOrdinal = counterClockwise ? dir.ordinal()-1 : dir.ordinal() + 1; | |
if(newOrdinal >= Direction.values().length) { | |
newOrdinal = 0; | |
} | |
else if(newOrdinal < 0) { | |
newOrdinal = Direction.values().length-1; | |
} | |
dir = Direction.values()[newOrdinal]; | |
} | |
public enum Direction { | |
NORTH, EAST, SOUTH, WEST | |
} | |
/** | |
* An exception for when a weird argument is given | |
*/ | |
public class UnknownRuleException extends Exception { | |
public UnknownRuleException(String message) { | |
super(message); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment