Java
I never complained about GolfScript or CJam, but here's a Java answer for you anyway. This was a really enjoyable challenge. ;)
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.geom.Ellipse2D;
import java.util.*;
import javax.swing.*;
public class MazeMaker {
int row, col, exitRow, exitCol, numCells, score, array[][];
final int SQWIDTH = 20;
boolean gameOver = true;
Ellipse2D.Double ellipse;
JFrame frame;
JPanel mazePanel;
JLabel scoreLabel;
public MazeMaker() {
frame = new JFrame("Maze");
frame.setLayout(new BorderLayout());
JPanel topPanel = createTopPanel();
frame.add(topPanel,BorderLayout.NORTH);
createMazePanel();
frame.add(new JScrollPane(mazePanel),BorderLayout.CENTER);
setKeyActions();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private void constructArray(int seed, int rows, int cols) {
array = new int[rows*2-1][cols*2-1];
for (int[] a : array)
Arrays.fill(a,-1);
numCells = (array.length / 2 + 1) * (array[0].length / 2 + 1);
Random rand = new Random(seed);
int row = rand.nextInt(array.length / 2 + 1) * 2;
int col = rand.nextInt(array[0].length / 2 + 1) * 2;
array[row][col] = 0;
boolean first = true, a = false, exitFound = false;
while (true) {
if (first) {
int direction = rand.nextInt(4);
if (direction == 0 && row != 0) {
array[row-1][col] = 0;
array[row-2][col] = 0;
row -= 2;
first = false;
}
else if (direction == 1 && col != array[0].length - 1) {
array[row][col+1] = 0;
array[row][col+2] = 0;
col += 2;
first = false;
}
else if (direction == 2 && row != array.length - 1) {
array[row+1][col] = 0;
array[row+2][col] = 0;
row += 2;
first = false;
}
else if (direction == 3 && col != 0) {
array[row][col-1] = 0;
array[row][col-2] = 0;
col -= 2;
first = false;
}
}
else {
int availableCells = 0;
boolean up = false, down = false, left = false, right = false;
if (row != 0 && array[row-2][col] == -1) {
availableCells++;
up = true;
}
if (col != array[0].length-1 && array[row][col+2] == -1) {
availableCells++;
right = true;
}
if (row != array.length-1 && array[row+2][col] == -1) {
availableCells++;
down = true;
}
if (col != 0 && array[row][col-2] == -1) {
availableCells++;
left = true;
}
if (availableCells != 0) {
a = true;
while (true) {
boolean[] b = {up,right,down,left};
int i = rand.nextInt(4);
if (b[i]) {
if (i == 0) {
array[row-1][col] = 0;
array[row-2][col] = 0;
row -= 2;
}
else if (i == 1) {
array[row][col+1] = 0;
array[row][col+2] = 0;
col += 2;
}
else if (i == 2) {
array[row+1][col] = 0;
array[row+2][col] = 0;
row += 2;
}
else if (i == 3) {
array[row][col-1] = 0;
array[row][col-2] = 0;
col -= 2;
}
break;
}
}
}
else {
array[row][col] = 1;
if (!exitFound && a) {
if (new Random().nextInt(5) == 0) {
exitFound = true;
exitRow = row;
exitCol = col;
}
}
a = false;
if (row != 0 && array[row-1][col] == 0 && (array[row-2][col] == 0 || array[row-2][col] == 1)) {
array[row-1][col] = 1;
array[row-2][col] = 1;
row -= 2;
}
else if (col != array[0].length-1 && array[row][col+1] == 0 && (array[row][col+2] == 0 || array[row][col+2] == 1)) {
array[row][col+1] = 1;
array[row][col+2] = 1;
col += 2;
}
else if (row != array.length-1 && array[row+1][col] == 0 && (array[row+2][col] == 0 || array[row+2][col] == 1)) {
array[row+1][col] = 1;
array[row+2][col] = 1;
row += 2;
}
else if (col != 0 && array[row][col-1] == 0 && (array[row][col-2] == 0 || array[row][col-2] == 1)) {
array[row][col-1] = 1;
array[row][col-2] = 1;
col -= 2;
}
}
if (checkDone()) {
if (!exitFound) {
exitRow = row;
exitCol = col;
}
break;
}
}
}
}
private boolean checkDone() {
int count = 0;
for (int k = 0; k < array.length; k+=2) {
for (int l = 0; l < array[0].length; l+=2) {
if (array[k][l] == 0 || array[k][l] == 1)
count++;
}
}
return count == numCells;
}
private JPanel createTopPanel() {
GridBagLayout l = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
JPanel panel = new JPanel(l);
JLabel inputLabel = new JLabel("ID:");
c.gridwidth = 1;
l.setConstraints(inputLabel,c);
panel.add(inputLabel);
JTextField inputField = new JTextField(5);
l.setConstraints(inputField,c);
panel.add(inputField);
JLabel rowLabel = new JLabel("Rows:");
l.setConstraints(rowLabel,c);
panel.add(rowLabel);
JTextField rowField = new JTextField(3);
l.setConstraints(rowField,c);
panel.add(rowField);
JLabel colLabel = new JLabel("Columns:");
l.setConstraints(colLabel,c);
panel.add(colLabel);
JTextField colField = new JTextField(3);
l.setConstraints(colField,c);
panel.add(colField);
JButton applyButton = new JButton("Apply");
applyButton.addActionListener(ev -> {
try {
int seed = Integer.parseInt(inputField.getText()),
rows = Integer.parseInt(rowField.getText()),
cols = Integer.parseInt(colField.getText());
if (seed >= 0 && rows >= 3 && cols >= 3) {
gameOver = false;
scoreLabel.setText("Score: " + (score = 0));
constructArray(seed,rows,cols);
row = (int) (Math.random() * (array.length / 2 + 1)) * 2;
col = (int) (Math.random() * (array[0].length / 2 + 1)) * 2;
frame.setSize((1+SQWIDTH * array[0].length)/2 > 750 ? (1+SQWIDTH * array[0].length)/2 : 750,
75+(1+SQWIDTH * array.length)/2);
mazePanel.setPreferredSize(new Dimension(
(1+SQWIDTH * array[0].length)/2 > 750 ? (1+SQWIDTH * array[0].length)/2 - 15 : 750,
15+(1+SQWIDTH * array.length)/2));
ellipse = new Ellipse2D.Double(col*SQWIDTH/2+3,row*SQWIDTH/2+3,10,10);
setItems();
mazePanel.repaint();
}
} catch (NumberFormatException ignore) {}
});
l.setConstraints(applyButton,c);
panel.add(applyButton);
scoreLabel = new JLabel("Score: ");
c.gridwidth = GridBagConstraints.REMAINDER;
l.setConstraints(scoreLabel,c);
panel.add(scoreLabel);
return panel;
}
private void createMazePanel() {
mazePanel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = 0, y = 0;
if (!gameOver) {
for (int k = 0; k < array.length; k+=2) {
for (int l = 0; l < array[0].length; l+=2) {
int n = array[k][l];
if (n == 0 || n == 1 || n == 4 || n == 5 || n == 6)
g.setColor(Color.white);
else if (n == 2)
g.setColor(Color.green);
else if (n == 3)
g.setColor(Color.red);
g.fillRect(x, y, SQWIDTH, SQWIDTH);
if (n == 4) {
g.setColor(new Color(245,209,34));
g.fillOval(x+3, y+3, 10, 10);
}
else if (n == 5) {
g.setColor(new Color(255,223,55));
g.fillPolygon(new int[]{x,x+3,x+8,x+13,x+16,x+14,x+2},new int[]{y+2,y+6,y+2,y+6,y+2,y+16,y+16},7);
g.setColor(new Color(12,234,44));
g.fillOval(x+7,y+6,4,7);
}
else if (n == 6) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setStroke(new BasicStroke(2,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
g2.setColor(new Color(108,225,119));
g2.drawOval(x+5, y+1, 8, 8);
g2.drawLine(x+5, y+3, x+5, y+11);
g2.drawArc(x+5, y+8, 7, 7, 180, 180);
}
g.setColor(Color.black);
if (k != array.length-1 && array[k+1][l] == -1)
g.fillRect(x-3, y+SQWIDTH-3, SQWIDTH+3, 3);
if (l != array[0].length-1 && array[k][l+1] == -1)
g.fillRect(x+SQWIDTH-3,y,3,SQWIDTH);
x += SQWIDTH;
}
x = 0;
y += SQWIDTH;
}
g.setColor(Color.red);
((Graphics2D) g).fill(ellipse);
}
}
};
}
private void setKeyActions() {
InputMap im = mazePanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = mazePanel.getActionMap();
im.put(KeyStroke.getKeyStroke("pressed UP"), "up");
am.put("up", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ev) {
if (row != 0 && array[row-1][col] != -1 && array[row-2][col] != 3 && !gameOver) {
int n = array[row][col];
array[row][col] = n == 0 || n == 1 || n == 4 || n == 5 ? 2 : 3;
row -= 2;
n = array[row][col];
if (n == 4)
scoreLabel.setText("Score: " + (score += 20));
else if (n == 5)
scoreLabel.setText("Score: " + (score += 50));
else if (n == 2)
scoreLabel.setText("Score: " + (score -= 1));
ellipse.y = row * SQWIDTH/2 + 3;
mazePanel.repaint();
}
if (!gameOver && array[row][col] == 6) {
JOptionPane.showMessageDialog(frame, "Huzzah! You found the exit! ", "Finish", JOptionPane.PLAIN_MESSAGE);
gameOver = true;
}
else if (!gameOver && checkGameOver()) {
JOptionPane.showMessageDialog(frame, "You got trapped! Try again!", "Game over", JOptionPane.PLAIN_MESSAGE);
gameOver = true;
}
}
});
im.put(KeyStroke.getKeyStroke("pressed RIGHT"), "right");
am.put("right",new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ev) {
if (col != array[0].length-1 && array[row][col+1] != -1 && array[row][col+2] != 3 && !gameOver) {
int n = array[row][col];
array[row][col] = n == 0 || n == 1 || n == 4 || n == 5 ? 2 : 3;
col += 2;
n = array[row][col];
if (n == 4)
scoreLabel.setText("Score: " + (score += 20));
else if (n == 5)
scoreLabel.setText("Score: " + (score += 50));
else if (n == 2)
scoreLabel.setText("Score: " + (score -= 1));
ellipse.x = col * SQWIDTH/2 + 3;
mazePanel.repaint();
}
if (!gameOver && array[row][col] == 6) {
JOptionPane.showMessageDialog(frame, "Huzzah! You found the exit! ", "Finish", JOptionPane.PLAIN_MESSAGE);
gameOver = true;
}
else if (!gameOver && checkGameOver()) {
JOptionPane.showMessageDialog(frame, "You got trapped! Try again!", "Game over", JOptionPane.PLAIN_MESSAGE);
gameOver = true;
}
}
});
im.put(KeyStroke.getKeyStroke("pressed DOWN"), "down");
am.put("down", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ev) {
if (row != array.length-1 && array[row+1][col] != -1 && array[row+2][col] != 3 && !gameOver) {
int n = array[row][col];
array[row][col] = n == 0 || n == 1 || n == 4 || n == 5 ? 2 : 3;
row += 2;
n = array[row][col];
if (n == 4)
scoreLabel.setText("Score: " + (score += 20));
else if (n == 5)
scoreLabel.setText("Score: " + (score += 50));
else if (n == 2)
scoreLabel.setText("Score: " + (score -= 1));
ellipse.y = row * SQWIDTH/2 + 3;
mazePanel.repaint();
}
if (!gameOver && array[row][col] == 6) {
JOptionPane.showMessageDialog(frame, "Huzzah! You found the exit! ", "Finish", JOptionPane.PLAIN_MESSAGE);
gameOver = true;
}
else if (!gameOver && checkGameOver()) {
JOptionPane.showMessageDialog(frame, "You got trapped! Try again!", "Game over", JOptionPane.PLAIN_MESSAGE);
gameOver = true;
}
}
});
im.put(KeyStroke.getKeyStroke("pressed LEFT"), "left");
am.put("left",new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ev) {
if (col != 0 && array[row][col-1] != -1 && array[row][col-2] != -1 && !gameOver) {
int n = array[row][col];
array[row][col] = n == 0 || n == 1 || n == 4 || n == 5 ? 2 : 3;
col -= 2;
n = array[row][col];
if (n == 4)
scoreLabel.setText("Score: " + (score += 20));
else if (n == 5)
scoreLabel.setText("Score: " + (score += 50));
else if (n == 2)
scoreLabel.setText("Score: " + (score -= 1));
ellipse.x = col * SQWIDTH/2 + 3;
mazePanel.repaint();
}
if (!gameOver && array[row][col] == 6) {
JOptionPane.showMessageDialog(frame, "Huzzah! You found the exit! ", "Finish", JOptionPane.PLAIN_MESSAGE);
gameOver = true;
}
else if (!gameOver && checkGameOver()) {
JOptionPane.showMessageDialog(frame, "You got trapped! Try again!", "Game over", JOptionPane.PLAIN_MESSAGE);
gameOver = true;
}
}
});
}
private void setItems() {
array[exitRow][exitCol] = 6;
Random r = new Random();
for (int k = 1; k < (array.length * array[0].length) / 20; k++) {
int row = r.nextInt(array.length / 2 + 1) * 2,
col = r.nextInt(array[0].length / 2 + 1) * 2;
if ((row == this.row && col == this.col) || array[row][col] == 4 || array[row][col] == 5 || array[row][col] == 6)
k--;
else
array[row][col] = r.nextInt(2) + 4;
}
}
private boolean checkGameOver() {
if (row == 0 && col == 0)
return (array[row+2][col] == 3 && array[row][col+2] == 3) ||
(array[row+1][col] == -1 && array[row][col+2] == 3) ||
(array[row+2][col] == 3 && array[row][col+1] == -1);
else if (row == 0 && col == array[0].length-1)
return (array[row+2][col] == 3 && array[row][col-2] == 3) ||
(array[row+1][col] == -1 && array[row][col-2] == 3) ||
(array[row+2][col] == 3 && array[row][col-1] == -1);
else if (row == array.length-1 && col == 0)
return (array[row-2][col] == 3 && array[row][col+2] == 3) ||
(array[row-1][col] == -1 && array[row][col+2] == 3) ||
(array[row-2][col] == 3 && array[row][col+1] == -1);
else if (row == array.length-1 && col == array[0].length-1)
return (array[row-2][col] == 3 && array[row][col-2] == 3) ||
(array[row-1][col] == -1 && array[row][col-2] == 3) ||
(array[row-2][col] == 3 && array[row][col-1] == -1);
else if (row == 0)
return (array[row+2][col] == 3 && array[row][col-1] == -1 && array[row][col+1] == -1) ||
(array[row+1][col] == -1 && array[row][col-2] == 3 && array[row][col+1] == -1) ||
(array[row+1][col] == -1 && array[row][col-1] == -1 && array[row][col+2] == 3) ||
(array[row+1][col] == -1 && array[row][col-2] == 3 && array[row][col+2] == 3) ||
(array[row+2][col] == 3 && array[row][col-2] == 3 && array[row][col+1] == -1) ||
(array[row+2][col] == 3 && array[row][col-1] == -1 && array[row][col+2] == 3) ||
(array[row+2][col] == 3 && array[row][col-2] == 3 && array[row][col+2] == 3);
else if (col == 0)
return (array[row][col+2] == 3 && array[row-1][col] == -1 && array[row+1][col] == -1) ||
(array[row][col+1] == -1 && array[row-2][col] == 3 && array[row+1][col] == -1) ||
(array[row][col+1] == -1 && array[row-1][col] == -1 && array[row+2][col] == 3) ||
(array[row][col+1] == -1 && array[row-2][col] == 3 && array[row+2][col] == 3) ||
(array[row][col+2] == 3 && array[row-2][col] == 3 && array[row+1][col] == -1) ||
(array[row][col+2] == 3 && array[row-1][col] == -1 && array[row+2][col] == 3) ||
(array[row][col+2] == 3 && array[row-2][col] == 3 && array[row+2][col] == 3);
else if (row == array.length-1)
return (array[row-2][col] == 3 && array[row][col-1] == -1 && array[row][col+1] == -1) ||
(array[row-1][col] == -1 && array[row][col-2] == 3 && array[row][col+1] == -1) ||
(array[row-1][col] == -1 && array[row][col-1] == -1 && array[row][col+2] == 3) ||
(array[row-1][col] == -1 && array[row][col-2] == 3 && array[row][col+2] == 3) ||
(array[row-2][col] == 3 && array[row][col-2] == 3 && array[row][col+1] == -1) ||
(array[row-2][col] == 3 && array[row][col-1] == -1 && array[row][col+2] == 3) ||
(array[row-2][col] == 3 && array[row][col-2] == 3 && array[row][col+2] == 3);
else if (col == array[0].length-1)
return (array[row][col-2] == 3 && array[row-1][col] == -1 && array[row+1][col] == -1) ||
(array[row][col-1] == -1 && array[row-2][col] == 3 && array[row+1][col] == -1) ||
(array[row][col-1] == -1 && array[row-1][col] == -1 && array[row+2][col] == 3) ||
(array[row][col-1] == -1 && array[row-2][col] == 3 && array[row+2][col] == 3) ||
(array[row][col-2] == 3 && array[row-2][col] == 3 && array[row+1][col] == -1) ||
(array[row][col-2] == 3 && array[row-1][col] == -1 && array[row+2][col] == 3) ||
(array[row][col-2] == 3 && array[row-2][col] == 3 && array[row+2][col] == 3);
else
return (array[row-2][col] == 3 && array[row][col+1] == -1 && array[row+1][col] == -1 && array[row][col-1] == -1) ||
(array[row-1][col] == -1 && array[row][col+2] == 3 && array[row+1][col] == -1 && array[row][col-1] == -1) ||
(array[row-1][col] == -1 && array[row][col+1] == -1 && array[row+2][col] == 3 && array[row][col-1] == -1) ||
(array[row-1][col] == -1 && array[row][col+1] == -1 && array[row+1][col] == -1 && array[row][col-2] == 3) ||
(array[row-1][col] == -1 && array[row+1][col] == -1 && array[row][col-2] == 3 && array[row][col+2] == 3) ||
(array[row-2][col] == 3 && array[row+2][col] == 3 && array[row][col-1] == -1 && array[row][col+1] == -1) ||
(array[row-2][col] == 3 && array[row+1][col] == -1 && array[row][col-1] == -1 && array[row][col+2] == 3) ||
(array[row-1][col] == -1 && array[row+2][col] == 3 && array[row][col-2] == 3 && array[row][col+1] == -1) ||
(array[row-1][col] == -1 && array[row+2][col] == 3 && array[row][col-1] == -1 && array[row][col+2] == 3) ||
(array[row-2][col] == 3 && array[row+1][col] == -1 && array[row][col-2] == 3 && array[row][col+1] == -1) ||
(array[row-1][col] == -1 && array[row+2][col] == 3 && array[row][col-2] == 3 && array[row][col+2] == 3) ||
(array[row-2][col] == 3 && array[row+1][col] == -1 && array[row][col-2] == 3 && array[row][col+2] == 3) ||
(array[row-2][col] == 3 && array[row+2][col] == 3 && array[row][col-1] == -1 && array[row][col+2] == 3) ||
(array[row-2][col] == 3 && array[row+2][col] == 3 && array[row][col-2] == 3 && array[row][col+1] == -1) ||
(array[row-2][col] == 3 && array[row+2][col] == 3 && array[row][col-2] == 3 && array[row][col+2] == 3);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(MazeMaker::new);
}
}
The process of creating the actual maze uses the depth-first search algorithm, but with an iterative approach. Here's how it works.
We start with a 2D array of int
values, each element being a -1. A random element with even indices is chosen, and its value becomes 0:
-1 -1 -1 -1 -1 -1 0 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1
Then the program enters a loop, checking for available cells until it reaches a state where there are no available cells. This can happen several times, and is when it begins to backtrack. At this time all 0s that it comes across become 1s. Also during this time is when it determines whether an exit should be placed at that location. So at the end of it all, the array could look like this:
0 0 0 0 0 0 1 1 1
0 -1 -1 -1 -1 -1 0 -1 1
0 0 0 0 0 -1 0 -1 1
-1 -1 -1 -1 -1 -1 0 -1 1
0 0 0 0 0 0 0 -1 1
0 -1 -1 -1 -1 -1 -1 -1 1
0 -1 1 1 1 1 1 -1 1
0 -1 1 -1 1 -1 -1 -1 1
0 -1 1 -1 1 1 1 1 1
When there are a predetermined number of 1s or 0s at certain spots in the array, the loop exits. Then, using the array, the resulting maze is drawn:
It's easy to see that the -1s in the array represent walls, and the 0s and 1s are corridors. Items are randomly distributed throughout the maze. The red ellipse is the "player" that you control.
The maze is wrapped in a scroll pane for convenience so that if the size exceeds the frame's maximum size you can scroll to see the rest of the maze.
I'd say the only problem with this is how the end of game check is carried out. I thought about several ways to go about doing it, but I ended up resorting to hard-coding it all. I'm open to suggestions on how it can be done.
1I feel as though this may come under fire for being too much of an art competition vs a programming one. Since all of these games should behave in a nearly identical way, what reason would I have to vote for one over another besides how nice it looks? There doesn't seem to be much potential creativity in making this maze game. That said, I'm more of a golfer, so I'll let people who more frequently participate in pop-cons decide if this is on topic. – FryAmTheEggman – 2015-04-20T22:51:55.343
1There are two separate parts to this challenge: one is the labyrinth design (but it is difficult to algorithmically produce a satisfactory game labyrinth) and the other is the game engine (which is rather easier). I think it would be better if you restricted the challenge to the game engine (and made it a code golf.) You would need to post some sample labyrinths (to be taken as input from text file) in the question (possibly designed with your son's help.) – Level River St – 2015-04-20T23:00:43.507
1
Related (and perhaps a partial duplicate of the labyrinth design part) http://codegolf.stackexchange.com/q/25967/15599
– Level River St – 2015-04-20T23:03:53.4633I think this is an excellent challenge. It has several problems to solve and room for creativity. More challenges like this please +1. [back to building a solution...] – Logic Knight – 2015-04-20T23:16:21.540
Are we required to generate "arrows". Do they allow the player to cross them the other way? – TheNumberOne – 2015-04-22T02:25:16.003
@steveverrill Is there a reason you would say the game engine should be a code-golf? The original poster explicitly states he doesn't want golf-code. – tfitzger – 2015-04-22T13:27:07.330
@TheNumberOne: sorry for the late answer. a) there needn't be arrows, they are optional. b) Arrows can be helpful or misleading. The problem is, you don't know in advance. c) You can generate arrows in any direction. The player is allowed to cross them in any direction as often as the rest of the rules allow (i.e. walk on the tile twice) – Thomas Weller – 2015-04-24T22:54:45.010
is there any way you can extend this? i've been working on something but it's not finished yet... – sirpercival – 2015-05-03T11:16:44.350
never mind, i can't get it to work... :( – sirpercival – 2015-05-03T11:50:41.887
@sirpercival: I put a bounty on this to get more answers. Usually I participate in the puzzles myself, but I didn't find time for this one. I have no idea how hard it really is. – Thomas Weller – 2015-05-03T14:15:15.503
i've been working on a maze app w/ kivy, but i can't get my flood fill algorithm to work. – sirpercival – 2015-05-04T16:28:39.513