Keybind action being run at start but not from actual key press - java

I'm coding a game for my final project in Java, our teacher provided us with a Board class that is a component that allows us to place and remove pegs on a virtual game board instead of having to code one ourselves. I'm trying to add Key Binding to the Board component but the action I want performed on key press is happening when I run the program but It won't run when I type a Key.
The board class already has a method for getting the position clicked on the component and I think this might be interfering with my Code but I'm not sure.
This is my game class where I tried to add keybinding
package rpgGame;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
public class RPGGame
{
public static final GameWorld WORLD_MAP = new GameWorld();
public static Board LOCAL_MAP = new Board(20,50);
public static List<Mobile> allMobs = new ArrayList<Mobile>();
public static final Player PLAYER = new Player();
public static int xIndex = ((GameWorld.WORLD_SIZE-1)/2) - (50/2);
public static int yIndex = ((GameWorld.WORLD_SIZE-1)/2) - (20/2);
public static boolean boardUpdate = true;
public enum Direction {RIGHT,LEFT,UP,DOWN}
private static final String MOVE_PLAYER_UP = "move up";
private static final String MOVE_PLAYER_LEFT = "move left";
private static final String MOVE_PLAYER_RIGHT = "move right";
private static final String MOVE_PLAYER_DOWN = "move down";
public static final Thread SYNC_BOARD = new Thread()
{
public synchronized void run()
{
while (boardUpdate)
{
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 20; j++)
{
if (WORLD_MAP.isOccupied(i+xIndex, j+yIndex))
{
LOCAL_MAP.putPeg(Color.RED, j, i);
System.out.println("Successfully Updated");
}
else
{
LOCAL_MAP.putPeg(Color.GRAY, j,i);
}
}
}
boardUpdate = false;
}
}
};
public RPGGame()
{
generateMobs(200);
placeMobs();
placePlayer();
SYNC_BOARD.run();
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), MOVE_PLAYER_UP);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), MOVE_PLAYER_UP);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), MOVE_PLAYER_LEFT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), MOVE_PLAYER_LEFT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), MOVE_PLAYER_RIGHT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0), MOVE_PLAYER_RIGHT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), MOVE_PLAYER_DOWN);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0), MOVE_PLAYER_DOWN);
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_UP, new MoveAction(Direction.UP));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_LEFT, new MoveAction(Direction.LEFT));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_RIGHT, new MoveAction(Direction.RIGHT));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_DOWN, new MoveAction(Direction.DOWN));
}
public static void main(String[] args)
{
new RPGGame();
}
public static void generateMobs(int numOfMobs)
{
for (int i=0; i<numOfMobs; i++)
{
allMobs.add(new Mobile());
}
}
public static void generateMobs()
{
int numOfMobs = (int)(Math.random()*500);
for (int i=0;i<numOfMobs; i++)
{
allMobs.add(new Mobile());
}
}
public static void placeMobs()
{
for (int i=0; i<allMobs.size(); i++)
{
//i is used as a placeholder value for points until I create a random number generator.
WORLD_MAP.placeCharacter(i, i,allMobs.get(i));
allMobs.get(i).setLocation(i, i);
}
}
public static void placePlayer()
{
WORLD_MAP.placeCharacter(249, 249, PLAYER);
PLAYER.setLocation(249, 249);
}
#SuppressWarnings("serial")
public class MoveAction extends AbstractAction
{
Direction direction;
public MoveAction(Direction direction)
{
if (direction.equals(Direction.RIGHT))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x+1, y, x, y);
PLAYER.move(1, 0);
boardUpdate = true;
System.out.println("MOVE RIGHT");
}
if (direction.equals(Direction.LEFT))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x-1, y);
PLAYER.move(-1, 0);
boardUpdate = true;
System.out.println("MOVE LEFT");
}
if (direction.equals(Direction.UP))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x, y+1);
PLAYER.move(0, 1);
boardUpdate = true;
System.out.println("MOVE UP");
}
if (direction.equals(Direction.DOWN))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x, y-1);
PLAYER.move(0, -1);
boardUpdate = true;
System.out.println("MOVE DOWN");
}
}
#Override
public void actionPerformed(ActionEvent e)
{
}
}
}
This is the Board class
package rpgGame;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
/** Board GUI for implementation with various games
* Author: Kirill Levin, Troy Vasiga, Chris Ingram
*/
#SuppressWarnings("serial")
public class Board extends JPanel
{
private static final int X_DIM = 60;
private static final int Y_DIM = 60;
private static final int X_OFFSET = 30;
private static final int Y_OFFSET = 30;
private static final double MIN_SCALE = 0.25;
private static final int GAP = 10;
private static final int FONT_SIZE = 16;
// Grid colours
private static final Color GRID_COLOR_A = new Color(153,255,102);
private static final Color GRID_COLOR_B = new Color(136,255,77);
private Color[][] grid;
private Point lastClick; // How the mouse handling thread communicates
// to the board where the last click occurred
private String message = "";
private int numLines = 0;
private double[][] line = new double[4][100]; // maximum number of lines is 100
private int columns, rows;
private int originalWidth;
private int originalHeight;
private double scale;
/** A constructor to build a 2D board.
*/
public Board (int rows, int columns)
{
super( true );
JFrame boardFrame = new JFrame( "Board game" );
this.columns = columns;
this.rows = rows;
originalWidth = 2*X_OFFSET+X_DIM*columns;
originalHeight = 2*Y_OFFSET+Y_DIM*rows+GAP+FONT_SIZE;
this.setPreferredSize( new Dimension( originalWidth, originalHeight ) );
boardFrame.setResizable(true);
this.grid = new Color[columns][rows];
this.addMouseListener(
new MouseInputAdapter()
{
/** A method that is called when the mouse is clicked
*/
public void mouseClicked(MouseEvent e)
{
int x = (int)e.getPoint().getX();
int y = (int)e.getPoint().getY();
// We need to by synchronized to the parent class so we can wake
// up any threads that might be waiting for us
synchronized(Board.this)
{
int curX = (int)Math.round(X_OFFSET*scale);
int curY = (int)Math.round(Y_OFFSET*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*grid.length)*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale);
// Subtract one from high end so clicks on the black edge
// don't yield a row or column outside of board because of
// the way the coordinate is calculated.
if (x >= curX && y >= curY && x < nextX && y < nextY)
{
lastClick = new Point(y,x);
// Notify any threads that would be waiting for a mouse click
Board.this.notifyAll() ;
} /* if */
} /* synchronized */
} /* mouseClicked */
} /* anonymous MouseInputAdapater */
);
boardFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boardFrame.setContentPane( this );
boardFrame.pack();
boardFrame.setVisible(true);
}
/** A constructor to build a 1D board.
*/
public Board (int cols)
{
this(1, cols);
}
private void paintText(Graphics g)
{
g.setColor( this.getBackground() );
g.setFont(new Font(g.getFont().getFontName(), Font.ITALIC+Font.BOLD, (int)(Math.round(FONT_SIZE*scale))));
int x = (int)Math.round(X_OFFSET*scale);
int y = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale + GAP ) ;
g.fillRect(x,y, this.getSize().width, (int)Math.round(GAP+FONT_SIZE*scale) );
g.setColor( Color.black );
g.drawString(message, x, y + (int)Math.round(FONT_SIZE*scale));
}
private void paintGrid(Graphics g)
{
for (int i = 0; i < this.grid.length; i++)
{
for (int j = 0; j < this.grid[i].length; j++)
{
if ((i%2 == 0 && j%2 != 0) || (i%2 != 0 && j%2 == 0))
g.setColor(GRID_COLOR_A);
else
g.setColor(GRID_COLOR_B);
int curX = (int)Math.round((X_OFFSET+X_DIM*i)*scale);
int curY = (int)Math.round((Y_OFFSET+Y_DIM*j)*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*(i+1))*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*(j+1))*scale);
int deltaX = nextX-curX;
int deltaY = nextY-curY;
g.fillRect( curX, curY, deltaX, deltaY );
Color curColour = this.grid[i][j];
if (curColour != null) // Draw pegs if they exist
{
g.setColor(curColour);
g.fillOval(curX+deltaX/4, curY+deltaY/4, deltaX/2, deltaY/2);
}
}
}
((Graphics2D) g).setStroke( new BasicStroke(0.5f) );
g.setColor(Color.BLACK);
int curX = (int)Math.round(X_OFFSET*scale);
int curY = (int)Math.round(Y_OFFSET*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*grid.length)*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale);
g.drawRect(curX, curY, nextX-curX, nextY-curY);
}
private void drawLine(Graphics g)
{
for (int i =0; i < numLines; i++ )
{
((Graphics2D) g).setStroke( new BasicStroke( 5.0f*(float)scale) );
g.drawLine( (int)Math.round((X_OFFSET+X_DIM/2.0+line[0][i]*X_DIM)*scale),
(int)Math.round((Y_OFFSET+Y_DIM/2.0+line[1][i]*Y_DIM)*scale),
(int)Math.round((X_OFFSET+X_DIM/2.0+line[2][i]*X_DIM)*scale),
(int)Math.round((Y_OFFSET+Y_DIM/2.0+line[3][i]*Y_DIM)*scale) );
}
}
/**
* Convert a String to the corresponding Color defaulting to Black
* with an invald input
*/
/*private Color convertColour( String theColour )
{
for( int i=0; i<COLOUR_NAMES.length; i++ )
{
if( COLOUR_NAMES[i].equalsIgnoreCase( theColour ) )
return COLOURS[i];
}
return DEFAULT_COLOUR;
}*/
/** The method that draws everything
*/
public void paintComponent( Graphics g )
{
this.setScale();
this.paintGrid(g);
this.drawLine(g);
this.paintText(g);
}
public void setScale()
{
double width = (0.0+this.getSize().width) / this.originalWidth;
double height = (0.0+this.getSize().height) / this.originalHeight;
this.scale = Math.max( Math.min(width,height), MIN_SCALE );
}
/** Sets the message to be displayed under the board
*/
public void displayMessage(String theMessage)
{
message = theMessage;
this.repaint();
}
/** This method will save the value of the colour of the peg in a specific
* spot. theColour is restricted to
* "yellow", "blue", "cyan", "green", "pink", "white", "red", "orange"
* Otherwise the colour black will be used.
*/
public void putPeg(Color colour, int row, int col)
{
this.grid[col][row] = colour;
this.repaint();
}
/** Same as putPeg above but for 1D boards
*/
public void putPeg(Color colour, int col)
{
this.putPeg(colour, 0, col );
}
/** Remove a peg from the gameboard.
*/
public void removePeg(int row, int col)
{
this.grid[col][row] = null;
repaint();
}
/** Same as removePeg above but for 1D boards
*/
public void removePeg(int col)
{
this.grid[col][0] = null;
repaint();
}
/** Draws a line on the board using the given co-ordinates as endpoints
*/
public void drawLine(double row1, double col1, double row2, double col2)
{
this.line[0][numLines]=col1;
this.line[1][numLines]=row1;
this.line[2][numLines]=col2;
this.line[3][numLines]=row2;
this.numLines++;
repaint();
}
/** Removes one line from a board given the co-ordinates as endpoints
* If there is no such line, nothing happens
* If multiple lines, all copies are removed
*/
public void removeLine(int row1, int col1, int row2, int col2)
{
int curLine = 0;
while (curLine < this.numLines)
{
// Check for either endpoint being specified first in our line table
if ( (line[0][curLine] == col1 && line[1][curLine] == row1 &&
line[2][curLine] == col2 && line[3][curLine] == row2) ||
(line[2][curLine] == col1 && line[3][curLine] == row1 &&
line[0][curLine] == col2 && line[1][curLine] == row2) )
{
// found a matching line: overwrite with the last one
numLines--;
line[0][curLine] = line[0][numLines];
line[1][curLine] = line[1][numLines];
line[2][curLine] = line[2][numLines];
line[3][curLine] = line[3][numLines];
curLine--; // perhaps the one we copied is also a match
}
curLine++;
}
repaint();
}
/** Waits for user to click somewhere and then returns the click.
*/
public Point getClick()
{
Point returnedClick = null;
synchronized(this) {
lastClick = null;
while (lastClick == null)
{
try {
this.wait();
} catch(Exception e) {
// We'll never call Thread.interrupt(), so just consider
// this an error.
e.printStackTrace();
System.exit(-1) ;
} /* try */
}
int x = (int)Math.floor((lastClick.getY()-X_OFFSET*scale)/X_DIM/scale);
int y = (int)Math.floor((lastClick.getX()-Y_OFFSET*scale)/Y_DIM/scale);
// Put this into a new object to avoid a possible race.
returnedClick = new Point(x,y);
}
return returnedClick;
}
/** Same as getClick above but for 1D boards
*/
public double getPosition()
{
return this.getClick().getY();
}
public int getColumns()
{
return this.columns;
}
public int getRows()
{
return this.rows;
}
}

You're shooting yourself in the foot with that thread code -- you're calling run() not start() on it
SYNC_BOARD.run();
This will run on the Swing event thread and risks completely freezing your GUI.
As a general rule, you should almost never extend Thread but rather implement Runnable, but regardless, don't use that Thread code -- Instead use a Swing Timer since your code has no breaks in it, no Thread.sleeps and it will make your CPU awfully busy, and the Swing Timer will help make sure that your code obeys Swing threading rules.
Also your MoveAction is wrong. Most of that code should go in the actionPerformed method. The constructor should just set the direction field and that's it.
Something like:
#SuppressWarnings("serial")
public class MoveAction extends AbstractAction {
Direction direction;
public MoveAction(Direction direction) {
// this is the only code the constructor should have!
this.direction = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
// use direction to help make move in here
}
}
Understand that this is likely causing some major problems, since the constructor is called on program creation (hence your key bindings "work" when the program starts), but it's the actionPerformed that actually gets called when the right key is pressed.

Related

Swing draws 3 JLabels instead of one

I don't really understand why this program draws three pawns instead of one and two of which seem to have a random(probably not so random) positions. enter image description here
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GamePanel extends JPanel implements MouseListener {
static final int SCREEN_EDGE = 800;
static final int GAME_UNITS = 64;
static final int UNIT_SIZE = 100;
final int[] x = new int[GAME_UNITS];
final int[] y = new int[GAME_UNITS];
boolean running = false;
public GamePanel() {
this.setPreferredSize(new Dimension(SCREEN_EDGE, SCREEN_EDGE));
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
startGame();
int[] position = {0,0};
int[] position1 = {1, 0};
new Pawn(position,-1);
}
private void startGame() {
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g){
int counter = 1;
for (int y = 0; y < SCREEN_EDGE/UNIT_SIZE; y++) {
// 1 == "white" 2 == "black"
int color = (y % 2 == 0) ? 1 : 2;
for (int x = 0; x < SCREEN_EDGE/UNIT_SIZE; x++) {
g.setColor(color == 1 ? new Color(239,217, 181) : new Color(180, 136,98));
g.fillRect(x*UNIT_SIZE, y*UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
color = color == 1 ? 2 : 1;
}
}
for (int i = 0; i < Figure.figures.length; i++) {
JLabel figureSprite = new JLabel(Figure.figures[i].image, JLabel.CENTER);
figureSprite.setSize(90,90);
figureSprite.setLocation(Figure.figures[i].position[0] + 5,Figure.figures[i].position[1] + 5);
this.add(figureSprite);
}
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
public class MyKeyAdapter extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
}
}
}
import javax.swing.*;
public abstract class Figure{
protected int value;
protected ImageIcon image;
protected int[][] possibleMoves;
public int[] position;
// white = -1 black = 1
protected int whiteOrBlack;
protected static int figureCount = 1;
// int[figureCount][0 = x][1 = y][2 = color]
public static Figure[] figures = new Figure[figureCount];
public Figure(int value, int[] position, int[][] possibleMoves , int whiteOrBlack) {
this.value = value;
this.position = position;
this.possibleMoves = possibleMoves;
this.whiteOrBlack = whiteOrBlack;
Figure[] oldFigures = figures;
figures = new Figure[figureCount];
for (int i = 0; i < oldFigures.length; i++) {
figures[i] = oldFigures[i];
}
figures[figureCount - 1] = this;
figureCount++;
}
public abstract void move(int[] coordinates);
}
import javax.swing.*;
import java.awt.*;
import java.io.*;
public class Pawn extends Figure{
public Pawn(int[] position, int whiteOrBlack) {
super(1, position, new int[3][2], whiteOrBlack);
super.image = new ImageIcon(getClass().getClassLoader().getResource("graphics/" + (whiteOrBlack == -1 ? "whitePawn.png" : "blackPawn.png")));
Image newImage = super.image.getImage().getScaledInstance(90,90, Image.SCALE_AREA_AVERAGING);
super.image = new ImageIcon(newImage);
}
public void checkMoves(){
for (int i = 0; i < figures.length; i++) {
if((position[0] - 1) == figures[i].position[0] && (position[1] + this.whiteOrBlack) == figures[i].position[1] && figures[i].whiteOrBlack != this.whiteOrBlack) {
possibleMoves[0][0] = position[0] - 1;
possibleMoves[0][1] = position[1] + this.whiteOrBlack;
}else possibleMoves[0] = new int[2];
if((position[0]) != figures[i].position[0] && (position[1]) != figures[i].position[1]){
possibleMoves[1][0] = position[0];
possibleMoves[1][1] = position[1] + 1;
}else possibleMoves[1] = new int[2];
if((position[0] + 1) == figures[i].position[0] && (position[1] + this.whiteOrBlack) == figures[i].position[1] && figures[i].whiteOrBlack != this.whiteOrBlack) {
possibleMoves[2][0] = position[0] + 1;
possibleMoves[2][1] = position[1] + this.whiteOrBlack;
}else possibleMoves[2] = new int[2];
}
}
#Override
public void move(int[] coordinates) {
}
}
import javax.swing.*;
public class GameFrame extends JFrame {
public GameFrame(){
this.add(new GamePanel());
this.setTitle("Chess");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setResizable(false);
this.setVisible(true);
this.setLocationRelativeTo(null);
}
}
public class ChessGame {
public static void main(String[] args) {
new GameFrame();
}
}
I tried few thing like changing JLabel to BufferedImage but it would've generate other problems down the line like not being able to use MouseListener so i feel stuck. I would love to know why this code generates 3 textures too.
It looks like you are adding FigureSprites from within the drawComponent() method. The more often you draw the window, the more figures you have to draw.
Instead within drawComponent() just draw the current state but do not modify it. Adding figures has to come from somewhere else. For example, you could create the necessary pawns in the constructor. Or in extra methods that might get triggered based on user input.
In case you modify the GamePanel's state enough so that it should get painted freshly, just invoke repaint(). Swing will mark this component for repainting and decide on it's own when to run the paint() method, which in turn will run paintComponent().

How do I pass an object from another class into the paintComponent method?

My assignment is to create the game "FloodIt." You can play the game here if you need to understand it, but I don't think it's really necessary: http://unixpapa.com/floodit/
I have finished the actual game part of it, but now I need to make a graphical interface for it. I have three classes:
Board.java, which makes the board with random int values and contains several other methods to make the game work:
import java.awt.Color;
import java.util.Random;
/**The board class for the Flood-It game. This class implements a NxN board filled with numColors colors.
* The class implements several methods to allow the playing of the game.
*/
class Board {
//you will probably need to create some field variables
private int size;
private int numColors;
private int[][] board;
private int numOfMoves;
/**Constructs a new sizeXsize board filled where each element on the board is a random number between 0
* and numColors. Also initializes the number of moves to zero.
* #param size -- the size of the board
* #param numColors -- the number of possible entries on the board
*/
public Board(int size,int numColors) {
//TODO finish this constructor
this.size = size;
this.numColors = numColors;
numOfMoves = 0;
board = new int[size][size];
Random rand = new Random();
int randomNum = 0;
for (int count = 0; count < size; count++) {
for (int counter = 0; counter < size; counter++) {
randomNum = rand.nextInt(this.numColors);
board[count][counter] = randomNum;
}
}
}
/**Updates the board to fill (from the top left corner) with a specified color.
* Filling stops when any other color is hit besides the one in the top left corner.
* Play the game at http://www.lemoda.net/javascript/flood-it/ or http://unixpapa.com/floodit/?sz=14&nc=4
* to get a better understanding of what this method should do.
* You will probably also want to take a look at the algorithm described
* at http://en.wikipedia.org/wiki/Flood_fill which describes what this method should do.
* I recommend the Stack-based recursive implementation. It is a recursive algorithm for
* flooding the board. It is one of the easier ones to implement.
* You are free to have this method call other methods. I would recommend creating a private method that
* this method calls and have that private method be the recursive method.
* A recursive method is one that calls itself.
* #param color -- the new color to flood the board with.
*/
public void move(int replacementColor) {
int targetColor = board[0][0];
recursiveMove(0,0,targetColor,replacementColor);
numOfMoves++;
}
private void recursiveMove(int xCoord, int yCoord, int targetColor, int replacementColor) {
if (targetColor == replacementColor) {
return;
}
if (board[xCoord][yCoord] != targetColor) {
return;
}
board[xCoord][yCoord] = replacementColor;
if (yCoord != size-1) {
recursiveMove(xCoord,yCoord+1,targetColor,replacementColor);
}
if (yCoord != 0) {
recursiveMove(xCoord,yCoord-1,targetColor,replacementColor);
}
if (xCoord != 0) {
recursiveMove(xCoord-1,yCoord,targetColor,replacementColor);
}
if (xCoord != size-1) {
recursiveMove(xCoord+1,yCoord,targetColor,replacementColor);
}
}
/**returns true if the board is not completely filled with a single color.
* Otherwise it returns false.
* #return true if board is all one color
*/
public boolean finished() {
//TODO finish this method
for (int count = 0; count < size; count++) {
for (int counter = 0; counter < size; counter++) {
if (board[count][counter] != board[0][0]) {
return false;
}
}
}
return true;
}
/**returns how many times the move() method has been called.
* #return the number of times the move() method has been called.
*/
public int numMoves() {
//TODO finish this method
return numOfMoves;
}
/**Returns a string representation of the board. Use tabs between elements of the board.
* And have every row of the board be separated by a newline character.
* Example:
* "1\t0\t3\t\n2\t0\t2\t\n1\t0\t1\t\n"
* #return a String representation of the board
*/
public String toString() {
//TODO finish this method
String boardString = "";
for (int count = 0; count < board.length; count++) {
for (int counter = 0; counter < board.length; counter++) {
boardString += board[count][counter];
boardString += "\t";
}
boardString += "\n";
}
return boardString;
}
}
FloodIt.java, which contains the JFrame lines in order to load the graphical interface, as well as code to actually run the game (it's not entirely finished, as I got stuck):
import java.util.Scanner;
import javax.swing.JFrame;
/**This class is the main method for the Flood-It game as found on many web sites
* ( such as http://www.lemoda.net/javascript/flood-it/ or
http://unixpapa.com/floodit/?sz=14&nc=4 ).
* It prompts the user for the size of the board
* and the number of colors. The user is prompted for the next color until the board is flooded.
* After the game is over it prints how many turns the user took and then asks if they want to play again.
*/
class FloodIt {
private static final int FRAMESIZE = 1000;
public static void main(String args[]) {
JFrame frame = new JFrame();
frame.setSize(FRAMESIZE,FRAMESIZE);
frame.setTitle("Brennan's Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicalBoard component = new GraphicalBoard();
frame.add(component);
frame.setVisible(true);
String again="";
int size = 20;
int numColors = 7;
do {
Board board=new Board(size,numColors);
while(!board.finished()) {
//I will change the print statements below into graphical input boxes later
System.out.print("****************\n"+board+"\n****************\n");
System.out.print("What color do you choose? ");
int color=Integer.parseInt(scan.nextLine());
board.move(color);
}
System.out.println("Nice job, you finished in "+board.numMoves());
System.out.print("Would you like to play again (Y/N)? ");
again=scan.nextLine();
} while (again.equalsIgnoreCase("Y"));
scan.close();
}
}
And GraphicalBoard.java, which is supposed to take the values of the 2d array from Board.java and display the board in a graphical interface. Each number that could be in the 2d array corresponds with a color in the Colors array:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JComponent;
public class GraphicalBoard extends JComponent {
private int xSize = 50;
private int ySize = 50;
public void paintComponent(Graphics g, int size, Board board) {
String colors[] = {"BLUE","GREEN","YELLOW","RED","BLACK","ORANGE","PINK"};
Graphics2D g2 = (Graphics2D) g;
int xCoord = 0;
int yCoord = 0;
int colorNum = 0;
String colorOfSquare = "";
for (int count = 0; count < size; count++) {
for (int counter = 0; counter < size; counter++) {
colorNum = board[count][counter];
colorOfSquare = colors[colorNum];
g2.setColor(Color.colorOfSquare);
Rectangle square = new Rectangle(xCoord,yCoord,xSize,ySize);
xCoord += 50;
}
yCoord += 50;
}
}
}
Two problems:
In GraphicalBoard.java, on the line "colorNum = board[count][counter];", I am getting the error: "The type of expression must be an array type but it resolved to Board."
I seem to be having a problem bring over the already initialized board from the Board.java class into the GraphicalBoard.java class.
In GraphicalBoard.java, on the line "g2.setColor(Color.colorOfSquare);", I am getting the error: "colorOfSquare cannot be resolved or it not a field."
I know the problem, it is supposed to be something like "g2.setColor(Color.BLACK);", but I am going to have the user input the color, so it kind of needs to be a variable and I was hoping to have something cleaner than just an if statement for every color.
Any suggestions? Thanks!
Your Board class contains a member variable int[][] board, but its scope is private. When you call the following:
colorNum = board[count][counter];
This is wrong because the board variable here is an object of Board class. It itself is not the two day array, but it encapsulates int[][] board inside it. So you need to provide a getter method in Board to expose its board member variable like this:
public int[][] getBoard() {
return board;
}
Then in the paintComponent method you can access it as: board.getBoard()[count][counter].
You already seem to have a user inputted color in the colorOfSquare variable. But Graphics2D's setColor method would only accept a variable of type java.awt.Color. Since you have the String representation of the color, you can get its corresponding java.awt.Color value using reflection as mentioned here. The following should work for you:
Color color;
try {
Field field = Color.class.getField(colorOfSquare);
color = (Color) field.get(null);
} catch (Exception e) {
color = null; // Not defined
}
Two answers:
paintComponent ONLY receives a Graphics object. See this link for a short tutorial. If you need to access other objects in this method, make them variables of GraphicalBoard and pass them om during construction.
1.5 You need to access the Board's board, as this is what you are using. So add a getBoard(int i, int j) in class Board. Something like the following (I also added a getSize() method) :
public int getBoard(int i, int j) {
return board[i][j] ;
}
public int getSize() {
return size;
}
Your color colorOfSquare is already defined as a color. The error arises because the Color class doesn't have such a constant. You should just pass the color directly.
Try this:
public class GraphicalBoard extends JComponent {
private int xSize = 50;
private int ySize = 50;
private Board board;
private int size;
public GraphicalBoard() {
}
public void setBoard(Board board){
this.board = board;
}
public void paintComponent(Graphics g) {
super.paintComponent();
if(board == null) {
throw new RuntimeException("Board not set") ;
}
String colors[] = {"BLUE","GREEN","YELLOW","RED","BLACK","ORANGE","PINK"};
Graphics2D g2 = (Graphics2D) g;
int xCoord = 0;
int yCoord = 0;
int colorNum = 0;
int size = board.getSize() ;
String colorOfSquare = "";
for (int count = 0; count < size; count++) {
for (int counter = 0; counter < size; counter++) {
colorNum = board.getBoard(count, counter) ;
colorOfSquare = colors[colorNum];
g2.setColor(colorOfSquare);
Rectangle square = new Rectangle(xCoord,yCoord,xSize,ySize);
xCoord += 50;
}
yCoord += 50;
}
}
In very general terms,
your view, here the drawing JPanel, should contain a reference to the model object via a has-a or "composition" structure
The view should be notified of changes in the model, often via event listeners such as a PropertyChangeListener
The view then extracts the key information and uses that to help decide what to draw. So, if it has a reference to the current Board object, it can make getter method calls from within the paintComponent method.
Other issues in your code:
Make sure to call the super's paintComponent within your override, else you will not clean up "dirty" pixels
Don't mix linear code -- a Scanner based on System.in with GUI code. Make it all one or the other.
For example, in the code below, I use a model class, the class that holds the int[][] board variable and here called BoardModel, and I give it a SwingPropertyChangeSupport object.
class BoardModel {
// .....
private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this);
This object will accept listeners, and will allow me to notify listeners of changes to the model.
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
support.addPropertyChangeListener(propertyName, listener);
}
Then when the model changes, I notify all listeners by calling the support object's firePropertyChange(...) method:
public void selectSquare(int x, int y) {
int replacementValue = board[y][x];
int targetValue = board[0][0];
if (targetValue == replacementValue) {
return;
} else {
recursiveMove(0, 0, targetValue, replacementValue);
numOfMoves++;
support.firePropertyChange(BOARD, null, board); // ***** here
setWin(checkForWin());
}
}
Then in the control, I can add listeners that notify the view of changes:
model.addPropertyChangeListener(BoardModel.BOARD, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent e) {
view.repaint();
String moveCount = "" + model.getNumOfMoves();
controlPanel.setMoveCountFieldText(moveCount);
}
});
model.addPropertyChangeListener(BoardModel.WIN, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ((boolean) evt.getNewValue()) {
String message = "Move count: " + model.getNumOfMoves();
String title = "Game Over";
int messageType = JOptionPane.PLAIN_MESSAGE;
JOptionPane.showMessageDialog(view, message, title, messageType);
}
}
});
A working example could look like this:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.SwingPropertyChangeSupport;
public class BoardFun {
private static final int NUM_COLORS = 6;
private static final int SIZE = 20;
#SuppressWarnings("serial")
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
int size = SIZE;
int numColors = NUM_COLORS;
final BoardModel model = new BoardModel(size , numColors );
final BoardPanel view = new BoardPanel();
final ControlPanel controlPanel = new ControlPanel();
view.setModel(model);
view.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
Point p = mEvt.getPoint();
int row = view.getRow(p);
int col = view.getColumn(p);
model.selectSquare(col, row);
}
});
model.addPropertyChangeListener(BoardModel.BOARD, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent e) {
view.repaint();
String moveCount = "" + model.getNumOfMoves();
controlPanel.setMoveCountFieldText(moveCount);
}
});
model.addPropertyChangeListener(BoardModel.WIN, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ((boolean) evt.getNewValue()) {
String message = "Move count: " + model.getNumOfMoves();
String title = "Game Over";
int messageType = JOptionPane.PLAIN_MESSAGE;
JOptionPane.showMessageDialog(view, message, title, messageType);
}
}
});
controlPanel.setResetAction(new AbstractAction("Reset") {
#Override
public void actionPerformed(ActionEvent e) {
model.reset();
}
});
JFrame frame = new JFrame("Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(view);
frame.add(controlPanel, BorderLayout.PAGE_START);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
#SuppressWarnings("serial")
class ControlPanel extends JPanel {
private JTextField moveCountField = new JTextField("0", 10);
private JButton resetButton = new JButton();
public ControlPanel() {
add(new JLabel("Move Count:"));
add(moveCountField);
add(resetButton);
}
public void setResetAction(Action action) {
resetButton.setAction(action);
}
public void setMoveCountFieldText(String text) {
moveCountField.setText(text);
}
}
#SuppressWarnings("serial")
class BoardPanel extends JPanel {
private static final int PREF_W = 640;
private static final int PREF_H = PREF_W;
private BoardModel model;
private Color[] colors;
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
} else {
return new Dimension(PREF_W, PREF_H);
}
}
public void setModel(BoardModel model) {
this.model = model;
colors = new Color[model.getNumColors()];
// create colors.length Colors, all of different hue
for (int i = 0; i < colors.length; i++) {
float hue = (float) i / colors.length;
colors[i] = Color.getHSBColor(hue, 1f, 1f);
}
}
// translate point to logical square position
int getRow(Point p) {
return (p.y * model.getBoard().length) / getHeight();
}
int getColumn(Point p) {
return (p.x * model.getBoard()[0].length) / getWidth();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // always call the super's method
if (model == null) {
return;
}
int board[][] = model.getBoard();
int height = getHeight() / board.length;
int width = getWidth() / board[0].length;
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
Color color = colors[board[i][j]];
g.setColor(color);
int x = (j * getWidth()) / board[0].length;
int y = (i * getHeight()) / board.length;
g.fillRect(x, y, width, height);
}
}
}
}
class BoardModel {
public static final String BOARD = "board";
public static final String WIN = "win";
private int[][] board;
private int numColors;
private Random random = new Random();
private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this);
private int numOfMoves = 0;
private boolean win = false;
public BoardModel(int size, int numColors) {
board = new int[size][size];
this.numColors = numColors;
reset();
}
public void reset() {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
board[i][j] = random.nextInt(numColors);
}
}
numOfMoves = 0;
support.firePropertyChange(BOARD, null, board);
setWin(false);
}
public int[][] getBoard() {
return board;
}
public int getNumOfMoves() {
return numOfMoves;
}
public int getNumColors() {
return numColors;
}
public void setWin(boolean win) {
boolean oldValue = this.win;
boolean newValue = win;
this.win = win;
support.firePropertyChange(WIN, oldValue, newValue);
}
public boolean isWin() {
return win;
}
public void selectSquare(int x, int y) {
int replacementValue = board[y][x];
int targetValue = board[0][0];
if (targetValue == replacementValue) {
return;
} else {
recursiveMove(0, 0, targetValue, replacementValue);
numOfMoves++;
support.firePropertyChange(BOARD, null, board);
setWin(checkForWin());
}
}
public boolean checkForWin() {
int value = board[0][0];
for (int[] row : board) {
for (int cell : row) {
if (cell != value) {
return false;
}
}
}
return true;
}
private void recursiveMove(int i, int j, int targetValue, int replacementValue) {
int currentValue = board[i][j];
if (currentValue != targetValue || currentValue == replacementValue) {
return;
}
board[i][j] = replacementValue;
int rowMin = Math.max(0, i - 1);
int rowMax = Math.min(board.length - 1, i + 1);
int colMin = Math.max(0, j - 1);
int colMax = Math.min(board[i].length - 1, j + 1);
for (int i2 = rowMin; i2 <= rowMax; i2++) {
if (i2 != i) {
recursiveMove(i2, j, targetValue, replacementValue);
}
}
for (int j2 = colMin; j2 <= colMax; j2++) {
if (j2 != j) {
recursiveMove(i, j2, targetValue, replacementValue);
}
}
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
support.addPropertyChangeListener(propertyName, listener);
}
}

Isometric tiles drawing and picking - JAVA

I'm trying to draw isometric tiles in Java and implement a tile picking system using the mouse cursor. I draw the tiles using these math formulas I found and adapted to my tile textures which you can find below. Tiles are 64x64px but flat tiles are only 32px height even if I draw them using the 64x64 sprite.
The map is a simple 2d array where my tiles are represented by their id.
Here is the class I use to convert map coordinates to screen coordinates using the toIso() function. I pass my screen coordinates which represent the cursor position on the screen to the toGrid() function to convert them to map coordinates.
public class Utils {
private static int TILE_WIDTH = Tile.TILE_WIDTH;
private static int TILE_HEIGHT = Tile.TILE_HEIGHT;
private static int TILE_WIDTH_HALF = TILE_WIDTH/2;
private static int TILE_HEIGHT_HALF = TILE_HEIGHT/2;
private static int TILE_WIDTH_QUARTER = TILE_WIDTH_HALF/2;
private static int TILE_HEIGHT_QUARTER = TILE_HEIGHT_HALF/2;
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
//800 and 100 are temporary offsets I apply to center the map.
i+=800;
j+=100;
return new int[]{i,j};
}
public static int[] toGrid(int x, int y){
//800 and 100 are temporary offsets I apply to center the map.
x-=800;
y-=100;
int i = ( x / ( TILE_WIDTH_HALF ) + y / ( TILE_HEIGHT_QUARTER )) / 2;
int j = ( y / ( TILE_HEIGHT_QUARTER ) - ( x / ( TILE_WIDTH_HALF ))) / 2;
return new int[]{i,j};
}}
I currently render my tiles by using two for loops and converting the map coordinates to screen coordinates using the toIso() function.
public void render(Graphics g){
for(int x = 0;x<width;x++){
for(int y = 0;y<height;y++){
int[] isoCoords = Utils.toIso(x, y);
int fx = isoCoords[0];//
int fy = isoCoords[1];//
if(world[x][y] == 0){
Tile grass = new GrassTile(0);
grass.render(g, grass.getId(), fx, fy);
}else if(world[x][y] == 1){
Tile water = new WaterTile(1);
water.render(g, water.getId(), fx, fy);
}
}
}
}
I get a diamond shape as I wanted rendered on the screen.
I finally update each tick which are the actual mouse coordinates on screen.
int[] coords = Utils.toGrid(mouseManager.getMouseX(), mouseManager.getMouseY());
tileX = coords[0];
tileY = coords[1];
The selected tile is finally rendered:
BufferedImage selectedTexture = Assets.selected;
int[] coordsIsoSelected = Utils.toIso(this.tileX, this.tileY);
g.drawImage(selectedTexture, coordsIsoSelected[0], coordsIsoSelected[1], Tile.TILE_WIDTH, Tile.TILE_HEIGHT, null);
g.drawRect(Utils.toIso(tileX, tileY)[0], Utils.toIso(tileX, tileY)[1]+Tile.TILE_HEIGHT/2, Tile.TILE_WIDTH, Tile.TILE_HEIGHT/2);//I draw a rectangle to visualize what's happening.
Finally, my tile detection isn't working as expected, it isn't fitting the tiles perfectly, however it seems to be in relation with the rectangle I draw. I can't figure out the solution to this problem, I thank you in advance for reading or any advice you could give to me. If you need more precisions, I would be glad to give you more informations.
Here is a video showing what is actually happening: youtu.be/baCVIfJz2Wo
EDIT:
Here is some of my code you could use to run an application like mine. Sorry for this very messy code, but I tried to make it as short as possible without disturbing the behavior of the "game".
You will need to put the sheet provided before into a "textures" folder created into the ressource folder of the project.
The gfx package:
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
public class Assets {
private static final int width = 64, height = 64;
public static BufferedImage grass, water, selected;
public static void init(){
//Temp
SpriteSheet tileSheet = new SpriteSheet(ImageLoader.loadImage("/textures/sheet.png"));
grass = tileSheet.crop(width*2, 0, width, height);
water = tileSheet.crop(width*9, height*5, width, height);
selected = tileSheet.crop(0, height*5, width, height);
//
}
}
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageLoader {
public static BufferedImage loadImage(String path){
try {
return ImageIO.read(ImageLoader.class.getResource(path));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
return null;
}
}
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
public class SpriteSheet {
private BufferedImage sheet;
public SpriteSheet(BufferedImage sheet){
this.sheet = sheet;
}
public BufferedImage crop(int x, int y, int width, int height){
return sheet.getSubimage(x, y, width, height);
}
}
The rest of the project:
package fr.romainimberti.isometric;
public class Launcher {
public static void main(String args[]){
System.setProperty("sun.awt.noerasebackground", "true");
Game game = new Game("Isometric", 1280, 720);
game.start();
}
}
package fr.romainimberti.isometric;
import java.awt.Canvas;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Display {
private JFrame frame;
private Canvas canvas;
private String title;
private int width, height;
public Display(String title, int width, int height){
this.title = title;
this.width = width;
this.height = height;
createDisplay();
}
private void createDisplay(){
frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
canvas = new Canvas();
canvas.setPreferredSize(new Dimension(width, height));
canvas.setMaximumSize(new Dimension(width, height));
canvas.setMinimumSize(new Dimension(width, height));
canvas.setFocusable(true);
frame.add(canvas);
frame.pack();
}
public Canvas getCanvas(){
return canvas;
}
public JFrame getFrame(){
return frame;
}
}
package fr.romainimberti.isometric;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
import fr.romainimberti.isometric.gfx.Assets;
public class Game implements Runnable {
private Display display;
private int width, height;
public JFrame frame;
private boolean running = false;
private Thread thread;
public String title;
private BufferStrategy bs;
private Graphics g;
public int x, y;
public int[][] world;
public static final int TILE_WIDTH = 64;
public static final int TILE_HEIGHT = 64;
public static final int TILE_WIDTH_HALF = 32;
public static final int TILE_HEIGHT_HALF = 32;
public static final int TILE_WIDTH_QUARTER = 16;
public static final int TILE_HEIGHT_QUARTER = 16;
public int xOffset;
//Input
private MouseManager mouseManager;
public Game(String title, int width, int height){
this.width = width;
this.height = height;
this.mouseManager = new MouseManager(this);
this.world = new int[10][10];
}
private void init(){
display = new Display(title, width, height);
display.getFrame().addMouseListener(mouseManager);
display.getFrame().addMouseMotionListener(mouseManager);
display.getCanvas().addMouseListener(mouseManager);
display.getCanvas().addMouseMotionListener(mouseManager);
this.frame = display.getFrame();
Assets.init();
xOffset = frame.getWidth()/2;
//Fill the world
for(int i = 0;i<world.length;i++){
for(int j=0;j<world[0].length;j++){
int r = ThreadLocalRandom.current().nextInt(0,1+1);
if(r == 0)
world[i][j] = 0;
else
world[i][j] = 1;
}
}
}
private void tick(){
mouseManager.tick();
xOffset = frame.getWidth()/2;
}
private void render(){
bs = display.getCanvas().getBufferStrategy();
if(bs == null){
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
//Clear Screen
g.clearRect(0, 0, frame.getWidth(), frame.getHeight());
//Draw Here
//World render
for(int x = 0;x<world.length;x++){
for(int y = 0;y<world[0].length;y++){
int[] isoCoords = toIso(x, y);
int fx = isoCoords[0];//
int fy = isoCoords[1];//
if(world[x][y] == 0){
g.drawImage(Assets.grass, fx, fy, null);
}else if(world[x][y] == 1){
g.drawImage(Assets.water, fx, fy, null);
}
}
}
//Selected tile render
int[] coordsIsoSelected = toIso(x, y);
g.drawImage(Assets.selected, coordsIsoSelected[0], coordsIsoSelected[1], TILE_WIDTH, TILE_HEIGHT, null);
//End Drawing
bs.show();
g.dispose();
}
public void run(){
init();
int fps = 120;
double timePerTick = 1000000000 / fps;
double delta = 0;
long now;
long lastTime = System.nanoTime();
while(running){
now = System.nanoTime();
delta += (now - lastTime) / timePerTick;
lastTime = now;
if(delta >= 1){
tick();
render();
delta--;
}
}
stop();
}
public MouseManager getMouseManager(){
return mouseManager;
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
public synchronized void start(){
if(running)
return;
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop(){
if(!running)
return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
i+=xOffset;
return new int[]{i,j};
}
public static int[] toGrid(int x, int y){
x-=xOffset;
int i = ( x / ( TILE_WIDTH_HALF ) + y / ( TILE_HEIGHT_QUARTER )) / 2;
int j = ( y / ( TILE_HEIGHT_QUARTER ) - ( x / ( TILE_WIDTH_HALF ))) / 2;
return new int[]{i,j};
}
}
package fr.romainimberti.isometric;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
public class MouseManager implements MouseListener, MouseMotionListener {
private boolean leftPressed, rightPressed;
private int mouseX, mouseY;
private Game game;
public MouseManager(Game game){
this.game = game;
}
public void tick(){
game.x = game.toGrid(mouseX, mouseY)[0];
game.y = game.toGrid(mouseX, mouseY)[1];
}
// Getters
public boolean isLeftPressed(){
return leftPressed;
}
public boolean isRightPressed(){
return rightPressed;
}
public int getMouseX(){
return mouseX;
}
public int getMouseY(){
return mouseY;
}
// Implemented methods
#Override
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
leftPressed = true;
else if(e.getButton() == MouseEvent.BUTTON3)
rightPressed = true;
}
#Override
public void mouseReleased(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
leftPressed = false;
else if(e.getButton() == MouseEvent.BUTTON3)
rightPressed = false;
}
#Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
}
#Override
public void mouseDragged(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}
If you need it, you can find here my project architecture so you can organize all the files correctly.
Again, sorry for this very, very messy code but I had to split all the usefull parts of my game to reduce it's size. Also don't forget to download and place correctly the sheet file. Hope this will help.
128*64 tiles
just wanted to say that I finally solved it. It was just a conversion to int issue. These are the final methods that I use. Hope it will help people who are trying to work with isometric tiles. Thank you !
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
i += xOffset-TILE_WIDTH_HALF;
j+=yOffset;
return new int[]{i,j};
}
public static int[] toGrid(double i, double j){
i-=xOffset;
j-=yOffset;
double tx = Math.ceil(((i / TILE_WIDTH_HALF) + (j / TILE_HEIGHT_QUARTER))/2);
double ty = Math.ceil(((j / TILE_HEIGHT_QUARTER) - (i / TILE_WIDTH_HALF))/2);
int x = (int) Math.ceil(tx)-1;
int y = (int) Math.ceil(ty)-1;
return new int[]{x, y};
}
After replacing the spritesheet with the new one with 128x64 pixels' tiles, I've been able to achieve the desired output partially...
Why I say "partially"? Because I've managed to get the desired result only from the half right part of the map.
I believe it could have something to do with how the map is being painted, I'm not a native English speaker so I might be misunderstanding what the "Notes" section says in OP's link:
Notice that the "origin" of the isometric tile is the top corner. But usually when we draw a sprite it's from the top-left corner
I've called the methods toGrid() and toIso() at the beginning of the program as follows:
int[] coordinates = Game.toIso(2, 1);
System.out.println(coordinates[0] + "-" + coordinates[1]);
int[] coordinates2 = Game.toGrid(coordinates[0], coordinates[1]);
System.out.println(coordinates2[0] + "-" + coordinates2[1]);
And got the following results, (Which indeed are what we were expecting), so we know the methods work correctly:
64-96
2-1
I was sure to modify the Assets file:
public static final int WIDTH = 128, HEIGHT = 64;
Where I also changed the variable names following the Java naming conventions (ALL_WORDS_UPPER_CASE_CONSTANTS) and made it public instead of private
I also changed the Game file:
public static final int TILE_WIDTH = Assets.WIDTH;
public static final int TILE_HEIGHT = Assets.HEIGHT;
public static final int TILE_WIDTH_HALF = TILE_WIDTH / 2;
public static final int TILE_HEIGHT_HALF = TILE_HEIGHT / 2;
public static final int TILE_WIDTH_QUARTER = TILE_WIDTH / 4;
public static final int TILE_HEIGHT_QUARTER = TILE_HEIGHT / 4;
To use those constants on the Assets file and calculate the HALF and QUARTER instead of hardcoding it.
I also believe xOffset shouldn't be public but private as well as some other variables on the program...
The tick() method, doesn't need to calculate the xOffset everytime, so we can get rid of this line inside it:
xOffset = frame.getWidth() / 2 - 65;
I also changed the way you paint the tile you're selecting as:
// Selected tile render
int[] coordsIsoSelected = toIso(x, y);
g.drawImage(Assets.selected, coordsIsoSelected[0], coordsIsoSelected[1], TILE_WIDTH, TILE_HEIGHT, null);
And for the Tolso equations, I changed them to:
public static int[] toIso(int x, int y) {
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_HALF;
i += xOffset;
return new int[] { i, j };
}
Below I adjusted the parenthesis locations:
public static int[] toGrid(int x, int y) {
x -= xOffset;
int i = ((x / TILE_WIDTH_HALF) + (y / TILE_HEIGHT_HALF)) / 2;
int j = ((y / TILE_HEIGHT_HALF) - (x / TILE_WIDTH_HALF)) / 2;
return new int[] { i, j };
}

This code should create a JFrame and paint on it... but it doesn't

I've been staring at this for days...
I think the Imageicon part is correct, and is referencing the right object.
It has something to do with creating the board, map, and stuff, and I'm not really good with that.
public class Maze {
public static final int SCREEN_SIZE = 1024;
public static void main(String[] args){
new Maze();
}
// help from: http://docs.oracle.com/javase/tutorial/uiswing/components/frame.html
public Maze(){
JFrame f = new JFrame();
f.setTitle("Maze");
f.add(new Board());
f.setSize(SCREEN_SIZE,SCREEN_SIZE);
f.setLocationRelativeTo(null);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
So you create the screen, make the board, get the images, and then paint depending on the contents of the array.
public class Board extends JPanel implements ActionListener {
public static final int TILE_SIZE = 32;
private Timer timer;
public Map m;
public Board(){
m = new Map();
timer = new Timer(25, this);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint(); // TODO Auto-generated method stub
}
public void paint(Graphics g){
super.paint(g);
for (int y = 0; y < TILE_SIZE; y++) {
for (int x = 0; x < TILE_SIZE; x++) {
if(m.Map[x][y] == 0) {
g.drawImage(m.getFloor(), x * 32, y * 32, null);
}
if (m.Map[x][y] == 1) {
g.drawImage(m.getWall(), x * 32, y * 32, null);
}
}
}
}
}
public class Map {
public static Image floor, wall;
public int Map[][] = new int[Maze.SCREEN_SIZE / Board.TILE_SIZE][Maze.SCREEN_SIZE / Board.TILE_SIZE];
public Map() {
ImageIcon img = new ImageIcon("C://Users/Brian2/Pictures/backgrounds/FLOOR.png");
floor = img.getImage();
img = new ImageIcon("C://Users/Brian2/Pictures/backgrounds/WALL");
wall = img.getImage();
GenMap();
}
public void GenMap() {
for(int i = 0; i < Maze.SCREEN_SIZE / Board.TILE_SIZE; i++){
for(int j = 0; j < Maze.SCREEN_SIZE / Board.TILE_SIZE; j++){
Map[i][j] = 1;
}
}
}
public Image getWall() {
return wall;
}
public Image getFloor() {
return floor;
}
public void printMap() {}
}
It won't show anything on the JPanel that opens when I run it. I don't know what's wrong. There are no errors and I've been stuck for about 4 days.
I tried to reproduce your issue, but it worked for me.
Depends which JDK you use, I used 1.6.0_45.
However I assume you want to paint a map of a "world".
I reorganized your code because I think you tried to do also unnecessary things, like keeping an integer array to model the world, but in fact you translate it to images.
The Board class:
package org.myself.mazetest;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Board extends JPanel implements ActionListener
{
private static final int BOARD_SIZE = 1024;
private static final int FRAME_VERTICAL_BORDERS = 40;
private static final int FRAME_HORIZONTAL_BORDERS = 20;
private static final int TILE_SIZE = 32;
private static final int TIMER_DELAY = 25;
private final Timer timer;
private final int tileCount;
public Map map;
public Board( )
{
tileCount = Board.BOARD_SIZE / Board.TILE_SIZE;
map = new Map( tileCount );
timer = new Timer( Board.TIMER_DELAY, this );
timer.start();
setSize( Board.BOARD_SIZE, Board.BOARD_SIZE );
}
#Override
public void actionPerformed( final ActionEvent e )
{
repaint(); // TODO Auto-generated method stub
}
#Override
public void paintComponent( final Graphics g )
{
super.paintComponent( g );
Image img;
for ( int y = 0; y < tileCount; y++ )
{
for ( int x = 0; x < tileCount; x++ )
{
img = map.getImageFor( x, y );
if ( img != null )
{
g.drawImage( img, x * Board.TILE_SIZE, y * Board.TILE_SIZE, null );
}
}
}
}
public static void main( final String[] args )
{
final JFrame f = new JFrame();
f.setTitle( "Maze" );
f.setLayout( null );
f.add( new Board() );
f.setSize( Board.BOARD_SIZE + Board.FRAME_HORIZONTAL_BORDERS,
Board.BOARD_SIZE + Board.FRAME_VERTICAL_BORDERS );
f.setLocationRelativeTo( null );
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.setVisible( true );
}
}
And the Map class:
package org.myself.mazetest;
import java.awt.Image;
import javax.swing.ImageIcon;
public class Map
{
private final Image floor, wall;
private final Image mapArray[][];
private final int tileCount;
public Map( final int tileCount )
{
mapArray = new Image[tileCount][tileCount];
this.tileCount = tileCount;
ImageIcon img = new ImageIcon( "C:\\Users\\Brian2\\Pictures\\backgrounds\\FLOOR.png" );
floor = img.getImage();
//TODO: sure without extension?
img = new ImageIcon( "C:\\Users\\Brian2\\Pictures\\backgrounds\\WALL" );
wall = img.getImage();
//now build up the world's map :)
genMap();
}
private void genMap()
{
//we build the inner area
//that's why we start from 1, and go until tileCount - 1
for ( int row = 1; row < (tileCount - 1); row++ )
{
for ( int col = 1; col < (tileCount - 1); col++ )
{
mapArray[row][col] = floor;
}
}
//we build the wall itself
final int firstRow = 0;
final int lastRow = tileCount - 1;
final int firstCol = 0;
final int lastCol = tileCount - 1;
for ( int col = 0; col < tileCount; col++ )
{
mapArray[firstRow][col] = wall;
mapArray[lastRow][col] = wall;
}
for ( int row = 0; row < tileCount; row++ )
{
mapArray[row][firstCol] = wall;
mapArray[row][lastCol] = wall;
}
//this also can be done as follows, but I don't want do make confusion
/*
for ( int colOrRow = 0; colOrRow < tileCount; colOrRow++ )
{
mapArray[firstRow][colOrRow] = wall;
mapArray[lastRow][colOrRow] = wall;
mapArray[colOrRow][firstCol] = wall;
mapArray[colOrRow][lastCol] = wall;
}
*/
}
public Image getImageFor( final int col, final int row )
{
return mapArray[row][col];
}
}
Attached you find the output (with wall and floor images got from google).
Please check your wall image, in your example it is without extension... this can lead to problems in some image reading API-s.
The code from Maze class I moved to board because in fact it was just a frame creation, and I think is not needed to use a separate class for that.
I tried to give verbose names and comments, but feel free to come back to me if is unclear something.

Java Object Array - Drop objects in array by int

Im currently am working on a game in Java that spawns blocks on the top of the screen and they fall down (you have to dodge them). Currently I have the game spawning blocks on the top of the screen from an array but I do not know how to make their y position go down by (int).
Basically I just need help making a public void that checks an object array and with every instance of a object it finds it drops the y position of it by 12.
Selected snippits from code:
static Object[][] food = new Object[7][700];
public void draw() {
try {
Graphics g = bufferStrategy.getDrawGraphics();
g.clearRect(0, 0, this.getSize().width, this.getSize().height);
// Draw to back buffer
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getSize().width, this.getSize().height);
g.setColor(Color.BLUE);
g.fillRect(Player.getX(), Player.getY(), Player.WIDTH, Player.HEIGHT);
g.setColor(Color.GRAY);
for(int x = 0; x < food.length; x++) {
for(int y = 0; y < food[x].length; y ++) {
Object o = food[x][y];
if(o instanceof Hamburger) {
new Hamburger(x * 100, y, g);
}
else if(o instanceof Salad) {
new Salad(x * 100, y, g);
}
}
}
GUI.draw(g);
} catch(Exception e) {
e.printStackTrace();
} finally {
bufferGraphics.dispose();
}
}
public void dropBlocks() {
}
public void addBlock() {
if(new Random().nextInt(2) == 1) {
food[new Random().nextInt(7)][0] = new Hamburger(0, 0);
} else food[new Random().nextInt(7)][0] = new Salad(0, 0);
}
public void drawBackbufferToScreen() {
bufferStrategy.show();
Toolkit.getDefaultToolkit().sync();
}
public void run() {
int i = 0;
while(running) {
i++;
draw();
Player.update();
drawBackbufferToScreen();
if(i == 50) {
addBlock();
i = 0;
}
dropBlocks();
Thread.currentThread();
try {
Thread.sleep(10);
} catch(Exception e) {
e.printStackTrace();
}
}
}
Food.java (What Hamburger and Salad extend)
public class Food {
public static int x;
public static int y;
public static int HEIGHT;
public static int WIDTH;
public int type;
private static Image blockImage;
// Default constructor
public Food() {
Food.x = 0;
Food.y = 0;
Food.HEIGHT = 80;
Food.WIDTH = 80;
}
public Food(int x, int y) {
Food.x = x;
Food.y = y;
Food.HEIGHT = 80;
Food.WIDTH = 80;
}
// Getters and setters
I would do something like this:
Object[][] board = new Object[7][700]; //Game board.
//you can also switch the width and height
//Initialization
for (int x = 699; x >= 0; x--) {
for (int y = 0; y < 7; y++) {
if (x == 699) {
board[y][x] = BLANK; //set spot to open if it is the bottom row
continue;
}
board[y][x+1] = board[y][x]; //move row down
}
}
//Generate new Objects if needed for the top row of the board.
Clearly Object does not have a getY() method. You could do ugly things with casting and instanceof, but this design is rather putrid. Try moving towards an object oriented model.
interface Drawable {
int getX();
int getY();
void moveDown(int numSpaces);
}
class Hamburger implements Drawable {
private int x;
private int y;
public Hamburger(final int xPos, final int yPos) {
x = xPos;
y = yPos;
}
#Override
public void moveDown(final int numSpaces) {
// eg negative number, off the screen
if(isValid(numSpaces)) {
setY(getY() - numSpaces);
}
}
}
class Positioner {
public static void moveMyObjects(final Drawable[][] objs) {
for(Drawable[] rows : objs) {
for(Drawable col : rows) {
// clearly Object doesn't have a getY() method, so you should create your own interface and implement it
col.moveDown(12);
}
}
}
}

Categories

Resources