From d2b898228c621767bf12ae9da9608e8b2373299f Mon Sep 17 00:00:00 2001 From: Claudio Matsuoka Date: Thu, 20 Jan 2011 16:57:56 -0200 Subject: [PATCH] Initial commit Signed-off-by: Claudio Matsuoka --- .classpath | 6 + .project | 17 + .../helllabs/java/asciiquarium/Animation.java | 179 +++ .../java/asciiquarium/Asciiquarium.java | 1085 +++++++++++++++++ .../helllabs/java/asciiquarium/Entity.java | 196 +++ .../helllabs/java/asciiquarium/MyApplet.java | 90 ++ .../helllabs/java/asciiquarium/Screen.java | 27 + .../java/asciiquarium/TerminalBlitter.java | 66 + 8 files changed, 1666 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 src/org/helllabs/java/asciiquarium/Animation.java create mode 100644 src/org/helllabs/java/asciiquarium/Asciiquarium.java create mode 100644 src/org/helllabs/java/asciiquarium/Entity.java create mode 100644 src/org/helllabs/java/asciiquarium/MyApplet.java create mode 100644 src/org/helllabs/java/asciiquarium/Screen.java create mode 100644 src/org/helllabs/java/asciiquarium/TerminalBlitter.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..fb50116 --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..55c3ef2 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + asciiquarium-applet + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/src/org/helllabs/java/asciiquarium/Animation.java b/src/org/helllabs/java/asciiquarium/Animation.java new file mode 100644 index 0000000..33ca580 --- /dev/null +++ b/src/org/helllabs/java/asciiquarium/Animation.java @@ -0,0 +1,179 @@ +package org.helllabs.java.asciiquarium; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +public class Animation { + private List list; + private List addQueue; + private List deleteQueue; + Screen screen; + int physicalCount; + + public Animation(int columns, int rows) { + + //Log.d("Asciiquarium", "Animation constructor"); + + screen = new Screen(columns, rows); + list = new ArrayList(); + addQueue = new ArrayList(); + deleteQueue = new ArrayList(); + physicalCount = 0; + } + + // ########################## PHYSICS UTILITIES ########################## + + private void findCollisions() { + //Log.i("Asciiquarium", "Animation: find collisions"); + + for (Entity e : list) { + if (!e.physical) + continue; + + for (Entity f : list) { + if (!f.physical) + continue; + if (e.equals(f)) + continue; + if ((e.fx < f.fx && e.fx + e.width <= f.fx) || e.fx >= f.fx + f.width) + continue; + if ((e.fy < f.fy && e.fy + e.height <= f.fy) || e.fy >= f.fy + f.height) + continue; + + //Log.i("Asciiquarium", "Animation: " + e.name + " collided with " + f.name); + e.collisions.add(f); + } + } + } + + // ########## END PHYSICS UTILITIES ########### + + // Perform a single animation cycle. Runs all of the callbacks, + // does collision detection, and updates the display. + + void animate() { + // To prevent crashes with concurrent access + for (Entity e : addQueue) + list.add(e); + addQueue.clear(); + + doCallbacks(); + if (physicalCount > 0) { + findCollisions(); + collisionHandlers(); + } + removeDeletedEntities(); + buildScreen(); + //displayScreen(); + } + + private void buildScreen() { + screen.clear(); + + Collections.sort(list); + for (Entity e : list) { + e.draw(screen, e.frame); + } + } + + // Add one or more animation entities to the animation. + + void addEntity(Entity entity) { + entity.animation = this; + if (entity.physical) { + entity.collisions = new ArrayList(); + physicalCount++; + } + addQueue.add(entity); + } + + // Removes an entity from the animation. + + void delEntity(Entity entity) { + deleteQueue.add(entity); + } + + // Go through the list of entities that have been queued for + // deletion using delEntity and remove them + + private void removeDeletedEntities() { + // copy list so we can modify it + Entity[] delList = deleteQueue.toArray(new Entity[0]); + + for (Entity e : delList) { + if (e.deathCallback != null) { + //Log.i("Asciiquarium", "Animation: invoke death callback for " + e.name); + e.deathCallback.run(e); + } + + if (e.physical) + physicalCount--; + + list.remove(e); + } + + deleteQueue.clear(); + } + + // Returns a reference to a list of all entities in the animation + // that have the given type. + + Entity[] getEntitiesOfType(int type) { + List l = new ArrayList(); + for (Entity e : list) { + if (e.type == type) + l.add(e); + } + return l.toArray(new Entity[0]); + } + + // Run the callback routines for all entities that have them, and update + // the entity accordingly. Also checks for auto death status + + private void doCallbacks() { + //Log.i("Asciiquarium", "Animation: do callbacks"); + for (Entity e : list) { + + //Log.i("Asciiquarium", "Animation: do " + e.name + " callbacks"); + + // check for methods to automatically die + // dieTime + + if (e.dieFrame > 0) { + if (e.frameCount >= e.dieFrame) { + //Log.i("Asciiquarium", "Animation: " + e.name + " dies at frame " + e.frame); + delEntity(e); + } + } + + // dieFrame + // dieEntity + + if (e.dieOffscreen) { + if (e.fx >= screen.columns || e.fy >= screen.rows || + e.fx < -e.width || e.fy < -e.height) { + //Log.i("Asciiquarium", "Animation: " + e.name + " dies offscreen"); + delEntity(e); + continue; + } + } + + // callback + if (e.callback != null) { + e.callback.run(e); + } + } + } + + private void collisionHandlers() { + for (Entity e : list) { + if (e.collisions != null) { + if (e.collHandler != null) + e.collHandler.run(e); + e.collisions.clear(); + } + } + } +} diff --git a/src/org/helllabs/java/asciiquarium/Asciiquarium.java b/src/org/helllabs/java/asciiquarium/Asciiquarium.java new file mode 100644 index 0000000..6654c73 --- /dev/null +++ b/src/org/helllabs/java/asciiquarium/Asciiquarium.java @@ -0,0 +1,1085 @@ +package org.helllabs.java.asciiquarium; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.helllabs.java.asciiquarium.Entity.EntityCallback; + + +public class Asciiquarium { + // Under water + final static int DEPTH_BUBBLE = 1; + final static int DEPTH_SHARK = 2; + final static int DEPTH_FISH_START = 3; + final static int DEPTH_FISH_END = 20; + final static int DEPTH_SEAWEED = 21; + final static int DEPTH_CASTLE = 22; + + // Waterline + final static int DEPTH_WATER_LINE3 = 2; + final static int DEPTH_WATER_GAP3 = 3; + final static int DEPTH_WATER_LINE2 = 4; + final static int DEPTH_WATER_GAP2 = 5; + final static int DEPTH_WATER_LINE1 = 6; + final static int DEPTH_WATER_GAP1 = 7; + final static int DEPTH_WATER_LINE0 = 8; + final static int DEPTH_WATER_GAP0 = 9; + + final static int CELL_WIDTH = 8; + final static int CELL_HEIGHT = 16; + + final static int ENTITY_TYPE_WATERLINE = 1; + final static int ENTITY_TYPE_BUBBLE = 2; + final static int ENTITY_TYPE_FISH = 3; + final static int ENTITY_TYPE_TEETH = 4; + + Animation anim; + int columns; + int rows; + Random random; + + final static int WATER_LEVEL = 9; + + public abstract interface Renderer { + void putChar(int row, int column, char c, char color); + } + + Renderer renderer; + + public Asciiquarium(int columns, int rows) { + + //Log.d("Asciiquarium", "Asciiquarium constructor"); + + this.columns = columns; + this.rows = rows; + + random = new Random(); + + anim = new Animation(columns, rows); + //anim.halfDelay(1); + + addEnvironment(); + addCastle(); + addAllSeaweed(); + addAllFish(); + randomObject(); + } + + public void setRenderer(Renderer renderer) { + this.renderer = renderer; + } + + public Entity findEntityAt(int x, int y, int type) { + final Entity[] fish = anim.getEntitiesOfType(ENTITY_TYPE_FISH); + Entity target = null; + int depth = 999; + + for (Entity e : fish) { + if ((e.fx < x && e.fx + e.width <= x) || e.fx >= x) + continue; + if ((e.fy < y && e.fy + e.height <= y) || e.fy >= y) + continue; + + if (e.depth < depth) { + depth = e.depth; + target = e; + } + } + + return target; + } + + public void draw() { + anim.animate(); + + char[] buffer = anim.screen.text; + char[] cbuffer = anim.screen.color; + char c; + for (int row = 0; row < rows; row++) { + int r = row * columns; + for (int col = 0; col < columns; col++) { + c = buffer[r + col]; + if (c != ' ') + renderer.putChar(col, row, c, cbuffer[r + col]); + } + } + } + + + private void addEnvironment() { + final String[] waterLineSegment = new String[] { + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "^^^^ ^^^ ^^^ ^^^ ^^^^ ", + "^^^^ ^^^^ ^^^ ^^ ", + "^^ ^^^^ ^^^ ^^^^^^ " + }; + + // tile the segments so they stretch across the screen + final int segmentSize = waterLineSegment[0].length(); + final int segmentRepeat = columns / segmentSize + 1; + + for (int i = 0; i < waterLineSegment.length; i++) { + StringBuffer s = new StringBuffer(); + for (int j = 0; j < segmentRepeat; j++) { + s.append(waterLineSegment[i]); + } + waterLineSegment[i] = s.toString(); + + Entity entity = new Entity("water_seg_" + i, + s.toString(), 0, WATER_LEVEL + i, 8 - i * 2); + entity.type = ENTITY_TYPE_WATERLINE; + entity.defaultColor = 'c'; + entity.physical = true; + + anim.addEntity(entity); + } + } + + private void addCastle() { + final String[] castleImage = new String[] { + " T~~", + " |", + " /^\\", + " / \\", + " _ _ _ / \\ _ _ _", + "[ ]_[ ]_[ ]/ _ _ \\[ ]_[ ]_[ ]", + "|_=__-_ =_|_[ ]_[ ]_|_=-___-__|", + " | _- = | =_ = _ |= _= |", + " |= -[] |- = _ = |_-=_[] |", + " | =_ |= - ___ | =_ = |", + " |= []- |- /| |\\ |=_ =[] |", + " |- =_ | =| | | | |- = - |", + " |_______|__|_|_|_|__|_______|" + }; + + final String[] castleMask = new String[] { + " RR", + "", + " yyy", + " y y", + " y y", + " y y", + "", + "", + "", + " yyy", + " yy yy", + " y y y y", + " yyyyyyy" + }; + + anim.addEntity(new Entity("castle", castleImage, castleMask, + columns - 32, rows - 13, DEPTH_CASTLE)); + } + + private void addAllSeaweed() { + final int seaweedCount = columns / 12; // 15 + for (int i = 0; i < seaweedCount; i++) + addSeaweed(); + } + + private void addSeaweed() { + List[] seaweedList = new ArrayList[2]; + //int height = random.nextInt(4) + 3; + int height = random.nextInt(8) + 3; + + seaweedList[0] = new ArrayList(); + seaweedList[1] = new ArrayList(); + + for (int i = 0; i < height; i++) { + int leftSide = i % 2; + int rightSide = leftSide == 0 ? 1 : 0; + seaweedList[leftSide].add("("); + seaweedList[rightSide].add(" )"); + } + + String[][] seaweedImage = new String[2][]; + seaweedImage[0] = seaweedList[0].toArray(new String[0]); + seaweedImage[1] = seaweedList[1].toArray(new String[0]); + + final int x = random.nextInt(columns - 2) + 1; + final int y = rows - height; + final float animSpeed = random.nextFloat() * 0.5F + 0.25F; + + Entity entity = new Entity("seaweed", seaweedImage, x, y, DEPTH_SEAWEED); + + // seaweed lives for 8 to 12 minutes + entity.dieTime = System.currentTimeMillis() / 1000L + random.nextInt(4 * 60) + 8 * 60; + entity.deathCallback = seaweedDeathCallback; + entity.callbackArguments[3] = animSpeed; + entity.defaultColor = 'g'; + + anim.addEntity(entity); + } + + EntityCallback seaweedDeathCallback = new EntityCallback() { + public void run(Entity entity) { + addSeaweed(); + } + }; + + private void addAllFish() { + // figure out how many fish to add by the size of the screen + // minus the stuff above the water + final int screenSize = (rows - (WATER_LEVEL + 4)) * columns; + final int fishCount = screenSize / 350; + + for (int i = 0; i < fishCount; i++) { + addFish(); + } + } + + private void addBubble(Entity fish) { + int x = (int)fish.fx; + int y = (int)fish.fy; + float[] cbArgs = fish.callbackArguments; + + // moving right + if (cbArgs[0] > 0) { + x += fish.width; + } + y += fish.height / 2; + + // bubble always goes on top of the fish + addBubble(x, y, fish.depth - 1); + } + + private void addBubble(int x, int y, int depth) { + final String[][] shape = { + { "." }, { "o" }, { "O" }, { "O" }, { "O" } + }; + + Entity entity = new Entity("bubble", shape, x, y, depth); + entity.type = ENTITY_TYPE_BUBBLE; + entity.callbackArguments[1] = -1.0F; + entity.callbackArguments[3] = 0.1F; + entity.dieOffscreen = true; + entity.physical = true; + entity.collHandler = bubbleCollision; + entity.defaultColor = 'C'; + + anim.addEntity(entity); + } + + EntityCallback bubbleCollision = new EntityCallback() { + public void run(Entity bubble) { + for (Entity e : bubble.collisions) { + if (e.type == ENTITY_TYPE_WATERLINE) { + bubble.kill(); + break; + } + } + } + }; + + private void addFish() { + final String fishImage[][] = { + { + " \\", + " ...\\..,", + "\\ /' \\", + " >= ( ' >", + "/ \\ / /", + " `\"'\"'/''" + }, + { + " 2", + " 1112111", + "6 11 1", + " 66 7 4 5", + "6 1 3 1", + " 11111311" + }, + { + " /", + " ,../...", + " / '\\ /", + "< ' ) =<", + " \\ \\ / \\", + " `'\\'\"'\"'" + }, + { + " 2", + " 1112111", + " 1 11 6", + "5 4 7 66", + " 1 3 1 6", + " 11311111" + }, + { + " \\", + "\\ /--\\", + ">= (o>", + "/ \\__/", + " /" + }, + { + " 2", + "6 1111", + "66 745", + "6 1111", + " 3" + }, + { + " /", + " /--\\ /", + "::::::::;;\\\\", + " ''\\\\\\\\\'' ';\\" + }, + { + " 222", + " 1122211 666", + " 4111111111666", + "51111111111666", + " 113333311 666" + }, + { + " __", + "><_'>", + " '" + }, + { + " 11", + "61145", + " 3" + }, + { + " __", + "<'_><", + " `" + }, + { + " 11", + "54116", + " 3" + }, + { + " ..\\,", + ">=' ('>", + " '''/''" + }, + { + " 1121", + "661 745", + " 111311" + }, + { + " ,/..", + "<') `=<", + " ``\\```" + }, + { + " 1211", + "547 166", + " 113111" + }, + { + " \\", + " / \\", + ">=_('>", + " \\_/", + " /" + }, + { + " 2", + " 1 1", + "661745", + " 111", + " 3" + }, + { + " /", + " / \\", + "<')_=<", + " \\_/", + " \\" + }, + { + " 2", + " 1 1", + "547166", + " 111", + " 3" + }, + { + " ,\\", + ">=('>", + " '/" + }, + { + " 12", + "66745", + " 13" + }, + { + " /,", + "<')=<", + " \\`" + }, + { + " 21", + "54766", + " 31" + }, + { + " __", + "\\/ o\\", + "/\\__/" + }, + { + " 11", + "61 41", + "61111" + }, + { + " __", + "/o \\/", + "\\__/\\" + }, + { + " 11", + "14 16", + "11116" + } + }; + + // 1: body + // 2: dorsal fin + // 3: flippers + // 4: eye + // 5: mouth + // 6: tailfin + // 7: gills + + int fishNum = random.nextInt(fishImage.length / 2); + int fishIndex = fishNum * 2; + float speed = random.nextFloat() * 2 + 0.25F; + int depth = random.nextInt(DEPTH_FISH_END - DEPTH_FISH_START) + DEPTH_FISH_START; + String[] colorMask = fishImage[fishIndex + 1]; + + randColor(colorMask); + + if (fishNum % 2 != 0) + speed *= -1; + + Entity fishObject = new Entity("fish", fishImage[fishIndex], colorMask, 0, 0, depth); + fishObject.type = ENTITY_TYPE_FISH; + fishObject.callback = fishCallback; + fishObject.autoTrans = true; + fishObject.dieOffscreen = true; + fishObject.deathCallback = fishDeathCallback; + fishObject.callbackArguments[0] = speed; + fishObject.physical = true; + fishObject.collHandler = fishCollision; + + int maxHeight = WATER_LEVEL + 4; + int minHeight = rows - fishObject.height; + fishObject.fy = random.nextInt(minHeight - maxHeight) + maxHeight; + + if (fishNum % 2 != 0) { + fishObject.fx = columns - 2; + } else { + fishObject.fx = 1 - fishObject.width; + } + + anim.addEntity(fishObject); + } + + EntityCallback fishCallback = new EntityCallback() { + public void run(Entity entity) { + if (random.nextInt(100) > 97) + addBubble(entity); + + entity.move(); + } + }; + + EntityCallback fishDeathCallback = new EntityCallback() { + public void run(Entity entity) { + addFish(); + } + }; + + EntityCallback fishCollision = new EntityCallback() { + public void run(Entity fish) { + for (Entity e : fish.collisions) { + if (e.type == ENTITY_TYPE_TEETH) { + addSplat(e.fx, e.fy, e.depth); + fish.kill(); + break; + } + } + } + }; + + private void addSplat(float fx, float fy, int depth) { + final String[][] splatImage = { + { + "", + " .", + " ***", + " '", + "" + }, + { + "", + " \",*;`", + " \"*,**", + " *\"'~'", + "" + }, + { + " , ,", + " \" \",\"'", + " *\" *'\"", + " \" ; .", + "" + }, + { + "* ' , ' `", + "' ` * . '", + " ' `' \",'", + "* ' \" * .", + "\" * ', '" + } + }; + + Entity entity = new Entity("splat", splatImage, (int)fx - 4, (int)fy - 2, depth - 2); + entity.defaultColor = 'R'; + entity.callbackArguments[3] = 0.25F; + entity.transparent = ' '; + entity.dieFrame = 15; + anim.addEntity(entity); + } + + private void addShark() { + final String[][] sharkImage = { + { + " __", + " ( `\\", + " ,??????????????????????????) `\\", + ";' `.????????????????????????( `\\__", + " ; `.?????????????__..---'' `~~~~-._", + " `. `.____...--'' (b `--._", + " > _.-' .(( ._ )", + " .`.-`--...__ .-' -.___.....-(|/|/|/|/'", + " ;.'?????????`. ...----`.___.',,,_______......---'", + " '???????????'-'" + }, + { + " __", + " /' )", + " /' (??????????????????????????,", + " __/' )????????????????????????.' `;", + " _.-~~~~' ``---..__?????????????.' ;", + " _.--' b) ``--...____.' .'", + "( _. )). `-._ <", + " `\\|\\|\\|\\|)-.....___.- `-. __...--'-.'.", + " `---......_______,,,`.___.'----... .'?????????`.;", + " `-`???????????`" + } + }; + + final String[][] sharkMask = { + { + "", + "", + "", + "", + "", + " cR", + "", + " cWWWWWWWW", + "", + "" + }, + { + "", + "", + "", + "", + "", + " Rc", + "", + " WWWWWWWWc", + "", + "" + } + }; + + int dir = random.nextInt(2); + int x = -53; + int y = random.nextInt(rows - (10 + (WATER_LEVEL + 4))) + (WATER_LEVEL + 4); + int teethX = -9; + int teethY = y + 7; + float speed = 2.0F; + if (dir > 0) { + speed *= -1; + x = columns - 2; + teethX = x + 9; + } + + Entity teeth = new Entity("teeth", "*", teethX, teethY, DEPTH_SHARK + 1); + teeth.type = ENTITY_TYPE_TEETH; + teeth.callbackArguments[0] = speed; + teeth.physical = true; + anim.addEntity(teeth); + + Entity entity = new Entity("shark", sharkImage[dir], sharkMask[dir], + x, y, DEPTH_SHARK); + entity.callbackArguments[0] = speed; + entity.dieOffscreen = true; + entity.deathCallback = sharkDeathCallback; + entity.defaultColor = 'C'; + anim.addEntity(entity); + } + + // when a shark dies, kill the "teeth" too, the associated + // entity that does the actual collision + EntityCallback sharkDeathCallback = new EntityCallback() { + public void run(Entity entity) { + Entity[] teeth = anim.getEntitiesOfType(ENTITY_TYPE_TEETH); + for (Entity e : teeth) + anim.delEntity(e); + randomObject(); + } + }; + + private void addShip() { + final String[][] shipImage = { + { + " | | |", + " )_) )_) )_)", + " )___))___))___)\\", + " )____)____)_____)\\\\", + "_____|____|____|____\\\\\\__", + "\\ /" + }, + { + " | | |", + " (_( (_( (_(", + " /(___((___((___(", + " //(_____(____(____(", + "__///____|____|____|_____", + " \\ /" + } + }; + + final String[][] shipMask = { + { + " y y y", + " ", + " w", + " ww", + "yyyyyyyyyyyyyyyyyyyywwwyy", + "y y" + }, + { + " y y y", + " ", + " w ", + " ww ", + "yywwwyyyyyyyyyyyyyyyyyyyy", + " y y" + } + }; + + int dir = random.nextInt(2); + int x = -24; + float speed = 1.0F; + if (dir > 0) { + speed *= -1; + x = columns - 2; + } + + Entity entity = new Entity("ship", shipImage[dir], shipMask[dir], + x, WATER_LEVEL - 5, DEPTH_WATER_GAP1); + entity.callbackArguments[0] = speed; + entity.dieOffscreen = true; + entity.deathCallback = randomDeathCallback; + entity.defaultColor = 'W'; + + anim.addEntity(entity); + } + + final private void addWhale() { + final String[][] whaleImage = { + { + " .-----:", + " .' `.", + ",????/ (o) \\", + "\\`._/ ,__)" + }, + { + " :-----.", + " .' `.", + " / (o) \\????,", + "(__, \\_.'/" + } + }; + + final String[][] whaleMask = { + { + " C C", + " CCCCCCC", + " C C C", + " BBBBBBB", + " BB BB", + "B B BWB B", + "BBBBB BBBB" + }, + { + " C C", + " CCCCCCC", + " C C C", + " BBBBBBB", + " BB BB", + " B BWB B B", + "BBBB BBBBB" + } + }; + + final String[][] waterSpout = { + { + "", + "", + " :" + }, + { + "", + " :", + " :", + }, + { + " . .", + " -:-", + " :", + }, + { + " . .", + " .-:-.", + " :", + }, + { + " . .", + " '.-:-.`", + " ' : '" + }, + { + "", + " .- -.", + " ; : ;" + }, + { + "", + "", + " ; ;" + } + }; + + int dir = random.nextInt(2); + int x; + float speed = 1.0F; + String[][] whaleAnim = new String[12][]; + + if (dir > 0) { + speed *= -1; + x = columns - 2; + } else { + x = -18; + } + + // no water spout + for (int i = 0; i < 5; i++) { + whaleAnim[i] = new String[7]; + for (int j = 0; j < 3; j++) + whaleAnim[i][j] = ""; + for (int j = 0; j < 4; j++) + whaleAnim[i][3 + j] = whaleImage[dir][j]; + } + + // animate water spout + for (int i = 0; i < 7; i++) { + whaleAnim[5 + i] = new String[7]; + for (int j = 0; j < 3; j++) { + whaleAnim[5 + i][j] = dir > 0 ? "" : " "; + whaleAnim[5 + i][j] += waterSpout[i][j]; + } + for (int j = 0; j < 4; j++) + whaleAnim[5 + i][3 + j] = whaleImage[dir][j]; + } + + Entity entity = new Entity("whale", whaleAnim, whaleMask[dir], + x, WATER_LEVEL - 5, DEPTH_WATER_GAP2); + entity.callbackArguments[0] = speed; + entity.callbackArguments[3] = 1.0F; + entity.dieOffscreen = true; + entity.deathCallback = randomDeathCallback; + + anim.addEntity(entity); + } + + private void addMonster() { + final String[][][] monsterImage = { + { + { + " ____", + " __??????????????????????????????????????????/ o \\", + " / \\????????_?????????????????????_???????/ ____ >", + " _??????| __ |?????/ \\????????_????????/ \\????| |", + " | \\?????| || |????| |?????/ \\?????| |???| |" + }, + { + " ____", + " __?????????/ o \\", + " _?????????????????????_???????/ \\?????/ ____ >", + " _???????/ \\????????_????????/ \\????| __ |???| |", + " | \\?????| |?????/ \\?????| |???| || |???| |" + }, + { + " ____", + " __????????????????????/ o \\", + " _??????????????????????_???????/ \\????????_???????/ ____ >", + "| \\??????????_????????/ \\????| __ |?????/ \\????| |", + " \\ \\???????/ \\?????| |???| || |????| |???| |" + }, + { + " ____", + " __???????????????????????????????/ o \\", + " _??????????_???????/ \\????????_??????????????????/ ____ >", + " | \\???????/ \\????| __ |?????/ \\????????_??????| |", + " \\ \\?????| |???| || |????| |?????/ \\????| |" + } + }, + { + { + " ____", + " / o \\??????????????????????????????????????????__", + "< ____ \\???????_?????????????????????_????????/ \\", + " | |????/ \\????????_????????/ \\?????| __ |??????_", + " | |???| |?????/ \\?????| |????| || |?????/ |" + }, + { + " ____", + " / o \\?????????__", + "< ____ \\?????/ \\???????_?????????????????????_", + " | |???| __ |????/ \\????????_????????/ \\???????_", + " | |???| || |???| |?????/ \\?????| |?????/ |" + }, + { + " ____", + " / o \\????????????????????__", + "< ____ \\???????_????????/ \\???????_??????????????????????_", + " | |????/ \\?????| __ |????/ \\????????_??????????/ |", + " | |???| |????| || |???| |?????/ \\???????/ /" + }, + { + " ____", + " / o \\???????????????????????????????__", + "< ____ \\??????????????????_????????/ \\???????_??????????_", + " | |??????_????????/ \\?????| __ |????/ \\???????/ |", + " | |????/ \\?????| |????| || |???| |?????/ /" + } + }}; + + final String[][] monsterMask = { + { + "", + " W", + "", + "", + "" + }, + { + "", + " W", + "", + "", + "" + } + }; + + int dir = random.nextInt(2); + int x; + float speed = 2.0F; + + if (dir > 0) { + speed *= -1; + x = columns - 2; + } else { + x = -64; + } + + String[][] monsterAnimMask = new String[4][]; + for (int i = 0; i < 4; i++) + monsterAnimMask[i] = monsterMask[dir]; + + Entity entity = new Entity("monster", monsterImage[dir], monsterAnimMask, + x, WATER_LEVEL - 3, DEPTH_WATER_GAP2); + entity.callbackArguments[0] = speed; + entity.callbackArguments[3] = 0.25F; + entity.dieOffscreen = true; + entity.deathCallback = randomDeathCallback; + entity.defaultColor = 'G'; + + anim.addEntity(entity); + } + + private void addBigFish() { + final String[][] bigFishImage = { + { + " ______", + "`\"\"-. `````-----.....__", + " `. . . `-.", + " : . . `.", + " , : . . _ :", + ": `. : (@) `._", + " `. `..' . =`-. .__)", + " ; . = ~ : .-", + " .' .'`. . . =.-' `._ .'", + ": .' : . .'", + "' .' . . . .-'", + " .'____....----''.'=.'", + " \"\" .'.'", + " ''\"'`" + }, + { + " ______", + " __.....-----''''' .-\"\"'", + " .-' . . .'", + " .' . . :", + " : _ . . : ,", + " _.' (@) : .' :", + "(__. .-'= . `..' .'", + " \"-. : ~ = . ;", + " `. _.' `-.= . . .'`. `.", + " `. . : `. :", + " `-. . . . `. `", + " `.=`.``----....____`.", + " `.`. \"\"", + " '`\"``" + } + }; + + final String[][] bigFishMask = { + { + " 111111", + "11111 11111111111111111", + " 11 2 2 111", + " 1 2 2 11", + " 1 1 2 2 1 1", + "1 11 1 1W1 111", + " 11 1111 2 1111 1111", + " 1 2 1 1 1 111", + " 11 1111 2 2 1111 111 11", + "1 11 1 2 11", + " 1 11 2 2 2 111", + " 111111111111111111111", + " 11 1111", + " 11111" + }, + { + " 111111", + " 11111111111111111 11111", + " 111 2 2 11", + " 11 2 2 1", + " 1 1 2 2 1 1", + " 111 1W1 1 11 1", + "1111 1111 2 1111 11", + " 111 1 1 1 2 1", + " 11 111 1111 2 2 1111 11", + " 11 2 1 11 1", + " 111 2 2 2 11 1", + " 111111111111111111111", + " 1111 11", + " 11111" + } + }; + + int dir = random.nextInt(2); + int x; + float speed = 3.0F; + if (dir > 0) { + x = columns - 1; + speed *= -1; + } else { + x = -34; + } + int maxHeight = WATER_LEVEL + 4; + int minHeight = rows - 15; + int y = random.nextInt(minHeight - maxHeight) + maxHeight; + randColor(bigFishMask[dir]); + + Entity entity = new Entity("big_fish", bigFishImage[dir], + bigFishMask[dir], x, y, DEPTH_SHARK); + entity.callbackArguments[0] = speed; + entity.dieOffscreen = true; + entity.deathCallback = randomDeathCallback; + entity.defaultColor = 'Y'; + + anim.addEntity(entity); + } + + EntityCallback randomDeathCallback = new EntityCallback() { + public void run(Entity entity) { + randomObject(); + } + }; + + private void randomObject() { + switch (random.nextInt(5)) { + case 0: addShip(); break; + case 1: addWhale(); break; + case 2: addMonster(); break; + case 3: addBigFish(); break; + case 4: addShark(); break; + } + } + + private void randColor(String[] mask) { + final char[] colors = { 'c','C','r','R','y','Y','b','B','g','G','m','M' }; + final char[] keys = { '1', '2', '3', '5', '6', '7' }; + + char[] newColors = new char[keys.length]; + for (int i = 0; i < newColors.length; i++) + newColors[i] = colors[random.nextInt(colors.length)]; + + // Set eye white, rest as random color + for (int i = 0; i < mask.length; i++) { + mask[i] = mask[i].replace('4', 'W'); + for (int j = 0; j < keys.length; j++) { + mask[i] = mask[i].replace(keys[j], newColors[j]); + } + } + } +} diff --git a/src/org/helllabs/java/asciiquarium/Entity.java b/src/org/helllabs/java/asciiquarium/Entity.java new file mode 100644 index 0000000..ea6622a --- /dev/null +++ b/src/org/helllabs/java/asciiquarium/Entity.java @@ -0,0 +1,196 @@ +package org.helllabs.java.asciiquarium; + +import java.util.List; + +public class Entity implements Comparable { + String name; + int type = -1; + String[][] shape; + String[][] mask; + float fx, fy; + int height; + int width; + int depth; + float[] callbackArguments = { 0.0F, 0.0F, 0.0F, 0.0F }; + EntityCallback callback = null; + EntityCallback deathCallback = null; + EntityCallback collHandler = null; + List collisions = null; + boolean autoTrans = false; + boolean dieOffscreen = false; + boolean physical = false; + long dieTime = -1; + int dieFrame = -1; + float fframe = 0.0F; + int frame = 0; + int frameCount = 0; + Animation animation; + char defaultColor = 'w'; + char transparent = '?'; + + abstract interface EntityCallback { + public void run(Entity entity); + } + + public Entity(String name, String[] shape, String[] mask, int x, int y, int depth) { + this.shape = new String[1][]; + this.shape[0] = shape; + this.mask = new String[1][]; + this.mask[0] = mask; + setVars(name, x, y, depth); + } + + public Entity(String name, String shape, int x, int y, int depth) { + this.shape = new String[1][0]; + this.shape[0] = new String[] { shape }; + this.mask = null; + setVars(name, x, y, depth); + } + + public Entity(String name, String[][] shape, String[][] mask, int x, int y, int depth) { + this.shape = shape; + this.mask = mask; + setVars(name, x, y, depth); + } + + public Entity(String name, String[][] shape, String[] mask, int x, int y, int depth) { + this.shape = shape; + this.mask = new String[shape.length][]; + for (int i = 0; i < shape.length; i++) + this.mask[i] = mask; + setVars(name, x, y, depth); + } + + public Entity(String name, String[][] shape, int x, int y, int depth) { + this.shape = shape; + this.mask = null; + setVars(name, x, y, depth); + } + + private void setVars(String name, int x, int y, int depth) { + this.name = name; + this.fx = x; + this.fy = y; + this.depth = depth; + + int width = 0; + for (String s : shape[0]) { // we assume all frames have the same size + if (s.length() > width) + width = s.length(); + } + this.height = shape[0].length; + this.width = width; + this.callback = moveCallback; + + //Log.i("Asciiquarium", "Entity constructor: " + name + " (" + this.width + "x" + this.height + ")"); + } + + public void draw(Screen screen) { + draw(screen, 0); + } + + public void draw(Screen screen, int frame) { + //Log.d("Asciiquarium", "Entity: draw " + name + " " + frame + "/" + (shape.length - 1)); + int x = (int)fx; + int y = (int)fy; + + if (frame >= shape.length || x >= screen.columns || y >= screen.rows) + return; + + for (int lineNum = 0; lineNum < height; lineNum++) { + int yy = y + lineNum; + + if (yy < 0) + continue; + + if (yy >= screen.rows) + break; + + final int length = shape[frame][lineNum].length(); + + /* Log.d("Asciiquarium", "Entity: draw start=" + start + " end=" + end + + " line=" + yy + " col=" + x); */ + + final String line = shape[frame][lineNum]; + String attr = ""; + int aLength = 0; + + if (mask != null) { + attr = mask[frame][lineNum]; + aLength = attr.length(); + } + + int offset = yy * screen.columns + x; + boolean flag = false; + char c, a; // character and attribute + for (int srcPos = 0; srcPos < length; srcPos++, offset++) { + c = line.charAt(srcPos); + + if (c != ' ') + flag = true; + + if (x + srcPos < 0 || x + srcPos >= screen.columns) + continue; + + if (flag && c != transparent) { + screen.text[offset] = c; + + if (srcPos < aLength) { + a = attr.charAt(srcPos); + if (a == ' ') + a = defaultColor; + screen.color[offset] = a; + } else { + screen.color[offset] = defaultColor; + } + } + } + } + + //Log.d("Asciiquarium", "Entity: draw complete"); + } + + // The default callback. You can also call this from your own + // callback to do the work of moving and animating the entity + // after you have done whatever other processing you want to do. + + EntityCallback moveCallback = new EntityCallback() { + public void run(Entity entity) { + entity.move(); + } + }; + + public void move() { + if (callbackArguments[3] > 0) { + fframe += callbackArguments[3]; + if (fframe >= shape.length) + fframe -= shape.length; + frame = (int)fframe; + } + frameCount++; + + fx += callbackArguments[0]; + fy += callbackArguments[1]; + + //Log.d("Asciiquarium", name + " frame = " + frame); + } + + // Remove this entity from the animation. This is equivalent to: + // + // animation.delEntity(entity); + // + // This does not destroy the object, so you can still + // read it later (or put it in a different animation) as long + // as you have a reference to it. + + public void kill() { + animation.delEntity(this); + } + + // Comparable + + @Override + public int compareTo(Entity e) { + return this.depth < e.depth ? 1 : this.depth > e.depth ? -1 : 0; + } +} diff --git a/src/org/helllabs/java/asciiquarium/MyApplet.java b/src/org/helllabs/java/asciiquarium/MyApplet.java new file mode 100644 index 0000000..3c0a921 --- /dev/null +++ b/src/org/helllabs/java/asciiquarium/MyApplet.java @@ -0,0 +1,90 @@ +package org.helllabs.java.asciiquarium; + +import org.helllabs.java.asciiquarium.Asciiquarium.Renderer; +import java.applet.Applet; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Image; + + +public class MyApplet extends Applet implements Runnable { + private static final long serialVersionUID = 1L; + private int width, height; + private int columns, rows; + private int cellWidth, cellHeight; + private long previousTime; + private Asciiquarium asciiquarium; + private TerminalBlitter blitter; + private Thread animThread; + private boolean running; + private Graphics graphics; + private Image image; + + public void init() { + cellWidth = Asciiquarium.CELL_WIDTH; + cellHeight = Asciiquarium.CELL_HEIGHT; + columns = 100; + rows = 40; + width = columns * cellWidth; + height = rows * cellHeight; + + image = createImage(width, height); + graphics = image.getGraphics(); + + asciiquarium = new Asciiquarium(columns, rows); + asciiquarium.setRenderer(renderer); + blitter = new TerminalBlitter(columns, rows, cellWidth, cellHeight); + + running = true; + animThread = new Thread(this); + animThread.start(); + } + + public void destroy() { + running = false; + if (animThread.isAlive()) { + try { + animThread.join(); + } catch (InterruptedException e) { } + } + } + + public void paint(Graphics g) { + update(g); + } + + public void update(Graphics g) { + doDraw(); + g.drawImage(image, 0, 0, this); + } + + void doDraw() { + // clear background + graphics.setColor(Color.black); + graphics.fillRect(0, 0, width, height); + + if (blitter != null && asciiquarium != null) { + asciiquarium.draw(); + } + } + + Renderer renderer = new Renderer() { + public void putChar(int column, int row, char c, char color) { + blitter.setChar(graphics, column, row, c, color); + } + }; + + @Override + public void run() { + this.running = true; + + while (running) { + repaint(); + + try { + Thread.sleep(160); + } catch (InterruptedException e) { } + } + } +} + diff --git a/src/org/helllabs/java/asciiquarium/Screen.java b/src/org/helllabs/java/asciiquarium/Screen.java new file mode 100644 index 0000000..e560d23 --- /dev/null +++ b/src/org/helllabs/java/asciiquarium/Screen.java @@ -0,0 +1,27 @@ +package org.helllabs.java.asciiquarium; + + +public class Screen { + int columns; + int rows; + char[] text; + char[] color; + + public Screen(int columns, int rows) { + //Log.d("Asciiquarium", "Screen constructor, size " + columns + "x" + rows); + + this.columns = columns; + this.rows = rows; + + text = new char[columns * rows]; + color = new char[columns * rows]; + } + + public void clear() { + int size = columns * rows; + for (int i = 0; i < size; i++) { + text[i] = ' '; + color[i] = ' '; + } + } +} diff --git a/src/org/helllabs/java/asciiquarium/TerminalBlitter.java b/src/org/helllabs/java/asciiquarium/TerminalBlitter.java new file mode 100644 index 0000000..ba177e3 --- /dev/null +++ b/src/org/helllabs/java/asciiquarium/TerminalBlitter.java @@ -0,0 +1,66 @@ +package org.helllabs.java.asciiquarium; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; + + +public class TerminalBlitter { + int columns; + int rows; + int cellWidth; + int cellHeight; + Font font; + static final Color[]colors = new Color[] { + new Color(0x7f0000), // r + new Color(0x007f00), // g + new Color(0x7f7f00), // y + new Color(0x00007f), // b + new Color(0x7f007f), // m + new Color(0x007f7f), // c + new Color(0x7f7f7f), // w + new Color(0xff0000), // R + new Color(0x00ff00), // G + new Color(0xffff00), // Y + new Color(0x0000ff), // B + new Color(0xff00ff), // M + new Color(0x00ffff), // C + new Color(0xffffff) // W + }; + + public TerminalBlitter(int columns, int rows, int cellWidth, int cellHeight) { + //Log.i("Asciiquarium", "TerminalBlitter constructor: " + columns + "x" + rows + " (" + cellWidth + "x" + cellHeight + ")"); + this.columns = columns; + this.rows = rows; + this.cellWidth = cellWidth; + this.cellHeight = cellHeight; + + font = new Font("Monospaced", Font.PLAIN, cellHeight); + } + + + public void setChar(Graphics graphics, int column, int row, char c, char color) { + int n; + String ca = Character.toString(c); + + switch (color) { + case 'r': n = 0; break; case 'R': n = 7; break; + case 'g': n = 1; break; case 'G': n = 8; break; + case 'y': n = 2; break; case 'Y': n = 9; break; + case 'b': n = 3; break; case 'B': n = 10; break; + case 'm': n = 4; break; case 'M': n = 11; break; + case 'c': n = 5; break; case 'C': n = 12; break; + case 'w': n = 6; break; case 'W': n = 13; break; + default: n = 6; + } + + int x = column * cellWidth; + int y = row * cellHeight; + + graphics.setColor(colors[n]); + + if (graphics != null) { + graphics.drawString(ca, x, y); + } + } +}