Memory Game in java - java

I am trying to write a memory game. When we run app, "menu" appears first. The choices are one player and two player. When we click one player "level" selection part appears. Here choices are easy, medium, hard. Then one of them is clicked and game starts. But i am having trouble while implementing how to check selected cards equal or not. When a card is created, an id is assigned to it. And when two cards are clicked, I am checking ids. If ids are same, it returns "match", if it is not then "close" returns that means cards turn facedown. I am using MVC pattern. I have 9 classes. Card, Game, Level(enum), Menu, State(enum); views CardButton, GamePanel, LevelPanel, MenuPanel.
Class GamePanel:
package view;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import model.Game;
import model.State;
public class GamePanel extends JPanel {
private Game model;
ActionListener cardButtonActionListener;
private JFrame frame;
public GamePanel(Game gameModel, JFrame frame) {
int width=gameModel.getRowSize();
int height=gameModel.getColumnSize();
setLayout(new GridLayout(width,height));
this.model = gameModel;
this.cardButtonActionListener = new CardButtonActionListener();
this.frame = frame;
show();
}
public void show() {
for(int row=0; row<model.getRowSize(); row++) {
for (int col=0; col<model.getColumnSize(); col++) {
CardButton cardBtn = new CardButton(model.getCard(row, col));
cardBtn.addActionListener(cardButtonActionListener);
add(cardBtn);
}
}
frame.repaint();
}
public class CardButtonActionListener implements ActionListener {
private int i = 0;
CardButton b ,b2;
State currentState;
#Override
public void actionPerformed(ActionEvent e) {
if(i==0){
b = (CardButton) e.getSource();
b.setFaceUp();
JOptionPane.showInputDialog("id "+b.getCard().getValue());
i++;
}
else{
b2 = (CardButton) e.getSource();
b2.setFaceUp();
i--;
}
currentState = model.compareCards(b, b2);
if(currentState == State.Match){
b.setVisible(false);
b2.setVisible(false);
}
if(currentState == State.Close){
b.setFaceDown();
b2.setFaceDown();
}
if(currentState == State.Continue){
}
}
}
}
Class Game:
package model;
import java.util.ArrayList;
import java.util.Collections;
import javax.swing.JPanel;
import view.CardButton;
public class Game extends JPanel {
private Level level;
private ArrayList<ArrayList<Card>> board;
private int rowSize;
private int colSize;
private ArrayList<Card> cardList;
public Game() {
}
public void setLevel(Level level,int x) {
this.level = level;
initBoard(x);
}
private void initBoard(int x) {
// Create board according to level variable
int s = x;
rowSize = s;
colSize = s;
int a=rowSize*colSize/2;
int b=0;
this.board = new ArrayList<ArrayList<Card>>();
for(int row=0; row<s; row++) {
this.cardList = new ArrayList<Card>();
for (int col=0; col<s/2; col++) {
cardList.add(new Card(b));
cardList.add(new Card(b));
b++;
}
board.add(getCardList());
}
Collections.shuffle(board);
}
public ArrayList<Card> getCardList(){
Collections.shuffle(cardList);
return cardList;
}
public int getRowSize() {
return rowSize;
}
public int getColumnSize() {
return colSize;
}
public Card getCard(int row, int col) {
return board.get(row).get(col);
}
public State compareCards(CardButton b, CardButton b2) {
int v1, v2;
v1 = b.getCard().getValue();
v2 = b2.getCard().getValue();
if(b.getCard()!= null && b2.getCard()!= null){
return State.Continue;
}else{
if(v1 == v2){
return State.Match;
}
else{
return State.Close;
}
}
}
}
GamePanel takes state info and decides card which position card should stay: faceup, facedown. But I couldn t implement action performed method(in GamePanel) and compareCards method(in Game) correctly. When I run code, I get null pointer exeption. Because I can not take 2 buttons info. One of them always stays null. I probably need to change this part:
if(i==0){
b = (CardButton) e.getSource();
b.setFaceUp();
JOptionPane.showInputDialog("id "+b.getCard().getValue());
i++;
}
else{
b2 = (CardButton) e.getSource();
b2.setFaceUp();
i--;
}
But I don t know how I can correct. Thank you.
Edit: Whole project is here http://cnv.as/21qoh

The b and b2 variables are null when the CardButtonActionListener is created. So the first time actionPerformed() is called and you fall through your if(i==0) conditional statement, only one of b or b2 gets assigned the return value from e.getSource(). So when you call currentState = model.compareCards(b, b2); either b or b2 will still be null which as you found out will cause that method to throw a null pointer exception.
It looks like this isn't so much a coding error as it is that your design needs some additions. The main reason is each card has its own instance of CardButtonActionListener and when clicked, this listener class isn't aware of any other cards that have already been clicked. For a quick remedy to this you could add a public static Card lastClicked; member variable to your Game class (disclaimer: note I said 'quick', not 'good' as this violates good OOP design and in a multithreaded app would give you tons of trouble... but for a single threaded app like yours if you just want it to work this may be ok for you - but be warned that using public static variables like this is definitely not a good habit to get into). Then you could modify your CardButtonActionListener.actionPerformed() like this (note that I did away with the 'i' variable):
#Override
public void actionPerformed(ActionEvent e) {
CardButton justClickedButton = e.getSource();
justClickedButton.setFaceUp();
CardButton previouslyClickedButton = Game.lastClicked;
if(previouslyClickedButton == null){
JOptionPane.showInputDialog("id "+justClickedButton.getCard().getValue());
previouslyClickedButton = justClickedButton;
}
else{
currentState = model.compareCards(justClickedButton, previouslyClickedButton);
if(currentState == State.Match){
justClickedButton.setVisible(false);
previouslyClickedButton.setVisible(false);
}
if(currentState == State.Close){
justClickedButton.setFaceDown();
previouslyClickedButton.setFaceDown();
}
if(currentState == State.Continue){
}
previouslyClickedButton = null;
}
}

Related

How do I make my two classes function together?

I am working on a text-based videogame and created a GUI for it, however, I am having issues getting my two classes to work together. I have a text field in my GUI class that receives input from the user, which I want to send to my Player class so that it can compare it and execute the appropriate methods. I am still very new to programming, so I hope that some of you out there might be able to help me. Excuse the horrendous code.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class GUI extends Player {
String input;
JLabel message;
public GUI() {
JFrame frame = new JFrame("SPACE GAME");
ImageIcon image = new ImageIcon("rocket.png");
frame.setIconImage(image.getImage());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1200, 800);
frame.setLocationRelativeTo(null);
frame.setFocusable(true);
frame.getContentPane();
JPanel panel = new JPanel();
JTextField commandLine = new JTextField(30);
JLabel message = new JLabel();
frame.add(panel);
panel.setBackground(Color.black);
panel.setLayout(null);
panel.setBorder(BorderFactory.createEmptyBorder(1000, 1000 ,1000 ,1000));
commandLine.setBackground(Color.WHITE);
commandLine.setBounds(5, 730, 300, 30);
commandLine.setBorder(BorderFactory.createLineBorder(Color.GRAY, 3));
commandLine.setFont(new Font("Zig", Font.PLAIN, 18));
commandLine.setForeground(Color.GREEN);
commandLine.setBackground(Color.BLACK);
commandLine.setCaretColor(Color.GREEN);
commandLine.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
}
});
commandLine.setVisible(false);
message.setForeground(Color.GREEN);
message.setText("Welcome to SPACE GAME! Press any key to start.");
message.setBounds(5,665, 1000, 100);
message.setFont(new Font("Courier", Font.PLAIN, 18));
panel.add(message);
panel.add(commandLine);
frame.setVisible(true);
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
commandLine.setVisible(true);
commandLine.requestFocusInWindow();
message.setText("Type \"help\" for help");
}
#Override
public void keyReleased(KeyEvent e) {
}
});
}
public JLabel getMessage(JLabel message) {
return message;
}
}
import javax.swing.*;
import java.util.Scanner;
public class Player {
//Attributes
private Room currentRoom;
Player player;
JLabel message;
// Handles player movement and commands
public void move() {
player = new Player();
Map map = new Map();
GUI gui = new GUI();
player.currentRoom = map.room1;
Scanner input = new Scanner(System.in);
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
System.out.println("Type \"help\" to get help");
//Commands
boolean isGameRunning = true;
while (isGameRunning) {
String goMessage = input.nextLine();
goMessage = goMessage.toLowerCase();
switch (goMessage) {
case "go north", "north", "go n", "n": goNorth(); break;
case "go east", "east", "go e", "e": goEast(); break;
case "go south", "south", "go s", "s": goSouth(); break;
case "go west", "west", "go w", "w": goWest(); break;
case "exit": isGameRunning = false; break;
case "look":
System.out.println(player.currentRoom.getRoomDescription());
break;
case "help":
System.out.println("""
"go (north, south, east, west)" to choose a direction to go.
"look" gives you a description of the room.
"exit" stops the game.""");
break;
default:
System.out.println("Unknown command");
break;
}
}
}
public void goNorth() {
if (player.currentRoom.getNorthRoom() != null) {
player.currentRoom = player.currentRoom.getNorthRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goEast() {
if (player.currentRoom.getEastRoom() != null) {
player.currentRoom = player.currentRoom.getEastRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goSouth() {
if (player.currentRoom.getSouthRoom() != null) {
player.currentRoom = player.currentRoom.getSouthRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goWest() {
if (player.currentRoom.getWestRoom() != null) {
player.currentRoom = player.currentRoom.getWestRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
}
There are some good things in your code as well as several major issues that may hamper your ability to mash a GUI with your current console-program code, and these include:
You do have some separation of the your program logic into separate classes such as Player, Map, and Room classes, and that is a very good thing, but you still could do more separation of the logic, and doing so can simplify your code making it easier to debug and enhance.
The Player class contains a Player instance, I'm not sure why. The outer main class should probably be re-named from Player to the Game class, and the Player class should be a totally separate class, one that holds the state of a single player instance, and shouldn't create instances of itself as you're doing. The key is to strive to "oop-ify" your code, especially the "model" portion of the code, the code that holds the non-user interface program logic, to make it more object-oriented, since this will make it much easier to allow you to connect a GUI or any other interface, to your model.
Your model (again, named Player) has user interface (UI) code within it: it has Scanner input code within it and println statements. This is problematic, especially now since you want to enhance the program and make it work with a GUI. The key thing that you must do, first and foremost, is to separate the user interface code (agian the code that gets input from the user and that displays output to the user) outside of model class, which is, again, class that holds the program logic.
Your Player class has a lot of non-Player code cluttering it. A basic OOPs principle is that a class should have a single responsibility (called the "single responsibility rule"), and so your Player class should only have code that is needed to encapsulate Player state (such as name, Room, perhaps other properties such as strength, weapons, health, intelligence....) and Player behavior (methods that allow Player to interact with other classes), and nothihg more or less.
Going on the same vein, it appears that all the program data and behaviors seem to be hard-wired into this single main Player class that drives the program. This is very limiting and will hinder your ability to create a rich playing environment with many rooms, items in the rooms, and things like this. Divide and conquer -- get that information out of the main program and put it into OOP-compliant classes, including a Room class that has fields for items it might contain, that has knowledge of its own connections to other rooms.
Your GUI program extends the Player object. This is a major problem since this program structure does not pass the basic test of inheritance, the "is-a" test: logically is a GUI a more specific type of player (much like a Dog is a more specific type of Animal, a structure which passes the "is-a" test)? No, it is not, and while this distinction may seem pendantic, you may try to use Player fields within the GUI class because of this inheritance, but if you try to do this, the code will not work correctly. Remove this inheritance, and instead try to connect classes through "composition" where one class holds an instance of another, rather than extends another. So for instance, the GUI could possibly hold a Player variable rather than extend from Player.
Bottom line:
You will want to fully "OOP-ify" your code before trying to add a GUI
In order for the program to be able to well-meshed with a GUI, I would suggest making your underlying logical code (the non-user interface logic code) much more object-oriented with classes that all have a single responsibility, classes that are easily testable in isolation (away from GUI or any user interface). Start with first principles and the program will be much easier to build.
So, for example, classes that could be considered for an adventure game include:
A Direction class, or better a Direction enum.
This would encapsulate the idea of "direction" and would be used for objects in the game to know which direction they are going and to be able to communicate this to other objects using constants, rather than Strings. The user of an enum would allow the compiler to check that directions are being used correctly, since if you used Strings, say, "West", the compiler would not automatically know if you mis-typed the String and used "Best" instead.
Something simple like this, for example:
public enum Direction {
NORTH, EAST, SOUTH, WEST
}
A GameRoom class
This would hold information telling it its location in the grid of rooms, Strings for both name and description properties, and a game player field or a List of game players if more than one are allowed, perhaps something that contains code like this:....
public class GameRoom {
// map with connections to other game rooms
private Map<Direction, GameRoom> connections = new HashMap<Direction, GameRoom>();
// location information
private int x;
private int y;
// identifying information
private String roomName;
private String description;
// holds any player objects that may be in the room
private List<GamePlayer> playersInRoom = new ArrayList<>();
The class will have appropriate constructors as well as getter and setter methods and also:
An addPlayer(...) method that allows the game to add a player into the room:
public void addPlayer(GamePlayer gamePlayer) {
playersInRoom.add(gamePlayer);
gamePlayer.setCurrentRoom(this); // we'll get to this later
}
A public boolean move(...) method that moves a player contained by the room to a connecting room. It first checks that the room actually contains the player being moved, and then next checks if the room has a connection to another room in the requested direction. If either are false, then the method returns false to let the calling code that the attempted move failed. Otherwise, if the move is allowed, it returns true:
public boolean move(GamePlayer gamePlayer, Direction direction) {
// if the room doesn't currently hold this player
if (!playersInRoom.contains(gamePlayer)) {
return false; // invalid move request
}
// if the room doesn't have a connecting room in the requested direction
if (!connections.containsKey(direction)) {
return false; // invalid move request
}
// otherwise, we're good
playersInRoom.remove(gamePlayer);
connections.get(direction).addPlayer(gamePlayer);
return true;
}
A GamePlayer class
This class will hold several properties such as a String for name and a GameRoom field for the current room, constructors, getter and setter methods. It's declaration and fields could look something like:
public class GamePlayer {
private String name;
private GameRoom currentRoom;
//.... other properties
It too should have a move method that calls the currentRoom GameRoom's move method and returns the same boolean, a result that tells the calling code if the move was valid and successful:
public boolean move(Direction direction) {
return currentRoom.move(this, direction);
}
A GameModel class:
This will hold the state of the current game, fields for players, a data structure to hold all the rooms. It can have its own move method.....
Only after creating your OOP-compliant classes should you move to the next step: the creation of your GUI or "view" class/classes. This class could hold a GameModel instance, and would be responsible for 1) displaying the state of the game (a visual representation of the rooms and the items that they hold), and 2) getting input from the user, and passing that input to the game model for processing.
I like to use an M-V-C program structure, which stands for "Model-View-Controller" where the program logic and GUI are kept as separate as possible, and there may be a controller class or classes that help tie the model and view together. Another principle that I try to follow is to keep the GUI as "dumb" as possible. By dumb, I mean that it should get input from the user, perhaps do the most basic of input verifications, and should display the state of the model, but that's it. Almost all the "brains" of the program should be held by the model itself and not the view.
Proof-of-concept Example:
An incomplete but running "proof-of-concept" example of what I am describing above is shown below. You should copy the program whole, paste it into your IDE into a single file named VideoGame.java, and then should be able to run it. It uses some concepts that you may not yet be familiar with, including the use of Key Bindings to get user input from the GUI and the use of PropertyChangeListeners and PropertyChangeSupport objects to allow clean communication between objects (to notify listeners if the state of one of the model object has changed). This program should respond to pressing of the arrow keys:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class VideoGame {
private static final int[][] ROOM_GRID_KEY = {
{ 1, 0, 0, 2, 0, 0, 3, 4 },
{ 5, 6, 7, 8, 9, 10, 11, 0 },
{ 0, 12, 0, 13, 0, 0, 0, 0 },
{ 0, 14, 0, 0, 0, 0, 15, 16 },
{ 17, 18, 0, 19, 0, 0, 20, 0 },
{ 21, 22, 23, 24, 25, 26, 27, 28 }
};
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
GameModel gameModel = new GameModel(ROOM_GRID_KEY);
GameView gameView = new GameView();
new GameController(gameModel, gameView);
JFrame frame = new JFrame("Video Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(gameView);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class GameController {
private GameModel gameModel;
private GameView gameView;
public GameController(GameModel gameModel, GameView gameView) {
this.gameModel = gameModel;
this.gameView = gameView;
ModelListener modelListener = new ModelListener();
gameView.setModel(gameModel);
gameModel.addPropertyChangeListener(modelListener);
}
private class ModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
gameView.modelChange(evt);
}
}
}
#SuppressWarnings("serial")
class DisplayPanel extends JPanel {
private JPanel[][] panelGrid;
private int gridCellSize;
private GameModel gameModel;
private GameRoom[][] roomGrid;
public DisplayPanel(int gridCellSize) {
this.gridCellSize = gridCellSize;
}
public void setModel(GameModel gameModel) {
this.gameModel = gameModel;
this.roomGrid = gameModel.getRoomGrid();
int rows = roomGrid.length;
int cols = roomGrid[0].length;
setBackground(Color.BLACK);
setLayout(new GridLayout(rows, cols, 1, 1));
setBorder(BorderFactory.createLineBorder(Color.BLACK));
panelGrid = new JPanel[rows][cols];
for (int r = 0; r < panelGrid.length; r++) {
for (int c = 0; c < panelGrid[r].length; c++) {
JPanel panel = new JPanel(new GridBagLayout());
panelGrid[r][c] = panel;
panel.setPreferredSize(new Dimension(gridCellSize, gridCellSize));
if (roomGrid[r][c] == null) {
panel.setBackground(Color.BLACK);
} else {
panel.setBackground(Color.PINK);
if (roomGrid[r][c].getPlayersInRoom().size() > 0) {
GamePlayer gamePlayer = roomGrid[r][c].getPlayersInRoom().get(0);
String name = gamePlayer.getName();
JLabel label = new JLabel(name);
panel.add(label);
}
}
add(panel);
}
}
// key bindings code
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), Direction.SOUTH);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), Direction.NORTH);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), Direction.WEST);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), Direction.EAST);
}
private void addBindings(KeyStroke keyStroke, Direction direction) {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
Action action = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
gameModel.move(direction);
}
};
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), action);
}
public void modelChange(PropertyChangeEvent evt) {
for (int r = 0; r < panelGrid.length; r++) {
for (int c = 0; c < panelGrid[r].length; c++) {
JPanel panel = panelGrid[r][c];
if (roomGrid[r][c] != null) {
if (roomGrid[r][c].getPlayersInRoom().size() > 0) {
GamePlayer gamePlayer = roomGrid[r][c].getPlayersInRoom().get(0);
String name = gamePlayer.getName();
JLabel label = new JLabel(name);
panel.add(label);
} else {
panel.removeAll();
}
}
}
}
revalidate();
repaint();
}
public GameModel getGameModel() {
return gameModel;
}
}
class GameView extends JPanel {
private static final int CELL_SIZE = 80;
private DisplayPanel displayPanel = new DisplayPanel(CELL_SIZE);
private GameModel gameModel;
// private JTextField textField = new JTextField();
public GameView() {
setLayout(new BorderLayout());
add(displayPanel);
// add(textField, BorderLayout.PAGE_END);
}
public void setModel(GameModel gameModel) {
this.gameModel = gameModel;
displayPanel.setModel(gameModel);
}
public void modelChange(PropertyChangeEvent evt) {
displayPanel.modelChange(evt);
}
public GameModel getGameModel() {
return gameModel;
}
}
class GameModel {
public static final String GAME_MODEL = "game model";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private GameRoom[][] roomGrid;
private GamePlayer player = new GamePlayer("Fred");
public GameModel(int[][] roomGridKey) {
roomGrid = new GameRoom[roomGridKey.length][roomGridKey[0].length];
// fill room grid with rooms if 1 in grid key array, with null if 0
for (int y = 0; y < roomGridKey.length; y++) {
for (int x = 0; x < roomGridKey[0].length; x++) {
roomGrid[y][x] = roomGridKey[y][x] != 0 ? new GameRoom("Some Room", "Some Description", x, y) : null;
}
}
// make room connections:
for (int y = 0; y < roomGrid.length; y++) {
for (int x = 0; x < roomGrid[0].length; x++) {
GameRoom thisRoom = roomGrid[y][x];
// if no room present, don't
if (thisRoom == null) {
continue;
}
if (x > 0) {
GameRoom otherGameRoom = roomGrid[y][x - 1];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.WEST, otherGameRoom);
}
}
if (x < roomGrid[0].length - 1) {
GameRoom otherGameRoom = roomGrid[y][x + 1];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.EAST, otherGameRoom);
}
}
if (y > 0) {
GameRoom otherGameRoom = roomGrid[y - 1][x];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.NORTH, otherGameRoom);
}
}
if (y < roomGrid.length - 1) {
GameRoom otherGameRoom = roomGrid[y + 1][x];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.SOUTH, otherGameRoom);
}
}
}
}
// put player in top left room
GameRoom currentRoom = roomGrid[0][0];
if (currentRoom == null) {
// some big error occurred
System.err.println("Current room at 0, 0 is null. Exiting");
System.exit(-1);
}
player.setCurrentRoom(currentRoom);
currentRoom.addPlayer(player);
player.addPropertyChangeListener(pce -> pcSupport.firePropertyChange(GAME_MODEL, null, player));
}
public boolean move(Direction direction) {
boolean success = player.move(direction);
return success;
}
public GamePlayer getPlayer() {
return player;
}
public GameRoom[][] getRoomGrid() {
return roomGrid;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(GAME_MODEL, listener);
}
}
class GamePlayer {
public static final String GAME_PLAYER = "game player";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private String name;
private GameRoom currentRoom;
public GamePlayer(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public boolean move(Direction direction) {
boolean success = currentRoom.move(this, direction);
return success;
}
public void setCurrentRoom(GameRoom currentRoom) {
GameRoom oldValue = this.currentRoom;
GameRoom newValue = currentRoom;
this.currentRoom = currentRoom;
pcSupport.firePropertyChange(GAME_PLAYER, oldValue, newValue);
}
public GameRoom getCurrentRoom() {
return currentRoom;
}
#Override
public String toString() {
return "GamePlayer [name=" + name + ", currentRoom=" + currentRoom + "]";
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(GAME_PLAYER, listener);
}
}
class GameRoom {
// map with connections to other game rooms
private Map<Direction, GameRoom> connections = new HashMap<Direction, GameRoom>();
// location information
private int x;
private int y;
// identifying information
private String roomName;
private String description;
// holds any player objects that may be in the room
private List<GamePlayer> playersInRoom = new ArrayList<>();
public GameRoom(String roomName, String description, int x, int y) {
this.roomName = roomName;
this.description = description;
this.x = x;
this.y = y;
}
public boolean move(GamePlayer gamePlayer, Direction direction) {
// if the room doesn't currently hold this player
if (!playersInRoom.contains(gamePlayer)) {
return false; // invalid move request
}
// if the room doesn't have a connecting room in the requested direction
if (!connections.containsKey(direction)) {
return false; // invalid move request
}
// otherwise, we're good
playersInRoom.remove(gamePlayer);
connections.get(direction).addPlayer(gamePlayer);
return true;
}
public void addPlayer(GamePlayer gamePlayer) {
playersInRoom.add(gamePlayer);
gamePlayer.setCurrentRoom(this);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public String getRoomName() {
return roomName;
}
public String getDescription() {
return description;
}
public List<GamePlayer> getPlayersInRoom() {
return playersInRoom;
}
public Map<Direction, GameRoom> getConnections() {
return connections;
}
public void putConnection(Direction direction, GameRoom otherGameRoom) {
connections.put(direction, otherGameRoom);
}
#Override
public String toString() {
return "GameRoom [x=" + x + ", y=" + y + "]";
}
}
enum Direction {
NORTH, EAST, SOUTH, WEST
}
Again, compile and run this code and use the arrow keys to move "Fred" around the grid.
A more complete program would separate the data completely out of the code and allow for file I/O for reading in the room grid into the program, possibly by creating a CSV file or files to hold the room information, or if the data is expected to grow and be more complex, then a relational database such as one that uses one of the flavors of SQL.
Since you've made the GUI extend the player, all you need to do is call the desired function from the ActionListener on the input field.
commandLine.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
if (input == "go north") {
GoNorth();
}
// .....
}
});
However, things are pretty messy. There's no real reason for the GUI to extend the player, for starters. I'd advise you to separate the GUI class from Player. That way, you won't need to use the Scanner or Print, and instead collect all your GUI functionality in the GUI class.
For instance:
public void goNorth() {
if (player.currentRoom.getNorthRoom() != null) {
player.currentRoom = player.currentRoom.getNorthRoom();
gui.print("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription())
} else {
gui.print("You cannot go that way");
}
}
You would need to implement a print function in the GUI class, that added a line of text to the message box, but I'm sure you were planning on that to begin with.
In the GUI class, you would then have a reference to the player, so reading the input field would look something like this:
commandLine.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
switch(input) {
case "go north":
player.goNorth();
break;
...
}
}
});

Removing object from a collection while iterating over it

I'm trying to remove objects that are outside of the JPanel.
However, when I do that I get this error
and my program crashes.
I was told by my lecturer that it's because two Threads are accessing the ArrayList that stores my objects.
I did to synchronize the functions but it didn't work.
Bullet
public void move(){
if(y< -height){
synchronized (this) {
bullet.remove(this);
}
}
y-=5;
}
Relevant Classes:
Application
import javax.swing.*;
public class Application {
public static String path ="C:\\Users\\jarek\\OneDrive\\NUIG Private\\(2) Semester 2 2019\\Next Generation Technologies II CT255\\Assignment 3\\";
private Application(){
JFrame frame = new JFrame("Ihsan The Defender");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GamePanel gamePanel= new GamePanel();
frame.add(gamePanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
new Thread(gamePanel).start();
}
public static void main (String args[]){
new Application();
}
}
GamePanel
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.security.Key;
import java.util.ArrayList;
public class GamePanel extends JPanel implements Runnable, KeyListener{
private String path = Application.path;
Image gameOverImg = new ImageIcon(path+"//images//gameover1.png").getImage();
private Ihsan ihsan;
private ArrayList <David> david = new ArrayList<>();
private int enemies=5;
private boolean pause=false;
private boolean gameOver=false;
GamePanel(){
ihsan = new Ihsan(this);
for(int i=0; i<enemies; i++){
david.add(new David(this));
}
setFocusable(true);
requestFocusInWindow();
addKeyListener(this);
}
#Override
public void run() {
while (!pause){
repaint();
for(David david:david){
david.move();
}
for(Bullet bullet:Bullet.bullet){
bullet.move();
}
try{Thread.sleep(30);}
catch (InterruptedException e){}
}
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.GRAY);
g2d.fillRect(0,0 ,getWidth(), getHeight());
for(David david : david){
g2d.drawImage(david.getImg(), david.getX(), david.getY(), null);
}
g2d.drawImage(ihsan.getImg(), ihsan.getX(), ihsan.getY(), null);
for (Bullet bullet:Bullet.bullet){
g2d.drawImage(bullet.getImg(), bullet.getX(), bullet.getY(), null);
}
if(gameOver){
g2d.drawImage(gameOverImg,0,getHeight()/4,null);
}
}
private static final Dimension DESIRED_SIZE = new Dimension(600,700);
#Override
public Dimension getPreferredSize(){
return DESIRED_SIZE;
}
public void setGameOver(boolean gameOver) {
this.gameOver = gameOver;
}
#Override
public void keyPressed(KeyEvent e) {
int key=e.getKeyCode();
if (key==KeyEvent.VK_D || key==KeyEvent.VK_RIGHT){
ihsan.move(4,0);
System.out.println("Right Key");
}
if (key==KeyEvent.VK_A || key== KeyEvent.VK_LEFT){
ihsan.move(-4,0);
System.out.println("Left Key");
}
if(key==KeyEvent.VK_SPACE){
Bullet.bullet.add(new Bullet(this,ihsan.getX()+(ihsan.getWidth()/2), ihsan.getY()));
}
}
#Override
public void keyTyped(KeyEvent e) { }
#Override
public void keyReleased(KeyEvent e) { }
public boolean getGameOver(){
return gameOver;
}
}
Bullet
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class Bullet {
//Environment
public static ArrayList<Bullet> bullet = new ArrayList<>();
private String path = Application.path;
private GamePanel gp;
//properties
private int x,y;
private int width,height;
private int yVector;
private Image image;
Bullet(GamePanel gp, int x, int y){
image = new ImageIcon(path+"\\images\\javaicon.png").getImage();
width=image.getWidth(null);
height=image.getHeight(null);
this.gp=gp;
this.x=x;
this.y=y;
yVector=5;
}
public void move(){
if(y< -height){
bullet.remove(this);
}
y-=5;
}
public Image getImg(){
return image;
}
public int getX(){
return x;
}
public int getY(){
return y;
}
}
Your current problem is not with synchronization, but that you modify the bullet list while iterating over it:
// GamePanel.java#run():
for (Bullet bullet:Bullet.bullet) { //your code is iterating over Bullet.bullet here
bullet.move(); //you call Bullet#move here
}
// Bullet.java#move():
public void move(){
if(y< -height){
bullet.remove(this); //this will remove the current bullet from Bullet.bullet
// ultimately causing the ConcurrrentModificationException in GamePanel.run()
}
y-=5;
}
Synchronization won't help, since both actions occur within the same thread.
To solve this issue the Bullet.move() method needs to return a boolean indicating whether it should be removed from the list. And GamePanel.run() must not use an enhanced for loop but an iterator (removing an element from a list using Iterator.remove() is safe if this is the only active Iterator):
// Bullet.java#move():
public boolean move(){
if(y< -height){
return true; // instruct GamePanel.run() to remove this bullet
}
y-=5;
return false; // keep this bullet
}
// GamePanel.java#run():
Iterator<Bullet> it = Bullet.bullet.iterator();
while (it.hasNext()) {
Bullet bullet = it.next();
if (bullet.move()) { // if bullet should be removed
it.remove(); // remove it from the list
}
}
There are other issues too:
you call #repaint() from your own thread instead of the Swing EDT
repainting iterates over the same Bullet.bullet list without synchronization (which may lead to a ConcurrentModificationException within GamePanel.paint())
The synchronized block must be around every piece of code that accessed or modifies the ArrayList. The object put in the parenthesis must be the same: It’s the lock.
Create a field of type Object named bulletLock for example, and use it as a lock, every time you access bullet.
The error occurs because you’re removing a bullet while another thread is in a for-loop on the list. As there is a concurrent modification, it can’t continue safely.
Another solution would be to make a copy of the ArrayList before your for-loop.
A simple solution to the problem described in Thomas Kläger answer could be:
for(Bullet bullet: new ArrayList(Bullet.bullet) ){ //iterate over a copy
bullet.move();
}
Alternatively:
Iterator<Bullet> it = Bullet.bullet.iterator();
while (it.hasNext()) {
Bullet bullet = it.next();
bullet.move();
}
without changing other parts of the code.

Error when adding event handler into a JButton to repaint the image inJava GUI

I've created 2 JButtons.One of them has the function of a button and the other handles an image.I want that image to change when this button is clicked.So inserted the method repaint() and added a listener to the first JButton to change the image.When trying to add the listener or the event handler to the first JButton nothing happens.So the image doesn't change.Can anyone show me how can I insert this listener in a way that it works(changes the image when the button is clicked)?Here is my code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.*;
import java.util.Random;
public class Back extends JFrame{
private Random ran;
private int value;
private JButton r;
private JButton c;
public Back(){
super("title");
ran = new Random();
value = nextValue();
setLayout(new FlowLayout());
r=new JButton("ROLL");
add(r);
Icon i=new ImageIcon(getClass().getResource("1.png"));
Icon img=new ImageIcon(getClass().getResource("2.png"));
c= new JButton(i);
if (value==1){
c= new JButton(i);
}
else if(value==2){
c= new JButton(img);
}
add(c);
thehandler hand=new thehandler(this);//konstruktori i handler merr nje instance te Background
r.addActionListener(hand);
c.addActionListener(hand);
}
private int nextValue() {
return Math.abs(ran.nextInt()) % 6 + 1 ;
}
public void roll() {
value = nextValue() ;
repaint() ;
}
public int getValue() {
return value ;
}
private class thehandler implements ActionListener{
private Back d;
thehandler(Back thisone) {
d = thisone ; }
public void actionPerformed(ActionEvent event) {
d.roll() ;
}
}
public static void main(String[] args) {
Back d = new Back() ;
d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
d.getContentPane().setBackground(Color.GREEN);
d.setSize(700,500);
d.setVisible(true);
}
}
So, basically, all your code comes down to here...
public void roll() {
value = nextValue();
repaint();
}
This calculates a new random value and calls repaint. But nothing in your code is effected by value at the point in time it's called.
Instead, you need to update the state of some control, maybe something more like...
public void roll() {
value = nextValue();
Icon i = new ImageIcon(getClass().getResource("1.png"));
Icon img = new ImageIcon(getClass().getResource("2.png"));
if (value == 1) {
c.setIcon(i);
} else if (value == 2) {
c.setIcon(img);
}
}
The next thing I would do is store all your images in some kind of array or List to make it easier to access, then you could simply do something
like...
public void roll() {
value = nextValue();
c.setIcon(listOfImages.get(value - 1));
}
Maybe have a look at Java Swing Timer and Animation: how to put it together for a more detailed example

Prevent switch from a specifc toggle button in button group unless a condition is met

I'd like to create a group of two buttons (A and B), where switching from B to A is always allowed, but switching from A to B is dependent on a condition. The condition may only be checked in the latter case (A to B) and the check may only be done once per switch attempt. If the switch is prevented, there must be no ItemEvent.SELECTED events generated for either of the buttons.
Seems pretty straightforward, so I am baffled as to why I haven't been able to do this in a simple and concise way. I thought extending ButtonGroup was the way to do this but now I'm not sure anymore.
import java.awt.FlowLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
public class ToggleGroup extends JFrame {
private ButtonGroup group = new MyButtonGroup();
private JToggleButton buttonA = new JToggleButton("A");
private JToggleButton buttonB = new JToggleButton("B");
public ToggleGroup() {
setLayout(new FlowLayout());
add(buttonA);
add(buttonB);
group.add(buttonA);
group.add(buttonB);
group.setSelected(buttonA.getModel(), true);
pack();
setLocationRelativeTo(null);
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
System.out.println("-> " + (e.getSource() == buttonA ? "A" : "B") + " selected");
}
}
};
buttonA.addItemListener(itemListener);
buttonB.addItemListener(itemListener);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ToggleGroup test = new ToggleGroup();
test.setVisible(true);
}
});
}
private class MyButtonGroup extends ButtonGroup {
private boolean check() {
int result = JOptionPane.showConfirmDialog(
ToggleGroup.this, "May I switch to B?",
"Important question", JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
return result == JOptionPane.YES_OPTION;
}
#Override
public void setSelected(ButtonModel m, boolean b) {
if (!b) {
return;
}
if (m == buttonA.getModel() || m == buttonB.getModel() && check()) {
super.setSelected(m, b);
}
}
}
}
The problem with my code is obvious. The condition is checked multiple times, therefore the dialog is also shown multiple times.
So how can I "consume" a switch attempt when the condition fails?
EDIT:
The context in which I'm using these buttons is an implementation of switching between different modes of an application. One of the modes enables data to be changed and later committed. If uncommitted changes exist, switching modes might imply data loss. I'd like to make sure that the switch was intentional. Disabling either of the buttons until the condition is met is not an option.
Interesting ... digging a bit turns out that the interaction of selected/armed/pressed is somewhat confused by the interrupted check (faintly remember some bug, but can't find it right now). The main issue is to not allow the re-entry into the setSelected method. A dirty (read: didn't to dig further) way out is to have toggle a flag, something like
private boolean isChecking;
#Override
public void setSelected(final ButtonModel m, boolean b) {
if (isChecking) return;
isChecking = false;
if (!b) {
return;
}
if (m == buttonB.getModel()) {
isChecking = true;
final boolean select = check();
if (select) {
superSetSelected(m, select);
}
isChecking = false;
return;
} else {
superSetSelected(m, b);
}
}
protected void superSetSelected(ButtonModel m, boolean b) {
super.setSelected(m, b);
}
After consulting the implementation of ButtonGroup I realized that calling ButtonGroup.setSelected(ButtonModel, boolean) might spawn new calls of the same method, leading to my condition being checked up to three times. So I now guard against subsequent calls. Seems to work. A little iffy, though.
private class MyButtonGroup extends ButtonGroup {
private Stack<ButtonModel> locked = new Stack<ButtonModel>();
#Override
public void setSelected(ButtonModel m, boolean b) {
if (!b || !locked.isEmpty() && locked.peek() == m) {
return;
}
locked.push(m);
if (m == buttonA.getModel() || m == buttonB.getModel() && check()) {
super.setSelected(m, b);
}
locked.pop();
}
}

Modifying a JTextField after reading String from another JTextField

Having some problems updating a JTextField in a different class after reading a String from another JTextField. Here's the method in question:
public JTextField buyVowel(playerPlate player)
{
String get = input.getText();
String[] vowels = new String[]{"a","e","i","o","u"};
for(int i =0; i<vowels.length; i++)
{
if(get.equals(vowels[i]))
{
player.pMoney =- 250;
player.playerMoney.setText("$"+player.pMoney);
}
}
return player.playerMoney;
}
playerPlate is a separate class.
I'm using this method to determine what player the program should be modifying:
public playerPlate getCurrentPlayer()
{
if(currentPlayer == 1)
{
return player1;
}
else if(currentPlayer == 2)
{
return player2;
}
else
{
return player3;
}
}
player(s) 1, 2, and 3 are instances of playerPlate.
I want it to be modifying instance variables in this class:
package wheelOfFortune;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class playerPlate extends JPanel
implements ActionListener
{
public String pName;
public int pMoney = 500;
public int currentPlayer;
public JTextField playerMoney;
public playerPlate(String player, Color color, int currentPlayer)
{
setBorder(BorderFactory.createLineBorder(Color.BLACK,2));
setBackground(color);
pName = player;
JTextField playerNames = new JTextField(pName);
playerNames.setBorder(BorderFactory.createLineBorder(Color.BLACK,2));
playerNames.setEditable(false);
playerNames.setFont(new Font("Impact", Font.PLAIN, 24));
playerNames.setHorizontalAlignment(JTextField.CENTER);
playerNames.setBackground(Color.WHITE);
JTextField playerMoney = new JTextField("$"+pMoney);
playerMoney.setBorder(BorderFactory.createLineBorder(Color.BLACK,2));
playerMoney.setEditable(false);
playerMoney.setFont(new Font("Impact", Font.BOLD, 32));
playerMoney.setHorizontalAlignment(JTextField.CENTER);
playerMoney.setBackground(Color.WHITE);
Box b1 = Box.createVerticalBox();
b1.add(playerNames);
b1.add(Box.createVerticalStrut(5));
Box b2 = Box.createHorizontalBox();
b2.add(Box.createHorizontalStrut(60));
Box b3 = Box.createVerticalBox();
b3.add(playerMoney);
b3.add(Box.createVerticalStrut(8));
b2.add(b3);
b1.add(b2);
b1.add(Box.createVerticalStrut(5));
add(b1);
}
public void actionPerformed(ActionEvent e)
{
}
}
Here is the actionPerformed method within the main class:
public void actionPerformed(ActionEvent e)
{
JButton b = (JButton)e.getSource();
if(b==spin)
{
spinWheel(wheelStuff);
repaint();
}
if(b==next)
{
updatePlayer();
repaint();
}
if(b==reset)
{
letterBoard.reset();
updateCat();
repaint();
}
if(b==buyVowel)
{
buyVowel(getCurrentPlayer());
repaint();
}
}
The gist of what I want to happen, is when the user types a vowel into JTextField input, and clicks JButton buyVowel it subtracts $250 from their total money (pMoney). And displays the change on the GUI. After tinkering with this for a couple hours, I honestly have no idea why this isn't working. I keep receiving nullPointerExceptions when trying to use it. Thanks for your help.
Note: everything except for class playerPlate is in the same class. playerPlate is in a separate class.
You're shadowing the variable playerMoney in the constructor of playerPlate. The method buyVowel relies on playerPlate being instantiated when invoking setText, otherwise a NullPointerException will be thrown. Replace
JTextField playerMoney = new JTextField("$"+pMoney);
with
playerMoney = new JTextField("$"+pMoney);
Aside: Java naming conventions indicate that class names start with uppcase letters so use class names such as PlayerPlate.

Categories