diff options
Diffstat (limited to 'app/src/main/java/space/jakob/mines/GameView.kt')
| -rw-r--r-- | app/src/main/java/space/jakob/mines/GameView.kt | 126 |
1 files changed, 51 insertions, 75 deletions
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) } |