Having the name that I do (Simon), of course I have to participate in this challenge! (Even though I am a bit "late", but Simon says "have forgiveness!")
This is an implementation of the Simon Says challenge using libGDX. I started using this framework a couple of months ago, and I love the fact that it can create games for Desktop, Android, iOS and HTML5, at once.
As it is libGDX, and Android support is needed, I'm limited to Java 6. As I'm also using GWT, there's some restriction there as well.
Class Summary
I have not included the several different launchers here, as I am just using the defaults that comes with a LibGDX project there.
- SimonGame: Main LibGDX entry point
- MenuScreen: Shows the different implemented playing "difficulties"
- GameScreen: The main game screen, with the buttons you are supposed to remember the sequence of and click at
- SequenceGenerator: Interface for generating a sequence
- AppendingGenerator: Adds one new random button each time, but keeping the old sequence
- SimpleGenerator: Completely generates a new sequence each time
Dependencies
- LibGDX
- GWT
Code
This code can also be downloaded from GitHub
AppendingGenerator.java:
public class AppendingGenerator implements SequenceGenerator {
private final Random random = new Random();
private int count;
private List<Integer> recent;
public AppendingGenerator(int count) {
this.count = count;
this.recent = new ArrayList<Integer>();
}
@Override
public void generate(int numButtons, List<Integer> result) {
while (recent.size() < count) {
recent.add(random.nextInt(numButtons));
}
result.addAll(recent);
this.count++;
}
}
GameScreen.java:
public class GameScreen implements Screen {
private final SimonGame game;
private final Button[] images;
private final LinkedList<Integer> sequence = new LinkedList<Integer>(); // GWT does not support 'Deque' interface
private final Table table = new Table();
private final SequenceGenerator generator;
private final int numButtons;
private static final float fadeTime = 0.7f;
public GameScreen(SimonGame game, int numButtons, SequenceGenerator generator) {
this.game = game;
this.generator = generator;
this.numButtons = numButtons;
this.images = new Button[numButtons];
table.setFillParent(true);
for (int i = 0; i < images.length; i++) {
final int index = i;
images[i] = new TextButton("", game.getSkin());
images[i].addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
buttonClick(index);
}
});
table.add(images[i]).expand().fill();
if (i % 2 == 1) {
table.row();
}
}
generateSequence();
}
private void generateSequence() {
sequence.clear();
generator.generate(numButtons, sequence);
showSequence();
}
private void showSequence() {
setButtonsEnabled(false);
showSequence(0);
}
private void setButtonsEnabled(boolean enabled) {
for (Button image : images) {
image.setTouchable(enabled ? Touchable.enabled : Touchable.disabled);
}
}
private void showSequence(final int index) {
if (sequence.size() <= index) {
setButtonsEnabled(true);
return;
}
Integer i = sequence.get(index);
Actor actor = images[i];
actor.addAction(sequence(alpha(0.7f, fadeTime), alpha(1.0f, fadeTime), run(new Runnable() {
@Override
public void run() {
showSequence(index + 1);
}
})));
}
private void buttonClick(int index) {
if (sequence.getFirst() == index) {
sequence.removeFirst();
if (sequence.isEmpty()) {
generateSequence();
}
}
else {
game.setScreen(new MenuScreen(game));
}
}
@Override
public void render(float delta) {
}
@Override
public void resize(int width, int height) {
}
@Override
public void show() {
game.getStage().addActor(table);
}
@Override
public void hide() {
table.remove();
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
}
}
MenuScreen.java:
public class MenuScreen implements Screen {
private static final int NUM_BUTTONS = 4;
private final SimonGame game;
private final Table table;
public MenuScreen(final SimonGame game) {
this.game = game;
this.table = new Table();
table.setFillParent(true);
addPlayButton("Simple", NUM_BUTTONS, new AppendingGenerator(3));
addPlayButton("Random", NUM_BUTTONS, new SimpleGenerator(3));
}
private void addPlayButton(String text, final int numButtons, final SequenceGenerator generator) {
TextButton playButton = new TextButton(text, game.getSkin());
playButton.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
game.setScreen(new GameScreen(game, numButtons, generator));
}
});
table.add(playButton).expand().fill();
}
@Override
public void render(float delta) {
}
@Override
public void resize(int width, int height) {
}
@Override
public void show() {
game.getStage().addActor(table);
}
@Override
public void hide() {
table.remove();
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
}
}
SequenceGenerator.java:
public interface SequenceGenerator {
void generate(int numButtons, List<Integer> result);
}
SimonGame.java:
public class SimonGame extends Game {
private SpriteBatch batch;
private Camera camera;
private Stage stage;
private Skin skin;
@Override
public void create () {
batch = new SpriteBatch();
camera = new OrthographicCamera(800, 480);
stage = new Stage(new FitViewport(800, 480, camera), batch);
skin = new Skin(Gdx.files.internal("data/uiskin.json"));
setScreen(new MenuScreen(this));
Gdx.input.setInputProcessor(stage);
}
@Override
public void render () {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setProjectionMatrix(camera.combined);
batch.begin();
super.render();
batch.end();
stage.act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f));
stage.draw();
}
public Stage getStage() {
return stage;
}
public Skin getSkin() {
return skin;
}
}
SimpleGenerator.java:
public class SimpleGenerator implements SequenceGenerator {
private final Random random;
private int count;
public SimpleGenerator(int count) {
this.count = count;
this.random = new Random();
}
@Override
public void generate(int numButtons, List<Integer> result) {
for (int i = 0; i < count; i++) {
result.add(random.nextInt(numButtons));
}
count++;
}
}
Usage / Test
Available to play here!
Questions
My primary concern is with passing on the SimonGame
to the screens. I am perfectly aware that I can extract an interface, but is it really worth it? The screens would not be useful without this game, and the game would not be useful without those screens - is it worth decoupling them? It seems to be very common for LibGDX projects to pass their main game class on to other classes - many other projects even use the camera
, stage
, etc as package-private fields which I wanted to avoid, I think I prefer a getter instead.
My secondary concern is the data structure for the sequence. I don't think there's too much wrong with using a LinkedList
here. And I still want to support different sequence generators.
My third concern is everything else. Comment on what you want.
Note that I am fully aware that the "graphical design" sucks. Luckily, this is Code Review and not Graphics Review