Java tutorial
/******************************************************************************** * Amazing Maze is an educational game created in Java with the libGDX library. * Copyright (C) 2017 Hip Hip Array * * This file is part of Amazing Maze. * * Amazing Maze 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. * * Amazing Maze 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 Amazing Maze. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package ca.hiphiparray.amazingmaze; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Button; import com.badlogic.gdx.scenes.scene2d.ui.Dialog; import com.badlogic.gdx.scenes.scene2d.ui.Image; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.TextField; import com.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldListener; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.viewport.ScreenViewport; import ca.hiphiparray.amazingmaze.MusicManager.Song; /** * The fish minigame. * * @since 0.3 * @author Susie Son * @author Vincent Macri * <br> * Time (Susie): 8 hours. * <br> * Time (Vincent): 1 hour */ public class FishMiniGame implements Screen, InputProcessor { /** The {@link AmazingMazeGame} instance that is managing this screen. */ private final AmazingMazeGame game; /** Manages mouse input. */ private InputMultiplexer input; /** Stage that contains all of the screen components. */ private Stage stage; /** The pause menu. */ private Stage pauseMenu; /** Table for the top menu buttons. */ private Table menuTable; /** Table for the fish counting. */ private Table fishTable; /** Pencil button. */ private Button pencilButton; /** Eraser button. */ private Button eraserButton; /** Help button. */ private Button helpButton; /** Answer field. */ private TextField answerField; /** Check answer button. */ private Button checkButton; /** Clear canvas button. */ private Button clearButton; /** Player's answer. */ private int message; /** Answer value. */ private int answer; /** If user's answers is within this range, they get 70% of the fish. */ private final int range70 = 5; /** If user's answers is within this range, they get 50% of the fish. */ private final int range50 = 15; /** Number of fish for each type. */ private int[] fishNumber; /** Values of each type of fish. */ private final int[] fishValue = { 5, 10, 20, 50, 100 }; /** Fish images. */ private Image[] fishImage; /** Custom class for Pixmap manipulation. */ private Canvas canvas; /** Number of pixels the pixmap drawing should be shifted. */ private final static int shift = -80; /** Saves last drawing point. */ private Vector2 previous; /** Whether the mouse has been pressed and left down. */ private boolean leftDown; /** The pencil colour. */ private final static Color drawColor = Color.LIGHT_GRAY; /** The eraser colour. */ private final static Color clearColor = Color.DARK_GRAY; /** If the game is paused. */ private boolean paused; /** The {@link Player} instance that completed the last level. */ private Player player; /** * Constructor for FishMiniGame. * * @param game the {@link AmazingMazeGame} instance that is managing this screen. * @param player the {@link Player} instance that completed the last level. */ public FishMiniGame(final AmazingMazeGame game, Player player) { this.game = game; this.player = player; fishNumber = new int[5]; fishNumber[0] = this.player.blueCollected; fishNumber[1] = this.player.purpleCollected; fishNumber[2] = this.player.greenCollected; fishNumber[3] = this.player.redCollected; fishNumber[4] = this.player.orangeCollected; answer = 0; for (int i = 0; i < fishNumber.length; i++) { answer += fishNumber[i] * fishValue[i]; } stage = new Stage(new ScreenViewport(), this.game.batch); menuTable = new Table(); fishTable = new Table(); canvas = new Canvas( new Pixmap(Gdx.graphics.getWidth(), Gdx.graphics.getHeight() + shift, Pixmap.Format.RGB565)); menuTable.setFillParent(true); menuTable.top(); fishTable.bottom(); fishTable.setFillParent(true); fishImage = new Image[5]; TextureAtlas atlas = this.game.assets.manager.get(Assets.GAME_ATLAS_LOCATION, TextureAtlas.class); fishImage[0] = new Image(atlas.findRegion(Assets.FISH + Assets.BLUE_MODIFIER)); fishImage[0].setScale(4f); fishImage[1] = new Image(atlas.findRegion(Assets.FISH + Assets.PURPLE_MODIFIER)); fishImage[1].setScale(4f); fishImage[2] = new Image(atlas.findRegion(Assets.FISH + Assets.GREEN_MODIFIER)); fishImage[2].setScale(4f); fishImage[3] = new Image(atlas.findRegion(Assets.FISH + Assets.RED_MODIFIER)); fishImage[3].setScale(4f); fishImage[4] = new Image(atlas.findRegion(Assets.FISH + Assets.ORANGE_MODIFIER)); fishImage[4].setScale(4f); pencilButton = new Button(new TextureRegionDrawable( new TextureRegion(this.game.assets.manager.get(Assets.PENCIL_BUTTON, Texture.class)))); pencilButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { if (pencilButton.isPressed()) { canvas.setColor(drawColor); } } }); eraserButton = new Button(new TextureRegionDrawable( new TextureRegion(this.game.assets.manager.get(Assets.ERASER_BUTTON, Texture.class)))); eraserButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { if (eraserButton.isPressed()) { canvas.setColor(clearColor); } } }); helpButton = new Button(new TextureRegionDrawable( new TextureRegion(this.game.assets.manager.get(Assets.HELP_BUTTON, Texture.class)))); helpButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { if (helpButton.isPressed()) { Label.LabelStyle labelStyle = new Label.LabelStyle( game.assets.getFont(Assets.MONO_REGULAR, Assets.SMALL_FONT_SIZE), Color.WHITE); final Dialog dialog = new Dialog("Help", game.assets.skin); final TextButton okButton = new TextButton("OK", game.assets.skin); dialog.getButtonTable().bottom(); Label label = new Label("Find the total value of fish that you retrieved!\n" + "Each colour corresponds to the colour of Canadian money.\n" + "The numbers correspond to each number of fish you got.\n\n" + "In case you forgot: blue is 5, purple is 10, green is 20, red is 50, and orange is 100.", labelStyle); label.setScale(.5f); label.setWrap(true); label.setAlignment(Align.center); dialog.add(label).width(500).pad(50); dialog.add(okButton).bottom(); okButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { if (okButton.isPressed()) { dialog.hide(); canvas.setColor(drawColor); } } }); dialog.key(Keys.ENTER, true); dialog.show(stage); } } }); checkButton = new Button(new TextureRegionDrawable( new TextureRegion(this.game.assets.manager.get(Assets.CHECK_BUTTON, Texture.class)))); checkButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { dialog(); } }); clearButton = new Button(new TextureRegionDrawable( new TextureRegion(this.game.assets.manager.get(Assets.CLEAR_BUTTON, Texture.class)))); clearButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { if (clearButton.isPressed()) { canvas.clear(); canvas.setColor(drawColor); } } }); answerField = new TextField("", game.assets.skin); answerField.setTextFieldListener(new TextFieldListener() { @Override public void keyTyped(TextField textField, char key) { if (key == (char) 13) { stage.unfocus(answerField); dialog(); } } }); stage.addActor(menuTable); stage.addActor(canvas); stage.addActor(fishTable); menuTable.clear(); fishTable.clear(); helpButton.right(); menuTable.background(new TextureRegionDrawable( new TextureRegion(this.game.assets.manager.get(Assets.MINI_BACKGROUND, Texture.class)))); menuTable.add(pencilButton).pad(10).size(64); menuTable.add(eraserButton).pad(10).size(64); menuTable.add(clearButton).pad(10).size(64); menuTable.add(helpButton).pad(10).size(64); menuTable.row(); Label.LabelStyle labelStyle = new Label.LabelStyle( game.assets.getFont(Assets.SANS_REGULAR, Assets.REGULAR_FONT_SIZE), Color.WHITE); for (int i = 0; i < 5; i++) { fishTable.add(fishImage[i]).bottom().left().padLeft(10); } fishTable.add(answerField).minWidth(150).padLeft(150); fishTable.add(checkButton).pad(10).size(64); fishTable.row(); for (int i = 0; i < 5; i++) { fishTable.add(new Label(fishNumber[i] + "", labelStyle)).pad(30).center(); } fishTable.row(); setupPauseMenu(); input = new InputMultiplexer(stage, this); input.addProcessor(pauseMenu); } /** Create the pause menu. */ private void setupPauseMenu() { pauseMenu = new Stage(new ScreenViewport(), game.batch); Table table = new Table(); table.setFillParent(true); table.center(); pauseMenu.addActor(table); TextButton resumeButton = new TextButton("Resume", game.assets.skin); resumeButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { paused = false; } }); table.add(resumeButton).pad(10).width(Gdx.graphics.getWidth() / 4).height(Gdx.graphics.getHeight() / 8); table.row(); TextButton settingsButton = new TextButton("Settings", game.assets.skin); final Screen sourceScreen = this; settingsButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { game.settingsScreen.setSourceScreen(sourceScreen); game.setScreen(game.settingsScreen); } }); table.add(settingsButton).pad(10).width(Gdx.graphics.getWidth() / 4).height(Gdx.graphics.getHeight() / 8); table.row(); TextButton quitButton = new TextButton("Quit", game.assets.skin); quitButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { game.save.setLevel(game.save.getLevel() - 1); game.setScreen(game.menuScreen); } }); table.add(quitButton).pad(10).width(Gdx.graphics.getWidth() / 4).height(Gdx.graphics.getHeight() / 8); } /** * Displays the results dialog. */ public void dialog() { message = formatString(answerField.getText()); Label.LabelStyle labelStyle = new Label.LabelStyle( game.assets.getFont(Assets.MONO_REGULAR, Assets.SMALL_FONT_SIZE), Color.WHITE); final Dialog dialog = new Dialog("Results", game.assets.skin); final TextButton okButton = new TextButton("OK", game.assets.skin); dialog.getButtonTable().bottom(); if (checkAnswer() == -1) { Label label = new Label("Invalid answer. Please try again.", labelStyle); label.setScale(.5f); label.setWrap(true); label.setAlignment(Align.center); dialog.add(label).width(500).pad(50); dialog.add(okButton).bottom(); okButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { if (okButton.isPressed()) { dialog.hide(); canvas.setColor(drawColor); } } }); dialog.addListener(new InputListener() { @Override public boolean keyDown(InputEvent event, int keycode) { if (keycode == Keys.ENTER) { dialog.hide(); return true; } return false; } }); } else { Label label = new Label("Your answer was: " + message + ". " + "The correct answer was: " + answer + ". " + "You get " + checkAnswer() + " back!", labelStyle); game.save.addScore(checkAnswer()); game.save.setLives(player.getLives()); label.setScale(.5f); label.setWrap(true); label.setAlignment(Align.center); dialog.add(label).width(500).pad(50); dialog.add(okButton).bottom(); okButton.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { if (okButton.isPressed()) { dialog.cancel(); if ((game.save.getLevel() - 1) % 5 == 0) { game.setScreen(new ContinueScreen(game, true)); } else { game.setScreen(new MazeScreen(game, false)); } } } }); dialog.addListener(new InputListener() { @Override public boolean keyDown(InputEvent event, int keycode) { if (keycode == Keys.ENTER) { if ((game.save.getLevel() - 1) % 5 == 0) { game.setScreen(new ContinueScreen(game, true)); } else { game.setScreen(new MazeScreen(game, false)); } return true; } return false; } }); } dialog.show(stage); } /** * Formats the string into an integer. * * @param s the string being formatted. * @return the formatted string. */ public int formatString(String s) { if (s == null) return -1; try { if (Integer.parseInt(s) < 0) return -1; return Integer.parseInt(s); } catch (NumberFormatException e) { return -1; } } /** * Checks the player's answer and gives fish accordingly. * * @return The number of fish given back to the player. */ public int checkAnswer() { if (message == -1) { return -1; } if (message == answer) { return answer; } if (Math.abs(message - answer) <= range70) { return (int) (answer * 0.70); } if (Math.abs(message - answer) <= range50) { return (int) (answer * 0.50); } return (int) (answer * 0.15); } @Override public void show() { Gdx.input.setInputProcessor(input); Gdx.input.setCursorCatched(false); game.music.setSong(Song.MATH); } @Override public void render(float delta) { Gdx.gl.glClearColor(1, 1, 1, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); canvas.update(); stage.act(delta); stage.draw(); if (paused) { pauseMenu.act(); pauseMenu.draw(); } } @Override public void resize(int width, int height) { stage.getViewport().update(width, height, true); } @Override public void pause() { } @Override public void resume() { } @Override public void hide() { } @Override public void dispose() { stage.dispose(); canvas.dispose(); } /** Custom Canvas class for Pixmap manipulation. */ private static class Canvas extends Actor implements Disposable { /** Radius of the drawing tool. */ private static final int pencilSize = 1; /** Radius of the erasing tool. */ private static final int eraserSize = 5; /** The current brush size. */ private int brushSize; /** Canvas' pixmap. */ private Pixmap pixmap; /** Texture that updates from the pixmap. */ private Texture texture; /** Whether the pixmap has been manipulated. */ private boolean pixmapChanged; /** The Canvas constructor. */ public Canvas(Pixmap pixmap) { this.pixmap = pixmap; this.clear(); pixmap.setColor(drawColor); this.texture = new Texture(pixmap); this.pixmapChanged = false; this.brushSize = pencilSize; } /** * Sets the color of the drawing tool. * * @param color color selected for drawing. */ @Override public void setColor(Color color) { pixmap.setColor(color); if (color.equals(drawColor)) { brushSize = pencilSize; } else { brushSize = eraserSize; } } @Override public void draw(Batch batch, float parentAlpha) { batch.draw(texture, 0, 0); } /** Updates the texture. */ public void update() { if (pixmapChanged) { texture.draw(pixmap, 0, 0); pixmapChanged = false; } } /** Clears the pixmap. */ @Override public void clear() { pixmap.setColor(clearColor); pixmap.fill(); pixmapChanged = true; } /** * Draw a dot on the pixmap. * * @param x The x-coordinate. * @param y The y-coordinate. */ private void drawDot(Vector2 spot) { pixmap.fillCircle((int) spot.x, (int) spot.y + shift, brushSize); pixmapChanged = true; } /** * Draws a line when mouse is dragged. * * @param from * Where line starts. * @param to * Where line ends. */ public void drawLine(Vector2 from, Vector2 to) { for (float i = 0; i < 1f; i += brushSize / (100f * to.dst(from))) { Vector2 line = from.lerp(to, i); drawDot(line); } drawDot(to); pixmapChanged = true; } @Override public void dispose() { texture.dispose(); pixmap.dispose(); } } @Override public boolean keyDown(int keycode) { if (keycode == Keys.ENTER) { dialog(); } else if (keycode == game.save.getPauseButton()) { paused = !paused; } return true; } @Override public boolean keyUp(int keycode) { return false; } @Override public boolean keyTyped(char character) { return false; } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { if (!paused && button == Input.Buttons.LEFT) { Vector2 current = new Vector2(screenX, screenY); canvas.drawDot(current); previous = current; leftDown = true; return true; } return false; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { if (!paused && button == Input.Buttons.LEFT) { canvas.drawDot(new Vector2(screenX, screenY)); previous = null; leftDown = false; return true; } return false; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { if (!paused && leftDown) { Vector2 current = new Vector2(screenX, screenY); canvas.drawLine(previous, current); previous = current; return true; } return false; } @Override public boolean mouseMoved(int screenX, int screenY) { return false; } @Override public boolean scrolled(int amount) { return false; } }