Author Archives: Toni

Transferring files to the Osborne 1

As any of my friends could tell you, I’m pretty big into collecting vintage computers. It costs me $70/month in storage fees, but I am just completely drawn to some of the awesome technical creativity that went into early machines before standardization took hold of the market. Mixed with the nostalgia, closeness to the hardware, and challenge of using something less than user-friendly, these old machines are great. In particular, I’m a big fan of the early portables, or luggables to be more realistic.

One fantastic machine that I’m lucky to have 3 of is the Osborne 1.

Osborne 1

It’s a pretty sweet machine, running a Z-80 processor, 64K RAM, dual 5 1/4 floppies, a monochrome 5″ screen (love this, its absolutely tiny compared to the machine), modem port, serial port, battery port, parallel port, all folding into a suitcase.

If you’re lucky enough to get the original software pack, it actually comes with the a good amount of applications, including it’s OS CP/M (the precursor to DOS), Wordstar, Supercalc, CBASIC, MBASIC, and dBase II. However, I personally love vintage machines for their games and getting them connected to other computers. The machine sports a DB-25 serial port at 1200 bps, and I knew I could transfer the modest sized library of CP/M software that can still be found on the net over the serial link. I had no terminal software though, so I did some research and came up with the following after a long struggle (please leave a comment if you have any better info or corrections):

Transferring Files to the Osborne 1

  1. Use the DB-25 port – The machine has two serial interfaces, a DB-9 and a DB-25. There is not a lot of documentation out there regarding the difference, but from what I’ve gathered, the DB-9 is not a standard RS-232 serial port, but a TTL serial port specifically designed for use with the Osborne modem (that fit snuggling in the storage bay under the left disk drive). While I’m sure it’s possible to build or use a UART to convert TTL to RS-232, it’s easier to use the DB-25, which is indeed an RS-232 port.
  2. Do not use a null modem – One of the first things I kept running into was once I managed to find a way to transmit data (see below), my remote computer (my primary Debian box) got absolutely nothing over the line. Not garbled characters from a mismatch in speed, just absolutely nothing. I thought maybe my port was bad, so I tried a new Osborne hooked to another machine (even with a new cable), and still nothing. Of course, normally you would use a null modem to switch the send/receive pins when connecting a computer to another computer over serial link, which I was doing. It turns out that the Osborne serial port was probably designed to be used as a terminal, and hence it already does the crossover. I had to directly connect my serial cable from one computer to another, with no null modem, for it to work.
  3. Set the serial speed – Depending on what revision of the Osborne 1 you have, it will either have a default speed of 300 or 1200 bps. 1200 is slow enough, so you definitely want to make sure it isn’t set at 300 bps. The CP/M disk will contain a “setup.com” program that you can run to configure various settings, including serial speed. Make sure 1200 is selected.
  4. Use PIP to initially transfer files – PIP is a fantastic utility included with CP/M that allows data to be moved from one device to another – devices being both files or physical interfaces. With PIP, we can instruct the Osborne to dump a text file from a remote computer to a local file. While PIP has no error correction and is not designed to be a full terminal, it does get the job of initially getting a terminal program onto the machine. Usage consists of “PIP <target>=<source>”. So you could use it to copy a file from drive B to drive A, “PIP A:FILE.TXT=B:FILE.TXT”, or from the serial port to a file, “PIP B:FILE.TXT=RDR:”. RDR (short for reader, as in paper tape reader, oh the historical goodness in that one) is the device name for the serial port.
  5. Get the Kermit assembly modules (in hex) onto the machine – This part I gathered from instructions on z80.eu and using files from Columbia University’s Kermit Page. Columbia University has a huge archive of versions of Kermit for different computers, including CP/M machines. These consist of two files – the base Kermit module, and then a small module that is specific to the machine. In particular, cpsker.hex (the main kermit module), and cpvosb.hex (the Osborne 1 specific code). These previous links are copies I made of these files in case you can’t reach Columbia’s site for whatever reason. Start by instructing PIP to save data from the serial port to a file, e.g. “PIP B:CPVOSB.HEX=RDR:”, then dump the contents of this file on the remote computer to the serial port. After the transfer, you will have a good copy of cpvosb.hex on your Osborne (you may have to control-d, control-c, or control-z to end the transfer if the Osborne does not quit out automatically, sometimes it worked for me and sometimes it didn’t). The larger, main kermit module is a little more tricky.
  6. Get the main Kermit assembly module – Unfortunately, the main module is a little more tricky as its bigger than PIP’s buffer, so it must write a portion of the file to disk during transfer (as opposed to the entire thing at the end). The problem is, PIP has no error correction, and when it goes to use the disk drive, you WILL lose part of the file being transferred. The transfer takes a while anyway, so having to restart because of corruption is a giant pain. Since these hex files are simply text files, I split the file into 4 parts to decrease the chance of file corruption (and decrease the time I’d have to wait for each one if I had to retransmit). After splitting them into 4 parts, I followed the same procedure as in step 5 for each file. Once they were all on the machine, I cleaned up any extra line feeds introduced by the transfer with ED (ed is so non-intuitive it makes vi look like MS Word – reference for its use can be found here), then concatenated the files together with PIP (PIP full.txt=file1.txt,file2.txt,file3.txt,file4.txt). I then had both the kermit and machine module.
  7. Compile Kermit – Luckily, CP/M comes with both ASM and DDT on disk that allow compiling and linking source files. We will use DDT to link these two modules together. The following blurb is from the Columbia page with a few comments from me:
    
    
              ddt cpsker.hex <--- At the command line, DDT your base module
    
              NEXT  PC   
              3500 0100
              -cpvosb.hex  <--- insert the machine dependent module
              -r
              NEXT  PC
              xxxx 0000
              -^C  <---- control-c
    
              A>save dd kerm411.com  <---- executable file to create
    
          "The page count ("dd") used in the SAVE command is calculated from
          the last address ("xxxx") given by DDT in response to the R command:
          drop the last two digits and add 1 if they were not zero, then
          convert from hexadecimal (base 16) to decimal (base 10): 684F
          becomes 69 hex, which is 105 decimal (5 times 16 plus 9) -- but 6700
          becomes 67 hex, or 103 decimal."
    
    
    

    If you receive any errors or strange messages from DDT that don't match what's listed above, it's likely your files were corrupted during transfer (this happened twice to me) and you will need to try again. Also make sure that you have enough room on your floppy to create the executable, you might have to do some disk and file juggling!

  8. Assign Logical to Physical Device - This one you'll have to do every time you restart the machine before you use Kermit. CP/M machines have logical and physical devices. We've worked with the physical serial port device RDR, but there are logical devices that are connected to physical devices to perform translations. Kermit makes use of the PTR (paper tape reader) logical device, so we need to connect this logical device to the serial port physical device. We do this using the STAT command - "STAT PTR:=RDR:".
  9. You're Done! - At this point, you have Kermit, ready to use, on your Osborne 1. There are many implementations (as seen on the Columbia page) of Kermit for every OS, so finding one for Windows, OS X or Linux won't be an issue. There are lots of guides out there for using Kermit as well, so I won't mention the commands here - they're fairly straight forward. Kermit does have error correction and multi-file transferring capabilities, so you can let it go for 30 minutes and transfer something big with no worries. It can also be used as a terminal, so you can connect over serial link to a linux shell and have some fun!
  10. The first thing I transferred once Kermit was good to go was Zork-I, there is just something about text adventures on monochrome screens that is just awesome. Zork I, II, and III for CP/M can be found here. What's nice is these archives make use of a separate z-machine from the data file, so you can copy any z3 data file (which are the vast majorities, I used this method to play The Lurking Horror) in place of zork1.dat - just remember to rename it the same thing.

    Have fun with your Osborne 1!

    Zork 1 on Osborne 1

Expanding the blog

So, the last week or so I’ve been thinking a lot about the direction I want this blog to go. I’ve decided to widen the areas I talk about to include the following:

1. Personal Entries – not so much in a “today I had an excellent ham sandwich” way, but there are often times when I want to write about things going in my life that are technology or computer science related, and I don’t have a good forum to do that. I would love to have a place where I can post thoughts and quick ideas, and I figured this blog would be a good choice.

2. Technology (non-CS) tutorials – especially in the area of retrocomputing, there are a lot of times when I’m looking to do something, can’t find a great solution that’s already been done online, so I have to experiment and find my own path. I would like to post my experiences to hopefully help someone else avoid some headaches. This happens quite a lot with working with older machines, due to the fact that there is a lot of information that simply didn’t make it to the Internet. If a computer was only semi-popular in the early 80s and not used since, it’s often hard to find information.

3. Who knows what else?

I basically want to use this blog for more things, as well as be a little more active documenting things going on with Shredz64 and the PSX64. So I hope you’ll read along and perhaps find some useful information!

Shredz64 – Production Started

If you’ve been keeping up with this blog, you’ll know that the prototypes had checked out and we were awaiting the final product. I’m happy to say we received a batch of 20 PCBs from the fab house the other day, and I’ve soldered one up, and it works great. That means that we’re good to go, it’s just a matter of us soldering them up and shipping them out! As soon as the first batch of 20 have been assembled, preregistration will close and normal sales will open. Those who preregistered will be given first priority to the purchase of a PSX64 and Shredz64. More instructions are to follow soon. After the first batch of 20 ship, more batches will come in 50 at a time. Please note, it will take a while to assemble each batch, but we will try to be timely so you won’t have to wait long.

Below is a picture of the finished product as you will receive it. Note – as you can see we changed the design slightly to use rectangular connectors which should add a lot of stress relief/protection to the cables.

When the first 20 are assembled, a final price will be posted as well. Thanks again for your continued patience, we’re definitely rolling along!

PSX64

Shredz64 – PSX64 PCB checks out!

First off, I want to apologize for not updating this blog for a few weeks, I’ve started a new job and things have been hectic getting up to speed on things. But life is starting to calm down, and more importantly, I’ve received the first PSX64 PCB, soldered on the chips, uploaded the firmware, and it works!

There are a few changes to make to the board, mainly due to the pinout of available parts (I have a large stock of voltage regulators that are a different pinout than how the board is configured). I’m also going to use rectangular headers with stress relief connectors for the PSX and DB9 cables so they can take more tugging (as opposed to soldering them directly to the board). However, these changes aren’t major and don’t require another single-board run. The next run will be a batch of 10-30 boards, which will then be available for purchase! I suspect it will be about 4 weeks or so.

Thanks to everyone who has signed up on the notification list, it won’t be too much longer now!

Creating a Blackberry Game – Part 6

Looking for part 5?

Let’s Hear It!

Our game is almost done – the only part left is the sound (and vibration) processing. This is a fairly simple class as well – again, most of the low level processing is done already by the Blackberry. However – you may want to play with the methods in this class. We have functionality for playing a midi file – and we could also have functionality for playing a wav file as well, but I haven’t included it. The reason – on my 8830, the sound engine (at least the way I was using it), could only seem to mix one sound at a time, and completely stopped the other sound if a second one was played. There may be specific methods to mix two sounds together that I did not research, or otherwise it’s a limitation/bug of the 8830. So, if music was playing, and I then played a wav sound effect, the music would stop.

I overcame this by using the Alert.startAudio method. This takes frequency/duration pairs from an array and plays simple sounds with it. When this method is used, it does indeed mix the audio with the midi playing in the background, so I stuck with it. It makes for less sophisticated sound effects, but it helps us for now.

Additionally, I wanted to include vibration in the game, so I included a small method for triggering this off. Vibration is used for when spaceships explode, either yours or the enemy’s.

SND.java

package com.synthdreams.GalacticBlast;

import net.rim.device.api.ui.component.Dialog;
import java.io.InputStream;
import java.lang.Class;
import javax.microedition.media.Manager;
import javax.microedition.media.Player;
import net.rim.device.api.system.Alert;

// Sound engine
class SND {
    
    Player _musicPlayer; // Java media player
    
    SND() { }
    
    // Play a midi file for background music
    void playMusic(String passMusic)
    {
        try
        {
            // Set InputStream to a midi file included as resource, as specified by
            // passMusic
            InputStream in = getClass().getResourceAsStream("/" + passMusic);            
            
            // Create a media player with mime type of audio/midi using our inputstream
            _musicPlayer = javax.microedition.media.Manager.createPlayer(in, "audio/midi");
            
            // Ready the data and start playing it.  To loop indefinitely, we set loopcount
            // to -1.
            _musicPlayer.realize();
            _musicPlayer.prefetch();
            _musicPlayer.setLoopCount(-1);
            _musicPlayer.start();

        }
        catch (Exception e)
        {
            Dialog.alert("Error playing music");
        }   
    }
    
    // Stop playing music
    void stopMusic()
    {
        try
        {
            // Tell player to stop playing
            _musicPlayer.stop();
            
        }
        catch (Exception e)
        {
            Dialog.alert("Error stopping music");
        }
        
        // Then release the data and close out the player
        _musicPlayer.deallocate();
        _musicPlayer.close();
    }
    
    // The Playsound method plays a simple combinations of tones to simulate a firing
    // noise.  This was necessary, as due to a bug or limitation of the BlackBerry 8830 
    // (the phone I do my testing on), playing a WAV file stopped the midi player and
    // any other sound effects.  Player doesn't appear to mix properly (if at all).  However,
    // a midi file can be played while using the Alert objects startAudio method which
    // can play a sequence of tones, so this is what we've done for now.
    void playSound()
    {
        // A sequence of frequencies and durations (eg 1400hz for 15ms, 1350hz for 15ms, etc)
        short[] fire = {1400, 15, 1350, 15, 1320, 20, 1300, 20, 1250, 25, 1200, 35};
        
        try
        {
            Alert.startAudio(fire, 100);
            
        }
        catch (Exception e)
        {
            Dialog.alert("Error playing sound effect.");
        }   
        
    }
    
    // Activates the phone's vibration functionality for a specific number of ms
    void vibrate(int passMilli)
    {
        Alert.startVibrate(passMilli);
    }
} 

Nothing too complex at all going on here. You may wonder what all the player initialization methods are doing – they deal mainly with making sure the sound data is available and buffered before playing. Just call them in order and you’re good to go.

That’s All Folks!

At this point, you have all the basic functionality necessary for making whatever kind of Blackberry game you’d like. Your logic may have to be much more complex, and you may have to include additional classes to accommodate everything, but the basic principles stay the same. Also – all the classes used here are unsigned and available without a license from Research in Motion, which means you can make, play, and distribute these games for free.

Feel free to comment if you have any questions, or even suggestions! These were my own experiences and suggestions with creating a Blackberry game, but you might have your own! Thanks for checking out this tutorial, good luck – and most importantly, have fun!

Creating a Blackberry Game – Part 5

Looking for part 4?

Let’s See Our Game

Now that our logic is done, we have a few holes in the program where calls are made to graphics and sound processing. By isolating (for the most part, as much as our program allows) graphics and sound processing from the rest of the program, it offers for a little more portability. In general, this isn’t too big of an issue in Java since Java itself is cross platform, but even so we run into areas where certain libraries might be available for one setup and not for others (for example, our program in which we use Blackberry specific libraries, wouldn’t be valid in a non-Blackberry environment). For systems where processor speed and memory are in ample supply (which are most systems these days – as compared to an MCU or retro environment when you’re dealing with 64K of RAM), it’s a good practice to isolate things out like this – it makes porting a lot easier (Think Windows -> Mac & Linux, or PS3 -> XBox & Wii, that kind of thing).

Anyway, game programming philosophy aside, let’s dig into the graphics functionality of GFX. GFX is actually fairly simple since most of the low level graphic processing is done by the Blackberry itself. We don’t have a lot of ways to get close to the hardware, which is a good and bad thing – but for us here, the built in graphics functionality of the Blackberry is good enough and saves us work.

Besides some initialization routines, the GFX class consists mainly of the process method, which takes a handle to the graphics of the current screen from Gameplay. This handle indirectly allows us to write to the screen memory through simple graphic methods like drawBitmap and drawText. The overall algorithm is to first write the background (aligned properly, it moves every update) to the screen, then draw all objects, then the score and our hero’s health meter. There is some math for doing coordinate and dimension calculations, but everything is fairly straightforward.

GFX.java

package com.synthdreams.GalacticBlast;

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.*;
import java.util.Vector;

// The GFX class is our game's graphics engine with all the drawing routines
class GFX 
{
    int _backPos, _backSpeed; // Background position and speed
    Bitmap _backgroundBM; // The bitmap for the background
    Bitmap _healthBM; // The bitmap for the health meter
    Font _gameFont; // The font used for drawing score and lives
    
    GFX() 
    {
        // First see if we can get the BBCondensed font at size 16 for our game font.
        // If not, we just go with the default, but all Blackberrys should have this font.
        try
        {
            _gameFont = FontFamily.forName("BBCondensed").getFont(FontFamily.SCALABLE_FONT,16);
        }
        catch (Exception e) { }
        
        // Load the health bitmap from health.png
        _healthBM = Bitmap.getBitmapResource("health.png");
    }
    
    // Get current background position
    public int getBackPos() { return _backPos; }
    
    // Method that sets the bitmap for the background and sets the scroll speed
    public void initBackground(String passBackground, int passSpeed)
    {
       _backgroundBM = Bitmap.getBitmapResource(passBackground);
       _backPos = 0;
       _backSpeed = passSpeed;
    }
    
    // Primary function for the graphics engine, draws all the objects, text, health, etc
    public void process(Graphics passGraphics, Vector passObjects, int passScore, int passLives)
    {
        // Draw our background at the correct position
        passGraphics.drawBitmap(0,0, Graphics.getScreenWidth(), Graphics.getScreenHeight(), _backgroundBM, 0, _backPos);
        
        // Move our background.  
        _backPos -= _backSpeed;
        
        //If we're at the beginning of our bitmap, reset to end
        if (_backPos < 0)
        {
           _backPos = _backgroundBM.getHeight() - _backSpeed;
        }
        
        // If we're within screenheight of the end of our bitmap, we need to draw another copy to fill in the blank
        if (_backgroundBM.getHeight() - _backPos < Graphics.getScreenHeight()+_backSpeed)
        {
           passGraphics.drawBitmap(0, _backgroundBM.getHeight() - _backPos - _backSpeed, Graphics.getScreenWidth(), Graphics.getScreenHeight(), _backgroundBM, 0, 0);
        }
        
        // Now draw each of our objects to the screen
        for (int lcv = 0 ; lcv < passObjects.size() ; lcv++)
        {
            passGraphics.drawBitmap(((OBJ) passObjects.elementAt(lcv)).getX(),
                                    ((OBJ) passObjects.elementAt(lcv)).getY(),
                                    ((OBJ) passObjects.elementAt(lcv)).getBitmap().getWidth(),
                                    ((OBJ) passObjects.elementAt(lcv)).getBitmap().getHeight(),
                                    ((OBJ) passObjects.elementAt(lcv)).getBitmap(),
                                    0, 0);
        }
        
        // Draw score
        String zeroPad;
        
        // We want to pad the score with 0s
        zeroPad = "";
        if (passScore < 10000)
          zeroPad += "0";        
        if (passScore < 1000)
          zeroPad += "0";
        if (passScore < 100)
          zeroPad += "0";
        if (passScore < 10)
          zeroPad += "0";
          
        // Draw score and lives in white with the game font
        passGraphics.setColor(0xFFFFFF);
        passGraphics.setFont(_gameFont);
        passGraphics.drawText("Score: " + zeroPad + passScore, Graphics.getScreenWidth()-93, 2);
        passGraphics.drawText("Lives: " + passLives, 7, Graphics.getScreenHeight() - 20);
        
        //Draw health, with width dependent on hero's life
        if (((OBJ) passObjects.elementAt(0)).getLife() > 0)
            passGraphics.drawBitmap(7, 7, _healthBM.getWidth() * ((OBJ) passObjects.elementAt(0)).getLife() / 5, _healthBM.getHeight(), _healthBM, 0, 0);
    }

}

As can be seen, nothing major happening here. The only weird part is how we draw the background. We basically have a long bitmap that scrolls in the background. When it hits the end of the bitmap, it needs to repeat it. It does this by continuing to scroll the original bitmap, and then filling in the whitespace with a second bitmap, placed flush to the first bitmap. Since we’re dealing with random stars, its hard to make out any seams on the screen. If you were doing something with a more ordered/textured background, you’d need to make sure the top of the bitmap flowed well with the bottom of the bitmap.

Graphics are done – you should be able to disable your audio calls and get a playable game at this point! It should look something like this (only with your graphics, of course)

Onto our final part, audio…

Creating a Blackberry Game – Part 4

Looking for part 3?

Cue the Objects

Just like Object is the root for all classes in Java, OBJ is the root class for all of our classes in our game that represent sprites on the screen. This is where object oriented programming really shines through for game creation. By setting up a parent class, we can assign properties that are common to all our sprite objects, like position, velocity, a bitmap, etc. Then we can define classes for each individual type of object, like a Hero class, EnemyDrone class, and Photon class that will extend our base OBJ class. This allows them to inherit common properties, and add their own that are specific to them (For example, our hero will have a properties for how many lives it has left, which no other class has, as the rest of the objects simply go away after being destroyed). Since sometimes we work with our objects as OBJ in a vector, we also have a “type” property that is simply an identifying string that allows us to easily determine exactly what our object is (hero, enemy, or photon).

In addition to our OBJ classes, we also have a few static methods that are designed to work with multiple objects as opposed to any single object. CollisionDetect will loop through all objects and check for bounding box collisions, while cleanObjects will loop through all objects and remove dead ones from our object vector.

OBJ.java

package com.synthdreams.GalacticBlast;

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.Graphics;
import java.util.*;

// OBJ is our root class for all objects, it defines behaviors and properties
// that are the same for all objects, whether its the hero, enemies, or photons
class OBJ 
{
    // Objects can have different states, these are general ones
    public static int STATE_NORMAL = 0;  // Object is alive and functioning normally
    public static int STATE_HIT = 100; // Object just got hit, flash red
    public static int STATE_DYING = 1000; // Object is dying, show an explosion
    
    int _posX, _posY, _velX, _velY;  // All objects have position and velocity
    int _life, _value, _state; // All objects have life, point value, and a current state
    OBJ _parent; // All objects can have a parent (e.g. a photon belongs to the object that shot it)
    String _type; // Type is a string that stores what the object is for easy reference
    Bitmap _bitmap; // Bitmap to be drawn for the object
    
    // Objects are initialized globally with a position
    OBJ(int passX, int passY) 
    {
        _posX = passX;
        _posY = passY;
        _velX = 0;
        _velY = 0;
        _life = 1;
        _value = 0;
        _state = STATE_NORMAL;
        _type = "generic";
        
    }
    
    // Setters and getters
    public int getX() { return _posX; }
    public int getY() { return _posY; }
    public void setX(int passX) { _posX = passX; }
    public void setY(int passY) { _posY = passY; }
    public int getVelX() { return _velX; }
    public int getVelY() { return _velY; }
    public void setVelX(int passX) { _velX = passX; }
    public void setVelY(int passY) { _velY = passY; }
    public int getLife() { return _life; }
    public void setLife(int passLife) { _life = passLife; }
    public int getValue() { return _value; }
    public int getState() { return _state; }
    public void setState(int passState) { _state = passState; }
    public String getType() { return _type; }
    public Bitmap getBitmap() { return _bitmap; }
    public OBJ getParent() { return _parent; }
    
    // Process, think, and damager are all specific to the object, so these are blank
    public void process() { }
    public void think(Vector passObjects) { }
    public void damage() { }
    
    // In our game, firing means shooting a photon and playing the zap tone, this is the same
    // for all objects
    public void fire(Vector passObjects, OBJ passParent, int passVelocity)
    {
        Photon tempPhoton;
        
        // If the photon is going up, start it from the top of the object firing it.
        // If its going down, start it from bottom of object firing it
        if (passVelocity > 0)
            tempPhoton = new Photon(passParent.getX(),passParent.getY()+passParent.getBitmap().getHeight(), 0, passVelocity, passParent);        
        else
            tempPhoton = new Photon(passParent.getX(),passParent.getY(), 0, passVelocity, passParent);    
                
        // set X coordinate of photon to the middle of the object firing it
        tempPhoton.setX(tempPhoton.getX() + passParent.getBitmap().getWidth()/2);
        
        // Add the photon object to our object vector
        passObjects.addElement(tempPhoton);
        
        // Play a zap tone
        GamePlay.snd.playSound();
        
    }
    
    
    // Collision detection routine using an AABB test (Axis Align Bounding Box).  This
    // is a quick and easy test great for games with simple squarish sprites which simply
    // looks to see if the bounding boxes overlap in any way.
    public static void collisionDetect(Vector passObjects)
    {
        OBJ tempObject1, tempObject2; // temporarily points to the two objects being tested
        boolean intersect, check; // flags during testing
        
        // Loop through all objects in our vector
        for (int lcv = 0 ; lcv < passObjects.size() ; lcv++)
        {
            // Set tempObject1 to the current object
            tempObject1 = (OBJ) passObjects.elementAt(lcv);
            
            // Now loop from the current object to the end of the vector
            for(int lcv2 = lcv ; lcv2 < passObjects.size() ; lcv2++)
            {
                // Set tempObject2 to the current object of the nested loop
                tempObject2 = (OBJ) passObjects.elementAt(lcv2);
                
                // See if we need to check for collision (e.g. some objects dont matter if 
                // they collide, enemy with enemy or fire with fire for example)
                
                // Assume we dont need to check
                check = false;
                
                // Hero and enemy would be something to check for
                if (tempObject1.getType() == "hero" && tempObject2.getType().startsWith("enemy"))
                    check = true;
                
                // Hero and enemy fired photons would be something to check for    
                if (tempObject1.getType() == "hero" && tempObject2.getType().startsWith("fire") && tempObject2.getParent().getType().startsWith("enemy"))
                    check = true;
               
                // Enemy and hero fired photons would be something ot check for     
                if (tempObject1.getType().startsWith("enemy") && tempObject2.getType().startsWith("fire") && tempObject2.getParent().getType() == "hero")
                    check = true;
                
                // If our check flag is set to true, and the state of the objects is normal
                // (e.g. an object in a hit or exploded state can't collide with something),
                // then lets check for the actual collision
                if (check && tempObject1.getState() == 0 && tempObject2.getState() == 0)
                {
                    
                    // We assume the two objects collided
                    intersect = true;
                    
                    // Left and Right sides of bounding box check
                    if (!(Math.abs((tempObject1.getX() + tempObject1.getBitmap().getWidth()/2) - (tempObject2.getX() + tempObject2.getBitmap().getWidth()/2)) <= tempObject1.getBitmap().getWidth() / 2 + tempObject2.getBitmap().getWidth() / 2))
                        intersect = false;
    
                    // Top and Bottom sides of bounding box check
                    if (!(Math.abs((tempObject1.getY() + tempObject1.getBitmap().getHeight()/2) - (tempObject2.getY() + tempObject2.getBitmap().getHeight()/2)) <= tempObject1.getBitmap().getHeight() / 2 + tempObject2.getBitmap().getHeight() / 2))
                        intersect = false;
    
                    // If the objects collided, damage each one.
                    if (intersect)
                    {
                        tempObject1.damage();
                        tempObject2.damage();
                    }                    
                }
            }
        }    
    }

    // Clean up objects that have died or are way off screen.
    public static int cleanObjects(Vector passObjects)
    {
        OBJ tempObject; // Temporary points to object we're checking
        boolean delFlag; // Flag if we should get rid of it or not
        int scoreAdd; // Aggregate points to add to user's score
        
        // Start out with no points added
        scoreAdd = 0;
        
        // Loop through all objects in our vector
        for (int lcv = 0 ; lcv < passObjects.size() ; lcv++)
        {
            // Set tempObject to current object
            tempObject = (OBJ) passObjects.elementAt(lcv);
            
            // Assume we're not deleting it
            delFlag = false;
            
            // Check the object's state.  If its been dying for 10 refreshes, its
            // time to get rid of it.
            if (tempObject.getState() > STATE_DYING + 10)
            {
                // In the case of our hero, the player has lives, so if the hero
                // dies, we need to check to see if any lives are left before
                // quitting the game
                if (tempObject.getType() == "hero")
                {
                    // If there are lives left...
                    if (((Hero)tempObject).getLives() > 0)
                    {
                       // Decrement the number of lives left, set state, bitmap,
                       // and position back to normal
                       ((Hero)tempObject).setLives(((Hero)tempObject).getLives()-1);
                       tempObject.setLife(5);
                       tempObject.setState(STATE_NORMAL);
                       tempObject.setX(Graphics.getScreenWidth() / 2);
                       tempObject.setY(Graphics.getScreenHeight() - 50);   
                       tempObject._bitmap = Bitmap.getBitmapResource("herogame.png");
                    }
                    else
                    {
                       // The player is out of lives, lets destroy the hero (which will
                       // end the game)
                       delFlag = true;    
                    }
                }
                else
                {
                    // Enemies only have 1 life, so they are set to be deleted, and we
                    // add their value to the total score the player got this cleanup.
                    delFlag = true;
                    scoreAdd += tempObject.getValue();
                }
            }
            
            // If the object is a photon...
            if (tempObject.getType() == "firephoton" && delFlag == false)
            {  
                // Delete if off left side of screen
                if(tempObject.getX() + tempObject.getBitmap().getWidth() < 0)
                    delFlag = true;
                
                // Delete if off right side of screen    
                if(tempObject.getX() > Graphics.getScreenWidth())
                    delFlag = true;
                    
                // Delete if off top of screen
                if(tempObject.getY() + tempObject.getBitmap().getHeight() < 0)
                    delFlag = true;
                
                // Delete if off bottom of screen    
                if(tempObject.getY() > Graphics.getScreenHeight())
                    delFlag = true;            
            }
            
            // We need to check for enemies that are way off screen.  
            // Normally enemies will swarm around here, but there are 
            // also kamikaze enemies that will aim toward the hero,
            // and if miss, keep going forever
            if (tempObject.getType() == "enemydrone" && delFlag == false)
            {  
                // Check each of the four sides of the screen, if an enemy is
                // past any of them plus 100 pixels its considered lost.  No
                // points are scored for these enemies though.
                if(tempObject.getX() + tempObject.getBitmap().getWidth() < -100)
                    delFlag = true;
                    
                if(tempObject.getX() > Graphics.getScreenWidth() + 100)
                    delFlag = true;
                    
                if(tempObject.getY() + tempObject.getBitmap().getHeight() < -100)
                    delFlag = true;
                    
                if(tempObject.getY() > Graphics.getScreenHeight() + 100)
                    delFlag = true;            
            }
            
            // If the delete flag is true
            if (delFlag)
            {
                // Remove the object from the vector
                passObjects.removeElementAt(lcv);
                
                // Set our temporary object to null
                tempObject = null;
                
                // If this was our hero object (eg location 0), then we return a -1
                // to communicate this back to our processing routine
                if (lcv == 0)
                {
                    return(-1);
                }
            }
        }
        
        // If our hero is still alive, we return to the number of points scored
        return scoreAdd;
    }
        
    // A quick method simply to ensure screen bound objects don't go off screen.
    // For now this is just our hero, but there may be other objects that function
    // like this.    
    public void boundToScreen()
    {
      // If the coordinates are off screen in any direction, correct the coordinate and
      // set that velocity to 0
      if (_posX < 0)
      {
         _posX = 0;
         _velX = 0;
      }
      
      if (_posY < 0)
      {
          _posY = 0;
          _velY = 0;
      }
      
      if (_posX > Graphics.getScreenWidth() - _bitmap.getWidth())
      {
          _posX = Graphics.getScreenWidth() - _bitmap.getWidth();
          _velX = 0;
      }

      if (_posY > Graphics.getScreenHeight() - _bitmap.getHeight())
      {
          _posY = Graphics.getScreenHeight() - _bitmap.getHeight();
          _velY = 0;
      }
        
    }

}

// Hero object
class Hero extends OBJ
{
   int _lives; // The hero (the player) has multiple lives, different from other objects
   
   Hero(int passX, int passY)
   {
      super(passX, passY);
      
      // Set bitmap to herogame
      _bitmap = Bitmap.getBitmapResource("herogame.png");    
      
      // Set coordinates, velocity, lives, and type identifier
      _velX = 0;
      _velY = 0;
      _life = 5;
      _lives = 2;
      _type = "hero";
   }    
   
   // Hero processing
   public void process()
   {
      // The hero has a max velocity of 10 in any direction
      if (_velX > 10)
        _velX = 10;
      
      if (_velX < -10)
        _velX = -10;
      
      if (_velY > 10)
        _velY = 10;
      
      if (_velY < -10)
        _velY = -10;
        
      // Movement is simply adding velocity to position  
      _posX += _velX;
      _posY += _velY;  

      // If the current life of the hero is less than 1, we check the state of hero
      if (_life < 1)
      {
        
        // If the hero is currently in a normal state, it is put into a dying state
        if (_state == STATE_NORMAL)
        {
            // Set hero bitmap to an explosion
            _bitmap = Bitmap.getBitmapResource("explosiongame.png");    
            
            // Set state to dying
            _state = STATE_DYING;
            
            // Vibrate the phone
            GamePlay.snd.vibrate(180);
        }
        
      }
      
      // If the hero is in an abnormal state (hit or dying), there is additional processing
      // that must happen
      if (_state > STATE_NORMAL)
      {
          // First, we increment the state by 1.  Non normal states are temporary and are checked
          // for terminate by seeing if the initial state value plus a certain number of 
          // refreshes has been reached.  It allows the object to be in a abnormal state for 10 
          // refreshes, 3 refreshes, however many necessary, and then continue onto 
          // some other state, either back to normal or deleted.
          _state++;
          
          // If the ship has been in the hit state for more than 3 refreshes, set it back to normal
          if ((_state > STATE_HIT + 3) && (_state < STATE_DYING))
          {
            _state = STATE_NORMAL;
            _bitmap = Bitmap.getBitmapResource("herogame.png");    
          }
      }
            
      // Bound our hero to the screen
      boundToScreen();
   }
   
   // Our hero's damage method
   public void damage()
   {
      // Decrease life (not lives) by 1
      _life--;
      
      // If life is still above 0, change our hero to the hit state
      if (_life > 0)
      {
        _bitmap = Bitmap.getBitmapResource("herogamehit.png");
        _state = STATE_HIT;
      }
   }
   
   // Our hero's fire method for when it fires a photon
   public void fire(Vector passObjects)
   {
      //Call the parents fire method, with a velocity of -20
      super.fire(passObjects, this, -20);
   }
   
   // Lives setter and getter
   public int getLives() { return _lives; }
   public void setLives(int passLives) { _lives = passLives; }
        
}

// Photon class
class Photon extends OBJ
{
    
    // Initializes like hero object, only has photon.png as a bitmap, and starts
    // with a program specified velocity
    Photon(int passX, int passY, int passVelX, int passVelY, OBJ passParent)
    {
      super(passX, passY);
      _bitmap = Bitmap.getBitmapResource("photon.png");    
      _velX = passVelX;
      _velY = passVelY;
      _type = "firephoton";        
      _parent = passParent;
    }

    // Photon processing is simple, we simply move the object in accordance to its
    // velocity
    public void process()
    {
        
      // Position = Position + Velocity
      _posX += _velX;
      _posY += _velY;  
               
    }


    // When a photon is damaged, it simply dies and disappears instantly.  
    // This is accomplished by setting the state to STATE_DYING + 11.  Since
    // we put objects in the dying state for 10 frames before delition, this
    // immediate deletes it.
    public void damage()
    {
      _life = 0;
      _state = STATE_DYING+11;
    }

}

// Our enemy class
class EnemyDrone extends OBJ
{
   int _AIRoutine; // AI routine stores what kind of enemy this is, normal or kamikaze
   
   // Enemy initialization is like other objects, except we randomly choose what
   // kind of AI routine it should use
   EnemyDrone(int passX, int passY)
   {
      super(passX, passY);
      _bitmap = Bitmap.getBitmapResource("enemygame.png");    
      _value = 50;
      _type = "enemydrone";
      
      // Statistically, 3 out of 5 enemies are normal, 2 are kamikaze
      if (GamePlay.rndGenerator.nextInt() % 10 < 6)
         _AIRoutine = 0;
      else
         _AIRoutine = 1;
         
   }    
   
   // Enemy processing is identical to hero processing, except enemies don't
   // have a hit state, since they have one life (not to be confused with lives), 
   // one hit kills them, hence theres no need for a hit state
   public void process()
   {
      _posX += _velX;
      _posY += _velY;  
      
      if (_life < 1)
      {
        if (_state == STATE_NORMAL)
        {
            _bitmap = Bitmap.getBitmapResource("explosiongame.png");    
            _state = STATE_DYING;
            GamePlay.snd.vibrate(180);            
        }
       
        _state++;
      }
      
   }
   
   // The think method is where the individual enemy AI takes place
   public void think(Vector passObjects)
   {
    
      // If they've blown up, they can no longer think
      if (_life < 1)
         return;
      
      // We start off with a velocity of 0 in both directions
      _velX = 0;
      _velY = 0;
      
      // Grab a handle on the hero object so we know how to direct our enemies
      Hero tempHero = (Hero) passObjects.elementAt(0);
      
      // If we're in normal AI mode
      if (_AIRoutine == 0)
      {
        // If hero is to our right, set velocity to right
        if (_posX + _bitmap.getWidth() / 2 < tempHero.getX() + tempHero.getBitmap().getWidth()/2)
            _velX = 5;
                    
        // If hero is to our left, set velocity to our left
        if (_posX + _bitmap.getWidth() / 2 > tempHero.getX() + tempHero.getBitmap().getWidth()/2)
            _velX = -5;
        
        // Enemies try to stay 40 pixels above hero    
        if (_posY + _bitmap.getHeight()  < tempHero.getY() - 40)
            _velY = 5;
                    
        // If enemy is below hero, they move up            
        if (_posY > tempHero.getY() + tempHero.getBitmap().getHeight())
            _velY = -5;
            
        // Add a little bit of random movement in
        _velX += GamePlay.rndGenerator.nextInt() % 4 - 2;
        _velY += GamePlay.rndGenerator.nextInt() % 4 - 2;
        
        // Random firing, fire 1 in 7 times thinking
        if (GamePlay.rndGenerator.nextInt() % 4 == 1)
        {
            fire(passObjects);
        }
     }
     else
     {         
        // Kamikaze AI is the same for as above for horizontal processing.  
        if (_posX + _bitmap.getWidth() / 2 < tempHero.getX() + tempHero.getBitmap().getWidth()/2)
            _velX = 5;
                    
        if (_posX + _bitmap.getWidth() / 2 > tempHero.getX() + tempHero.getBitmap().getWidth()/2)
            _velX = -5;
      
        // For vertical though, the enemy drone is always going downward at a faster rate
        _velY = 8;   
     }
   }

   // If enemy is damaged, their life is decreased
   public void damage()
   {
     _life--;
   }

   // An enemy firing calls the Object's fire method with a downward direction
   public void fire(Vector passObjects)
   {
      super.fire(passObjects, this, 20);
   }

}

Now that we have our Gameplay and OBJ defined, we’ve taken care of pretty much all of our game logic. Now all that’s left is the code to drive our graphics and sound processing. These classes are called, aptly, GFX and SND!

Onto GFX in part 5…

Creating a Blackberry Game – Part 3

Looking for part 2?

Gameplay

Now that our initial menu switches control to the Gameplay class (by putting an instance of it on the screen stack – [gameplay extends FullScreen]), we need to actually define it! Gameplay takes care of high level game functionality by multitasking actions via threads.

One thread, “Refresher”, takes care of creating new objects, looping through existing objects and telling them to process themselves, then checks for collision detection, adds points gained to total score, tells the graphics engine to redraw the screen, etc. It is basically the primary heartbeat loop that ensures everything keeps updating properly.

The other thread is the EnemyAI thread, which is responsible for looping through all the objects and telling enemies to run their intelligence processing, which sets their movement and firing.

Please note – this is one of the areas where the Blackberry emulator seemed to differ from the real thing. On the emulator, thread processing would simply halt if no key was being pressed, while on the Blackberry it would have no issues. While this might be a fault of mine, it doesn’t change the fact that the emulator acted very much differently than the real thing.

Lastly, gameplay has keychar and navigationMovement overridden to process when the Escape or Spacebar is pressed, as well as when the trackball is moved.

GamePlay.java

package com.synthdreams.GalacticBlast;

import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.container.FullScreen;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.system.Characters;
import net.rim.device.api.system.Application;
import java.lang.Thread;
import java.lang.Math;
import java.util.*;

// Gameplay extends FullScreen only, it doesn't need the functionality of the
// MainScreen object.  Either are screens though, and can be pushed onto the 
// screen stack
// Special Note: For those familiar with the concept double buffering, supposedly
// the rim UI automatically does this.  It seems to from what I can see, I
// notice no tearing or graphic anomalies.
// For those unfamiliar, if you blit (write) graphic data directly primary graphic
// buffer, it is out of sync with the refresh of the screen.  This can cause
// tearing or other weird graphic issues, as half of the screen is still being shown
// where the objects were, and the other half where the objects are now.  This
// is solved by first drawing to a back buffer, and then flipping the entire
// back buffer with the front during the screen's refresh.  In java there are
// specific game-oriented screen classes to do this, but supposedly the RIM
// libs do this automatically. 
public class GamePlay extends FullScreen
{
    // These three objects are called from many places and are made public
    public static GFX gfx;  // Static object for graphics control
    public static SND snd;  // Static object for sound control
    public static Random rndGenerator; // Static object for random numbers
    
    Refresher _refresher;  // Thread that continually refreshes the game
    EnemyAI _enemyai; // Thread when the enemies think
    Vector _objects; // A vector of all objects currently in play
    
    boolean _active; // Flag if our game is currently active or not (eg did we lose)
    int _score; // Player's current score
    
    // Getters for active and score
    boolean getActive() { return _active; }
    int getScore() {return _score; }
    
    // Refresh is a type of thread that runs to refresh the game.  
    // Its job is to make sure all the processing is called for each object, update the background,
    // update score, check for end of game, etc.  This is the main heartbeat.
    private class Refresher extends Thread
    {
        // When the object is created it starts itself as a thread
        Refresher()
        { 
           start();
        }
        
        // This method defines what this thread does every time it runs.
        public void run()
        {
            // Temporary variable that stores the score value of all
            // the objects that were just cleaned (destroyed/removed)
            // return a negative number if we died
            int cleanReturn;
            
            // This thread runs while the game is active
            while (_active)
            {
                
                // Level population/processing, responsible for creating new enemies
                processLevel();
                
                // Perform physics by calling each object's process command.  Objects
                // are unique and control their own physics (e.g. a photon blast can move
                // faster than our hero ship), so we loop through all our objects
                // and call the process method on each.
                for (int lcv = 0 ; lcv < _objects.size() ; lcv++)
                {
                    ((OBJ) _objects.elementAt(lcv)).process();
                }
                
                // Collision detection of objects.  If they collide, this method
                // will call the damage method of each object, which might lead
                // to no life for the object
                OBJ.collisionDetect(_objects);
                
                // Clean up stuff that's gone (e.g. life of 0 and in 
                // destroyable state, eg explosion graphic), quit if we were destroyed
                cleanReturn = OBJ.cleanObjects(_objects);
                
                // If we didn't die, add the score, redraw, etc.
                if (cleanReturn >= 0)
                {
                   _score += cleanReturn;
                   
                   // Now that all our processing is done, tell the graphics engine to 
                   // redraw the screen through a call to invalidate (invalidates the screen
                   // and automatically causes a redraw)
                   invalidate();
                }
                else
                {
                   // if we died, mark active as false.
                   _active = false;    
                }
                   
                try
                {
                    // Attempt to sleep for 50 ms
                    this.sleep(50);
                        
                }
                catch (InterruptedException e)
                {
                    // Do nothing if we couldn't sleep, we don't care about exactly perfect
                    // timing.
                }
            }
        }
                
    }

    // We have a separate thread that takes care of enemy AI.  This allows us to control how
    // quickly the enemies think outside of our main refresh thread.  That way we can make
    // dumb enemies that think much slower than the action happening around them, or smart
    // enemies that think as fast.
    private class EnemyAI extends Thread
    {
        EnemyAI()
        { 
           start();
           
        }
        
        public void run()
        {
            // Just make sure the thread doesn't accidentally run when the game is over
            while (_active)
            {
                // Loop through all the objects and call the think method, which
                // controls the actions of that object.  Technically, this call's
                // the hero's think method as well, but Hero doesn't have the
                // think method overridden from the parent's, which is just blank,
                // so the hero does no automatic thinking.
                for (int lcv = 0 ; lcv < _objects.size() ; lcv++)
                {
                    if (_active)
                        ((OBJ) _objects.elementAt(lcv)).think(_objects);
                }
                
                try
                {
                    // Enemies think 5 times a second
                    this.sleep(1000/5);
                        
                }
                catch (InterruptedException e)
                {
                    // Do nothing, again we don't care if timing isn't exact
                }
            }

        }
        
    }

    public GamePlay()
    {
        snd = new SND(); // Create sound engine
        gfx = new GFX(); // Create graphics engine
    
        rndGenerator = new Random(); // Create random number generator

        _active = true; // Mark the game as active
        _score = 0; // Start with a score of 0

        // Set our background to stars.png with a speed of 3 pixels per refresh
        gfx.initBackground("stars.jpg", 3);

        // Start our music playing.  Something odd I noticed with my Blackberry 8830,
        // which may either be a bug or some functionality I'm missing from the
        // sound routines, is the volume starts out very quiet the first time you
        // start playing. If you stop the music playing and play it again, the volume
        // is fine.  This might not be true for all Blackberrys, but it doesn't hurt
        // anything to start, stop, and start again, so that's what we've done here,
        // just to solve the volume bug/misundertsanding.
        snd.playMusic("music.mid");
        snd.stopMusic();
        snd.playMusic("music.mid");

        // Create a new vector to hold all the active objects (hero, enemies, photons, etc)
        _objects = new Vector();
        
        // Our hero will always be the very first object in the vector.
        _objects.addElement(new Hero(Graphics.getScreenWidth() / 2, Graphics.getScreenHeight() - 50));
        
        // Create the refresher and enemy AI last, we want to make sure all our objects are setup first
        // so the threads don't make use of uninitialized objects
        _refresher = new Refresher();        
        _enemyai = new EnemyAI();
       
    }
  
    // Process level is responsible for new computer generated events in the game.  In our
    // case, the only one really is creating new enemies.  
    public void processLevel()
    {
        OBJ tempObject;
        
        // Throw a new enemy in every 25 pixels.  This can be made more complex if desired
        // of course
        if (gfx.getBackPos() % 25 == 0)
        {
            // Create a new enemy drone with the X coordinate somewhere between the two edges of the
            // screen
            tempObject = new EnemyDrone(rndGenerator.nextInt() % Graphics.getScreenWidth(), 0);
            
            // Set the Y coordinate to above the screen, so it comes in from the top
            tempObject.setY(tempObject.getY() - tempObject.getBitmap().getHeight() + 3);
            
            // Add the enemy to the object vector
            _objects.addElement(tempObject);    
        }
    }
 
    // This method is called when the invalidate method is called from the refresh thread.
    // We have it passing the graphics object over to our graphics engine so our
    // custom graphics routines can take care of any drawing necessary.
    protected void paint(Graphics graphics)
    { 
       gfx.process(graphics, _objects, _score, ((Hero)_objects.elementAt(0)).getLives()); 
    }
    
    // The keyChar method is called by the event handler when a key is pressed.  
    public boolean keyChar(char key, int status, int time) 
    {
        
        boolean retVal = false;
        
        switch (key)
        {
            // If escape is pressed, we set the game to inactive (quit)
            case Characters.ESCAPE:
                _active = false;
                retVal = true;
                break;
                
            // If the spacebar is pressed, we call the fire method of our
            // hero object, causing him to fire a photon
            case Characters.SPACE:
                ((Hero)_objects.elementAt(0)).fire(_objects);

                retVal = true;
                break;
            
            default:
               break;
        }
                 
        return retVal;
    }
    
    // The navigationMovement method is called by the event handler when the trackball is used. 
    protected boolean navigationMovement(int dx, int dy, int status, int time)
    {
        // If the trackball was scrolled in the horizontal direction, we add that amount to
        // our hero's X velocity
        if (dx != 0)
            ((OBJ)_objects.elementAt(0)).setVelX(dx/Math.abs(dx)*5+((OBJ)_objects.elementAt(0)).getVelX());

        // If the trackball was scrolled in the vertical direction, we add that amount to
        // our hero's Y velocity            
        if (dy != 0)
            ((OBJ)_objects.elementAt(0)).setVelY(dy/Math.abs(dy)*5+((OBJ)_objects.elementAt(0)).getVelY());
            
        return true;
    }
    
}

Gameplay together with the OBJ set of classes is the heart of the logic of our game. Gameplay represents the manager, the high level control that directs actions, while the OBJ classes do the nitty gritty work of processing for each object (objects being things like the hero, the enemies, and photons). Now that we’ve seen Gameplay, lets take a look at OBJ (objects).

Onto the objects in part 4…

Creating a Blackberry Game – Part 2

GalacticBlast.java

package com.synthdreams.GalacticBlast;

import net.rim.device.api.ui.UiApplication;

/**
 * Initial main class that starts the ball rolling
 * UiApplication is extended to provide Blackberry
 * functionality, specifically the Event Dispatcher
 * and Screen pusher
 */
public class GalacticBlast extends UiApplication
{
  
    public static void main(String[] args)
    {
        GalacticBlast GalacticBlast = new GalacticBlast();
        
        // The Blackberry's message pump, handles key
        // presses and system events
        GalacticBlast.enterEventDispatcher();
    }
    
    public GalacticBlast()
    {
        // The first thing we do is show the menu screen, defined by the
        // 'Menu' class.  We accomplish this with a call to pushScreen,
        // which puts screens on the stack (Class specified must be a type
        // of screen, such as MainScreen).  The top most screen is the
        // one shown.  Our first one will be the menu, and later the game
        // itself will sit on top of this.  When the game quits, it will
        // pop that screen off the stack and return to the menu screen.
        pushScreen(new Menu());              
    }
} 

Nothing major going on there, simply a place to get started. As we can see, things are immediately handed off to our Menu class, which handles the main menu. It is common functionality to have a main menu where the player can play, save, load, configure, quit, etc.

The Main Menu

Our main menu extends MainScreen, as I wanted just a simple screen with text and buttons, using the built in Blackberry look and feel. However, there is nothing stopping you from using the techniques in the actual gameplay section for displaying more advanced graphics in the menu section. But for this tutorial we have a simple main menu.

The concept behind Blackberry forms is there is a layout manager that occupies a certain amount of screen space, and contains fields that it positions accordingly. You can have multiple layout managers on one screen, each with its own child fields. There are different built in layout managers, and you can make your own custom ones as well. For our main menu, we’ll both use the default built in manager that simply repeats fields in the vertical direction, as well as a custom manager that allows us to specify the exact X,Y coordinates of fields within the layout manager.

Additionally, we’ll be making a special start button that will start the game play by instantiating the Gameplay class and pushing it onto the screen stack (just like our initial class pushed our Menu class onto the screen stack). Also check out our custom fields and field manager that allow us to position fields specifically by X and Y coordinate. Our field manager allows us to specify its height, so we can use it as a simple vertical-space buffer as well.

Menu.java

package com.synthdreams.GalacticBlast;

import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;

/**
 * The menu class handles showing the main menu and intercepting when the user quits or 
 * begins the game.  If the user presses the button to begin, the Menu class starts
 * the game going.
 */

class Menu extends MainScreen
{
    GamePlay _game;  // GamePlay handles the action part of the game itself
    int _invokeID; // A handle to our invocation of scanning for when the game ends
    
    // To add functionality to clicking the button, we need to override the button's
    // trackwheelClick method.  Here we define _startButton as a button with
    // text, positioning, and trackwheelClick we want.
    ButtonField _startButton = new ButtonField("Start Game!", ButtonField.FIELD_HCENTER | ButtonField.FIELD_BOTTOM)
    {

       protected boolean trackwheelClick(int status, int time)
       {
          // If the button is pressed, we create a new GamePlay object
          _game = new GamePlay();
          
          // Then we push it onto the screen stack.  This then becomes the
          // active screen.  See GalacticBlast.java for notes about pushing
          // screens onto the stack
          getUiEngine().pushScreen(_game);
          
          // The invokeLater method allows us to continually run a segment of
          // code from outside the GamePlay object.  In this case, we do
          // this to monitor if the gameplay object is active our not.  
          // When the player loses, the object marks itself inactive, at
          // which point we first cancel invoking further, then stop
          // the music from playing, then pop the gameplay screen off
          // the stack so we return to the main menu.
          _invokeID = getApplication().invokeLater(new Runnable()
            {
                public void run()
                {
                    // Check to see if the game is done.
                    if (_game.getActive() == false)
                    {
                        // Cancel invoking this piece of code again (normally is invoked
                        // every 500 ms, as specified below)
                        getApplication().cancelInvokeLater(_invokeID);
                        
                        // Kill the music
                        GamePlay.snd.stopMusic();
                        
                        // Pop the gameplay screen off the stack, which returns
                        // the user to the main menu
                        getUiEngine().popScreen(_game);
                        
                        // Display the final score
                        Dialog.inform("Final Score: " + _game.getScore());
                        
                        // We're done with our game object now
                        _game = null;
                    }
                }
            }
            , 500,true); // rerun this code every 500ms
          
          return true;
       }    
    };   
    
    // Normally LabelFields are arranged in a very plain order depending on the
    // Layout manager.  They'll repeat vertically with the option of left/center/right
    // justifying.  But sometimes it's nice to be able to specificy exactly in X,Y coordinates
    // where you want the field to go.  This class, in conjunction with the Custom Manager
    // defined below, allows for this
    // Additionally, a "customStyle" is defined for the field.  For our cases, this 
    // is either left to 0 for normal X,Y positioning, set to 1 for X set to 1/8th the total
    // width of the screen, or set to 2 for X+fieldwidth set to 7/8th the total width of the screen.
    // 1/8th and 7/8th for the edge of the text allows for columns to be made, for scores,
    // instructions, or anything else.  More custom centering or something more dynamic
    // could be done with this variable, but its fine for our purposes right now.
    class CustomTextField extends LabelField
    {
       int _xPos, _yPos, _customStyle; // coordinates and style
       
       // We pass in the coordinates and the custom style
       CustomTextField(String passLabel, int passStyle, int passX, int passY)
       {
              super(passLabel);
              _xPos = passX;
              _yPos = passY;
              _customStyle = passStyle;
       }    
       
       // Getters for position and style
       int getX() { return _xPos; }
       int getY() { return _yPos; }
       int getCustomStyle() { return _customStyle; }
    }
    
    // Our custom manager is where the magic happens for allowing customtextfields
    // to be placed at any X,Y coordinate.  It reads in the coordinates from the
    // field and places it at those coordinates in the layout.  It will also
    // look at custom layout, and if centered is specified, will ignore the X coordinate
    // and center it at the Y coordinate on the screen.  Additionally, the total height
    // of the manager can be control how much space it takes up regardless of how many
    // fields it contains
    class CustomManager extends Manager
    {
        int _managerHeight; // Total height of the manager
        
        // Pass in desired height.  Scrolling is turned off in both directions.
        public CustomManager(int passHeight)
        {
            super(Manager.NO_HORIZONTAL_SCROLL | Manager.NO_VERTICAL_SCROLL);
            _managerHeight = passHeight;
        }   
        
        // Sublayout is called automatically to position all the internal fields to this
        // layout.  Its sublayouts job to read in the custom coordinates of each of the fields
        // and place them accordingly.
        protected void sublayout(int width, int height) 
        {
            CustomTextField field;
        
            // Loop through all the fields contained with the layout manager
            for (int lcv = 0; lcv < getFieldCount(); lcv++)
            {
                //Get the field.
                field = (CustomTextField)getField(lcv);
            
                //Obtain the custom x and y coordinates for
                //the field and set the position for
                //the field.
                switch (field.getCustomStyle())
                {
                   // Custom style 1 is for the left side of the text to be at 1/8th the width
                   // of the screen
                   case 1:
                        setPositionChild(field, width / 8 , field.getY());
                        break;
                        
                   // Custom style 2 is for the right side of the text to be at 7/8ths the width
                   // of the screen    
                   case 2:
                        setPositionChild(field, width * 7 / 8 - field.getPreferredWidth(), field.getY());    
                        break;
                        
                   // Any other custom style gets position strictly from X,Y
                   default:
                        setPositionChild(field, field.getX(), field.getY());  
                    
                }
                 
                
                 //Layout the field.
                 layoutChild(field, width, height);
            }

            //Set the manager's dimensions
            setExtent(width, _managerHeight);
        }
        
        public int getPreferredWidth()
        {
            return Graphics.getScreenWidth();
        }

        public int getPreferredHeight()
        {
            return Graphics.getScreenHeight();
        }
    }

    // Menu constructor
    public Menu()
    {
        // First, turn off scroll bars for this screen in case we accidentally push past the 
        // edge with our fields/whitespace
        super(NO_VERTICAL_SCROLL);

        // We set the title on the screen
        LabelField title = new LabelField("Galactic Blast Demo", LabelField.FIELD_HCENTER);
        setTitle(title);

        // CustomManagers can also be used just as space buffers.  First we make
        // 20 pixels of space
        getScreen().add(new CustomManager(20));
        
        // Add some text        
        add(new LabelField("Instructions", LabelField.FIELD_HCENTER));
        
        // Create another custom manager, but this one we'll use for more than just
        // spacing, we'll actually position some text fields (our instructions)
        CustomManager instManager = new CustomManager(Graphics.getScreenHeight() - 145);                
        getScreen().add(instManager);       

        // A multi-d array that will store our instruction fields
        CustomTextField instArray[][] = new CustomTextField[3][2];
        
        instArray[0][0]= new CustomTextField("Trackball", 1, 0, 20);
        instArray[1][0] = new CustomTextField("Space", 1, 0, 40);
        instArray[2][0] = new CustomTextField("Escape", 1, 0, 60);
        instArray[0][1] = new CustomTextField("Move Ship", 2, 0, 20);
        instArray[1][1] = new CustomTextField("Fire Cannon", 2, 0, 40);
        instArray[2][1] = new CustomTextField("Quit Game", 2, 0, 60);

        // Loop through our array and add each field to the layout manager with a different
        // font
        for (int lcv = 0 ; lcv < 3 * 2 ; lcv++)
        {
            instArray[lcv%3][lcv/3].setFont(Font.getDefault().derive(Font.PLAIN, 16));
            instManager.add(instArray[lcv%3][lcv/3]);
        }

        // add our button that has the click method overridden
        add(_startButton);
        
        // Add a buffer of 10 pixels
        getScreen().add(new CustomManager(10));   
        
        // More text
        LabelField copyrightText = new LabelField("Copyright 2008 Synthetic Dreams", LabelField.FIELD_HCENTER);        
        copyrightText.setFont(Font.getDefault().derive(Font.ITALIC, 14));
        add(copyrightText);
       
    }
        
} 

At this point, your program should be able to load with a menu screen (well, you’ll have to comment out any mentions of other undefined classes). You should see something like this:

Only with your own text, of course. Now onto Part 3…

Creating a Blackberry Game – Part 1

Introduction

I’ve used a number of cell phones, and without a doubt RIM’s Blackberry continues to be my favorite by far. Blackberry users will know what I’m talking about – it’s just a fantastic communications device. One of its few shortcomings, though, is its lack of a large game library. Historically, the Blackberry has been targeted at the business market, and its software reflects that. However, more and more of these fantastic cell phones have wound up in the hands of the average consumer, and with good reason. And with that happening, the need for some good, fun games increases. This is where you come in!

The first thing that happens to me whenever I get a new device, after playing around with it of course, is wanting to program it. It’s just the engineer in me popping out. I’ve made a number of Blackberry applications since I first got my 8830, but I wanted to tackle making a game. There are a few resources on the web, but some are targeted at java devices in general, and others are slightly old, so I wanted to put together a good tutorial about creating a Blackberry game for modern devices from start to finish.

This series of tutorials will document creating a simple space fighter game. A completed example of such a game is available on the Synthetic Dreams website, called “Galactic Blast”. The Galactic Blast Demo is a simple example of how easy it is to create a Blackberry game. With the source code documented here, you can go and create your own game, making it as complex as you want. While Galactic Blast is a space fighter, the principle is the same creating any style, from RPG to platform adventure. All development work will be done in Java, and this tutorial assumes you are familiar with this language. Some quick screenshots from the demo:

Getting Started

While strictly speaking it isn’t necessary to own a Blackberry to do Blackberry development, it definitely helps. While the RIM development tools come with a Blackberry emulator that is pretty spot on to the real thing, there are a few areas where the emulator differs (which will be mentioned as we go), and it’s nice to test it out on a real Blackberry. You definitely will need a compiler and IDE. While there are plugins for Visual Studio, they are limited and not as robust as the full Blackberry Java Development Environment provided by RIM. The Blackberry JDE can be obtained for free from RIM here: http://na.blackberry.com/eng/developers/downloads/jde.jsp.

At the time of this entry, JDE 4.3.0 was used. This download will come with the JDE as well as a device simulator, and tools to simulate being connected to a wireless network on the simulator. The IDE is fairly nice as well, with some intellisense type method/property/class tips.

jde1.jpg

Setting up the Project

After starting up the JDE, you’ll want to create a new workspace/project. Name it whatever you’d like. After your project is open, there are a few things you can set immediately. Right click on the project name in the workspace window, and select “Properties”. In the General tab, you can set information such as Title, Version, Vendor, and Description. This will appear when the user downloads your application, as well as the title of the icon on the Blackberry itself.

Right clicking on the project in the workspace window will also allow you to add existing items (such as png/jpegs/midis) to your project or create new ones (java source). For this project, we make use of 6 source files, though only 4 of them contain anything major.

Note – when you’re compiling and running your program, by default it will run on the emulator. When you want to upload it to your Blackberry to test (without going through the hassle of using the Desktop Manager or OTA downloads), the JDE includes a handy utility called the “javaloader” located in the bin directory. There are a number of tasks it can perform, but one of the most useful is simply copying an executable to an actual device. If your device is connected to your computer via USB, use the “-u” option, and then supply the cod file of the program you wish to upload. E.g. “javaloader -u C:\projects\MyProj1\MyProj1.cod”.

Now that you have your project set up, you’re ready to start the actual programming. See you in part 2…