/* * LittleLives, by Dan Krusi for airbit AG (www.airbit.ch) * Zurich, Switzerland, October 2008. Version 1 * * LittleLives is a very simple side-scrolling game written for * mShell 3.0. It takes advantage of m's new object-oriented * programming support, such as classes and inheritance. It is * designed to show the power and speed of mShell on Symbian * mobile devices, and furthermore the creative results a small * m script can produce. * * The game is built up around the Object class which contains * two methods process() and paint(). The process() method is * responsible for processing the objects logics, such as * movement and simple AI. The paint() method is responsible for * drawing the object on screen, including it's rotation and * viewport offset. For each frame in the game, every living * (active) object is processed and painted once. * * The arrow keys on the mobile device control an on-screen * cursor. The object .tadpole will always follow this cursor. * Furthermore, moving the cursor off the edge of the screen * will scroll the viewport. * * There are no real events in the game; each object is * responsible for figuring out its own events. * * Please feel free to modify this script to your wishes and * needs. Feedback is always appreciated: dan.kruesi@airbit.ch. */ use graph; use array; use ui; use math; use time; use try accel; // Forward declarations function getAngle(x, y, destX, destY) forward; function rndInt(min, max = null) forward; function getObjectsOfType(type) forward; function getRndObjectOfType(type) forward; function addLetterText(x, y, size, text) forward; // Global constants Optimal value WATERPLANT_CLUSTERS_MIN = 5; // 5 WATERPLANT_CLUSTERS_MAX = 6; // 6 WATERPLANT_PER_CLUSTER_MIN = 2; // 2 WATERPLANT_PER_CLUSTER_MAX = 6; // 6 FOOD_CLUSTERS_MIN = 5; // 5 FOOD_CLUSTERS_MAX = 20; // 20 FOOD_PER_CLUSTER_MIN = 2; // 2 FOOD_PER_CLUSTER_MAX = 10; // 10 TADPOLES_MIN = 5; // 5 TADPOLES_MAX = 10; // 20 TADPOLE_STARTING_SIZE = 0.05; // 0.05 TADPOLE_STARTING_TAILSIZE = 0.12; // 0.12 TADPOLE_ENERGY_MAX = 1.0; // 1.0 RANDOM_RIPPLE_THRESHOLD = 20; // 20 MAP_WIDTH = 5.0; // 5.0 MAP_HEIGHT = 5.0; // 5.0 TARGET_FRAME_TIME = 1; // 20 FOOD_ENERGY_RATIO = 1; // 1 FOOD_SIZE_RATIO = 0.08; // 0.08 FOOD_TAILSIZE_RATIO = 0.06; // 0.06 RADIO_WAVE_DISTANCE = 0.4; // 0.4 RADIO_WAVE_ENERGY = 0.02; // 0.02 FISH_MIN = 2; // 2 FISH_MAX = 4; // 4 FISH_SIZE = 0.15; // 0.15 FISH_TAILSIZE = 0.22; // 0.22 FISH_FINSIZE = 0.20; // 0.20 GAME_STATE_SPLASH = 0; GAME_STATE_PLAYING = 1; GAME_STATE_GAMEOVER = 2; GAME_STATE_FINISHED = 3; /* * View - used by most objects during thier paint() method * for thier on-screen offset. This class does not offer any * functionality (yet). */ class View x; y; function init(x,y) this.x = x; this.y = y; end; end; view : View = View(0,0); /* * Map - contains all the objects within the game. objects is the * superset of backgroundObjects, gameObjects and guiObjects. The * objects are organized in such a way for easy and quick layering. */ class Map width; height; objects; backgroundObjects; gameObjects; guiObjects; function init(width, height) this.width = width; this.height = height; objects = []; backgroundObjects = []; gameObjects = []; guiObjects = []; end; end; map : Map = Map(0,0); /* * Object - the core class of the game. Every entity must extend * this class. The two methods process() and paint() must be * implemented by these subclasses. These methods are called on * all living objects on every frame. */ class Object x; y; speed; type; // The object type, should correspond to the class layer; // Drawing layer, either "background", "game" or "gui" age; // The age, incremented on every process() dead; // false when the object's die() method has been called. // Use this instead of object = null comparison. size; // The size, used for collision detection... /* * Initializes the object. Subclasses should call this prior * to thier own initialization. * @param type The type of object, i.e. class name. Used for * reflection... * @param layer The layer on which the object is draw on. Either * "background", "game" or "gui". */ function init(type, layer) this.type = type; this.layer = layer; speed = 0.0; x = 0.0; y = 0.0; size = 0.0; age = 0; dead = false; // Insert self into the correct object arrays... array.insert(.map.objects, .len(.map.objects), this); if layer = null or layer = "game" then array.insert(.map.gameObjects, .len(.map.gameObjects), this); elsif layer = "background" then array.insert(.map.backgroundObjects, .len(.map.backgroundObjects), this); elsif layer = "gui" then array.insert(.map.guiObjects, .len(.map.guiObjects), this); end; end; /* * Processes the current object. Subclasses should call this * method prior to doing thier own processing. process() of * each object is called exactly once during every frame. */ function process() age++; end; /* * Draws the current object. paint() of * each object is called exactly once during every frame. */ function paint() end; /* * Kills the object in the game. The object is remove from * the map according to which layer it belongs on. */ function die() // Remove self from the correct object arrays... array.remove(.map.objects, array.index(.map.objects, this)); if layer = null or layer = "game" then array.remove(.map.gameObjects, array.index(.map.gameObjects, this)); elsif layer = "background" then array.remove(.map.backgroundObjects, array.index(.map.backgroundObjects, this)); elsif layer = "gui" then array.remove(.map.guiObjects, array.index(.map.guiObjects, this)); end; dead = true; end; /* * Moves the object in the given direction (angle) for the * given distance * @param angle The absolute direction * @param distance The distance to move */ function move(angle, distance) offsetX = math.cos(angle*math.pi/180) * distance; offsetY = math.sin(angle*math.pi/180) * distance; x += offsetX; y += offsetY; end; /* * Checks whether an object is on screen or not. This method * compares only by using a box calculation based on size. * @return true if the object is on screen, false otherwise. */ function isOnScreen() if layer = "gui" or (x + size > -.view.x and x - size < -.view.x + 1.0 and y + size > -.view.y and y - size < -.view.y + 1.0) then return true; else return false; end; end; end; /* * Debug Object - used for displaying a series of debug information * on screen. */ class DebugObject is Object text; // Complete text buffer to display... function init() super.init("debug", "gui"); text = ""; end; /* * Adds the given text to the series of debug information to * display. The text is removed after every frame. * @param text The text to add. */ function print(text) this.text = this.text + text + " "; end; function paint() graph.font(["Arial", 16, false, false]); graph.text(0, 0, text); text = ""; end; end; debug : DebugObject = DebugObject(); /* * Test Object - object used for testing. Bounces back-and-forth * on screen. This object does not adhere to the view. */ class TestObject is Object speedX; speedY; /* * @param x Starting x coordinate * @param y Starting y coordinate * @param speed Movement speed */ function init(x,y,speed) super.init("test", "gui"); this.x = x; this.y = y; this.speed = speed; speedX = speed; speedY = speed; end; function process() super.process(); if x > 1.0 then speedX = -speed; end; if x < 0 then speedX = speed; end; if y > 1.0 then speedY = -speed; end; if y < 0 then speedY = speed; end; x += speedX; y += speedY; end; function paint() graph.text(x, y, "Test Object"); end; end; /* * Cursor Object - displays a cross-hair cursor on screen. Used * for influencing the .tadpole movement as well as controlling * the viewport position. */ class Cursor is Object SPEED_STEP; BORDER_SIZE; MOVE_SPEED; speedX; speedY; function init(x, y) super.init("cursor", "gui"); SPEED_STEP = 0.002; BORDER_SIZE = 0.05; MOVE_SPEED = 0.03; this.x = x; this.y = y; size = 0.06; speedX = 0.0; speedY = 0.0; end; function process() super.process(); // Process speed if speedX > 0.0 then speedX = speedX - SPEED_STEP; if speedX < 0.0 then speedX = 0.0; end; elsif speedX < 0.0 then speedX = speedX + SPEED_STEP; if speedX > 0.0 then speedX = 0.0; end; end; if speedY > 0.0 then speedY = speedY - SPEED_STEP; if speedY < 0.0 then speedY = 0.0; end; elsif speedY < 0.0 then speedY = speedY + SPEED_STEP; if speedY > 0.0 then speedY = 0.0; end; end; // Do movement if x + speedX > 0.0 + BORDER_SIZE and x + speedX < 1.0 - BORDER_SIZE then x += speedX; end; if y + speedY > 0.0 + BORDER_SIZE and y + speedY < 1.0 - BORDER_SIZE then y += speedY; end; // Move view with cursor... if x < BORDER_SIZE*2 then .view.x -= speedX; elsif x > 1.0-(BORDER_SIZE*2) then .view.x -= speedX; end; if y < BORDER_SIZE*2 then .view.y -= speedY; elsif y > 1.0-(BORDER_SIZE*2) then .view.y -= speedY; end; end; function paint() graph.alpha(0.5); graph.line(x, y-(size/2), x, y+(size/2)); graph.line(x-(size/2), y, x+(size/2), y); end; function moveLeft() speedX = -MOVE_SPEED; end; function moveRight() speedX = MOVE_SPEED; end; function moveUp() speedY = MOVE_SPEED; end; function moveDown() speedY = -MOVE_SPEED; end; /* * Get's the cursors position on the map. * @return The x coordinate of the cursor. */ function mapX() return x - .view.x; end; /* * Get's the cursors position on the map. * @return The y coordinate of the cursor. */ function mapY() return y - .view.y; end; end; cursor : Cursor = Cursor(0, 0); /* * Ripple Object - mimicks a single ripple moving through the * water. Eventually fades away when reaching maxSize. */ class Ripple is Object maxSize; /* * @param x The x coordinate of the epicenter. * @param y The y coordinate of the epicenter. * @param maxSize The maximum size the ripple will reach * before dissappearing... */ function init(x, y, maxSize) super.init("ripple", "background"); this.x = x; this.y = y; this.maxSize = maxSize; size = 0.0; end; function process() super.process(); if size > maxSize then die(); else size += 0.002; end; end; function paint() graph.pen(graph.white); if size/maxSize > 1.0 then graph.alpha(1.0); else graph.alpha(size/maxSize); end; graph.ellipse(.view.x + x-(size/2), .view.y + y-(size/2), size, size); end; end; /* * Beam Object - A horizontal and vertical beam flashes and * dissappears slowly at the given x and y coordinates... */ class Beam is Object ALPHA_STEP; alpha; function init(x, y) super.init("beam", "gui"); this.x = x; this.y = y; alpha = 0.0; ALPHA_STEP = 0.1; end; function process() super.process(); if alpha + ALPHA_STEP > 1.0 then die(); else alpha += ALPHA_STEP; end; end; function paint() graph.pen(graph.white); graph.alpha(alpha); graph.line(.view.x + x, 0, .view.x + x, 1); // Vertical line graph.line(0, .view.y + y, 1, .view.y + y); // Horizontal line end; end; /* * Energy Object - visual representation of energy consumption. * Displayed when a tadpole eats some food and gains energy. */ class Energy is Object ALPHA_STEP; SIZE_STEP; alpha; color; /* * @param x The x coordinate of the epicenter. * @param y The y coordinate of the epicenter. * @param size Starting size of the energy wave. * @param color Wave color. */ function init(x, y, size, color) super.init("energy", "gui"); ALPHA_STEP = 0.1; SIZE_STEP = 0.01; this.x = x; this.y = y; alpha = 0.0; this.size = size; this.color = color; end; function process() super.process(); size += SIZE_STEP; if alpha + ALPHA_STEP > 1.0 then die(); else alpha += ALPHA_STEP; end; end; function paint() graph.pen(color); graph.alpha(alpha); graph.ellipse(.view.x + x-(size/2), .view.y + y-(size/2), size, size); end; end; /* * Water Plant Object - a simple floating water plant. This object * drifts slowly in the water and is designed to be in a cluster. */ class WaterPlant is Object MAX_SPEED; MAX_DRIFT; speedX; speedY; origX; origY; color; /* * Sets a new drifting speed in a random direction. */ function setNewDrift() speedX = (-math.random() + math.random()) * MAX_SPEED; speedY = (-math.random() + math.random()) * MAX_SPEED; end; function init(x, y) super.init("waterplant", "background"); MAX_SPEED = 0.001; MAX_DRIFT = 0.10; this.x = x; this.y = y; origX = x; origY = y; color = 0x5fb3ae; size = 0.4 + math.random()*0.4; setNewDrift(); end; function process() super.process(); if x + speedX < origX - MAX_DRIFT or x + speedX > origX + MAX_DRIFT or y + speedY < origY - MAX_DRIFT or y + speedY > origY + MAX_DRIFT then setNewDrift(); else x += speedX; y += speedY; end; end; function paint() graph.pen(false); graph.brush(color); graph.ellipse(.view.x + x-(size/2), .view.y + y-(size/2), size, size); end; end; /* * Food Object - a randomly drifting piece of food which can * be eaten by tadpoles. When a food is eaten, the tadpole * gains in energy and size... */ class Food is Object MAX_SPEED; MAX_DRIFT; speedX; speedY; origX; origY; color; /* * Sets a new drifting speed in a random direction. */ function setNewDrift() speedX = (-math.random() + math.random()) * MAX_SPEED; speedY = (-math.random() + math.random()) * MAX_SPEED; end; function init(x, y) super.init("food", "background"); MAX_SPEED = 0.008; MAX_DRIFT = 1.0; this.x = x; this.y = y; origX = x; origY = y; color = graph.yellow; size = 0.01 + math.random()*0.02; setNewDrift(); end; function process() super.process(); // Move randomly... if x + speedX < origX - MAX_DRIFT or x + speedX > origX + MAX_DRIFT or y + speedY < origY - MAX_DRIFT or y + speedY > origY + MAX_DRIFT then setNewDrift(); else x += speedX; y += speedY; end; end; function paint() graph.pen(false); graph.brush(color); graph.ellipse(.view.x + x-(size/2), .view.y + y-(size/2), size, size); end; end; /* * Letter Objects - represents a single character floating * randomly onscreen. Use the function addLetterText(x,y,text) * to display a series of these letters onscreen. */ class Letter is Object MAX_SPEED; ALPHA_STEP; SIZE_STEP; speedX; speedY; color; alpha; letter; /* * Sets a new drifting speed in a random direction. */ function setNewDrift() speedX = (-math.random() + math.random()) * MAX_SPEED; speedY = (-math.random() + math.random()) * MAX_SPEED; end; /* * @param x The x coordinate of the letter. * @param y The y coordinate of the letter. * @param size Size of the letter font in pixels. * @param letter Character to display. */ function init(x, y, size, letter) super.init("letter", "gui"); MAX_SPEED = 0.002; ALPHA_STEP = 0.03; SIZE_STEP = 2; this.x = x; this.y = y; this.letter = letter; this.size = size; color = graph.white; alpha = 0.0; speedX = (-math.random() + math.random()) * MAX_SPEED; speedY = (-math.random() + math.random()) * MAX_SPEED; end; function process() super.process(); size += SIZE_STEP; if alpha + ALPHA_STEP > 1.0 then die(); else alpha += ALPHA_STEP; end; x += speedX; y += speedY; end; function paint() graph.pen(color); graph.brush(color); graph.alpha(alpha); graph.font(["Arial", size, true, false]); graph.text(x, y, letter); end; end; /* * Water Animal - super class for any moving water animal. * This class provides the functionality for moving through the * water, wiggling tails and/or fins, navigating to a specific * point, and accelerating/decelerating... */ class WaterAnimal is Object SPEED_STEP; WIGGLE_STEP_SLOW; WIGGLE_STEP_FAST; WIGGLE_MAX; ROTATION_STEP; RIPPLE_THRESHOLD; wiggleOffset; // The current wiggle offset of a tail/fin rotation; // Absolute rotation currentSpeed; // The speed at which the object moves wantedSpeed; // The requested speed destX; // If set, the animal moves to these coordinates destY; energy; // Current energy of the animal. Increased when the animal eats food. wiggleStep; // The current wiggle step. This is faster as the animal moves, slower as it is idle. color; maxSpeed; /* * Sets the wiggle step to the given value without changing * the wiggle direction * @param step Wiggle step amout. */ function setWiggleStep(step) if wiggleStep > 0 then wiggleStep = step; else wiggleStep = -step; end; end; /* * @param type The object type. * @param x The starting x coordinate. * @param y The starting y coordinate. * @param size Main body size. * @param color Main body color. * @param maxSpeed The object maximum speed when moving. */ function init(type, x, y, size, color, maxSpeed) super.init(type, "game"); SPEED_STEP = 0.001; ROTATION_STEP = 5; WIGGLE_STEP_SLOW = 1; WIGGLE_STEP_FAST = 4; WIGGLE_MAX = 10; RIPPLE_THRESHOLD = 6; this.x = x; this.y = y; this.size = size; this.color = color; this.maxSpeed = maxSpeed; wiggleOffset = 0; rotation = 0; currentSpeed = 0.0; wantedSpeed = 0.0; destX = 0.0; destY = 0.0; energy = 0.0; wiggleStep = WIGGLE_STEP_SLOW; end; function process() super.process(); // Steer to wanted speed... if currentSpeed < wantedSpeed then currentSpeed += SPEED_STEP; elsif currentSpeed > wantedSpeed then currentSpeed -= SPEED_STEP; end; // Are we at our destination? if x >= destX - maxSpeed and x <= destX + maxSpeed and y >= destY - maxSpeed and y <= destY + maxSpeed then wantedSpeed = 0; destX = x; destY = y; setWiggleStep(WIGGLE_STEP_SLOW); else rotation = .getAngle(x,y,destX,destY); end; // Are we moving? if currentSpeed # 0 then // Cause ripple and move... if age % RIPPLE_THRESHOLD = 0 then ripple:Ripple = Ripple(x, y, 0.1); end; move(rotation, currentSpeed); end; // Wiggle tail wiggleOffset += wiggleStep; if (wiggleStep > 0 and wiggleOffset > WIGGLE_MAX) or (wiggleStep < 0 and wiggleOffset < -WIGGLE_MAX) then wiggleStep = -wiggleStep; end; end; /* * Moves the object to the given point. This will not happen * immediately, but rather causes an animated effect for the * object over the forthcoming frames where the object slowly * progresses to the direction of the given coordinates. * @param x Destination x coordinate. * @param y Destination y coordinate. */ function moveToPoint(x, y) setWiggleStep(WIGGLE_STEP_FAST); destX = x; destY = y; wantedSpeed = maxSpeed; end; end; /* * Tadpole Water Animal - represents a small tadpole. Tadpole is a WaterAnimal, * thus it automatically knows how to move through the water and * wiggle it's tail. */ class Tadpole is WaterAnimal TAIL_ANGLE; tailSize; targetFood : Food; /* * @param x The starting x coordinate. * @param y The starting y coordinate. * @param size Body size. * @param tailSize Tail size. * @param color Body and tail color. */ function init(x, y, size, tailSize, color) super.init("tadpole", x, y, size, color, 0.02); TAIL_ANGLE = 20; this.tailSize = tailSize; targetFood = null; end; function process() super.process(); // Can we eat something? foodObjects = getObjectsOfType("food"); for food : Food in foodObjects do if food.x >= x - size and food.x <= x + size and food.y >= y - size and food.y <= y + size then energy += food.size * .FOOD_ENERGY_RATIO; size += food.size * .FOOD_SIZE_RATIO; tailSize += food.size * .FOOD_TAILSIZE_RATIO; energyObject : Energy = Energy(food.x, food.y, food.size, food.color); food.die(); end; end; end; /* * Sets a new food target, making the tadpole slowly move * towards the food object... */ function moveToFood() targetFood = getRndObjectOfType("food"); end; function paint() graph.pen(false); graph.brush(color); graph.circle(.view.x + x-(size/2), .view.y + y-(size/2), size); graph.ellipse(.view.x + x-(tailSize/2), .view.y + y-(tailSize/2), tailSize, tailSize, rotation+180+wiggleOffset-(TAIL_ANGLE/2), rotation+180+wiggleOffset+(TAIL_ANGLE/2)); end; end; tadpole : Tadpole = Tadpole(0,0,0,0,0); /* * AI Tadpole Water Animal - a tadpole which can move by itself. This object * either moves to a random location, to our own tadpole, or to a * targetted random piece of food. */ class AITadpole is Tadpole MOVE_THRESHOLD; MAX_MOVE_DISTANCE; /* * @param x The starting x coordinate. * @param y The starting y coordinate. * @param size Body size. * @param tailSize Tail size. * @param color Body and tail color. */ function init(x, y, size, tailSize, color) super.init(x, y, size, tailSize, color); MOVE_THRESHOLD = 20; MAX_MOVE_DISTANCE = 1.0;x end; function process() super.process(); // Move to a random location if currentSpeed = 0 and rndInt(0, MOVE_THRESHOLD) = 0 then rnd = rndInt(1,8); if rnd = 1 then // Move to random location offsetX = -MAX_MOVE_DISTANCE + (MAX_MOVE_DISTANCE*2*math.random()); offsetY = -MAX_MOVE_DISTANCE + (MAX_MOVE_DISTANCE*2*math.random()); moveToPoint(x + offsetX, y + offsetY); elsif rnd = 2 then // Move to our tadpole moveToPoint(.tadpole.x, .tadpole.y); else // Move to a food piece if we can find one if targetFood = null or targetFood.dead = true then targetFood = getRndObjectOfType("food"); end; if targetFood # null then moveToPoint(targetFood.x, targetFood.y); end; end; end; end; end; /* * Fish Water Animal - a large fish water animal which can move through * the water by itself. This object moves to either a random location * or towards our own tadpole. */ class Fish is WaterAnimal TAIL_ANGLE; FIN_ANGLE; MOVE_THRESHOLD; MAX_MOVE_DISTANCE; tailSize; finSize; /* * @param x The starting x coordinate. * @param y The starting y coordinate. * @param size Body size. * @param tailSize Tail size. * @param finSize Fin size. * @param color Body, tail and fin color. */ function init(x, y, size, tailSize, finSize, color) super.init("fish", x, y, size, color, 0.005); TAIL_ANGLE = 40; FIN_ANGLE = 18; MOVE_THRESHOLD = 20; MAX_MOVE_DISTANCE = 1.0; this.tailSize = tailSize; this.finSize = finSize; end; function process() super.process(); // Move to a random location if currentSpeed = 0 and rndInt(0, MOVE_THRESHOLD) = 0 then rnd = rndInt(1,4); if rnd = 1 then // Move to random location offsetX = -MAX_MOVE_DISTANCE + (MAX_MOVE_DISTANCE*2*math.random()); offsetY = -MAX_MOVE_DISTANCE + (MAX_MOVE_DISTANCE*2*math.random()); moveToPoint(x + offsetX, y + offsetY); else // Move to our tadpole moveToPoint(.tadpole.x, .tadpole.y); end; end; // Can we eat something? eatableObjects = getObjectsOfType("tadpole"); for tadpole : Tadpole in eatableObjects do if tadpole.x >= x - size/2 and tadpole.x <= x + size/2 and tadpole.y >= y - size/2 and tadpole.y <= y + size/2 then energyObject : Energy = Energy(tadpole.x, tadpole.y, tadpole.size, tadpole.color); tadpole.die(); if tadpole = .tadpole then addLetterText(0.28, 0.6, 30, "game"); addLetterText(0.28, 0.45, 30, "over"); end; end; end; end; function paint() graph.pen(false); graph.brush(color); graph.circle(.view.x + x-(size/2), .view.y + y-(size/2), size); graph.ellipse(.view.x + x-(tailSize/2), .view.y + y-(tailSize/2), tailSize, tailSize, rotation+180+wiggleOffset-(TAIL_ANGLE/2), rotation+180+wiggleOffset+(FIN_ANGLE/2)); graph.ellipse(.view.x + x-(finSize/2), .view.y + y-(finSize/2), finSize, finSize, rotation+240-(FIN_ANGLE/2), rotation+240+(FIN_ANGLE/2)); graph.ellipse(.view.x + x-(finSize/2), .view.y + y-(finSize/2), finSize, finSize, rotation+120-(FIN_ANGLE/2), rotation+120+(FIN_ANGLE/2)); end; end; /* * Energy Bar Object - A thin horizontal bar at the screen bottom * which displays our tadpoles current energy. The bar dissappears * after a short moment in order to enable a fuller gaming experience. */ class EnergyBar is Object ALPHA_STEP; BORDER_SPACE; HEIGHT; alpha; lastEnergy; tadpole : Tadpole; percent; width; /* * @param tadpole A Tadpole Object which the energy bar * is locked too. Whenever the energy of * this tadpole changes, the energy bar will * display itself briefly and then fade out. */ function init(tadpole : Tadpole) super.init("energybar", "gui"); ALPHA_STEP = 0.05; BORDER_SPACE = 0.05; HEIGHT = 0.05; this.tadpole = tadpole; alpha = 1.0; lastEnergy = 0; width = 1.0 - 2 * BORDER_SPACE; percent = 0.0; end; function process() super.process(); if lastEnergy # tadpole.energy then lastEnergy = tadpole.energy; alpha = 0.0; percent = (tadpole.energy / .TADPOLE_ENERGY_MAX); if percent > 1.0 then percent = 1.0; end; end; if alpha + ALPHA_STEP < 1.0 then alpha += ALPHA_STEP; end; end; function paint() if alpha + ALPHA_STEP < 1.0 then graph.pen(graph.white); graph.alpha(alpha); graph.brush(graph.white); graph.rect(BORDER_SPACE, BORDER_SPACE, width * percent, HEIGHT); // Fill graph.brush(false); graph.rect(BORDER_SPACE, BORDER_SPACE, width, HEIGHT); // Outline end; end; end; /* * Radio Wave Object - a set of small radio waves which are * dispersed at the given angle from the origin. */ class RadioWave is Object SIZE_STEP; ARC_ANGLE; WAVE_OFFSET; WAVE_NUMBER; maxSize; angle; sender : Tadpole collided; /* * @param x The x coordinate of the epicenter. * @param y The y coordinate of the epicenter. * @param maxSize Maximum size of the radio wave, i.e. the distance of the wave. * @param angle Angle, or direction of the waves. * @param sender The sending tadpole object. Used to make sure we don't interact with our own radio waves... */ function init(x, y, maxSize, angle, sender) super.init("radiowave", "background"); SIZE_STEP = 0.05; ARC_ANGLE = 30; WAVE_OFFSET = 0.1; WAVE_NUMBER = 3; this.x = x; this.y = y; this.maxSize = maxSize; this.angle = angle; this.sender = sender; size = 0.0; collided = false; end; function process() super.process(); if size > maxSize then die(); else size += SIZE_STEP; end; // Are we reaching a tadpole? if collided = false then for tadpole : Tadpole in getObjectsOfType("tadpole") do if tadpole # sender and tadpole.x >= x - size/2 and tadpole.x <= x + size/2 and tadpole.y >= y - size/2 and tadpole.y <= y + size/2 then // Mark as collided and tell the tadpole to go swim to some food... collided = true; tadpole.targetFood = null; tadpole.moveToPoint(.map.width*math.random(), .map.height*math.random()); end; end; end; end; function paint() graph.brush(false); graph.pen(graph.green); // Just draw a series of arcs... if size/maxSize > 1.0 then graph.alpha(1.0); else graph.alpha(size/maxSize); end; for i = 1 to WAVE_NUMBER do waveSize = size + (WAVE_OFFSET * i); if waveSize > 0 then graph.ellipse(.view.x + x-(waveSize/2), .view.y + y-(waveSize/2), waveSize, waveSize, angle - ARC_ANGLE/2, angle + ARC_ANGLE/2); end; end; end; end; /* * rndInt Global Function * @param min The minimum returnable value. * @param max The maximum returnable value. * If null or omitted then min is used as the * maximum while the minimum is assumed to be 0. * @return Random whole number between min and max. */ function rndInt(min, max = null) if max = null then return (math.random()*10000) % min; else return min + ((math.random()*10000) % (max-min)); end; end; /* * getObjectsOfType Global Function * @param type The requested type of object (string). * @return Returns an array containing all the matching objects. */ function getObjectsOfType(type) if type = null then return .map.objects; else objectsOfType = []; for obj : Object in .map.objects do if obj.type = type then array.insert(objectsOfType, .len(objectsOfType), obj); end; end; return objectsOfType; end; end; /* * getRndObjectOfType Global Function * @param type The requested type of object (string). * @return Returns a random matching object. */ function getRndObjectOfType(type) objectsOfType = getObjectsOfType(type); if .len(objectsOfType) = 0 then return null; end; rndIndex = rndInt(0, .len(objectsOfType)); return objectsOfType[0]; end; /* * getAngle Global Function * @param x The source x coordinate. * @param y The source y coordinate. * @param destX The destination x coordinate. * @param destY The destination y coordinate. * @return Returns the relative angle in degrees between the source and destination. */ function getAngle(x, y, destX, destY) if x = destX and y = destY then return 0; end; delta = 0.0; if destX >= x and destY >= y then delta = math.atan( (destY-y) / (destX-x) ) * (180 / math.pi); elsif destX >= x and destY < y then delta = 360 + math.atan( (destY-y) / (destX-x) ) * (180 / math.pi); elsif destX < x and destY < y then delta = 180 + math.atan( (destY-y) / (destX-x) ) * (180 / math.pi); elsif destX < x and destY >= y then delta = 180 + math.atan( (destY-y) / (destX-x) ) * (180 / math.pi); end; return delta; end; /* * resetGraph Global Function - Resets the graph so that it is * ready for drawing a object. */ function resetGraph() graph.alpha(0); graph.pen(graph.black); graph.brush(false); end; /* * addLetterText Global Function - Adds a string text onscreen * using Letter objects. * @param x The x starting coordinate. * @param y The y starting coordinate. * @param size Starting font size in pixels of letters. * @param text String of text to show. */ function addLetterText(x, y, size, text) for i = 0 to len(text)-1 do letter : Letter = Letter(x + (0.1)*i, y, 30, substr(text, i, 1)); end; end; // Init game objects ui.busy("Loading..."); gameState = GAME_STATE_SPLASH; map = Map(MAP_WIDTH, MAP_HEIGHT); view = View(-map.width/2 + 0.5, -map.width/2 + 0.5); debug = DebugObject(); cursor = Cursor(0.5, 0.5); tadpole = Tadpole(map.width/2, map.height/2, TADPOLE_STARTING_SIZE, TADPOLE_STARTING_TAILSIZE, graph.black); energyBar : EnergyBar = EnergyBar(tadpole); addLetterText(0.12, 0.6, 30, "little"); addLetterText(0.2, 0.3, 30, "lives"); // Create some random water plants for waterPlantClusterIndex = 0 to rndInt(WATERPLANT_CLUSTERS_MIN, WATERPLANT_CLUSTERS_MAX) do waterPlantClusterX = math.random() * map.width; waterPlantClusterY = math.random() * map.height; for plantIndex = 0 to rndInt(WATERPLANT_PER_CLUSTER_MIN, WATERPLANT_PER_CLUSTER_MAX) do plantX = math.random() * 0.5; plantY = math.random() * 0.5; clusterWaterPlant:WaterPlant = WaterPlant(waterPlantClusterX + plantX, waterPlantClusterY + plantY); end; end; // Create some random food... for foodClusterIndex = 0 to rndInt(FOOD_CLUSTERS_MIN, FOOD_CLUSTERS_MAX) do foodClusterX = math.random() * map.width; foodClusterY = math.random() * map.height; for plantIndex = 0 to rndInt(FOOD_PER_CLUSTER_MIN, FOOD_PER_CLUSTER_MAX) do foodX = math.random() * 0.1; foodY = math.random() * 0.1; clusterFood:Food = Food(foodClusterX + foodX, foodClusterY + foodY); end; end; // Create some other tadpoles... for tadpoleIndex = 0 to rndInt(TADPOLES_MIN, TADPOLES_MAX) do aiTadpole = AITadpole(math.random() * map.width, math.random() * map.height, TADPOLE_STARTING_SIZE, TADPOLE_STARTING_TAILSIZE, graph.black); end; // Create some big pad fish... for tadpoleIndex = 0 to rndInt(FISH_MIN, FISH_MAX) do aiFish = Fish(math.random() * map.width, math.random() * map.height, FISH_SIZE, FISH_TAILSIZE, FISH_FINSIZE, 0x660000); end; // Test objects //aiTadpole : AITadpole = AITadpole(0.7, 0.7, 0.05, 0.12, graph.black); //clusterFood : Food = Food(0.1, 0.1); //clusterFoodx : Food = Food(0.5, 0.1); //letter : Letter = Letter(0.5,0.5,30,"L"); //addLetterText(0.1, 0.5, 30, "text"); //fish = Fish(map.width/2- 0.5, map.height/2-0.5, FISH_SIZE, FISH_TAILSIZE, FISH_FINSIZE, 0x660000); // Init drawing canvas graph.scale(true); graph.bg(0x5f97b3); ui.keys(false); stop = false; accelEnabled = false; accelOffsetX = 0; accelOffsetY = 0; accelTrigger = 30; ui.busy(); ui.msg("Use the arrow keys to move the cursor which your tadpole always follows.", "Little Lives - 1/4"); ui.msg("Eat the floating food before your friends do in order to grow larger and gain energy.","Little Lives - 2/4"); ui.msg("Press the GO key to speak with other tadpoles in order to mislead them from food.","Little Lives - 3/4"); ui.msg("Press the * key at any time to quit. Happy Swimming!", "Little Lives - 4/4"); if accel.version # null then accelEnabled = ui.confirm("Your phone contains an accelerometer. Would you like to use this feature?", "Little Lives"); a = accel.get(); accelOffsetX = a["x"]; accelOffsetY = a["y"]; end; // Application loop gameState = GAME_STATE_PLAYING; while(stop = false) do // Debug counters... frameTimeBegin = time.get(); processedObjects = 0; paintedObjects = 0; ui.idletime(true); // Process input... cmd = ui.cmd(0); if cmd # null then if cmd = 42 then // * key // Exit game loop stop = true; elsif cmd = 35 then // # key // Test key... //rndFood : Food = getRndObjectOfType("food"); //tadpole.moveToPoint(rndFood.x, rndFood.y); elsif cmd = ui.downkey and tadpole.dead = false then cursor.moveDown(); tadpole.moveToPoint(cursor.mapX(), cursor.mapY()); elsif cmd = ui.upkey and tadpole.dead = false then cursor.moveUp(); tadpole.moveToPoint(cursor.mapX(), cursor.mapY()); elsif cmd = ui.leftkey and tadpole.dead = false then cursor.moveLeft(); tadpole.moveToPoint(cursor.mapX(), cursor.mapY()); elsif cmd = ui.rightkey and tadpole.dead = false then cursor.moveRight(); tadpole.moveToPoint(cursor.mapX(), cursor.mapY()); elsif cmd = ui.gokey and tadpole.dead = false then // Send a readio beam in direction of cursor beam : Beam = Beam(cursor.mapX(), cursor.mapY()); waveAngle = getAngle(tadpole.x, tadpole.y, cursor.mapX(), cursor.mapY()); radioWave : RadioWave = RadioWave(tadpole.x, tadpole.y, RADIO_WAVE_DISTANCE, waveAngle, tadpole); end; elsif accelEnabled = true then a = accel.get(); if a["x"]-accelOffsetX < -accelTrigger then cursor.moveDown(); tadpole.moveToPoint(cursor.mapX(), cursor.mapY()); elsif a["x"]-accelOffsetX > accelTrigger then cursor.moveUp(); tadpole.moveToPoint(cursor.mapX(), cursor.mapY()); end; if a["y"]-accelOffsetY > accelTrigger then cursor.moveLeft(); tadpole.moveToPoint(cursor.mapX(), cursor.mapY()); elsif a["y"]-accelOffsetY < -accelTrigger then cursor.moveRight(); tadpole.moveToPoint(cursor.mapX(), cursor.mapY()); end; end; // Other processing if rndInt(RANDOM_RIPPLE_THRESHOLD) = 0 then randomRipple : Ripple = Ripple(-.view.x + math.random(), -.view.y + math.random(), math.random()*0.3); end; if gameState = GAME_STATE_PLAYING and energyBar.percent >= 1.0 then gameState = GAME_STATE_FINISHED; ui.msg("Congratulations! You are now a full grown tadpole!\n\nHappy Swimming!", "Little Lives"); end; // Process objects for obj : Object in map.objects do obj.process(); processedObjects++; end; // Paint all objects, one layer at a time graph.clear(); for backgroundObject : Object in map.backgroundObjects do if backgroundObject.isOnScreen() = true then resetGraph(); backgroundObject.paint(); paintedObjects++; end; end; for gameObject : Object in map.gameObjects do if gameObject.isOnScreen() = true then resetGraph(); gameObject.paint(); paintedObjects++; end; end; for guiObject : Object in map.guiObjects do if guiObject.isOnScreen() = true then resetGraph(); guiObject.paint(); paintedObjects++; end; end; // Show debug infos... //debug.print(processedObjects); //debug.print(paintedObjects); // Show paint changes graph.show(); // Take a break if we were fast enough frameTimeEnd = time.get(); if frameTimeEnd-frameTimeBegin < TARGET_FRAME_TIME * 1000 then sleep( TARGET_FRAME_TIME - (frameTimeEnd-frameTimeBegin)/1000 ); end; end;