diff --git a/android/build.gradle b/android/build.gradle index 48ebb90..fb42526 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -32,8 +32,8 @@ android { applicationId 'com.andrzejn.chainrelations' minSdkVersion 19 targetSdkVersion 30 - versionCode 6 - versionName '1.5' + versionCode 7 + versionName '1.6' multiDexEnabled true } compileOptions { diff --git a/assets/Main.atlas b/assets/Main.atlas index 5ab2612..823b71c 100644 --- a/assets/Main.atlas +++ b/assets/Main.atlas @@ -86,12 +86,12 @@ icongithub index: -1 darktheme rotate: false - xy: 570, 665 + xy: 570, 675 size: 150, 150 index: -1 lighttheme rotate: false - xy: 570, 815 + xy: 570, 825 size: 150, 150 index: -1 logo @@ -154,3 +154,13 @@ playblue xy: 220, 580 size: 100,100 index: -1 +ghost + rotate: false + xy: 570, 370 + size: 150,150 + index: -1 +balloon + rotate: false + xy: 570, 520 + size: 150,150 + index: -1 diff --git a/assets/atlas.png b/assets/atlas.png index 6d5015d..1c4503d 100644 Binary files a/assets/atlas.png and b/assets/atlas.png differ diff --git a/build.gradle b/build.gradle index 5e221d9..43a1f4b 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ allprojects { apply plugin: 'eclipse' apply plugin: 'idea' - version = '1.5' + version = '1.6' ext { appName = 'ChainRelations' } diff --git a/core/src/com/andrzejn/chainrelations/Context.kt b/core/src/com/andrzejn/chainrelations/Context.kt index 352bb82..d5d86cb 100644 --- a/core/src/com/andrzejn/chainrelations/Context.kt +++ b/core/src/com/andrzejn/chainrelations/Context.kt @@ -173,6 +173,8 @@ class Context( val menu: TextureRegion get() = texture("menu") val resume: TextureRegion get() = texture("resume") val playblue: TextureRegion get() = texture("playblue") + val ghost: TextureRegion get() = texture("ghost") + val balloon: TextureRegion get() = texture("balloon") /** * Create a bitmap font with given size, base color etc. from the provided TrueType font. diff --git a/core/src/com/andrzejn/chainrelations/GameScreen.kt b/core/src/com/andrzejn/chainrelations/GameScreen.kt index c2b05da..adcfaf2 100644 --- a/core/src/com/andrzejn/chainrelations/GameScreen.kt +++ b/core/src/com/andrzejn/chainrelations/GameScreen.kt @@ -5,6 +5,7 @@ import aurelienribon.tweenengine.Tween import com.andrzejn.chainrelations.helper.TW_POS_XY import com.andrzejn.chainrelations.logic.* import com.badlogic.gdx.Gdx +import com.badlogic.gdx.Gdx.graphics import com.badlogic.gdx.Gdx.input import com.badlogic.gdx.Input import com.badlogic.gdx.InputAdapter @@ -13,6 +14,7 @@ import com.badlogic.gdx.graphics.g2d.Sprite import com.badlogic.gdx.math.Vector2 import ktx.app.KtxScreen import ktx.math.minus +import kotlin.random.Random import java.util.* /** @@ -33,7 +35,7 @@ class GameScreen( /** * Set to true on first show call. Used by the Home/Settings screen to determone what to do on the Back button */ - var wasDisplayed = false + var wasDisplayed: Boolean = false private val ball = Sprite(ctx.ball) private val play = Sprite(ctx.play).also { it.setAlpha(0.8f) } @@ -44,6 +46,14 @@ class GameScreen( private val hit = Sprite(ctx.hit).also { it.setAlpha(0.8f) } private val hand = Sprite(ctx.hand).also { it.setAlpha(0.6f) } private val menu = Sprite(ctx.menu).also { it.setAlpha(0.8f) } + private val ghost = Sprite(ctx.ghost).apply { + setAlpha(0.7f) + setPosition(-1000f, -1000f) + } + private val balloon = Sprite(ctx.balloon).apply { + setAlpha(0.7f) + setPosition(-1000f, -1000f) + } /** * The input adapter instance for this screen @@ -57,7 +67,7 @@ class GameScreen( // Initialize WorldConstants before creating the World ctx.wc = WorldConstants(ctx.gs.ballsCount).also { it.setValues( - Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat() + graphics.width.toFloat(), graphics.height.toFloat() ) } } @@ -95,7 +105,7 @@ class GameScreen( wasDisplayed = true input.inputProcessor = ia timeStart = Calendar.getInstance().timeInMillis - Gdx.graphics.isContinuousRendering = true + graphics.isContinuousRendering = true } /** @@ -115,16 +125,10 @@ class GameScreen( play.setBounds(width - 10f - 2 * buttonSize, fontHeight + 8 * buttonSize + 10, 2 * buttonSize, 2 * buttonSize) playblue.setBounds( - width - 10f - 2 * buttonSize, - fontHeight + 8 * buttonSize + 10, - 2 * buttonSize, - 2 * buttonSize + width - 10f - 2 * buttonSize, fontHeight + 8 * buttonSize + 10, 2 * buttonSize, 2 * buttonSize ) settings.setBounds( - width - 10f - 2 * buttonSize, - fontHeight + 5 * buttonSize + 10, - 2 * buttonSize, - 2 * buttonSize + width - 10f - 2 * buttonSize, fontHeight + 5 * buttonSize + 10, 2 * buttonSize, 2 * buttonSize ) exit.setBounds(width - 10f - 2 * buttonSize, fontHeight + 2 * buttonSize + 10, 2 * buttonSize, 2 * buttonSize) @@ -192,6 +196,8 @@ class GameScreen( private var isMenuDisplayed = false + private var drawGhost = false + /** * Invoked on each screen rendering. Recalculates ball moves, invokes timer actions and draws rthe screen. */ @@ -253,27 +259,30 @@ class GameScreen( help.draw(ctx.batch) hit.draw(ctx.batch) ctx.score.draw(ctx.batch) - if (inShowAMove) - hand.draw(ctx.batch) + if (noMoreBalls()) + ctx.sd.filledRectangle(menu.x - 5, menu.y - 5, menu.width + 10, menu.height + 10, ctx.theme.scorePoints) + if (inShowAMove) hand.draw(ctx.batch) else if (isMenuDisplayed) { ctx.sd.filledRectangle( - exit.x - 10, - exit.y - 10, - exit.width + 20, - exit.height * 4 + 20, - ctx.theme.gameBorders + exit.x - 10, exit.y - 10, exit.width + 20, exit.height * 4 + 20, ctx.theme.gameBorders ) - if (world.balls.size <= 6) - play.draw(ctx.batch) - else - playblue.draw(ctx.batch) + if (noMoreBalls()) play.draw(ctx.batch) else playblue.draw(ctx.batch) settings.draw(ctx.batch) exit.draw(ctx.batch) } menu.draw(ctx.batch) + if (drawGhost) { + ghost.draw(ctx.batch) + balloon.draw(ctx.batch) + } if (ctx.batch.isDrawing) ctx.batch.end() } + /** + * Minimum ball size reached + */ + private fun noMoreBalls() = world.balls.size <= 6 + /** * Ensures the ball is fully visible */ @@ -343,8 +352,7 @@ class GameScreen( hand.setPosition( help.x + help.width / 2 - hand.width / 2, help.y + help.height / 2 - hand.height ) - }) - .push(Tween.to(hand, TW_POS_XY, 1f).target(dF.ball.coord.x - hand.width / 2, dF.ball.coord.y - hand.height)) + }).push(Tween.to(hand, TW_POS_XY, 1f).target(dF.ball.coord.x - hand.width / 2, dF.ball.coord.y - hand.height)) .push(Tween.call { _, _ -> pointTheBall(dF.ball) }).setCallback { _, _ -> showAMoveMiddle(dF) } .start(ctx.tweenManager) } @@ -378,7 +386,7 @@ class GameScreen( ).pushPause(0.2f).push(Tween.call { _, _ -> val dF = dragFrom if (dF != null) { - world.addConnector(dF, dT) + world.addConnector(dF, dT) { endOfGameAnimation() } thereWasAMove = true } cleanDragState(true) @@ -458,7 +466,7 @@ class GameScreen( setDragTo(v) val otherBall = world.ballPointedBy(dragTo) if (otherBall != null && suitableTargets?.contains(otherBall) == true) { - world.addConnector(dF, otherBall) + world.addConnector(dF, otherBall) { endOfGameAnimation() } thereWasAMove = true } } @@ -507,4 +515,18 @@ class GameScreen( dragTo = v.sub(dragFromCoord).clamp(0f, maxConnLen * ctx.wc.radius).add(dragFromCoord) } } + + private fun endOfGameAnimation() { + if (noMoreBalls()) { + drawGhost = true + val sprite = (if (Random.nextFloat() < 0.5f) ghost else balloon).apply { + setSize(ctx.wc.radius * 3, ctx.wc.radius * 3) + setOriginCenter() + setPosition((graphics.width - width) / 2f, 0f) + } + Tween.to(sprite, TW_POS_XY, 3f) + .target((graphics.width - sprite.width) / 2f, graphics.height.toFloat()) + .setCallback { _, _ -> drawGhost = false }.start(ctx.tweenManager) + } + } } diff --git a/core/src/com/andrzejn/chainrelations/logic/World.kt b/core/src/com/andrzejn/chainrelations/logic/World.kt index 42e79d1..d22a664 100644 --- a/core/src/com/andrzejn/chainrelations/logic/World.kt +++ b/core/src/com/andrzejn/chainrelations/logic/World.kt @@ -108,7 +108,7 @@ class World( /** * Add the connector from the socket to the ball. Updates scores, triggers death of fully connected balls if any */ - fun addConnector(from: BaseSocket, otherBall: Ball): Boolean { + fun addConnector(from: BaseSocket, otherBall: Ball, deathCallback: () -> Unit): Boolean { val fromBall = from.ball val con = if (from is InSocket) { val outSocket = otherBall.outSock.firstOrNull { it.conn == null && it.color == from.color } ?: return false @@ -143,7 +143,10 @@ class World( .end() .setCallback { _, _ -> b.inDeath = false } } else - seq.setCallback { _, _ -> balls.remove(b) } + seq.setCallback { _, _ -> + balls.remove(b) + deathCallback() + } seq.start(ctx.tweenManager) } ballsToClear.flatMap { it.sockets }.mapNotNull { it.conn }.toMutableSet().also { it.add(con) }.forEach {