A frequent problem I encounter when programming is how to handle an unknown number of objects. By handling I mean referencing them, manipulating them etc. As for me, this would be when developing smaller games and programs.
Currently I am working on a score-keeping program, which should display the names of the players, their score as well and various other features. Furthermore, there should be two buttons that allow for adding and removing players from the score table which is what I'll be focusing on here. It might look something like this:
//A very limited version of my program
import java.awt.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
class Application extends JFrame implements ActionListener{
//Fields, variables and components
Container mainCont = getContentPane(); //Main container, the window itself
private JPanel buttonPanel;
private JPanel namePanel;
private JButton addPlayerButton;
private JButton removePlayerButton;
//...
//Many more components
public Application(){
//Basic window initiation
setTitle("Score Keeper");
this.setSize(650, 700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainCont.setBackground(Color.BLACK);
this.setContentPane(mainCont);
buttonPanel = new JPanel();
namePanel = new JPanel();
addPlayerButton = new JButton();
addPlayerButton.addActionListener(this);
buttonPanel.add(addPlayerButton);
removePlayerButton = new JButton();
removePlayerButton.addActionListener(this);
buttonPanel.add(removePlayerButton);
this.add(buttonPanel);
this.add(namePanel);
this.setVisible(true);
//Other code omitted for now
//Includes other graphic components, layout managers etc.
}
/*
* Action-Listener.
* Performs an event on an action.
*/
#Override
public void actionPerformed(ActionEvent event){
if(event.getSource() == addPlayerButton){
Application.Player newPlayer = this.new Player(); //Creates a new object, i.e. a new player
//See below for Player class
}
if(event.getSource() == removePlayerButton){
//******
// This is where the problem lies
}
}
//I use a nested class to create a new player
public class Player{
//Components etc.
private String name;
private JLabel nameLabel;
public Player(){
name = getName();
nameLabel = new JLabel(name);
namePanel.add(nameLabel);
}
public String getName(){
//This basically gets input from the user to assign a name to the new player
//Code omitted for now
}
}
}
So far all is good. This program basically only has two buttons, where the addPlayerButton adds a player object, which has a name that is displayed on the screen. Every time this button is pressed a new player is added to the screen. And this can be done an indefinite number of times.
The problem comes when we want to remove a player. How can we do that? We can't reference it by name, as all player objects are practically anonymous.
The alternative, of course, would be to pre-define a fixed amount of player objects:
class Application extends JFrame implements ActionListener{
//Fields, variables and components
Container mainCont = getContentPane(); //Main container, the window itself
private JPanel buttonPanel;
private JPanel namePanel;
private JButton addPlayerButton;
private JButton removePlayerButton;
private Player player1;
private Player player2;
private Player player3;
//...
//Etc.
Then we would be able to directly address each player object, but this is simply too impractical. We cannot add more players than the pre-defined amount, and if we want fewer players we have a bunch of player objects that are never used. Furthermore, we would have to hardcode every initiation of every player - every nameLabel would have to be manually added to the screen etc.
Please share your knowledge on how you handle this kind of problem, of how you deal with an unknown number of objects.
Thanks for taking the time and for the help!
P.S. I'm still pretty new to this forum. Please let me know if there is anything with this question I can change etc. I did my research and found no previous question that tackled this, but if there is one I missed feel free to let me know!
EDIT 1: Okay. There were a lot of great answers. I chose the one using hashmaps as the right solution, since I consider this the best solution for the premises I provided. The way I actually solved my problem is that I added, to the player object, a JButton that removes the player object it is stored in. I also scrapped the concept of using a nested class for the player and just implemented it in a separate class.
What I've learnt overall, though, is that when handling objects and you don't know the amount of objects it is generally best to store them in a collection of some sort. My preference is the Hashmap, as it provides an easy way of accessing the object based on one of its properties, like a String name or similar.
You can use a Map/Hashmap and each time you create a player, add them to the map.
You also have to change from directly drawing the player on the screen, probably to drawing all the players in the Map, that way when a player gets removed from the Map it would no longer be drawn.
You would do something like this:
Map<String, Player> map = new HashMap<>();
map.put(player.Name, player);
And then you would draw everything in that hashmap. To remove you just need to provide the name of the player to remove.
map.remove(player.Name);
And of course then you would change your code a bit to render everything inside the map, I believe you need a way to know which player is to be removed, you might want to add a text field to input the name of the player to be removed.
What you could do if you want to delete a Player based on it's name is the following:
// Create a list of players, which you can define globally
ArrayList<Player> players = new ArrayList<>();
// The name of the player to find
String name = "theNameOfThePlayerToFind";
// Loop through the players and remove the player with the given name
for (Player player : players) {
if (player.getName().equals(name)) {
players.remove(player);
}
}
You can also add new players to lists easily:
players.add(new Player());
I would #Katada Freije method of using a HashMap. Just to elaborate a little, you basically have a collection of Players with their names as a key. You then use the key to remove the Player.
But I might also avoid this as some scenarios have multiple Players with the same name. I'd go with a List<Player>. This way the Player will be defined by the index rather than the name. You'd then use the index to remove the player with some in inbuilt methods.
Let us assume that you are using a JList to display the current players. Java's Swing separates the model (where the objects that are displayed are actually stored) and the view and control (the JList that displays them). This design is called MVC, and is very common.
We have different ways of storing the actual Player objects. Which to choose depends on how you plan to manipulate your players. The most straightforward is to use an array, but that only works if you will never have too many players:
Player[] players = new Player[MAX_PLAYERS](); // define MAX_PLAYERS somewhere
int currentPlayers = 0; // always < MAX_PLAYERS
To expose this to the JList, you would use a custom adapter model as follows (in an inner class with access to the players array):
private final class PlayerListModel extends AbstractListModel<Player> {
#Override
Player getElementAt(int position) { return players[position]; }
#Override
int getSize() { return currentPlayers; }
}
You can then pass this to the JList at construction time:
private PlayerListModel playerListModel = new PlayerListModel();
private JList playersListView = new JList(playerListModel);
Now, to remove a player by name, you would first update the model, and then cause the view to be refreshed:
private void removePlayerByName(String name) {
int toRemove = -1;
for (int i=0; i<currentPlayers; i++) {
Player p = players[i];
if (p.getName().equals(name)) {
toRemove = i;
break;
}
}
if (toRemove != -1) {
// update model
currentPlayers --;
players[toRemove] = players[currentPlayers];
// update views
playerListModel.fireContentsChanged(this, toRemove, currentPlayers);
}
}
Instead of a players array, it would be much easier and safer to use an ArrayList<Player> players. However, if you are naming your variables player1, player2 and so on, I think that you should begin with arrays. If you want to have much faster lookup of players, then a TreeMap<String, Player> would keep them sorted by name and easy to find. You would, in both cases, have to update the model and the removePlayerByName function accordingly. For example, if using the TreeMap, it would be much shorter (and faster):
private void removePlayerByName(String name) {
if (players.containsKey(name)) {
players.remove(name);
playerListModel.fireContentsChanged(this, 0, currentPlayers);
}
}
On the other hand, it is far more common to find interfaces where you click on the player that you want to remove to select it, and then click the remove button to actually do so. You can know which player was selected with this code:
Player selected = playersListView.getSelectedValue();
If there is a selection (selected != null) you could either call removePlayerByName(selected.getName()), or even better, code a removePlayer(Player p) that did not rely on names, but on a (currently missing) implementation of Player.equals(Player another).
Related
I am very new to Java, so sorry if this is stupid and obvious or worded poorly. I don't really know enough yet to know what I don't know.
So I decided that since I have to learn Java, I'd just jump in head first and try to figure it out as I go. So far, it's worked decently. I'm trying to reinforce some basic concepts I already know by writing small programs that do trivial stuff.
I decided I'd write a little text based adventure game and it's working well so far. I'm using Scanners and Switches to call methods that use Scanners and Switches to call other methods. That's all working fine.
So far it's a very linear straight line, like an old choose your own adventure book. But, I wanted to add a player inventory. I have a very vague idea of how to do it, but I have a pretty solid idea of what I want it to do.
So, basically I want to store a piece of information that says the player has a specific item. I want to be able to test for the presence of more than one item at once. And I want to be able to tell the player what items he has at any point in the game.
I don't really know how to ask the question better.
My best guess is doing something like
int key, potion;
key = 0
potion = 2
and then testing the values of each one
if (key = 0) {
System.out.println("you don't have the key ");
}
if (key > 0) {
System.out.prinln("You unlock the door");
}
I'm doing each new room as a separate method, so the whole game is just a big chain of methods. So my hope is that the information about items can be stored in a separate method that I can access through switches or if/else in the current room method the player is in. So, the player is unlocking a door in room2, which is its own method, and he picked up the key in room1, which is its own method, and the key is stored as an integer in the inventory method. But the key was one use, so the key integer is set to 0 and the method room3 starts. If that makes any sense.
Again, sorry if this is really stupid basic stuff. I'm very new to programming.
No problem and I applaud you for choosing to learn programming. This is basic data structures. If you want to hold a value, in most programming languages, you'll have an array. I think breaking your logic down is a good idea i.e., (store an item, test for > 1, list items). The first step is making this as simple as possible and than maybe adding getters/setters later through refactors. Ultimately, your goal is to make the most basic code work first (like this) and than refactoring towards an object oriented class with getters/setters and/or a HashMap.
1:
public class PlayerInventory
{
private String[] inventoryStr = new String[20]; // basic implementation
inventoryStr[0] = "Phone";
inventoryStr[0] = "Book";
}
2:
int arrayLength = inventoryStr.length;
3:
for(int i=0; i < inventoryStr.length; i++) {
System.out.println( inventoryStr[i] );
}
Refactor (after you write unit tests for this)
1*: (with a list)
import java.util.*;
import java.util.*;
public class CollectionGetterSetter {
private List<String> playerInventory;
public void setPlayerInventory(List<String> inv) {
this.playerInventory = inv;
}
public List<String> getPlayerInventory() {
return this.playerInventory;
}
public static void main(String[] args) {
CollectionGetterSetter app = new CollectionGetterSetter();
List<String> PlayerInventory = new ArrayList();
PlayerInventory.add("phone");
PlayerInventory.add("book");
PlayerInventory.add("glasses");
PlayerInventory.add("nav");
app.setPlayerInventory(PlayerInventory);
System.out.println("Player 1: " + PlayerInventory);
List<String> PlayerInventory2 = new ArrayList();
PlayerInventory2.add("cap");
PlayerInventory2.add("gown");
PlayerInventory2.add("foo");
PlayerInventory2.add("bar");
}
}
I have a JPanel on which I've dynamically added quite a few JButtons. All of this is working perfectly. Later on in my program execution, I need to refer back to these buttons and pull out the button text. I'm having trouble figuring out how to refer back to them.
When I created each button, I gave it a unique name. Let's say this is the code where I created the button:
public void createButton(Container parent, String btnName) {
JButton btn = new JButton("xyz");
btn.setName(btnName);
btn.addActionListner(new ActionListner() {
//code
}
parent.add(btn);
}
In another method, I'm trying to retrieve the label on the button since it may have changed at run time. Do I need to keep an array of these buttons as they are created? Or is there a way that I can refer back to them directly?
This is what I was working on, but it's stupid. Can anyone suggest a correct approach?
public String getBtnLabel(String btnName) {
JButton btn = (JButton) btnName;
return btn.getText();
}
If the answer is that I just need to create the array and then iterate over it, that's fine. Just looking for other options.
You need to use a Map<String, JButton> so when you create your dynamic buttons you give them some sort of unqiue name:
//somewhere at the top of your class
private final Map<String, JButton> myButtonMap = new HashMap<>();
public void createButton(Container parent, String btnName) {
JButton btn = new JButton("xyz");
btn.setName(btnName);
btn.addActionListner(new ActionListner() {
//code
}
parent.add(btn);
myButtonMap.put(btnName, btn);
}
And then simply get from the map
public String getBtnLabel(String btnName) {
return myButtonMap.get(btnName).getText();
}
This will obviously throw an NPE if the button isn't defined...
Also you will need to delete from your map when you're done with it otherwise you're asking for a memory leak...
I suggest you to use a Map< String, JButton >.
At creation time you put new button into it with buttons.put( name, btn )
In event handler you use JButton btn = buttons.get( name )
Yes you need to keep references to the buttons. An array would be an option, but since arrays are awkward to use, you should prefer a List.
If you have a a reference to the JPanel containing the buttons, you could get them from it. but that is likely to be rather bothersome.
I would recommend keeping a list of your buttons or a reference to them in a map, however you could do this:
for (Component i : parent.getComponents()) {
if (i.getName().equals(btnName)) {
JButton b = (JButton) i;
// do stuff..
}
}
Using the parent component and iterating over the added components.
This is my first attempt at a decent GUI for a Java app and I needed to use JLists with custom ListModels in order to represent certain structures.
//The 2 below structures implement the ListModel interface, using an internal
//ArrayList, in order to be used as
//a model for 2 different JLists in my GUI.
private PropertyList propertiesList = new PropertyList();
private SelectedProperties selProperties = new SelectedProperties();
//and these are the two JLists they are the models for
private javax.swing.JList Properties_JList;
private javax.swing.JList SelectedProperties_JList;
Here I populate my first JList via a stream:
private void OpenFile_MenuItemActionPerformed(java.awt.event.ActionEvent evt) {
final JFileChooser fc = new JFileChooser();
fc.setCurrentDirectory(null);
int returnVal = fc.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
this.Properties_JList.setModel(propertiesList);
this.propertiesList.AddFromFile(file);
} else {
//...
}
}
which happens to be working perfectly fine. I import a few entries by reading the file and they are all displayed as expected in a .toString() representation.
The problem is the second JList:
private void AddToSelected_JButtonActionPerformed(java.awt.event.ActionEvent evt) {
Property p = (Property) this.Properties_JList.getSelectedValue();
this.SelectedProperties_JList.setModel(selProperties);
this.selProperties.InsertProperty(p);
this.SelectedProperties_JList.revalidate();
}
Which appears to be displaying only the very first item I attempt to add to it through the above button event, and I have no idea why. I considered moving both .setModel(...) calls right after the form's initComponents() call but if I do that none of the lists gets populated, at all.
Logging messages made it clear that the internal structures are getting populated, but even though they are both respective ListModels for my JLists, one of them isn't working as expected.
A sufficient portion of the code is generated by Netbeans and I have spent hours looking up the API but still have trouble finding out what I'm doing wrong. Any ideas?
I have a JFrame with a menubar, in which i'd like some dynamic menus, as in, depending on the size of the ArrayList with HashLists. The problem here is that i then got a dynamic amount of JMenuItems, so i need a way to get as much variables as HashLists. In this case i made the name of the variable static, but obviously that's not working on the ActionListener, since all MenuItems will have the same ActionListener.
I'm wondering how to solve this, and how to get a menu with a dynamic amount of menuitems which all have unique actionlisteners.
private ArrayList<HashMap> menuLijst;
.
for (HashMap h : menuLijst) {
String vraag = (String) h.get("vraag");
JMenuItem qMenu = new JMenuItem(vraag);
informatie.add(qMenu);
qMenu.addActionListener(this);
}
Thanks in advance.
Depending on what you want to do in your ActionListener, you can either use this, as you do right now and in the actionPerformed you can then use ((JMenutItem)event.getSource()) to see which menu item has been clicked. Alternatively, you could register as many ActionListeners as there are menus, like this:
for (final HashMap h : menuLijst) {
final String vraag = (String) h.get("vraag");
final JMenuItem qMenu = new JMenuItem(vraag);
informatie.add(qMenu);
qMenu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
// here event.getSource() is the same as qMenu
System.out.println("Menu "+qMenu+" with label "+vraag+" has been pressed");
System.out.println("HashMap is "+h);
}
});
}
But to me (and also seeing your previous questions), it seems that you are abusing the usage of HashMap instead of using appropriate new objects. I don't know what else is in your HashMap, let's say that you have 3 keys: "vraag", "answer", "grade", you could create the following class:
public class MyClass {
private String vraag;
private String answer;
private int grade;
// And getters and setters here.
}
And have a List<MyClass> instead of List<HashMap>.
I don't see why you want to use a HashMap for your Strings. If you save an ArrayList of Strings, and loop over them to add them all to the menu, you can add actionListeners to all of them, just as you are doing now.
In your ActionListener, check which button is clicked by looping through the ArrayList, and comparing to the name of the clicked button.
I am using an ArrayList as my "inventory".
I am having trouble figuring out a way to add multiples of the same item without taking up a spot in the "inventory". For example: I add a potion to my inventory. Now I add ANOTHER potion but this time instead of adding ANOTHER potion to the inventory, it should instead show that I have: Potions x 2, while only taking up one spot in the ArrayList. I have come up with a few solutions but I feel as if they are bad practices. One solution I tried was to add an AMOUNT variable to the item itself and increment that. Help me find a much better solution?
EDIT: Ok please ignore the above. I have gotten pretty good answers for that but what surprised me was that there were almost no tutorials on role playing game inventory systems. I've done a lot of google searching and cannot find any good examples/tutorials/source code. If anyone could point me to some good examples/tutorials/source code (does not matter what language, but preferable java, or even c/c++) I would appreciate it, thanks. Oh, and any books on the subject matter.
The usual way to solve this (using the standard API) is to use a Map<Item, Integer> that maps an item to the number of of such items in the inventory.
To get the "amount" for a certain item, you then just call get:
inventory.get(item)
To add something to the inventory you do
if (!inventory.containsKey(item))
inventory.put(item, 0);
inventory.put(item, inventory.get(item) + 1);
To remove something from the inventory you could for instance do
if (!inventory.containsKey(item))
throw new InventoryException("Can't remove something you don't have");
inventory.put(item, inventory.get(item) - 1);
if (inventory.get(item) == 0)
inventory.remove(item);
This can get messy if you do it in many places, so I would recommend you to encapsulate these methods in an Inventory class.
Good luck!
Similar to aioobe's solution, you can use TObjectIntHashMap.
TObjectIntHashMap<Item> bag = new TObjectIntHashMap<Item>();
// to add `toAdd`
bag.adjustOrPutValue(item, toAdd, toAdd);
// to get the count.
int count = bag.get(item);
// to remove some
int count = bag.get(item);
if (count < toRemove) throw new IllegalStateException();
bag.adjustValue(item, -toRemove);
// to removeAll
int count = bag.remove(item);
You can create a multiples class.
class MultipleOf<T> {
int count;
final T t;
}
List bag = new ArrayList();
bag.add(new Sword());
bag.add(new MultipleOf(5, new Potion());
Or you can use a collection which records multiples by count.
e.g. a Bag
Bag bag = new HashBag() or TreeBag();
bag.add(new Sword());
bag.add(new Potion(), 5);
int count = bag.getCount(new Potion());
You're probably better off creating a class called InventorySlot, with a quantity and contents field. This also give you the flexibility of adding other properties, such as what the inventory slot can contain, should you decide to create a 'potions' only sack or something like that.
Alternatively, a StackCount and a boolean IsStackable, or perhaps MaxStack property is used in quite a few MMO's, it's a perfectly valid technique too.
or an class InventoryField with an item and an integer for the amount.
public class InventoryField{
int count;
Item item;
}
public class Inventory extends ArrayList<InventoryField>{
...
}
How about the following
public class Item{
int count;
String name;
}
Then have a list representing the inventory
public class Player {
List<Item> inventory = new ArrayLis<Item>();
}