How to pause graphics of JPanel using Swing Timer? - java

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
}
}

Related

Clickable Checkerboard applet

currently trying to correct some errors (maybe due to my misunderstanding of some concepts) for a clickable checkerboard applet.
using an array to indicate whether a square of the checkerboard was selected (1 = selected, 0 = not selected) with mouse click listener
add a white border to the square if mouse clicked (selected), without border if clicked again, or stay with a border if I click another square
attention was given to exclude coordinate (0, 0), as I don't want any square to be selected at the beginning, which was the case if I include (0, 0)
Errors:
- the array values changed but the repaint does not behaved as intended, border/ without border
- if I select a square next to another selected square, the selection of the other square disappears
Advices on how I can move forward with this would be much appreciated.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class ClickableCheckerboard extends Checkerboard implements MouseListener {
#Override
public void init() {
initSelectionRecord();
addMouseListener(this);
}
private static int
selectedRow, selectedCol, // row and col of checkerboard
x, y; // coordinates of a mouse click
// Selection state of each square of the Checkerboard board.
private static int selectionRecord[][]; // 8 x 8 array (0 to 7, row x col), 0 = not selected, 1 = selected
// Initialise selection state of each square of the Checkerboard board.
public void initSelectionRecord() {
int row, col;
selectionRecord = new int[8][8]; // Allocate memory for the array, otherwise end up with NullPointerException
for (row = 0; row < 8; row++) {
for (col = 0; col < 8; col++) {
selectionRecord[row][col] = 0;
}
}
}
#Override
public void mouseClicked(MouseEvent e) {
x = e.getX();
y = e.getY();
selectedRow = getRow(y);
selectedCol = getCol(x);
if (selectedRow != -1 && selectedCol != -1) {
repaint(selectedCol * 20, selectedRow * 20, 19, 19);
repaint(0, 161, 300, 300);
}
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
/**
* Returns the column of the checkerboard for an integer x coordinate.
*
* #param x
* #return col if inside checkerboard, or -1 if outside checkerboard
*/
public int getCol(int x) {
int col = -1;
if (x <= 160 && x != 0) {
col = (int) (x / 20);
}
return col;
}
/**
* Returns the row of the checkerboard for an integer y coordinate.
*
* #param y
* #return row if inside checkerboard, or -1 if outside checkerboard
*/
public int getRow(int y) {
int row = -1;
if (y <= 160 && y != 0) {
row = (int) (y / 20);
}
return row;
}
/**
* Returns the selection state of a square of the checkerboard
*
* #param row
* #param col
* #return i - 1 if selected, 0 if unselected, -1 if not applicable
*/
public static int getSelectionRecord(int row, int col) {
int i = -1;
if (row < 8 && col < 8){
i = selectionRecord[row][col];
}
return i;
}
/**
* Select a square by adding a white border to it, or unselect it by clearing the border
*
* #param row
* #param col
* #param g
*/
public void toggleSelection(int row, int col, Graphics g) {
int evenRow, evenCol, width, height;
evenRow = row % 2;
evenCol = col % 2;
width = 19;
height = 19;
if (getSelectionRecord(row, col) == 0) {
selectionRecord[row][col] = 1;
g.setColor(Color.white);
g.drawRect(col * 20 , row * 20 , width, height);
} else if (getSelectionRecord(row, col) == 1) {
selectionRecord[row][col] = 0;
if (evenRow == 0 && evenCol == 0) {
g.setColor(Color.red);
g.fillRect(col * 20, row * 20, width, height);
} else if (evenRow != 0 && evenCol != 0) {
g.setColor(Color.red);
g.fillRect(col * 20, row * 20, width, height);
} else if (evenRow == 0 && evenCol != 0) {
g.setColor(Color.black);
g.fillRect(col * 20, row * 20, width, height);
} else if (evenRow != 0 && evenCol == 0) {
g.setColor(Color.black);
g.fillRect(col * 20, row * 20, width, height);
}
} else if (getSelectionRecord(row, col) == -1) {
}
}
public void paint(Graphics g) {
super.paint(g);
g.drawString("x is " + x + ". y is " + y + ".", 2, 180);
if (x != 0 && y != 0 && x <=160 && y <= 160) {
if (selectedRow != -1 && selectedCol != -1) {
toggleSelection(selectedRow, selectedCol, g);
}
}
g.drawString("Selection state of toggled square is " + getSelectionRecord(selectedRow, selectedCol), 2, 200);
int i, j;
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
g.drawString(getSelectionRecord(i, j) + " ", (2 + j * 20), (220 + i * 20));
}
}
}
}

NullPointer Exception in Java tetris game

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.

Java Tetris rotation bug

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

Java Tetris - Confused on grid[row][col] and coordinates (x, y)

I'm making Tetris in java for fun... I pretty much had everything working... but later found out that when I wanted to change the dimensions so it was square ([10 row][10 col] matrix, but instead a [12 row][10 col] matrix), that I started getting Index Out of Bound exceptions... see here: Java Tetris - weird row clearing issue
So I tried fixing everything so that the rows and columns weren't flip flopped... But am now getting hung up on the fact that the grid takes [row][col], but I’m moving around the tiles as (x, y) coordinates…
What’s confusing me is that row = y and col = x… which is reversed… so when I pass in coordinates I’m not sure when to swap them.
I know it’s a simple thing, but it’s confusing the hell out of me and I keep getting out of bounds exceptions whenever I think I have it right.
I'm not sure where the exact issue is, so I'm posting a full Sscce of my program... I think the issue is in the Board class...
Here, the block should still be able to move down... but if it tries to go down further than this...
I get:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 10
at Board.getTileAt(Board.java:177)
at Tile.collision(Tile.java:31)
at Piece.isCollision(Piece.java:172)
at Board.collisionCheck(Board.java:192)
at Piece.movePieceCheck(Piece.java:87)
at Board.keyPressed(Board.java:160)
Sscce:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MainSscce extends JPanel {
static MainSscce runMe;
BoardSscce gameBoard, scoreBoard;
public MainSscce() { //creates a new frame window and sets properties
JFrame f = new JFrame("Tetris");
//width (height), length, tilesize
gameBoard = new BoardSscce(12, 10, 35);
// scoreBoard = new BoardSscce(10, 10, 35);
f.add(gameBoard);
f.setSize(gameBoard.getWidth(), gameBoard.getHeight());
f.setVisible(true);
f.setResizable(false);
f.setVisible(true);
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
//set j frame location to appear in middle of screen
f.setLocation( (screensize.width - f.getWidth())/2,
(screensize.height - f.getHeight())/2-100 );
}
public static void main(String[] args) {
runMe = new MainSscce();
}
}
import java.awt.Graphics;
import java.awt.Color;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.event.*; // for ActionListener and ActionEvent
import java.util.Random;
public class BoardSscce extends JPanel implements KeyListener {
private TileSscce grid[][];
private int totalRows, totalCols, tilesize, level, totalScore;
private final int changeLevelMultiplier;
private PieceSscce newPiece, nextPiece;
private String randomPiece;
private boolean gameLost;
public BoardSscce(int r, int c, int ts) {
totalRows = r;
totalCols = c;
tilesize = ts;
//set grid size to [# rows][# columns], aka [height][width]
grid = new TileSscce[totalRows][totalCols];
gameLost = false;
System.out.println("TotalRows: " + totalRows + ", " + "TotalCols: " + totalCols);
//multiplier to determine what score the level changes, which is:
//level * changeLevelMultiplier;
changeLevelMultiplier = 40;
//initialize score to 0
totalScore = 0;
//initialize level to 0
level = 0;
newPiece = new PieceSscce(this, randomPiece(), getColor());
addKeyListener(this);
setFocusable(true);
//getTranspose();
timer();
}
public String randomPiece() {
String[] Pieces = {"L", "O", "Z", "RevZ", "Bar", "T", "RevL"};
int rand = (int) (Math.random() * Pieces.length);
randomPiece = Pieces[rand];
return randomPiece;
}
public Color getColor() {
Color color;
if (randomPiece.equals("L"))
color = new Color(17, 255, 0);
else if(randomPiece.equals("O"))
color = new Color(117, 168, 255);
else if(randomPiece.equals("Z"))
color = new Color(255, 187, 82);
else if(randomPiece.equals("RevZ"))
color = new Color(206, 27, 72);
else if(randomPiece.equals("Bar"))
color = new Color(50, 216, 219);
else if(randomPiece.equals("T"))
color = new Color(252, 148, 240);
else
color = new Color(255, 255, 52);
//Random rand = new Random();
//float r = rand.nextFloat();
//float g = rand.nextFloat();
//float b = rand.nextFloat();
//Color randomColor = new Color(r, g, b);
return color;
}
//dimensions of board = width * tilesize
public int getWidth() {
return totalCols * tilesize;
}
public int getHeight() {
return totalRows * tilesize;
}
public int getTileSize() {
return tilesize;
}
public void paintComponent(Graphics g) {
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
for(int row = 0; row < grid.length; row++) {
for(int col = 0; col < grid[row].length; col++) {
//System.out.println(row + ", " + col);
g.drawString("[" + row + "][" + col + "]", col * tilesize, row * tilesize+10);
System.out.println(row + ", " + col);
//if there is a non-null space, that is a Tetris piece... fill it
if(grid[row][col] != null) {
g.setColor(grid[row][col].getColor());
g.fillRect(row * tilesize, col * tilesize, tilesize, tilesize);
g.setColor(Color.WHITE);
}
}
}
// g.drawString("Level: " + level, this.getWidth()/2, this.getHeight()/2-130);
// g.drawString("Score: " + totalScore, this.getWidth()/2, this.getHeight()/2-100);
if (gameLost == true) {
g.drawString("Way to go, loser...", this.getWidth()/2, this.getHeight()/2);
messageTimer();
}
}
//Auto move piece
public void timer () {
int interval;
switch (level) {
//each level increases drop speed by .10 seconds
case 1: interval = 800;
break;
case 2: interval = 700;
break;
case 3: interval = 600;
break;
case 4: interval = 500;
break;
default: interval = 1000;
break;
}
Timer t = new Timer(interval, new ActionListener() {
public void actionPerformed(ActionEvent e) {
//newPiece.autoMove();
//repaint();
}
});
t.start();
}
public void messageTimer() {
Timer t = new Timer(5000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
gameLost = false;
}
});
t.start();
}
//move piece on key input
public void keyPressed(KeyEvent e) {
newPiece.movePieceCheck(e.getKeyCode());
repaint();
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
public boolean isValidCoordinate(int x, int y) {
return x >= 0 && y >= 0 && x < totalCols && y < totalRows;
}
// returns the tile at (x, y) or null if empty
public Tile getTileAt(int x, int y) {
if(isValidCoordinate(x, y))
return grid[x][y];
return null;
}
// sets the tile at (x, y) to tile
public void setTileAt(Tile tile, int x, int y) {
if(isValidCoordinate(x, y))
grid[x][y] = tile;
}
public boolean isOpen(int x, int y) {
return isValidCoordinate(x, y) && (getTileAt(x, y) == null);
}
public void collisionCheck() {
if (newPiece.isCollision()){
newPiece = new PieceSscce(this, randomPiece(), getColor());
}
}
public void changeLevel () {
int max = (level+1)*changeLevelMultiplier;
if (totalScore >= max) {
System.out.println(max + "reached... next level");
level++;
totalScore = 0;
timer();
}
}
public int tallyScore(int totalLines) {
int score = 0;
switch (totalLines) {
case 1: score = 40 * (level + 1);
break;
case 2: score = 100 * (level + 1);
break;
case 3: score = 300 * (level + 1);
break;
case 4: score = 1200 * (level + 1);
break;
default: break;
}
return score;
}
//loop through all rows starting at bottom (12 rows)
public void checkBottomFull() {
int lines = 0;
for(int row = 12; row > 0; row--) {
/* while (isFull(row)) {
lines++;
// clearRow(row);
}*/
}
totalScore += tallyScore(lines);
//check if level needs to be changed based on current score...
changeLevel();
//reset lines after score has been incremented
lines=0;
}
//loop through all columns in that row (10 columns)
public boolean isFull(int row) {
for (int col = 0; col <= 10; col++) {
System.out.println(row + ", " + col);
if(grid[row][col] == null) {
return false;
}
}
return true;
}
public void clearRow(int rowToClear) {
for(int row = rowToClear; row > 0; row--) {
for(int col = 0; col < grid[row].length; col++) {
grid[col][row] = grid[col][row-1];
}
}
}
public void checkEndGame(int x, int y) {
//if currPiece y location = 0 AND the space below is filled...
if (y <= 2 && !isOpen(x, y+1)) {
gameLost = true;
level = 0;
totalScore = 0;
//reset timer
timer();
for(int row = 0; row < grid.length; row++) {
for(int col = 0; col < grid[row].length; col++) {
grid[row][col] = null;
}
}
}
}
}
import java.awt.Color;
import java.awt.event.KeyEvent;
public class PieceSscce {
public int[] pieceCoordinates;
public String shape, currRotation;
public Color color;
public BoardSscce board;
public int rotationsCounter;
public TileSscce tile[];
public int[] newPositionX, newPositionY, currPositionX, currPositionY;
//don't need to pass in board because I'm already utilizing the Tiles class, which knows about the board
public Piece(Board b, String randomPiece, Color randomColor) {
shape = randomPiece;
color = randomColor;
board = b;
newPositionX = new int[4];
newPositionY = new int[4];
currPositionX = new int[4];
currPositionY = new int[4];
pieceCoordinates = new int[8];
//set pieceCoordinates global variable
getShape(shape);
tile = new TileSscce[4];
int counterX = 0, counterY = 1;
System.out.print("\"" + shape + "\" Coordinates: ");
//generate 4 new Tiles at specified coordinates that will compose the Piece
for (int i = 0; i < tile.length; i++) {
tile[i] = new TileSscce(board, pieceCoordinates[counterX], pieceCoordinates[counterY]);
System.out.print("(" + pieceCoordinates[counterX] + ", " + pieceCoordinates[counterY] + ") ");
//increment by 2 because x,y values are next to each other in array
counterX+=2;
counterY+=2;
}
System.out.println("\n");
for (int i = 0; i < tile.length; i++) {
tile[i].setColor(color);
}
}
public void calcNewPosition(int newX, int newY, int currTile) {
newPositionX[currTile] = newX;
newPositionY[currTile] = newY;
}
public void clearCurrPosition() {
for (int i = 0; i < tile.length; i++) {
currPositionX[i] = tile[i].getX();
currPositionY[i] = tile[i].getY();
board.setTileAt(null, currPositionX[i], currPositionY[i]);
}
}
public void autoMove() {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getX(), tile[i].getY()+1, i);
}
clearCurrPosition();
for (int i = 0; i < tile.length; i++) {
board.checkEndGame(tile[i].getX(), tile[i].getY());
System.out.println("Checking..." + tile[i].getX() + ", " + tile[i].getY());
}
board.checkBottomFull();
board.collisionCheck();
move();
}
public void movePieceCheck(int keycode) {
if (keycode == KeyEvent.VK_DOWN) {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getX(), tile[i].getY()+1, i);
}
clearCurrPosition();
for (int i = 0; i < tile.length; i++) {
board.checkEndGame(tile[i].getX(), tile[i].getY());
System.out.println("Checking..." + tile[i].getX() + ", " + tile[i].getY());
}
board.checkBottomFull();
board.collisionCheck();
move();
}
if (keycode == KeyEvent.VK_RIGHT) {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getX()+1, tile[i].getY(), i);
}
clearCurrPosition();
move();
}
if (keycode == KeyEvent.VK_LEFT) {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getX()-1, tile[i].getY(), i);
}
clearCurrPosition();
move();
}
//rotate left
if (keycode == KeyEvent.VK_A) {
int[] rotatedCoords = calcRotation("left");
clearCurrPosition();
rotate(rotatedCoords, "left");
}
//rotate right
if (keycode == KeyEvent.VK_D) {
int[] rotatedCoords = calcRotation("right");
clearCurrPosition();
rotate(rotatedCoords, "right");
}
}
public boolean movePieceValid() {
boolean valid = true;
for (int i = 0; i < tile.length; i++) {
if(!tile[i].checkNewLocation(newPositionX[i], newPositionY[i]))
valid = false;
}
return valid;
}
public boolean validRotation(int[] rotatedCoordinates) {
boolean valid = true;
int counterX = 0, counterY = 1;
for (int i = 0; i < tile.length; i++) {
if(!tile[i].checkNewLocation(rotatedCoordinates[counterX], rotatedCoordinates[counterY]))
valid = false;
counterX +=2;
counterY +=2;
}
return valid;
}
public void move() {
if (movePieceValid()) {
for (int i = 0; i < tile.length; i++) {
tile[i].setLocation(newPositionX[i], newPositionY[i]);
}
} else {
for (int i = 0; i < tile.length; i++) {
tile[i].setLocation(currPositionX[i], currPositionY[i]);
}
}
}
public void rotate(int[] rotatedCoordinates, String rotation) {
int counterX = 0, counterY = 1;
if (validRotation(rotatedCoordinates)) {
for (int i = 0; i < tile.length; i++) {
tile[i].setLocation(rotatedCoordinates[counterX], rotatedCoordinates[counterY]);
counterX+=2;
counterY+=2;
}
//else, if not valid move set the original location
} else {
for (int i = 0; i < tile.length; i++) {
tile[i].setLocation(currPositionX[i], currPositionY[i]);
}
}
}
public boolean isCollision() {
boolean collision = false;
for (int i = 0; i < tile.length; i++) {
if(tile[i].collision(newPositionX[i], newPositionY[i])) {
collision = true;
}
}
return collision;
}
//calc curr coordinates, send them to getRotation... which will create new piece based on coords
public int[] calcRotation(String direction) {
for (int i = 0; i < tile.length; i++) {
currPositionX[i] = tile[i].getX();
currPositionY[i] = tile[i].getY();
System.out.println("Current position: (" + currPositionX[i] + "," + currPositionY[i]+")");
}
return getRotation(currPositionX, currPositionY, direction);
}
public int[] getRotation (int coordinatesX[], int coordinatesY[], String direction) {
int[] rotationDirection;
int[] coordinates = new int[8];
int[] origin = new int[2];
int[] newCoordinates = new int[8];
int[] resultCoordinates = new int[8];
int[] finalCoordinates = new int[8];
int vectorMatrix[][] = new int[2][4];
//set either R(90) or R(-90) rotation matrix values:
if (direction.equals("right")) {
rotationDirection = new int[] {0, -1, 1, 0};
}
else {
rotationDirection = new int[] {0, 1, -1, 0};
}
int counterX = 0, counterY = 1, x = 0;
while (counterY < coordinates.length) {
//add arrays coordinatesX and coordinatesY into a single array: coordinates
coordinates[counterX] = coordinatesX[x];
coordinates[counterY] = coordinatesY[x];
counterX+=2;
counterY+=2;
x++;
}
//set origin so it rotates around center...
if (shape.equals("RevZ")) {
origin[0] = coordinates[6];
origin[1] = coordinates[7];
}
else if (shape.equals("T")) {
origin[0] = coordinates[4];
origin[1] = coordinates[5];
}
else {
origin[0] = coordinates[2];
origin[1] = coordinates[3];
}
//subtract origin from vectors
System.out.println();
counterX = 0;
counterY = 1;
while (counterY < newCoordinates.length) {
//System.out.println(coordinates[counterX] + ", " + coordinates[counterY]);
newCoordinates[counterX] = coordinates[counterX] - origin[0];
newCoordinates[counterY] = coordinates[counterY] - origin[1];
System.out.println("Translated coordinates: (" + newCoordinates[counterX] + ", " + newCoordinates[counterY] + ")");
counterX+=2;
counterY+=2;
}
System.out.println();
System.out.println("vector matrix:");
//fill up vectorMatrix with coordinates
int k = 0;
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 2; row++) {
vectorMatrix[row][col] = newCoordinates[k++];
}
}
//print vectorMatrix:
for (int i = 0; i < vectorMatrix.length; i++) {
System.out.print("[");
for (int j = 0; j < vectorMatrix[i].length; j++) {
System.out.print(vectorMatrix[i][j]);
}
System.out.println("]");
}
int rotationMatrix[][] = new int[2][2];
//fill up rotationMatrix
System.out.println();
System.out.println("multiplicative matrix:");
k = 0;
for (int row = 0; row < 2; row++) {
System.out.print("[");
for (int col = 0; col < 2; col++) {
rotationMatrix[row][col] = rotationDirection[k++];
System.out.print(rotationMatrix[row][col]);
}
System.out.println("]");
}
//perform matrix multiplication
int[][] result = multiplyMatrices(rotationMatrix, vectorMatrix);
//print resulting matrix
System.out.println();
System.out.println("result matrix:");
for (int i = 0; i < result.length; i++) {
System.out.print("[");
for (int j = 0; j < result[i].length; j++) {
System.out.print(result[i][j]);
}
System.out.println("]");
}
//load new matrix coordinates back into array
k = 0;
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 2; row++) {
resultCoordinates[k] = result[row][col];
k++;
}
}
System.out.println();
System.out.println("result coordinates:");
counterX = 0;
counterY = 1;
while (counterY < resultCoordinates.length) {
finalCoordinates[counterX] = resultCoordinates[counterX] + origin[0];
finalCoordinates[counterY] = resultCoordinates[counterY] + origin[1];
System.out.print("("+finalCoordinates[counterX] + ", " + finalCoordinates[counterY]+")");
counterX+=2;
counterY+=2;
}
return finalCoordinates;
}
public int[][] multiplyMatrices(int rotationMatrix[][], int vectorMatrix[][]) {
int mA = rotationMatrix.length;
int nA = rotationMatrix[0].length;
int mB = vectorMatrix.length;
int nB = vectorMatrix[0].length;
if (nA != mB) throw new RuntimeException("Illegal matrix dimensions.");
int[][] C = new int[mA][nB];
for (int i = 0; i < mA; i++) {
for (int j = 0; j < nB; j++) {
for (int k = 0; k < nA; k++) {
C[i][j] += (rotationMatrix[i][k] * vectorMatrix[k][j]);
}
}
}
return C;
}
public int[] getShape(String shape) {
if (shape.equals("L")) {
//pieceCoordinates = new int[] {0, 1, 0, 2, 1, 2, 2, 2};
pieceCoordinates = new int[] {4, 0, 4, 1, 5, 1, 6, 1};
}
else if (shape.equals("O")) {
pieceCoordinates = new int[] {0, 1, 1, 1, 0, 2, 1, 2};
}
else if (shape.equals("Z")) {
pieceCoordinates = new int[] {0, 1, 1, 1, 1, 2, 2, 2};
}
else if (shape.equals("RevZ")) {
pieceCoordinates = new int[] {1, 1, 2, 1, 0, 2, 1, 2};
}
else if (shape.equals("Bar")) {
//pieceCoordinates = new int[] {0, 1, 1, 1, 2, 1, 3, 1};
pieceCoordinates = new int[] {0, 1, 1, 1, 2, 1, 3, 1};
}
else if (shape.equals("T")) {
pieceCoordinates = new int[] {1, 1, 0, 2, 1, 2, 2, 2};
}
else if (shape.equals("RevL")) {
pieceCoordinates = new int[] {0, 2, 1, 2, 2, 2, 2, 1};
}
return pieceCoordinates;
}
}
import java.awt.Color;
import java.util.Random;
public class TileSscce {
private BoardSscce board;
private int currX, currY;
private Color color;
public TileSscce(BoardSscce b, int x, int y) {
board = b;
//when Tile is instantiated, set its position
setLocation(x, y);
}
public int getX() {
return currX;
}
public int getY() {
return currY;
}
public boolean checkNewLocation(int newX, int newY) {
boolean newLocationOK = board.isOpen(newX, newY);
return newLocationOK;
}
public boolean collision(int newX, int newY) {
boolean collision = this.getY() == ((board.getHeight()/board.getTileSize()))-2 || board.getTileAt(newX, newY) != null;
return collision;
}
public void setLocation(int newX, int newY) {
// board.setTileAt(null, currX, currY);
currX = newX;
currY = newY;
board.setTileAt(this, currX, currY);
}
public Color getColor() {
return setColor(color);
}
public Color setColor(Color myColor) {
color = myColor;
return color;
}
}
Thanks!
EDIT----------
I've tried implementing both ValarDohaeris and Svend Hansen's suggestions... Now the block is moving right when I press down, up when I press left, and down when I press right...
It seems to have to do with these methods in Board class which get and set tile locations...
// returns the tile at (x, y) or null if empty
public Tile getTileAt(int row, int col) {
System.out.println("getTileAt: " + row + ", " + col);
if(isValidCoordinate(row, col))
return grid[row][col];
return null;
}
// sets the tile at (x, y) to tile
public void setTileAt(Tile tile, int row, int col) {
System.out.println("setTileAt: " + row + ", " + col);
if(isValidCoordinate(row, col))
grid[row][col] = tile;
}
And in Piece class... movements are defined as:
public void movePieceCheck(int keycode) {
if (keycode == KeyEvent.VK_DOWN) {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getRow()+1, tile[i].getCol(), i);
}
clearCurrPosition();
for (int i = 0; i < tile.length; i++) {
board.checkEndGame(tile[i].getRow(), tile[i].getCol());
}
board.checkBottomFull();
if (isCollision()) board.createNewPiece();
move();
}
if (keycode == KeyEvent.VK_RIGHT) {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getRow(), tile[i].getCol()+1, i);
}
clearCurrPosition();
move();
}
if (keycode == KeyEvent.VK_LEFT) {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getRow(), tile[i].getCol()-1, i);
}
clearCurrPosition();
move();
}
You have
grid = new TileSscce[totalRows][totalCols];
So when you want to access grid[x][y], you should check
x >= 0 && y >= 0 && x < totalRows && y < totalCols
in isValidCoordinate(x, y).
Emm... Quite interesting question. So to find out where the problem(s) may be I'll try to analyze your code a little bit...
You paste stack trace as
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 10
at Board.getTileAt(Board.java:177)
...
and at the same time the getTileAt()
// returns the tile at (x, y) or null if empty
public Tile getTileAt(int row, int col) {
System.out.println("getTileAt: " + row + ", " + col);
if(isValidCoordinate(row, col))//isValidCoordinate()?
return grid[row][col];
return null;
}
public boolean isValidCoordinate(int x, int y) {
return x >= 0 && y >= 0 && x < totalCols && y < totalRows;
}
... so the isValidCoordinate method return terms as
x >= 0 && y >= 0 && x < totalCols && y < totalRows
...the method doesn't allow to avoid array out-of-bounds problems; Seems like you put wrong array element indexes.
A. As I can notice, you trying to put a classic math matrix on Java [][] arrays as
public void clearRow(int rowToClear) {
for(int row = rowToClear; row > 0; row--) {
for(int col = 0; col < grid[row].length; col++) {//<-- ?
grid[col][row] = grid[col][row-1];
}
}
}
... and here I must say that you should know that in [][] arrays x,y are backwards and it is y,x because :
y (or classic i) - sub-array index (vertical)
x (or classic j) - sub-array's element index (horizontal)
so you should use array index something this way grid[y][x] or grid[i][j]
As a useful tip, I recommend you to analyze your code for logic errors in this field...
B. According to your app screenshot as
... it seems like the x,y problem takes place here too because you trying to control y (vertical) coordinates but (in real) you control x (horizontal) coordinates only :S It is still because of the row,col instead of a classic Java (col,row or y,x) [][] array index positions.
C. And again concerning to the wrong directions...
...up when I press left, and down when I press right...
if (keycode == KeyEvent.VK_RIGHT) {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getRow(), tile[i].getCol()+1, i);
}
clearCurrPosition();
move();
}
Here I'll try to analyze the event as (you press right but move down)...
OK... according to one of your tasks you need to move by x coordinate (horizontally) but look closer... you make tile[i].getCol()+1 so it is newY and, of course, it moves vertically :S In your case it really moves down because you make increment as y++ ...
public void calcNewPosition(int newX, int newY, int currTile) {
newPositionX[currTile] = newX;
newPositionY[currTile] = newY;
}
public void clearCurrPosition() {
for (int i = 0; i < tile.length; i++) {
currPositionX[i] = tile[i].getX();
currPositionY[i] = tile[i].getY();
board.setTileAt(null, currPositionX[i], currPositionY[i]);
}
}
public void move() {
if (movePieceValid()) {
for (int i = 0; i < tile.length; i++) {
tile[i].setLocation(newPositionX[i], newPositionY[i]);//<-- !
}
} else {
for (int i = 0; i < tile.length; i++) {
tile[i].setLocation(currPositionX[i], currPositionY[i]);
}
}
}
...as a conclusion, I may recommend to change code (move right) something this way...
if (keycode == KeyEvent.VK_RIGHT) {
for (int i = 0; i < tile.length; i++) {
calcNewPosition(tile[i].getRow()+1, tile[i].getCol(), i);
}
clearCurrPosition();
move();
}
I hope my tips will help you to figure out what to look closer. Anyway, if you have some additional information please do comment my answer
Report if that helped you
This is based on x corresponds to columns and y corresponds to rows.
However grid is indexed by [row][col].
TileSscce grid[][] = new TileSscce[totalRows][totalCols]; // 12 => totalRows, 10 => totalCols
public int getWidth() {
return totalCols * tilesize;
}
public int getHeight() {
return totalRows * tilesize;
}
Following changes (based on your initial code - Sscce: - without later edits) will get rid of the exception and allow drawing till bottom of the board.
public void paintComponent(Graphics g) {
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length; col++) {
if (grid[row][col] != null) {
g.setColor(grid[row][col].getColor());
g.fillRect(col * tilesize, row * tilesize, tilesize, tilesize); // changed, check below snippet from fillRect documentation
g.setColor(Color.WHITE);
}
}
}
}
public TileSscce getTileAt(int x, int y) {
if (isValidCoordinate(x, y))
return grid[y][x]; // changed to [y][x] as grid is indexed by [row][col]
return null;
}
public void setTileAt(TileSscce tile, int x, int y) {
if (isValidCoordinate(x, y))
grid[y][x] = tile; // changed to [y][x] as grid is indexed by [row][col]
}
From fillRect documentation.
public abstract void fillRect(int x, int y, int width, int height)
The left and right edges of the rectangle are at x and x + width - 1.
The top and bottom edges are at y and y + height - 1.
This is correct.
public boolean isValidCoordinate(int x, int y) {
return x >= 0 && y >= 0 && x < totalCols && y < totalRows;
}

Java for-loop problem

The problem is that I am trying to make certain tiles blocked so the player cannot walk on them. However, it's only reading the FIRST tile which is board[0][0] and everything else is not checked....
What am I doing wrong? :(
Here's my code:
import java.applet.*;
import java.applet.Applet;
import java.awt.*;
import java.awt.Canvas.*;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.*;
public class gen extends Applet implements KeyListener {
Image[] tiles;
Image player;
int x;
int y;
int px;
int py;
boolean left;
boolean right;
boolean down;
boolean up;
int[][] board;
final int NUM_TILES = 5;
public final int BLOCKED = 1;
public void init() {
// Load board
board = loadBoard();
tiles = new Image[NUM_TILES];
for(int i = 0;i < NUM_TILES;i++) {
tiles[i] = getImage(getClass().getResource(String.format("tile%d.png", i)));
}
for (int y=0;y< NUM_TILES;y++) {
board[1][1] = BLOCKED;
board[1][2] = BLOCKED;
board[1][3] = BLOCKED;
}
player = getImage(getClass().getResource("player.png")); // our player
addKeyListener(this);
px = 0;
py = 0;
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
left = true;
px = px - 32;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
right = true;
px = px + 32;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
down = true;
py = py + 32;
}
if (e.getKeyCode() == KeyEvent.VK_UP) {
up = true;
py = py - 32;
}
repaint();
}
public void keyReleased(KeyEvent e) {
} // ignore
public void keyTyped(KeyEvent e) {
} // ignore
public void paint(Graphics g) {
x = 0;
y = 0;
// here's a sample map!
// but we're loading from a text file!
// so... why is this needed?
for (int row = 0; row < board.length; row++) {
for (int col = 0; col < board[row].length; col++) {
int index = board[row][col];
g.drawImage(tiles[index], 32 * col, 32 * row, this);
if (board[row][col] == BLOCKED) {
System.out.println("\n"+board[row][col] + "is BLOCKED!\n");
}else{System.out.println("\n"+board[row][col] + "is NOT Blocked!\n");}
}
}
g.drawImage(player, px, py, this);
try {
System.out.println("ON BLOCKED TILE?: " + blocked(px,py) + "\n");
}catch(ArrayIndexOutOfBoundsException e) {}
} // end paint method
public void update(Graphics g) {
paint(g);
}
private int[][] loadBoard() {
int[][] board = {
{ 0, 1, 2, 3, 4, 4, 3, 4 },
{ 0, 1, 2, 3, 4, 4, 3, 4 },
{ 2, 2, 4, 2, 2, 1, 1, 0 },
{ 0, 1, 2, 3, 4, 4, 3, 4 },
{ 0, 0, 0, 2, 3, 4, 4, 2 },
{ 2, 2, 4, 2, 2, 1, 1, 0 },
{ 0, 1, 2, 3, 4, 4, 3, 4 },
{ 0, 0, 0, 2, 3, 4, 4, 2 }
};
return board;
}
public boolean blocked(int tx, int ty) {
return board[tx][ty] == BLOCKED;
}
} // end whole thing
One of the problems in your code is on this line you are calling blocked with px, py which are multiples of 32:
blocked(px, py)
But you use these number as indexes into your array which would cause an ArrayIndexOutOfBoundsException:
public boolean blocked(int tx, int ty)
{
return board[tx][ty] == BLOCKED;
}
Which you've tried to "fix" by ignoring it:
try
{
System.out.println("ON BLOCKED TILE?: " + blocked(px,py) + "\n");
}
catch(ArrayIndexOutOfBoundsException e) {}
So I suspect that it only works for (0,0) because (32,32) is out of bounds. There are also other errors in your code, but this should be a good start for you.

Categories

Resources