mirror of
https://github.com/jlengrand/korge-samples.git
synced 2026-03-10 08:31:18 +00:00
Ported minesweeper sample (from my old Lunea engine) using Process (similar to DIV Game Studio)
This commit is contained in:
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,6 @@
|
||||
#Sun Feb 16 00:33:26 CET 2020
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
1
sample-minesweeper/.gitignore
vendored
Normal file
1
sample-minesweeper/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
9
sample-minesweeper/build.gradle
Normal file
9
sample-minesweeper/build.gradle
Normal file
@@ -0,0 +1,9 @@
|
||||
// Ported from here: https://github.com/soywiz/lunea/tree/master/samples/busca
|
||||
|
||||
apply plugin: "korge"
|
||||
|
||||
korge {
|
||||
entryPoint = "com.soywiz.korge.samples.minesweeper.main"
|
||||
jvmMainClassName = "com.soywiz.korge.samples.minesweeper.MainKt"
|
||||
id = "com.soywiz.samples.minesweeper"
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
package com.soywiz.korge.samples.minesweeper
|
||||
|
||||
import com.soywiz.klock.*
|
||||
import com.soywiz.korau.sound.*
|
||||
import com.soywiz.korge.html.*
|
||||
import com.soywiz.korge.render.*
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korio.lang.*
|
||||
import com.soywiz.korma.random.*
|
||||
import kotlin.random.*
|
||||
|
||||
// Proceso que se encarga del tablero
|
||||
class Board(
|
||||
parent: Container,
|
||||
val imageset: BmpSlice,
|
||||
val imagenes: List<BmpSlice>,
|
||||
val click: NativeSound,
|
||||
val boom: NativeSound,
|
||||
// Se establecen el ancho, el alto y la cantidad de minas
|
||||
// Características del tablero: ancho, alto, cantidad de minas
|
||||
var bwidth: Int,
|
||||
var bheight: Int,
|
||||
var minas: Int
|
||||
) : Process(parent) {
|
||||
// Matriz con el tablero
|
||||
var board: Array<IntArray> = arrayOf()
|
||||
// Matriz de máscara (indica que partes del tablero están destapadas)
|
||||
var mask: Array<BooleanArray> = arrayOf()
|
||||
// Matriz de marcado (indica que partes del tablero están marcadas como "posible mina") (click derecho)
|
||||
var mark: Array<BooleanArray> = arrayOf()
|
||||
|
||||
// Variables utilizadas para el contador
|
||||
var stime: DateTime = DateTime.EPOCH
|
||||
var tstop: DateTime = DateTime.EPOCH
|
||||
var timeText: Text
|
||||
|
||||
var lastx: Int = 0
|
||||
var lasty: Int = 0
|
||||
|
||||
// Constructor del proceso en el cual se le pasan el ancho, el alto y la cantidad de minas
|
||||
init {
|
||||
// Se crea el texto del contador
|
||||
//timeText = new Text("", 50, 50, Text.Align.center, Text.Align.middle, Color.white, new Font("Arial", 40));
|
||||
//timeText = Text("", 50, 50, Text.Align.center, Text.Align.middle, Color.white, Font.fromResource("font.ttf", 40));
|
||||
val FONT_HEIGHT = 32.0
|
||||
timeText = text("", textSize = FONT_HEIGHT).xy((bwidth * imageset.height) / 2, -FONT_HEIGHT).apply {
|
||||
//format = Html.Format(align = Html.Alignment.CENTER, size = FONT_HEIGHT.toInt())
|
||||
format = Html.Format(align = Html.Alignment.CENTER)
|
||||
}
|
||||
// Se pinta el contador como hijo del tablero
|
||||
//timeText.group.z = this;
|
||||
// Y se actualiza su texto
|
||||
updateTimeText()
|
||||
|
||||
// Se centra el tablero en la pantalla
|
||||
x = Screen.width / 2 - (bwidth * imageset.height) / 2
|
||||
y = Screen.height / 2 - (bheight * imageset.height - 10 - FONT_HEIGHT) / 2
|
||||
|
||||
// Se establecen algunas características del texto, posición, borde y sombra
|
||||
//timeText.shadow = 5;
|
||||
//timeText.border = 1;
|
||||
|
||||
// Se reinicia el tablero
|
||||
clear()
|
||||
}
|
||||
|
||||
// Destructor, aquí se quita el texto cuando se borra el tablero
|
||||
override fun onDestroy() {
|
||||
timeText.removeFromParent()
|
||||
}
|
||||
|
||||
// Devuelve el tiempo actual (en milisegundos)
|
||||
val time: DateTime get() = DateTime.now()
|
||||
|
||||
// Resetea el contador
|
||||
fun resetTimer() {
|
||||
stime = time
|
||||
tstop = DateTime.EPOCH
|
||||
}
|
||||
|
||||
// Para el contador
|
||||
fun stopTimer() {
|
||||
tstop = time
|
||||
}
|
||||
|
||||
// Devuelve el tiempo ha pasado en segundos desde que se inició el contador
|
||||
val elapsed: Int
|
||||
get() = run {
|
||||
var ctime = time
|
||||
if (tstop != DateTime.EPOCH) ctime = tstop
|
||||
return (ctime - stime).seconds.toInt()
|
||||
}
|
||||
|
||||
// Actualiza el texto del contador con el formato %02d:%02d MM:SS
|
||||
fun updateTimeText() {
|
||||
timeText.text = "%02d:%02d".format(elapsed / 60, elapsed % 60)
|
||||
}
|
||||
|
||||
// Función que se encarga de borrar el tablero y crear uno nuevo
|
||||
fun clear() {
|
||||
// Ahora que vamos a borrar un nuevo tablero (y que vamos a crear una nueva partida)
|
||||
// reiniciamos el contador
|
||||
resetTimer()
|
||||
|
||||
// Creamos las matrices con el tablero, la máscara de visión y la máscara de marcado (de posibles minas)
|
||||
board = Array(bheight) { IntArray(bwidth) }
|
||||
mask = Array(bheight) { BooleanArray(bwidth) }
|
||||
mark = Array(bheight) { BooleanArray(bwidth) }
|
||||
|
||||
// Comprobamos que no se intenten colocar mas minas que posiciones hay, evitando así un bucle infinito
|
||||
// en realidad solo se colocan como mucho una cantidad de minas igual a las posiciones del tablero - 1
|
||||
// para que pueda haber partida (si no se ganaría directamente)
|
||||
if (minas > bwidth * bheight - 1) minas = bwidth * bheight - 1
|
||||
|
||||
// Ahora procederemos a colocar las minas en el tablero
|
||||
for (n in 0 until minas) {
|
||||
// Declaramos px, py que utilizaremos para almacenar las posiciones temporales de la mina
|
||||
var px: Int = 0
|
||||
var py: Int = 0
|
||||
do {
|
||||
// Obtenemos una posible posición de la mina
|
||||
px = Random[0, bwidth - 1]
|
||||
py = Random[0, bheight - 1]
|
||||
// Comprobamos si en esa posición hay una mina y estaremos buscando posiciones hasta
|
||||
// que en esa posición no haya mina
|
||||
} while (board[py][px] == 10)
|
||||
|
||||
// Ahora que sabemos que en esa posición no hay mina, colocamos una
|
||||
board[py][px] = 10
|
||||
}
|
||||
|
||||
// Ahora que hemos colocado las minas, vamos a colocar los números alrededor de ellas
|
||||
// Esta es una parte interesante del buscaminas, aquí se colcan los numeros alrededor de las minas
|
||||
|
||||
// Nos recorremos el tablero entero
|
||||
for (y in 0 until bheight) {
|
||||
for (x in 0 until bwidth) {
|
||||
// Comprobamos que en esa posición no haya mina, si hay mina, "pasamos", hacemos un continue y seguimos a la siguiente posición
|
||||
// sin ejecutar lo que viene después
|
||||
if (board[y][x] == 10) continue
|
||||
|
||||
// Ahora vamos a contar las minas que hay alrededor de esta posición (ya que en esta posición no hay mina y es posible que tengamos
|
||||
// que poner un número si tiene alguna mina contigua)
|
||||
var count = 0
|
||||
// Recorremos con x1 € [-1,1], y1 € [-1, 1]
|
||||
for (y1 in -1..+1) {
|
||||
for (x1 in -1..+1) {
|
||||
// Ahora x + x1 y y + y1 tomaran posiciones de la matriz contiguas a la posición actual
|
||||
// empezando por x - 1, y - 1 para acabar en x + 1, y + 1
|
||||
// Comprobamos que la posición esté dentro de la matriz, ya que por ejemplo en la posición 0
|
||||
// la posición 0 - 1, 0 - 1, sería la -1, -1, que no está dentro de la matriz y si no está dentro
|
||||
// de los límites de la matriz, pasamos
|
||||
if (!in_bounds(x + x1, y + y1)) continue
|
||||
// Si en esta posición contigua hay una mina entonces incrementamos el contador
|
||||
if (board[y + y1][x + x1] == 10) count++
|
||||
}
|
||||
}
|
||||
|
||||
// Introducimos en el tablero la nueva imagen (puesto que la imagen con 0 posiciones es la 1 y las siguientes
|
||||
// son 1, 2, 3, 4, 5, 6, 7, 8) ponemos la imagen correspondiente a count + 1
|
||||
board[y][x] = count + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Ahora ya tenemos el tablero preparado
|
||||
}
|
||||
|
||||
// Indica si una posición está dentro de la matriz
|
||||
fun in_bounds(px: Int, py: Int): Boolean {
|
||||
// Si la posición es negativa o si la posición está mas a la derecha del ancho del tablero, devuelve false (no está dentro)
|
||||
if (px < 0 || px >= bwidth) return false
|
||||
// Si ocurre lo mismo con la posición y, también devolvemos false
|
||||
if (py < 0 || py >= bheight) return false
|
||||
// Si no hemos devuelto ya false, quiere decir que la posición si que está dentro del tablero, así que devolvemos true
|
||||
return true
|
||||
}
|
||||
|
||||
var fillpos = 0
|
||||
|
||||
// Rellena una posición (recursivamente; la forma mas clara y sencilla)
|
||||
suspend fun fill(px: Int, py: Int) {
|
||||
if (!in_bounds(px, py)) return
|
||||
if (mask[py][px] || mark[py][px]) return
|
||||
mask[py][px] = true
|
||||
|
||||
if (fillpos % 7 == 0) audio.play(click)
|
||||
frame()
|
||||
fillpos++
|
||||
|
||||
if (board[py][px] != 1) return
|
||||
fill(px - 1, py)
|
||||
fill(px + 1, py)
|
||||
fill(px, py - 1)
|
||||
fill(px, py + 1)
|
||||
fill(px - 1, py - 1)
|
||||
fill(px + 1, py + 1)
|
||||
fill(px + 1, py - 1)
|
||||
fill(px - 1, py + 1)
|
||||
}
|
||||
|
||||
suspend fun show_board_lose() {
|
||||
// Subfunción de show_board_lose que se encarga de
|
||||
// desenmascarar una posición despues de comprobar
|
||||
// si es correcta
|
||||
fun unmask(x: Int, y: Int): Boolean {
|
||||
if (!in_bounds(x, y)) return false
|
||||
mask[y][x] = true
|
||||
return true
|
||||
}
|
||||
|
||||
// Propagación con forma de diamante
|
||||
var dist = 0
|
||||
while (true) {
|
||||
var drawing = false
|
||||
|
||||
for (n in 0..dist) {
|
||||
if (unmask(lastx - n + dist, lasty - n)) drawing = true
|
||||
if (unmask(lastx + n - dist, lasty - n)) drawing = true
|
||||
if (unmask(lastx - n + dist, lasty + n)) drawing = true
|
||||
if (unmask(lastx + n - dist, lasty + n)) drawing = true
|
||||
}
|
||||
|
||||
if (!drawing) break
|
||||
|
||||
dist++
|
||||
frame()
|
||||
//if (dist >= max(width * 2, height * 2)) break;
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun show_board_win() {
|
||||
for (y in 0 until bheight) {
|
||||
for (x in 0 until bwidth) {
|
||||
if (board[y][x] == 10) {
|
||||
mask[y][x] = false
|
||||
mark[y][x] = true
|
||||
frame()
|
||||
} else {
|
||||
mask[y][x] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun check(px: Int, py: Int): Boolean {
|
||||
if (!in_bounds(px, py)) return false
|
||||
|
||||
// Guardamos la última posición en la que hicimos click
|
||||
lastx = px; lasty = py
|
||||
|
||||
// Estamos ante una mina
|
||||
if (board[py][px] == 10) return true
|
||||
|
||||
// Estamos ante una casilla vacía
|
||||
if (board[py][px] == 1) {
|
||||
fps = 140.0
|
||||
fillpos = 0
|
||||
fill(px, py)
|
||||
fps = 60.0
|
||||
return false
|
||||
}
|
||||
|
||||
if (!mask[py][px]) {
|
||||
mask[py][px] = true
|
||||
audio.play(click)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Comprueba si el tablero está en un estado en el cual podemos dar por ganada la partida
|
||||
fun check_win(): Boolean {
|
||||
var count = 0
|
||||
for (y in 0 until bheight) {
|
||||
for (x in 0 until bwidth) {
|
||||
if (mask[y][x]) count++
|
||||
}
|
||||
}
|
||||
|
||||
return (count == bwidth * bheight - minas)
|
||||
}
|
||||
|
||||
// La acción principal redirecciona a la acción de juego
|
||||
override suspend fun main() = action(::play)
|
||||
|
||||
// La acción principal de juego que se encarga de gestionar los clicks de ratón
|
||||
suspend fun play() {
|
||||
while (true) {
|
||||
//println("Mouse.x: ${Mouse.x}, x=$x")
|
||||
if (Mouse.x >= x && Mouse.x < x + bwidth * imageset.height) {
|
||||
if (Mouse.y >= y && Mouse.y < y + bheight * imageset.height) {
|
||||
val px = ((Mouse.x - x) / imageset.height).toInt()
|
||||
val py = ((Mouse.y - y) / imageset.height).toInt()
|
||||
|
||||
if (Mouse.released[0]) {
|
||||
if (!mark[py][px]) {
|
||||
if (check(px, py)) {
|
||||
action(::lose)
|
||||
} else if (check_win()) {
|
||||
action(::win)
|
||||
}
|
||||
}
|
||||
} else if (Mouse.released[1] || Mouse.released[2]) {
|
||||
mark[py][px] = !mark[py][px]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame()
|
||||
}
|
||||
}
|
||||
|
||||
// Acción del tablero que ocurre cuando el jugador ha perdido
|
||||
suspend fun lose() {
|
||||
audio.play(boom, 0)
|
||||
stopTimer()
|
||||
show_board_lose()
|
||||
|
||||
while (true) {
|
||||
if (Mouse.left || Mouse.right) {
|
||||
clear()
|
||||
for (n in 0 until 10) frame()
|
||||
action(::play)
|
||||
}
|
||||
frame()
|
||||
}
|
||||
}
|
||||
|
||||
// Acción del tablero que ocurre cuando el jugador ha ganado
|
||||
suspend fun win() {
|
||||
stopTimer()
|
||||
show_board_win()
|
||||
|
||||
while (true) {
|
||||
if (Mouse.left || Mouse.right) {
|
||||
clear()
|
||||
for (n in 0 until 10) frame()
|
||||
action(::play)
|
||||
}
|
||||
frame()
|
||||
}
|
||||
}
|
||||
|
||||
val images = Array(bheight) { py ->
|
||||
Array(bwidth) { px ->
|
||||
image(Bitmaps.transparent).xy(px * imageset.height, py * imageset.height).scale(0.9)
|
||||
}
|
||||
}
|
||||
|
||||
override fun renderInternal(ctx: RenderContext) {
|
||||
for (py in 0 until bheight) {
|
||||
for (px in 0 until bwidth) {
|
||||
val image = if (!mask[py][px]) {
|
||||
imagenes[if (mark[py][px]) 11 else 0]
|
||||
} else {
|
||||
imagenes[board[py][px]]
|
||||
}
|
||||
|
||||
images[py][px].texture = image
|
||||
}
|
||||
}
|
||||
|
||||
super.renderInternal(ctx)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package com.soywiz.korge.samples.minesweeper
|
||||
|
||||
import com.soywiz.kds.*
|
||||
import com.soywiz.klock.*
|
||||
import com.soywiz.kmem.*
|
||||
import com.soywiz.korau.sound.*
|
||||
import com.soywiz.korev.*
|
||||
import com.soywiz.korev.KeysEvents
|
||||
import com.soywiz.korge.component.*
|
||||
import com.soywiz.korge.input.*
|
||||
import com.soywiz.korge.time.*
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.format.*
|
||||
import com.soywiz.korio.async.*
|
||||
import com.soywiz.korio.file.std.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.reflect.*
|
||||
|
||||
abstract class Process(parent: Container) : Container() {
|
||||
init {
|
||||
parent.addChild(this)
|
||||
}
|
||||
|
||||
override val stage: Stage get() = super.stage!!
|
||||
val views: Views get() = stage.views
|
||||
val type: KClass<out View> get() = this::class
|
||||
var fps: Double = 60.0
|
||||
|
||||
val key get() = stage.views.key
|
||||
|
||||
val Mouse get() = views.mouseV
|
||||
val Screen get() = views.screenV
|
||||
val audio get() = views.audioV
|
||||
|
||||
var angle: Double
|
||||
get() = rotationDegrees
|
||||
set(value) = run { rotationDegrees = value }
|
||||
|
||||
suspend fun frame() {
|
||||
//delayFrame()
|
||||
delay((1.0 / fps).seconds)
|
||||
}
|
||||
|
||||
fun action(action: KSuspendFunction0<Unit>) {
|
||||
throw ChangeActionException(action)
|
||||
}
|
||||
|
||||
abstract suspend fun main()
|
||||
|
||||
private lateinit var job: Job
|
||||
|
||||
fun destroy() {
|
||||
removeFromParent()
|
||||
}
|
||||
|
||||
open fun onDestroy() {
|
||||
}
|
||||
|
||||
class ChangeActionException(val action: KSuspendFunction0<Unit>) : Exception()
|
||||
|
||||
init {
|
||||
job = views.launchAsap {
|
||||
var action = ::main
|
||||
while (true) {
|
||||
try {
|
||||
action()
|
||||
break
|
||||
} catch (e: ChangeActionException) {
|
||||
action = e.action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addComponent(object : StageComponent {
|
||||
override val view: View = this@Process
|
||||
|
||||
override fun added(views: Views) {
|
||||
//println("added: $views")
|
||||
}
|
||||
|
||||
override fun removed(views: Views) {
|
||||
//println("removed: $views")
|
||||
job.cancel()
|
||||
onDestroy()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun collision(type: KClass<out View>): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
|
||||
class extraPropertyFixed<T : Any?>(val name: String? = null, val default: () -> T) {
|
||||
inline operator fun getValue(thisRef: Extra, property: KProperty<*>): T {
|
||||
if (thisRef.extra == null) thisRef.extra = LinkedHashMap()
|
||||
return (thisRef.extra!!.getOrPut(name ?: property.name) { default() } as T)
|
||||
}
|
||||
|
||||
inline operator fun setValue(thisRef: Extra, property: KProperty<*>, value: T): Unit = run {
|
||||
if (thisRef.extra == null) thisRef.extra = LinkedHashMap()
|
||||
thisRef.extra!![name ?: property.name] = value as Any?
|
||||
}
|
||||
}
|
||||
|
||||
private val Views.componentsInStagePrev by extraPropertyFixed { linkedSetOf<StageComponent>() }
|
||||
private val Views.componentsInStageCur by extraPropertyFixed { linkedSetOf<StageComponent>() }
|
||||
private val Views.componentsInStage by extraPropertyFixed { linkedSetOf<StageComponent>() }
|
||||
private val Views.tempComponents2 by extraPropertyFixed { arrayListOf<Component>() }
|
||||
|
||||
fun Views.registerStageComponent() {
|
||||
onBeforeRender {
|
||||
componentsInStagePrev.clear()
|
||||
componentsInStagePrev += componentsInStageCur
|
||||
componentsInStageCur.clear()
|
||||
stage.forEachComponent<StageComponent>(tempComponents2) {
|
||||
componentsInStageCur += it
|
||||
//println("DEMO: $it -- $componentsInStage")
|
||||
if (it !in componentsInStage) {
|
||||
componentsInStage += it
|
||||
it.added(views)
|
||||
}
|
||||
}
|
||||
for (it in componentsInStagePrev) {
|
||||
if (it !in componentsInStageCur) {
|
||||
it.removed(views)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component with [added] and [removed] methods that are executed
|
||||
* once the view is going to be displayed, and when the view has been removed
|
||||
*/
|
||||
interface StageComponent : Component {
|
||||
fun added(views: Views)
|
||||
fun removed(views: Views)
|
||||
}
|
||||
|
||||
class Key2(val views: Views) {
|
||||
operator fun get(key: Key): Boolean = views.keysPressed[key] == true
|
||||
}
|
||||
|
||||
|
||||
class MouseV(val views: Views) {
|
||||
val left: Boolean get() = pressing[0]
|
||||
val right: Boolean get() = pressing[1] || pressing[2]
|
||||
val x: Int get() = (views.input.mouse.x / views.stage.scaleX).toInt()
|
||||
val y: Int get() = (views.input.mouse.y / views.stage.scaleY).toInt()
|
||||
val pressing = BooleanArray(8)
|
||||
val pressed = BooleanArray(8)
|
||||
val released = BooleanArray(8)
|
||||
val _pressed = BooleanArray(8)
|
||||
val _released = BooleanArray(8)
|
||||
}
|
||||
|
||||
class ScreenV(val views: Views) {
|
||||
val width: Double get() = views.virtualWidth.toDouble()
|
||||
val height: Double get() = views.virtualHeight.toDouble()
|
||||
}
|
||||
|
||||
class AudioV(val views: Views) {
|
||||
fun play(sound: NativeSound, repeat: Int = 0) {
|
||||
}
|
||||
}
|
||||
|
||||
val Views.keysPressed by extraPropertyFixed { LinkedHashMap<Key, Boolean>() }
|
||||
val Views.key by Extra.PropertyThis<Views, Key2> { Key2(this) }
|
||||
|
||||
val Views.mouseV by Extra.PropertyThis<Views, MouseV> { MouseV(this) }
|
||||
val Views.screenV by Extra.PropertyThis<Views, ScreenV> { ScreenV(this) }
|
||||
val Views.audioV by Extra.PropertyThis<Views, AudioV> { AudioV(this) }
|
||||
|
||||
fun Views.registerProcessSystem() {
|
||||
registerStageComponent()
|
||||
|
||||
stage.addEventListener<MouseEvent> { e ->
|
||||
when (e.type) {
|
||||
MouseEvent.Type.MOVE -> Unit
|
||||
MouseEvent.Type.DRAG -> Unit
|
||||
MouseEvent.Type.UP -> {
|
||||
mouseV.pressing[e.button.id] = false
|
||||
mouseV._released[e.button.id] = true
|
||||
}
|
||||
MouseEvent.Type.DOWN -> {
|
||||
mouseV.pressing[e.button.id] = true
|
||||
mouseV._pressed[e.button.id] = true
|
||||
}
|
||||
MouseEvent.Type.CLICK -> Unit
|
||||
MouseEvent.Type.ENTER -> Unit
|
||||
MouseEvent.Type.EXIT -> Unit
|
||||
MouseEvent.Type.SCROLL -> Unit
|
||||
}
|
||||
}
|
||||
// @TODO: Use onAfterRender
|
||||
onBeforeRender {
|
||||
arraycopy(mouseV._released, 0, mouseV.released, 0, 8)
|
||||
arraycopy(mouseV._pressed, 0, mouseV.pressed, 0, 8)
|
||||
|
||||
mouseV._released.fill(false)
|
||||
mouseV._pressed.fill(false)
|
||||
}
|
||||
stage.addEventListener<KeyEvent> { e ->
|
||||
keysPressed[e.key] = e.type == KeyEvent.Type.DOWN
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun readImage(path: String) = resourcesVfs[path].readBitmapSlice()
|
||||
suspend fun readSound(path: String) = resourcesVfs[path].readNativeSoundOptimized()
|
||||
|
||||
// @TODO: Move to KorIM
|
||||
fun <T : Bitmap> BitmapSlice<T>.split(width: Int, height: Int): List<BmpSlice> {
|
||||
val self = this
|
||||
val nheight = self.height / height
|
||||
val nwidth = self.width / width
|
||||
return arrayListOf<BmpSlice>().apply {
|
||||
for (y in 0 until nheight) {
|
||||
for (x in 0 until nwidth) {
|
||||
add(self.sliceWithSize(x * width, y * height, width, height))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.soywiz.korge.samples.minesweeper
|
||||
|
||||
import com.soywiz.korge.view.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korma.random.*
|
||||
import kotlin.math.*
|
||||
import kotlin.random.*
|
||||
|
||||
class RandomLight(
|
||||
parent: Container,
|
||||
light: BmpSlice
|
||||
) : Process(parent) {
|
||||
var w2: Double = stage.width / 2
|
||||
var h2: Double = stage.height / 2
|
||||
val random = Random
|
||||
var sx: Double = 0.0
|
||||
var sy: Double = 0.0
|
||||
var inca: Double = 0.0
|
||||
var incs: Double = 0.0
|
||||
var excx: Double = 0.0
|
||||
var excy: Double = 0.0
|
||||
|
||||
init {
|
||||
image(light, 0.5, 0.5).apply {
|
||||
this.blendMode = BlendMode.ADD
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun main() {
|
||||
sx = random[-w2, w2]
|
||||
sy = random[-h2, h2]
|
||||
inca = random[0.0001, 0.03]
|
||||
incs = random[0.5, 2.0]
|
||||
excx = random[0.7, 1.3]
|
||||
excy = random[0.7, 1.3]
|
||||
alpha = random[0.4, 0.7]
|
||||
alpha = 0.1
|
||||
|
||||
while (true) {
|
||||
angle += inca
|
||||
x = w2 - cos(angle) * w2 * excx + sx
|
||||
y = h2 - sin(angle) * h2 * excy + sy
|
||||
scale = 1 + (cos(angle) / 6) * incs
|
||||
|
||||
// Comprueba si una esfera de luz ha chocado con otra
|
||||
// El sistema de colisión por defecto es inner circle
|
||||
if (this.collision(this.type)) {
|
||||
if (alpha <= 0.8) alpha += 0.01
|
||||
} else {
|
||||
if (alpha >= 0.1) alpha -= 0.01
|
||||
}
|
||||
|
||||
frame()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fadeout() {
|
||||
while (alpha > 0) {
|
||||
alpha -= 0.1
|
||||
frame()
|
||||
}
|
||||
}
|
||||
|
||||
fun draw() {
|
||||
//graph.draw(__x, __y, alpha, 0, size, alpha, alpha, alpha, GL_ONE, GL_ONE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.soywiz.korge.samples.minesweeper
|
||||
|
||||
import com.soywiz.korev.*
|
||||
import com.soywiz.korge.*
|
||||
import com.soywiz.korge.view.*
|
||||
|
||||
// Ported from here: https://github.com/soywiz/lunea/tree/master/samples/busca
|
||||
|
||||
suspend fun main() = Korge(width = 800, height = 600, virtualWidth = 640, virtualHeight = 480, title = "Minesweeper") {
|
||||
views.registerProcessSystem()
|
||||
MainProcess(this)
|
||||
}
|
||||
|
||||
class MainProcess(parent: Container) : Process(parent) {
|
||||
val lights = arrayListOf<RandomLight>()
|
||||
|
||||
override suspend fun main() {
|
||||
val light = readImage("light.png")
|
||||
image(readImage("bg.jpg"))
|
||||
for (n in 0 until 20) {
|
||||
lights += RandomLight(this, light)
|
||||
}
|
||||
|
||||
val imageset = readImage("buscaminas.png")
|
||||
val imagenes = imageset.split(imageset.height, imageset.height)
|
||||
val click = readSound("click.wav")
|
||||
val boom = readSound("boom.wav")
|
||||
|
||||
val board = Board(this, imageset, imagenes, click, boom, 22, 15, 40)
|
||||
|
||||
while (true) {
|
||||
if (key[Key.ESCAPE]) {
|
||||
error("ESC!")
|
||||
}
|
||||
if (key[Key.UP]) {
|
||||
lights += RandomLight(this, light)
|
||||
}
|
||||
if (key[Key.DOWN]) {
|
||||
if (lights.isNotEmpty()) {
|
||||
lights.removeAt(lights.size - 1).destroy()
|
||||
}
|
||||
}
|
||||
board.updateTimeText()
|
||||
frame()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
sample-minesweeper/src/commonMain/resources/bg.jpg
Normal file
BIN
sample-minesweeper/src/commonMain/resources/bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
BIN
sample-minesweeper/src/commonMain/resources/bomba.ico
Normal file
BIN
sample-minesweeper/src/commonMain/resources/bomba.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
sample-minesweeper/src/commonMain/resources/boom.wav
Normal file
BIN
sample-minesweeper/src/commonMain/resources/boom.wav
Normal file
Binary file not shown.
BIN
sample-minesweeper/src/commonMain/resources/buscaminas.png
Normal file
BIN
sample-minesweeper/src/commonMain/resources/buscaminas.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
BIN
sample-minesweeper/src/commonMain/resources/click.wav
Normal file
BIN
sample-minesweeper/src/commonMain/resources/click.wav
Normal file
Binary file not shown.
BIN
sample-minesweeper/src/commonMain/resources/font.ttf
Normal file
BIN
sample-minesweeper/src/commonMain/resources/font.ttf
Normal file
Binary file not shown.
BIN
sample-minesweeper/src/commonMain/resources/light.png
Normal file
BIN
sample-minesweeper/src/commonMain/resources/light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Reference in New Issue
Block a user