From 38f6c935fd131314c6d95fcac9b0b2fec4108deb Mon Sep 17 00:00:00 2001 From: John Date: Mon, 20 Jun 2022 16:13:04 -0700 Subject: [PATCH] Add a lot of documentation --- cheats.txt | 1 + src/DialogueMenu.java | 50 ++++++++++++++++++++-------- src/GameFrame.java | 15 +++------ src/GamePanel.java | 10 +++--- src/GenericSprite.java | 6 ++-- src/GlobalState.java | 5 +++ src/LevelManager.java | 36 +++++++++++++++----- src/Main.java | 1 + src/MapReader.java | 54 ++++++++++++++++++------------ src/MenuPanel.java | 60 ++++++++++++++++++++++++---------- src/Middleware.java | 27 +++++---------- src/NonPlayer.java | 7 ++-- src/Particle.java | 1 - src/SafeObjectInputStream.java | 1 - src/SettingPanel.java | 9 ++--- src/SingleTile.java | 1 - src/SoundWrapper.java | 4 --- src/StickyBomb.java | 3 -- src/UtilityFunction.java | 3 -- 19 files changed, 177 insertions(+), 117 deletions(-) create mode 100644 cheats.txt diff --git a/cheats.txt b/cheats.txt new file mode 100644 index 0000000..fecc29e --- /dev/null +++ b/cheats.txt @@ -0,0 +1 @@ +Press P to skip to the next level. \ No newline at end of file diff --git a/src/DialogueMenu.java b/src/DialogueMenu.java index c26a6bd..d8f6ac9 100644 --- a/src/DialogueMenu.java +++ b/src/DialogueMenu.java @@ -1,8 +1,10 @@ +// Eric Li, Charlie Zhao, ICS4U, Finished 6/17/2022 +// displays dialogue, animates dialogue, and renders box that contains dialogue as well as the portraits of the characters +// that speak to the players + import java.awt.*; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; public class DialogueMenu extends TextBox implements Serializable { public static final int PORTRAIT_WIDTH = 200; @@ -10,18 +12,19 @@ public class DialogueMenu extends TextBox implements Serializable { public static final int TOP_PADDING = 10; public static final double LINE_SPACING = 1.5; public static final int FREQUENCY = 2; - public BufferedImageWrapper PORTRAIT; + public BufferedImageWrapper portrait; public int currentFrame = 0; public int frameCounter = 0; public boolean isNarrator; public DialogueMenu(int y, int yHeight, Font font, BufferedImageWrapper portrait, boolean isNarrator) { super(y, GamePanel.GAME_WIDTH - PORTRAIT_WIDTH - PADDING*3, yHeight, 0, font, null, null); - PORTRAIT = portrait; + this.portrait = portrait; checkForNarrator(); this.isNarrator = isNarrator; } + // check if the person speaking is currently the narrator, and adjust the newX value accordingly public void checkForNarrator() { if (isNarrator) { newX = PORTRAIT_WIDTH + PADDING*2; @@ -30,64 +33,85 @@ public class DialogueMenu extends TextBox implements Serializable { } } + // draw a centered text box public void drawCenteredTextBox(Graphics g, String text, Color backgroundColor, Color textColor) { + // only draw currentFrame number of text characters; this animates the process of drawing the text text = text.substring(0, currentFrame); + // only set the background color if it is not null if (backgroundColor != null) { g.setColor(textColor); + // if the person speaking is the narrator, draw the textbox to the right of the portrait if (isNarrator) { - g.drawImage(PORTRAIT.image, newX - PORTRAIT_WIDTH - PADDING, newY, PORTRAIT_WIDTH, yHeight, null); - } else { - g.drawImage(PORTRAIT.image, GamePanel.GAME_WIDTH - PORTRAIT_WIDTH - PADDING, newY, PORTRAIT_WIDTH, yHeight, null); + g.drawImage(portrait.image, newX - PORTRAIT_WIDTH - PADDING, newY, PORTRAIT_WIDTH, yHeight, null); + } else { // otherwise, draw the textbox to the left of the portrait + g.drawImage(portrait.image, GamePanel.GAME_WIDTH - PORTRAIT_WIDTH - PADDING, newY, PORTRAIT_WIDTH, yHeight, null); } + // create border and set it to a width of 4.0 ((Graphics2D)g).setStroke(new BasicStroke(4f)); + // draw the border g.drawRect(newX, newY, xWidth, yHeight - 4); + // set color of the rectangle inside the border, and draw it g.setColor(backgroundColor); g.fillRect(newX, newY, xWidth, yHeight - 4); } + // set text color and draw it g.setColor(textColor); drawCenteredString(g, newY, newX, text); } + // draw a centered string public static void drawCenteredString(Graphics g, int y, int x, String text) { - // split text by spaces + // split text by spaces (into individual words) String[] newText = text.split(" "); - ArrayList lines = new ArrayList(); + // create new ArrayList that will compose of every line in the new text + ArrayList lines = new ArrayList<>(); + // add an empty line; this line will eventually have more text added to it lines.add(""); // get font size FontMetrics metrics = g.getFontMetrics(); + // declare temporary variables used in the for loop, and initialize them int currentLineWidth = 0, lastLineIndex = 0; for (String s: newText) { + // add width of new word to current line width currentLineWidth += metrics.stringWidth(s + " "); + // if the newLineWidth still fits in the dialogue box, add it to the current line if (currentLineWidth - metrics.stringWidth(" ") < (GamePanel.GAME_WIDTH - PORTRAIT_WIDTH - PADDING*5)) { lines.set(lastLineIndex, lines.get(lastLineIndex) + s + " "); - } else { + } else { // otherwise, create a new line, set the current line width to the current width, and increment the current line index currentLineWidth = metrics.stringWidth(s); lines.add(s + " "); lastLineIndex ++; } } + // leave TOP_PADDING + (metrics.getAscent() - metrics.getDescent()) * LINE_SPACING) + // space between the top of the dialogue box and the text y += TOP_PADDING; - // center y (half is above y value, half is below y value) for (String s: lines) { + // add spacing since last line y += (metrics.getAscent() - metrics.getDescent()) * LINE_SPACING; - // draw string + // draw actual string g.drawString(s + "\n", x + PADDING, y); } } public boolean draw(Graphics g, String text, Color backgroundColor, Color textColor) { + // before every draw, check if the status of the current person speaking changed checkForNarrator(); + // if more than FREQUENCY ticks has passed since the last text animation, animate by drawing one more char if (frameCounter >= FREQUENCY) { frameCounter -= FREQUENCY; currentFrame += 1; } + // set font of string to be drawn g.setFont(font); drawCenteredTextBox(g, text, backgroundColor, textColor); + // increment the frame counter frameCounter++; + // if the text has been completely drawn (nothing left to animate), return true if (currentFrame >= text.length()) { currentFrame = 0; return true; - } else { + } else { // otherwise, return false return false; } } diff --git a/src/GameFrame.java b/src/GameFrame.java index f0eab5a..1566cd3 100644 --- a/src/GameFrame.java +++ b/src/GameFrame.java @@ -4,18 +4,13 @@ It is a child of JFrame because JFrame manages frames Creates new JPanel child, and adds GamePanel, MenuPanel, and SettingPanel classes to it, allowing for navigation between them Also initializes and deserializes them as necessary */ -import java.awt.*; -import java.io.IOException; -import java.nio.file.FileSystemException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; + import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import javax.swing.*; +import java.awt.*; +import java.io.IOException; +import java.util.Arrays; public class GameFrame extends JFrame{ @@ -55,7 +50,7 @@ public class GameFrame extends JFrame{ game.addMouseListener(); } catch (IOException | ClassNotFoundException | ClassCastException | SecurityException e) { // if an exception occurs during serialization, it is not a game-breaking exception; it is logged in the console and a new game is created - System.out.println("[LOG] NotAnError: " + e); + System.out.println("[LOG] " + e.toString().replace("Exception", "NotAnError")); game = new GamePanel(main); //run GamePanel constructor } // start game thread to allow the game to run diff --git a/src/GamePanel.java b/src/GamePanel.java index 4958e16..4081a3a 100644 --- a/src/GamePanel.java +++ b/src/GamePanel.java @@ -9,21 +9,21 @@ Implements KeyListener interface to listen for keyboard input Implements Runnable interface to use "threading" - let the game do two things at once */ + +import javax.imageio.ImageIO; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; -import javax.imageio.ImageIO; import java.io.Serializable; -import java.lang.reflect.Array; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.ConcurrentModificationException; -import javax.sound.sampled.LineUnavailableException; -import javax.sound.sampled.UnsupportedAudioFileException; -import javax.swing.*; import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; diff --git a/src/GenericSprite.java b/src/GenericSprite.java index b2b8f91..bbbdc14 100644 --- a/src/GenericSprite.java +++ b/src/GenericSprite.java @@ -4,11 +4,13 @@ child of Rectangle because that makes it easy to draw and check for collision In 2D GUI, basically everything is a rectangle even if it doesn't look like it! -*/ +*/ + import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import java.awt.*; -import java.awt.event.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; import java.io.IOException; import java.io.Serializable; diff --git a/src/GlobalState.java b/src/GlobalState.java index e60f2be..734d2b1 100644 --- a/src/GlobalState.java +++ b/src/GlobalState.java @@ -1,5 +1,10 @@ +// Eric Li, Charlie Zhao, ICS4U, Finished 6/19/2022 +// determines the standard time unit for exploding objects (i.e., bombs) +// while not very useful now, GlobalState was added to increase extensibility in the future + import java.io.Serializable; public class GlobalState implements Serializable { + // each object takes GlobalState.second * 5 ticks to explode public static final int second = 10; } diff --git a/src/LevelManager.java b/src/LevelManager.java index 1aa4009..039a5a7 100644 --- a/src/LevelManager.java +++ b/src/LevelManager.java @@ -1,3 +1,6 @@ +// Eric Li, Charlie Zhao, ICS4U, Finished 6/20/2022 +// loads levels into GamePanel and manages levels, as well as some features like the amount of bombs the player has + import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import java.io.IOException; @@ -12,26 +15,30 @@ public class LevelManager implements Serializable { public static String filePath; public static int bombs; + // set current level, then load the map, enemies, dialogue, signs, and bomb count for that level public static void setLevel(int level, boolean hasDied){ + // remove all current bombs and fireballs GameFrame.game.bombs.clear(); GameFrame.game.fireballs.clear(); + // set the player velocity to zero GameFrame.game.player.yVelocity = 0; GameFrame.game.player.xVelocity = 0; GameFrame.game.level = level; + // change spawn coordinates, bomb count, and save file path based on level inputted if(level == 1){ - //-400/450 + // spawn coordinates: -400/450 xSpawn = -400; ySpawn = 450; filePath = "saves/Level1.txt"; bombs = 8; } else if(level == 2){ - //-400/400 + // spawn coordinates: -400/400 xSpawn = -400; ySpawn = 400; filePath = "saves/Level2.txt"; bombs = 3; } else if(level == 3){ - //-800/100 + // spawn coordinates: -800/100 xSpawn = -800; ySpawn = 100; filePath = "saves/Level3.txt"; @@ -50,17 +57,27 @@ public class LevelManager implements Serializable { bombs = 0; } try { + // load map into GamePanel MapReader.inputMap(filePath); + // if the player has not died yet (i.e., first time seeing the dialogue), load it if (!hasDied) { + // do not load dialogue if there is no dialogue to load if (!(MapReader.inputDialogue(filePath)[0].equals("$Empty"))) { - GameFrame.game.dialogueArray = new ArrayList(Arrays.asList(MapReader.inputDialogue(filePath))); + // convert dialogue from String[] to ArrayList, and load it into GamePanel + GameFrame.game.dialogueArray = new ArrayList<>(Arrays.asList(MapReader.inputDialogue(filePath))); + // if the dialogue file starts with $Villain, have the portrait display on the right side of the screen, and display the alternate portrait if (GameFrame.game.dialogueArray.get(0).contains("$Villain")) { + // delete the first item in the array so the villain does not say "$Villain" GameFrame.game.dialogueArray.remove(0); + // display portrait on right side of screen GameFrame.game.dialogueMenu.isNarrator = false; - GameFrame.game.dialogueMenu.PORTRAIT = GameFrame.game.villainPortrait; + // change portrait + GameFrame.game.dialogueMenu.portrait = GameFrame.game.villainPortrait; } + // reset dialogue frame to zero, so it restarts the animation every time the dialogue is loaded GameFrame.game.dialogueMenu.currentFrame = 0; GameFrame.game.dialogueMenu.frameCounter = 0; + // tell the GameFrame to load the DialogueMenu GameFrame.game.isDialogue = true; } } @@ -70,25 +87,28 @@ public class LevelManager implements Serializable { // temporary boolean, so only declared here boolean stillTutorial = true; for (String[] sA: MapReader.inputSign(filePath)) { + // if the line contains "/", skip the line, stop adding signs to tutorialSign and instead add signs to loreSign + // this is important because loreSigns and tutorialSigns have different colours and font types if (sA[0].contains("/")) { stillTutorial = false; } else if (stillTutorial) { - //System.out.println("" + sA[0] + sA[1]); + // add sign to tutorialSign GameFrame.game.tutorialSign.add(new WallSign(Integer.parseInt(sA[0]), Integer.parseInt(sA[1]), GamePanel.tutorialFont, sA[2])); } else { + // add sign to loreSign if stillTutorial is false GameFrame.game.loreSign.add(new WallSign(Integer.parseInt(sA[0]), Integer.parseInt(sA[1]), GamePanel.loreFont, sA[2])); } } } catch (IOException | SpriteException | UnsupportedAudioFileException | LineUnavailableException e) { throw new RuntimeException(e); } - //GamePanel.player.reset(); - //System.out.println("done111"); } + // overloaded setLevel that accepts only a level argument public static void setLevel(int level) { setLevel(level, false); } + // go to the next level public static void nextLevel(){ setLevel(GameFrame.game.level+1); } diff --git a/src/Main.java b/src/Main.java index cac4b9a..bc79c25 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,3 +1,4 @@ +// Eric Li, Charlie Zhao, ICS4U, Finished 5/30/2022 /* Main class starts the game All it does is run the constructor in GameFrame class diff --git a/src/MapReader.java b/src/MapReader.java index 7dc3566..056023c 100644 --- a/src/MapReader.java +++ b/src/MapReader.java @@ -1,11 +1,15 @@ +// Eric Li, Charlie Zhao, ICS4U, Finished 6/16/2022 +// reads map, dialogue, and signs from files and loads them into GamePanel + import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; -import java.io.*; +import java.io.IOException; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; public class MapReader implements Serializable { - //Input game map + // loads game map into GamePanel /* 1: Normal Grass 2: Left Grass: @@ -23,28 +27,36 @@ public class MapReader implements Serializable { // h k Shooting // m public static void inputMap(String filePath) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException { + int TileX, TileY; int x = 0; int y = 0; - int TileX = 0; - int TileY = 0; + // clears existing enemies, particles, fireball shooters, and fireballs GameFrame.game.enemy.clear(); GameFrame.game.particleTiles.clear(); GameFrame.game.shootingTiles.clear(); GameFrame.game.fireballs.clear(); + // clears current map for(int i=0; i0&&GameFrame.game.map[x][y-1]==null) { GameFrame.game.particleTiles.add(GameFrame.game.map[x][y]); } - } else if(file.charAt(i)=='o'){ + } else if(file.charAt(i)=='o'){ // steel tiles can be picked up and moved newTile("img/tiles/boxes/steel.png", x, y, TileX, TileY); GameFrame.game.map[x][y].movable = true; - } else if(file.charAt(i)=='h'){ + } else if(file.charAt(i)=='h'){ // the following tiles shoot fireballs in the directions indicated; these fireballs kill players on contact newTile("img/tiles/boxes/boxShootLeft.png", x, y, TileX, TileY); GameFrame.game.map[x][y].shootingDir = "left"; GameFrame.game.shootingTiles.add(GameFrame.game.map[x][y]); @@ -118,25 +129,28 @@ public class MapReader implements Serializable { GameFrame.game.map[x][y].shootingDir = "down"; GameFrame.game.shootingTiles.add(GameFrame.game.map[x][y]); } - x+=1; + x+=1; // increment x value by one after every character read } } + // return dialogue array given the filePath inputted in inputMath for further processing in LevelManager public static String[] inputDialogue(String mapFilePath) throws IOException { - String filePath = mapFilePath.replace(".txt", "-dialogue.txt"); - return FileManager.readFile(filePath).split("\n"); + String filePath = mapFilePath.replace(".txt", "-dialogue.txt"); // format path to open the dialogue file instead + return FileManager.readFile(filePath).split("\n"); // read and split file, before returning } - public static ArrayList inputSign(String signFilePath) throws IOException { - String filePath = signFilePath.replace(".txt", "-signs.txt"); + // return sign array given the filePath inputted in inputMath for further processing in LevelManager + public static ArrayList inputSign(String mapFilePath) throws IOException { + String filePath = mapFilePath.replace(".txt", "-signs.txt"); // format path to open the sign file instead String[] temporaryStringArray = FileManager.readFile(filePath).split("\n"); - ArrayList returnArray = new ArrayList(); + ArrayList returnArray = new ArrayList<>(); // create new ArrayList and populate it with details about the text for (String s: temporaryStringArray) { - returnArray.add(s.split(" ", 3)); + returnArray.add(s.split(" ", 3)); // s[0] = x, s[1] = y, s[2] = text; given that the text often has spaces, the amount of items resulting from the split was limited to three } return returnArray; } + // populate GamePanel tile array with new tile with TileX and TileY positions public static void newTile(String filePath, int x, int y, int TileX, int TileY) throws IOException, SpriteException { GameFrame.game.map[x][y]=(new SingleTile(TileX,TileY, new BufferedImageWrapper((filePath)))); } diff --git a/src/MenuPanel.java b/src/MenuPanel.java index 3784875..b97e790 100644 --- a/src/MenuPanel.java +++ b/src/MenuPanel.java @@ -1,4 +1,5 @@ -/* GamePanel class acts as the main "game loop" - continuously runs the game and calls whatever needs to be called +// Eric Li, Charlie Zhao, ICS4U, Finished 6/17/2022 +/* MenuPanel class acts as the menu - launches the other panels that need to be called Child of JPanel because JPanel contains methods for drawing to the screen @@ -7,15 +8,15 @@ Implements KeyListener interface to listen for keyboard input Implements Runnable interface to use "threading" - let the game do two things at once */ -import java.awt.*; -import java.awt.event.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; + import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; public class MenuPanel extends JPanel implements Runnable, KeyListener{ @@ -31,7 +32,7 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ public Graphics graphics; public BackgroundImage background; public TextBox title, enter, settings, continueGame; - public ArrayList textBoxArray = new ArrayList(); + public ArrayList textBoxArray = new ArrayList<>(); public Font standardFont = new Font(Font.MONOSPACED, Font.BOLD, 60); public int playerFrame, enemyFrame; // keeps track of how many ticks has elapsed since last frame change @@ -39,28 +40,30 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ public static boolean gameStart = false; - // image imports begin here public BufferedImageWrapper backgroundImage = new BufferedImageWrapper(("img/backgrounds/pointyMountains.png")); public MenuPanel(CameraPanel gameFrame) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException { this.gameFrame = gameFrame; + // initialize shared camera to ensure that the background stays consistent between MenuPanel and SettingPanel camera = gameFrame.camera; - + // create title textbox title = new TextBox(100, 400, 100, GAME_WIDTH, standardFont, "Platformer", null); + // initialize selectable menu options and add the text boxes to the textBoxArray, which controls which text box is currently highlighted continueGame = new TextBox(300, 600, 100, GAME_WIDTH, standardFont, "Continue", "game"); enter = new TextBox(400, 600, 100, GAME_WIDTH, standardFont, "Start Game", "game-start"); settings = new TextBox(500, 600, 100, GAME_WIDTH, standardFont, "Settings", "settings"); textBoxArray.add(enter); textBoxArray.add(settings); + // if there is a loadable save, isContinue is set to true and the Continue textbox is selectable if (GameFrame.game.isContinue) { textBoxArray.add(0, continueGame); } background = new BackgroundImage(0, 0, backgroundImage, GAME_WIDTH, GAME_HEIGHT, 10, camera); - // the height of 35 is set because it is half of the original tile height (i.e., 70px) this.setFocusable(true); //make everything in this class appear on the screen this.addKeyListener(this); //start listening for keyboard input // request focus when the CardLayout selects this game + // this allows the OS to forward HID input to this panel this.addComponentListener(new ComponentAdapter() { @Override @@ -73,6 +76,7 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ //add the MousePressed method from the MouseAdapter - by doing this we can listen for mouse input. We do this differently from the KeyListener because MouseAdapter has SEVEN mandatory methods - we only need one of them, and we don't want to make 6 empty methods addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { + // check if the mouse is hovering over a textbox; if it is, run the action connected to the textbox if (hoverCheck(e)) { keyPressed(new KeyEvent(new Component() { }, 0, -1, 0, KeyEvent.VK_ENTER, (char)KeyEvent.VK_ENTER)); @@ -81,6 +85,7 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved(MouseEvent e) { + // check if the mouse is now hovering over a text box; if it is, select that textbox hoverCheck(e); } }); @@ -103,20 +108,25 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ //call the draw methods in each class to update positions as things move public void draw(Graphics g, int playerFrame, int enemyFrame){ + // draw background background.draw(g); + // draw title title.draw(g,null, Color.black); + // if there is no loadable save, gray out the Continue textbox if (!GameFrame.game.isContinue) { continueGame.draw(g, null, Color.gray); } + // draw each selectable textBox in the textbox array for (TextBox t: textBoxArray) { t.draw(g, null, Color.cyan); } + // overwrite the selectable text box with a background color and a different text color (blue instead of cyan) textBoxArray.get(currentBox).draw(g, Color.gray, Color.blue); } - //call the move methods in other classes to update positions - //this method is constantly called from run(). By doing this, movements appear fluid and natural. If we take this out the movements appear sluggish and laggy - public void move(){ + // this function does nothing, but was retained to allow the children of this class to implement their own functions without also having to reimplement run() + // e.x., a child could override doAction() to launch someFunction() + public void doAction(){ } //run() method is what makes the game continue running without end. It calls other methods to move objects, check for collision, and update the screen @@ -135,7 +145,8 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ //only move objects around and update screen if enough time has passed if(delta >= 1){ - move(); + doAction(); + // shift the camera to cause the parallax effect camera.x += 10; repaint(); delta--; @@ -143,8 +154,10 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ } } + // check if the mouse is hovering over a textbox; if the mouse is hovering over a textbox, select the textbox hovered over public boolean hoverCheck(MouseEvent e) { for (TextBox t: textBoxArray) { + // select the textbox hovered over if it is hovering over a textbox if (t.isHover(e.getX(), e.getY())) { currentBox = textBoxArray.indexOf(t); return true; @@ -155,35 +168,48 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ //if a key is pressed, we'll send it over to the Player class for processing public void keyPressed(KeyEvent e) { - + // intercept keypresses and replace them with previously defined keypresses through the Middleware class e = UtilityFunction.intercept(e, GameFrame.game.middlewareArray); + // if the keypress is ENTER, run the action indicated by the connected textbox's id if (e.getKeyCode() == KeyEvent.VK_ENTER) { + // indicate that the game is starting if the player presses "Continue" or "New Game" if(textBoxArray.get(currentBox).id.contains("game")){ gameStart = true; } // always unpause game, no matter what screen is navigated to GameFrame.game.isPaused = false; // logic for different screens starts here + // if the user presses "New Game", reset GamePanel and navigate to it if (textBoxArray.get(currentBox).id.equals("game-start")) { try { + // remove the old GamePanel from the CardLayout CameraPanel GameFrame.main.remove(GameFrame.game); + // stop the run() while loop, effectively killing the thread GameFrame.game.isRunning = false; + // reset player velocities to prevent race conditions GameFrame.game.player.xVelocity = 0; GameFrame.game.player.yVelocity = 0; + // reset the tile map to prevent race conditions GameFrame.game.map = new Tile[1000][18]; GameFrame.game = new GamePanel(GameFrame.main); //run GamePanel constructor + // make it so that the game can be resumed if the player leaves GameFrame.game.isContinue = true; textBoxArray.add(continueGame); + // start the game GameFrame.game.startThread(); - GameFrame.main.add(GameFrame.game, "game", 0); + // add the game to the CardLayout CameraPanel, enabling navigation + GameFrame.main.add(GameFrame.game, "game"); } catch (IOException | SpriteException | UnsupportedAudioFileException | LineUnavailableException ex) { ex.printStackTrace(); } + // switch to the game panel ((CardLayout)gameFrame.getLayout()).show(gameFrame, "game"); } else { + // switch to the panel indicated by the id of the textbox ((CardLayout) gameFrame.getLayout()).show(gameFrame, textBoxArray.get(currentBox).id); } } else { + // if the keypress is not ENTER, either select the textbox above or below the currently selected textbox currentBox = UtilityFunction.processBox(e, currentBox, textBoxArray); } } diff --git a/src/Middleware.java b/src/Middleware.java index b88aaf8..77f3f49 100644 --- a/src/Middleware.java +++ b/src/Middleware.java @@ -1,47 +1,38 @@ -import com.sun.jdi.request.DuplicateRequestException; - +// Eric Li, Charlie Zhao, ICS4U, Finished 6/16/2022 +// intercepts keystrokes and substitutes new keystrokes in the place of the old keystrokes +// allows for arbitrary replacement of keystrokes, including chained replacement of keystrokes if necessary +// although those features have not yet been implemented import java.awt.event.KeyEvent; import java.io.Serializable; import java.util.ArrayList; public class Middleware implements Serializable { - public static ArrayList allOldCode = new ArrayList(); - public static ArrayList allNewCode = new ArrayList(); + public static ArrayList allOldCode = new ArrayList<>(); + public static ArrayList allNewCode = new ArrayList<>(); public final int oldCode; public final int newCode; public boolean isDestroyed = false; + // create new Middleware which intercepts newCode and replaces it with oldCode Middleware(int oldCode, int newCode) { - // if (!canCreate(oldCode, newCode)) { - // TODO: replace with more appropriate exception - // throw new DuplicateRequestException(); - // } allOldCode.add(oldCode); allNewCode.add(newCode); this.oldCode = oldCode; this.newCode = newCode; } + // checks if the keypress can be intercepted (i.e., if it is the same keyCode as newCode) public boolean canIntercept(KeyEvent e) { return e.getKeyCode() == newCode && !isDestroyed; } + // intercepts the key if it has been found to be interceptable public KeyEvent interceptKey(KeyEvent e) { e.setKeyCode(oldCode); return e; } - public void destroy() { - allOldCode.remove(oldCode); - allNewCode.remove(newCode); - isDestroyed = true; - } - - public static boolean canCreate(int oldCode, int newCode) { - return (!allOldCode.contains(oldCode) && !allNewCode.contains(newCode)); - } - @Override public boolean equals(Object o) { try { diff --git a/src/NonPlayer.java b/src/NonPlayer.java index c685f76..70f6714 100644 --- a/src/NonPlayer.java +++ b/src/NonPlayer.java @@ -1,16 +1,14 @@ -/* Eric Li, ICS4U, Completed 5/29/2022 +/* Eric Li, ICS4U, Completed 6/19/2022 -Paddle class defines behaviours for the left and right player-controlled paddles */ +NonPlayer class defines behaviour for enemies and characters that are not controlled by the player */ import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import java.awt.*; -import java.awt.image.BufferedImage; import java.io.IOException; import java.io.Serializable; public class NonPlayer extends GenericSprite implements Serializable { - public final int SPEED = 3; // please note that these are not static, in contrast to the player class, as different enemies will have different heights public int npcWidth; public int npcHeight; @@ -22,7 +20,6 @@ public class NonPlayer extends GenericSprite implements Serializable { public int health; public double fadeCounter; - // private final Sound bump; public BufferedImageWrapper[][][] spriteArray; public NonPlayer(int x, int y, BufferedImageWrapper[][][] sprites, int npcWidth, int npcHeight, int health) throws UnsupportedAudioFileException, LineUnavailableException, IOException { diff --git a/src/Particle.java b/src/Particle.java index e1eda69..c0734b0 100644 --- a/src/Particle.java +++ b/src/Particle.java @@ -1,5 +1,4 @@ import java.awt.*; -import java.awt.image.BufferedImage; import java.io.IOException; import java.io.Serializable; diff --git a/src/SafeObjectInputStream.java b/src/SafeObjectInputStream.java index 3230005..d0e4715 100644 --- a/src/SafeObjectInputStream.java +++ b/src/SafeObjectInputStream.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; -import java.util.ArrayList; import java.util.List; public class SafeObjectInputStream extends ObjectInputStream { diff --git a/src/SettingPanel.java b/src/SettingPanel.java index de31668..5198958 100644 --- a/src/SettingPanel.java +++ b/src/SettingPanel.java @@ -10,14 +10,11 @@ Implements Runnable interface to use "threading" - let the game do two things at import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; -import javax.swing.*; import java.awt.*; -import java.awt.event.*; -import java.awt.image.BufferedImage; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; import java.io.IOException; -import java.security.Key; import java.util.ArrayList; -import java.util.Arrays; public class SettingPanel extends MenuPanel { @@ -87,7 +84,7 @@ public class SettingPanel extends MenuPanel { } // move is repurposed to change key bind - public void move() { + public void doAction() { changeKeyBind(); } diff --git a/src/SingleTile.java b/src/SingleTile.java index 445dc4c..70ce65a 100644 --- a/src/SingleTile.java +++ b/src/SingleTile.java @@ -1,5 +1,4 @@ import java.awt.*; -import java.awt.image.BufferedImage; import java.io.Serializable; public class SingleTile extends Tile implements Serializable { diff --git a/src/SoundWrapper.java b/src/SoundWrapper.java index ac1eed8..f1e0c26 100644 --- a/src/SoundWrapper.java +++ b/src/SoundWrapper.java @@ -1,10 +1,6 @@ import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import java.io.*; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.Clip; -import javax.swing.*; public class SoundWrapper implements Serializable { transient public Sound sound; diff --git a/src/StickyBomb.java b/src/StickyBomb.java index 4b673c7..b5ee74e 100644 --- a/src/StickyBomb.java +++ b/src/StickyBomb.java @@ -1,9 +1,6 @@ import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; import java.io.IOException; import java.io.Serializable; diff --git a/src/UtilityFunction.java b/src/UtilityFunction.java index 5bb6c46..e1edcf3 100644 --- a/src/UtilityFunction.java +++ b/src/UtilityFunction.java @@ -1,7 +1,4 @@ import java.awt.event.KeyEvent; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.lang.reflect.Array; import java.util.ArrayList; public final class UtilityFunction {