summaryrefslogtreecommitdiff
path: root/app/src/main/java/space/jakob/mines
diff options
context:
space:
mode:
authorJakob L. Kreuze <jakob@memeware.net>2019-01-12 15:31:19 -0500
committerJakob L. Kreuze <jakob@memeware.net>2019-01-12 15:31:19 -0500
commit335191bf832d749f679f4d88e7b04ce8b24e3803 (patch)
treea714fc6cdfc7d83297d03b29aa6c6a398ba84276 /app/src/main/java/space/jakob/mines
parentef496487ae49a1e98ea3c9de0531ca4716f7cebf (diff)
Use linear interpolation for camera movementHEADmaster
Diffstat (limited to 'app/src/main/java/space/jakob/mines')
-rw-r--r--app/src/main/java/space/jakob/mines/GameActivity.kt21
-rw-r--r--app/src/main/java/space/jakob/mines/GameView.kt126
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)
}