// Mines is free software: you can redistribute it and/or modify it under the // terms of the GNU General Public License as published by the Free Software // Foundation, either version 3 of the License, or (at your option) any later // version. // Mines is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR // A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with // Mines. If not, see . package space.jakob.mines import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Rect import android.graphics.Paint import android.util.AttributeSet import android.view.View import android.util.Log import kotlin.math.ceil import kotlin.math.floor enum class AtlasEntry { BLANK, MASKED, UNMASKED, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, 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); // The number of tiles contained within each row of atlas. val atlasTilesPerRow = 3 // The size, in pixels, of each tile in atlas. val atlasTileWidth = atlas.width / 3 // The camera's current position. var cameraX = 0.0 var cameraY = 0.0 // 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 /** * @return the grid coordinates for the tile at the given screen coordinates. */ fun toGridCoordinates(x: Int, y: Int) = Pair( 0, 0 ) /** * Moves the camera by (@param dx, @param dy), clamping to grid bounds. */ fun moveCamera(dx: Double, dy: Double) { 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) { } /** * @return a source Rect into @see atlas for an AtlasEntry. */ private fun atlasRect(entry: AtlasEntry): Rect { 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 = (cameraScale * tileSpacing).toInt() val ySpacing = (cameraScale * tileSpacing).toInt() val dst = Rect( x + xSpacing, y + ySpacing, x + cameraScale.toInt() - xSpacing, y + cameraScale.toInt() - ySpacing ) if (tile.masked) { canvas.drawBitmap(atlas, atlasRect(AtlasEntry.MASKED), dst, null) if (tile.flagged) { canvas.drawBitmap(atlas, atlasRect(AtlasEntry.FLAG), dst, null) } } else { canvas.drawBitmap(atlas, atlasRect(AtlasEntry.UNMASKED), dst, null) val overlay = when { tile.mine -> AtlasEntry.MINE tile.adjacentMines == 1 -> AtlasEntry.ONE tile.adjacentMines == 2 -> AtlasEntry.TWO tile.adjacentMines == 3 -> AtlasEntry.THREE tile.adjacentMines == 4 -> AtlasEntry.FOUR tile.adjacentMines == 5 -> AtlasEntry.FIVE tile.adjacentMines == 6 -> AtlasEntry.SIX tile.adjacentMines == 7 -> AtlasEntry.SEVEN tile.adjacentMines == 8 -> AtlasEntry.EIGHT else -> AtlasEntry.BLANK } canvas.drawBitmap(atlas, atlasRect(overlay), dst, null) } } private fun drawGrid(canvas: Canvas, grid: Grid) { for (x in 0 until grid.width) { for (y in 0 until grid.height) { 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) } } }