Card Game Graphics: Updating Vector of Canvas Objects in a Panel - java

So I am creating the card game Gin Rumy. I am working on the graphics, specifically, creating a panel that updates so that it always displays the vector of Card objects in the player's hand. The Cards themselves are a component that extends the canvas class. What I am asking is how to create some function updatePanel() that updates the panel as the player's hand (the vector of Card objects) changes. All I can think of is to remove the old panel from the GUI frame, create a new panel, fill that new panel with the Cards in the player's hand, and then stick that new panel onto the GUI frame. I haven't even coded this because I worry that taking a component off of the GUI frame and then slapping it back on runs the risk of disrupting the layout, and my gut tells me that creating a new panel every time is just a bad idea. So I'm asking for a better plan.
You'll also notice that the selectButton's actionPerformed() method is incomplete. What I'm trying to do is remove the cards that have focus. I understand how focus and action events work, but what I don't understand is how to search through the panel and find the card's that are selected and remove them.
I'd greatly appreciate any help on on either the updatePanel() or selectButton's actionPerformed() method, or any other useful information about adding and removing objects from panels throughout the course of a program.
// GraphicGinRumy
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Vector;
import java.util.Random;
public class GraphicGinRumy extends GUIFrame {
protected Card card;
protected Button selectButton, newGameButton;
protected Panel controlPanel, playerPanel;
protected RandomCardDeck deck;
protected Vector<Card> playerHand;
//Pardon this weird indentation...the code didn't copy to stack overflow correctly
public GraphicGinRumy() {
super("Gin Rumy");
deck = new RandomCardDeck();
playerHand = new Vector<Card>();
MouseListener ml = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
Card selectedCard = (Card) e.getSource();
if (selectedCard.getFocused()) {
selectedCard.setFocused(false);
} else {
selectedCard.setFocused(true);
}
}
};
deck.shuffle();
rumyDeal();
//Player Panel
playerPanel = new Panel();
for (int c = 0; c < playerHand.size(); c++) {
playerPanel.add(playerHand.elementAt(c));
}
add(playerPanel, BorderLayout.CENTER);
//Control Panel
controlPanel = new Panel();
selectButton = new Button("Select");
selectButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
}
});
controlPanel.add(selectButton);
newGameButton = new Button("New Game");
newGameButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
deck.shuffle();
deck.deal();
updatePanel();
}
});
controlPanel.add(newGameButton);
add(controlPanel, BorderLayout.SOUTH);
setSize(500, 100);
setVisible(true);
//__________________________________________________________________
//Game Setup
//__________________________________________________________________
//Set card values:
//2 to 9 is worth 5 pts; 10 to K is worth 10 pts; A is worth 15 pts
for (int c = 0; c < deck.getNumCards(); c++) {
card = deck.getCard(c);
if (card.getFaceValue() < 10) {
card.setValue(5);
} else if (card.getFaceValue() < Card.ACE) {
card.setValue(10);
} else {
card.setValue(15);
}
}
}
public static void main(String args[]) {
GraphicGinRumy rumy = new GraphicGinRumy();
//rumy.play();
}
protected void rumyDeal() {
//Give player and CPU 7 cards and add 1 to the discardPile
Card card;
int numCards = 7;
for (int i = 0; i < numCards; i++) {
playerHand.add(deck.deal());
}
for (int i = 0; i < numCards; i++) {
card = deck.deal();
card.setVisible(true);
cpuHand.add(card);
}
discardPile.add(deck.deal());
}
protected void updatePanel() {
playerPanel = new Panel();
}
}

Related

Grid of jpanels (for connect four game) - paintComponent only works for top left panel

I'm trying to make a simple connect four GUI by using a grid of JPanels each of which paints a colored disk when the button below them is pressed and the panels under it are full. Before adding the game rules I'm trying to just make sure the buttons and display work properly. But it is not working - only the top left panel displays a disk (after pressing button 1 6 times). here is my code:
public class ConnectFourFrame extends JFrame {
private final JPanel gamePanelsPanel; // panel to hold the game panels
private final GamePanel[][] gamePanels; // a 2D array to hold the grid of panels to display the game disks
private final JPanel buttonsPanel; // panel to hold the buttons panels
private final JPanel gameButtonsPanel; // panel to hold the game buttons to add disk to a column
private final JButton[] gameButtons; // an array to hold the game buttons
private final JPanel clearButtonPanel; // panel to hold the clear button
private final JButton clearButton; // button to clear the game grid from disks
private enum Turn {RED_PLAYER, BLUE_PLAYER}; // keeps track of which players turn it is
private Turn turn;
// no argument constructor
public ConnectFourFrame() {
super("Connect Four");
this.setLayout(new BorderLayout());
//add panels to hold the game panel and the buttons
gamePanelsPanel = new JPanel();
add(gamePanelsPanel, BorderLayout.CENTER);
buttonsPanel = new JPanel();
buttonsPanel.setLayout(new BorderLayout());
add(buttonsPanel, BorderLayout.SOUTH);
//set up game panels
gamePanelsPanel.setLayout(new GridLayout(6,7,3,3));
gamePanelsPanel.setBackground(Color.BLACK);
gamePanels = new GamePanel[6][7];
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 7; j++) {
gamePanels[i][j] = new GamePanel(false, Color.WHITE);
gamePanelsPanel.add(gamePanels[i][j]);
}
}
//set up game and clear buttons
gameButtonsPanel = new JPanel();
gameButtonsPanel.setLayout(new GridLayout(1,7));
clearButtonPanel = new JPanel();
gameButtons = new JButton[7];
for (int i = 0; i < 7; i++) {
gameButtons[i] = new JButton("" + (i+1));
gameButtonsPanel.add(gameButtons[i]);
}
clearButton = new JButton("CLEAR");
clearButtonPanel.add(clearButton);
buttonsPanel.add(gameButtonsPanel, BorderLayout.NORTH);
buttonsPanel.add(clearButtonPanel, BorderLayout.SOUTH);
add(buttonsPanel, BorderLayout.SOUTH);
// register event handlers
ClearButtonHandler clearButtonHandler = new ClearButtonHandler();
clearButton.addActionListener(clearButtonHandler);
GameButtonHandler gameButtonHandler = new GameButtonHandler();
for (int i = 0; i < 7; i++) {
gameButtons[i].addActionListener(gameButtonHandler);
}
turn = Turn.RED_PLAYER; //set first turn to player1
}
// inner class for game button event handling
private class GameButtonHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// get the number of the pressed button
int pressedButtonNum = Integer.parseInt(((JButton) e.getSource()).getActionCommand());
// display disk in top empty panel of the column
for (int i = 5; i >= 0; i--) {
if (!gamePanels[i][pressedButtonNum - 1].isFull()) {
if (turn == Turn.RED_PLAYER) {
gamePanels[i][pressedButtonNum - 1].setDiskColor(Color.RED);
turn = Turn.BLUE_PLAYER;
}
else {
gamePanels[i][pressedButtonNum - 1].setDiskColor(Color.BLUE);
turn = Turn.RED_PLAYER;
}
gamePanels[i][pressedButtonNum - 1].setFull(true);
gamePanels[i][pressedButtonNum - 1].repaint();
return;
}
}
// if column is full display message to try again
JOptionPane.showMessageDialog(gamePanelsPanel, "Column " + pressedButtonNum + " is full. Try again.");
}
}
public class GamePanel extends JPanel{
private boolean isFull; // true if the panel has a disk in it. default is empty (false).
private Color diskColor; //color of disks. default is white (same as background)
public GamePanel(boolean isFull, Color diskColor) {
super();
this.isFull = isFull;
this.diskColor = diskColor;
}
public Color getDiskColor() {
return diskColor;
}
public void setDiskColor(Color diskColor) {
this.diskColor = diskColor;
}
public boolean isFull() {
return isFull;
}
public void setFull(boolean isFull) {
this.isFull = isFull;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
this.setBackground(Color.WHITE);
g.setColor(diskColor);
g.fillOval(this.getX() + this.getWidth()/4 , this.getY() + this.getHeight()/4, this.getWidth()/2, this.getHeight()/2);
}
}
The problem is right here...
g.fillOval(this.getX() + this.getWidth()/4 , this.getY() + this.getHeight()/4, this.getWidth()/2, this.getHeight()/2);
The Graphics context passed to your paintComponent method has already been translated by the components x/y position, meaning that the top/left corner of the component is always 0x0
g.fillOval(this.getWidth()/4 , this.getHeight()/4, this.getWidth()/2, this.getHeight()/2);
will probably work better
Also, calling this.setBackground(Color.WHITE); inside paintComponent is unadvisable, as it will setup a situation where by a new paint cycle will be scheduled, over and over again. Don't change the state of the UI from within a paint method

Every time I pressed Roll(JButton) it left a picture of roll button on

I'm trying to make a snakes and ladders game. It doesn't done yet(no swap turn, no use of ladder and snake) and have so many bug.
But My point is that
I found a problem that make me very curious(Picture Below). It about making a token move. My strategy is that I add a[10][10] array of JPanal(I named it class as Cell) on a big JPanel(I named it class as Board) whose I set its bg as a picture of snakes and ladders game from google and set the layout to gridlayout(10,10). And on every Cell there's one token which is hiding and will only reveal when press the roll button and the output point to that Cell.
This is where the problem happened.
Image of the program when execute
When I press roll button for sometimes
There's a button appear every time I press!(They are not clickable though.)
I know that my start point doesn't even on the left bottom square but where is all that jbutton came from!
This is my main class
public class Main extends JFrame {
TextField text = new TextField();
Dice dice = new Dice();
int tempi = -1, tempj = -1,sum =0;
//Main Method
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Main mPage = new Main();
mPage.setVisible(true);
}
});
}
//Constructor
public Main(){
super("Snakes and Ladders");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setSize(1280,768);
setLocation(400,150);
setLayout(new FlowLayout(FlowLayout.LEFT,30,100));
Board board = new Board();
getContentPane().add(board);
getContentPane().add(dice);
getContentPane().add(text);
//my problem is here.
dice.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int score = Dice.rollDice();
text.setText(String.valueOf(score));
if (tempi != -1 || tempj != -1){
board.cell[9-tempi][9-tempj].fade();
}
if (tempi == -1 && tempj == -1){
sum = sum + score - 1;
}
else sum = sum + score;
tempj = sum%10;
tempi = (sum - tempj)/10;
board.cell[9-tempi][9-tempj].reveal();
}
});
pack();
setMinimumSize(this.getSize());
}
}
This is Cell class
public class Cell extends JPanel implements Cloneable {
private Token pl1 = new Token();
//constructor
public Cell(){
setOpaque(true);
setBackground(new Color(0,0,0,0));
setLayout(new GridLayout(2,2));
this.fade();
this.add(pl1);
}
public void fade(){
pl1.setVisible(false);
}
public void reveal(){
pl1.setVisible(true);
}
}
This is Token class
public class Token extends JLabel {
private BufferedImage image = null;
public Token(){
try {
image = ImageIO.read(new File("C:\\Users\\myacc\\IdeaProjects\\Snakes and Ladders\\src\\Token.png"));
} catch (IOException e) {
e.printStackTrace();
}
Image player = image.getScaledInstance(20,20,Image.SCALE_SMOOTH);
this.setIcon(new ImageIcon(player));
}
}
setBackground(new Color(0,0,0,0));
Don't use backgrounds with transparency. Swing does not know how to paint transparent backgrounds properly.
For full transparency you just make the component non-opaque:
//setOpaque(true);
//setBackground(new Color(0,0,0,0));
setOpaque(false);
If you need semi-transparency, then you need to do custom painting yourself. Check out Background With Transparency for more information on this topic.
Also don't use a TextField. That is an AWT component. Use a JTextField which is the Swing component.

How to make Tetris blocks fall down every second (error)

Beginner Java developer. Trying to make a Tetris applet as part of my personal projects.
I'm at the point were I can draw tetris blocks onto the screen but I cannot make it vertically go downwards every second.
Code:
public class InitialScreen extends JApplet implements ActionListener {
public JPanel cards = new JPanel();
private JPanel introPanel = new JPanel();
public CardLayout c1 = new CardLayout();
public void init() {
initiateIntroScreen();
//game();
add(cards, BorderLayout.NORTH);
setSize(500, 100);
}
private void initiateIntroScreen() {
Frame title = (Frame)this.getParent().getParent();
cards.setLayout(c1);
JLabel centralWords = new JLabel("Click the following button options: 'Play' or 'Instructions'.");
JButton playBtn = new JButton("Play!");
JButton instructionsBtn = new JButton("Instructions!");
introPanel.add(centralWords);
introPanel.add(playBtn);
introPanel.add(instructionsBtn);
cards.add(introPanel,"1");
playBtn.addActionListener(this);
playBtn.addActionListener(new MainGame(cards,c1));
}
#Override
public void actionPerformed(ActionEvent e) {
setSize(300,410);
getContentPane().setBackground(Color.BLACK);
}
So this is the initial screen for the JApplet. Has two buttons. When you press the 'Play' button it goes to the Main Game Screen.
public class MainGame extends JApplet implements ActionListener {
private JPanel cards;
private CardLayout c1;
private JPanel gamePanel = new JPanel();
public MainGame(JPanel cards, CardLayout c1) {
this.c1 = c1;
this.cards = cards;
gamePanel.add(new Tetris_Block(new int[10][20]));
}
#Override
public void actionPerformed(ActionEvent e) {
JLabel scoreLbl = new JLabel("Score:");
gamePanel.add(scoreLbl);
cards.add(gamePanel,"game");
c1.show(cards,"game");
}
This is the game screen were Tetris is played. In the constructor it calls a Tetris Block.
public class Tetris_Block extends JComponent implements ActionListener {
static Color[] colors =
{darkGray, green, blue, red,
yellow, magenta, pink, cyan};
int[][] a;
int w, h;
static int horizontalPos, verticalPos = 0;
static int size = 20;
private int verticalPos1 = 1;
public Tetris_Block(int[][] a) {
this.a = a;
w = a.length;
h = a[0].length;
square_Block();
startTimer();
}
private void nextMove() {
verticalPos++;
verticalPos1++;
}
public void square_Block(){ //Horizontal || Vertical || Colour
//Horizontal never changes for this as I just want the blocks to go down.
a[0][verticalPos] = 3;
a[0][verticalPos1] = 3;
a[1][verticalPos] = 3;
a[1][verticalPos1] = 3;
}
#Override
public void actionPerformed(ActionEvent e) {
nextMove();
square_Block();
System.out.println(verticalPos);
}
public void startTimer(){
Timer timer = new Timer(1000,this);
timer.start();
}
public void paintComponent(Graphics g) {
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
g.setColor(colors[a[i][j]]);
g.fill3DRect(i * size, j * size,
size, size, true);
}
}
}
public Dimension getPreferredSize() {
return new Dimension(w * size, h * size);
}
My aim is to make the vertical position increment by 1 every second (So it goes down the window in second intervals.
I don't think the Timer function is the problem. When I print verticalPos it prints out the incremented value every second, however it's just displaying the new location onto the screen- that is the problem.
Image of window right now.
[img]http://i.imgur.com/au5fceO.png?1[/img]
Start by adding a call to repaint in you actionPerformed method of your Tetris_Block
#Override
public void actionPerformed(ActionEvent e) {
nextMove();
square_Block();
System.out.println(verticalPos);
// This is important
repaint();
}
This will schedule a paint event on the event queue which will eventually call your paintComponent method (indirectly)
This will get the block to start moving. The next problem you will have is you're not actually "removing" the block from it's previous position, so it will continue to bleed/grow down the screen
You could solve this by passing in the color of the block to square_Block, for example...
public void square_Block(int color) { //Horizontal || Vertical || Colour
//Horizontal never changes for this as I just want the blocks to go down.
a[0][verticalPos] = color;
a[0][verticalPos1] = color;
a[1][verticalPos] = color;
a[1][verticalPos1] = color;
}
And then "rest" the blocks of the current position, update the position and then set the new block colors;
#Override
public void actionPerformed(ActionEvent e) {
square_Block(0);
nextMove();
square_Block(3);
System.out.println(verticalPos);
repaint();
}
Your design here may be faulty. You need to have a game loop that runs in a separate thread. It has to be a separate thread from the main thread so the user can still click buttons. Once you have a loop in the separate thread you need to have a method that you call for every game tick. It's in that method that you update the coordinates of the blocks.
Game loop works like this:
1. Read state of the game and draw the blocks
2. Process user input.
3. Update game state
I know this is abstract but I hope it helps. Google about java games and game loops.

Jbutton event handler not working properly

rollDice.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
//generate roll
int roll = dice.getRoll();
addGameFeedMessage(players[1].getName() + " rolled " + roll);
//store the player's position before the roll
int currentPlayerPos = players[1].getPosition();
//update player's position
players[1].movePlayer(roll);
//move the player icon to the player's position
tiles[players[1].getPosition()].addIconCurrentPlayersPanel(players[1].getPlayerIcon());
//revalidate components
//tiles[currentPlayerPos].getCurrentPlayersPanel().revalidate();
//tiles[players[1].getPosition()].getCurrentPlayersPanel().revalidate();
//this loop was put in to see if doing revalidate() on all panels would make a difference but the problem is still there
for(int i = 0; i < tiles.length; i++)
{
tiles[i].getCurrentPlayersPanel().revalidate();
}
}
});
Can anyone explain why the for loop in the actionListener for roll dice doesn't seem to work yet the for loop just above it which is the exact same thing works? There's no actual error as such it just doesn't do anything, however it does get inside the actionListener and executes as we've put print statements etc in it.
Guessing its something strange about actionListeners but I'm not sure what.
Any assistance would be appreciated, thankyou.
You should call validate() after the for loop as follows:
for(int i = 0; i < getNumOfPlayers(); i++)
{
currentPlayersOnTile[5].add(players[i].getPlayerIcon());
}
validate();
For Example consider the code given below:
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
class DemoPanels extends JFrame
{
public void createAndShowGUI()
{
final JPanel panel = new JPanel();
getContentPane().add(panel);
JButton button = new JButton("Click");
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
for (int i = 0 ; i < 10 ; i++ )
{
JPanel panel1 = new JPanel();
panel1.add(new JButton(String.valueOf(i)));
panel.add(panel1);
}
validate();//comment this line and then compile and execute the code to see the effect
}
});
getContentPane().add(button,BorderLayout.SOUTH);
setVisible(true);
setSize(300,400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
DemoPanels dp = new DemoPanels();
dp.createAndShowGUI();
}
});
}
}
NOTE: comment the code validate() and then compile and execute it to see the effect.

Best way of Changing the background of JButtons

Right now i change the background color of a button by using
button.setBackground(Color.WHITE);
That being an example.
But when i have a massive grid out of jbuttons (1000+), just running a for loop to change every buttons background is very, very slow. You can see the grid slowly turning white, box by box. I really don't want this
Is there a better way of changing every JButton on the grid to the same color at the same time?
This is how i am making the grid, the numbers used are only for example...
grid = new JPanel(new GridLayout(64, 64, 0, 0));
That's 4096 buttons, takes about 30+ seconds to change every button to the same color.
Edit 1: I need the buttons to be clickable, like when i click a button it turns blue for example. when all of the buttons are clicked, change the color of every button to white. Right now i have that working fine, but it is just slow to change the color of every button.
Edit 2: this is how i am changing the buttons:
new javax.swing.Timer(300, new ActionListener() {
int counter = 0;
public void actionPerformed(ActionEvent e) {
if (counter >= counterMax) {
((Timer) e.getSource()).stop();
}
Color bckgrndColor = (counter % 2 == 0) ? flashColor : Color.white;
for (JButton button : gridButton) {
button.setBackground(bckgrndColor);
}
counter++;
}
}).start();
The fact that you see the boxes being repainted individually indicates that either double buffering is turned off, or that the paint code in the button UI makes use of paintImmediately().
I tested your setup with 64x64 JButtons, an made sure that all UI operations were executed in the EDT (Event Dispatch Thread). I can confirm the effect you saw, changing the background of all buttons took about 1200 ms, with every box repainted immediately.
You can bypass the immediate repaints by setting the grid to non-visible before, and to visible after you changed the backgrounds:
grid.setVisible(false);
for (Component comp : grid.getComponents()) {
comp.setBackground(color);
}
grid.setVisible(true);
This caused the grid to do only one repaint, and reduced the time to ~300ms (factor 4).
This is still too slow for frequent updates, so you're better off with a custom component which draws the grid, or a flyweight container (what trashgod suggested in the comment to your question) if you want allow the grid cells to be arbitrary components.
You can get a considerable benefit if only visible buttons need to be repainted. In the MVC approach shown below, each button listens to a model that defines it's current state. Updating the model is quite fast compared to repainting. Although startup takes a few seconds, I see updates taking < 10 ms. in the steady-state. It's not as scalable as the flyweight pattern used by JTable, illustrated here, but it may serve.
import java.awt.*;
import java.awt.event.*;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import javax.swing.*;
/** #see https://stackoverflow.com/questions/6117908 */
public class UpdateTest {
private static final int ROW = 64;
private static final int COL = 64;
private static final int MAX = COL * ROW;
private final DataModel model = new DataModel(MAX);
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new UpdateTest().create();
}
});
}
void create() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(ROW, COL));
for (int i = 0; i < MAX; i++) {
panel.add(new ViewPanel(model, i));
}
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long start = System.nanoTime();
model.update();
System.out.println(
(System.nanoTime() - start) / (1000 * 1000));
}
});
JFrame f = new JFrame("JTextTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(panel), BorderLayout.CENTER);
f.setPreferredSize(new Dimension(800, 600));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
timer.start();
}
private static class ViewPanel extends JPanel implements Observer {
private final JButton item = new JButton();
private DataModel model;
private int index;
public ViewPanel(DataModel model, int i) {
this.model = model;
this.index = i;
this.add(item);
item.setText(String.valueOf(i));
item.setOpaque(true);
item.setBackground(new Color(model.get(index)));
model.addObserver(this);
}
#Override
public void update(Observable o, Object arg) {
int value = model.get(index);
item.setBackground(new Color(value));
}
}
private static class DataModel extends Observable {
private final Random rnd = new Random();
private final int[] data;
public DataModel(int n) {
data = new int[n];
fillData();
}
public void update() {
fillData();
this.setChanged();
this.notifyObservers();
}
public int get(int i) {
return data[i];
}
private void fillData() {
for (int i = 0; i < data.length; i++) {
data[i] = rnd.nextInt();
}
}
}
}

Categories