Refactor effects to use the Factory pattern (#43)

This shows several advantages:
- No need for several switchs with hardcoded names.
- Easier and better control per theme (name, price…).
- Easier to keep track of available effects and their order.
This commit is contained in:
andidevi 2018-02-05 10:23:12 +01:00 committed by Lonami
parent 3429eda94c
commit d7db9b8f82
19 changed files with 694 additions and 516 deletions

View file

@ -1,142 +0,0 @@
/*
1010! Klooni, a free customizable puzzle game for Android and Desktop
Copyright (C) 2017 Lonami Exo | LonamiWebs
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.lonamiwebs.klooni;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.effects.EvaporateEffect;
import io.github.lonamiwebs.klooni.effects.ExplodeEffect;
import io.github.lonamiwebs.klooni.effects.IEffect;
import io.github.lonamiwebs.klooni.effects.SpinEffect;
import io.github.lonamiwebs.klooni.effects.VanishEffect;
import io.github.lonamiwebs.klooni.effects.WaterdropEffect;
import io.github.lonamiwebs.klooni.game.Cell;
public class Effect {
//region Members
private final int effectId;
private final Sound effectSound;
public final String name;
public final int price;
//endregion
//region Constructor
// This method will load the sound "sound/effect_{effectName}.mp3"
public Effect(final String effectName) {
this(effectName, effectNameToInt(effectName));
}
public Effect(final String effectName, final int id) {
name = effectName;
effectId = id;
FileHandle soundFile = Gdx.files.internal("sound/effect_" + name + ".mp3");
if (!soundFile.exists())
soundFile = Gdx.files.internal("sound/effect_vanish.mp3");
effectSound = Gdx.audio.newSound(soundFile);
price = effectId == 0 ? 0 : 200; // TODO For now they're all 200 coins except the first
}
//endregion
//region Public methods
public void playSound() {
effectSound.play(MathUtils.random(0.7f, 1f), MathUtils.random(0.8f, 1.2f), 0);
}
public String getDisplay() {
// TODO For now, uppercase the first letter
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
//endregion
//region Static methods
public static Effect[] getEffects() {
int id = 0;
return new Effect[] {
new Effect("vanish", id++),
new Effect("waterdrop", id++),
new Effect("evaporate", id++),
new Effect("spin", id++),
new Effect("explode", id++)
};
}
//endregion
//region Name <-> ID <-> IEffect
// Effects used when clearing a row
public IEffect create(final Cell deadCell, final Vector2 culprit) {
final IEffect effect;
switch (effectId) {
default:
case 0:
effect = new VanishEffect();
break;
case 1:
effect = new WaterdropEffect();
break;
case 2:
effect = new EvaporateEffect();
break;
case 3:
effect = new SpinEffect();
break;
case 4:
effect = new ExplodeEffect();
break;
}
effect.setInfo(deadCell, culprit);
return effect;
}
private static int effectNameToInt(final String name) {
// String comparision is more expensive compared to a single integer one,
// and when creating instances of a lot of effects it's better if we can
// save some processor cycles.
if (name.equals("vanish")) return 0;
if (name.equals("waterdrop")) return 1;
if (name.equals("evaporate")) return 2;
if (name.equals("spin")) return 3;
if (name.equals("explode")) return 4;
return -1;
}
//endregion
//region Disposal
void dispose() {
effectSound.dispose();
}
//endregion
}

View file

@ -22,18 +22,59 @@ import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import java.util.HashMap;
import java.util.Map;
import io.github.lonamiwebs.klooni.effects.EvaporateEffectFactory;
import io.github.lonamiwebs.klooni.effects.ExplodeEffectFactory;
import io.github.lonamiwebs.klooni.effects.SpinEffectFactory;
import io.github.lonamiwebs.klooni.effects.VanishEffectFatory;
import io.github.lonamiwebs.klooni.effects.WaterdropEffectFactory;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
import io.github.lonamiwebs.klooni.screens.MainMenuScreen;
import io.github.lonamiwebs.klooni.screens.TransitionScreen;
public class Klooni extends Game {
// region Effects
// ordered list of effects. index 0 will get default if VanishEffectFactory is removed from list
public final static IEffectFactory[] EFFECTS = {
new VanishEffectFatory(),
new WaterdropEffectFactory(),
new EvaporateEffectFactory(),
new SpinEffectFactory(),
new ExplodeEffectFactory(),
};
private Map<String, Sound> effectSounds;
private void loadEffectSound(final String effectName) {
FileHandle soundFile = Gdx.files.internal("sound/effect_" + effectName + ".mp3");
if (!soundFile.exists())
soundFile = Gdx.files.internal("sound/effect_vanish.mp3");
effectSounds.put(effectName, Gdx.audio.newSound(soundFile));
}
public void playEffectSound() {
effectSounds.get(effect.getName())
.play(MathUtils.random(0.7f, 1f), MathUtils.random(0.8f, 1.2f), 0);
}
// endregion
//region Members
// TODO Not sure whether the theme should be static or not since it might load textures
// FIXME theme should NOT be static as it might load textures which will expose it to the race condition iff GDX got initialized before or not
public static Theme theme;
public Effect effect;
public IEffectFactory effect;
public Skin skin;
@ -74,13 +115,21 @@ public class Klooni extends Game {
Gdx.input.setCatchBackKey(true); // To show the pause menu
setScreen(new MainMenuScreen(this));
effect = new Effect(prefs.getString("effectName", "vanish"));
String effectName = prefs.getString("effectName", "vanish");
effectSounds = new HashMap<String, Sound>(EFFECTS.length);
effect = EFFECTS[0];
for (IEffectFactory e : EFFECTS) {
loadEffectSound(e.getName());
if (e.getName().equals(effectName)) {
effect = e;
}
}
}
//endregion
//region Screen
// TransitionScreen will also dispose by default the previous screen
public void transitionTo(Screen screen) {
transitionTo(screen, true);
@ -99,7 +148,12 @@ public class Klooni extends Game {
super.dispose();
skin.dispose();
theme.dispose();
effect.dispose();
if (effectSounds != null) {
for (Sound s : effectSounds.values()) {
s.dispose();
}
effectSounds = null;
}
}
//endregion
@ -183,40 +237,40 @@ public class Klooni extends Game {
}
// Effects related
public static boolean isEffectBought(Effect effect) {
if (effect.price == 0)
public static boolean isEffectBought(IEffectFactory effect) {
if (effect.getPrice() == 0)
return true;
String[] effects = prefs.getString("boughtEffects", "").split(":");
for (String e : effects)
if (e.equals(effect.name))
if (e.equals(effect.getName()))
return true;
return false;
}
public static boolean buyEffect(Effect effect) {
public static boolean buyEffect(IEffectFactory effect) {
final float money = getRealMoney();
if (effect.price > money)
if (effect.getPrice() > money)
return false;
setMoney(money - effect.price);
setMoney(money - effect.getPrice());
String bought = prefs.getString("boughtEffects", "");
if (bought.equals(""))
bought = effect.name;
bought = effect.getName();
else
bought += ":" + effect.name;
bought += ":" + effect.getName();
prefs.putString("boughtEffects", bought);
return true;
}
public void updateEffect(Effect newEffect) {
prefs.putString("effectName", newEffect.name).flush();
public void updateEffect(IEffectFactory newEffect) {
prefs.putString("effectName", newEffect.getName()).flush();
// Create a new effect, since the one passed through the parameter may dispose later
effect = new Effect(newEffect.name);
effect = newEffect;
}
// Money related

View file

@ -33,6 +33,7 @@ public class SkinLoader {
private final static float bestMultiplier;
// FIXME this static code is exposed to a race condition and will fail if called class gets loaded before execution of Klooni.create
static {
// Use the height to determine the best match
// We cannot use a size which is over the device height,

View file

@ -21,19 +21,19 @@ import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Rectangle;
import io.github.lonamiwebs.klooni.Effect;
import io.github.lonamiwebs.klooni.Klooni;
import io.github.lonamiwebs.klooni.Theme;
import io.github.lonamiwebs.klooni.game.Board;
import io.github.lonamiwebs.klooni.game.GameLayout;
import io.github.lonamiwebs.klooni.game.Piece;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
// Card-like actor used to display information about a given theme
public class EffectCard extends ShopCard {
//region Members
private final Effect effect;
private final IEffectFactory effect;
private final Board board;
// We want to create an effect from the beginning
@ -45,7 +45,7 @@ public class EffectCard extends ShopCard {
//region Constructor
public EffectCard(final Klooni game, final GameLayout layout, final Effect effect) {
public EffectCard(final Klooni game, final GameLayout layout, final IEffectFactory effect) {
super(game, layout, effect.getDisplay(), Klooni.theme.background);
background = Theme.getBlankTexture();
this.effect = effect;
@ -118,12 +118,12 @@ public class EffectCard extends ShopCard {
@Override
public void usedItemUpdated() {
if (game.effect.name.equals(effect.name))
if (game.effect.getName().equals(effect.getName()))
priceLabel.setText("currently used");
else if (Klooni.isEffectBought(effect))
priceLabel.setText("bought");
else
priceLabel.setText("buy for "+effect.price);
priceLabel.setText("buy for "+effect.getPrice());
}
@Override
@ -139,12 +139,12 @@ public class EffectCard extends ShopCard {
@Override
public boolean isUsed() {
return game.effect.name.equals(effect.name);
return game.effect.getName().equals(effect.getName());
}
@Override
public float getPrice() {
return effect.price;
return effect.getPrice();
}
@Override

View file

@ -1,72 +0,0 @@
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.game.Cell;
public class EvaporateEffect implements IEffect {
private Vector2 pos;
private float originalX;
private float size;
private Color vanishColor;
private float vanishSize;
private float vanishElapsed;
private float driftMagnitude;
private float randomOffset;
private static final float UP_SPEED = 100.0f;
private static final float LIFETIME = 3.0f;
private static final float INV_LIFETIME = 1.0f / 3.0f;
public EvaporateEffect() {
vanishElapsed = Float.POSITIVE_INFINITY;
}
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
pos = deadCell.pos.cpy();
originalX = pos.x;
size = deadCell.size;
vanishSize = deadCell.size;
vanishColor = deadCell.getColorCopy();
driftMagnitude = Gdx.graphics.getWidth() * 0.05f;
vanishElapsed = 0;
randomOffset = MathUtils.random(MathUtils.PI2);
}
@Override
public void draw(Batch batch) {
vanishElapsed += Gdx.graphics.getDeltaTime();
// Update the size as we fade away
final float progress = vanishElapsed * INV_LIFETIME;
vanishSize = Interpolation.fade.apply(size, 0, progress);
// Fade away depending on the time
vanishColor.set(vanishColor.r, vanishColor.g, vanishColor.b, 1.0f - progress);
// Ghostly fade upwards, by doing a lerp from our current position to the wavy one
pos.x = MathUtils.lerp(
pos.x,
originalX + MathUtils.sin(randomOffset + vanishElapsed * 3f) * driftMagnitude,
0.3f
);
pos.y += UP_SPEED * Gdx.graphics.getDeltaTime();
Cell.draw(vanishColor, batch, pos.x, pos.y, vanishSize);
}
@Override
public boolean isDone() {
return vanishElapsed > LIFETIME;
}
}

View file

@ -0,0 +1,117 @@
/*
1010! Klooni, a free customizable puzzle game for Android and Desktop
Copyright (C) 2017 Lonami Exo | LonamiWebs
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.game.Cell;
import io.github.lonamiwebs.klooni.interfaces.IEffect;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
public class EvaporateEffectFactory implements IEffectFactory {
@Override
public String getName() {
return "evaporate";
}
@Override
public String getDisplay() {
return "Evaporate";
}
@Override
public int getPrice() {
return 200;
}
@Override
public IEffect create(Cell deadCell, Vector2 culprit) {
IEffect effect = new EvaporateEffect();
effect.setInfo(deadCell, culprit);
return effect;
}
private class EvaporateEffect implements IEffect {
private Vector2 pos;
private float originalX;
private float size;
private Color vanishColor;
private float vanishSize;
private float vanishElapsed;
private float driftMagnitude;
private float randomOffset;
private static final float UP_SPEED = 100.0f;
private static final float LIFETIME = 3.0f;
private static final float INV_LIFETIME = 1.0f / 3.0f;
EvaporateEffect() {
vanishElapsed = Float.POSITIVE_INFINITY;
}
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
pos = deadCell.pos.cpy();
originalX = pos.x;
size = deadCell.size;
vanishSize = deadCell.size;
vanishColor = deadCell.getColorCopy();
driftMagnitude = Gdx.graphics.getWidth() * 0.05f;
vanishElapsed = 0;
randomOffset = MathUtils.random(MathUtils.PI2);
}
@Override
public void draw(Batch batch) {
vanishElapsed += Gdx.graphics.getDeltaTime();
// Update the size as we fade away
final float progress = vanishElapsed * INV_LIFETIME;
vanishSize = Interpolation.fade.apply(size, 0, progress);
// Fade away depending on the time
vanishColor.set(vanishColor.r, vanishColor.g, vanishColor.b, 1.0f - progress);
// Ghostly fade upwards, by doing a lerp from our current position to the wavy one
pos.x = MathUtils.lerp(
pos.x,
originalX + MathUtils.sin(randomOffset + vanishElapsed * 3f) * driftMagnitude,
0.3f
);
pos.y += UP_SPEED * Gdx.graphics.getDeltaTime();
Cell.draw(vanishColor, batch, pos.x, pos.y, vanishSize);
}
@Override
public boolean isDone() {
return vanishElapsed > LIFETIME;
}
}
}

View file

@ -1,66 +0,0 @@
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import io.github.lonamiwebs.klooni.game.Cell;
public class ExplodeEffect implements IEffect {
private Color color;
boolean dead;
private final static float EXPLOSION_X_RANGE = 0.25f;
private final static float EXPLOSION_Y_RANGE = 0.30f;
private final static float GRAVITY_PERCENTAGE = -0.60f;
class Shard {
final Vector2 pos, vel, acc;
final float size;
public Shard(final Vector2 pos, final float size) {
final float xRange = Gdx.graphics.getWidth() * EXPLOSION_X_RANGE;
final float yRange = Gdx.graphics.getHeight() * EXPLOSION_Y_RANGE;
vel = new Vector2(MathUtils.random(-xRange, +xRange), MathUtils.random(-yRange * 0.2f, +yRange));
acc = new Vector2(0f, Gdx.graphics.getHeight() * GRAVITY_PERCENTAGE);
this.size = size * MathUtils.random(0.40f, 0.60f);
this.pos = pos.cpy().add(this.size * 0.5f, this.size * 0.5f);
}
public void draw(final Batch batch, final float dt) {
vel.add(acc.x * dt, acc.y * dt).scl(0.99f);
pos.add(vel.x * dt, vel.y * dt);
Cell.draw(color, batch, pos.x, pos.y, size);
}
}
private Shard[] shards;
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
color = deadCell.getColorCopy();
shards = new Shard[MathUtils.random(4, 6)];
for (int i = 0; i != shards.length; ++i)
shards[i] = new Shard(deadCell.pos, deadCell.size);
}
@Override
public void draw(Batch batch) {
dead = true; // assume we're death
final Vector3 translation = batch.getTransformMatrix().getTranslation(new Vector3());
for (int i = shards.length; i-- != 0; ) {
shards[i].draw(batch, Gdx.graphics.getDeltaTime());
dead &= translation.y + shards[i].pos.y + shards[i].size < 0; // all must be dead
}
}
@Override
public boolean isDone() {
return dead;
}
}

View file

@ -0,0 +1,112 @@
/*
1010! Klooni, a free customizable puzzle game for Android and Desktop
Copyright (C) 2017 Lonami Exo | LonamiWebs
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import io.github.lonamiwebs.klooni.game.Cell;
import io.github.lonamiwebs.klooni.interfaces.IEffect;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
public class ExplodeEffectFactory implements IEffectFactory {
@Override
public String getName() {
return "explode";
}
@Override
public String getDisplay() {
return "Explode";
}
@Override
public int getPrice() {
return 200;
}
@Override
public IEffect create(Cell deadCell, Vector2 culprit) {
IEffect effect = new ExplodeEffect();
effect.setInfo(deadCell, culprit);
return effect;
}
private class ExplodeEffect implements IEffect {
private Color color;
boolean dead;
private final static float EXPLOSION_X_RANGE = 0.25f;
private final static float EXPLOSION_Y_RANGE = 0.30f;
private final static float GRAVITY_PERCENTAGE = -0.60f;
private class Shard {
final Vector2 pos, vel, acc;
final float size;
Shard(final Vector2 pos, final float size) {
final float xRange = Gdx.graphics.getWidth() * EXPLOSION_X_RANGE;
final float yRange = Gdx.graphics.getHeight() * EXPLOSION_Y_RANGE;
vel = new Vector2(MathUtils.random(-xRange, +xRange), MathUtils.random(-yRange * 0.2f, +yRange));
acc = new Vector2(0f, Gdx.graphics.getHeight() * GRAVITY_PERCENTAGE);
this.size = size * MathUtils.random(0.40f, 0.60f);
this.pos = pos.cpy().add(this.size * 0.5f, this.size * 0.5f);
}
public void draw(final Batch batch, final float dt) {
vel.add(acc.x * dt, acc.y * dt).scl(0.99f);
pos.add(vel.x * dt, vel.y * dt);
Cell.draw(color, batch, pos.x, pos.y, size);
}
}
private Shard[] shards;
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
color = deadCell.getColorCopy();
shards = new Shard[MathUtils.random(4, 6)];
for (int i = 0; i != shards.length; ++i)
shards[i] = new Shard(deadCell.pos, deadCell.size);
}
@Override
public void draw(Batch batch) {
dead = true; // assume we're death
final Vector3 translation = batch.getTransformMatrix().getTranslation(new Vector3());
for (int i = shards.length; i-- != 0; ) {
shards[i].draw(batch, Gdx.graphics.getDeltaTime());
dead &= translation.y + shards[i].pos.y + shards[i].size < 0; // all must be dead
}
}
@Override
public boolean isDone() {
return dead;
}
}
}

View file

@ -1,59 +0,0 @@
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.game.Cell;
public class SpinEffect implements IEffect {
private float age;
private Vector2 pos;
private float size;
private Color color;
private static final float LIFETIME = 2.0f;
private static final float INV_LIFETIME = 1.0f / LIFETIME;
private static final float TOTAL_ROTATION = 600;
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
age = 0;
pos = deadCell.pos.cpy();
size = deadCell.size;
color = deadCell.getColorCopy();
}
@Override
public void draw(Batch batch) {
age += Gdx.graphics.getDeltaTime();
final float progress = age * INV_LIFETIME;
final float currentSize = Interpolation.pow2In.apply(size, 0, progress);
final float currentRotation = Interpolation.sine.apply(0, TOTAL_ROTATION, progress);
final Matrix4 original = batch.getTransformMatrix().cpy();
final Matrix4 rotated = batch.getTransformMatrix();
final float disp =
+ 0.5f * (size - currentSize) // the smaller, the more we need to "push" to center
+ currentSize * 0.5f; // center the cell for rotation
rotated.translate(pos.x + disp, pos.y + disp, 0);
rotated.rotate(0, 0, 1, currentRotation);
rotated.translate(currentSize * -0.5f, currentSize * -0.5f, 0); // revert centering for rotation
batch.setTransformMatrix(rotated);
Cell.draw(color, batch, 0, 0, currentSize);
batch.setTransformMatrix(original);
}
@Override
public boolean isDone() {
return age > LIFETIME;
}
}

View file

@ -0,0 +1,105 @@
/*
1010! Klooni, a free customizable puzzle game for Android and Desktop
Copyright (C) 2017 Lonami Exo | LonamiWebs
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.game.Cell;
import io.github.lonamiwebs.klooni.interfaces.IEffect;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
public class SpinEffectFactory implements IEffectFactory {
@Override
public String getName() {
return "spin";
}
@Override
public String getDisplay() {
return "Spin";
}
@Override
public int getPrice() {
return 200;
}
@Override
public IEffect create(Cell deadCell, Vector2 culprit) {
IEffect effect = new SpinEffect();
effect.setInfo(deadCell, culprit);
return effect;
}
private class SpinEffect implements IEffect {
private float age;
private Vector2 pos;
private float size;
private Color color;
private static final float LIFETIME = 2.0f;
private static final float INV_LIFETIME = 1.0f / LIFETIME;
private static final float TOTAL_ROTATION = 600;
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
age = 0;
pos = deadCell.pos.cpy();
size = deadCell.size;
color = deadCell.getColorCopy();
}
@Override
public void draw(Batch batch) {
age += Gdx.graphics.getDeltaTime();
final float progress = age * INV_LIFETIME;
final float currentSize = Interpolation.pow2In.apply(size, 0, progress);
final float currentRotation = Interpolation.sine.apply(0, TOTAL_ROTATION, progress);
final Matrix4 original = batch.getTransformMatrix().cpy();
final Matrix4 rotated = batch.getTransformMatrix();
final float disp =
+0.5f * (size - currentSize) // the smaller, the more we need to "push" to center
+ currentSize * 0.5f; // center the cell for rotation
rotated.translate(pos.x + disp, pos.y + disp, 0);
rotated.rotate(0, 0, 1, currentRotation);
rotated.translate(currentSize * -0.5f, currentSize * -0.5f, 0); // revert centering for rotation
batch.setTransformMatrix(rotated);
Cell.draw(color, batch, 0, 0, currentSize);
batch.setTransformMatrix(original);
}
@Override
public boolean isDone() {
return age > LIFETIME;
}
}
}

View file

@ -1,72 +0,0 @@
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.game.Cell;
public class VanishEffect implements IEffect {
private Cell cell;
private Color vanishColor;
private float vanishSize;
private float vanishElapsed;
private float vanishLifetime;
private final static float MINIMUM_SIZE = 0.3f;
public VanishEffect() {
vanishElapsed = Float.POSITIVE_INFINITY;
}
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
cell = deadCell;
vanishSize = cell.size;
vanishColor = cell.getColorCopy();
vanishLifetime = 1f;
// The vanish distance is this measure (distance² + size³ * 20% size)
// because it seems good enough. The more the distance, the more the
// delay, but we decrease the delay depending on the cell size too or
// it would be way too high
Vector2 center = new Vector2(cell.pos.x + cell.size * 0.5f, cell.pos.y + 0.5f);
float vanishDist = Vector2.dst2(
culprit.x, culprit.y, center.x, center.y) / ((float)Math.pow(cell.size, 4.0f) * 0.2f);
// Negative time = delay, + 0.4*lifetime because elastic interpolation has that delay
vanishElapsed = vanishLifetime * 0.4f - vanishDist;
}
@Override
public void draw(Batch batch) {
vanishElapsed += Gdx.graphics.getDeltaTime();
// vanishElapsed might be < 0 (delay), so clamp to 0
float progress = Math.min(1f,
Math.max(vanishElapsed, 0f) / vanishLifetime);
// If one were to plot the elasticIn function, they would see that the slope increases
// a lot towards the end- a linear interpolation between the last size + the desired
// size at 20% seems to look a lot better.
vanishSize = MathUtils.lerp(
vanishSize,
Interpolation.elasticIn.apply(cell.size, 0, progress),
0.2f
);
float centerOffset = cell.size * 0.5f - vanishSize * 0.5f;
Cell.draw(vanishColor, batch, cell.pos.x + centerOffset, cell.pos.y + centerOffset, vanishSize);
}
@Override
public boolean isDone() {
return vanishSize < MINIMUM_SIZE;
}
}

View file

@ -0,0 +1,117 @@
/*
1010! Klooni, a free customizable puzzle game for Android and Desktop
Copyright (C) 2017 Lonami Exo | LonamiWebs
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.game.Cell;
import io.github.lonamiwebs.klooni.interfaces.IEffect;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
public class VanishEffectFatory implements IEffectFactory {
@Override
public String getName() {
return "vanish";
}
@Override
public String getDisplay() {
return "Vanish";
}
@Override
public int getPrice() {
return 0;
}
@Override
public IEffect create(Cell deadCell, Vector2 culprit) {
IEffect effect = new VanishEffect();
effect.setInfo(deadCell, culprit);
return effect;
}
private class VanishEffect implements IEffect {
private Cell cell;
private Color vanishColor;
private float vanishSize;
private float vanishElapsed;
private float vanishLifetime;
private final static float MINIMUM_SIZE = 0.3f;
VanishEffect() {
vanishElapsed = Float.POSITIVE_INFINITY;
}
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
cell = deadCell;
vanishSize = cell.size;
vanishColor = cell.getColorCopy();
vanishLifetime = 1f;
// The vanish distance is this measure (distance² + size³ * 20% size)
// because it seems good enough. The more the distance, the more the
// delay, but we decrease the delay depending on the cell size too or
// it would be way too high
Vector2 center = new Vector2(cell.pos.x + cell.size * 0.5f, cell.pos.y + 0.5f);
float vanishDist = Vector2.dst2(
culprit.x, culprit.y, center.x, center.y) / ((float) Math.pow(cell.size, 4.0f) * 0.2f);
// Negative time = delay, + 0.4*lifetime because elastic interpolation has that delay
vanishElapsed = vanishLifetime * 0.4f - vanishDist;
}
@Override
public void draw(Batch batch) {
vanishElapsed += Gdx.graphics.getDeltaTime();
// vanishElapsed might be < 0 (delay), so clamp to 0
float progress = Math.min(1f,
Math.max(vanishElapsed, 0f) / vanishLifetime);
// If one were to plot the elasticIn function, they would see that the slope increases
// a lot towards the end- a linear interpolation between the last size + the desired
// size at 20% seems to look a lot better.
vanishSize = MathUtils.lerp(
vanishSize,
Interpolation.elasticIn.apply(cell.size, 0, progress),
0.2f
);
float centerOffset = cell.size * 0.5f - vanishSize * 0.5f;
Cell.draw(vanishColor, batch, cell.pos.x + centerOffset, cell.pos.y + centerOffset, vanishSize);
}
@Override
public boolean isDone() {
return vanishSize < MINIMUM_SIZE;
}
}
}

View file

@ -1,74 +0,0 @@
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import io.github.lonamiwebs.klooni.SkinLoader;
import io.github.lonamiwebs.klooni.game.Cell;
public class WaterdropEffect implements IEffect {
private Vector2 pos;
private boolean dead;
private Color cellColor;
private Color dropColor;
private float cellSize;
private final float fallAcceleration;
private float fallSpeed;
private static final float FALL_ACCELERATION = 500.0f;
private static final float FALL_VARIATION = 50.0f;
private static final float COLOR_SPEED = 7.5f;
private final static Texture dropTexture;
static {
dropTexture = SkinLoader.loadPng("cells/drop.png");
}
public WaterdropEffect() {
fallAcceleration = FALL_ACCELERATION + MathUtils.random(-FALL_VARIATION, FALL_VARIATION);
}
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
pos = deadCell.pos.cpy();
cellSize = deadCell.size;
cellColor = deadCell.getColorCopy();
dropColor = new Color(cellColor.r, cellColor.g, cellColor.b, 0.0f);
}
@Override
public void draw(Batch batch) {
final float dt = Gdx.graphics.getDeltaTime();
fallSpeed += fallAcceleration * dt;
pos.y -= fallSpeed * dt;
cellColor.set(
cellColor.r, cellColor.g, cellColor.b,
Math.max(cellColor.a - COLOR_SPEED * dt, 0.0f)
);
dropColor.set(
cellColor.r, cellColor.g, cellColor.b,
Math.min(dropColor.a + COLOR_SPEED * dt, 1.0f)
);
Cell.draw(cellColor, batch, pos.x, pos.y, cellSize);
Cell.draw(dropTexture, dropColor, batch, pos.x, pos.y, cellSize);
final Vector3 translation = batch.getTransformMatrix().getTranslation(new Vector3());
dead = translation.y + pos.y + dropTexture.getHeight() < 0;
}
@Override
public boolean isDone() {
return dead;
}
}

View file

@ -0,0 +1,121 @@
/*
1010! Klooni, a free customizable puzzle game for Android and Desktop
Copyright (C) 2017 Lonami Exo | LonamiWebs
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.lonamiwebs.klooni.effects;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import io.github.lonamiwebs.klooni.SkinLoader;
import io.github.lonamiwebs.klooni.game.Cell;
import io.github.lonamiwebs.klooni.interfaces.IEffect;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
public class WaterdropEffectFactory implements IEffectFactory {
private Texture dropTexture;
private void init() {
if(dropTexture == null)
dropTexture = SkinLoader.loadPng("cells/drop.png");
}
@Override
public String getName() {
return "waterdrop";
}
@Override
public String getDisplay() {
return "Waterdrop";
}
@Override
public int getPrice() {
return 200;
}
@Override
public IEffect create(Cell deadCell, Vector2 culprit) {
init();
IEffect effect = new WaterdropEffect();
effect.setInfo(deadCell, culprit);
return effect;
}
private class WaterdropEffect implements IEffect {
private Vector2 pos;
private boolean dead;
private Color cellColor;
private Color dropColor;
private float cellSize;
private final float fallAcceleration;
private float fallSpeed;
private static final float FALL_ACCELERATION = 500.0f;
private static final float FALL_VARIATION = 50.0f;
private static final float COLOR_SPEED = 7.5f;
WaterdropEffect() {
fallAcceleration = FALL_ACCELERATION + MathUtils.random(-FALL_VARIATION, FALL_VARIATION);
}
@Override
public void setInfo(Cell deadCell, Vector2 culprit) {
pos = deadCell.pos.cpy();
cellSize = deadCell.size;
cellColor = deadCell.getColorCopy();
dropColor = new Color(cellColor.r, cellColor.g, cellColor.b, 0.0f);
}
@Override
public void draw(Batch batch) {
final float dt = Gdx.graphics.getDeltaTime();
fallSpeed += fallAcceleration * dt;
pos.y -= fallSpeed * dt;
cellColor.set(
cellColor.r, cellColor.g, cellColor.b,
Math.max(cellColor.a - COLOR_SPEED * dt, 0.0f)
);
dropColor.set(
cellColor.r, cellColor.g, cellColor.b,
Math.min(dropColor.a + COLOR_SPEED * dt, 1.0f)
);
Cell.draw(cellColor, batch, pos.x, pos.y, cellSize);
Cell.draw(dropTexture, dropColor, batch, pos.x, pos.y, cellSize);
final Vector3 translation = batch.getTransformMatrix().getTranslation(new Vector3());
dead = translation.y + pos.y + dropTexture.getHeight() < 0;
}
@Override
public boolean isDone() {
return dead;
}
}
}

View file

@ -27,8 +27,8 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import io.github.lonamiwebs.klooni.Effect;
import io.github.lonamiwebs.klooni.effects.IEffect;
import io.github.lonamiwebs.klooni.interfaces.IEffect;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
import io.github.lonamiwebs.klooni.serializer.BinSerializable;
// Represents the on screen board, with all the put cells
@ -183,7 +183,7 @@ public class Board implements BinSerializable {
//
// If the piece is put on the top left corner, all the cells will be cleared.
// If we first cleared the columns, then the rows wouldn't have been cleared.
public int clearComplete(final Effect effect) {
public int clearComplete(final IEffectFactory effect) {
int clearCount = 0;
boolean[] clearedRows = new boolean[cellCount];
boolean[] clearedCols = new boolean[cellCount];
@ -235,7 +235,7 @@ public class Board implements BinSerializable {
return clearCount;
}
public void clearAll(final int clearFromX, final int clearFromY, final Effect effect) {
public void clearAll(final int clearFromX, final int clearFromY, final IEffectFactory effect) {
final Vector2 culprit = cells[clearFromY][clearFromX].pos;
for (int i = 0; i < cellCount; ++i) {

View file

@ -1,4 +1,4 @@
package io.github.lonamiwebs.klooni.effects;
package io.github.lonamiwebs.klooni.interfaces;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Vector2;

View file

@ -0,0 +1,36 @@
/*
1010! Klooni, a free customizable puzzle game for Android and Desktop
Copyright (C) 2017 Lonami Exo | LonamiWebs
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.github.lonamiwebs.klooni.interfaces;
import com.badlogic.gdx.math.Vector2;
import io.github.lonamiwebs.klooni.game.Cell;
/**
* IEffectEfactory interface has to be implemented for each effect.
*
* It tells the name and the price of the effect and will create it, when needed.
*
* @see IEffect
*/
public interface IEffectFactory {
public String getName();
public String getDisplay();
public int getPrice();
public IEffect create(final Cell deadCell, final Vector2 culprit);
}

View file

@ -34,7 +34,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.utils.SnapshotArray;
import io.github.lonamiwebs.klooni.Effect;
import io.github.lonamiwebs.klooni.Klooni;
import io.github.lonamiwebs.klooni.Theme;
import io.github.lonamiwebs.klooni.actors.EffectCard;
@ -43,6 +42,7 @@ import io.github.lonamiwebs.klooni.actors.ShopCard;
import io.github.lonamiwebs.klooni.actors.SoftButton;
import io.github.lonamiwebs.klooni.actors.ThemeCard;
import io.github.lonamiwebs.klooni.game.GameLayout;
import io.github.lonamiwebs.klooni.interfaces.IEffectFactory;
// Screen where the user can customize the look and feel of the game
class CustomizeScreen implements Screen {
@ -200,7 +200,7 @@ class CustomizeScreen implements Screen {
shopGroup.clear();
if (showingEffectsShop)
for (Effect effect : Effect.getEffects())
for (IEffectFactory effect : Klooni.EFFECTS)
addCard(new EffectCard(game, layout, effect));
else // showingThemesShop

View file

@ -241,7 +241,7 @@ class GameScreen implements Screen, InputProcessor, BinSerializable {
if (bonus > 0) {
bonusParticleHandler.addBonus(result.pieceCenter, bonus);
if (Klooni.soundsEnabled()) {
game.effect.playSound();
game.playEffectSound();
}
}