diff options
| author | Jakob L. Kreuze <jakob@memeware.net> | 2019-01-12 15:31:19 -0500 |
|---|---|---|
| committer | Jakob L. Kreuze <jakob@memeware.net> | 2019-01-12 15:31:19 -0500 |
| commit | 335191bf832d749f679f4d88e7b04ce8b24e3803 (patch) | |
| tree | a714fc6cdfc7d83297d03b29aa6c6a398ba84276 /app/src | |
| parent | ef496487ae49a1e98ea3c9de0531ca4716f7cebf (diff) | |
Diffstat (limited to 'app/src')
| -rw-r--r-- | app/src/main/java/space/jakob/mines/GameActivity.kt | 21 | ||||
| -rw-r--r-- | app/src/main/java/space/jakob/mines/GameView.kt | 126 |
2 files changed, 61 insertions, 86 deletions
diff --git a/app/src/main/java/space/jakob/mines/GameActivity.kt b/app/src/main/java/space/jakob/mines/GameActivity.kt index 2ef2b70..f33be3a 100644 --- a/app/src/main/java/space/jakob/mines/GameActivity.kt +++ b/app/src/main/java/space/jakob/mines/GameActivity.kt @@ -19,6 +19,8 @@ import android.view.GestureDetector import android.view.ScaleGestureDetector import android.view.MotionEvent import android.util.Log +import java.util.Timer +import kotlin.concurrent.schedule class GameActivity : AppCompatActivity() { lateinit var grid : Grid @@ -32,7 +34,6 @@ class GameActivity : AppCompatActivity() { override fun onSingleTapUp(e: MotionEvent): Boolean { val (x, y) = gameView.toGridCoordinates(e.x.toInt(), e.y.toInt()) grid.reveal(x, y) - gameView.invalidate() return false } @@ -40,14 +41,10 @@ class GameActivity : AppCompatActivity() { override fun onLongPress(e: MotionEvent) { val (x, y) = gameView.toGridCoordinates(e.x.toInt(), e.y.toInt()) grid.flag(x, y) - gameView.invalidate() } override fun onScroll(e1: MotionEvent, e2: MotionEvent, dx: Float, dy: Float): Boolean { - with (gameView) { - moveCamera(dx.toDouble(), dy.toDouble()) - invalidate() - } + gameView.moveCamera(dx.toDouble(), dy.toDouble()) return false } } @@ -55,11 +52,8 @@ class GameActivity : AppCompatActivity() { // Implements the callbacks for scaleGestureDetector. val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() { override fun onScale(scaleGestureDetector: ScaleGestureDetector) : Boolean { - with (gameView) { - val factor = scaleGestureDetector.getScaleFactor() - scaleCamera(factor.toDouble()) - invalidate() - } + val factor = scaleGestureDetector.getScaleFactor() + gameView.scaleCamera(factor.toDouble()) return false } } @@ -90,5 +84,10 @@ class GameActivity : AppCompatActivity() { scaleGestureDetector = ScaleGestureDetector(this, scaleListener) gameView.grid = grid + + // TODO: Document regular invalidation. + Timer("Refresh", false).schedule(0, 1000 / 60) { + gameView.invalidate() + } } } diff --git a/app/src/main/java/space/jakob/mines/GameView.kt b/app/src/main/java/space/jakob/mines/GameView.kt index 84877e6..4fb6563 100644 --- a/app/src/main/java/space/jakob/mines/GameView.kt +++ b/app/src/main/java/space/jakob/mines/GameView.kt @@ -31,92 +31,84 @@ enum class AtlasEntry { FLAG, UNKNOWN, MINE, TRIPPED, } +/** + * Linearly interpolate between @param v0 and @param v1. + */ +fun lerp(v0: Double, v1: Double, dt: Double) = v0 + dt * (v1 - v0) + class GameView(context: Context, attrs: AttributeSet) : View(context, attrs) { val atlas = BitmapFactory.decodeResource(context.resources, R.drawable.atlas); - val tileWidth = atlas.width / 3 - val scaledTileWidth: Int - get() = (minOf(width, height) / cameraBreadth).toInt() + // The number of tiles contained within each row of atlas. + val atlasTilesPerRow = 3 - // Reference to GameActivity's grid that should be treated as read-only. - var grid: Grid? = null + // The size, in pixels, of each tile in atlas. + val atlasTileWidth = atlas.width / 3 - // All of the state for maintaining the view's current positioning. For an - // explanation of these values, see `drawGrid`. + // The camera's current position. var cameraX = 0.0 var cameraY = 0.0 - var cameraBreadth = 5.0 - // Properties for scaling cameraBreadth to the screen resolution. - val cameraBreadthX: Double - get() = if (width > height) { - cameraBreadth * width.toDouble() / height.toDouble() - } else { - cameraBreadth - } - val cameraBreadthY: Double - get() = if (height > width) { - cameraBreadth * height.toDouble() / width.toDouble() - } else { - cameraBreadth - } + // The position that the camera will interpolate towards. + var cameraDestX = 0.0 + var cameraDestY = 0.0 + + // Offset values to the center of the screen. + val cameraOffsetX: Double + get() = width / 2.0 + val cameraOffsetY: Double + get() = height / 2.0 + + // The current scale at which to draw tiles. + var cameraScale = 64.0 + + // Reference to GameActivity's grid that should be treated as read-only. + var grid: Grid? = null /** - * Returns the grid coordinates for the tile at the given screen coordinates. + * @return the grid coordinates for the tile at the given screen coordinates. */ fun toGridCoordinates(x: Int, y: Int) = Pair( - // These were derived algebraically from the formula in `drawGrid`. - ((x.toDouble() / scaledTileWidth.toDouble()) - cameraBreadthX / 2 + cameraX).toInt(), - ((y.toDouble() / scaledTileWidth.toDouble()) - cameraBreadthY / 2 + cameraY).toInt() + 0, + 0 ) /** * Moves the camera by (@param dx, @param dy), clamping to grid bounds. */ fun moveCamera(dx: Double, dy: Double) { - grid?.let { - cameraX = (cameraX + dx / 100.0).coerceIn(0.0, (it.width).toDouble()) - cameraY = (cameraY + dy / 100.0).coerceIn(0.0, (it.height).toDouble()) - } + val minimumX = 0.0 + val minimumY = 0.0 + val maximumX = grid?.let { it.width.toDouble() } + val maximumY = grid?.let { it.height.toDouble() } + cameraDestX = (cameraDestX + dx / 64.0).coerceIn(minimumX, maximumX) + cameraDestY = (cameraDestY + dy / 64.0).coerceIn(minimumY, maximumY) } fun scaleCamera(factor: Double) { - val maxBreadth = grid?.width?.toDouble() ?: 0.0 - cameraBreadth = (cameraBreadth / factor).coerceIn(5.0, maxBreadth) } /** - * Flag and @return the tile at (@param x, @param y). + * @return a source Rect into @see atlas for an AtlasEntry. */ - fun flagAt(x: Int, y: Int): Tile? = grid?.let { - - if (it.valid(x, y)) { - it.flag(x, y) - return it[x, y] - } else { - return null - } - } - private fun atlasRect(entry: AtlasEntry): Rect { - val atlasWidth = 3 - - val x = (entry.ordinal % atlasWidth) * tileWidth - val y = (entry.ordinal / atlasWidth) * tileWidth - return Rect(x, y, x + tileWidth, y + tileWidth) + val x = (entry.ordinal % atlasTilesPerRow) * atlasTileWidth + val y = (entry.ordinal / atlasTilesPerRow) * atlasTileWidth + return Rect(x, y, x + atlasTileWidth, y + atlasTileWidth) } private fun drawTile(canvas: Canvas, tile: Tile, x: Int, y: Int) { + // Amount of blank space for padding between tiles. val tileSpacing = 2.0 / 27.0 - val xSpacing = (scaledTileWidth * tileSpacing).toInt() - val ySpacing = (scaledTileWidth * tileSpacing).toInt() + val xSpacing = (cameraScale * tileSpacing).toInt() + val ySpacing = (cameraScale * tileSpacing).toInt() val dst = Rect( x + xSpacing, y + ySpacing, - x + scaledTileWidth - xSpacing, - y + scaledTileWidth - ySpacing + x + cameraScale.toInt() - xSpacing, + y + cameraScale.toInt() - ySpacing ) if (tile.masked) { @@ -146,38 +138,22 @@ class GameView(context: Context, attrs: AttributeSet) : View(context, attrs) { } private fun drawGrid(canvas: Canvas, grid: Grid) { - // The state maintained for drawing the view is a position, (`cameraX`, - // `cameraY`), representing the centroid of a rectangle whose sides are - // of length `cameraBreadthX` and `cameraBreadthY`. Only the tiles - // intersecting this rectangle are drawn. - // - // +============+ - // / | | - // | | | - // | | | - // cameraBreadthY < | x | - // | | \__________ (cameraX, cameraY) - // | | | - // \ | | - // +============+ - // \_____ ______/ - // v - // cameraBreadthX - for (x in 0 until grid.width) { for (y in 0 until grid.height) { - drawTile( - canvas, - grid[x, y], - ((x + cameraBreadthX / 2 - cameraX) * scaledTileWidth).toInt(), - ((y + cameraBreadthY / 2 - cameraY) * scaledTileWidth).toInt() - ) + val screenX = (x - cameraX) * cameraScale + cameraOffsetX + val screenY = (y - cameraY) * cameraScale + cameraOffsetY + drawTile(canvas, grid[x, y], screenX.toInt(), screenY.toInt()) } } } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) + + // TODO: Replace 0.1 with dynamic dt + cameraX = lerp(cameraX, cameraDestX, 0.1) + cameraY = lerp(cameraY, cameraDestY, 0.1) + grid?.let { drawGrid(canvas, it) } |