Answer To: The major assignment involves producing a Digger style game with Clara using the project files that...
Aditi answered on Jun 16 2022
My Clara - Digger
Implementation
The game's implementation contains examples of effective/efficient class hierarchy (including abstract classes and interfaces), user input through action listeners, simple graphics in Java, cycle-based game progression, and level information loaded to external files.
General Game Structure
Most classes in this repo are examples of "game components" (not to be confused with JComponent-related classes). Game components are objects that must either change properties when a new cycle occurs or be interacted with by other game components. Examples include the digger, the bullet, the enemies, the dirt squares, the emeralds, the gold bags, and the gold coins. The game progresses by loading up a list of game components on the field, and executing their progressCycle methods. One-by-one, the game components take in information about their surroundings and update their states accordingly.
The Digger
The digger is controlled by several action listeners corresponding to the arrow keys (for movement) and the space bar (for shooting). Because of the large-cell grid system that the game uses to handle positioning, cycles for movement (i.e. almost all cycle timing) must be kept far apart. Because of this, it was found effective to simply call checks on the state of the arrow keys and space bar at each cycle change to determine the action for the digger to take. It follows that the game would take on a sort of "turn-based action" system, in which the player must take the short amount of time between cycles to decide what action to take.
The Enemies
The enemies' actions are also taken on the same cycle points as the digger, but unlike the diggers' actions, the enemies' are determined by a simple AI script that varies depending on the type of enemy (and notably, whether or not it can dig through dirt blocks).
The Collectibles
The gold bag follows a simple script to determine whether it needs to fall on any given cycle (i.e. if nothing is below it) or whether to break open (i.e. if it has fallen more than one square since it started falling). The gold coins and emeralds follow virtually no scripting, as they take no actions.
The Levels
Levels in this game are stored in external files consisting of different characters aligned to represent different game components. For example, a 'd' in the top-left position of the file signifies a dirt block to be placed in the top-left corner of the level. When a new level is loaded, the game reads through the file and places a game component at each square in the grid (or nothing, if the space is an underscore).
Create a new java project in your NetBeans IDE.
Inside the src folder, create the following classes and interfaces:
Bullet.java
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
public class Bullet extends Component {
// Instance variables
private String direction;
private static final int CYCLES_PER_TURN = 15;
private int cyclesUntilTurn;
/**
* Constructs a new Bullet object given a starting direction and a level
*
* @param direction
* the starting direction of the Bullet
* @param level
* the level in which to put the Bullet
*/
public Bullet(String direction, Level level) {
super();
this.level = level;
this.direction = direction;
this.cyclesUntilTurn = CYCLES_PER_TURN;
}
/**
* Draws the Bullet object to the given Graphics2D object
*
* @param g
* the Graphics2D object to draw to
*/
@Override
public void drawTo(Graphics2D g) {
g.translate(this.getPosition().getX() * UI.CELL_SIDE_SIZE, this
.getPosition().getY() * UI.CELL_SIDE_SIZE);
Rectangle2D.Double bgRect = new Rectangle2D.Double(0, 0, UI.CELL_SIDE_SIZE, UI.CELL_SIDE_SIZE);
g.setColor(Color.BLACK);
g.fill(bgRect);
Rectangle2D.Double bulletRect = new Rectangle2D.Double(20, 20, 10, 10);
g.setColor(Color.GRAY);
g.fill(bulletRect);
g.translate(-this.getPosition().getX() * UI.CELL_SIDE_SIZE, -this
.getPosition().getY() * UI.CELL_SIDE_SIZE);
}
/**
* Moves the Bullet one space in the direction of travel
*/
public void takeTurn() {
switch (this.direction) {
case "up":
if (this.getPosition().getY() > 0)
this.moveTo(new Coordinate(this.getPosition().getX(), this
.getPosition().getY() - 1));
else
this.die();
break;
case "down":
if (this.getPosition().getY() < 9)
this.moveTo(new Coordinate(this.getPosition().getX(), this
.getPosition().getY() + 1));
else
this.die();
break;
case "left":
if (this.getPosition().getX() > 0)
this.moveTo(new Coordinate(this.getPosition().getX() - 1, this
.getPosition().getY()));
else
this.die();
break;
case "right":
if (this.getPosition().getX() < 9)
this.moveTo(new Coordinate(this.getPosition().getX() + 1, this
.getPosition().getY()));
else
this.die();
break;
}
}
/**
* Gets the position of the sprite on the drawing component
*
* @return the sprite position
*/
/**
* Reacts to a cycle change in the game
*/
@Override
public void progressCycle() {
if (this.cyclesUntilTurn == 0) {
this.takeTurn();
this.cyclesUntilTurn = CYCLES_PER_TURN;
} else {
this.cyclesUntilTurn--;
}
}
/**
* Collides with another component
*
* @param component
* the component to collide with
*/
@Override
public void collideWith(Component component) {
this.die();
if (component instanceof Enemy) {
component.die();
}
}
}
Component.java
public abstract class Component implements Movable, Drawable {
// Instance variables
/**
* The position of this Component in the level's grid.
*/
protected Coordinate position;
/**
* The level to which this Component belongs.
*/
protected Level level;
/**
* Returns the level that this Component belongs to
*
* @return level
*/
public Level getLevel() {
return this.level;
}
/**
* Handles moving to another space. If the space is null, this method will
* handle the moving. If the space is not null, the collideWith method must
* handle movement.
*
* @param destination
* the space to move to
*/
@Override
public void moveTo(Coordinate destination) {
if (this.getLevel().getComponentAt(destination) == null) {
this.getLevel().setCoordinateTo(destination, this);
this.getLevel().clearCoordinate(this.getPosition());
this.setPosition(destination);
} else {
this.collideWith(this.level.getComponentAt(destination));
}
}
/**
* Sets the position of the Component on the grid
*
* @param newPosition
* the position to move the Component to
*/
public void setPosition(Coordinate newPosition) {
this.position = newPosition;
}
/**
* Gets the current position of the Component on the grid
*
* @return position
*/
@Override
public Coordinate getPosition() {
return this.position;
}
/**
* Removes the Component from the grid
*/
public void die() {
this.getLevel().clearCoordinate(this.getPosition());
}
// Abstract methods
/**
* Handles a collision between this Component and the other specified Component.
*
* @param component
*/
public abstract void collideWith(Component component);
/**
* Handles a single progression of the game's cycle.
*/
public abstract void progressCycle();
}
Coordinate.java
public class Coordinate {
private int x;
private int y;
/**
* Constructs a new Coordinate object with the given X and Y values
*
* @param i
* the X value
* @param j
* the Y value
*/
public Coordinate(int i, int j) {
this.x = i;
this.y = j;
}
/**
* Sets the X value of the Coordinate
*
* @param x
*/
public void setX(int x) {
this.x = x;
}
/**
* Sets the Y value of the Coordinate
*
* @param y
*/
public void setY(int y) {
this.y = y;
}
/**
* Returns the X value of the Coordinate
*
* @return x
*/
public int getX() {
return this.x;
}
/**
* Returns the Y value of the Coordinate
*
* @return y
*/
public int getY() {
return this.y;
}
}
CycleTimer.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class CycleTimer implements ActionListener {
// Instance variables
private Game game;
/**
* Constructs a new CycleTimer object with the given Game object
*
* @param game
*/
public CycleTimer(Game game) {
this.game = game;
}
/**
* Handles a timer event by progressing the cycle of the game
*
* @param e
* the ActionEvent called
*/
@Override
public void actionPerformed(ActionEvent e) {
this.game.progressCycle();
}
}
Dirt.java
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
public class Dirt extends Component {
//Instance variables
private Ellipse2D.Double[] rocks;
/**
* Constructs a new Dirt object with no given values
*
* @param level
*/
public Dirt(Level level) {
super();
this.level = level;
this.rocks = new Ellipse2D.Double[5];
for (int i = 0; i < 5; i++) {
rocks[i] = new Ellipse2D.Double(Math.random() * UI.CELL_SIDE_SIZE,
Math.random() * UI.CELL_SIDE_SIZE, 1, 1);
}
}
/**
* Draws the Dirt object to the given Graphics2D object
*
* @param g
* the object to draw to
*/
@Override
public void drawTo(Graphics2D g) {
g.translate(this.getPosition().getX() * UI.CELL_SIDE_SIZE, this
.getPosition().getY() * UI.CELL_SIDE_SIZE);
Rectangle2D.Double dirtRect = new Rectangle2D.Double(0, 0,
UI.CELL_SIDE_SIZE, UI.CELL_SIDE_SIZE);
g.setColor(new Color(102, 51, 0));
g.fill(dirtRect);
g.setColor(Color.BLACK);
for(int i = 0; i < 5; i++) {
g.draw(rocks[i]);
g.fill(rocks[i]);
}
if (this.getPosition().getY() == 0) {
Rectangle2D.Double grassRect = new Rectangle2D.Double(0, 0,
UI.CELL_SIDE_SIZE, UI.CELL_SIDE_SIZE * 0.4);
g.setColor(Color.GREEN);
g.fill(grassRect);
Rectangle2D.Double skyRect = new Rectangle2D.Double(0, 0,
UI.CELL_SIDE_SIZE, UI.CELL_SIDE_SIZE * 0.2);
g.setColor(new Color(51, 255, 255));
g.fill(skyRect);
}
g.translate(-this.getPosition().getX() * UI.CELL_SIDE_SIZE, -this
.getPosition().getY() * UI.CELL_SIDE_SIZE);
}
/**
* Used in other components, but not in the Dirt component
*/
@Override
public void moveTo(Coordinate destination) {
// Do nothing
}
/**
* Used in other components, but not in the Dirt component
*/
@Override
public void progressCycle() {
// Do nothing
}
/**
* Used in other components, but not in the Dirt component
*/
@Override
public void collideWith(Component component) {
// Do nothing
}
}
Drawable.java
import java.awt.Graphics2D;
public interface Drawable {
/**
* Draws the object to the specified graphics object.
*
* @param g
*/
void drawTo(Graphics2D g);
}
Emerald.java
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.Rectangle2D;
public class Emerald extends Component {
// Instance variables
private static final int VALUE = 50;
/**
* Constructs a new Emerald object with the given Level object
*
* @param level
*/
public Emerald(Level level) {
super();
this.level = level;
}
/**
* Draws the Emerald object to the given Graphics2D object
*
* @param g
* the object to draw to
*/
@Override
public void drawTo(Graphics2D g) {
g.translate(this.getPosition().getX() * UI.CELL_SIDE_SIZE, this
.getPosition().getY() * UI.CELL_SIDE_SIZE);
Rectangle2D.Double blackBG = new Rectangle2D.Double(0, 0, UI.CELL_SIDE_SIZE, UI.CELL_SIDE_SIZE);
g.setColor(new Color(102, 51, 0));
g.fill(blackBG);
int[] xCoords = new int[6];
int[] yCoords = new int[6];
xCoords[0] = 0;
xCoords[1] = 8;
xCoords[2] = 25;
xCoords[3] = 41;
xCoords[4] = 49;
xCoords[5] = 25;
yCoords[0] = 15;
yCoords[1] = 6;
yCoords[2] = 1;
yCoords[3] = 6;
yCoords[4] = 15;
yCoords[5] = 49;
Polygon emeraldBG = new Polygon(xCoords, yCoords, 6);
g.setColor(Color.GREEN);
g.fill(emeraldBG);
g.setColor(Color.BLACK);
g.drawLine(0, 15, 25, 49);
g.drawLine(8, 6, 25, 49);
g.drawLine(25, 1, 25, 49);
g.drawLine(41, 6, 25, 49);
g.drawLine(49, 15, 25, 49);
g.drawLine(0, 15, 49, 15);
g.translate(-this.getPosition().getX() * UI.CELL_SIDE_SIZE, -this
.getPosition().getY() * UI.CELL_SIDE_SIZE);
}
/**
* Returns the number of points picking up this item gives to the player.
*
* @return the value of emerald
*/
public int getValue() {
return VALUE;
}
/**
* Used in other components, but not the Emerald component
*/
@Override
public void collideWith(Component component) {
// Do nothing
}
/**
* Used in other components, but not the Emerald component
*/
@Override
public void progressCycle() {
// Do nothing
}
}
Enemy.java
public abstract class Enemy extends Component {
private static final int CYCLES_PER_TURN = 28;
private int cyclesUntilTurn;
/**
* Constructs a new Enemy object with the given respawn position
*
* @param respawnPosition
*/
public Enemy() {
super();
this.cyclesUntilTurn = CYCLES_PER_TURN;
}
/**
* Handles a collision with the given Component object
*
* @param component
* the Component to collide with
*/
@Override
public void collideWith(Component component) {
if (component instanceof Player || component instanceof Bullet) {
this.die();
component.die();
} else {
Coordinate destination = component.getPosition();
component.die();
this.getLevel().setCoordinateTo(destination, this);
this.getLevel().clearCoordinate(this.getPosition());
this.setPosition(destination);
}
}
/**
* Handles a cycle change in the game
*/
@Override
public void progressCycle() {
if (this.cyclesUntilTurn == 0) {
this.takeTurn();
this.cyclesUntilTurn = CYCLES_PER_TURN;
} else {
this.cyclesUntilTurn--;
}
}
/**
* Moves the Enemy in the direction given by getDirectionToMove
*/
public void takeTurn() {
switch (this.getDirectionToMove()) {
case "up":
if (this.getPosition().getY() > 0)
this.moveTo(new Coordinate(this.getPosition().getX(), this
.getPosition().getY() - 1));
break;
case "down":
if (this.getPosition().getY() < 9)
this.moveTo(new Coordinate(this.getPosition().getX(), this
.getPosition().getY() + 1));
break;
case "left":
if (this.getPosition().getX() > 0)
this.moveTo(new Coordinate(this.getPosition().getX() - 1, this
.getPosition().getY()));
break;
case "right":
if (this.getPosition().getX() < 9)
this.moveTo(new Coordinate(this.getPosition().getX() + 1, this
.getPosition().getY()));
break;
}
}
// Abstract methods
/**
* @return the direction the enemy's AI decides it should move
*/
public abstract String getDirectionToMove();
}
Game.java
import java.util.ArrayList;
public class Game {
// Instance variables
private int score;
private Level level;
private int levelNumber;
private UI ui;
private Player player;
private boolean paused;
/**
* Constructs a new Game object with no given values
*/
public Game() {
this.score = 0;
this.ui = new UI(this);
this.level = new Level(1, this);
this.levelNumber = 1;
this.paused = false;
this.ui.updateLevel();
this.ui.createWindow();
}
/**
* Pauses the game if it is currently running, resumes the game if it is currently paused.
*/
public void togglePaused(){
if(this.paused){
this.paused = false;
}else{
this.paused = true;
}
}
/**
* @return true if the game is paused.
*/
public boolean getPaused(){
return this.paused;
}
/**
* Sets the current Player object
*
* @param p
* the Player object
*/
public void setPlayer(Player p) {
this.player = p;
}
/**
* Sets the curent level number and Level object
*
* @param levelNum
* the number of the level
*/
public void setLevel(int levelNum) {
if (levelNum > 0 && levelNum < 3) {
this.levelNumber = levelNum;
this.level = new Level(this.levelNumber, this);
this.ui.getLevelComponent().setLevel(this.level);
}
}
/**
* Progresses the game by one cycle
*/
public void progressCycle() {
if (this.player.alive() && !this.paused) {
ArrayList gameComponents = new ArrayList();
for (int i = 0; i < this.level.getGrid().length; i++) {
for (int j = 0; j < this.level.getGrid()[i].length; j++) {
if (this.level.getGrid()[i][j] != null)
gameComponents.add(this.level.getGrid()[i][j]);
}
}
for (Component comp : gameComponents) {
comp.progressCycle();
}
this.level.progressRespawns();
this.ui.drawLevel();
if (this.level.countEmeralds() == 0) {
this.setLevel(this.levelNumber + 1);
}
}
}
/**
* Gets the current score
*
* @return score
*/
public int getScore()...