__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))

Files