I've got problem with JFrame and KeyListerner in Tetris game. I've got two frames - one with Start Button and second with Board. In the second frame I want to control shapes from keyboard, but when I click start I can't do that. When I disactivate first frame - everything is ok. I know I have to focus on second frame and I tried do that, but with no effect. Could someone help me?
First frame:
package tetris;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Tetris implements ActionListener {
public Tetris() {
initComponents();
}
Component contentPane;
JLabel statusbar = new JLabel(" 0");
JLabel name = new JLabel("Tetris");
JButton startbut = new JButton("Start");
JFrame window = new JFrame();
JPanel panelStart = new JPanel();
JPanel game = new JPanel();
int nameSize=24;
Font fontName = new Font("Dialog", Font.BOLD, nameSize);
BorderLayout borderLayout = new BorderLayout();
Board board = new Board(this);
public void initComponents() {
window.setBounds(500,200,200,400);
window.setTitle("Tetris");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
window.add(panelStart);
window.add(game);
panelStart.setLayout (null);
panelStart.setVisible(true);
window.setContentPane(panelStart);
name.setSize(70,25);
name.setLocation(53,10);
name.setFont(fontName);
panelStart.add(name);
startbut.setSize(70,25);
startbut.setLocation(50,80);
panelStart.add(startbut);
startbut.addActionListener(this);
}
public void initGame() {
game.setLayout (borderLayout);
panelStart.setVisible(false);
window.remove(panelStart);
game.setVisible(true);
window.setContentPane(game);
game.setFocusable(true);
statusbar = new JLabel(" 0");
game.add(statusbar, BorderLayout.SOUTH);
game.add(board);
board.start();
}
public JLabel getStatusBar() {
return statusbar;
}
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if(e.getSource() == startbut)
{
initGame();
}
}
}
Board code:
package tetris;
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.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import tetris.Shape.Tetrominoes;
public class Board extends JPanel implements ActionListener {
final int BoardWidth = 10;
final int BoardHeight = 22;
Timer timer;
boolean isFallingFinished = false;
boolean isStarted = false;
boolean isPaused = false;
int numLinesRemoved = 0;
int curX = 0;
int curY = 0;
JLabel statusbar;
Shape curPiece;
Tetrominoes[] board;
public Board(Tetris parent) {
// setFocusable(true);
curPiece = new Shape();
timer = new Timer(400, this);
timer.start();
statusbar = parent.getStatusBar();
board = new Tetrominoes[BoardWidth * BoardHeight];
KeyListener keyListener = new TAdapter();
addKeyListener(keyListener);
repaint();
clearBoard();
}
public void actionPerformed(ActionEvent e) {
if (isFallingFinished) {
isFallingFinished = false;
newPiece();
} else {
oneLineDown();
}
}
int squareWidth() { return (int) getSize().getWidth() / BoardWidth; }
int squareHeight() { return (int) getSize().getHeight() / BoardHeight; }
Tetrominoes shapeAt(int x, int y) { return board[(y * BoardWidth) + x]; }
public void start()
{
if (isPaused)
return;
isStarted = true;
isFallingFinished = false;
numLinesRemoved = 0;
clearBoard();
newPiece();
timer.start();
}
private void pause()
{
if (!isStarted)
return;
isPaused = !isPaused;
if (isPaused) {
timer.stop();
statusbar.setText("paused");
} else {
timer.start();
statusbar.setText(String.valueOf(numLinesRemoved));
}
repaint();
}
public void paint(Graphics g)
{
super.paint(g);
Dimension size = getSize();
int boardTop = (int) size.getHeight() - BoardHeight * squareHeight();
for (int i = 0; i < BoardHeight; ++i) {
for (int j = 0; j < BoardWidth; ++j) {
Tetrominoes shape = shapeAt(j, BoardHeight - i - 1);
if (shape != Tetrominoes.NoShape)
drawSquare(g, 0 + j * squareWidth(),
boardTop + i * squareHeight(), shape);
}
}
if (curPiece.getShape() != Tetrominoes.NoShape) {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
drawSquare(g, 0 + x * squareWidth(),
boardTop + (BoardHeight - y - 1) * squareHeight(),
curPiece.getShape());
}
}
}
private void dropDown()
{
int newY = curY;
while (newY > 0) {
if (!tryMove(curPiece, curX, newY - 1))
break;
--newY;
}
pieceDropped();
}
private void oneLineDown()
{
if (!tryMove(curPiece, curX, curY - 1))
pieceDropped();
}
private void clearBoard()
{
for (int i = 0; i < BoardHeight * BoardWidth; ++i)
board[i] = Tetrominoes.NoShape;
}
private void pieceDropped()
{
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
board[(y * BoardWidth) + x] = curPiece.getShape();
}
removeFullLines();
if (!isFallingFinished)
newPiece();
}
private void newPiece()
{
curPiece.setRandomShape();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.minY();
if (!tryMove(curPiece, curX, curY)) {
curPiece.setShape(Tetrominoes.NoShape);
timer.stop();
isStarted = false;
statusbar.setText("game over");
}
}
private boolean tryMove(Shape newPiece, int newX, int newY)
{
for (int i = 0; i < 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
return false;
if (shapeAt(x, y) != Tetrominoes.NoShape)
return false;
}
curPiece = newPiece;
curX = newX;
curY = newY;
repaint();
return true;
}
private void removeFullLines()
{
int numFullLines = 0;
for (int i = BoardHeight - 1; i >= 0; --i) {
boolean lineIsFull = true;
for (int j = 0; j < BoardWidth; ++j) {
if (shapeAt(j, i) == Tetrominoes.NoShape) {
lineIsFull = false;
break;
}
}
if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
board[(k * BoardWidth) + j] = shapeAt(j, k + 1);
}
}
}
if (numFullLines > 0) {
numLinesRemoved += numFullLines;
statusbar.setText(String.valueOf(numLinesRemoved));
isFallingFinished = true;
curPiece.setShape(Tetrominoes.NoShape);
repaint();
}
}
private void drawSquare(Graphics g, int x, int y, Tetrominoes shape)
{
Color colors[] = { new Color(0, 0, 0), new Color(204, 102, 102),
new Color(102, 204, 102), new Color(102, 102, 204),
new Color(204, 204, 102), new Color(204, 102, 204),
new Color(102, 204, 204), new Color(218, 170, 0)
};
Color color = colors[shape.ordinal()];
g.setColor(color);
g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);
g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);
g.setColor(color.darker());
g.drawLine(x + 1, y + squareHeight() - 1,
x + squareWidth() - 1, y + squareHeight() - 1);
g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1,
x + squareWidth() - 1, y + 1);
}
public boolean isFocusable() {
return true;
}
public class TAdapter implements KeyListener {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (!isStarted || curPiece.getShape() == Tetrominoes.NoShape) {
return;
}
int keycode = e.getKeyCode();
if (keycode == 'p' || keycode == 'P') {
pause();
return;
}
if (isPaused)
return;
switch (keycode) {
case KeyEvent.VK_LEFT:
tryMove(curPiece, curX - 1, curY);
break;
case KeyEvent.VK_RIGHT:
tryMove(curPiece, curX + 1, curY);
break;
case KeyEvent.VK_DOWN:
tryMove(curPiece.rotateRight(), curX, curY);
break;
case KeyEvent.VK_UP:
tryMove(curPiece.rotateLeft(), curX, curY);
break;
case KeyEvent.VK_SPACE:
dropDown();
break;
case 'd':
oneLineDown();
break;
case 'D':
oneLineDown();
break;
}
}
#Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
}
}
}
Your code is to large for me to want to dig through, but what you want to do is possible.
Either way, I would suggest you move to one frame and possibly use a CardLayout or another GUI item to hide and show components because having the user change screens when the game starts is a strange UI. It will be more user friendly to just have it all in one frame. And this might fix your current issue.
Same here, the code is too large to dig. However if you are facing problems in setting the focus try frame.requestFocus();
First, a JPanel that wants to grab focus has to be focusable or focus traversable.
You should consider overriding isFocusTraversable to return true.
In your panel class :
#Override
public boolean isFocusTraversable()
{
return true;
}//met
This means your component can have focus.
Second, if you want your jpanel to* actually have the focus*, you can call his method
panel.requestFocus();
you should do that at the beginning of the game, right after the pack/setVisible( true ); and at the end of every actionlistener that will make it loose focus (typically buttons'actionlisteners).
Regards,
Stéphane
Instead of using a KeyListener you should be using Key Bindings.
keyListener was not working for me in Frame. Later I found out that the problem was that my key controls were going to the Button that I had added in the Frame. When I removed the Button, The key control went back to the frame and keyEvents were working.
But I have not found a way to use keyEvents even after keeping the Button in Frame!
Related
I am writing a video game GUI and I want to firstly open a frame with a menu bar and when the user clicks on play in the menu a JPanel that is in a different class gets added to the current one and it ends up with a frame containing the menu bar and the JPanel. When I run the code bellow I don't get any errors and the console initiates the process. The problem is nothing shows on the screen?? Not the initial frame or anything else.
The code for the frame that calls the class with the JPanel is:
/*-----------------------------------------------------------------------------------------------------*/
package Testes;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Tetris extends JFrame{
private static final long serialVersionUID = 1L;
public static final int WIDTH = 250;
public static final int HEIGHT = 490;
public Tetris() {
JFrame frame = new JFrame();
TetrisBoard janela = new TetrisBoard();
JMenuBar menubar = new JMenuBar();
JMenu start = new JMenu("Start");
JMenuItem play = new JMenuItem("Play");
play.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
janela.startGame();
}
});
start.add(play);
JMenuItem exit = new JMenuItem("Exit");
exit.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
});
start.add(exit);
JMenu help = new JMenu("Help");
JMenuItem manual = new JMenuItem("User Manual");
manual.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
JOptionPane.showMessageDialog(null, "The goal of Tetris is to eliminate \nas many lines as possible before\n the Tetrominoes reach the top.\nControls:\n\u2190 - Move Left\n\u2192 - Move Right\n\u2193 - Drop\n" +
"C - Rotate AntiClockwise\nV - Rotate Clockwise\nP - Pause\nEsc - Quit","Instructions", JOptionPane.OK_OPTION, new ImageIcon());
}
});
help.add(manual);
JMenuItem about = new JMenuItem("About");
about.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
JLabel label = new JLabel("<html><center>Tetris made by GG<br>MEEC<br>2020<html>");
label.setHorizontalAlignment(SwingConstants.CENTER);
JOptionPane.showMessageDialog(null, label, "About", JOptionPane.INFORMATION_MESSAGE);
}
});
help.add(about);
menubar.add(start);
menubar.add(help);
frame.add(janela,BorderLayout.CENTER);
frame.add(menubar,BorderLayout.NORTH);
janela.setFocusable(true);
frame.setTitle("Tetris");
frame.setLayout(new BorderLayout());
frame.setSize(250, 490);
//setPreferredSize(new Dimension(255, 495));
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.pack();
}
public static void main(String[] args) {
new Tetris();
}
}
/*-----------------------------------------------------------------------------------------------------*/
And the class with the JPanel is:
/*-----------------------------------------------------------------------------------------------------*/
package Testes;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
public class TetrisBoard extends JPanel implements KeyListener/*, ActionListener*/{
private static final long serialVersionUID = 1L;
public static final int COLOR_MIN = 35;
public static final int COLOR_MAX = 255 - COLOR_MIN;
public static final int BORDER_WIDTH = 5;
public static final int COL_COUNT = 10;
public static final int VISIBLE_ROW_COUNT = 20;
public static final int HIDDEN_ROW_COUNT = 2;
public static final int ROW_COUNT = VISIBLE_ROW_COUNT + HIDDEN_ROW_COUNT;
public static final int TILE_SIZE = 24;
public static final int SHADE_WIDTH = 4;
private static final int CENTER_X = COL_COUNT * TILE_SIZE / 2;
public static final int CENTER_Y = VISIBLE_ROW_COUNT * TILE_SIZE / 2;
public static final int PANEL_WIDTH = COL_COUNT * TILE_SIZE + BORDER_WIDTH * 2;
public static final int PANEL_HEIGHT = VISIBLE_ROW_COUNT * TILE_SIZE + BORDER_WIDTH * 2;
public static final Font LARGE_FONT = new Font("Tahoma", Font.BOLD, 16);
public static final Font SMALL_FONT = new Font("Tahoma", Font.BOLD, 12);
public static final long FRAME_TIME = 20L;
public static final int TYPE_COUNT = TileType.values().length;
public boolean isPaused;
public boolean isNewGame;
public boolean isGameOver;
public int level;
public int score;
public Random random;
public Clock logicTimer;
public TileType currentType;
public TileType nextType;
public int currentCol;
private int currentRow;
public int currentRotation;
public int dropCooldown;
public float gameSpeed;
public String difficulty = "Easy";
public int newLevel;
public int lines;
public int cleared;
public TileType[][] tiles;
public TetrisBoard() {
addKeyListener(this);
this.tiles = new TileType[ROW_COUNT][COL_COUNT];
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
setBackground(Color.BLACK);
startGame();
}
public void clear() {
for(int i = 0; i < ROW_COUNT; i++) {
for(int j = 0; j < COL_COUNT; j++) {
tiles[i][j] = null;
}
}
}
public boolean isValidAndEmpty(TileType type, int x, int y, int rotation) {
if(x < -type.getLeftInset(rotation) || x + type.getDimension() - type.getRightInset(rotation) >= COL_COUNT) {
return false;
}
if(y < -type.getTopInset(rotation) || y + type.getDimension() - type.getBottomInset(rotation) >= ROW_COUNT) {
return false;
}
for(int col = 0; col < type.getDimension(); col++) {
for(int row = 0; row < type.getDimension(); row++) {
if(type.isTile(col, row, rotation) && isOccupied(x + col, y + row)) {
return false;
}
}
}
return true;
}
public void addPiece(TileType type, int x, int y, int rotation) {
for(int col = 0; col < type.getDimension(); col++) {
for(int row = 0; row < type.getDimension(); row++) {
if(type.isTile(col, row, rotation)) {
setTile(col + x, row + y, type);
}
}
}
}
public int checkLines() {
int completedLines = 0;
for(int row = 0; row < ROW_COUNT; row++) {
if(checkLine(row)) {
completedLines++;
}
}
return completedLines;
}
public boolean checkLine(int line) {
for(int col = 0; col < COL_COUNT; col++) {
if(!isOccupied(col, line)) {
return false;
}
}
for(int row = line - 1; row >= 0; row--) {
for(int col = 0; col < COL_COUNT; col++) {
setTile(col, row + 1, getTile(col, row));
}
}
return true;
}
public boolean isOccupied(int x, int y) {
return tiles[y][x] != null;
}
public void setTile(int x, int y, TileType type) {
tiles[y][x] = type;
}
public TileType getTile(int x, int y) {
return tiles[y][x];
}
//#Override
public void paintComponent(Graphics g) {
this.paintComponent(g);
g.translate(BORDER_WIDTH, BORDER_WIDTH);
if(isPaused()) {
g.setFont(LARGE_FONT);
g.setColor(Color.GREEN);
String msg = "PAUSED";
g.drawString(msg, CENTER_X - g.getFontMetrics().stringWidth(msg) / 2, CENTER_Y);
} else if(isNewGame() || isGameOver()) {
g.setFont(LARGE_FONT);
g.setColor(Color.WHITE);
g.setColor(Color.GREEN);
String msg = isNewGame() ? "TETRIS" : "GAME OVER";
g.drawString(msg, CENTER_X - g.getFontMetrics().stringWidth(msg) / 2, 150);
g.setFont(SMALL_FONT);
msg = "Press Enter to Play" + (isNewGame() ? "" : " Again");
g.drawString(msg, CENTER_X - g.getFontMetrics().stringWidth(msg) / 2, 300);
} else {
for(int x = 0; x < COL_COUNT; x++) {
for(int y = HIDDEN_ROW_COUNT; y < ROW_COUNT; y++) {
TileType tile = getTile(x, y);
if(tile != null) {
drawTile(tile, x * TILE_SIZE, (y - HIDDEN_ROW_COUNT) * TILE_SIZE, g);
}
}
}
TileType type = getPieceType();
int pieceCol = getPieceCol();
int pieceRow = getPieceRow();
int rotation = getPieceRotation();
for(int col = 0; col < type.getDimension(); col++) {
for(int row = 0; row < type.getDimension(); row++) {
if(pieceRow + row >= 2 && type.isTile(col, row, rotation)) {
drawTile(type, (pieceCol + col) * TILE_SIZE, (pieceRow + row - HIDDEN_ROW_COUNT) * TILE_SIZE, g);
}
}
}
g.setColor(Color.DARK_GRAY);
for(int x = 0; x < COL_COUNT; x++) {
for(int y = 0; y < VISIBLE_ROW_COUNT; y++) {
g.drawLine(0, y * TILE_SIZE, COL_COUNT * TILE_SIZE, y * TILE_SIZE);
g.drawLine(x * TILE_SIZE, 0, x * TILE_SIZE, VISIBLE_ROW_COUNT * TILE_SIZE);
}
}
}
g.setColor(Color.GREEN);
g.drawRect(0, 0, TILE_SIZE * COL_COUNT, TILE_SIZE * VISIBLE_ROW_COUNT);
}
public void drawTile(TileType type, int x, int y, Graphics g) {
drawTile(type.getBaseColor(), type.getLightColor(), type.getDarkColor(), x, y, g);
}
public void drawTile(Color base, Color light, Color dark, int x, int y, Graphics g) {
g.setColor(base);
g.fillRect(x, y, TILE_SIZE, TILE_SIZE);
g.setColor(dark);
g.fillRect(x, y + TILE_SIZE - SHADE_WIDTH, TILE_SIZE, SHADE_WIDTH);
g.fillRect(x + TILE_SIZE - SHADE_WIDTH, y, SHADE_WIDTH, TILE_SIZE);
g.setColor(light);
for(int i = 0; i < SHADE_WIDTH; i++) {
g.drawLine(x, y + i, x + TILE_SIZE - i - 1, y + i);
g.drawLine(x + i, y, x + i, y + TILE_SIZE - i - 1);
}
}
public void startGame() {
this.random = new Random();
this.isNewGame = true;
if(this.difficulty.equals("Easy")) {
this.gameSpeed=1.0f;
}else if(this.difficulty.equals("Intermediate")) {
this.gameSpeed=3.0f;
}else if(this.difficulty.equals("Hard")) {
this.gameSpeed=6.0f;
}
this.level=1;
this.cleared=0;
this.newLevel=0;
this.logicTimer = new Clock(gameSpeed);
logicTimer.setPaused(true);
while(true) {
long start = System.nanoTime();
logicTimer.update();
if(logicTimer.hasElapsedCycle()) {
updateGame();
}
//Decrement the drop cool down if necessary.
if(dropCooldown > 0) {
dropCooldown--;
}
renderGame();
long delta = (System.nanoTime() - start) / 1000000L; // delta in miliseconds
if(delta < FRAME_TIME) {
try {
Thread.sleep(FRAME_TIME - delta); // sleeps the difference between the fps and the time for the game to process (delta)
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
public void updateGame() {
if(isValidAndEmpty(currentType, currentCol, currentRow + 1, currentRotation)) {
currentRow++;
} else {
addPiece(currentType, currentCol, currentRow, currentRotation);
cleared = checkLines();
if(cleared > 0) {
lines += cleared;
score += 50 << cleared; // left bit shift - add the number of zeros on the right to the binary version of the number on the right
// score = score + 50 << cleared;
}
//newLevel+=cleared;
gameSpeed += 0.035f;
logicTimer.setCyclesPerSecond(gameSpeed);
logicTimer.reset();
dropCooldown = 25;
if(newLevel<10) {
newLevel+=cleared;
}else if(newLevel>=10) {
level+=1;
newLevel=0;
cleared=0;
}
spawnPiece();
}
}
public void renderGame() {
repaint();
}
public void resetGame() {
this.level = 1;
this.score = 0;
this.lines = 0;
this.newLevel = 0;
this.cleared = 0;
if(this.difficulty.equals("Easy")) {
this.gameSpeed=1.0f;
}else if(this.difficulty.equals("Intermediate")) {
this.gameSpeed=3.0f;
}else if(this.difficulty.equals("Hard")) {
this.gameSpeed=6.0f;
}
this.nextType = TileType.values()[random.nextInt(TYPE_COUNT)];
this.isNewGame = false;
this.isGameOver = false;
clear();
logicTimer.reset();
logicTimer.setCyclesPerSecond(gameSpeed);
spawnPiece();
}
public void spawnPiece() {
this.currentType = nextType;
this.currentCol = currentType.getSpawnColumn();
this.currentRow = currentType.getSpawnRow();
this.currentRotation = 0;
this.nextType = TileType.values()[random.nextInt(TYPE_COUNT)];
if(!isValidAndEmpty(currentType, currentCol, currentRow, currentRotation)) {
lose();
}
}
public void lose()
{
this.isGameOver = true;
logicTimer.setPaused(isPaused);
String info = "";
if (score>HighScore.getHighScores()[9].getScore())
{
info="You got a high score!\n<br>Please enter you name.\n<br>(Note: Only 10 characters will be saved)";
JLabel label = new JLabel("<html><center>GAME OVER\n<br>" + info);
label.setHorizontalAlignment(SwingConstants.CENTER);
String name=JOptionPane.showInputDialog(null, label,"Tetris", JOptionPane.INFORMATION_MESSAGE);
if (name!=null) {
HighScore.addHighScore(new HighScore(score,level,lines,(name.length()>10)?name.substring(0, 10):name,(difficulty.length()>12)?difficulty.substring(0, 12):difficulty));
}
}else {
info="You didn't get a high score:( \n<br>Keep trying you will get it next time!";
JLabel label = new JLabel("<html><center>GAME OVER\n<br>" + info);
label.setHorizontalAlignment(SwingConstants.CENTER);
JOptionPane.showMessageDialog(null, label, "Tetris", JOptionPane.PLAIN_MESSAGE);
}
if (JOptionPane.showConfirmDialog(null, "Do you want to play again?",
"Tetris", JOptionPane.YES_NO_OPTION)==JOptionPane.YES_OPTION) {
this.score=0;
this.level=0;
this.lines=0;
startGame();
}else
{
//If not, quit
System.exit(0);
}
}
public void rotatePiece(int newRotation) {
int newColumn = currentCol;
int newRow = currentRow;
int left = currentType.getLeftInset(newRotation);
int right = currentType.getRightInset(newRotation);
int top = currentType.getTopInset(newRotation);
int bottom = currentType.getBottomInset(newRotation);
if(currentCol < -left) {
newColumn -= currentCol - left;
} else if(currentCol + currentType.getDimension() - right >= COL_COUNT) {
newColumn -= (currentCol + currentType.getDimension() - right) - COL_COUNT + 1;
}
if(currentRow < -top) {
newRow -= currentRow - top;
} else if(currentRow + currentType.getDimension() - bottom >= ROW_COUNT) {
newRow -= (currentRow + currentType.getDimension() - bottom) - ROW_COUNT + 1;
}
if(isValidAndEmpty(currentType, newColumn, newRow, newRotation)) {
currentRotation = newRotation;
currentRow = newRow;
currentCol = newColumn;
}
}
public boolean isPaused() {
return isPaused;
}
public boolean isGameOver() {
return isGameOver;
}
public boolean isNewGame() {
return isNewGame;
}
public int getScore() {
return score;
}
public int getLevel() {
return level;
}
public String getDiff(){
return difficulty;
}
public int getLines() {
return lines;
}
public TileType getPieceType() {
return currentType;
}
public TileType getNextPieceType() {
return nextType;
}
public int getPieceCol() {
return currentCol;
}
public int getPieceRow() {
return currentRow;
}
public int getPieceRotation() {
return currentRotation;
}
//#Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_DOWN:
if(!isPaused && dropCooldown == 0) {
logicTimer.setCyclesPerSecond(25.0f);
}
break;
case KeyEvent.VK_LEFT:
if(!isPaused && isValidAndEmpty(currentType, currentCol - 1, currentRow, currentRotation)) {
currentCol--;
}
break;
case KeyEvent.VK_RIGHT:
if(!isPaused && isValidAndEmpty(currentType, currentCol + 1, currentRow, currentRotation)) {
currentCol++;
}
break;
case KeyEvent.VK_C:
if(!isPaused) {
rotatePiece((currentRotation == 0) ? 3 : currentRotation - 1);
}
break;
case KeyEvent.VK_V:
if(!isPaused) {
rotatePiece((currentRotation == 3) ? 0 : currentRotation + 1);
}
break;
case KeyEvent.VK_P:
if(!isGameOver && !isNewGame) {
isPaused = !isPaused;
logicTimer.setPaused(isPaused);
}
break;
case KeyEvent.VK_ENTER:
if(isGameOver || isNewGame) {
resetGame();
}
break;
case KeyEvent.VK_ESCAPE:
int ans = JOptionPane.showConfirmDialog(null, "Are you sure you want to quit?\n", "Tetris", JOptionPane.INFORMATION_MESSAGE);
if(ans==1 || ans==2) {
//isPaused = !isPaused;
//logicTimer.setPaused(isPaused);
return;
}else if(ans==0) {
System.exit(0);
}
}
}
}
//#Override
public void keyReleased(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_S:
logicTimer.setCyclesPerSecond(gameSpeed);
logicTimer.reset();
break;
}
}
#Override
public void keyTyped(KeyEvent e) {}
}
First of all, dont add a JMenuBar by frame.add(myJMenuBar,someConstraints). Do it by calling the method frame.setJMenuBar(myJMenuBar);
Secondly, you add the components into the frame (its content pane), and after that you frame.setLayout(new BorderLayout());, while you should first set the layout and AFTER add the components to it:
frame.setLayout(new BorderLayout());
frame.add(janela, BorderLayout.CENTER);
This works:
You might notice it replaces the ridiculously long & complicated TetrisBoard with a red panel with a preferred size of 400 x 200. This is how you should figure such things out. Post a minimal reproducible example in future.
import java.awt.*;
import javax.swing.*;
public class Tetris extends JFrame {
public Tetris() {
JFrame frame = new JFrame();
JPanel janela = new JPanel();
janela.setBackground(Color.RED);
janela.setPreferredSize(new Dimension(400,200));
JMenuBar menubar = new JMenuBar();
JMenu start = new JMenu("Start");
JMenuItem play = new JMenuItem("Play");
start.add(play);
JMenuItem exit = new JMenuItem("Exit");
start.add(exit);
JMenu help = new JMenu("Help");
JMenuItem manual = new JMenuItem("User Manual");
help.add(manual);
JMenuItem about = new JMenuItem("About");
help.add(about);
menubar.add(start);
menubar.add(help);
frame.add(janela, BorderLayout.CENTER);
frame.setJMenuBar(menubar);
janela.setFocusable(true);
frame.setTitle("Tetris");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
new Tetris();
}
}
I'm currently working on an application which uses the draw function to animate "bouncing images" in a JPanel. To accomplish it I had to learn to use threads. When I used them in my code someone recommended that instead of using threads directly I can use stuff like Executor framework and ExecutorService.
Right now my problem is that when adding new images I need to make sure that they don't get created inside each other. When the program detects that they would be intersecting it should wait some amount of time, while all the other threads keep running, so images are still getting moved and drawn in current positions.
What happens however is that when I make one of the threads sleep to wait for a spot to be empty the whole program seems to freeze. The only thing that seems to be running is the image moving function.
Code in a Github Gist
Here is the code:
This is the BouncingImages class
/* NOTE: requires MyImage.java */
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.swing.*;
public class BouncingImages extends JFrame implements ActionListener {
public static void main(String[] args) {
new BouncingImages();
}
static boolean imagesLoaded = true;
JPanel resetPanel = new JPanel();
JPanel runningPanel = new JPanel();
JPanel pausedPanel = new JPanel();
JPanel btnPanel = new JPanel();
public static AnimationPanel animationPanel;
ArrayList <MyImage> imageList = new ArrayList <MyImage>();
private volatile boolean stopRequested = false; //maybe should use AtomicBoolean
boolean isRunning = false;
boolean isReset = true;
ExecutorService service = Executors.newCachedThreadPool();
Future f;
//here is the part of the code responsible for creating a JFrame
BouncingImages() {
//set up button panel
JButton btnStart = new JButton("Start");
JButton btnResume = new JButton("Resume");
JButton btnAdd = new JButton("Add");
JButton btnAdd10 = new JButton("Add 10");
JButton btnStop = new JButton("Stop");
JButton btnReset = new JButton("Reset");
JButton btnExit = new JButton("Exit");
btnStart.addActionListener(this);
btnResume.addActionListener(this);
btnAdd.addActionListener(this);
btnAdd10.addActionListener(this);
btnStop.addActionListener(this);
btnReset.addActionListener(this);
btnExit.addActionListener(this);
resetPanel.add(btnStart);
runningPanel.add(btnAdd);
runningPanel.add(btnAdd10);
runningPanel.add(btnStop);
pausedPanel.add(btnResume);
pausedPanel.add(btnReset);
animationPanel = new AnimationPanel();
resetButtons();
this.add(btnPanel, BorderLayout.SOUTH);
this.add(animationPanel);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack(); //since the JPanel is controlling the size, we need pack() here.
this.setLocationRelativeTo(null); //after pack();
this.setVisible(true);
}
//I use different JPanels with designated buttons to make them display different buttons when the program is running, paused or completely restarted.
public void resetButtons() {
btnPanel.updateUI();
if (isReset) {
btnPanel.removeAll();
btnPanel.add(resetPanel, BorderLayout.SOUTH);
} else {
if (isRunning) {
btnPanel.removeAll();
btnPanel.add(runningPanel, BorderLayout.SOUTH);
}
if (!isRunning) {
btnPanel.removeAll();
btnPanel.add(pausedPanel, BorderLayout.SOUTH);
}
}
}
//ActionListener for Buttons
#Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Start")) {
startAnimation();
isRunning = true;
isReset = false;
resetButtons();
}
if (e.getActionCommand().equals("Resume")) {
startAnimation();
isRunning = true;
resetButtons();
}
if (e.getActionCommand().equals("Add")) {
addImage();
}
if (e.getActionCommand().equals("Add 10")) {
for(int i = 0; i <10; i++) {
addImage();
startAnimation();
}
}
if (e.getActionCommand().equals("Stop")) {
pauseAnimation();
isRunning = false;
resetButtons();
}
if (e.getActionCommand().equals("Reset")) {
imageList.clear();
repaint();
isReset = true;
resetButtons();
}
if (e.getActionCommand().equals("Exit")) {
System.exit(0);
}
}
//This function starts all the animations using the Runnable AnimationThread
void startAnimation() {
//this starts the program
if (f == null) {
f = service.submit(new AnimationThread());
}
//this starts the program after it got paused
else if (f.isCancelled()) {
f = service.submit(new AnimationThread());
}
}
//this pauses all the animations
void pauseAnimation() {
f.cancel(true);
}
//here is the part of the code that I have problems with. I'm not sure how to make the program wait for a spot to be empty while all the other threads are running rather than pausing them all.
void addImage(){
int i = 0;
MyImage image;
while (true) {
image= new MyImage("image.png");
if (checkCollision(image)) {
if(i > 100){
System.out.println("Something went wrong");
System.exit(0);
}
try {
i++;
Thread.sleep(50);
} catch (InterruptedException e) {}
} else {
System.out.println("image added");
break;
}
}
imageList.add(image);
}
//this part of the program does all the image moving. It uses the function move image from the MyImage class and a checkCollision function from this class.
void moveAllImages() {
for (MyImage image : imageList) {
image.moveImage(animationPanel);
image.calculatePoints();
checkCollision(image);
}
}
//this checks all the collisions with other images. It can be used for checking if a image can be created in some spot and also for all the bouncing callculations
boolean checkCollision(MyImage currentImage){
if(imageList.isEmpty()) return false;
for(MyImage im : imageList){
if(currentImage == im) continue;
if(currentImage.intersects(im)){
if(im.contains(currentImage.ml) || im.contains(currentImage.mr)){
currentImage.undoMove();
currentImage.vx = -currentImage.vx;
return true;
}
}
if(im.contains(currentImage.mt) || im.contains(currentImage.mb)){
currentImage.undoMove();
currentImage.vy = - currentImage.vy;
return true;
}
if(currentImage.contains(im.ml) || currentImage.contains(im.mr)){
currentImage.undoMove();
currentImage.vx = -currentImage.vx;
return true;
}
if (im.contains(currentImage.tl) || im.contains(currentImage.tr) || im.contains(currentImage.bl) || im.contains(currentImage.br)) {
currentImage.undoMove();
currentImage.vx *= -1;
currentImage.vy *= -1;
return true;
}
}
return false;
}
//This class does drawing graphics and nothing else
public class AnimationPanel extends JPanel {
AnimationPanel() {
this.setBackground(Color.BLACK);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setPreferredSize(new Dimension((int) screenSize.getWidth() / 2, (int) screenSize.getHeight() / 2));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (MyImage im : imageList) {
g.drawImage(im.image, im.x, im.y, im.width, im.height, null);
}
}
}
//this is the Runnable AnimationThread which does all the image moving and repainting.
private class AnimationThread implements Runnable {
#Override
public void run() {
while (!f.isCancelled()) {
moveAllImages();
animationPanel.repaint();
try {
Thread.sleep(5);
}
catch (InterruptedException e) {
System.out.println(e.getMessage());
f.cancel(true);
}
}
}
}
}
This is the MyImage class
package bouncer;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
//NOTE: This class requires BouncingImages.java
/* This class combines an image with a rectangle
* which allows for easy movement and collision detection
*/
class MyImage extends Rectangle {
//this gives it an x,y,width,height
static int biggestDim = 0;
BufferedImage image;
Color color = Color.RED;
//these are the speeds of movement
int vx = 1;
int vy = 1;
int lastx = -1;
int lasty = -1;
Point ctr, tl, tr, bl, br, ml, mr, mt, mb;
MyImage(String filename) {
vx = (int) (Math.random() * 5 + 1);
vy = (int) (Math.random() * 5 + 1);
//load the image
try {
image = ImageIO.read(new File(filename));
width = image.getWidth(null);
height = image.getHeight(null);
}
catch (IOException e) {
System.out.println("ERROR: image file \"" + filename + "\" not found");
//e.printStackTrace();
BouncingImages.imagesLoaded = false;
width = 100 + (int) (Math.random() * 100);
height = width - (int) (Math.random() * 70);
color = Color.getHSBColor((float) Math.random(), 1.0f, 1.0f); // a quick way to get random colours
//System.exit(0);
}
//update the variable containing the biggest dimension
if (width > biggestDim) biggestDim = width;
if (height > biggestDim) biggestDim = height;
calculatePoints();
}
void calculatePoints() {
//Calculate points
//corners
tl = new Point(x, y);
tr = new Point(x + width, y);
bl = new Point(x, y + height);
br = new Point(x + width, y + height);
//center
ctr = new Point(x + width / 2, y + height / 2);
//mid points of sides
ml = new Point(x, y + height / 2);
mr = new Point(x + width, y + height / 2);
mt = new Point(x + width / 2, y);
mb = new Point(x + width / 2, y + height);
}
void moveImage(BouncingImages.AnimationPanel panel) {
lastx = x;
lasty = y;
x += vx;
y += vy;
if (x < 0 && vx < 0) {
x = 0;
vx = -vx;
}
if (y < 0 && vy < 0) {
y = 0;
vy = -vy;
}
if (x + width > panel.getWidth() && vx > 0) {
x = panel.getWidth() - width;
vx = -vx;
}
if (y + height > panel.getHeight() && vy > 0) {
y = panel.getHeight() - height;
vy = -vy;
}
}
void undoMove() {
if (lastx > 0) {
x = lastx;
y = lasty;
}
lastx = lasty = -1;
}
}
You are blocking the main thread in method addImage in Thread.sleep(50). Instead, you could ensure that you call method addImage asynchronously, e.g.
if (e.getActionCommand().equals("Add")) {
CompletableFuture.runAsync(this::addImage);
}
if (e.getActionCommand().equals("Add 10")) {
CompletableFuture.runAsync(() -> addImages(10));
}
with
private void addImages(int numberOfImages) {
for(int i = 0; i < numberOfImages; i++) {
addImage();
startAnimation();
}
}
If you want to experiment a bit more with threads, then you can think about how to do it "by hand".
I want to add a simple menu to my Java Snake game which will contain three buttons (New Game, Difficulty and Quit). I have created a class for the menu and a class for the mouse input. I have used an enum to store the states of the game (MAIN_MENU, DIFFICULTY_MENU and GAME) and a variable called STATE which stores the current state.
The main problem is that although the state of the game is changed when the "New Game" button is pressed the game doesn't start. I have tried to add game state checks inside the methods that are involved in setting the gameplay and I have also tried calling those methods from the mousePressed() method of the MouseInput class but none of these worked.
I would greatly appreciate if you could give me some tips on how to solve this issue.
Here's my code:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class SnakeGame extends JFrame{
public SnakeGame() {
initGUI();
}
public void initGUI() {
add(new GamePlay());
setResizable(false);
pack();
setTitle("The Snake");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new SnakeGame();
frame.setVisible(true);
});
}
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.*;
public class GamePlay extends JPanel implements ActionListener {
public static final int HEIGHT = 600;
public static final int WIDTH = 600;
private final int TOTAL_DOTS = 3600;
private final int DOT_SIZE = 10;
private final int RAND_POSITION = 29;
//private final int DELAY = 140;
private final int[] X = new int[TOTAL_DOTS];// the dots on X axis
private final int[] Y = new int[TOTAL_DOTS];//the dots on Y axis
private int dots;
private int apple_X;
private int apple_Y;
private int score = 0;
private int DELAY = 140;
private boolean leftDirection = false;
private boolean rightDirection = true;
private boolean upDirection = false;
private boolean downDirection = false;
private boolean inGame = true;
private Timer timer;
private Image bodyPart;
private Image snakeHead;
private Image apple;
private Menu menu;
public static GameState STATE = GameState.MAIN_MENU;//current game state(it changes when menu buttons are pressed)
public static enum GameState{
MAIN_MENU,
DIFFICULTY_MENU,
GAME
}
public GamePlay() {
setGamePlay();
}
public void setGamePlay() {
addKeyListener(new KeyboardEvent());
addMouseListener(new MouseInput());
setBackground(Color.BLACK);
setFocusable(true);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
if(STATE == GameState.GAME) {
loadImages();
startGame();
}
}
private void loadImages() {
ImageIcon img2 = new ImageIcon("src/images/dot.png");
bodyPart = img2.getImage();
ImageIcon img1 = new ImageIcon("src/images/head.png");
snakeHead = img1.getImage();
ImageIcon img3 = new ImageIcon("src/images/apple.png");
apple = img3.getImage();
}
//setting the starting snake length and the apple position on the board
public void startGame() {
dots = 3;
for(int i = 0 ; i < dots; i++) {
X[i] = 50 - i * 10;
Y[i] = 50;
}
locateApple();
timer = new Timer(DELAY,this);
//timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(STATE == GameState.GAME) {
draw(g);
}else if(STATE == GameState.MAIN_MENU) {
new Menu().displayMainMenu(g);
}else if(STATE == GameState.DIFFICULTY_MENU) {
new Menu().displayDifficultyMenu(g);
}
System.out.println("End of paint component method");
}
public void draw(Graphics g) {
scoreDisplay(g);
if(inGame) {
g.drawImage(apple, apple_X, apple_Y,this);
for(int i = 0 ; i < dots; i++) {
if(i == 0) {
g.drawImage(snakeHead,X[i], Y[i], this);
}else {
g.drawImage(bodyPart, X[i], Y[i], this);
}
}
Toolkit.getDefaultToolkit().sync();
}else {
gameOver(g);
}
}
public void gameOver(Graphics g) {
String msg = "Game Over!";
String msg2 = "Score: " + score + " " + "Length: " + dots;
Font small = new Font("Comic Sans",Font.BOLD,14 );
FontMetrics messageSize = getFontMetrics(small);
g.setColor(Color.WHITE);
g.setFont(small);
g.drawString(msg, (WIDTH - messageSize.stringWidth(msg)) / 2, HEIGHT / 2);
g.drawString(msg2, (WIDTH - messageSize.stringWidth(msg2)) / 2, (HEIGHT / 2) + 20);
restartMessageDisplay(g);
}
public void scoreDisplay(Graphics g) {
String gameScore = "Score: " + score;
String snakeLength = "Snake length: " + dots;
Font small = new Font("Arial", Font.BOLD, 14);
g.setColor(Color.WHITE);
g.setFont(small);
g.drawString(gameScore,480, 20);
g.drawString(snakeLength, 480, 40);
}
public void restartMessageDisplay(Graphics g) {
String restartMessage = " Press Enter to restart";
Font small = new Font("Arial", Font.BOLD, 14);
FontMetrics metrics = getFontMetrics(small);
g.drawString(restartMessage, (WIDTH - metrics.stringWidth(restartMessage)) / 2, (HEIGHT / 2) + 40);
}
private void checkApple() {
if((X[0] == apple_X) && (Y[0] == apple_Y)) {
dots++;
locateApple();
score += 5;//score update
DELAY -= 5;// speed increase when snake eats the apple
if(DELAY >= 40) {
timer.setDelay(DELAY);
}
}
}
//snake move logic
private void move() {
for(int i = dots; i > 0; i--) {
X[i] = X[(i-1)];
Y[i] = Y[(i-1)];
}
if(leftDirection) {
X[0] -= DOT_SIZE;
}
if(rightDirection) {
X[0] += DOT_SIZE;
}
if(upDirection) {
Y[0] -= DOT_SIZE;
}
if(downDirection) {
Y[0] += DOT_SIZE;
}
}
//checking for collision with the walls or the snake itself
private void checkCollision() {
for(int i = dots; i > 0; i--) {
if((i > 4) && (X[0] == X[i]) && (Y[0] == Y[i])) {
inGame = false;
}
}
if(X[0] < 0) {
inGame = false;
}
if(X[0] > WIDTH) {
inGame = false;
}
if(Y[0] < 0) {
inGame = false;
}
if(Y[0] > HEIGHT) {
inGame = false;
}
if(!inGame) {
timer.stop();
}
}
//setting apple position on the board
public void locateApple() {
int random = (int) (Math.random() * RAND_POSITION);
apple_X = random * DOT_SIZE;
random = (int) (Math.random()* RAND_POSITION);
apple_Y = random * DOT_SIZE;
}
#Override
public void actionPerformed(ActionEvent e) {
if(inGame) {
checkApple();
checkCollision();
move();
}
repaint();
}
private class KeyboardEvent extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if((keyCode == KeyEvent.VK_LEFT) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
if((keyCode == KeyEvent.VK_RIGHT) && (!leftDirection)) {
rightDirection = true;
upDirection = false;
downDirection = false;
}
if((keyCode == KeyEvent.VK_UP) && (!downDirection)) {
upDirection = true;
leftDirection = false;
rightDirection = false;
}
if((keyCode == KeyEvent.VK_DOWN) && (!upDirection)) {
downDirection = true;
leftDirection = false;
rightDirection = false;
}
if(keyCode == KeyEvent.VK_PAUSE) {
timer.stop();
}
if(keyCode == KeyEvent.VK_SPACE) {
timer.start();
}
//game restart
if(keyCode == KeyEvent.VK_ENTER) {
if(!inGame) {
inGame = true;
leftDirection = false;
rightDirection = true;
upDirection = false;
downDirection = false;
score = 0;
DELAY = 140;
setGamePlay();
repaint();
}
}
}
}
}
import java.awt.*;
import javax.swing.*;
public class Menu {
public Rectangle newGameButton;
public Rectangle gameDifficultyButton;
public Rectangle quitButton;
public Rectangle easyDifficultyButton;
public Rectangle mediumDifficultyButton;
public Rectangle hardDifficultyButton;
public Button button1;
public Menu() {
int buttonWidth = 150;
int buttonHeight = 50;
newGameButton = new Rectangle((GamePlay.WIDTH - buttonWidth) / 2, (GamePlay.HEIGHT / 2) - 50, buttonWidth, buttonHeight);
gameDifficultyButton = new Rectangle((GamePlay.WIDTH - buttonWidth) / 2, (GamePlay.HEIGHT / 2) + 50, buttonWidth, buttonHeight);
quitButton = new Rectangle((GamePlay.WIDTH - buttonWidth) / 2, (GamePlay.HEIGHT / 2) + 150, buttonWidth, buttonHeight);
easyDifficultyButton = new Rectangle((GamePlay.WIDTH - buttonWidth) / 2, (GamePlay.HEIGHT / 2) - 50, buttonWidth, buttonHeight);
mediumDifficultyButton = new Rectangle((GamePlay.WIDTH - buttonWidth) / 2, (GamePlay.HEIGHT / 2) + 50, buttonWidth, buttonHeight);
hardDifficultyButton = new Rectangle((GamePlay.WIDTH - buttonWidth) / 2, (GamePlay.HEIGHT / 2) + 150, buttonWidth, buttonHeight);
}
public void displayMainMenu(Graphics g) {
Graphics2D buttonGraphics =(Graphics2D) g;
String title = "THE SNAKE GAME";
Font font = new Font("Arial", Font.BOLD, 50);
FontMetrics size = g.getFontMetrics(font);
g.setFont(font);
g.setColor(Color.WHITE);
g.drawString(title,(GamePlay.WIDTH-size.stringWidth(title)) / 2, (GamePlay.HEIGHT / 2) - 130);
Font buttonFont = new Font("Arial", Font.BOLD, 28);
g.setFont(buttonFont);
g.drawString("New Game",newGameButton.x + 2, newGameButton.y + 35);
buttonGraphics.draw(newGameButton);
g.drawString("Difficulty",gameDifficultyButton.x + 15,gameDifficultyButton.y + 35);
buttonGraphics.draw(gameDifficultyButton);
g.drawString("Quit", quitButton.x + 45, quitButton.y + 35);
buttonGraphics.draw(quitButton);
}
public void displayDifficultyMenu(Graphics g) {
Graphics2D difficultyMenuGraphics = (Graphics2D) g;
String title = "Difficulty level";
Font difficultyMenuTitleFont = new Font("Arial", Font.BOLD,40);
FontMetrics size = g.getFontMetrics(difficultyMenuTitleFont);
g.setFont(difficultyMenuTitleFont);
g.setColor(Color.WHITE);
g.drawString(title, (GamePlay.WIDTH - size.stringWidth(title)) / 2, (GamePlay.HEIGHT / 2) - 100);
Font difficultyButtonFont = new Font("Arial", Font.BOLD, 28);
g.setFont(difficultyButtonFont);
g.drawString("Easy", easyDifficultyButton.x + 40 , easyDifficultyButton.y + 35);
difficultyMenuGraphics.draw(easyDifficultyButton);
g.drawString("Medium",mediumDifficultyButton.x + 20, mediumDifficultyButton.y + 35);
difficultyMenuGraphics.draw(mediumDifficultyButton);
g.drawString("Hard", hardDifficultyButton.x + 40, hardDifficultyButton.y + 35);
difficultyMenuGraphics.draw(hardDifficultyButton);
}
}
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.lang.Thread.State;
public class MouseInput extends MouseAdapter {
public Rectangle newGameButton;
public Rectangle gameDifficultyButton;
public Rectangle quitButton;
public Rectangle easyDifficultyButton;
public Rectangle mediumDifficultyButton;
public Rectangle hardDifficultyButton;
Graphics g;
public void mousePressed(MouseEvent e) {
int mouseX = e.getX();
int mouseY = e.getY();
//NewGame button
if(mouseX >= (GamePlay.WIDTH - 150) / 2 && mouseX <= ((GamePlay.WIDTH - 150) / 2) + 150) {
if(mouseY >= (GamePlay.HEIGHT / 2) - 50 && mouseY <= (GamePlay.HEIGHT/ 2)) {
GamePlay.STATE = GamePlay.GameState.GAME;
System.out.println(GamePlay.STATE);
}
}
//Difficulty button
if(mouseX >= (GamePlay.WIDTH - 150) / 2 && mouseX <= ((GamePlay.WIDTH - 150)/ 2 ) + 150) {
if(mouseY >= (GamePlay.HEIGHT / 2) + 50 && mouseY <= (GamePlay.HEIGHT/ 2) + 100) {
GamePlay.STATE = GamePlay.STATE.DIFFICULTY_MENU;
System.out.println("Game state = " + GamePlay.STATE)
}
}
//Quit button
if(mouseX >= (GamePlay.WIDTH - 150) / 2 && mouseX <= ((GamePlay.WIDTH - 150)/ 2 ) + 150) {
if(mouseY >= (GamePlay.HEIGHT / 2) + 150 && mouseY <= (GamePlay.HEIGHT/ 2) + 200) {
System.exit(0);
}
}
}
}
I am making a snake game, and I am stuck at where making the tails follow the head. And I heard using an add and remove on the head and tails could make that happen, but I have no idea where to start with that.
Here's my code so far:
Screen.java
public class Screen extends JPanel implements ActionListener, KeyListener {
public static final JLabel statusbar = new JLabel("Default");
public static final int WIDTH = 800, HEIGHT = 800;
Timer t = new Timer(100, this);
int x = 400;
int y = 400;
int size = 5; //increase size if eat
private boolean right = false, left = false, up = false, down = false;
int head = 0;
private LinkedList<BodyPart> snake = new LinkedList<BodyPart>();
private BodyPart b;
public Screen(){
initSnake();
t.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void update(){
}
public void direction(){
if(right) x+=10;
if(left) x-=10;
if(up) y-=10;
if(down) y+=10;
}
public void trackOutBound(){
if(x < 0 || x > 800 || y < 0 || y > 800) {
x = 400;
y = 400;
}
}
public void initSnake(){
if(snake.size() == 0){
b = new BodyPart(x, y);
for(int i = 0; i < size; i++) {
snake.add(b);
}
System.out.println(snake);
}
}
public static void main(String[] args) {
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(new Color(10, 50, 0));
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.BLACK);
for(int i = 0; i < WIDTH / 10; i++) {
g.drawLine(i * 10, 0, i * 10, HEIGHT);
}
for(int i = 0; i < HEIGHT / 10; i++) {
g.drawLine(0, i * 10, WIDTH, i * 10);
}
int tempx = 0, tempy = 0;
int temp = 0;
for(int i = 0; i < size; i++){
if(i == head) {
snake.get(i).x = x;
snake.get(i).y = y;
snake.get(i).draw(g);
g.setColor(Color.blue);
g.fillRect(x, y, 10, 10);
g.setColor(Color.white);
g.drawRect(x, y, 10, 10);
} else if(i > 0 && up) {
snake.get(i).x = x;
snake.get(i).y = y + temp;
snake.get(i).draw(g);
} else if(i > 0 && down) {
snake.get(i).x = x;
snake.get(i).y = y - temp;
snake.get(i).draw(g);
} else if(i > 0 && left) {
snake.get(i).x = x + temp;
snake.get(i).y = y;
snake.get(i).draw(g);
} else if(i > 0 && right) {
snake.get(i).x = x - temp;
snake.get(i).y = y;
snake.get(i).draw(g);
}
temp += 10;
}
/*
if(snake.size() == 5){
snake.add(b);
size += 1;
}
*/
}
#Override
public void actionPerformed(ActionEvent e) {
direction();
trackOutBound();
repaint();
// System.out.println(snake);
statusbar.setText("(" + x + " , " + y + ")");
}
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if(key == KeyEvent.VK_RIGHT && !left) {
up = false;
down = false;
right = true;
}
if(key == KeyEvent.VK_LEFT && !right) {
up = false;
down = false;
left = true;
}
if(key == KeyEvent.VK_UP && !down) {
left = false;
right = false;
up = true;
}
if(key == KeyEvent.VK_DOWN && !up) {
left = false;
right = false;
down = true;
}
}
#Override
public void keyReleased(KeyEvent e) {}
}
BodyPart.java
public class BodyPart {
int x;
int y;
public BodyPart(int x, int y) {
this.x = x;
this.y = y;
}
public void draw(Graphics g) {
this.x = x;
this.y = y;
g.setColor(Color.red);
g.fillRect(x, y, 10, 10);
g.setColor(Color.white);
g.drawRect(x, y, 10, 10);
}
}
Frame.java
public class Frame extends JPanel {
private static JLabel statusbar = new JLabel("Default");
public void statusbar(){
statusbar = Screen.statusbar;
}
public static void main(String[] args) {
JFrame f = new JFrame();
Screen s = new Screen();
f.add(s);
f.add(statusbar, BorderLayout.SOUTH);
f.setSize(800, 800);
f.setVisible(true);
f.setLocationRelativeTo(null);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Now this code would only make the tails flip to the horizontal or vertical, is it possible to make the tails follow the head by using this code? or I need to change my code?
Thank you
The basic idea is, you need some kind of List which contains ALL the points of the snake. Conceptually, the List would contain virtual coordinates, that is 1x1 would represent a coordinate in virtual space, which presented a place on a virtual board (which would have some wide and height).
You could then translate that to the screen, so this would allow each part of the snake to be larger then a single pixel. So, if each part was 5x5 pixels, then 1x1 would actually be 5x5 in the screen.
Each time the snake moves, you add a new value to the head and remove the last value from tail (assuming it's not growing). When you needed to paint the snake, you would simply iterate over the List, painting each point of the snake.
The following is a simple example, which uses a LinkedList, which pushes a new Point onto the List, making a new head, and removing the last element (the tail) on each cycle.
Which basically boils down to...
snakeBody.removeLast();
snakeBody.push(new Point(xPos, yPos));
As a runnable concept
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Snake {
public static void main(String[] args) {
new Snake();
}
public Snake() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
public enum Direction {
UP, DOWN, LEFT, RIGHT
}
private int xPos, yPos;
private Direction direction = Direction.UP;
private LinkedList<Point> snakeBody = new LinkedList<>();
public TestPane() {
xPos = 100;
yPos = 100;
for (int index = 0; index < 50; index++) {
snakeBody.add(new Point(xPos, yPos));
}
bindKeyStrokeTo("up.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), new MoveAction(Direction.UP));
bindKeyStrokeTo("down.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), new MoveAction(Direction.DOWN));
bindKeyStrokeTo("left.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), new MoveAction(Direction.LEFT));
bindKeyStrokeTo("right.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), new MoveAction(Direction.RIGHT));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (direction) {
case UP:
yPos--;
break;
case DOWN:
yPos++;
break;
case LEFT:
xPos--;
break;
case RIGHT:
xPos++;
break;
}
if (yPos < 0) {
yPos--;
} else if (yPos > getHeight() - 1) {
yPos = getHeight() - 1;
}
if (xPos < 0) {
xPos--;
} else if (xPos > getWidth() - 1) {
xPos = getWidth() - 1;
}
snakeBody.removeLast();
snakeBody.push(new Point(xPos, yPos));
repaint();
}
});
timer.start();
}
public void bindKeyStrokeTo(String name, KeyStroke keyStroke, Action action) {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
for (Point p : snakeBody) {
g2d.drawLine(p.x, p.y, p.x, p.y);
}
g2d.dispose();
}
public class MoveAction extends AbstractAction {
private Direction moveIn;
public MoveAction(Direction direction) {
this.moveIn = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
direction = this.moveIn;
}
}
}
}
Now, this has no collision detection or other functionality, but you can move the snake around and it will follow itself
For snake style movement, you can, from the tail to the head, move each BodyPart position to the position of the BodyPart ahead of it. For the head there is no part ahead so you have to write decision code whether to simply move the same direction as the part before it or a new direction based on input. Then update the screen.
I'm trying to make a match 3 game. I am trying to create some visual aid to what is actually happening by first marking the gems that need to be deleted "black", and after that letting gravity do it's job. I'm struggling to do this, I called repaint(); after I marked them "black", but it doesn't seem to work. I also tried adding in revalidate(); as suggested in another question but that doesn't seem to fix the problem either. Here's the piece of code that's troubling me.
Trouble code:
public void deletePattern(Set<Gem> gemsToDelete){
for(Gem gem : gemsToDelete)
gem.setType(7);
repaint(); //This doesn't seem to work
doGravity();
switchedBack = true;
checkPattern();
}
I want to repaint the board before doGravity() and after the enhanced for loop. Could it be that I'm not using the thread correctly in the doGravity() method?
Here's the full code:
Board.java
package Game;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedHashSet;
import java.util.Set;
public class Board extends JPanel{
final int BOARDWIDTH = 8;
final int BOARDHEIGHT = 8;
private static final Color COLORS[] = { new Color(255, 0, 0), new Color(255, 128, 0), new Color(255, 255, 0), new Color(0, 255, 0), new Color(0, 255, 255), new Color(0, 0, 255), new Color(127, 0, 255), new Color(0, 0, 0), new Color(0, 0, 0), new Color(255, 255, 255)};
boolean isAlive, isPattern, switchedBack;
boolean isFirstSelected = false;
Gem[][] gems;
int fromX, fromY, toX, toY;
public Board() {
gems = new Gem[BOARDWIDTH][BOARDHEIGHT];
addMouseListener(new MouseInputAdapter());
}
int cellWidth() { return (int) getSize().getWidth() / BOARDWIDTH; }
int cellHeight() { return (int) getSize().getHeight() / BOARDHEIGHT; }
public void start(){
isPattern = switchedBack = false;
isAlive = true;
fillBoard();
checkPattern();
switchedBack = false;
}
public void paint(Graphics g) {
super.paint(g);
for (int x = 0; x < BOARDWIDTH; x++) {
for (int y = 0; y < BOARDHEIGHT; y++)
drawCell(g, x, y, gems[x][y]);
}
}
public void fillBoard(){
for (int x = 0; x < BOARDWIDTH; x++) {
for (int y = 0; y < BOARDHEIGHT; y++)
gems[x][y] = new Gem();
}
}
public void drawCell(Graphics g, int x, int y, Gem gem) {
x = x * cellWidth();
y = y * cellHeight();
g.setColor(COLORS[gem.getType()]);
g.fillRect(x, y, x + cellWidth(), y + cellHeight());
}
class MouseInputAdapter extends MouseAdapter { #Override public void mouseClicked(MouseEvent e) { selectGems(e); } }
public void selectGems(MouseEvent e){
int x = e.getX() / cellWidth();
int y = e.getY() / cellHeight();
if(!isFirstSelected) {
fromX = x;
fromY = y;
isFirstSelected = true;
}else{
toX = x;
toY = y;
if((Math.abs(fromX - toX) == 1 ^ Math.abs(fromY - toY) == 1) & (gems[fromX][fromY].getType() != gems[toX][toY].getType())) {
switchGems();
isFirstSelected = false;
}
}
}
public void switchGems(){
int tempType = gems[fromX][fromY].getType();
gems[fromX][fromY].setType(gems[toX][toY].getType());
gems[toX][toY].setType(tempType);
checkPattern();
switchedBack = false;
repaint();
}
public void checkPattern() {
Set<Gem> gemsToDelete = new LinkedHashSet<>();
isPattern = false;
for (int x = 0; x < BOARDWIDTH; x++) {
for (int y = 0; y < BOARDHEIGHT; y++) {
if (x + 2 < BOARDWIDTH && (gems[x][y].getType() == gems[x + 1][y].getType()) && (gems[x + 1][y].getType() == gems[x + 2][y].getType())) { //Checks for 3 horizontal gems in a row
isPattern = true;
gemsToDelete.add(gems[x][y]);
gemsToDelete.add(gems[x + 1][y]);
gemsToDelete.add(gems[x + 2][y]);
}
if (y + 2 < BOARDHEIGHT && (gems[x][y].getType() == gems[x][y + 1].getType()) && (gems[x][y + 1].getType() == gems[x][y + 2].getType())) { //Check for 3 vertical gems in a row
isPattern = true;
gemsToDelete.add(gems[x][y]);
gemsToDelete.add(gems[x][y + 1]);
gemsToDelete.add(gems[x][y + 2]);
}
}
}
if(!gemsToDelete.isEmpty())
deletePattern(gemsToDelete);
if(!isPattern && !switchedBack){
switchedBack = true;
switchGems();
}
}
public void deletePattern(Set<Gem> gemsToDelete){
for(Gem gem : gemsToDelete)
gem.setType(7);
repaint(); //This doesn't seem to work
doGravity();
switchedBack = true;
checkPattern();
}
public void doGravity(){
try{
Thread.sleep(1000);
}catch(InterruptedException e){e.printStackTrace();}
for (int y = 0; y < BOARDHEIGHT; y++) {
for (int x = 0; x < BOARDWIDTH; x++) {
if(gems[x][y].getType() == 7){
for (int i = y; i >= 0; i--) {
if(i == 0)
gems[x][i].setType(gems[x][i].genType());
else
gems[x][i].setType(gems[x][i-1].getType());
}
}
}
}
}
}
Gem.java
package Game;
public class Gem {
private int type;
public Gem(){
this.type = genType();
}
public int genType(){
return (int) (Math.random() * 7);
}
public void setType(int type){
this.type = type;
}
public int getType(){
return type;
}
}
Game.java
package Game;
import javax.swing.*;
public class Game extends JFrame{
public Game(){
Board board = new Board();
getContentPane().add(board);
board.start();
setTitle("Game");
setSize(600, 600);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args){
Game game = new Game();
game.setLocationRelativeTo(null);
game.setVisible(true);
}
}
Your code is initiated via a mouse click. Code invoked from a Swing listener is executed on the Event Dispatch Thread (EDT), which is also responsible for painting the GUI
The Thread.sleep() in your doGravaity() method causes the EDT to sleep, therefore the GUI can't repaint() itself until the whole looping code is finished, at which point it will just paint the final state of your animation.
Instead of sleeping, you need to use a Swing Timer to schedule animation. So basically, in the deletePattern() method you would start the Timer to do the gravity animation. This will free up the EDT to repaint itself and when the Timer fires you would animate your components one move and then do repaint() again. When the components are finished moving you stop the timer.
Read the section from the Swing tutorial on Concurrency for more information about the EDT.
Call this.invalidate() or this.postInvalidate() which then forces a repaint.