__package__ = "com.example.fluid"
from java.lang import Integer, Math, Object, Runnable, String
from java.util import ArrayList, HashMap, Random, Timer, TimerTask
import android.app
from android.content import Context
from android.content.res import Resources
from android.graphics import Bitmap, BitmapFactory, Canvas, Color, Paint, \
PorterDuff, PorterDuffXfermode, Rect
from android.os import Bundle, Handler
from android.util import Log
from android.view import MotionEvent, View, ViewGroup, Window
from android.widget import LinearLayout, ScrollView
from app_resources import R
# Application classes
class Point(Object):
__fields__ = {"x": int, "y": int, "dx": int, "frozen": int}
@args(void, [int, int])
def __init__(self, x, y):
Object.__init__(self)
self.x = x
self.y = y
self.dx = 0
self.frozen = 0
class PointArray(ArrayList):
__item_types__ = [Point]
def __init__(self):
ArrayList.__init__(self)
class TileArray(ArrayList):
__item_types__ = [Integer]
def __init__(self):
ArrayList.__init__(self)
class TileMap(HashMap):
__item_types__ = [String, Integer]
def __init__(self):
HashMap.__init__(self)
class BitmapArray(ArrayList):
__item_types__ = [Bitmap]
def __init__(self):
ArrayList.__init__(self)
class DrawView(View):
WIDTH = 128
HEIGHT = 256
TILE_WIDTH = 16
TILE_HEIGHT = 16
VIEW_WIDTH = 8
VIEW_HEIGHT = 16
TILES = 128
MAX_VOLUME = 1600
EMPTY = 0x00000000
FLUID_COLOUR = 0xff28a0ff
DOOR_COLOUR = 0xffe0865c
FREEZE_VALUE = 3
BLOCK_INDEX = 6
LEFT_DOOR_INDEX = 15
RIGHT_DOOR_INDEX = 16
__fields__ = {
"bitmap": Bitmap,
"points": PointArray,
"resources": Resources
}
@args(void, [Context, Resources])
def __init__(self, context, resources):
View.__init__(self, context)
self.resources = resources
self.ready = False
self.srcPaint = Paint()
self.srcPaint.setColor(self.EMPTY)
self.srcPaint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC))
self.srcPaint.setFilterBitmap(False)
self.points = PointArray()
self.srcRect = Rect(0, 0, self.WIDTH, self.HEIGHT)
self.destRect = Rect()
self.bitmap = None
self.random = Random()
self.sx = self.WIDTH/2
self.sy = 0
self.tiles = BitmapArray()
options = BitmapFactory.Options()
options.inScaled = False
options.inDensity = self.resources.getDisplayMetrics().densityDpi
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.wall1, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.wall2, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.wall3, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.iceblock, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.grate, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.block, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.left, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.right, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.top, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.bottom, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.topleft, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.topright, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.bottomleft, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.bottomright, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.doorleft, options))
self.tiles.add(BitmapFactory.decodeResource(self.resources, R.raw.doorright, options))
self.tileMap = TileMap()
self.tileMap["X"] = Integer(1)
self.tileMap["C"] = Integer(2)
self.tileMap["Z"] = Integer(3)
self.tileMap["O"] = Integer(4)
self.tileMap["#"] = Integer(5)
self.tileMap["*"] = Integer(self.BLOCK_INDEX)
self.tileMap["g"] = Integer(7)
self.tileMap["j"] = Integer(8)
self.tileMap["y"] = Integer(9)
self.tileMap["n"] = Integer(10)
self.tileMap["t"] = Integer(11)
self.tileMap["u"] = Integer(12)
self.tileMap["b"] = Integer(13)
self.tileMap["m"] = Integer(14)
self.tileMap["A"] = Integer(self.LEFT_DOOR_INDEX)
self.tileMap["D"] = Integer(self.RIGHT_DOOR_INDEX)
self.tileArray = TileArray()
@args(void, [Canvas])
def onDraw(self, canvas):
View.onDraw(self, canvas)
canvas.drawBitmap(self.bitmap, self.srcRect, self.destRect, None)
@args(int, [])
def getSuggestedMinimumHeight(self):
return self.HEIGHT
@args(int, [])
def getSuggestedMinimumWidth(self):
return self.WIDTH
@args(void, [int, int])
def onMeasure(self, widthSpec, heightSpec):
w = View.MeasureSpec.getSize(widthSpec)
h = View.MeasureSpec.getSize(heightSpec)
self.setMeasuredDimension(w, (w * self.HEIGHT)/self.WIDTH)
@args(void, [int, int, int, int])
def onSizeChanged(self, width, height, oldWidth, oldHeight):
self.scale = Math.min(float(width)/self.WIDTH, float(height)/self.HEIGHT)
w = self.scale * self.WIDTH
h = self.scale * self.HEIGHT
self.bitmap = Bitmap.createBitmap(self.WIDTH, self.HEIGHT,
Bitmap.Config.ARGB_8888)
self.bitmap.eraseColor(Color.argb(0, 0, 0, 0))
canvas = Canvas(self.bitmap)
level = self.resources.getString(R.string.level)
i = 0
x = 0
y = 0
while i < self.TILES:
ch = level.subSequence(i, i + 1).toString()
index = self.tileMap.get(ch)
n = 0
if index != None:
n = index.intValue()
tile = self.tiles[n - 1]
canvas.drawBitmap(tile, x, y, None)
self.tileArray.add(Integer(n))
if i % self.VIEW_WIDTH == 7:
x = 0
y += 16
else:
x += self.TILE_WIDTH
i += 1
x = (width - w)/2
y = (height - h)/2
self.destRect = Rect(x, y, x + w, y + h)
self.ready = True
@args(bool, [MotionEvent])
def onTouchEvent(self, event):
action = event.getAction()
if action == MotionEvent.ACTION_DOWN:
# Start checking to see if we should place a tile.
self.dragX = event.getX()
self.dragY = event.getY()
self.dragging = True
return True
elif action == MotionEvent.ACTION_MOVE:
# Keep track of how far the "cursor" moved.
if self.dragging and self.notMovedTooFar(event):
# We are dragging and the cursor hasn't moved too far.
return True
else:
# Cancel dragging and reject the event.
self.dragging = False
return False
elif action == MotionEvent.ACTION_UP:
# If not much motion occurred then add a tile. Otherwise, reject
# the event.
if not self.dragging or not self.notMovedTooFar(event):
return False
else:
# Ignore unknown events.
return False
x = int(event.getX())
y = int(event.getY())
# We would use self.destRect.contains(x, y) here but it seems to test
# containment incorrectly.
if x < self.destRect.left or x >= self.destRect.right or \
y < self.destRect.top or y >= self.destRect.bottom:
return False
sx = (x - self.destRect.left)/self.scale
sy = (y - self.destRect.top)/self.scale
self.addTile(sx/self.TILE_WIDTH, sy/self.TILE_HEIGHT)
self.invalidate()
return True
@args(bool, [MotionEvent])
def notMovedTooFar(self, event):
dx = Math.abs(event.getX() - self.dragX)
dy = Math.abs(event.getY() - self.dragY)
tw = self.scale * self.TILE_WIDTH
th = self.scale * self.TILE_HEIGHT
if dx > tw:
return False
elif dy > th:
return False
return True
def update(self):
# Ensure that we don't try to update the bitmap if it hasn't been
# created yet.
if self.bitmap == None:
return
# Add more droplets if there is less than the allowed volume in play.
l = len(self.points)
if l < self.MAX_VOLUME:
if self.bitmap.getPixel(self.sx, self.sy) == self.EMPTY:
self.points.add(Point(self.sx, self.sy))
self.bitmap.setPixel(self.sx, self.sy, self.FLUID_COLOUR)
self.sx = self.WIDTH/2 + self.random.nextInt(5) - 2
it = self.points.iterator()
while it.hasNext():
p = it.next()
x = p.x
y = p.y
old_colour = self.bitmap.getPixel(x, y)
if old_colour != self.FLUID_COLOUR and old_colour != Color.WHITE and \
old_colour != self.EMPTY:
continue
self.bitmap.setPixel(p.x, p.y, self.EMPTY)
# Remove droplets that leave the play area.
if y == self.HEIGHT - 1:
it.remove()
continue
if self.bitmap.getPixel(p.x, y + 1) == self.EMPTY:
# The droplet can move vertically so let it fall, unfreezing it
# if it is frozen.
p.y = y + 1
p.dx = 0
p.frozen = 0
elif p.frozen < self.FREEZE_VALUE:
# Try to move the droplet horizonally if it is not frozen.
dx = p.dx
if dx == 0:
dx = self.random.nextInt(3) - 1
# Remove droplets that leave the play area.
x += dx
if x < 0 or x >= self.WIDTH:
it.remove()
continue
# Move or freeze the droplet.
adjacent = self.bitmap.getPixel(x, y)
if adjacent == self.EMPTY:
# Move the droplet to its new position.
p.x = x
# Try to move down as well. This reduces the occurrence of
# "spikes" in the flow.
if self.bitmap.getPixel(p.x, y + 1) == self.EMPTY:
y += 1
elif adjacent == Color.WHITE:
p.frozen += 1
elif y > 0 and self.bitmap.getPixel(x, y - 1) == Color.WHITE:
p.frozen += 1
elif self.bitmap.getPixel(x, y + 1) == Color.WHITE:
p.frozen += 1
else:
dx = -dx
p.dx = dx
p.y = y
# Fully frozen droplets are shown as ice. Otherwise they are shown
# as normal.
if p.frozen == self.FREEZE_VALUE:
self.bitmap.setPixel(p.x, p.y, Color.WHITE)
else:
self.bitmap.setPixel(p.x, p.y, self.FLUID_COLOUR)
self.invalidate()
@args(void, [int, int])
def addTile(self, tx, ty):
canvas = Canvas(self.bitmap)
i = (ty * 8) + tx
left = tx * self.TILE_WIDTH
top = ty * self.TILE_WIDTH
value = self.tileArray[i].intValue()
if value == self.BLOCK_INDEX:
# Erase the block tile.
canvas.drawRect(left, top, left + self.TILE_WIDTH,
top + self.TILE_HEIGHT, self.srcPaint)
value = 0
self.tileArray[i] = Integer(value)
class FluidActivity(android.app.Activity):
__interfaces__ = [Runnable]
def __init__(self):
android.app.Activity.__init__(self)
@args(void, [Bundle])
def onCreate(self, bundle):
android.app.Activity.onCreate(self, bundle)
window = self.getWindow()
window.requestFeature(Window.FEATURE_NO_TITLE)
self.handler = Handler()
self.view = DrawView(self, self.getResources())
self.view.setLayoutParams(ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT))
scrollView = ScrollView(self)
scrollView.addView(self.view)
self.setContentView(scrollView)
def onResume(self):
android.app.Activity.onResume(self)
self.handler.postDelayed(self, long(25))
def onPause(self):
android.app.Activity.onPause(self)
self.handler.removeCallbacks(self)
def run(self):
if self.view.ready == True:
self.view.update()
self.handler.postDelayed(self, long(25))