JGameGrid
 
 

The source code of all examples is included in the JGameGrid distribution.

Ex07, Ex07a: Polling the Keyboard

Many games use the keyboard as main input device when the game is running. Keys can be rapidly pressed and a special training is required to become a winner. Tactile skills based on visual detection and the close coordination between seeing and acting is very important in everyday life and in many professions. Computer games may contribute to train this faculty.

In JGameGrid there are three different ways to detect a key stroke: Either you repeatedly look if a key is pressed (you "poll" the keyboard), you use the "kbhit" mechanism or you consider the key hit as an event to be managed by an event listener. All three implementations have pro and cons. While polling is somewhat tedious and tends to produce confusing code, events are more elegant, but happens asynchronously (in a separate thread) and care must be taken that the otherwise running program is not disturbed. When polling the keyboard by looking if a key is pressed, the danger is high that you eventually miss a key stroke if the polling cycle is not fast enough or when the computer is busy. On the other hand if you move an actor with a key event callback, the actor's act() may miss an intermediate new location, if the simulation cycling is not fast enough.

First we demonstrate polling: By pressing the space bar we would like to create a new actor that you can move up and down with the cursor up/down keys. The application uses the Clownfish class already known from example Ex01. We poll the keyboard by calling isKeyPressed() repeatedly in the act() method. isKeyPressed() returns true, if a key with the given key code is pressed at the current time. The different key codes are defined as static integer constants in the class KeyEvent. (For more information consult the API documentation of this class.) The program looks evident but behaves badly for two reasons: first because nothing happens if you click the keys very shortly, second because more than one fish is created if you keep the space key down.

import ch.aplu.jgamegrid.*;
import
 java.awt.event.KeyEvent;
import
 java.awt.Color;

public
 class Ex07 extends GameGrid
{
  
private Clownfish fish = null;

  
public Ex07()
  
{
    
super(10, 10, 60, Color.red, false);
    
setTitle("Space bar for new object, cursor to move up/down");
    
doRun();
    
show();
  
}

  
public void act()
  
{
    
if (isKeyPressed(KeyEvent.VK_SPACE))
    
{
      fish 
= new Clownfish();
      
addActor(fish, new Location(0, 0));
    
}
    
if (fish == null)
      
return;
    
if (isKeyPressed(KeyEvent.VK_UP) && fish.getY() > 0)
      fish.
setY(fish.getY() - 1);
    
if (isKeyPressed(KeyEvent.VK_DOWN) && fish.getY() < 9)
      fish.
setY(fish.getY() + 1);
  
}

  
public static void main(String[] args)
  
{
    
new Ex07();
  
}
}

Execute the program locally using WebStart.

The first fault can be eliminated by decreasing the simulation period, say to 50 ms. To tackle the second fault we inhibit the multiple call of the creating code by a boolean state variable isSpaceKeyPressed that tells whether the space key is kept down. As side effect the fish is now moving much faster. We can compensate this by calling setSlowDown(4).

import ch.aplu.jgamegrid.*;
import
 java.awt.event.KeyEvent;
import
 java.awt.Color;

public
 class Ex07a extends GameGrid
{
  
private Clownfish fish;
  
private boolean isSpaceKeyDown = false;

  
public Ex07a()
  
{
    
super(10, 10, 60, Color.red, false);
    
setSimulationPeriod(50);
    
setTitle("Space bar for new object, cursor to move up/down");
    
doRun();
    
show();
  
}

  
public void act()
  
{
    
if (isKeyPressed(KeyEvent.VK_UP) && fish.getY() > 0)
      fish.
setY(fish.getY() - 1);
    
if (isKeyPressed(KeyEvent.VK_DOWN) && fish.getY() < 9)
      fish.
setY(fish.getY() + 1);
    
if (isKeyPressed(KeyEvent.VK_SPACE) && !isSpaceKeyDown)
    
{
      fish 
= new Clownfish();
      
addActor(fish, new Location(0, 0));
      fish.
setSlowDown(4);
    
}
    isSpaceKeyDown 
= isKeyPressed(KeyEvent.VK_SPACE);
  
}

  
public static void main(String[] args)
  
{
    
new Ex07a();
  
}
}

Execute the program locally using WebStart.

 

Ex07b: The "kbhit" Mechanism

You can circumvent the problem of missed key events by using the "kbhit" mechanism of JGameGrid, inspired by other programming languages. Calling kbhit() tells you, if a key has been pressed any time before the last invocation of getKeyCode() or getKeyChar(). In this case, you can retrieve the information about the key by calling one of these two methods. Obviously the last key event is stored in an internal buffer. If you keep the space bar down, the multiple invocation comes from the fact that the operating system's key-repeat is turned on. A drawback of the kbhit mechanism is that you don't get a notification when the key is released.

import ch.aplu.jgamegrid.*;
import
 java.awt.event.KeyEvent;
import
 java.awt.Color;

public
 class Ex07b extends GameGrid
{
  
private Clownfish fish;

  
public Ex07b()
  
{
    
super(10, 10, 60, Color.red, false);
    
setTitle("Space bar for new object, cursor to move up/down");
    
doRun();
    
show();
  
}

  
public void act()
  
{
    
if (kbhit())
    
{
      
switch (getKeyCode())
      
{
        
case KeyEvent.VK_UP:
          
if (fish.getY() > 0)
            fish.
setY(fish.getY() - 1);
          
break;
        
case KeyEvent.VK_DOWN:
          
if (fish.getY() < 9)
            fish.
setY(fish.getY() + 1);
          
break;
        
case KeyEvent.VK_SPACE:
        
{
          fish 
= new Clownfish();
          
addActor(fish, new Location(0, 0));
        
}
      
}
    
}
  
}

  
public static void main(String[] args)
  
{
    
new Ex07b();
  
}
}

Execute the program locally using WebStart.

 

Ex07c: The Better World: GGKeyListener

Considering a key stroke as an event is very natural for everyone. Indeed, key events trigger callback code called interrupt routines at the very low level of your computer. The currently executing code is interrupted and the interrupt routine code executed. The same event model is used in Java event model: a event listener code is registered and executed by a separate thread whenever the event is triggered. For Swing GUI events this thread is called the Event Dispatch Thread (EDT). In JGameGrid keyboard events can be caught by registering a GGKeyListener that declares the two methods keyPressed() and keyReleased(). When implemented and registered by you, these methods will be called by the EDT anytime when a key is pressed, asynchronously to the running program. This gives this code a fantastic independence, but care must be taken that it does not disturb other parts of the program by side effects (illegal change of state variables). Another point is often overlooked: if the callback code changes state variables whose modifications should be detected by the running program, some intermediate values may got undetected when the callback code is retriggered rapidly.

Again we use the same problem as above and solve it now using the event model. The solution is much prettier because the code for moving the actor is swept out from the global application class to where OOP claims: in a derived actor class called Angelfish. Because Angelfish implements the GGKeyListener, both of its methods, keyPressed() and keyReleased() must be implemented even if the body is empty.

import java.awt.event.KeyEvent;
import
 ch.aplu.jgamegrid.*;

public
 class Angelfish extends Actor implements GGKeyListener
{
  
public Angelfish()
  
{
    
super("sprites/angelfish.gif");
  
}

  
public void act()
  
{
    
move();
    
if (getX() < 0 || getX() > 599)
    
{
      
turn(180);
      
setHorzMirror(!isHorzMirror());
    
}
  
}

  
public boolean keyPressed(KeyEvent evt)
  
{
    
switch (evt.getKeyCode())
    
{
      
case KeyEvent.VK_UP:
        
if (getY() > 0)
          
setY(getY() - 1);
        
break;
      
case KeyEvent.VK_DOWN:
        
if (getY() < 599)
          
setY(getY() + 1);
        
break;
    
}
    
return false;  // Don't consume the key
  
}

  
public boolean keyReleased(KeyEvent evt)
  
{
    
return false;
  
}
}

The application class also implements GGKeyListener because it should create new angelfishes when the space bar is hit. As clearly seen, multiple GGKeyListeners are registered, the application instance and each Angelfish instance as well. All callback methods should return false to indicate that the event is not "consumed" but passed to all other registered listeners. Because all listeners are triggered at the same time, all angelfishes will move up and down together.

import ch.aplu.jgamegrid.*;
import
 java.awt.event.KeyEvent;

public
 class Ex07c extends GameGrid implements GGKeyListener
{
  
public Ex07c()
  
{
    
super(600, 600, 1, null"sprites/reef.gif"false);
    setTitle("Space bar for new fish, cursor to move up/down");
    
setSimulationPeriod(50);
    
addKeyListener(this);
    
doRun();
    
show();
  
}

  
public boolean keyPressed(KeyEvent evt)
  
{
    
if (evt.getKeyCode() == KeyEvent.VK_SPACE)
    
{
      Angelfish fish 
= new Angelfish();
      
addActor(fish, new Location(300, 300));
      
addKeyListener(fish);
    
}
    
return false;  // Don't consume
  
}

  
public boolean keyReleased(KeyEvent evt)
  
{
    
return false;
  
}

  
public static void main(String[] args)
  
{
    
new Ex07c();
  
}
}

Execute the program locally using WebStart.

GG7