Sunday, August 21, 2011

Writing a game from scratch, part 2: Activity Stubs

A word of warning here.  This blog in its raw state will cycle from too much detail like a tutorial to completely abstract.  Apologies in advance for those expecting a consistent tutorial throughout.

My natural instinct is to be a bottom-up developer.  As such, when presented with a problem, I get the overview of what needs to be done and then start building the tools and frameworks that I need to complete it.  I have to fight that urge a bit and write high-level stubs, filled with FIXME and TODO descriptions of what should be plugged in.

That is where things will begin here, to allow the most visible parts (the most important parts to the user) to be stubbed first.  This forces the people writing the guts to see them on a regular basis and hopefully clean them up or make suggestions to better them.

So, where do we start?  Pick a platform.  As I happen to have an Android phone and the SDK installed, it makes an obvious choice.  If you do not have it installed, install it, eclipse, and the android plugins for eclipse.  You can find the SDK and, more indirectly, instructions for the plugin here: http://developer.android.com/sdk/index.html

Okay, now what?  We need a project with a bunch of activities.  On each, we will initially stub out some components to reach the others.  While this could be done in a classic game programming style with everything relating to the game completed within a single game loop, I think it is easier -- at least initially -- to do things as separate activities or components.

Create a project called "AfroScratch" with package "com.example.afroscratch", and a new activity "TitleScreen".  You can pick the version of android you wish to target, but 2.1 should provide the widest range of devices.  If all goes well, and you have an AVD configured, you should be able to immediately run your new program in all of its "Hello World" glory.  When satisfied that things run, start adding more activities.  We need the "MainMenu" and "Game" activities to proceed, both of which should belong to the same package as the "TitleScreen".  To create these, add a new class of the desired name and package with the superclass as "android.app.Activity".

To make things more modular, we need multiple views (layouts) defined.  The default "main.xml" should be renamed to "titlescreen.xml", and another should be added for each of the activities you added above, noting that android only allows lowercase characters for resource files.  It is probably also a good habit to create a new string resource file when creating a layout, so they can both be moved to a new project if required.

Now we need to define the initial stubbed UI and hook up the controls.

Title Screen
On the title screen, we need a simple TextView that we can use to display our initial message and act as a button when someone presses it.
To do so, we define the layout in res/layout/titlescreen.xml as:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/TitleText"
android:id="@+id/TitleScreenButton"
/>
</LinearLayout>

That defines a new ID for the control as "TitleScreenButton" and uses a string reference from a newly-added res/values/titlestring.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="TitleText">Click here to skip the title screen</string>
</resources>
 A run can be done to show what the title screen looks like (just a text label), but it still does not do anything.  To make it useful, we need to add a time-delay switch to the MainMenu activity and a button-press action to do the same.

With android applications, it should be noted that all updates to controls must be done through the main thread.  It is possible to schedule the main thread to call back at a later time to update something.  We are going to use this behavior to add our time-delay switch.  Although not technically necessary for switching activities, this may serve useful in your future projects.

Our time delay will make use of a android.os.Handler and a Runnable in src/com/example/afroscratch/TitleScreen.java:
package com.example.afroscratch;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class TitleScreen extends Activity implements View.OnClickListener
{
private static final String LOG_TAG = "TitleScreen";
private void switchToMainMenu()
{
// Switch to the new activity.
startActivity(new android.content.Intent(getApplicationContext(), MainMenu.class)); 
finish(); // Quit the current activity
}
private int m_countdown = 10;
private void updateCountdown()
{
Log.d(LOG_TAG, "run called with countdown=" + m_countdown);
if( m_countdown > 0 )
{
// Decrease the timer by a second and force the handler to update again in the main thread.
--m_countdown;
m_handler.postDelayed(m_updateTask, 1000);
return;
}

switchToMainMenu();
}
private android.os.Handler m_handler = new android.os.Handler();
private Runnable m_updateTask = new Runnable() {
public void run()
{
updateCountdown();
}
};
public void onClick(View v)
{
// Stop the timer.
m_handler.removeCallbacks(m_updateTask);
switchToMainMenu();
}
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.titlescreen);
        
        // Tell the TextView to notify us when it is clicked.
        TextView tv = (TextView)findViewById(R.id.TitleScreenButton);
        tv.setOnClickListener(this);
        
        // Start the timeout update cycle, giving time for this function to return.
        m_handler.postDelayed(m_updateTask, 100);
    }
}

For the activity switch to actually work, the target activity must be listed in the AndroidManifest.xml:
<activity android:name=".MainMenu" android:label="@string/app_name" />
<activity android:name=".Game" android:label="@string/app_name" />

We still need some glue for the main menu and the game.  The activity switch from the main menu to the game will be done in the same manor as from the title screen to the main menu, with the exception of the timer.

More resources and glue are needed...

MainMenu:
  • res/layout/mainmenu.xml:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/Welcome"
    android:id="@+id/WelcomeLabel"
    />
    <Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/NewGame"
    android:id="@+id/NewGameButton"
    />
    <Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/Quit"
    android:id="@+id/QuitButton"
    />
    </LinearLayout>
  • res/values/mainmenu.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="Welcome">Welcome to AfroScratch</string>
<string name="NewGame">New Game</string>
<string name="Quit">Quit</string>
</resources> 
  •  src/com/example/afroscratch/MainMenu.java:
package com.example.afroscratch;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainMenu extends Activity implements OnClickListener
{
/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mainmenu);
        
        Button newGame = (Button)findViewById(R.id.NewGameButton);
        Button quitButton = (Button)findViewById(R.id.QuitButton);
        
        newGame.setOnClickListener(this);
        quitButton.setOnClickListener(this);
    }
    
    public void onClick(View v)
    {
    if( v.getId() == R.id.QuitButton )
    {
    finish();
    }
    else if( v.getId() == R.id.NewGameButton )
    {
    startActivity(new android.content.Intent(getApplicationContext(), Game.class));
    }
    }
}
And finally the game screen:

  • res/layout/game.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="FIXME! Replace me with a game display" />
</LinearLayout>

  • src/com/example/afroscratch/Game.java:
package com.example.afroscratch;

import android.app.Activity;
import android.os.Bundle;

public class Game extends Activity 
{
/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.game);
    }
}

If all goes as planned, the title screen should appear for about 10 seconds, switching automatically to the main menu.  Switching can be done faster by clicking on the title screen's one and only label.

Writing a game from scratch, part 1: Design Overview

All games start with a basic idea.  For this game, we are going to start with the simple game Ataxx.  This game will be referred to as "Afroscratch" (Ataxx from scratch) or just "the game".

While this is not a proper design document, basic functionality and rules will be described before implementation is done.

The rules used for the game are as follows (even if they do not exactly match those of the Wikipedia article):
  1. The game starts with a fixed number of pieces on a grid-like board.  Each player begins with an equal number of pieces each already present in a game square.  Although they will be referred to as a square, the shape can vary and a number of pre-defined shapes will be allowed: square, hexagon, octagon, triangle. 
  2. Up to four players are allowed in a game, although not all game boards will support four players.
  3. Each player takes a turn moving until the board is filled or a single player remains after eliminating the competition.
  4. A move consists of "growing" a piece into an empty square adjacent to an existing square, or "jumping" a piece into a more distant square.  Both moves result in some number of captures. 
    1. "Growing" duplicates the existing piece into the new square, resulting in an additional piece placed on the board.
    2. "Jumping" moves the existing piece into the new square.
    3. All squares within "grow" range of the target square will be "captured" and changed into pieces of the moving player.
  5. If no pieces of a player remain on the board, that player is eliminated.
  6. If a player cannot make a legal move, the next opponent moves instead.  A player may not forfeit their turn if no desirable moves are available.
  7. When all squares on the board are filled the pieces are counted and the player with the most on the board is declared the winner.
  8. Boards must be designed in such a way that no opening move can eliminate an opponent.  That is, each player must be capable of taking at least one turn (preferably two) before they are captured by an opponent.  More specifically, this means that no opening "jump" or "grow" move may capture a piece from an opponent's starting position.
With the basic rules down, we need to decide what will be present in the game.  Initially this will be done as an offline game, although multi-player (hot-seat and online) and online high scores will be considered for all design decisions.

To add an element of fun and challenge, there should be a "story" mode in which the user may progress along levels of unique game boards with opponents of varying levels of difficulty. 

Like most games, we need the basic screens:
  1.  Title screen
    • Normally done with some logo and animation, this will be kept simple initially.
    • With user input, any animation will be skipped and proceed directly to the main menu.
  2. Main menu
    • Allows the user to select the following (initial) options:
      • New game
      • Begin story
      • Continue story
        • Only available if the user has already started in "story" mode.
      • Options
      • Quit
        • Most of the phone platforms discourage this kind of behavior, and the expected behavior of hitting "back" or "home" to exit the game must exist. 
  3. New Game
    • Allows the user to select options for a new game.  Initially this will all be on one screen, but will be changed to multiple screens as the game and graphics are added.  These options will include:
      • Board type
      • Opponents and AI difficulty
      • (online options)
  4. Begin Story
    • The user will start a new story-mode game starting at the intro (tutorial) level.
    • The story mode will require basic game scripting abilities with dialog and required user input to complete specific operations.
    • With each successful win of a story-mode game, the next level in the story is unlocked and the user will be asked how to proceed.
  5. Continue Story
    • Allow the user to select which level they want to play.  Only unlocked levels may be selected.
  6. Gameplay
    • Normal mechanics of a game.
    • Indicating turns of players
    • Indicating the current piece count of each opponent
    • Moving
      • Allow a player to select a piece
      • Showing the user the target "jump" and "grow" squares for that piece
      • Allow users to select a destination square to finalize a move 
  7. Game completion
    • "you won" or "you lost", showing a tally of piece counts.
    • "play again"
    • "next level" (story mode)
    • "quit to menu"

Now that we have a rough idea of what the game does, we can start writing some of the basic code for it.  This will be continued in future parts.