Bad Color Referencing? - java

I am in the middle of designing a game very similar to Flow free. I have run into an issue in my code where I want to be able to click on a circle in the grid, be able to gauge its color, and then check if the color is white. If the color is white, I will then print error, which is a placeholder for now. However, even when I click on non-white tiles, I still see the error message being printed. What Am I doing wrong?
My Code:
import javax.swing.JFrame; //JFrame Class
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Shape;
import java.util.ArrayList;
import javax.swing.JPanel;
import java.awt.geom.Ellipse2D;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class DriverV2 extends JPanel {
static ArrayList<Shape> circles;
static ArrayList<Color> colors;
public DriverV2() {
circles = new ArrayList<Shape>();
colors = new ArrayList<Color>();
colors.add( Color.CYAN);//1
colors.add( Color.BLUE);//2
colors.add( Color.WHITE);//3
colors.add( Color.WHITE);//4
colors.add( Color.GREEN);//5
colors.add( Color.WHITE);//6
colors.add( Color.CYAN);//7
colors.add( Color.WHITE);//8
colors.add( Color.WHITE);//9
colors.add( Color.RED);//10
colors.add( Color.WHITE);//11
colors.add( Color.YELLOW);//12
colors.add( Color.WHITE);//13
colors.add( Color.GREEN);//14
colors.add( Color.WHITE);//15
colors.add( Color.WHITE);//16
colors.add( Color.BLUE);//17
colors.add( Color.WHITE);//18
colors.add( Color.RED);//19
colors.add( Color.WHITE);//20
colors.add( Color.WHITE);//21
colors.add( Color.WHITE);//22
colors.add( Color.WHITE);//23
colors.add( Color.WHITE);//24
colors.add( Color.YELLOW);//25
int rows = 5;
for (int y=0;y< rows;y++)
{
for (int x=0;x<rows;x++)
{
circles.add( new Ellipse2D.Double((x + 1) * 150, (y + 1) *150, 100, 100) );
}
}
}
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
Color foreground = g2d.getColor();
int h = 0;
for (Shape shape : circles)
{
g2d.setColor( colors.get(h));
g2d.draw( shape );
g2d.fill(shape);
h++;
}
}
public static boolean checkLinearity(int index1, int index2){
if((index1%5) == (index2 %5)) { //vertical line check
return true;
}
if(Math.abs(index1-index2) < 5) { //horizantal line check
return true;
}
else {
return false;
}
}
static int counter = 0;
static int index1 = 0;
static int index2 = 0;
public static void main(String[] args)
{
JFrame f = new JFrame("Flow"); //new JFrame
DriverV2 t = new DriverV2();
f.setSize(900,900);//sets size of frame
// f.setLocation(100,50);//sets location of frame
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//sets default close operation
// f.setContentPane(new Panel()); //sets content pane
f.setVisible(true);//makes panel visible
f.add(t);
f.addMouseListener( new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
for (Shape shape: circles) {
if (shape.contains(x,y)) {
counter++;
if(counter % 2 == 1) {
index1 = circles.indexOf(shape)+1;
if(Color.WHITE.equals(colors.get(index1))) {
counter--;
System.out.println("error");
}
}
else{
index2 = circles.indexOf(shape) +1;
if(checkLinearity(index1, index2) == true) {
//I want to be able to draw a rectange here
}
}
}
}
}
});
}
}

Your problem seems another example of why many people (including me) advocate to always add curly braces (even on 1-statement blocks - they might not stay that way):
Have a look at this snippet:
if (shape.contains(x,y))
counter++;
if(counter % 2 == 1) {
...
It doesn't do what you think, i.e. if you hit the shape counter get's increased but the rest of the checks is performed for every shape. That's because to the compiler the code looks like this:
if (shape.contains(x,y))
counter++;
if(counter % 2 == 1) {
...
Thus, when you hit a circle counter will be 1 and hence counter % 2 == 1 will be true for every circle being checked afterwards.
So, add curly braces around the indented code. Additionally you might want to break the loop once you've detected a hit since you're probably not interested in check all the other circles as well.
You might also want to introduce a class Circle which might look like this:
class Circle {
Shape shape;
Color color;
... //additional properties, e.g. position, and methods
public boolean wasHit(int x, int y) {
return shape.contains(x,y);
}
public boolean isValid() {
return Color.WHITE.equals( color );
}
}
Then you'd iterate over those like this:
for( Circle circle : circles ) {
if( circle.wasHit(x, y) {
if( circle.isValid() ) {
//do what you'd need here, e.g. change the shape to a rectangle
} else {
//error
}
//break the loop since other circles can't be hit or we're not interested in those
break;
}
}

Also, in addition to what Thomas mentioned (1+ to his answer) don't add your MouseListener to the JFrame. Add it instead to the JPanel that is doing the drawing, else your x and y positions will be off.
Myself, I would use a grid of JLabels, labels that hold icons. You can even give the labels row and column values as properties that can be retrieved. This would allow easy swapping of Icons if that is your goal, and would allow easy retrieval of which grid cell was pressed.
Some advangages:
If the JLabels are also placed into a List<JLabel> then they can be shuffled easily if you need random placement of colors
Error testing for white is easy. Simply get the icon from the selected JLabel and test for equality with the white icon: if (label.getIcon == whiteIcon).
Again, getting the row/column position of the selected disk is easy since it is an intrinsic property of the JLabel itself and can be retrieved by calling getClientProperty on the JLabel, for example: label1.getClientProperty(ROW);
If you need to check for two mouse presses in a row, save the first mouse press result into a field of the class, here a JLabel called label1.
For example:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.*;
public class DriverV2b extends JPanel {
private static final String ROW = "row";
private static final String COLUMN = "column";
private static final int DIM = 5;
private static final int ICON_WIDTH = 150;
private static final int CIRCLE_WIDTH = 100;
private JLabel[][] labelGrid = new JLabel[DIM][DIM];
private Icon whiteIcon = createIcon(Color.WHITE);
private Icon blueIcon = createIcon(Color.BLUE);
private Icon greenIcon = createIcon(Color.GREEN);
private Icon yellowIcon = createIcon(Color.YELLOW);
private Icon redIcon = createIcon(Color.RED);
private Icon[] coloredIcons = {blueIcon, greenIcon, yellowIcon, redIcon};
private JLabel label1 = null;
public DriverV2b() {
// grid to hold the JLabels
setLayout(new GridLayout(DIM, DIM));
// mouse listener to add to all JLabels
MyMouse myMouse = new MyMouse();
// list of JLabels so we can shuffle them for random placement
List<JLabel> labels = new ArrayList<>();
for (int row = 0; row < labelGrid.length; row++) {
for (int col = 0; col < labelGrid[row].length; col++) {
// initially give all a white icon
labelGrid[row][col] = new JLabel(whiteIcon);
// add mouselistener
labelGrid[row][col].addMouseListener(myMouse);
// client property so we know what row and column the label is on
labelGrid[row][col].putClientProperty(ROW, row);
labelGrid[row][col].putClientProperty(COLUMN, col);
// add JLabel to the array list
labels.add(labelGrid[row][col]);
// add it to the GUI
add(labelGrid[row][col]);
}
}
// shuffle the array list of JLabels, so we can add colored icons randomly
Collections.shuffle(labels);
// for each colored icon in the array, add *two** to the list
for (int i = 0; i < coloredIcons.length; i++) {
int index = 2 * i;
labels.get(index).setIcon(coloredIcons[i]);
labels.get(index + 1).setIcon(coloredIcons[i]);
}
}
private Icon createIcon(Color color) {
int w = ICON_WIDTH;
BufferedImage img = new BufferedImage(w, w, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
// rendering hints used to smooth the image edges
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
int width = CIRCLE_WIDTH;
int x = (ICON_WIDTH - CIRCLE_WIDTH) / 2;
g2.fillOval(x, x, width, width);
g2.dispose();
return new ImageIcon(img);
}
private class MyMouse extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel label = (JLabel) e.getSource();
if (label.getIcon() == whiteIcon) {
System.out.println("error -- ignoring this press");
return;
}
if (label1 == null) {
// first press
label1 = label;
} else {
JLabel label2 = label;
int row1 = (int) label1.getClientProperty(ROW);
int col1 = (int) label1.getClientProperty(COLUMN);
int row2 = (int) label2.getClientProperty(ROW);
int col2 = (int) label2.getClientProperty(COLUMN);
Icon icon1 = label1.getIcon();
Icon icon2 = label2.getIcon();
// just to show that this works:
System.out.printf("Locaions: [%d, %d] [%d, %d]%n",
row1, col1, row2, col2);
if (checkLinearity(row1, col1, row2, col2)) {
// TODO: do something
}
label1 = null; // reset this
}
}
private boolean checkLinearity(int row1, int col1, int row2, int col2) {
// TODO finish this method
return false;
}
}
private static void createAndShowGui() {
DriverV2b mainPanel = new DriverV2b();
JFrame frame = new JFrame("DriverV2b");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
added error testing for whiteIcon press

Related

Jpanel 2d array of objects drawing problem

I am trying two draw a 2D array(dynamic) of random boxes with different colors,
this is the code:
Main.java
public class Main {
public static void main(String[] args) {
CustomPanel f = new CustomPanel (4, 5);
JFrame frame = new JFrame("Testing");
frame.add(f);
frame.setSize(1000, 1000);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
CustomPanel.java:
import javax.swing.*;
import java.awt.*;
import java.util.Random;
public class CustomPanel extends JPanel {
Drawable [][]boxes;
public CustomPanel (int rows, int cols)
{
this.setLayout(null);
boxes = new Drawable[rows][cols];
Random rand = new Random();
for(int i = 0 ; i < rows ; i ++)
{
for(int j = 0 ; j < cols ; j++)
{
switch(rand.nextInt(3))
{
case 0:
boxes [i][j] = new Box1();
break;
case 1:
boxes [i][j] = new Box2();
break;
case 2:
boxes [i][j] = new Box3();
break;
}
}
}
}
public void paintComponent (Graphics g) {
super.paintComponent(g);
Rectangle t = g.getClipBounds();
int box_width = t.width/ this.boxes[0].length;
int box_heigt = t.height/ this.boxes.length;
for(int i = 0 ; i < this.boxes.length; i ++)
{
for(int j = 0 ; j < this.boxes[0].length; j++)
{
System.out.println("" + i + ":" + j);
boxes [i][j].draw(i * box_width, j * box_heigt, box_width, box_heigt, g);
}
}
}
}
Drawable.java:
import java.awt.Graphics;
public interface Drawable {
public abstract void draw(int x, int y, int width, int height, Graphics g);
}
Box1(Box2, Box3 are the same, just different colors):
import java.awt.Color;
import java.awt.Graphics;
public class Box1 implements Drawable{
public Box1 () { //default constructor
}
#Override
public void draw(int x, int y, int width, int height, Graphics g) {
g.setColor(Color.CYAN);
g.fillRect(x, y, width, height);
}
}
The problem is that some of the boxes do not appear at the panel at all(altought I do iterate over both rows and columns).
I debugged it but could not find out why it happens(it might be silly - i know)
Box1(Box2, Box3 are the same, just different colors):
Don't create separate classes, just pass the Color as a parameter.
do you mean at paintComponent ? how? I guess this.getParent().getSize().width ?
Yes, paintComponent().
No, you don't get the parent. You are doing custom painting on a JPanel. You want the width/height of the panel using the methods I suggested in my comment.
The problem is that some of the boxes do not appear at the panel at all
You have your x/y values reversed when you paint each Box. The "i" variable represents the rows (or the y value) and the "j" variable represents the columns (or the x value).
So your logic should b:
for(int i = 0 ; i < this.boxes.length; i ++)
{
for(int j = 0 ; j < this.boxes[0].length; j++)
{
//boxes [i][j].draw(i * box_width, j * box_heigt, box_width, box_heigt, g);
boxes [i][j].draw(j * box_width, i * box_heigt, box_width, box_heigt, g);
}
}
instead of using the Array length property to control the rows/columns, why not just save the row/column parameters as variable in your class which might help make your code easier to read.

Setting a label text in Swing undos all button location movements

I have the weirdest bug ever.
I have this puzzle game that moves puzzle pieces (which really are buttons with images attached to them).
Everything worked fine until I tried to change the text of some label (to indicate how many steps the player has done).
Everytime I call someControl.setText("text");, the puzzle pieces that moved are set back to the their first position. I have no idea why, but they just do.
Here's my window:
It consists of two panels, each uses a GridBagLayout.
The main frame uses a gridBagLayout as well, which consists of the two panels.
I know it's weird as hell, but I can't figure out what may cause this GUI bug. Any idea?
The pieces of code:
increaseSteps which is called everytime I click a puzzle button
void increaseSteps() {
_steps++;
_lblSteps.setText("Steps: " + _steps);
}
Creation of the puzzle panel (the left panel)
private JPanel puzzlePanel() {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
for (int i = 0; i < _splitImage.getSize(); i++)
for (int j = 0; j < _splitImage.getSize(); j++) {
int valueAtPos = _board.getMatrix()[i][j];
if (valueAtPos == 0)
continue;
int imageRow = _board.getImageRowFromValue(valueAtPos);
int imageCol = _board.getImageColFromValue(valueAtPos);
ImageIcon imageIcon = new ImageIcon(_splitImage.getImages()[imageRow][imageCol]);
JButton btn = new JButton(imageIcon);
_tileButtons[i][j] = new TileButton(btn, i, j);
btn.setPreferredSize(new Dimension(_splitImage.getImages()[i][j].getWidth(null),
_splitImage.getImages()[i][j].getHeight(null)));
// add action listener
btn.addActionListener(this);
btn.addKeyListener(this);
gbc.gridx = j;
gbc.gridy = i;
panel.add(_tileButtons[i][j].getButton(), gbc);
}
return panel;
}
actionPerformed:
#Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JButton))
return;
JButton btn = (JButton) e.getSource();
TileButton tile = getTileButtonFromBtn(btn);
if (tile == null)
return;
// check if we can move the tile
String moveDir = _board.canMoveTile(tile.getRow(), tile.getCol());
if (moveDir.equals("no"))
return;
increaseSteps();
int dirx = 0;
int diry = 0;
if (moveDir.equals("left")) {
dirx = -1;
_board.move("left", true);
tile.setCol(tile.getCol() - 1);
} else if (moveDir.equals("right")) {
dirx = 1;
_board.move("right", true);
tile.setCol(tile.getCol() + 1);
} else if (moveDir.equals("up")) {
diry = -1;
_board.move("up", true);
tile.setRow(tile.getRow() - 1);
} else { // down
diry = 1;
_board.move("down", true);
tile.setRow(tile.getRow() + 1);
}
moveButton(btn, dirx, diry, MOVE_SPEED);
if (_board.hasWon())
win();
}
moveButton: (moves the button in a seperate thread, calling btn.setLocation())
private void moveButton(JButton btn, int dirx, int diry, int speed) {
Point loc = btn.getLocation();
// get start ticks, calculate distance etc...
StopWatch stopper = new StopWatch();
int distance;
if (dirx != 0)
distance = _splitImage.getImages()[0][0].getWidth(null) * dirx;
else
distance = _splitImage.getImages()[0][0].getHeight(null) * diry;
if (speed > 0) {
// run the animation in a new thread
Thread thread = new Thread() {
public void run() {
int currentTicks;
int elapsed;
do {
int newX = loc.x;
int newY = loc.y;
elapsed = stopper.getElapsed();
int moved = (int) ((double) distance * (double) (elapsed / (double) speed));
if (dirx != 0)
newX += moved;
else
newY += moved;
btn.setLocation(newX, newY);
} while (elapsed <= MOVE_SPEED);
// make sure the last location is exact
btn.setLocation(loc.x + (dirx == 0 ? 0 : distance), loc.y + (diry == 0 ? 0 : distance));
}
};
thread.start();
}
else
btn.setLocation(loc.x + (dirx == 0 ? 0 : distance), loc.y + (diry == 0 ? 0 : distance));
}
You're trying to set the absolute position of a component via setLocation(...) or setBounds(...), one that is held by a container that uses a layout manager. This may work temporarily, but will fail if the container's layout manager is triggered to re-do the layout of its contained components. When that happens, the GridBagConstraints will take over and the components will move to their gridbag constraints assigned location.
The solution is to not do this, and instead to place the location of your components in concert with the layout managers used.
Another problem is that your current code is not Swing thread-safe since you're making Swing state changes from within a background thread. This won't always cause problems, but since it's a threading issue, risks causing intermittent hard to debug problems (ones that usually only occur when your boss or instructor are trying to run your code).
Possible solutions:
For a grid of images, you could use a grid of JLabels (or JButtons if you must) held in a container that uses GridLayout. When you need to reposition components, remove all components held by that JPanel, and then re-add, using the order of addition to help you position the components.
Easiest though would be to use a grid of non-moving JLabels, give them MouseListeners, and instead of moving the JLabels, remove and add Icons to them, including a blank Icon.
If you need to do Swing animation, use a Swing Timer to drive the animation. This will allow your code to make repetitive calls with delay between the calls, and with these calls being made on the Swing event thread, the EDT (event dispatch thread).
Demo proof of concept example code that shows swapping icons, but without animation, and without test of solution yet:
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class ImageShuffle extends JPanel {
private static final int SIDES = 3;
public static final String IMG_PATH = "https://upload.wikimedia.org/wikipedia/commons/"
+ "thumb/5/5a/Hurricane_Kiko_Sep_3_1983_1915Z.jpg/"
+ "600px-Hurricane_Kiko_Sep_3_1983_1915Z.jpg";
private List<Icon> iconList = new ArrayList<>(); // shuffled icons
private List<Icon> solutionList = new ArrayList<>(); // in order
private List<JLabel> labelList = new ArrayList<>(); // holds JLabel grid
private Icon blankIcon;
public ImageShuffle(BufferedImage img) {
setLayout(new GridLayout(SIDES, SIDES, 1, 1));
fillIconList(img); // fill array list with icons and one blank one
Collections.shuffle(iconList);
MyMouseListener myMouse = new MyMouseListener();
for (Icon icon : iconList) {
JLabel label = new JLabel(icon);
label.addMouseListener(myMouse);
add(label);
labelList.add(label);
}
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel selectedLabel = (JLabel) e.getSource();
if (selectedLabel.getIcon() == blankIcon) {
return; // don't want to move the blank icon
}
// index variables to hold selected and blank JLabel's index location
int selectedIndex = -1;
int blankIndex = -1;
for (int i = 0; i < labelList.size(); i++) {
if (selectedLabel == labelList.get(i)) {
selectedIndex = i;
} else if (labelList.get(i).getIcon() == blankIcon) {
blankIndex = i;
}
}
// get row and column of selected JLabel
int row = selectedIndex / SIDES;
int col = selectedIndex % SIDES;
// get row and column of blank JLabel
int blankRow = blankIndex / SIDES;
int blankCol = blankIndex % SIDES;
if (isMoveValid(row, col, blankRow, blankCol)) {
Icon selectedIcon = selectedLabel.getIcon();
labelList.get(selectedIndex).setIcon(blankIcon);
labelList.get(blankIndex).setIcon(selectedIcon);
// test for win here by comparing icons held by labelList
// with the solutionList
}
}
private boolean isMoveValid(int row, int col, int blankRow, int blankCol) {
// has to be on either same row or same column
if (row != blankRow && col != blankCol) {
return false;
}
// if same row
if (row == blankRow) {
// then columns must be off by 1 -- they're next to each other
return Math.abs(col - blankCol) == 1;
} else {
// or else rows off by 1 -- above or below each other
return Math.abs(row - blankRow) == 1;
}
}
public void shuffle() {
Collections.shuffle(iconList);
for (int i = 0; i < labelList.size(); i++) {
labelList.get(i).setIcon(iconList.get(i));
}
}
}
private void fillIconList(BufferedImage img) {
// get the width and height of each individual icon
// which is 1/3 the image width and height
int w = img.getWidth() / SIDES;
int h = img.getHeight() / SIDES;
for (int row = 0; row < SIDES; row++) {
int y = (row * img.getWidth()) / SIDES;
for (int col = 0; col < SIDES; col++) {
int x = (col * img.getHeight()) / SIDES;
// create a sub image
BufferedImage subImg = img.getSubimage(x, y, w, h);
// create icon from the image
Icon icon = new ImageIcon(subImg);
// add to both icon lists
iconList.add(icon);
solutionList.add(icon);
}
}
// create a blank image and corresponding icon as well.
BufferedImage blankImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
blankIcon = new ImageIcon(blankImg);
iconList.remove(iconList.size() - 1); // remove last icon from list
iconList.add(blankIcon); // and swap in the blank one
solutionList.remove(iconList.size() - 1); // same for the solution list
solutionList.add(blankIcon);
}
private static void createAndShowGui(BufferedImage img) {
JFrame frame = new JFrame("ImageShuffle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ImageShuffle(img));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
URL imgUrl = null;
BufferedImage img;
try {
imgUrl = new URL(IMG_PATH);
img = ImageIO.read(imgUrl);
SwingUtilities.invokeLater(() -> createAndShowGui(img));
} catch (IOException e) {
e.printStackTrace();
}
}
}
If I wanted animation, again, I'd raise the icon into the JFrame's glasspane, animate it to the new position using a Swing Timer, and then place the icon into the new JLabel. I'd also disable the MouseListener using a boolean field, a "flag", until the animation had completed its move.

Which Swing layout should i use for moving JButtons

I have a Board 14x14 which has JButtons and every Jbutton has a different color. When you click one of those buttons, it checks the neighbors with the same color and removes them. When it removes them, theres a blank space between the board so the above buttons, should move down to fill the blank space. I tried with GridLayout but I don't know how to move the above buttons.
This actually is a case where you can hardly use a layout manager at all.
A LayoutManager is supposed to compute the layout of all components at once. It is triggered by certain events (e.g. when the parent component is resized). Then it computes the layout and arranges the child components accordingly.
In your case, the situation is quite different. There is no layout manager that can sensibly represent the "intermediate" state that appears while the upper buttons are falling down. While the components are animated, they cannot be part of a proper layout.
The animation itself may also be a bit tricky, but can fortunately be solved generically. But you still have to keep track of the information about where each component (i.e. each button) is currently located in the grid. When one button is removed, you have to compute the buttons that are affected by that (namely, the ones directly above it). These have to be animated. After the animation, you have to assign the new grid coordinates to these buttons.
The following is a MCVE that shows one basic approach. It simply removes the button that was clicked, but it should be easy to generalize it to remove other buttons, based on other conditions.
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class FallingButtons
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int rows = 8;
int cols = 8;
GridPanel gridPanel = new GridPanel(rows, cols);
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
JButton button = new JButton(r+","+c);
gridPanel.addComponentInGrid(r, c, button);
button.addActionListener(e ->
{
Point coordinates = gridPanel.getCoordinatesInGrid(button);
if (coordinates != null)
{
gridPanel.removeComponentInGrid(
coordinates.x, coordinates.y);
}
});
}
}
f.getContentPane().add(gridPanel);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class GridPanel extends JPanel
{
private final int rows;
private final int cols;
private final JComponent components[][];
GridPanel(int rows, int cols)
{
super(null);
this.rows = rows;
this.cols = cols;
this.components = new JComponent[rows][cols];
addComponentListener(new ComponentAdapter()
{
#Override
public void componentResized(ComponentEvent e)
{
layoutGrid();
}
});
}
private void layoutGrid()
{
int cellWidth = getWidth() / cols;
int cellHeight = getHeight() / rows;
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
JComponent component = components[r][c];
if (component != null)
{
component.setBounds(
c * cellWidth, r * cellHeight, cellWidth, cellHeight);
}
}
}
}
Point getCoordinatesInGrid(JComponent component)
{
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
if (components[r][c] == component)
{
return new Point(r, c);
}
}
}
return null;
}
void addComponentInGrid(int row, int col, JComponent component)
{
add(component);
components[row][col] = component;
layoutGrid();
}
JComponent getComponentInGrid(int row, int col)
{
return components[row][col];
}
void removeComponentInGrid(int row, int col)
{
remove(components[row][col]);
components[row][col] = null;
List<Runnable> animations = new ArrayList<Runnable>();
for (int r=row-1; r>=0; r--)
{
JComponent component = components[r][col];
if (component != null)
{
Runnable animation =
createAnimation(component, r, col, r + 1, col);
animations.add(animation);
}
}
for (Runnable animation : animations)
{
Thread t = new Thread(animation);
t.setDaemon(true);
t.start();
}
repaint();
}
private Runnable createAnimation(JComponent component,
int sourceRow, int sourceCol, int targetRow, int targetCol)
{
int cellWidth = getWidth() / cols;
int cellHeight = getHeight() / rows;
Rectangle sourceBounds = new Rectangle(
sourceCol * cellWidth, sourceRow * cellHeight,
cellWidth, cellHeight);
Rectangle targetBounds = new Rectangle(
targetCol * cellWidth, targetRow * cellHeight,
cellWidth, cellHeight);
Runnable movement = createAnimation(
component, sourceBounds, targetBounds);
return () ->
{
components[sourceRow][sourceCol] = null;
movement.run();
components[targetRow][targetCol] = component;
repaint();
};
}
private static Runnable createAnimation(JComponent component,
Rectangle sourceBounds, Rectangle targetBounds)
{
int delayMs = 10;
int steps = 20;
Runnable r = () ->
{
int x0 = sourceBounds.x;
int y0 = sourceBounds.y;
int w0 = sourceBounds.width;
int h0 = sourceBounds.height;
int x1 = targetBounds.x;
int y1 = targetBounds.y;
int w1 = targetBounds.width;
int h1 = targetBounds.height;
int dx = x1 - x0;
int dy = y1 - y0;
int dw = w1 - w0;
int dh = h1 - h0;
for (int i=0; i<steps; i++)
{
double alpha = (double)i / (steps - 1);
int x = (int)(x0 + dx * alpha);
int y = (int)(y0 + dy * alpha);
int w = (int)(w0 + dw * alpha);
int h = (int)(h0 + dh * alpha);
SwingUtilities.invokeLater(() ->
{
component.setBounds(x, y, w, h);
});
try
{
Thread.sleep(delayMs);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
SwingUtilities.invokeLater(() ->
{
component.setBounds(x1, y1, w1, h1);
});
};
return r;
}
}
You could try using a 2-dimensional array of JButtons
JButton[][] buttons = new JButton[14][14];
for (int i=0; i < buttons.length; i++) {
for (int j=0; j < buttons[i].length; j++) {
buttons[i][j] = new JButton("Button [" + i + "][" + j + "]");
}
}
// Then do whatever,remove,change color,check next element in array
// and compare colors etc
buttons[2][3].setText("changed text");
If you want the above buttons to take more space to fill the empty space when you remove a component well, this is not possible using GridLayout, but you can add some empty components like JLabels to fill the space.
You can add a component in a container at a specific index for this purpose, by using Container's add (Component comp, int index) method.
This code snippet will replace a button at a specified index (45, just for example) with a blank component in a panel which has a GridLayout set:
JPanel boardPanel = new JPanel (new GridLayout (14, 14));
// ... add your buttons ...
// This code could be invoked inside an ActionListener ...
boardPanel.remove (45);
boardPanel.add (new JLabel (""), 45);
boardPanel.revalidate ();
boardPanel.repaint ();
This way, the rest of the components will not move, and you will just see a blank space replacing your button.
You can achieve more: if you add the empty label at index = 0, all the buttons will move to the right (remember that the number of components should not change, else the components will resize and you could obtain bad behaviour), and so on, you can "move" a single component by simply removing it and adding it at a different index.
Another way to go would be to store a 2-dimensional array of objects representing your model logic (you can store color and all the stuff you need), and painting them on your own by overriding paintComponent method.
For an example of a custom painting approach, take a look at this MadProgrammer's answer, where he shows how to highlight a specific cell in a grid (in this case he uses a List to store objects, but a 2d array will work as well).

Calculate cell sizes and draw (with lines in between) them

I want to draw a grid and draw stuff in the cells (to keep things easy just fill them).
Overall I've got it pretty much working only in some panel sizes the cell is about 1 pixel off of where it should be placed (overlapping the line).
TBH I haven't really done enough calculating to possibly find the answer myself, so my apologies for that, I'm really not too sure how to approach this "bug" either though.
Anyway, here's the code:
public class Gui extends JFrame {
public static void main(String[] args) {
new Gui().setVisible(true);
}
public Gui() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
add(new JPanel() {
public static final int SIZE = 3;
/** Line thickness ratio to a block */
public static final float LINE_THICKNESS = 0.1f;
/** #return the width of a block. */
protected final int getBlockWidth() {
return getWidth() / SIZE;
}
/** #return the height of a block. */
protected final int getBlockHeight() {
return getHeight() / SIZE;
}
/** #return the width of a cell. */
protected final int getCellWidth() {
return (int) Math.ceil(getBlockWidth()*(1-LINE_THICKNESS));
}
/** #return the height of a cell. */
protected final int getCellHeight() {
return (int) Math.ceil(getBlockHeight()*(1-LINE_THICKNESS));
}
#Override
public void paintComponent(Graphics g) {
g.setColor(new Color(0, 0, 255, 100));
int lineWidth = (int) (LINE_THICKNESS * getBlockWidth());
int lineHeight = (int) (LINE_THICKNESS * getBlockHeight());
for(int i = 0; i <= SIZE; i++) {
g.fillRect(i * getBlockWidth() - lineWidth / 2, 0, lineWidth, getHeight());
g.fillRect(0, i * getBlockHeight() - lineHeight/2, getWidth(), lineHeight);
}
g.setColor(new Color(255, 0, 0, 100));
for(int i = 0; i < SIZE; i++) {
for(int j = 0; j < SIZE; j++) {
int x = j * getBlockWidth() + lineWidth/2;
int y = i * getBlockHeight() + lineHeight/2;
Graphics temp = g.create(x, y, getCellWidth(), getCellHeight());
drawCell(temp, i, j);
}
}
}
private void drawCell(Graphics g, int i, int j) {
g.fillRect(0, 0, getCellWidth(), getCellHeight());
}
});
setLocation(new Point(500, 200));
setSize(new Dimension(600, 600));
}
}
If you run it you'll probably see what I mean. I can't think of a good explanation in words. At first I thought I had to add + 1 to x and y since I want to draw next to the line, but this (obviously) just shifts the problem to the other side.
Running this with a SIZE bigger (like 30) gives me another bug that it gives open space to the sides. I know (or assume) this is because I'm using integers and it isn't too big of a deal though. But hints for a better approach (in general) are always welcome.
There are several ways you can fix this. I will not give you code, since I believe (based on how you asked your question) you are one of those who like to think and solve problems on their own.
First of all: first draw the background on the whole panel and then draw the lines. There'll be no while lines and the drawing will be slightly faster.
Second way: the order of drawing is important. You can safely draw the background first (even if it overlaps) and then overwrite it with borders.
Third way: do not use ints. Use floats or doubles. All your trouble will go away.
Fourth way: calculate the remainder. You can predict when the lines are drawn and when not, think about it. Predict it and draw appropriately.
Hi I had your same problem but the solution I implemented is inspired by the sample available from the Java Tutorial for drawing multiline text and draws the text on the cell using the text APIs.
http://java.sun.com/docs/books/tutorial/2d/text/drawmulstring.html
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextLayout;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.BreakIterator;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
public class MultilineTableCell
implements TableCellRenderer {
class CellArea extends DefaultTableCellRenderer {
/**
*
*/
private static final long serialVersionUID = 1L;
private String text;
protected int rowIndex;
protected int columnIndex;
protected JTable table;
protected Font font;
private int paragraphStart,paragraphEnd;
private LineBreakMeasurer lineMeasurer;
public CellArea(String s, JTable tab, int row, int column,boolean isSelected) {
text = s;
rowIndex = row;
columnIndex = column;
table = tab;
font = table.getFont();
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
}
}
public void paintComponent(Graphics gr) {
super.paintComponent(gr);
if ( text != null && !text.isEmpty() ) {
Graphics2D g = (Graphics2D) gr;
if (lineMeasurer == null) {
AttributedCharacterIterator paragraph = new AttributedString(text).getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
FontRenderContext frc = g.getFontRenderContext();
lineMeasurer = new LineBreakMeasurer(paragraph,BreakIterator.getWordInstance(), frc);
}
float breakWidth = (float)table.getColumnModel().getColumn(columnIndex).getWidth();
float drawPosY = 0;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: this won't occur for the English text in this sample.
// Note: drawPosX is always where the LEFT of the text is placed.
float drawPosX = layout.isLeftToRight()
? 0 : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(g, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
table.setRowHeight(rowIndex,(int) drawPosY);
}
}
}
public Component getTableCellRendererComponent(
JTable table, Object value,boolean isSelected, boolean hasFocus, int row,int column
)
{
CellArea area = new CellArea(value.toString(),table,row,column,isSelected);
return area;
}
}
It resizes row heigth too but it does it well only when this renderer is used for a single column.
And this is the way I used to invoke it for render my table.
final int wordWrapColumnIndex = ...;
myTable = new JTable() {
public TableCellRenderer getCellRenderer(int row, int column) {
if (column == wordWrapColumnIndex ) {
return wordWrapRenderer;
}
else {
return super.getCellRenderer(row, column);
}
}
};

Creating a Checkers Board in Java

I'm begining a little project to create a simple checkers game. However it's been a long time since I've used the java GUI tools. The goal of the code at this point is to draw the initial board (red pieces at top, black at bottom). However all I get when I run the code is a blank frame. I'm also a little uncertain if my circle drawing code will do what I want (ie create solid red or black circles inside certain squares) Here is the code. Thanks in advance for any help/suggestions
EDIT: I should probably alternate drawing blue and gray squares or else the thing will probably just be a giant blue blob, however I'll settle for a giant blue blob at this point :p
import javax.swing.*;
import java.awt.*;
public class CheckersServer
{
public static class Board
{
private JFrame frame = new JFrame();
private JPanel backBoard = new JPanel();
Board()
{
frame.setSize(905,905);
backBoard.setSize(900,900);
frame.setTitle("Checkers");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
backBoard.setVisible(true);
boardSquare bs;
String type = null;
//Filling in Red Side
for (int i = 0; i <=1; i++)
{
for(int j = 0; j < 9; j++)
{
if(j % 2 == 0)
{
type = "Red";
}
else
{
type = "Blank";
}
bs = new boardSquare(100*j,100*i,type);
backBoard.add(bs);
}
}
//Filling in empty middle
type = "Blank";
for (int i = 2; i < 7; i++)
{
for(int j = 0; j < 9; j++)
{
bs = new boardSquare(100*j,100*i,type);
backBoard.add(bs);
}
}
//Filling in Black side
for (int i = 7; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
if(j % 2 != 0)
{
type = "Black";
}
else
{
type = "Blank";
}
bs = new boardSquare(100*j,100*i,type);
backBoard.add(bs);
}
}
backBoard.repaint();
frame.add(backBoard);
frame.repaint();
}
private class boardSquare extends JComponent
{
private int x; //x position of the rectangle measured from top left corner
private int y; //y position of the rectangle measured from top left corner
private boolean isBlack = false;
private boolean isRed = false;
public boardSquare(int p, int q, String type)
{
x = p;
y = q;
if (type.equals("Black"))
{
isBlack = true;
isRed = false;
}
else if (type.equals("Red"))
{
isRed = true;
isBlack = false;
}
else if (type.equals("Blank"))
{
isBlack = false;
isRed = false;
}
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
Rectangle box = new Rectangle(x,y,100,100);
g2.draw(box);
g2.setPaint(Color.BLUE);
g2.fill(box);
if(isBlack)
{
g2.fillOval(x, y,100 ,100 );
g2.setColor(Color.black);
g2.drawOval(x, y, 100, 100);
}
else if(isRed)
{
g2.fillOval(x, y,100 ,100 );
g2.setColor(Color.red);
g2.drawOval(x, y, 100, 100);
}
}
}
}
public static void main(String[] args)
{
Board game = new Board();
}
}
You have several issues.
Java UI is layout-based, which means that when you add a component to a parent, the parent's layout determines where the child component will be placed. You don't have any code to set up the layout, and so your application is using the defaults (FlowLayout is the default, and this may work in your case, as long as your JFrame and children are the appropriate size).
The bigger problems are in your boardSquare class. By default, JPanels have a dimension of 10x10. You aren't specifying the size, and so all your squares are 10x10. You need to tell the squares how big they are. You can do this in the boardSquare constructor:
setPreferredSize(new Dimension(100, 100));
Finally, in your drawing code, you are doing an offset of x,y when drawing the squares and circles. This is an offset from the top-left corner of the component. Your components (after setting the size) will be 100x100 pixels. But if your x,y are greater than these values, you will be drawing outside of the bounds of the component. Instead, these values should be set to 0,0 because that is the top-left corner of the component you are drawing in.
By just setting the preferred size of the squares and setting x,y to 0, I was able to get the squares drawing in the frame, though it wasn't pretty. You will need to work on setting the correct layout before it will be laid out correctly.
Here are some hints:
Your BoardSquares have dimension 0x0. Not a good size for something you want to be visible to the user.
To help visualize what's going on, cause each BoardSquare to be 100x100 pixels in size, and give them a border. Now you can see where they are showing up in your GUI. Your GUI code still needs significant changes, but this will at least let you start seeing what you're dealing with.
public BoardSquare(int p, int q, String type)
{
this.setBorder(new LineBorder(Color.CYAN, 2));
this.setPreferredSize(new Dimension(100, 100));
// ... etc ...
BoardSquare seems to be coded to draw its contents based on coordinates from the absolute topmost leftmost point in the window, but they should be coded to draw themselves from the topmost leftmost point of the BoardSquare itself. That is, components should only draw within their own boundaries, and they should use coordinates that assume 0,0 designates the top,left of the component, not of the window.
If you want to use BoardSquares (JComponents) and add them to the frame, you probably should use a different layout manager, like GridLayout. FlowLayout won't give you the kind of precise positioning you want.
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
public class CheckersServer2
{
public static String type_BLANK = "BLANK";
public static String type_RED = "RED";
public static String type_BLACK = "BLACK";
public static int width = 100;
public static int height = 100;
public static class Board
{
private JFrame frame = new JFrame();
private JPanel backBoard = new JPanel();
Board()
{
int numRows = 8;
int numCols = 8;
frame.setSize(905,905);
backBoard.setSize(900,900);
frame.setTitle("Checkers");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
backBoard.setVisible(true);
String type;
for(int r=0; r<numRows; r++){
for(int c=0; c<numCols; c++){
//
type = type_BLANK;
if(c%2==0){
if(r==0 || r==2) {
type = type_RED;
}else if(r==6){
type = type_BLACK;
}
}else{
if(r==1){
type = type_RED;
} else if(r==5 || r==7) {
type = type_BLACK;
}
}
backBoard.add(new BoardSquare(r,c,type));
}
}
backBoard.repaint();
frame.add(backBoard);
frame.repaint();
}
private class BoardSquare extends JComponent
{
/**
*
*/
private static final long serialVersionUID = 1L;
private int x; //x position of the rectangle measured from top left corner
private int y; //y position of the rectangle measured from top left corner
private boolean isBlack = false;
private boolean isRed = false;
public BoardSquare(int p, int q, String type)
{
//this.setBorder(new LineBorder(Color.CYAN, 2));
this.setPreferredSize(new Dimension(width, height));
x = p;
y = q;
if (type.equals(type_BLACK))
{
isBlack = true;
isRed = false;
}
else if (type.equals(type_RED))
{
isRed = true;
isBlack = false;
}
else if (type.equals(type_BLANK))
{
isBlack = false;
isRed = false;
}
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
Rectangle box = new Rectangle(x,y,width,height);
g2.draw(box);
g2.setPaint(Color.BLUE);
g2.fill(box);
int ovalWidth = width - 15;
int ovalHeight = ovalWidth;
if(isBlack)
{
g2.setColor(Color.black);
g2.fillOval(x, y, ovalWidth, ovalHeight);
g2.drawOval(x, y, ovalWidth, ovalHeight);
}
else if(isRed)
{
g2.setColor(Color.red);
g2.fillOval(x, y, ovalWidth, ovalHeight);
g2.drawOval(x, y, ovalWidth, ovalHeight);
}
}
}
}
public static void main(String[] args)
{
Board game = new Board();
}
}

Categories

Resources