java swing keylistener 2d boulderdash - java

I'm looking to make a boulderdash problem but first i'm just trying to get the player to move around, he's allowed to move to any space that is not a rock(1) or a wall(0).
My up and left motions work fine but right and down are screwed up, they're moving multiple spaces even if the key is only pressed once. Here's a visualization if it helps, i'm posting all the relevant code but i'm guessing the bug is in he keylistener section
Here's the code i uploaded on another website http://textuploader.com/13k1
I spent several minutes trying to copy paste the code here but the code function but not all the code was being classified as code and i wasn't allowed to submit it
Here are the links to other relevant classes and files
http://www.scs.ryerson.ca/~ikokkari/BDTile.java
http://www.scs.ryerson.ca/~ikokkari/BDLevelReader.java
http://www.scs.ryerson.ca/~ikokkari/levels.xml

First of all never use the numeric constants instead of the field constants,
public void keyPressed(KeyEvent k) {
int keyCode = k.getKeyCode();
if(keyCode == KeyEvent.VK_R) // not keyCode == 82
}
What would be a better approach is to use an InputMap and ActionMap, which I think is also called "Key Binding" (as suggested by MadProgrammer). The input map maps a key stroke to an action name, and the action map maps the action name to the action you want to take.
Replace your line (and the whole KeyListener extended class)
this.addKeyListener(new MyKeyListener());
with something like
this.getInputMap().put(KeyStroke.getKeyStroke("control L"), "link");
where you need to refer to the documentation of KeyStroke.getKeyStroke to modify the specified key stroke to your needs. In my example "link" is the name of the action to be taken when CTRL+L is pressed. Now we need to specify what "link does"
this.getActionMap().put("link", new LinkAction());
where LinkAction is my class extending AbstractAction which in your case should includes your methods such as levelReaderObject.setCurrentLevel(presentLevel);.
Note that you don't need to create an Action for every key. For movement (up, down, left, right) I would bind all of the movement buttons to different action names ("move up" etc.), but then map all the action names to the same action and let the methods inside that action do the work:
this.getActionMap().put("move up", new MoveAction(0));
this.getActionMap().put("move down", new MoveAction(1));
this.getActionMap().put("move right", new MoveAction(2));
this.getActionMap().put("move left", new MoveAction(3));
with
class MoveAction extends AbstractAction {
int direction;
public MoveAction (int direction) {
this.direction = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
switch(direction) // perform the action according to the direction
}
}
Please note that my suggestion to grouping the movement actions together is a design decision and you should decide how to structure the bindings yourself (you could use one action for everything or one action for each).

Related

Java if two buttons have the same icons increase score and if not display "wrong match"

Creating a really basic Memory game using Java Swing. I created my GUI with a list of blank buttons where I set the icon property to none.
My code for some of the buttons is:
private void tbtnCard3ActionPerformed(java.awt.event.ActionEvent evt) {
tbtnCard3.setIcon(new javax.swing.ImageIcon(getClass().getResource("/Card3Logo.png")));
if(tbtnCard5.isSelected()){
score++;
lblScore.setText(""+score);
}
}
private void tbtnCard4ActionPerformed(java.awt.event.ActionEvent evt) {
tbtnCard4.setIcon(new javax.swing.ImageIcon(getClass().getResource("/Card7EWaste.png")));
if(tbtnCard7.isSelected()){
score++;
lblScore.setText(""+score);
}
}
private void tbtnCard5ActionPerformed(java.awt.event.ActionEvent evt) {
tbtnCard5.setIcon(new javax.swing.ImageIcon(getClass().getResource("/Card3Logo.png")));
if(tbtnCard3.isSelected()){
score++;
lblScore.setText(""+score);
}
}
I have about 20 toggle buttons and for example the code above works and the scores go up by 1 when a match is found. So for tbtnCard3, if tbtnCard5 is selected the score goes up by 1. Now my question is how would I make it so that if tbtnCard3 is selected but tbtnCard 5 is not selected, display "Wrong Match". Since im using if Selected I'm not too sure how to display "wrong match" when the case is false. It doesn't make sense to say else ifSelected as no parameters can be put either....
In my opinion, the OPs suggestion is not a good approach. You do not want the listener of one button to be "aware" of some other component unnecessarily. Suppose you have an 8-by-8 grid with toggle buttons. You don't want each toggle button listener to be aware of the other 63 toggle buttons.
I believe there is a much simpler (and cleaner) approach. What you want is for the toggle button listener to register and deregister the toggle when the state of the button changes. Let say, you add the toggle button to or remove from a list (most likely a custom class) where you can trigger some logic when the list size reaches two. Then, depending on the outcome of the comparison, it will count a match (and disable these two toggle buttons in the current state), or will display some message like "Try again" and then toggle the buttons to hide the image.
In pseudocode, this will look something like this:
public class ToggleListener implements ItemListener {
public void actionPerformed (ItemEvent event) {
JToggleButton button = (JToggleButton) event.getSource();
if (event.getStateChange()==ItemEvent.SELECTED) {
// TODO Add the button to your list..
} else {
// remove button
}
}
}
In your Swing application, you can create a single instance of the above listener and add it to every single toggle button. And, as you can see, this listener is only responsible to register and unregister the component associated with the triggered event.
The "List Listener" on the other hand, is responsible to trigger the comparison logic when the size of the list reaches two. So, if you click on the same toggle button over and over again, the only thing the button listener will do is add or remove the button from the list depending on the button's current state. However, once a second button is toggled to reveal its image, the list listener will trigger the comparison logic. I am not 100% sure, but I think you could use JavaFX ObservableList interface or one of its implementing classes to do this. If the ListChangeListener.Change class is not suitable to figure out the size of the list to trigger the logic, you will have to implement this on your own. Regardless, in pseudocode, you need to do something like this:
public void onListChange(Event event) {
if (list.size() == 2 && btn1.getIconName().equals(btn2.getIconName())) {
displayMatchMessage();
btn1.setEnabled(false);
btn2.setEnabled(false);
list.clear(); // you should remove matched items from list manually
} else {
displayMismatchMessage();
btn1.setSelected(false); // flip the card
btn2.setSelected(false); // flip the card
// list.clear(); // you should not need this because the setSelected should trigger the Item Listener which should remove item from list.
}
}
Doing something like this is a much cleaner implementation where the button listener have a single job to do and the "list listener" has another job to do. Neither one encroaches on the other's job.

Java game: How to overcome a hardware-limitation of a keyboard

I'm working on a Java 2D game which requires a max of six keys be held down at the same time.
The game is for two players on the same keyboard, playing simultaneously.
However, all three computers I ran the program on only allow a max of three keys held at a time. They all have trouble with reacting to more than three keys being held.
It seems that pressing a new key after three are already held, either cancels some other key-holding or is ignored.
I've been told that this is a hardware issue. Most keyboards can't handle more than three keys held at a time. But a lot of games do require this, and they do not require special gaming-keyboards to run on my computer without problems.
So there has to be a solution that will make the game playable on any standard keyboard.
If there is, could you please explain to me how to code it in my program?
(I use Key Bindings).
The game's controls:
Player 1
Rotate sprite and set angle of movement: LEFT arrow
Rotate sprite and set angle of movement: RIGHT arrow
Move forward: UP arrow
Shoot missile: ENTER key
Player 2
Rotate sprite and set angle of movement: 'A' key
Rotate sprite and set angle of movement: 'D' key
Move forward: 'W' key
Shoot missile: 'T' key
Relevant code:
The Key Bindings part:
// An action for every key-press.
// Each action sets a flag indicating the key is pressed.
leftAction = new AbstractAction(){
public void actionPerformed(ActionEvent e){
keysPressed1[0] = true;
}
};
rightAction = new AbstractAction(){
public void actionPerformed(ActionEvent e){
keysPressed1[1] = true;
}
};
// And so on...
// ....
// An action for every key-release.
// Each action sets a flag indicating the key was released.
// This is only necessary for some of the keys.
leftReleased = new AbstractAction(){
public void actionPerformed(ActionEvent e){
keysPressed1[0] = false;
}
};
rightReleased = new AbstractAction(){
public void actionPerformed(ActionEvent e){
keysPressed1[1] = false;
}
};
// And so on...
// ....
// Binding the keys to the actions.
inputMap.put(KeyStroke.getKeyStroke("UP"),"upAction");
inputMap.put(KeyStroke.getKeyStroke("LEFT"),"leftAction");
// etc...
actionMap.put("upAction",upAction);
actionMap.put("leftAction",leftAction);
// etc...
In the Board class. It has most of the game's code.
This part checks the flags and reacts to key presses and releases.
keysPressed1 = tank1.getKeys(); // get flags-array of tank1.
keysPressed2 = tank2.getKeys(); // get flags-array of tank2.
if(keysPressed1[0]==true) // if LEFT is pressed.
tank1.setAngle(tank1.getAngle()-3);
if(keysPressed1[1]==true) // if RIGHT is pressed.
tank1.setAngle(tank1.getAngle()+3);
if(keysPressed1[2]==true){ // if UP is pressed.
tank1.setDX(2 * Math.cos(Math.toRadians(tank1.getAngle())));
tank1.setDY(2 * Math.sin(Math.toRadians(tank1.getAngle())));
}
if(keysPressed1[2]==false){ // if UP is released.
tank1.setDX(0);
tank1.setDY(0);
}
// And the same for the second player's keys...
This is mostly how reacting to key-presses and key-releases works in my program. When a key is pressed or released, a flag is set. The Board class reades the flags every game-loop cycle and reacts accordingly.
As I said, the program doesn't react correctly to more than 3 keys held at a time, probably because of the keyboard. Is there a way to code a solution?
Help will be very appreciated.
Thanks a lot
Are you sure you're not experiencing ghosting? If it is ghosting, it's a hardware limitation.
Here is a tester -> http://www.microsoft.com/appliedsciences/content/projects/KeyboardGhostingDemo.aspx
And here is a description of ghosting -> http://www.microsoft.com/appliedsciences/antighostingexplained.mspx
I'm not very familiar with this topic, but doing some research I stumbled upon LWJGL. It's a Java game library and looks pretty promising, according to your problem. Take a look at this.

About JTextFields and typing

I'm working on a JTexfield which sets its own text to the name of the key pressed when it has the focus on the window. I've managed to let it have only a word with the code:
#Override
public void keyReleased(KeyEvent ev) {
if (ev.getKeyCode() != 0) {
keyTrigger = ev.getKeyCode();
txtTrigger.setText(ev.getKeyText(keyTrigger));
}
}
#Override
public void keyTyped(KeyEvent ev) {
txtTrigger.setText("");
}
However it looks horrible when you press special keys like F1--12 or Ctrl because it keeps the last typed non-special key (for example, if you press 'T' and then 'Ctrl', the text in the field keeps being 't' until you release the 'Ctrl' key).
This is the code so far for the JTextField:
txtTrigger = new JTextField();
txtTrigger.setColumns(10);
txtTrigger.addKeyListener(this);
txtTrigger.setBounds(80, 5, 64, 20);
contentPane.add(txtTrigger);
What I want is the field to be empty until you release the key. How can I get the application working this way?
I don't think a editable text field is your best choice here. What I've done in the past is basically faked it.
I've generated a custom component that "looks" like a JTextField and, using my own KeyListener, I've added elements to the view (I did my own "key" renderer, but you could simply have a list of String elements that you can render).
Basically, when keyPressed is triggered, I would add the key code to a list (taking into consideration things like its modifier state). If another key event is triggered with the same key code, then you can ignore it.
When keyReleased is triggered, you can remove that keycode from the active list.
You can add the keylistener to the jframe or many components...
Just make sure that component has the focus.

handle multiple key presses ignoring repeated key

I had asked this in the comments section of another question (> How do I handle simultaneous key presses in Java?), and was asked to make a new question altogether.
My problem is that when I create an ArrayList of keypresses they are not removed fast enough via the keyReleased event if the user holds down the keys. I want movement to be with "asdf" and North, East, South, West, NorthEast... etc.
Here is my code for both events:
#Override
public void keyPressed(KeyEvent e) {
if(chatTextField.isFocusOwner() == true){
//do nothing - don't walk
} else {
logger.debug("Key Pressed: " + e.getKeyChar());
lastKey = keysPressed.get(keysPressed.size()-1);
for (String key : keysPressed){
if (!key.contains(String.valueOf(e.getKeyChar())) && !lastKey.contains(String.valueOf(e.getKeyChar()))){
keysPressed.add(String.valueOf(e.getKeyChar()));
System.out.println("ADDED: " + keysPressed);
}
}
String keysList = keysPressed.toString();
if (keysList.contains("w")){
if (keysList.contains("d")){
requestCharacterMove("NorthEast");
} else if(keysList.contains("a")){
requestCharacterMove("NorthWest");
} else{
requestCharacterMove("North");
}
} else if (keysList.contains("s")){
if (keysList.contains("d")){
requestCharacterMove("SouthEast");
} else if(keysList.contains("a")){
requestCharacterMove("SouthWest");
} else{
requestCharacterMove("South");
}
} else if (keysList.contains("d")){
requestCharacterMove("East");
} else if (keysList.contains("a")){
requestCharacterMove("West");
}
}
}
#Override
public void keyReleased(KeyEvent e) {
if(chatTextField.isFocusOwner() == true){
//do nothing - don't walk
} else {
logger.debug("Key Released: " + e.getKeyChar());
for (String key : keysPressed){
if (key.contains(String.valueOf(e.getKeyChar()))){
keysPressed.remove(String.valueOf(e.getKeyChar()));
System.out.println("REMOVED: " + keysPressed);
}
}
}
}
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
Until I added the second check in there via the lastKey(String) variable the pyramid created was enormous. Even with that second check the list grows and almost always has two-three duplicates. Any help on this would be great as my character is moving awkwardly. :(
Also any way to remove duplicate conversions to char, string, arrayList would be great as I'm nervous I used too many types for something "simple".
Your obseravtion that things are handled slowly most likely is caused solely be the many System.out.println() statements.
Your problem that you do not get diagonal movement stems from your somewhat faulty checking logic - instead of explicitly checking if (for example) keys A and B are pressed, just check them independently - key A moves the character in one direction, B in another. In total (e.g.), by moving WEST and NORTH you will have effectively moved NORTHWEST.
Instead of a list of pressed keys, you could use a java.util.BitSet and just set the bit for each key that is currently pressed. That should also drastically reduce the amount of code you need to write (keyPressed just sets the bit indicated by key code, keyReleased clears it). To check if a key is pressed you ask the BitSet then if the bit for the code is currently set.
EDIT: Example of using BitSet instead of a list
public class BitKeys implements KeyListener {
private BitSet keyBits = new BitSet(256);
#Override
public void keyPressed(final KeyEvent event) {
int keyCode = event.getKeyCode();
keyBits.set(keyCode);
}
#Override
public void keyReleased(final KeyEvent event) {
int keyCode = event.getKeyCode();
keyBits.clear(keyCode);
}
#Override
public void keyTyped(final KeyEvent event) {
// don't care
}
public boolean isKeyPressed(final int keyCode) {
return keyBits.get(keyCode);
}
}
I made the example implement KeyListener, so you could even use it as is. When you need to know if a key is pressed just use isKeyPressed(). You need to decide if you prefer with raw key code (like I did) or go with key character (like you currently do). In any case, you see how using the BitSet class the amount of code for recording the keys reduces to a few lines :)
As an alternative, this game uses the numeric keypad to implement each (semi-) cardinal direction with a single keystroke. The default arrangement is shown in the Design section. The keys may be individually reassigned to map a similar rosette anywhere on the keyboard.
Looks like you are not handling threading in Java right. There are three threads (minimum) to any Java program. They are the main program thread, the event dispatch thread, and one more that i can't remember right now.
Whenever you get an event it is delivered to you by a special thread (I believe it's the event dispatch thread, but that is besides the point). You are not allowed to do anything (that takes time) on this thread, that will freeze up your input and cause you to miss events, making Java look unresponsive. So what has happened is you have broke the event system in java. What you should do is store the result in some sort of buffer, which is the fasted thing you can be expected to do with the event, then it is handled later as I will describe.
[Aside:
A funny application is to make a simple gui, and on the press of the button call wait on the thread for like 5 seconds. Your entire gui will freeze until the delay has finished!]
You should have a different thread running on the side (probably your main thread). It will run some sort of loop, which controls the frames in your program, completing once per game cycle. Once each cycle this thread reads the results stored in the input buffer and processes them. The theory behind this is simple, but the execution can be a little messy, because you will need to make sure that no input events are dropped or read more then once. Either way, good luck with your game!

Should I implement actions inside the methods of an InputListener object?

I am making a button using MouseOverArea. After some trial and error, I realized I can override the methods in InputListener to do particular actions when an input event is notified.
For example, do things when mouse left button is pressed while cursor is over the component.
#Override
public void mousePressed(int button, int mx, int my) {
if (isMouseOver() && button == Input.MOUSE_LEFT_BUTTON) {
// Some magic happens
}
}
However, I will not able to do things like changing current game state because no Game object around. I know there are many ways to solve this problem, but I would like to know what is the Slick way to do this.
Are these methods suitable for such behavior ?
One way to modify game states is to use boolean states; Which are boolean variables that hold the state of the game or player. For example:
boolean isMovingUp, isMovingLeft, isMovingRight, isMovingDown;
You can then set these to true/false depending on what mouse or keyboard event takes place and your game class then read these variables, like so:
if (isMovingUp) {
// do something
isMovingUp = !isMovingUp;
}
Hope that helps!

Categories

Resources