Related
I am building a Sudoku Solver visualizer that will solve the sudoku board and show the computer's steps as it tries each number in the available slots. I have been using the JFrame/JPanel framework to visualize this but have had problems updating the graphics to show the computer solving it and pausing the graphics in between each new attempt at each slot.
The solver works perfectly and correctly solves the board but I can only see the unsolved board and the solved board when I click the enter button.
Here is my class with the main method:
public class sudokuSolverVisualizer {
public static void main(String[] args) {
new GameFrame();
}
}
Here is the Game Frame class:
import javax.swing.*;
public class GameFrame extends JFrame {
GameFrame(){
this.add(new GamePanel());
this.setTitle("Sudoku Solver");
this.setResizable(false);
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
And here is my JPanel class that solves the sudoku board and updates the graphics:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
public class GamePanel extends JPanel implements ActionListener {
//region Variable Declaration
private static final int SIZE = 9;
static final int SCREEN_WIDTH = 270;
static final int SCREEN_HEIGHT = 270;
static final int UNIT_SIZE = SCREEN_WIDTH/SIZE;
static final int GAME_DELAY = 25;
static final int[][] board = {
{7, 0, 2, 0, 5, 0, 6, 0, 0},
{0, 0, 0, 0, 0, 3, 0, 0, 0},
{1, 0, 0, 0, 0, 9, 5, 0, 0},
{8, 0, 0, 0, 0, 0, 0, 9, 0},
{0, 4, 3, 0, 0, 0, 7, 5, 0},
{0, 9, 0, 0, 0, 0, 0, 0, 8},
{0, 0, 9, 7, 0, 0, 0, 0, 5},
{0, 0, 0, 2, 0, 0, 0, 0, 0},
{0, 0, 7, 0, 4, 0, 2, 0, 3}
};
static boolean solving = false;
static boolean solved = false;
static Timer timer;
//endregion
GamePanel(){
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.lightGray);
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
startGame();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(g);
}
public void startGame(){
timer = new Timer(GAME_DELAY, this);
timer.start();
}
private static void draw(Graphics g) {
if (solving){
solveBoard(board, g);
}
Graphics2D g2 = (Graphics2D) g;
for (int row = 0; row < SIZE; row++){
if (row % 3 == 0 && row != 0){
g2.setStroke(new BasicStroke(5));
} else {
g2.setStroke(new BasicStroke(1));
}
g2.draw(new Line2D.Float(0, row * UNIT_SIZE, SCREEN_WIDTH, row * UNIT_SIZE));
for (int column = 0; column < SIZE; column++){
if (column % 3 == 0 && column != 0){
g2.setStroke(new BasicStroke(5));
} else {
g2.setStroke(new BasicStroke(1));
}
g2.draw(new Line2D.Float(column * UNIT_SIZE, 0, column * UNIT_SIZE, SCREEN_HEIGHT));
if (solved){
g.setColor(Color.green);
}
g2.drawString(String.valueOf(board[row][column]), column * UNIT_SIZE + 12, row * UNIT_SIZE + 22);
g.setColor(Color.black);
}
}
}
private static boolean isInRow(int[][] board, int num, int row){
for (int i = 0; i < SIZE; i++){
if (board[row][i] == num){
return true;
}
}
return false;
}
private static boolean isInColumn(int[][] board, int num, int column){
for (int i = 0; i < SIZE; i++){
if (board[i][column] == num){
return true;
}
}
return false;
}
private static boolean isInBox(int[][] board, int num, int row, int column){
int rowStart = row - row % 3;
int columnStart = column - column % 3;
for (int i = rowStart; i < rowStart + 3; i++){
for (int j = columnStart; j < columnStart + 3; j++) {
if (board[i][j] == num) {
return true;
}
}
}
return false;
}
private static boolean isValidPlace(int[][] board, int num, int row, int column){
return !isInRow(board, num, row) &&
!isInColumn(board, num, column) &&
!isInBox(board, num, row, column);
}
private static boolean solveBoard(int[][] board, Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.black);
for (int row = 0; row < SIZE; row++) {
for (int column = 0; column < SIZE; column++) {
if (board[row][column] == 0) {
for (int num = 1; num <= SIZE; num++) {
if (isValidPlace(board, num, row, column)) {
board[row][column] = num;
drawElements(g2, row, column, true);
if (solveBoard(board, g)) {
return true;
}
else{
board[row][column] = 0;
drawElements(g2, row, column, false);
}
}
}
return false;
}
}
}
solving = false;
solved = true;
draw(g);
return true;
}
private static void drawElements(Graphics2D g2, int row, int column, boolean correct){
if (correct) {
g2.setColor(Color.green);
} else {
g2.setColor(Color.red);
}
g2.clearRect(column * UNIT_SIZE, row * UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
g2.drawString(String.valueOf(board[row][column]), column * UNIT_SIZE + 12, row * UNIT_SIZE + 22);
g2.drawRect(column * UNIT_SIZE, row * UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
g2.setColor(Color.black);
}
private static void printBoard(int[][] board){
for (int row = 0; row < SIZE; row++){
if (row % 3 == 0 && row != 0){
System.out.println("-------------------");
}
for (int column = 0; column < SIZE; column++){
if (column % 3 == 0 && column != 0){
System.out.print("|");
}
System.out.print(board[row][column] + " ");
}
System.out.println();
}
}
#Override
public void actionPerformed(ActionEvent e) {
if (solving) {
repaint();
}
}
public class MyKeyAdapter extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()){
case KeyEvent.VK_ENTER:
solving = true;
break;
}
}
}
}
If you could help me find a way to pause the graphics in between each attempt at each slot or somehow repaint the graphics every time it tries a number, that would be great!
Visualising recursion like this is very hard, because what you need is control over the flow. Rather than the algorithm being allowed to run "freely", you need some way that you can control each iteration.
The first thing I did was adapted a non-recursive solver algorithm from Java | Recursive and non-recursive Sudoku solutions (Starter-friendly)
This allowed to create a step method which would move the solution along by a single (or close enough for the purpose) step.
This means that I can call step from Timer and control when the algorithm updates.
Important
This is NOT a Sudoku solution, this focus on how you might adopt a "recursive" style algorithm so it can be visualised, I take no responsibility for the accuracy of the algorithm 😉
Runnable example
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Stack;
import java.util.StringJoiner;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
int[][] board = {
{7, 0, 2, 0, 5, 0, 6, 0, 0},
{0, 0, 0, 0, 0, 3, 0, 0, 0},
{1, 0, 0, 0, 0, 9, 5, 0, 0},
{8, 0, 0, 0, 0, 0, 0, 9, 0},
{0, 4, 3, 0, 0, 0, 7, 5, 0},
{0, 9, 0, 0, 0, 0, 0, 0, 8},
{0, 0, 9, 7, 0, 0, 0, 0, 5},
{0, 0, 0, 2, 0, 0, 0, 0, 0},
{0, 0, 7, 0, 4, 0, 2, 0, 3}
};
public static void main(String[] args) {
new Main();
}
public Main() {
// print(board);
SudokuSolver solver = new SudokuSolver(board);
// System.out.println();
print(solver.solve());
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SudokuSolver solver = new SudokuSolver(board);
JFrame frame = new JFrame("Test");
frame.add(new MainPane(solver));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public void print(int[][] board) {
for (int[] row : board) {
StringJoiner joiner = new StringJoiner(" ", "", "\n");
for (int cell : row) {
joiner.add(String.format("%d", cell));
}
System.out.print(joiner.toString());
}
}
public class MainPane extends JPanel {
private SolverPane solverPane;
public MainPane(SudokuSolver solver) {
setLayout(new BorderLayout());
solverPane = new SolverPane(solver);
add(solverPane);
JButton startButton = new JButton("Go");
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
solverPane.start(new SolverPane.Observer() {
#Override
public void didCompleteSolution() {
startButton.setEnabled(true);
}
});
}
});
add(startButton, BorderLayout.SOUTH);
}
}
public class SolverPane extends JPanel {
public interface Observer {
public void didCompleteSolution();
}
private SudokuSolver solver;
private Observer observer;
private Dimension preferredSize;
private int gap = 4;
public SolverPane(SudokuSolver solver) {
this.solver = solver;
solver.prepareSolution();
setFont(new Font("Monospaced", Font.PLAIN, 20));
}
#Override
public Dimension getPreferredSize() {
if (preferredSize == null) {
FontMetrics fm = getFontMetrics(getFont());
preferredSize = new Dimension(10 + ((fm.stringWidth("0") + gap) * 9), 10 + (fm.getHeight() * 9));
}
return preferredSize;
}
public void start(Observer observer) {
this.observer = observer;
solver.prepareSolution();
Instant startedAt = Instant.now();
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (solver.step()) {
observer.didCompleteSolution();
((Timer) e.getSource()).stop();
Duration duration = Duration.between(Instant.now(), startedAt);
System.out.println(duration);
}
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
FontMetrics fm = g2d.getFontMetrics();
int gap = 4;
int cellWidth = fm.stringWidth("0") + 4;
int xPos = (getWidth() - (cellWidth * 9)) / 2;
int yPos = (getHeight() - (cellWidth * 9)) / 2;
g2d.translate(xPos, yPos);
Stack<SudokuSolver.Cell> solved = solver.getSolved();
if (solved != null) {
for (SudokuSolver.Cell cell : solver.getSolved()) {
int x = cell.getCol() * cellWidth;
int y = ((cell.getRow() - 1) * fm.getHeight());
g2d.drawString(Integer.toString(cell.getValue()), x, y);
}
}
g2d.dispose();
}
}
class SudokuSolver {
// Adapted from https://leetcode.com/problems/sudoku-solver/discuss/1392747/java-recursive-and-non-recursive-sodoku-solutions-starter-friendly
private int[][] board;
private LinkedList<Cell> unsolved;
private Stack<Cell> solved;
public SudokuSolver(int[][] originalBoard) {
this.board = originalBoard;
}
public LinkedList<Cell> getUnsolved() {
return unsolved;
}
public Stack<Cell> getSolved() {
return solved;
}
protected void prepareSolution() {
// all we need are just 1 stack, and 1 queue.
unsolved = new LinkedList<>(); // queue: all unconfirmed cells
solved = new Stack<>(); // stack: all cells which are confirmed temporarily
// init candidates
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == 0) {
Cell cell = new Cell(i, j, board);
unsolved.addLast(cell);
} else {
Cell cell = new Cell(i, j);
cell.value = board[i][j];
solved.add(cell);
}
}
}
}
public boolean step() {
if (unsolved == null || solved == null) {
prepareSolution();
}
if (unsolved.peekFirst() == null) {
return true;
}
Cell curr = unsolved.removeFirst();
if (curr.isValidCandid()) {
solved.push(curr); // *
unsolved = excludeCandid(curr, unsolved);
} else { // MARK-s4
unsolved.addFirst(curr); // *
Cell prev = solved.pop(); // *
unsolved = revertCandid(prev, unsolved);
curr.resetCandid(); // restart selection
prev.nextCandid();
unsolved.addFirst(prev); // *
}
return unsolved.peekFirst() == null;
}
public int[][] solve() {
prepareSolution();
// try different combinations until all unsolved cells gone
Cell curr;
while (unsolved.peekFirst() != null) {
curr = unsolved.removeFirst();
if (curr.isValidCandid()) {
solved.push(curr); // *
unsolved = excludeCandid(curr, unsolved);
} else { // MARK-s4
unsolved.addFirst(curr); // *
Cell prev = solved.pop(); // *
unsolved = revertCandid(prev, unsolved);
curr.resetCandid(); // restart selection
prev.nextCandid();
unsolved.addFirst(prev); // *
}
}
int[][] solution = new int[board.length][board.length];
for (int row = 0; row < board.length; row++) {
for (int col = 0; col < board[row].length; col++) {
solution[row][col] = board[row][col];
}
}
// solutions back
while (!solved.empty()) {
confirm(solved.pop(), solution);
}
return solution;
}
void confirm(Cell solution, int[][] problem) {
problem[solution.row][solution.col] = solution.value;
}
// once some candidates are taken, we need to exclude them in related cells
LinkedList<Cell> excludeCandid(Cell target, LinkedList<Cell> before) {
LinkedList<Cell> after = new LinkedList<Cell>();
Cell curr;
while ((curr = before.peekFirst()) != null) {
before.removeFirst();
curr.excludeCandidate(target); // exclude the target candidate
// OPTIONAL, but win more about 70% time!
if (curr.isValidCandid()) // if there is conflict, handle it first
{
after.addLast(curr);
} else {
after.addFirst(curr);
}
}
return after;
}
// once the previous candidates were incorrectly taken, we need to revert/recover them in related cells
LinkedList<Cell> revertCandid(Cell target, LinkedList<Cell> before) {
LinkedList<Cell> after = new LinkedList<Cell>();
Cell curr = null;
while ((curr = before.peekFirst()) != null) {
before.removeFirst();
curr.enableCandidate(target);
after.addLast(curr);
}
return after;
}
// >> SOLUTION
// << STRUCT
public class Cell {
/**
*
* To solve sudoku, we can use one stack to save all temporarily
* confirmed cells, and one queue to save all unconfirmed ones, then
* repeatedly dequeue elements from queue and push them into the
* stack, analysing them at the same time, until queue is empty,
* then Sudoku is solved. If stack encounter EmptyStackException at
* some point of time, this method then is not capable to solve the
* given Sudoku. Note, analysing cells is simple and
* straightforward, just the "pointer" stuff, and the detail is
* shown below.
*
*
* ################################## ## "1 stack and 1 queue" model
* : ##################################
*
* ........................................................................
* .----------- (2,5) (2,4) (2,6) (2,0) .... |
* ........................................................................
* | (unsolved Queue/List) | | \/ | | | | | (2,3) | | .... | | ....
* | | .... | ---------- (solved Stack)
*
*
*
* ################################## ## "candidate pointer" model :
* ( cell(2,0) at any possible point of time )
* ##################################
*
* Characters: 1 2 3 4 5 6 7 8 9
*
* candidates: [-999999, 0 , 33 , -1 , 78 , 0 , 0 , -1 , 0 , -1] ^
* [Stage 1] ... 21 ^ [Stage 2] ... 21 23 22 25 ^(>9) [Stage 3]
*
* Explanations: [Stage 1] candidate pointer(cPtr), when at very
* beginning, and there are 3 possible values that can be chosen;
* [Stage 2] after '1' is selected by 21st cell, i.e. problem[2][2],
* cPtr moves to 5th; [Stage 3] at this point, the '1' was taken by
* 21st cell, '5' was by 23rd, '6' was by 22nd, '8' was by 25th,
* result in cPtr overflow, which means some of previous candidates
* were taken incorrectly, and getting back and retrying is needed.
* [Stage 4] details is shown in some place of codes, marked as
* "MARK-s4"
*
*
*
*/
private int row;
private int col;
private int[] candidates; // -1: confirmed 0: possible >0: be selected by others
private int value = -1; // [1,9] or 10,11,...
public Cell(int i, int j) {
row = i;
col = j;
candidates = new int[10];
candidates[0] = -999999;
}
public Cell(int i, int j, int[][] datasource) {
this(i, j);
initCandidates(datasource);
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
public int getValue() {
return value;
}
protected void initCandidates(int[][] datasource) {
// same row
for (int i = 0; i < 9; i++) {
if (datasource[row][i] != 0) {
candidates[datasource[row][i]] = -1;
}
}
// same col
for (int i = 0; i < 9; i++) {
if (datasource[i][col] != 0) {
candidates[datasource[i][col]] = -1;
}
}
// same 9-cell
int start_i = row / 3 * 3;
int start_j = col / 3 * 3;
for (int i = start_i; i < start_i + 3; i++) {
for (int j = start_j; j < start_j + 3; j++) {
if (datasource[i][j] != 0) {
candidates[datasource[i][j]] = -1;
}
}
}
// init candid ptr
resetCandid();
}
protected int getCurrCandid() { // [1-9] or -1
if (isValidCandid()) {
return value;
}
return -1;
}
private void resetCandid() {
// to left most 0
int i = 1;
for (; i < 10; i++) {
if (candidates[i] == 0) {
break;
}
}
value = i; // 1..9 or 10
}
private void nextCandid() {
int i = value + 1;
while (i < 10 && candidates[i] != 0) {
i++;
}
value = i; // 1..9 or 10,11,...
}
private void excludeCandidate(Cell by) {
int their = by.getCurrCandid();
if (candidates[their] == 0) {
int theirIdx = by.row * 9 + by.col + 1;
// same row
if (by.row == row) {
candidates[their] = theirIdx;
}
// same col
if (by.col == col) {
candidates[their] = theirIdx;
}
// same cell
if (by.row / 3 * 3 == row / 3 * 3 && by.col / 3 * 3 == col / 3 * 3) {
candidates[their] = theirIdx;
}
}
if (!isValidCandid()) {
nextCandid();
}
}
private void enableCandidate(Cell by) {
int their = by.getCurrCandid(); // must exist
int theirIdx = by.row * 9 + by.col + 1;
if (candidates[their] > 0 && candidates[their] == theirIdx) { // result from their
candidates[their] = 0; // >0 -> 0
if (value >= their) {
resetCandid(); // *
}
}
}
private int numOfCandidate() {
int num = 0;
for (int i = 1; i < 10; i++) {
if (candidates[i] == 0) {
num++;
}
}
return num;
}
private boolean isValidCandid() {
if (value < 1 || value > 9) {
return false;
}
return candidates[value] == 0;
}
public String toString() {
return String.format("%d,%d cptr:%d(%b) (%d)candids:%s\n", row, col, value, isValidCandid(), numOfCandidate(), Arrays.toString(candidates));
}
}
// >> STRUCT
}
}
I am currently making a maze solving game in Java, and I am currently running into a snag. All of the random maze generation algorithms that I could find output in a way that I couldn't figure out how to implement into my current code. I was considering using the Depth First Search, Recursive Backtracker, or Prim's Algorithm, since I thought they would be the easiest to implement while still generating good mazes. What would be a working use of one of those algorithms that works with my current program? This is my Game Class: (Feel free to point out any bad practices as well, I am pretty new to Java)
package game;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Game extends JPanel implements ActionListener, KeyListener {
private boolean upPressed = false;
private boolean downPressed = false;
private boolean rightPressed = false;
private boolean leftPressed = false;
private final int playerDiam = 100;
private final int playerSpeed = 15;
private final int tileSize = 400;
private int[][] maze = {{1, 1, 1, 1, 1, 1},
{1, 2, 1, 1, 3, 1},
{1, 0, 1, 0, 0, 1},
{1, 0, 1, 0, 1, 1},
{1, 0, 0, 0, 1, 1},
{1, 1, 1, 1, 1, 1},
};
private int[][] initX = new int[maze.length][maze.length];
private int[][] initY = new int[maze.length][maze.length];
private int deltaX = -210;
private int deltaY = -210;
private String screen = "menu";
public Game() {
setFocusable(true);
addKeyListener(this);
setUpInitialCoordinates();
Timer timer = new Timer(1000 / 60, this);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
tick();
}
private void setUpInitialCoordinates() {
int x = 0;
int y;
for (int[] rowData : maze) {
y = 0;
for (int ignored : rowData) {
initX[x][y] = x * tileSize;
initY[x][y] = y * tileSize;
y++;
}
x++;
}
}
private void generateMaze() {
}
private void tick() {
if (screen.equals("playing")) {
if (upPressed) {
deltaY += playerSpeed;
} else if (downPressed) {
deltaY -= playerSpeed;
}
if (rightPressed) {
deltaX -= playerSpeed;
} else if (leftPressed) {
deltaX += playerSpeed;
}
}
repaint();
}
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) {
upPressed = true;
} else if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_S) {
downPressed = true;
} else if (e.getKeyCode() == KeyEvent.VK_RIGHT || e.getKeyCode() == KeyEvent.VK_D) {
rightPressed = true;
} else if (e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_A) {
leftPressed = true;
}
}
#Override
public void keyReleased(KeyEvent e) {
if (screen.equals("menu") && e.getKeyCode() == KeyEvent.VK_ENTER) {
upPressed = false;
downPressed = false;
rightPressed = false;
leftPressed = false;
screen = "playing";
} else if (screen.equals("playing")) {
if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) {
upPressed = false;
} else if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_S) {
downPressed = false;
} else if (e.getKeyCode() == KeyEvent.VK_RIGHT || e.getKeyCode() == KeyEvent.VK_D) {
rightPressed = false;
} else if (e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_A) {
leftPressed = false;
} else if (e.getKeyCode() == KeyEvent.VK_P) {
screen = "paused";
}
} else if (screen.equals("paused" ) && e.getKeyCode() == KeyEvent.VK_P) {
upPressed = false;
downPressed = false;
rightPressed = false;
leftPressed = false;
screen = "playing";
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(new Font("Aharoni", Font.PLAIN, 36));
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
switch (screen) {
case "menu":
g.setColor(Color.BLACK);
g.drawString("Labyrinth", 300, 200);
g.drawString("Press Enter to Play!", getWidth() / 3, 500);
break;
case "playing":
int x = 0;
int y = 0;
for (int[] rowData : maze) {
for (int cellData : rowData) {
if (cellData == 1) {
g.setColor(Color.DARK_GRAY);
g.fillRect(x + deltaX, y + deltaY, tileSize, tileSize);
} else if (cellData == 2) {
g.setColor(Color.GREEN);
g.fillRect(x + deltaX, y + deltaY, tileSize, tileSize);
} else if (cellData == 3) {
g.setColor(Color.YELLOW);
g.fillRect(x + deltaX, y + deltaY, tileSize, tileSize);
}
x += tileSize;
if (x == maze.length * tileSize) {
x = 0;
y += tileSize;
}
}
} g.setColor(Color.RED);
g.fillOval(getWidth() / 2, getHeight() / 2, playerDiam, playerDiam);
break;
case "gameOver":
g.setColor(Color.BLACK);
g.drawString("Game Over",getWidth() / 3 ,50 );
break;
case "paused":
g.setColor(Color.BLACK);
g.drawString("Paused", getWidth() / 3, 50);
break;
}
}
}
as start I would
clear maze
randomly add walls
create random path for all entry/exit points pairs present
clear the maze cells along them.
If you just need maze solver (some algos for maze generation needs them)
then look here How to speed up A* algorithm at large spatial scales?
with use of solver you can change algorithm to
clear maze
add random wall
if adding it still provides solution
loop bullet(2) N times
You need to be careful about how to add the walls
for example if you add just simple lines then the maze will look like this:
This is the code for it (uses the class from linked answer)
// generate random maze with path from x0,y0 to x1,y1 present, n walls
void A_star::generate(int x0,int y0,int x1,int y1,int n)
{
int x,y,i,j,dx,dy,l,*p;
// [clear map]
for (y=0;y<ys;y++)
for (x=0;x<xs;x++)
map[y][x]=A_star_space;
// temp space
p=new int [xs>>1]; if (p==NULL) return;
// generate n walls
for (i=0;i<n;i++)
{
// random start pos,dir,length
x =Random(xs);
y =Random(ys);
dx=Random(4);
l =Random(xs>>2)+2;
if (dx==0) { dx=+1; dy= 0; }
else if (dx==1) { dx=-1; dy= 0; }
else if (dx==2) { dx= 0; dy=+1; }
else if (dx==3) { dx= 0; dy=-1; }
// add wall to maze remember set cells (for remowal if needed)
for (j=0;l;l--,x+=dx,y+=dy)
if ((x>=0)&&(x<xs))
if ((y>=0)&&(y<ys))
if (map[y][x]!=A_star_wall)
{
p[j]=x; j++;
p[j]=y; j++;
map[y][x]=A_star_wall;
}
// is there solution?
compute(x0,y0,x1,y1);
// if not remowe last added wall
if (ps==0) for (;j;)
{
j--; y=p[j];
j--; x=p[j];
map[y][x]=A_star_space;
}
}
delete[] p;
}
generated by this code:
A_star map;
map.resize(256,256);
map.generate(5,5,250,250,500);
Hello I am relatively new to java and I am writing a tetris style program. I am currently getting a nullpointerException like this :
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at tetris2.GameBoard.drawSquare(Tetris.java:359)
at tetris2.GameBoard.paint(Tetris.java:250)
I have marked out the lines where the exception is pointing to. I understand what a NulPointerException is but I cant work out what class that I have referenced without creating
here is my code:
public class Tetris {
public static void createGUI()
{
final JFrame frame = new JFrame("159.235 - A2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel contentPane = new JPanel(new BorderLayout());
frame.setContentPane(contentPane);
final GameBoard gameBoard = new GameBoard();
contentPane.add(gameBoard, BorderLayout.CENTER);
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e)
{
gameBoard.keyTyped(e);
}
#Override
public void keyReleased(KeyEvent e)
{
gameBoard.keyReleased(e);
}
#Override
public void keyPressed(KeyEvent e)
{
switch (e.getKeyChar()) {
case KeyEvent.VK_ESCAPE:
gameBoard.pauseGame();
System.exit(0);
break;
default:
gameBoard.keyPressed(e);
}
}
});
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
// Note: you might want to add a button to start, pause, or resume the
// game instead of automatically starting it here
gameBoard.startGame();
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run()
{
createGUI();
}
});
}
}
class GameBoard extends JPanel implements KeyListener {
private static final long serialVersionUID = 1L;
// the number of rows and columns on the game board
public static final int NUM_COLS = 10;
public static final int NUM_ROWS = 20;
// the size of each cell in pixels
public static final int CELL_SIZE = 20;
// the game board size in pixels
public final int GAME_FIELD_WIDTH = NUM_COLS * CELL_SIZE;
public final int GAME_FIELD_HEIGHT = NUM_ROWS * CELL_SIZE;
// the interval between game state updates
private int m_updateInterval = 500;
// the game timer initiates an update to the game state
private final Timer m_gameTimer = new Timer(m_updateInterval,
new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
if(isFallingFinished) {
isFallingFinished = false;
newPiece();;
} else {
oneLineDown();
}
}
});
// the game board, with [0][0] being the bottom left cell
//private final Block[][] m_cells = new Block[NUM_ROWS][NUM_COLS];
// the currently active shape
private Shapes m_currentShape = new Shapes();
public int curX = 0;
public int curY = 0;
tetrisPieces[] board;
boolean isFallingFinished = false;
boolean isStarted = false;
boolean isPaused = false;
int numLinesRemoved = 0;
// ////////////////////////////////////////////////////////////////////
public GameBoard()
{
setMinimumSize(new Dimension(GAME_FIELD_WIDTH + 1, GAME_FIELD_HEIGHT));
setPreferredSize(new Dimension(GAME_FIELD_WIDTH + 1, GAME_FIELD_HEIGHT));
setOpaque(true);
// set-up the timer for the render loop
m_gameTimer.setInitialDelay(m_updateInterval);
m_gameTimer.setRepeats(true);
board = new tetrisPieces[GAME_FIELD_WIDTH * GAME_FIELD_HEIGHT];
clearBoard();
newPiece();
}
int squareWidth() { return (int) getSize().getWidth() / GAME_FIELD_WIDTH;}
int squareHeight() { return (int) getSize().getHeight() / GAME_FIELD_HEIGHT; }
tetrisPieces shapeAt(int x, int y) { return board[(y * GAME_FIELD_WIDTH) + x]; }
#Override
public void keyPressed(KeyEvent e)
{
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
m_currentShape.rotateShape();
break;
case KeyEvent.VK_DOWN: // move down
oneLineDown();
break;
case KeyEvent.VK_LEFT: // move left
tryMove(m_currentShape, curX - 1, curY);
break;
case KeyEvent.VK_RIGHT: // move right
tryMove(m_currentShape, curX + 1, curY);
break;
case KeyEvent.VK_SPACE: // toggle pause / resume
if (m_gameTimer.isRunning()) pauseGame();
else startGame();
break;
}
}
#Override
public void keyReleased(KeyEvent e)
{
switch (e.getKeyCode()) {
case KeyEvent.VK_DOWN: // disable down key
break;
case KeyEvent.VK_LEFT: // disable left key
break;
case KeyEvent.VK_RIGHT: // disable right key
break;
}
}
#Override
public void keyTyped(KeyEvent e)
{}
public void startGame()
{
if(isPaused)
return;
isStarted = true;
isFallingFinished = false;
numLinesRemoved = 0;
clearBoard();
board = new tetrisPieces[getWidth() * getHeight()];
newPiece();
m_gameTimer.start();
}
public void pauseGame()
{
if (!isStarted)
return;
isPaused = !isPaused;
if(isPaused) {
m_gameTimer.stop();
//statusbar.setText("paused");
} else {
m_gameTimer.start();
//statusbar.setText(String.valueOf(numLinesRemoved));
}
repaint();
}
public void paint(Graphics g)
{
super.paint(g);
Dimension size = getSize();
int boardTop = (int) size.getHeight() - GAME_FIELD_HEIGHT * squareHeight();
for (int i = 0; i < GAME_FIELD_HEIGHT; ++i) {
for (int j = 0; j < GAME_FIELD_WIDTH; ++j) {
tetrisPieces shapes = shapeAt(j, GAME_FIELD_HEIGHT - i -1);
if (shapes != tetrisPieces.noPiece)
drawSquare(g, 0 + j * squareWidth(),boardTop + i * squareHeight(), shapes);
//**This line above**
}
}
if(m_currentShape.getShape() != tetrisPieces.noPiece) {
for(int i = 0; i <4; ++i) {
int x = curX + + m_currentShape.x(i);
int y = curY - m_currentShape.y(i);
drawSquare(g, 0 + x * squareWidth(),
boardTop + (GAME_FIELD_HEIGHT - y - 1) * squareHeight(),
m_currentShape.getShape());
}
}
}
private void oneLineDown()
{
if(!tryMove(m_currentShape, curX, curY - 1))
pieceDropped();
}
private void clearBoard() {
for (int i = 0; i <GAME_FIELD_HEIGHT * GAME_FIELD_WIDTH; ++i)
board[i] = tetrisPieces.noPiece;
}
private boolean tryMove(Shapes newPiece, int newX, int newY) {
for(int i = 0; i< 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if( x < 0 || x >= GAME_FIELD_WIDTH || y < 0 || y >= GAME_FIELD_HEIGHT)
return false;
if(shapeAt(x, y) != tetrisPieces.noPiece)
return false;
}
m_currentShape = newPiece;
curX = newX;
curY = newY;
repaint();
return true;
}
private void pieceDropped() {
for (int i =0; i < 4; ++i) {
int x = curX + m_currentShape.x(i);
int y = curY - m_currentShape.y(i);
board[(y * GAME_FIELD_WIDTH) + x] = m_currentShape.getShape();
}
removeFullLines();
if(!isFallingFinished)
newPiece();
}
private void removeFullLines() {
int numFullLines = 0;
for(int i = GAME_FIELD_HEIGHT -1; i >= 0; --i) {
boolean lineIsFull = true;
for(int j = 0; j < GAME_FIELD_WIDTH; ++j) {
if(shapeAt(j, i) == tetrisPieces.noPiece) {
lineIsFull = false;
break;
}
}
if(lineIsFull) {
++numFullLines;
for(int k = i; k < GAME_FIELD_HEIGHT - 1; ++k) {
for(int j = 0; j < GAME_FIELD_WIDTH; ++j)
board[(k* GAME_FIELD_WIDTH) + j] = shapeAt(j, k + 1);
}
}
}
}
private void newPiece() {
m_currentShape.setRandomShape();
curX = GAME_FIELD_WIDTH / 2 + 1;
curY = GAME_FIELD_HEIGHT - 1 + m_currentShape.minY();
if(!tryMove(m_currentShape, curX, curY)) {
m_currentShape.setShape(tetrisPieces.noPiece);
m_gameTimer.stop();
isStarted = false;
}
}
private void drawSquare(Graphics g, int x, int y, tetrisPieces shape)
{
Color colors[] = { new Color(0, 0, 0), new Color(204, 102, 102),
new Color(102, 204, 102), new Color(102, 102, 204),
new Color(204, 204, 102), new Color(204, 102, 204),
new Color(102, 204, 204), new Color(218, 170, 0)
};
Color color = colors[shape.ordinal()]; //**This line here**
g.setColor(color);
g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);
g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);
g.setColor(color.darker());
g.drawLine(x + 1, y + squareHeight() - 1,
x + squareWidth() - 1, y + squareHeight() - 1);
g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1,
x + squareWidth() - 1, y + 1);
}
}
The Shapes class:
public class Shapes {
enum tetrisPieces { noPiece, ZShape, SSHape, TShape, LShape, SquareShape,
ReverseLShape}
private tetrisPieces pieceShape;
private int coords[][];
private int coordsTable[][][];
public Shapes() {
coords = new int[4][2];
setShape(tetrisPieces.noPiece);
}
public void setShape(tetrisPieces shape) {
coordsTable = new int[][][] {
{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
{ { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } },
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } },
{ { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } },
{ { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } },
{ { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }
};
for(int i = 0; i < 4; i++) {
for(int j = 0; j<2; ++j) {
coords[i][j] = coordsTable[shape.ordinal()][i][j];
}
}
pieceShape = shape;
}
private void setX(int index, int x) { coords[index][0] = x; }
private void setY(int index, int y) { coords[index][1] = y; }
public int x(int index) { return coords[index][0]; }
public int y(int index) { return coords[index][1]; }
public tetrisPieces getShape() { return pieceShape; }
public void setRandomShape()
{
Random r = new Random();
int x = Math.abs(r.nextInt()) % 6 + 1;
tetrisPieces[] values = tetrisPieces.values();
setShape(values[x]);
}
public int minX() {
int m =coords[0][0];
for (int i = 0; i < 4; i++) {
m = Math.min(m, coords[i][0]);
}
return m;
}
public int minY() {
int m = coords[0][1];
for (int i =0; i <4; i++) {
m = Math.min(m, coords[i][1]);
}
return m;
}
public Shapes rotateShape() {
if(pieceShape == tetrisPieces.SquareShape)
return this;
Shapes result = new Shapes();
result.pieceShape = pieceShape;
for(int i = 0; i <4; ++i) {
result.setX(i, -y(i));
result.setY(i, x(i));
}
return result;
}
}
Color color = colors[shape.ordinal()];
The only thing that can be null at this line is shape, since colors is iniyialized the line before.
Where does shape come from? From the line
tetrisPieces shapes = shapeAt(j, GAME_FIELD_HEIGHT - i -1);
What does shapeAt() return?
tetrisPieces shapeAt(int x, int y) { return board[(y * GAME_FIELD_WIDTH) + x]; }
Have you initialized the elements of the board array before this line is executed? Probably not. The debugger will confirm that. What is probably buggy is these lines:
clearBoard();
board = new tetrisPieces[getWidth() * getHeight()];
The first one initializes every element of the board array, but the second throws away the initialized board array and replaces it by a new, empty one.
I'm having some problems with Tetris. So first of all, I have a class Shape, and then subclasses of Shape for each shape type. This is how a Shape subclass looks like:
public class SquareShape extends Shape {
public SquareShape(){
coords = squareShapeCoords;
shape = SQUARESHAPE;
}
}
In the Shape class I have a rotate method as follows:
protected void setX(int i, int x) { coords[i][0] = x; }
protected void setY(int i, int y) { coords[i][1] = y; }
public int x(int i) { return coords[i][0]; }
public int y(int i) { return coords[i][1]; }
public Shape rotate(){
if (this.getShape().equals(SQUARESHAPE)) return this;
Shape newShape = new Shape();
newShape.shape = this.shape;
for (int i = 0; i < 4; i++) {
newShape.setX(i, y(i));
newShape.setY(i, -x(i));
}
return newShape;
}
Note that I store the coordinates of each shape in 2D arrays.
Also, this is my game engine class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GameEngine extends JPanel implements ActionListener{
private final int HEIGHT = 15;
private final int WIDTH = 10;
private int score;
int coordX = 0;
int coordY = 0;
Timer timer;
boolean isFinishedFalling = false;
boolean isRunning = false;
boolean isPaused = false;
Shape block;
String[] shapes;
public GameEngine(){
setFocusable(true);
block = new Shape();
timer = new Timer(600, this);
timer.start();
addMouseListener(new MAdapter());
addMouseWheelListener(new WheelAdapter());
addKeyListener(new KAdapter());
setBackground(Color.BLACK);
shapes = new String[WIDTH * HEIGHT];
clearShapes();
}
int squareWidth() { return (int) getSize().getWidth() / WIDTH; }
int squareHeight() { return (int) getSize().getHeight() / HEIGHT; }
String shapeAt(int x, int y) { return shapes[(y * WIDTH) + x]; }
public int getScore() {return score;}
public void actionPerformed(ActionEvent e){
if(isFinishedFalling){
isFinishedFalling = false;
newBlock();
} else moveDown();
}
private boolean move(Shape newShape, int newCoordX, int newCoordY)
{
for (int i = 0; i < 4; ++i) {
int x = newCoordX + newShape.x(i);
int y = newCoordY - newShape.y(i);
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return false;
if (!shapeAt(x, y).equals(Shape.DEFAULTSHAPE)) return false;
}
block = newShape;
coordX = newCoordX;
coordY = newCoordY;
repaint();
return true;
}
private boolean moveLeft() { return move(block, coordX-1, coordY);}
private boolean moveRight() { return move(block, coordX+1, coordY);}
private boolean moveDown(){
if(!move(block, coordX, coordY-1)){
blockIsDown();
return false;
} else return true;
}
private void dropDown(){
int y = coordY;
while(y>0){
if(!move(block, coordX, y-1)) break;
y -= 1;
}
blockIsDown();
}
private boolean rotate() { return move(block.rotate(), coordX, coordY);}
private void blockIsDown(){
for(int i=0; i<4; i++){
int a = coordX + block.x(i);
int b = coordY - block.y(i);
shapes[b * WIDTH + a] = block.getShape();
}
clearFullLines();
if(!isFinishedFalling) newBlock();
}
private void clearFullLines(){
int fullLines = 0;
for(int i = HEIGHT-1; i>=0; i--){
boolean lineFull = true;
for(int j=0; j<WIDTH; j++){
if(shapeAt(j, i).equals(Shape.DEFAULTSHAPE)){
lineFull = false;
break;
}
}
if(lineFull){
fullLines++;
for(int m=i; m<HEIGHT-1; m++){
for(int n=0; n<WIDTH; n++)
shapes[(m*WIDTH) + n] = shapeAt(n, m+1);
}
}
}
if(fullLines>0){
score += fullLines*100;
isFinishedFalling = true;
block.setShape(Shape.DEFAULTSHAPE);
repaint();
}
}
private void newBlock()
{
block = new RandomShape();
coordX = WIDTH / 2 + 1;
coordY = HEIGHT - 1 + block.minY();
if (!move(block, coordX, coordY)) {
block.setShape(Shape.DEFAULTSHAPE);
timer.stop();
isRunning = false;
}
}
private void clearShapes(){
for(int i=0; i< WIDTH * HEIGHT; i++) shapes[i] = Shape.DEFAULTSHAPE;
}
private void drawSquare(Graphics g, int x, int y, String shape){
Color color = Color.BLACK;
if(shape.equals(Shape.ZSHAPE)) color = Color.GREEN;
if(shape.equals(Shape.SSHAPE)) color = Color.RED;
if(shape.equals(Shape.LINESHAPE)) color = Color.CYAN;
if(shape.equals(Shape.TSHAPE)) color = Color.BLUE;
if(shape.equals(Shape.SQUARESHAPE)) color = Color.YELLOW;
if(shape.equals(Shape.LSHAPE)) color = Color.MAGENTA;
if(shape.equals(Shape.MIRROREDLSHAPE)) color = Color.ORANGE;
g.setColor(color);
g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);
g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);
g.setColor(color.darker());
g.drawLine(x + 1, y + squareHeight() - 1, x + squareWidth() - 1, y + squareHeight() - 1);
g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1, x + squareWidth() - 1, y + 1);
}
public void paint(Graphics g){
super.paint(g);
Dimension size = getSize();
int top = (int) size.getHeight() - HEIGHT * squareHeight();
for(int i=0; i<HEIGHT; i++){
for(int j=0; j<WIDTH; j++){
String s = shapeAt(j, HEIGHT-i-1);
if(!s.equals(Shape.DEFAULTSHAPE))
drawSquare(g, j * squareWidth(), top + i * squareHeight(), s);
}
}
if(!block.getShape().equals(Shape.DEFAULTSHAPE)){
for(int i=0; i<4; i++){
int x = coordX + block.x(i);
int y = coordY - block.y(i);
drawSquare(g, x * squareWidth(), top + (HEIGHT - y - 1) * squareHeight(), block.getShape());
}
}
}
public void start(){
if(isPaused) return;
isRunning = true;
isFinishedFalling = false;
score = 0;
clearShapes();
newBlock();
timer.start();
}
private void pause(){
if(!isRunning) return;
isPaused = !isPaused;
if(isPaused){
timer.stop();
} else{
timer.start();
}
repaint();
}
class MAdapter extends MouseAdapter{
public void mouseClicked(MouseEvent e){
if (!isRunning || block.getShape().equals(Shape.DEFAULTSHAPE) || isPaused) return;
int buttonPressed = e.getButton();
if(buttonPressed == MouseEvent.BUTTON1) moveLeft();
if(buttonPressed == MouseEvent.BUTTON2) rotate();
if(buttonPressed == MouseEvent.BUTTON3) moveRight();
}
}
class WheelAdapter implements MouseWheelListener{
public void mouseWheelMoved(MouseWheelEvent e){
if (!isRunning || block.getShape().equals(Shape.DEFAULTSHAPE) || isPaused) return;
int wheelRotation = e.getWheelRotation();
if(wheelRotation == 1) moveDown();
if(wheelRotation == -1) dropDown();
}
}
class KAdapter extends KeyAdapter{
public void keyPressed(KeyEvent e){
if (!isRunning || block.getShape().equals(Shape.DEFAULTSHAPE) || isPaused) return;
int key = e.getKeyCode();
if(key == KeyEvent.VK_SPACE) pause();
}
}
}
My problem is the following: When I try to rotate the blocks, it works good the first time, but it messes up completely if I try to rotate them a second time.
This is supposed to be my line shape:
And this is my L shape (the yellow one):
Note that this isn't just a graphical bug, the game treats the elements as one single square, or two respectively.
I've been looking for hours at my code to see what the problem could be, but I had no luck. Any help would be appreciated
Thank you for the replies, but after further investigation, I found out what the problem was.
The problem was with the rotate method:
public Shape rotate(){
if (this.getShape().equals(SQUARESHAPE)) return this;
Shape newShape = new Shape();
newShape.shape = this.shape;
for (int i = 0; i < 4; i++) {
newShape.setX(i, -y(i));
newShape.setY(i, x(i));
}
return newShape;
}
I added to the mouse adapter the following code to see what happens with the coordinates of the current block:
if(buttonPressed == MouseEvent.BUTTON2) {
System.out.println(Arrays.deepToString(block.getCoords()));
rotate();
}
This is the output for the SShape:
[[0, -1], [0, 0], [1, 0], [1, 1]]
[[1, 0], [0, 0], [0, 1], [-1, 1]]
[[0, 0], [0, 0], [-1, -1], [-1, -1]]
[[0, 0], [0, 0], [1, 1], [1, 1]]
[[0, 0], [0, 0], [-1, -1], [-1, -1]]
[[0, 0], [0, 0], [1, 1], [1, 1]]
[[0, 0], [0, 0], [-1, -1], [-1, -1]]
The first line contains the initial coordinates I gave for the SShape. The second line contains the modified coordinates, after the rotate() method. As you can see, X takes the -Y value and Y takes the X value. In the third line however, X takes the -Y value, but Y takes the updated X value instead of the previous one, so X = Y from the third line onwards. To solve this problem, I made an array to hold the values of X before updating it, as follows:
public Shape rotate(){
if (this.getShape().equals(SQUARESHAPE)) return this;
Shape newShape = new Shape();
newShape.shape = this.shape;
int[] oldX = {this.x(0), this.x(1), this.x(2), this.x(3)};
for (int i = 0; i < 4; i++) {
newShape.setX(i, -y(i));
newShape.setY(i, oldX[i]);
}
return newShape;
}
Remove the rotate method, add a hard-coded rotation array each shape and all 4 rotations. First index should be the rotation index (0-3)
then add a member variable to the shape base class and change rotation to something like:
public void rotate(Boolean rotateRight) {
if (rotateRight) {
rotation++;
else {
rotation--;
}
if (rotation < 0) {
rotation = 3;
}
if (rotation > 3) {
rotation = 0;
}
}
Then make some new function like
public int[][] getCurrentRotation() {
return shapeMatrix[rotation];
}
and use this int[][] (or int[] if you want to flatten the array) to draw the appropriate squares
This is my first time implementing Dijkstra's algorithm. Okay so here I have a simple 2D 9 by 9 array:
Starting point is 1 and we're trying to get to any green square. Red squares are walls or lava (whatever satisfies your imagination).
How do I implement this in Java?
Computer science is not my field hence I'm not a seasoned programmer so I might not know how to do some stack pushing, only loops and recursion :( please keep it easy as possible and bear with me!
Here's something similiar that should get you started. However, the solution presented below attempts to get to the bottom right corner. You can relax that condition to find the bottom row. You will also need to change the encoding slightly to have a unique value that represents this row.
public class MazeSolver {
final static int TRIED = 2;
final static int PATH = 3;
// #formatter:off
private static int[][] GRID = {
{ 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 },
{ 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1 },
{ 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0 },
{ 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1 },
{ 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1 },
{ 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
};
// #formatter:off
public static void main(String[] args) {
MazeSolver maze = new MazeSolver(GRID);
boolean solved = maze.solve();
System.out.println("Solved: " + solved);
System.out.println(maze.toString());
}
private int[][] grid;
private int height;
private int width;
private int[][] map;
public MazeSolver(int[][] grid) {
this.grid = grid;
this.height = grid.length;
this.width = grid[0].length;
this.map = new int[height][width];
}
public boolean solve() {
return traverse(0,0);
}
private boolean traverse(int i, int j) {
if (!isValid(i,j)) {
return false;
}
if ( isEnd(i, j) ) {
map[i][j] = PATH;
return true;
} else {
map[i][j] = TRIED;
}
// North
if (traverse(i - 1, j)) {
map[i-1][j] = PATH;
return true;
}
// East
if (traverse(i, j + 1)) {
map[i][j + 1] = PATH;
return true;
}
// South
if (traverse(i + 1, j)) {
map[i + 1][j] = PATH;
return true;
}
// West
if (traverse(i, j - 1)) {
map[i][j - 1] = PATH;
return true;
}
return false;
}
private boolean isEnd(int i, int j) {
return i == height - 1 && j == width - 1;
}
private boolean isValid(int i, int j) {
if (inRange(i, j) && isOpen(i, j) && !isTried(i, j)) {
return true;
}
return false;
}
private boolean isOpen(int i, int j) {
return grid[i][j] == 1;
}
private boolean isTried(int i, int j) {
return map[i][j] == TRIED;
}
private boolean inRange(int i, int j) {
return inHeight(i) && inWidth(j);
}
private boolean inHeight(int i) {
return i >= 0 && i < height;
}
private boolean inWidth(int j) {
return j >= 0 && j < width;
}
public String toString() {
String s = "";
for (int[] row : map) {
s += Arrays.toString(row) + "\n";
}
return s;
}
}
I would suggest you start with writing down a method of applying dijkstras algorithm (assuming you know it in the first place) here in natural language and then start transforming it to your programming language.
The basic questions you will need to answer for that:
What are the nodes?
What are the connections?
What is the weight of each connection?
Once you did this you should be able to find a (probably not efficient) solution.
The optimal solution is indeed to use Dijkstra or AStar with a different finish condition. So you need to write if(targetNodes.contains(u)) break; instead of if(target == u) break;
(see wikipedia: If we are only interested in a shortest path between vertices source and target, we can terminate the search at line 13 if u = target.)
This is already implemented in my project called ... oh is this homework ;) ?