I am trying to make a replica of the Game of Life using swing, I admit i have used code from some 1 else as i am trying to get my head around it and then proceed with my own implementation. I have some understanding of their code, yet i wanted to implements 2 additional features to their code. However i am finding that the way it is written is posing problems as i wanted to add a MouseListener(To make a cell come to life when clicked) and WindowListener(To make a start,pause and resume button).
I do understand how they work to some extent, yet i need your help to get my headaround it.
Here is the code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.Transient;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class ConwaysGameOfLife extends JPanel implements MouseListener{
private int[][] cellsGrid; // grid is the size of the 2d array
private static final Random rnd = new Random(); // make a new random generator
private int generationCounter; // counter for the generation
public ConwaysGameOfLife(int width, int height) {
this.cellsGrid = new int[width / 4][height / 4];// divides by 4 whatever the width and height set is
setupGrid();
}// new method for creating the game with input sizes for the size of the game window
/*The grid consists fully of cells, the grid size is divided by 4 to make the cells
* setupGrid makes the grid of cells
*
* */
private void setupGrid() {
for (int[] row : cellsGrid) {
for (int j = 0; j < row.length; j++) {
if (rnd.nextDouble() < 0.92)
continue;
row[j] = rnd.nextInt(2);
//
}
}
}
/*
* applies the rule to the existing cells changing their state depending on the position to neighbors set in the rules
* */
public void updateGrid() {
for (int i = 0; i < cellsGrid.length; i++) {
for (int j = 0; j < cellsGrid[i].length; j++) {
applyRule(i, j);
}
}
}
// Rules of game of life cells iterations
private void applyRule(int i, int j) {
int left = 0, right = 0, up = 0, down = 0;
int dUpperLeft = 0, dUpperRight = 0, dLowerLeft = 0, dLowerRight = 0;
//this shows the 8 possible neighbors in terms of position
if (j < cellsGrid.length - 1) {
right = cellsGrid[i][j + 1];
if(i>0)
dUpperRight = cellsGrid[i - 1][j + 1];
if (i < cellsGrid.length - 1)
dLowerRight = cellsGrid[i + 1][j + 1];
}
if (j > 0) {
left = cellsGrid[i][j - 1];
if (i > 0)
dUpperLeft = cellsGrid[i - 1][j - 1];
if (i< cellsGrid.length-1)
dLowerLeft = cellsGrid[i + 1][j - 1];
}
if (i > 0)
up = cellsGrid[i - 1][j];
if (i < cellsGrid.length - 1)
down = cellsGrid[i + 1][j];
int sum = left + right + up + down + dUpperLeft + dUpperRight
+ dLowerLeft
+ dLowerRight;
if (cellsGrid[i][j] == 1) {
if (sum < 2)
cellsGrid[i][j] = 0;
if (sum > 3)
cellsGrid[i][j] = 0;
}
else {
if (sum == 3)
cellsGrid[i][j] = 1;
}
}
#Override
#Transient
public Dimension getPreferredSize() {
return new Dimension(cellsGrid.length * 4, cellsGrid[0].length * 4);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Color gColor = g.getColor();
g.drawString("Generation: " + generationCounter++, 0, 10);
for (int i = 0; i < cellsGrid.length; i++) {
for (int j = 0; j < cellsGrid[i].length; j++) {
if (cellsGrid[i][j] == 1) {
g.setColor(Color.black); // change colour
g.fillRect(j * 8, i * 8, 8, 8); // change size of cells
}
}
}
g.setColor(gColor);
//paint the cells to a colour
}
public static void main(String[] args) {
final ConwaysGameOfLife c = new ConwaysGameOfLife(800, 800);
JFrame frame = new JFrame();
frame.getContentPane().add(c);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.setVisible(true);
JButton start=new JButton("START");
/* This method specifies the location and size
* of button. In method setBounds(x, y, width, height)
* x,y) are cordinates from the top left
* corner and remaining two arguments are the width
* and height of the button.
*/
start.setBounds(80,0,80,20);
//Adding button onto the frame
//frame.add(start);
new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
frame.add(start);
c.updateGrid();
c.repaint();
}
}).start();
}
#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
}
#Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
}
First of all, The button flickers, and only appears when hovered over with the mouse, still flickering. Then i want the iterations to begin after start button is pressed, and pause button to pause it, and resume(Have a decent idea how it would work, but not how to implement it with the structure of swing that is done in this code.)
Secondly, I wanted the cells to come to life when they are pressed with the mouse, But, i am unsure how to implement the mouseListener to do this.
I tried something like cellsGrid[i][j] = 1; when clicked by mouse but i get errors, which is due to my lack of understanding of the implementation of cellsGrid.
I am not expecting solutions to the problem, I would like some guidance to understand the Listeners better and maybe how to make this simpler to understand for me. Thank You :)
Your simulation has a model that is a grid of cells; it has a view that paints an 8 x 8 square to represent a cell in the grid. As suggested here, you can map model and view coordinates using linear interpolation. In particular, given the following proportions, you can cross-multiply and solve for the missing coordinate.
view.x : panelWidthInPixels :: model.x : modelXRange
view.y : panelHeightInPixels :: model.y : modelYRange
For reference, this complete example maps mouse coordinates to pixel coordinates in an image. A complete example of John Conway’s Game of Life in Java Swing is cited here.
Related
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.
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).
I have a Java Swing assignment with the following objectives:
When the program starts, it draws 20 unfilled circles, with radius and location of each determined at random.
If the perimeter line of a circle does NOT intersect any other circle, draw the outline of the circle in RED. If it does intersect at least one other circle, draw it in BLACK.
Add a JButton that, each time it is pressed, creates a new set of circles as described above.
I've completed objectives #1 and #3 above, but I'm stumped on objective #2.
Before I present the code, let me give my understanding of the math behind it. There are two ways a circle can NOT intersect another circle:
The circles are too far apart to share a perimeter point, i.e. the distance between their centers is greater than the sum of their radii (d > r1 + r2). Example.
One circle is completely inside another circle, and their perimeters do not touch, i.e. the distance between their centers is less than the difference between their radii (d < |r1 - r2|). Example.
What I've got so far:
To compare circles, they must be specified before they are drawn, so I used a for-loop to store 20 values in arrays for the center coordinates (int[] x, int[] y) and the radius (double[] radius).
Next, I used nested for-loops to iterate through the array and compare two circles, except when a circle is compared with itself (index j = index k). If the circles intersect, g.setColor(Color.RED). If not, g.setColor(Color.BLACK).
When I execute my code, the circles without any overlap are properly colored red. However, some of the overlapping circles are colored red as well. I assume that they were non-overlapping at the time they were drawn, but were intersected thereafter. How do I fix the code to account for this discrepancy in time? (Problem area located near the bottom, in IntersectingCircles class)
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.JButton;
public class ButtonFrame extends JFrame
{
private final JButton resetButton = new JButton("Reset");
public ButtonFrame()
{
super("Drawing Random Circles");
setLayout(new BorderLayout());
IntersectingCircles intersectingCircles = new IntersectingCircles();
this.add(intersectingCircles, BorderLayout.CENTER);
this.add(resetButton, BorderLayout.SOUTH);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(1400, 1400);
ButtonHandler handler = new ButtonHandler();
resetButton.addActionListener(handler);
}
private class ButtonHandler implements ActionListener
{
#Override
public void actionPerformed(ActionEvent event)
{
reset();
}
}
public static void main(String[] args)
{
ButtonFrame buttonFrame = new ButtonFrame();
buttonFrame.setVisible(true);
}
public void reset()
{
ButtonFrame buttonFrame = new ButtonFrame();
buttonFrame.setVisible(true);
}
}
class IntersectingCircles extends JPanel
{
private static final JButton resetButton = new JButton("Reset Circles");
private static final JFrame frame = new JFrame("Intersecting Circles");
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
this.setBackground(Color.WHITE);
int[] x = new int[20];
int[] y = new int[20];
int[] diameter = new int[20];
double[] radius = new double[20];
for (int i = 0; i < 20; i++)
{
int xCoord = (int)(Math.random() * 600);
int yCoord = (int)(Math.random() * 600);
int circleSize = (int)(Math.random() * 550);
x[i] = xCoord;
y[i] = yCoord;
diameter[i] = circleSize;
radius[i] = circleSize / 2.0;
}
for (int j = 0; j < 20; j++)
{
for (int k = 0; k < 20; k++)
{
if (k != j)
{
if (((Math.sqrt((x[k] - x[j]) * (x[k] - x[j]) + (y[k] - y[j])
* (y[k] - y[j]))) > (radius[j] + radius[k])) ||
((Math.sqrt((x[k] - x[j]) * (x[k] - x[j]) + (y[k] - y[j])
* (y[k] - y[j]))) < (Math.abs(radius[j] - radius[k]))))
g.setColor(Color.RED);
else
g.setColor(Color.BLACK);
g.drawOval(x[j], y[j], diameter[j], diameter[j]);
}
else
continue;
}
}
}
}
You have logic mistake in if statement inside the cycle - you can set black color then revert to red for some other pair circle. Possible solution draft:
for (int j = 0; j < 20; j++)
{
g.setColor(Color.RED); //set non-intersect state
for (int k = j + 1; k < 20; k++) //avoid excessive work
{
if (intersect test)
{
g.setColor(Color.BLACK);
break; //can stop here
};
g.drawOval(x[j], y[j], diameter[j], diameter[j]);
}
}
I am creating a 3D renderer in Java but I have a problem when trying to render the polygons with a solid color fill. It works perfectly fine but every so often it's tearing but I'm not sure whether it is because the algorithm is inefficient or if it's something else because it's only at the vertices's it is tearing. Here is a picture:
Wireframe:
You can see that near the vertices's or rather points of the polygons it tears. I'm storing the color of the pixels in a 2 dimensional array and then cycling through it and rendering them. It still tears even when I make the polygon's really small so I don't think it's a performance problem. I use the Bresham algorithm and store the pixels in a 2 dimensional array then in the polygon I get the pixels and make them into one big array which I cycle through down the y and then across the x until I hit a pixel. That is set as beginLine and then the last one is set as endLine. I then draw a line between the points.
public void render()
{
int tempPixels[][] = new int[(int) Math.max(vertex_1.getX(), Math.max(vertex_2.getX(), vertex_3.getX())) + 30][(int) Math.max(vertex_1.getY(), Math.max(vertex_2.getY(), vertex_3.getY())) + 30];
for (int x = 0; x < vector_1.getWidth(); x++)
{
for (int y = 0; y < vector_1.getHeight(); y++)
{
if (vector_1.getPixels()[x][y] == 1)
{
tempPixels[(int) (x + Math.min(vertex_1.getX(), vertex_2.getX()))][(int) (y + Math.min(vertex_1.getY(), vertex_2.getY()))] = 1;
}
}
}
for (int x = 0; x < vector_2.getWidth(); x++)
{
for (int y = 0; y < vector_2.getHeight(); y++)
{
if (vector_2.getPixels()[x][y] == 1)
{
tempPixels[(int) (x + Math.min(vertex_2.getX(), vertex_3.getX()))][(int) (y + Math.min(vertex_2.getY(), vertex_3.getY()))] = 1;
}
}
}
for (int x = 0; x < vector_3.getWidth(); x++)
{
for (int y = 0; y < vector_3.getHeight(); y++)
{
if (vector_3.getPixels()[x][y] == 1)
{
tempPixels[(int) (x + Math.min(vertex_3.getX(), vertex_1.getX()))][(int) (y + Math.min(vertex_3.getY(), vertex_1.getY()))] = 1;
}
}
}
for (int y = 0; y < (int) Math.max(vertex_1.getY(), Math.max(vertex_2.getY(), vertex_3.getY())) + 4; y++)
{
int beginLine = -1;
int endLine = -1;
for (int x = 0; x < (int) Math.max(vertex_1.getX(), Math.max(vertex_2.getX(), vertex_3.getX())) + 4; x++)
{
if (tempPixels[x][y] == 1)
{
if (beginLine == -1)
{
beginLine = x;
}
else
{
endLine = x;
}
}
}
for (int i = beginLine; i < endLine; i++)
{
pixels[i][y] = 1;
colors[i][y] = Color.PINK;
}
}
vector_1.render();
vector_2.render();
vector_3.render();
vertex_1.render();
vertex_2.render();
vertex_3.render();
}
So basically my questions are:
Is this algorithm inefficient, if so what would be a better way?
Why is it tearing near the vertices's only?
Out of the problem description one cannot conclude that the attached image does not show what you want. Technically, the pink zone could depict a set of triangles you have 'correctly' painted (i.e. exactly the way you intended to) :p You could mark the triangles that you intended to be in the image, as an update. I suspect there are 4 triangles, though there are more such possible combinations.
First of all, since the part that, for each y, determines the beginLine and endLine seems to be correct, you should probably iterate till endLine when drawing the associated vertical segment (and not till endLine-1).
But that is probably not the real problem. Try drawing one triangle at a time. If some triangles still render incorrectly also try to see what happens when you eliminate the last part (the one rendering the vectors and the vertices). Why this?! Considering your implementation, you expect just a segment on each y. The image you provided indicates that your implementation sometimes renders more than one segment. So the rendering of the vectors and the vertices might be incorrect, though rendering multiple 'not perfectly aligned' triangles could also cause it.
If this does not solve it either, there might be some small offset in between your triangles. Try to see why that is.
Related to efficiency, that is not at fault. Generally, efficiency and correctness are not related in this way.
EDIT
You should add endLine = x after beginLine = x. With your implementation if you only have one pixel on a vertical line you do not draw it (since endLine will stay -1). This is one way to correct this issue. Also check that beginLine is greater than -1 before starting drawing. And do not forget to iterate from beginLine to exactly endLine.
You can use the method fillPolygon.
Syntax
g.setColor(Color.*color you want*)
g.fillPolygon (new int[]{width Dimensions}, new int [] {Height Dimensions}, no. of co-ordinates);
Note: - The 1st Value is of right Co-Ordinate, 2nd is of mid point and the 3rd is of Left Co-Ordinate.
The final programming with class, variables and methods.
/*Import the following files: -*/
import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ComponentListener;
import java.awt.event.ComponentEvent;
import java.awt.Font;
public class Shapes extends JPanel
{
public Shapes()
{
this.addComponentListener(new ComponentListener(){
public void componentShown(ComponentEvent arg0) {
}
public void componentResized(ComponentEvent arg0) {
paintComponent(getGraphics());
}
public void componentMoved(ComponentEvent arg0) {
}
public void componentHidden(ComponentEvent arg0) {
// TODO Auto-generated method stub
}
});
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
this.setBackground(Color.MAGENTA);
g.setColor(Color.BLUE);
g.fillPolygon (new int[]{250,135,10}, new int [] {160,15,160}, 3);
g.setFont(new Font("TimesRoman", Font.PLAIN, 35));
g.setColor(Color.GREEN);
g.drawString("Triangle", 75, 120);
}
public static void main(String[] args)
{
Shapes obj = new Shapes();
JFrame frame = new JFrame("Shapes");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(obj);
frame.setSize(600, 500);
frame.setVisible(true);
}
}
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
issue with my main method in my connect4 game
Hi,
on my connect4 game, whenever i click on any square it places the oval on that specific square, how do i get it so that it places the oval on the lowest square in that column so that it can stack up?
package Game;
import java.util.ArrayList;
public class ConnectFourBoard {
// This constant is used to indicate the width and height, in cells,
// of the board. Since we want an 8x8 board, we'll set it to 8.
private static final int WIDTH = 8;
private static final int HEIGHT = 8;
private ConnectFourCell[][] currentPlayer;
// The current state the Othello board is defined with these two
// fields: a two-dimensional array holding the state of each cell,
// and a boolean value indicating whether it's the black player's
// turn (true) or the white player's turn (false).
private ConnectFourCell[][] cells;
private boolean isBlueTurn;
// Since the board is a model and it needs to notify views of changes,
// it will employ the standard "listener" mechanism of tracking a list
// of listeners and firing "events" (by calling methods on those
// listeners) when things change.
private ArrayList<ConnectFourListener> listeners;
public ConnectFourBoard()
{
// Arrays, in Java, are objects, which means that variables of
// array types (like cells, which has type OthelloCell[][]) are
// really references that say where an array lives. By default,
// references point to null. So we'll need to create an actual
// two-dimensional array for "cells" to point to.
cells = new ConnectFourCell[WIDTH][HEIGHT];
listeners = new ArrayList<ConnectFourListener>();
reset();
isBlueTurn = true;
}
public void reset(){
for (int i = 0; i<WIDTH ; i++){
for (int j = 0; j<HEIGHT; j++){
cells[i][j] = ConnectFourCell.NONE;
}
}
isBlueTurn = true;
}
public void addConnectFourListener(ConnectFourListener listener)
{
listeners.add(listener);
}
public void removeConnectFourListener(ConnectFourListener listener)
{
if (listeners.contains(listener))
{
listeners.remove(listener);
}
}
// These are fairly standard "fire event" methods that we've been building
// all quarter, one corresponding to each method in the listener interface.
private void fireBoardChanged()
{
for (ConnectFourListener listener : listeners)
{
listener.boardChanged();
}
}
private void fireGameOver()
{
for (ConnectFourListener listener : listeners)
{
listener.gameOver();
}
}
// isBlackTurn() returns true if it's the black player's turn, and false
// if it's the white player's turn.
public boolean isBlueTurn()
{
return isBlueTurn;
}
public int getWidth()
{
return WIDTH;
}
public int getHeight(){
return HEIGHT;
}
// getBlackScore() calculates the score for the black player.
public int getBlackScore()
{
return getScore(ConnectFourCell.BLUE);
}
// getWhiteScore() calculates the score for the white player.
public int getWhiteScore()
{
return getScore(ConnectFourCell.RED);
}
// getScore() runs through all the cells on the board and counts the
// number of cells that have a particular value (e.g., BLACK or WHITE).
// This method uses the naive approach of counting them each time
// it's called; it might be better to keep track of this score as we
// go, updating it as tiles are added and flipped.
private int getScore(ConnectFourCell cellValue)
{
int score = 0;
for (int i = 0; i < WIDTH; i++)
{
for (int j = 0; j < HEIGHT; j++)
{
if (cells[i][j] == cellValue)
{
score++;
}
}
}
return score;
}
// getWhiteScore() calculates the score for the white player.
public int getRedScore()
{
return getScore(ConnectFourCell.RED);
}
public int getBlueScore() {
// TODO Auto-generated method stub
return getScore(ConnectFourCell.BLUE);
}
public ConnectFourCell getCell(int x, int y)
{
if (!isValidCell(x, y))
{
throw new IllegalArgumentException(
"(" + x + ", " + y + ") is not a valid cell");
}
return cells[x][y];
}
/**
* The drop method.
*
* Drop a checker into the specified HEIGHT,
* and return the WIDTH that the checker lands on.
*/
int drop(int HEIGHT) {
if (hasWon()) {
return -1;
}
for ( ; WIDTH<6 && HEIGHT != 0; WIDTH++) { };
if (WIDTH==6) {
// if the WIDTH is 6, it went through all 6 WIDTHs
// of the cells, and couldn't find an empty one.
// Therefore, return false to indicate that this
// drop operation failed.
return -1;
}
// fill the WIDTH of that HEIGHT with a checker.
cells[HEIGHT][WIDTH] = currentPlayer[HEIGHT][WIDTH];
// alternate the players
//currentPlayer = (currentPlayer%2)+1;
return WIDTH;
}
/**
* The toString method
*
* Returns a String representation of this
* Connect Four (TM) game.
*/
public String toString() {
String returnString = "";
for (int WIDTH=5; WIDTH>=0; WIDTH--) {
for (int HEIGHT=0; HEIGHT<7; HEIGHT++) {
returnString = returnString + cells[HEIGHT][WIDTH];
}
returnString = returnString + "\n";
}
return returnString;
}
/**
* The hasWon method.
*
* This method returns true if one of the
* players has won the game.
*/
public boolean hasWon()
{
// First, we'll establish who the current player and the opponent is.
//ConnectFourCell
ConnectFourCell myColor =
isBlueTurn() ? ConnectFourCell.BLUE : ConnectFourCell.RED;
ConnectFourCell otherColor =
isBlueTurn() ? ConnectFourCell.RED : ConnectFourCell.BLUE;
return true;
}
public void validMove( ){
}
public void makeAMove(int x, int y){
//System.out.println(x+" "+y);
// cells[x][y] = ConnectFourCell.BLUE;
//Check who's turn it is. Set to that color.
ConnectFourCell myColor = null;
if ( isBlueTurn == true){
myColor = myColor.BLUE;
}
else {
myColor = myColor.RED;
}
cells[x][y] = myColor;
//Check if it's a valid move. If there is a piece there. can't
// Look at the column. play piece in the highest available slot
//Check if there are 4 in a row.
for (int WIDTH=0; WIDTH<6; WIDTH++) {
for (int HEIGHT=0; HEIGHT<4; HEIGHT++) {
if (!(cells[HEIGHT][WIDTH] == ConnectFourCell.NONE) &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+1][WIDTH] &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+2][WIDTH] &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+3][WIDTH]) {
}
}
}
// check for a vertical win
for (int WIDTH=0; WIDTH<3; WIDTH++) {
for (int HEIGHT=0; HEIGHT<7; HEIGHT++) {
if (!(cells[HEIGHT][WIDTH] == ConnectFourCell.NONE) &&
cells[HEIGHT][WIDTH] == cells[HEIGHT][WIDTH+1] &&
cells[HEIGHT][WIDTH] == cells[HEIGHT][WIDTH+2] &&
cells[HEIGHT][WIDTH] == cells[HEIGHT][WIDTH+3]) {
}
}
}
// check for a diagonal win (positive slope)
for (int WIDTH=0; WIDTH<3; WIDTH++) {
for (int HEIGHT=0; HEIGHT<4; HEIGHT++) {
if (!(cells[HEIGHT][WIDTH] == ConnectFourCell.NONE) &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+1][WIDTH+1] &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+2][WIDTH+2] &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+3][WIDTH+3]) {
}
}
}
// check for a diagonal win (negative slope)
for (int WIDTH=3; WIDTH<6; WIDTH++) {
for (int HEIGHT=0; HEIGHT<4; HEIGHT++) {
if (!(cells[HEIGHT][WIDTH] == ConnectFourCell.NONE) &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+1][WIDTH-1] &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+2][WIDTH-2] &&
cells[HEIGHT][WIDTH] == cells[HEIGHT+3][WIDTH-3]) {
}
}
}
fireBoardChanged();
isBlueTurn = !isBlueTurn;
}
private boolean isValidCell(int x, int y)
{
return x >= 0 && x < WIDTH
&& x>= 0 && x<HEIGHT
&& y >= 0 && y < WIDTH
&& y>= 0 && y<HEIGHT;
}
}
package UI;
import java.awt.*;
import javax.swing.*;
import UI.ConnectFourBoardPanel;
import Game.ConnectFourBoard;
import Game.ConnectFourListener;
public class ConnectFourFrame extends JFrame implements ConnectFourListener {
// Variables
private ConnectFourBoard board;
private JLabel scoreLabel;
private ConnectFourBoardPanel boardPanel;
private JLabel statusLabel;
public ConnectFourFrame()
{
// The frame builds its own model.
board = new ConnectFourBoard();
// We want the frame to receive notifications from the board as its
// state changes.
System.out.println(this);
board.addConnectFourListener(this);
setTitle("Informatics 45 Spring 2011: ConnectFour Game");
setSize(700, 700);
setResizable(true);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
getContentPane().setBackground(Color.BLACK);
buildUI();
refreshUI();
}
private void buildUI()
{
GridBagLayout layout = new GridBagLayout();
getContentPane().setLayout(layout);
Font labelFont = new Font("SansSerif", Font.BOLD, 18);
scoreLabel = new JLabel();
scoreLabel.setForeground(Color.WHITE);
scoreLabel.setFont(labelFont);
layout.setConstraints(
scoreLabel,
new GridBagConstraints(
0, 0, 1, 1, 1.0, 0.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets(10, 10, 10, 10), 0, 0));
getContentPane().add(scoreLabel);
boardPanel = new ConnectFourBoardPanel(board);
layout.setConstraints(
boardPanel,
new GridBagConstraints(
0, 1, 1, 1, 1.0, 1.0,
GridBagConstraints.CENTER,
GridBagConstraints.BOTH,
new Insets(10, 10, 10, 10), 0, 0));
getContentPane().add(boardPanel);
statusLabel = new JLabel();
statusLabel.setForeground(Color.WHITE);
statusLabel.setFont(labelFont);
layout.setConstraints(
statusLabel,
new GridBagConstraints(
0, 2, 1, 1, 1.0, 0.0,
GridBagConstraints.CENTER,
GridBagConstraints.NONE,
new Insets(10, 10, 10, 10), 0, 0));
getContentPane().add(statusLabel);
}
private void refreshUI()
{
// Refreshing the UI means to change the text in each of the
// two labels (the score and the status) and also to ask the
// board to repaint itself.
scoreLabel.setText(
"Blue: " + board.getBlueScore() +
" Red: " + board.getRedScore());
if ( board.isBlueTurn() == false){
statusLabel.setText("Blue's Turn: ");
}
if ( board.isBlueTurn() == true){
statusLabel.setText("Red's Turn: ");
}
boardPanel.repaint();
}
// These are the ConnectFourBoardListener event-handling methods.
public void boardChanged()
{
// When the board changes, we'll refresh the entire UI. (There
// are times when this approach is too inefficient, but it will
// work fine for our relatively simple UI.)
refreshUI();
}
public void gameOver()
{
// When the game is over, we'll pop up a message box showing the final
// score, then, after the user dismisses the message box, dispose of
// this window and end the program.
JOptionPane.showMessageDialog(
this,
"Game over!\nFinal score: " + scoreLabel.getText(),
"Game Over",
JOptionPane.INFORMATION_MESSAGE);
dispose();
}
}
Welcome to StackOverflow. You're question is fine - but pasting your whole program, in general, is a bad idea. You should describe your program, and include the snippet where you calculate how the ovals are filled in.
Having said that, here's a crack at the answer :
- You want to look at the tile thats currently selected, and if the tile underneath it is occupied, fill in that cell. If its not occupied, set the current tile to the underneath tile, and repeat. You want to do this until you get to the bottom row.
The answer above doesn't incude checking if the selected tile is already occupied, but I'm sure you can easily figure that out.