Samples
Samples
LunarLander
Language: Oxygene, Platform: Cooper, Category: Android
https://github.com/remobjects/ElementsSamples/tree/master/Oxygene/Cooper/Android/LunarLander
-
com.example.android.lunarlander
-
References
- android
- Source Files
-
Other Files
- res\values\strings.android-xml
- Properties\AndroidManifest.android-xml
- res\layout\lunar_layout.layout-xml
- NOTICE
- Readme.txt
- res\drawable-land\earthrise.png
- res\drawable-port\earthrise.png
- res\drawable\app_lunar_lander.png
- res\drawable\lander_crashed.png
- res\drawable\lander_firing.png
- res\drawable\lander_plain.png
-
References
LunarThread.pas
namespace com.example.android.lunarlander;
{*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*}
//BUG: The original Android SDK demo has this bug:
//If the game is over and in a win or lose state, rotating the phone or switching away from
//the game pauses, so when we come back we can unpause and then immediately win or lose again.
//To work around that the implementation of saveState and restoreState should probably
//take into account a win or lose state
//BUG: The original Android SDK demo has this bug:
//Switching away from the app and then back crashes the app with an IllegalThreadStateException
interface
uses
android.content,
android.content.res,
android.graphics,
android.graphics.drawable,
android.os,
android.util,
android.view;
type
LunarThread = public class(Thread)
private
const KEY_DIFFICULTY = 'mDifficulty';
const KEY_DX = 'mDX';
const KEY_DY = 'mDY';
const KEY_FUEL = 'mFuel';
const KEY_GOAL_ANGLE = 'mGoalAngle';
const KEY_GOAL_SPEED = 'mGoalSpeed';
const KEY_GOAL_WIDTH = 'mGoalWidth';
const KEY_GOAL_X = 'mGoalX';
const KEY_HEADING = 'mHeading';
const KEY_LANDER_HEIGHT = 'mLanderHeight';
const KEY_LANDER_WIDTH = 'mLanderWidth';
const KEY_WINS = 'mWinsInARow';
const KEY_X = 'mX';
const KEY_Y = 'mY';
const KEY_MODE = 'mMode';
// Member (state) fields
// Handle to the application context, used to e.g. fetch Drawables.
var mContext: Context;
// The drawable to use as the background of the animation canvas
var mBackgroundImage: Bitmap;
// Current height of the surface/canvas.
// see setSurfaceSize
var mCanvasHeight: Integer := 1;
// Current width of the surface/canvas.
// see setSurfaceSize
var mCanvasWidth: Integer := 1; // What to draw for the Lander when it has crashed
var mCrashedImage: Drawable; //
// Current difficulty -- amount of fuel, allowed angle, etc. Default is
// MEDIUM.
var mDifficulty: Integer;
// Velocity dx.
var mDX: Double;
// Velocity dy.
var mDY: Double;
// Is the engine burning?
var mEngineFiring: Boolean;
// What to draw for the Lander when the engine is firing
var mFiringImage: Drawable;
// Fuel remaining
var mFuel: Double;
// Allowed angle.
var mGoalAngle: Integer;
// Allowed speed.
var mGoalSpeed: Integer;
// Width of the landing pad.
var mGoalWidth: Integer;
// X of the landing pad.
var mGoalX: Integer;
// Message handler used by thread to interact with TextView
var mHandler: Handler;
// Lander heading in degrees, with 0 up, 90 right. Kept in the range
// 0..360.
var mHeading: Double;
// Pixel height of lander image.
var mLanderHeight: Integer;
// What to draw for the Lander in its normal state
var mLanderImage: Drawable;
// Pixel width of lander image.
var mLanderWidth: Integer;
// Used to figure out elapsed time between frames
var mLastTime: Int64;
// Paint to draw the lines on screen.
var mLinePaint: Paint;
// "Bad" speed-too-high variant of the line color.
var mLinePaintBad: Paint;
// The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN
var mMode: Integer;
// Currently rotating, -1 left, 0 none, 1 right.
var mRotating: Integer;
// Scratch rect object.
var mScratchRect: RectF;
// Handle to the surface manager object we interact with
var mSurfaceHolder: SurfaceHolder;
// Number of wins in a row.
var mWinsInARow: Integer;
// X of lander center.
var mX: Double;
// Y of lander center.
var mY: Double;
// To cater for thread pause/resume management
var mPauseLock: Object := new Object;
var mPaused: Boolean;
method doDraw(aCanvas: Canvas);
method updatePhysics;
assembly
method doKeyDown(keyCode: Integer; msg: KeyEvent): Boolean;
method doKeyUp(keyCode: Integer; msg: KeyEvent): Boolean;
public
// Difficulty setting constants
const DIFFICULTY_EASY = 0;
const DIFFICULTY_HARD = 1;
const DIFFICULTY_MEDIUM = 2;
// Physics constants
const PHYS_DOWN_ACCEL_SEC = 35;
const PHYS_FIRE_ACCEL_SEC = 80;
const PHYS_FUEL_INIT = 60;
const PHYS_FUEL_MAX = 100;
const PHYS_FUEL_SEC = 10;
const PHYS_SLEW_SEC = 120; // degrees/second rotate
const PHYS_SPEED_HYPERSPACE = 180;
const PHYS_SPEED_INIT = 30;
const PHYS_SPEED_MAX = 120;
// State-tracking constants
const STATE_LOSE = 1;
const STATE_PAUSE = 2;
const STATE_READY = 3;
const STATE_RUNNING = 4;
const STATE_WIN = 5;
// Goal condition constants
const TARGET_ANGLE = 18; // > this angle means crash
const TARGET_BOTTOM_PADDING = 17; // px below gear
const TARGET_PAD_HEIGHT = 8; // how high above ground
const TARGET_SPEED = 28; // > this speed means crash
const TARGET_WIDTH = 1.6; // width of target
// UI constants (i.e. the speed & fuel bars)
const UI_BAR = 100; // width of the bar(s)
const UI_BAR_HEIGHT = 10; // height of the bar(s)
constructor(aSurfaceHolder: SurfaceHolder; aContext: Context; aHandler: Handler);
method doStart;
method pause;
method restoreState(savedState: Bundle); locked;
method run; override;
method saveState(map: Bundle): Bundle;
method setDifficulty(difficulty: Integer);
method setFiring(firing: Boolean);
method setState(mode: Integer);
method setState(mode: Integer; aMessage: CharSequence);
method setSurfaceSize(width: Integer; height: Integer);
method unpause;
end;
implementation
constructor LunarThread(aSurfaceHolder: SurfaceHolder; aContext: Context; aHandler: Handler);
begin
// get handles to some important objects
mSurfaceHolder := aSurfaceHolder;
mHandler := aHandler;
mContext := aContext;
var res: Resources := aContext.Resources;
// cache handles to our key sprites & other drawables
mLanderImage := res.Drawable[R.drawable.lander_plain];
mFiringImage := res.Drawable[R.drawable.lander_firing];
mCrashedImage := res.Drawable[R.drawable.lander_crashed];
// load background image as a Bitmap instead of a Drawable b/c
// we don't need to transform it and it's faster to draw this way
mBackgroundImage := BitmapFactory.decodeResource(res, R.drawable.earthrise);
// Use the regular lander image as the model size for all sprites
mLanderWidth := mLanderImage.IntrinsicWidth;
mLanderHeight := mLanderImage.IntrinsicHeight;
// Initialize paints for speedometer
mLinePaint := new Paint;
mLinePaint.AntiAlias := true;
mLinePaint.setARGB(255, 0, 255, 0);
mLinePaintBad := new Paint;
mLinePaintBad.AntiAlias := true;
mLinePaintBad.setARGB(255, 120, 180, 0);
mScratchRect := new RectF(0, 0, 0, 0);
mWinsInARow := 0;
mDifficulty := DIFFICULTY_MEDIUM;
// initial show-up of lander (not yet playing)
mX := mLanderWidth;
mY := mLanderHeight * 2;
mFuel := PHYS_FUEL_INIT;
mDX := 0;
mDY := 0;
mHeading := 0;
mEngineFiring := true
end;
/// <summary>
/// Starts the game, setting parameters for the current difficulty.
/// </summary>
method LunarThread.doStart;
begin
locking mSurfaceHolder do
begin
// First set the game for Medium difficulty
mFuel := PHYS_FUEL_INIT;
mEngineFiring := false;
mGoalWidth := Integer(mLanderWidth * TARGET_WIDTH);
mGoalSpeed := TARGET_SPEED;
mGoalAngle := TARGET_ANGLE;
var speedInit: Integer := PHYS_SPEED_INIT;
// Adjust difficulty params for EASY/HARD
if mDifficulty = DIFFICULTY_EASY then
begin
mFuel := mFuel * 3 div 2;
mGoalWidth := mGoalWidth * 4 div 3;
mGoalSpeed := mGoalSpeed * 3 div 2;
mGoalAngle := mGoalAngle * 4 div 3;
speedInit := speedInit * 3 div 4
end
else
if mDifficulty = DIFFICULTY_HARD then
begin
mFuel := mFuel * 7 div 8;
mGoalWidth := mGoalWidth * 3 div 4;
mGoalSpeed := mGoalSpeed * 7 div 8;
speedInit := speedInit * 4 div 3
end;
// pick a convenient initial location for the lander sprite
mX := mCanvasWidth div 2;
mY := mCanvasHeight - mLanderHeight div 2;
// start with a little random motion
mDY := Math.random * -speedInit;
mDX := (Math.random * 2 * speedInit) - speedInit;
mHeading := 0;
// Figure initial spot for landing, not too near center
while true do
begin
mGoalX := Integer(Math.random * (mCanvasWidth - mGoalWidth));
if Math.abs(mGoalX - (mX - mLanderWidth div 2)) > mCanvasHeight div 6 then
break
end;
mLastTime := System.currentTimeMillis + 100;
setState(STATE_RUNNING)
end
end;
/// <summary>
/// Pauses the physics update & animation.
/// </summary>
method LunarThread.pause;
begin
Log.w(&Class.Name, 'LunarThread.pause');
//Check for already-paused state
if mPaused then
exit;
locking mSurfaceHolder do
if mMode = STATE_RUNNING then
setState(STATE_PAUSE);
//NOTE: new thread management code
//To pause the thread we set a flag to true
locking mPauseLock do
mPaused := true
end;
/// <summary>
/// Resumes from a pause.
/// </summary>
method LunarThread.unpause;
begin
Log.w(&Class.Name, 'LunarThread.pause');
// Move the real time clock up to now
locking mSurfaceHolder do
mLastTime := System.currentTimeMillis + 100;
setState(STATE_RUNNING);
//NOTE: new thread management code
//To resume the thread we unset the flag and notify all waiting on the lock
locking mPauseLock do
begin
mPaused := false;
mPauseLock.notifyAll
end;
end;
/// <summary>
/// Dump game state to the provided Bundle. Typically called when the
/// Activity is being suspended.
/// </summary>
/// <param name="map"></param>
/// <returns>Bundle with this view's state</returns>
method LunarThread.saveState(map: Bundle): Bundle;
begin
locking mSurfaceHolder do
begin
if map <> nil then
begin
map.putInt(KEY_DIFFICULTY, mDifficulty);
map.putDouble(KEY_X, mX);
map.putDouble(KEY_Y, mY);
map.putDouble(KEY_DX, mDX);
map.putDouble(KEY_DY, mDY);
map.putDouble(KEY_HEADING, mHeading);
map.putInt(KEY_LANDER_WIDTH, mLanderWidth);
map.putInt(KEY_LANDER_HEIGHT, mLanderHeight);
map.putInt(KEY_GOAL_X, mGoalX);
map.putInt(KEY_GOAL_SPEED, mGoalSpeed);
map.putInt(KEY_GOAL_ANGLE, mGoalAngle);
map.putInt(KEY_GOAL_WIDTH, mGoalWidth);
map.putInt(KEY_WINS, mWinsInARow);
map.putDouble(KEY_FUEL, mFuel);
//NOTE: mMode wasn't stored in the original Java version. This meant
//that is you died and rotated the device, you'd die again. The same
//applied to winning
map.putInt(KEY_MODE, mMode);
end
end;
exit map
end;
/// <summary>
/// Restores game state from the indicated Bundle. Typically called when
/// the Activity is being restored after having been previously
/// destroyed.
/// </summary>
/// <param name="savedState">Bundle containing the game state</param>
method LunarThread.restoreState(savedState: Bundle);
begin
locking mSurfaceHolder do
begin
mRotating := 0;
mEngineFiring := false;
mDifficulty := savedState.Int[KEY_DIFFICULTY];
mX := savedState.Double[KEY_X];
mY := savedState.Double[KEY_Y];
mDX := savedState.Double[KEY_DX];
mDY := savedState.Double[KEY_DY];
mHeading := savedState.Double[KEY_HEADING];
mLanderWidth := savedState.Int[KEY_LANDER_WIDTH];
mLanderHeight := savedState.Int[KEY_LANDER_HEIGHT];
mGoalX := savedState.Int[KEY_GOAL_X];
mGoalSpeed := savedState.Int[KEY_GOAL_SPEED];
mGoalAngle := savedState.Int[KEY_GOAL_ANGLE];
mGoalWidth := savedState.Int[KEY_GOAL_WIDTH];
mWinsInARow := savedState.Int[KEY_WINS];
mFuel := savedState.Double[KEY_FUEL];
//NOTE: mMode wasn't stored in the original Java version. This meant
//that is you died and rotated the device, you'd die again. The same
//applied to winning
mMode := savedState.Int[KEY_MODE];
case mMode of
STATE_LOSE, STATE_WIN: setState(STATE_READY);
STATE_RUNNING: setState(STATE_PAUSE)
end;
end
end;
method LunarThread.run;
begin
while true do
begin
var c: Canvas := nil;
try
c := mSurfaceHolder.lockCanvas(nil);
locking mSurfaceHolder do
begin
if mMode = STATE_RUNNING then
updatePhysics;
//NOTE: original SDK demo did not check for nil here
//On some devices immediately after rotation c will be nil
if c <> nil then
doDraw(c);
end;
finally
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if c <> nil then
mSurfaceHolder.unlockCanvasAndPost(c)
end;
//NOTE: new thread management code
//If the thread has been paused, then block until we are
//notified we can proceed
locking mPauseLock do
while mPaused do
try
mPauseLock.wait
except
on e: InterruptedException do
;
end
end
end;
/// <summary>
/// Sets the current difficulty.
/// </summary>
method LunarThread.setDifficulty(difficulty: Integer);
begin
locking mSurfaceHolder do
mDifficulty := difficulty
end;
/// <summary>
/// Sets if the engine is currently firing.
/// </summary>
method LunarThread.setFiring(firing: Boolean);
begin
locking mSurfaceHolder do
mEngineFiring := firing
end;
/// <summary>
/// Sets the game mode. That is, whether we are running, paused, in the
/// failure state, in the victory state, etc.
/// see setState(int, CharSequence)
/// </summary>
/// <param name="mode">one of the STATE_* constants</param>
method LunarThread.setState(mode: Integer);
begin
locking mSurfaceHolder do
setState(mode, nil)
end;
/// <summary>
/// Sets the game mode. That is, whether we are running, paused, in the
/// failure state, in the victory state, etc.
/// </summary>
/// <param name="mode">one of the STATE_* constants</param>
/// <param name="message">string to add to screen or null</param>
method LunarThread.setState(mode: Integer; aMessage: CharSequence);
begin
// This method optionally can cause a text aMessage to be displayed
// to the user when the mode changes. Since the View that actually
// renders that text is part of the main View hierarchy and not
// owned by this thread, we can't touch the state of that View.
// Instead we use a Message + Handler to relay commands to the main
// thread, which updates the user-text View.
locking mSurfaceHolder do
begin
mMode := mode;
if mMode = STATE_RUNNING then
begin
var msg: Message := mHandler.obtainMessage;
var b: Bundle := new Bundle;
b.putString('text', '');
b.putInt('viz', View.INVISIBLE);
msg.Data := b;
mHandler.sendMessage(msg)
end
else
begin
mRotating := 0;
mEngineFiring := false;
var res: Resources := mContext.Resources;
var str: CharSequence := case mMode of
STATE_READY: res.Text[R.string.mode_ready];
STATE_PAUSE: res.Text[R.string.mode_pause];
STATE_LOSE: res.Text[R.string.mode_lose];
else {STATE_WIN} res.String[R.string.mode_win_prefix] + mWinsInARow + ' ' + res.String[R.string.mode_win_suffix]
end;
if aMessage <> nil then
str := aMessage.toString + #10 + str;
if mMode = STATE_LOSE then
mWinsInARow := 0;
var msg: Message := mHandler.obtainMessage;
var b: Bundle := new Bundle;
b.putString('text', str.toString);
b.putInt('viz', View.VISIBLE);
msg.Data := b;
mHandler.sendMessage(msg)
end
end
end;
/// <summary>
/// Callback invoked when the surface dimensions change.
/// </summary>
method LunarThread.setSurfaceSize(width: Integer; height: Integer);
begin
// synchronized to make sure these all change atomically
locking mSurfaceHolder do
begin
mCanvasWidth := width;
mCanvasHeight := height;
// don't forget to resize the background image
mBackgroundImage := Bitmap.createScaledBitmap(mBackgroundImage, width, height, true)
end
end;
/// <summary>
/// Handles a key-down event.
/// </summary>
/// <param name="keyCode">the key that was pressed</param>
/// <param name="msg">the original event object</param>
/// <returns>true</returns>
method LunarThread.doKeyDown(keyCode: Integer; msg: KeyEvent): Boolean;
begin
locking mSurfaceHolder do
begin
var okStart: Boolean := false;
if keyCode in [KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_S] then
okStart := true;
if okStart and (mMode in [STATE_READY, STATE_LOSE, STATE_WIN]) then
begin
// ready-to-start -> start
doStart;
exit true
end
else
if (mMode = STATE_PAUSE) and okStart then
begin
// paused -> running
unpause;
exit true
end
else
if mMode = STATE_RUNNING then
case keyCode of
// center/space -> fire
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_SPACE:
begin
Log.i(&Class.Name, 'firing');
setFiring(true);
exit true
end;
// left/q -> left
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_Q:
begin
mRotating := -1;
exit true
end;
// right/w -> right
KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_W:
begin
mRotating := 1;
exit true
end;
// up -> pause
KeyEvent.KEYCODE_DPAD_UP:
begin
pause;
exit true
end;
end;
exit false
end
end;
/// <summary>
/// Handles a key-up event.
/// </summary>
/// <param name="keyCode">the key that was pressed</param>
/// <param name="msg">the original event object</param>
/// <returns>true if the key was handled and consumed, or else false</returns>
method LunarThread.doKeyUp(keyCode: Integer; msg: KeyEvent): Boolean;
begin
var handled: Boolean := false;
locking mSurfaceHolder do
begin
if mMode = STATE_RUNNING then
case keyCode of
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_SPACE:
begin
Log.i(&Class.Name, 'not firing');
setFiring(false);
handled := true
end;
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_Q, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_W:
begin
mRotating := 0;
handled := true
end;
end;
end;
exit handled
end;
/// <summary>
/// Draws the ship, fuel/speed bars, and background to the provided
/// Canvas.
/// </summary>
method LunarThread.doDraw(aCanvas: Canvas);
begin
// Draw the background image. Operations on the Canvas accumulate
// so this is like clearing the screen.
aCanvas.drawBitmap(mBackgroundImage, 0, 0, nil);
var yTop: Integer := mCanvasHeight - Integer(mY + mLanderHeight div 2);
var xLeft: Integer := Integer(mX - mLanderWidth div 2);
// Draw the fuel gauge
var fuelWidth: Integer := Integer(UI_BAR * mFuel div PHYS_FUEL_MAX);
mScratchRect.&set(4, 4, 4 + fuelWidth, 4 + UI_BAR_HEIGHT);
aCanvas.drawRect(mScratchRect, mLinePaint);
// Draw the speed gauge, with a two-tone effect
var speed: Double := Math.sqrt(mDX * mDX + mDY * mDY);
var speedWidth: Integer := Integer(UI_BAR * speed div PHYS_SPEED_MAX);
if speed <= mGoalSpeed then
begin
mScratchRect.&set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT);
aCanvas.drawRect(mScratchRect, mLinePaint)
end
else
begin
// Draw the bad color in back, with the good color in front of
// it
mScratchRect.&set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT);
aCanvas.drawRect(mScratchRect, mLinePaintBad);
var goalWidth: Integer := UI_BAR * mGoalSpeed div PHYS_SPEED_MAX;
mScratchRect.&set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + goalWidth, 4 + UI_BAR_HEIGHT);
aCanvas.drawRect(mScratchRect, mLinePaint)
end;
// Draw the landing pad
aCanvas.drawLine(mGoalX, 1 + mCanvasHeight - TARGET_PAD_HEIGHT, mGoalX + mGoalWidth, 1 + mCanvasHeight - TARGET_PAD_HEIGHT, mLinePaint);
// Draw the ship with its current rotation
aCanvas.save;
aCanvas.rotate(Single(mHeading), Single(mX), mCanvasHeight - Single(mY));
if mMode = STATE_LOSE then
begin
mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight);
mCrashedImage.draw(aCanvas)
end
else
if mEngineFiring then
begin
mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight);
mFiringImage.draw(aCanvas)
end
else
begin
mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight);
mLanderImage.draw(aCanvas)
end;
aCanvas.restore
end;
/// <summary>
/// Figures the lander state (x, y, fuel, ...) based on the passage of
/// realtime. Does not invalidate(). Called at the start of draw().
/// Detects the end-of-game and sets the UI to the next state.
/// </summary>
method LunarThread.updatePhysics;
begin
var now: Int64 := System.currentTimeMillis;
// Do nothing if mLastTime is in the future.
// This allows the game-start to delay the start of the physics
// by 100ms or whatever.
if mLastTime > now then
exit;
var elapsed: Double := (now - mLastTime) / 1000.0;
// mRotating -- update heading
if mRotating <> 0 then
begin
mHeading := mHeading + (mRotating * PHYS_SLEW_SEC * elapsed);
// Bring things back into the range 0..360
if mHeading < 0 then
mHeading := mHeading + 360
else
if mHeading >= 360 then
mHeading := mHeading - 360
end;
// Base accelerations -- 0 for x, gravity for y
var ddx: Double := 0;
var ddy: Double := -PHYS_DOWN_ACCEL_SEC * elapsed;
if mEngineFiring then
begin
// taking 0 as up, 90 as to the right
// cos(deg) is ddy component, sin(deg) is ddx component
var elapsedFiring: Double := elapsed;
var fuelUsed: Double := elapsedFiring * PHYS_FUEL_SEC;
// tricky case where we run out of fuel partway through the
// elapsed
if fuelUsed > mFuel then
begin
elapsedFiring := mFuel div fuelUsed * elapsed;
fuelUsed := mFuel;
// Oddball case where we adjust the "control" from here
mEngineFiring := false
end;
mFuel := mFuel - fuelUsed;
// have this much acceleration from the engine
var accel: Double := PHYS_FIRE_ACCEL_SEC * elapsedFiring;
var radians: Double := 2 * Math.PI * mHeading div 360;
ddx := Math.sin(radians) * accel;
ddy := ddy + Math.cos(radians) * accel
end;
var dxOld: Double := mDX;
var dyOld: Double := mDY;
// figure speeds for the end of the period
mDX := mDX + ddx;
mDY := mDY + ddy;
// figure position based on average speed during the period
mX := mX + (elapsed * (mDX + dxOld) div 2);
mY := mY + (elapsed * (mDY + dyOld) div 2);
mLastTime := now;
// Evaluate if we have landed ... stop the game
var yLowerBound: Double := TARGET_PAD_HEIGHT + (mLanderHeight div 2) - TARGET_BOTTOM_PADDING;
if mY <= yLowerBound then
begin
mY := yLowerBound;
var &result: Integer := STATE_LOSE;
var msg: CharSequence := '';
var res: Resources := mContext.Resources;
var speed: Double := Math.sqrt(mDX * mDX + mDY * mDY);
var onGoal: Boolean := (mGoalX <= mX - mLanderWidth div 2) and
(mX + mLanderWidth div 2 <= mGoalX + mGoalWidth);
// "Hyperspace" win -- upside down, going fast,
// puts you back at the top.
if (onGoal and (Math.abs(mHeading - 180) < mGoalAngle) and
(speed > PHYS_SPEED_HYPERSPACE)) then
begin
&result := STATE_WIN;
inc(mWinsInARow);
doStart;
exit
end
else
if not onGoal then
msg := res.Text[R.string.message_off_pad]
else
if not (mHeading <= mGoalAngle) or (mHeading >= 360 - mGoalAngle) then
msg := res.getText(R.string.message_bad_angle)
else
if speed > mGoalSpeed then
msg := res.Text[R.string.message_too_fast]
else
begin
&result := STATE_WIN;
inc(mWinsInARow)
end;
setState(&result, msg)
end
end;
end.
