I am writing a small game where 20 balloons are created on screen and mouse released on them expands them. They are supposed to 'pop' when one balloon touches another, but at present when I click a balloon it pops a random one and throws an 'Array index out of bounds' exception. I've racked my brain to figure out why my code isn't working but just can't get it. Here's some of the code causing the problem:
import comp102.*;
import java.util.*;
import java.awt.Color;
public class BalloonGame implements UIButtonListener, UIMouseListener{
// Fields
private final int numBalloons = 20;
private int currentScore; // the score for the current game
private int highScore = 0; // highest score in all games so far.
private int totalPopped = 0;
Balloon balloons[] = new Balloon[numBalloons];
// Constructor
/** Set up the GUI, start a new game.
*/
public BalloonGame(){
UI.setMouseListener(this);
UI.addButton("New Game", this);
UI.addButton("Lock Score", this);
this.newGame();
}
// GUI Methods to respond to buttons and mouse
/** Respond to button presses, to start a new game and to end the current game */
public void buttonPerformed(String cmd){
if (cmd.equals("New Game")) { this.newGame(); }
else if (cmd.equals("Lock Score")) { this.endGame(); }
}
/** Respond to mouse released with the main action of the game*/
public void mousePerformed(String action, double x, double y) {
if (action.equals("released")) {
this.doAction(x, y);
}
}
/** Start the game:
Clear the graphics pane
Initialise the score information
Make a new set of Balloons at random positions
*/
public void newGame(){
UI.clearGraphics();
this.currentScore = 0;
this.totalPopped = 0;
for (int i = 0; i < this.balloons.length; i++) {
this.balloons[i] = new Balloon(50 + Math.random()*400, 50 + Math.random()*400);
this.balloons[i].draw();
}
UI.printMessage("New game: click on a balloon. High score = "+this.highScore);
}
/** Main game action.
Find the balloon at (x,y) if any,
Expand it
Check whether it is touching another balloon,
If so, update totalPopped, pop both balloons, and remove them from the list
Recalculate the score.
If there are no balloons left, end the game.
*/
public void doAction(double x, double y) {
for (int i = 0; i < this.balloons.length; i++) {
if (this.balloons[i].on(x, y) && !this.balloons[i].isPopped()) {
this.balloons[i].expand();
}
for (int j = 1; j <this.balloons.length; j++) {
if (this.balloons[i].isTouching(this.balloons[j]) && this.balloons[j] != null)
{
this.totalPopped +=2;
this.balloons[i].pop();
this.balloons[j].pop();
this.balloons[i] = null;
this.balloons[j] = null;
}
}
}
this.calculateScore();
if (totalPopped == numBalloons) {
this.endGame();
}
}
/** Find a balloon that the point (x, y) is on.
* Returns null if point is not on any balloon*/
public Balloon findBalloon(double x, double y){
return null;
}
/** Find and return another balloon that is touching this balloon
* Returns null if no such Balloon. */
public Balloon findTouching(Balloon balloon){
return null;
}
/** Calculate the score: sum of the sizes of current ballons, minus
the total of the popped balloons (totalPopped).
Report the score as a message */
public void calculateScore(){
for (Balloon b: balloons) {
this.currentScore += b.size();
}
if (currentScore >= highScore) {
this.highScore = this.currentScore;
}
UI.printMessage("Score = "+this.currentScore+" High score = "+this.highScore);
}
/** Returns true if all the balloons have been popped,
* Returns false if any of the balloons is not popped */
public boolean allPopped(){
for (Balloon b : this.balloons){
if (!b.isPopped()){
return false;
}
}
return true;
}
/** End the current game.
Record the the score as the new high score if it is better
Print a message
Clear the list of balloons (so the player can't keep playing)
*/
public void endGame(){
this.highScore = this.currentScore;
UI.println("High score = " + this.highScore);
Arrays.fill(balloons, null);
}
// Main
public static void main(String[] arguments){
BalloonGame ob = new BalloonGame();
}
}
uses the balloon class also:
import comp102.*;
import java.util.*;
import java.awt.Color;
import java.io.*;
/** Represents a balloon that can grow until it pops.
A Balloon can say whether a particular point is on it, and
whether it is touching another balloon.
It can also return its size.
Once it has popped, no point is on it, and it can't touch another balloon.
Also, its size is reported as a negative value.
*/
public class Balloon{
// Fields
private double radius = 10;
private double centerX, centerY;
private Color color;
private boolean popped = false;
// Constructors
/** Construct a new Balloon object.
Parameters are the coordinates of the center of the balloon
Does NOT draw the balloon yet.
*/
public Balloon(double x, double y){
this.centerX = x;
this.centerY = y;
this.color = Color.getHSBColor((float)Math.random(), 1.0f, 1.0f);
}
public void draw(){
UI.setColor(color);
UI.fillOval(centerX-radius, centerY-radius, radius*2, radius*2);
if (!this.popped){
UI.setColor(Color.black);
UI.drawOval(centerX-radius, centerY-radius, radius*2, radius*2);
}
}
/** Make the balloon larger by a random amount between 4 and 10*/
public void expand(){
if (! this.popped){
this.radius = this.radius + (Math.random()*6 + 4);
this.draw();
}
}
/** pop the balloon (changes colour to gray, draws, and pauses briefly)*/
public void pop(){
this.color = Color.lightGray;
this.popped = true;
this.draw();
UI.sleep(20);
}
/** Returns true if the balloon has been popped */
public boolean isPopped(){
return this.popped;
}
/** Returns true if the point (x,y) is on the balloon, and false otherwise */
public boolean on(double x, double y){
if (popped) return false;
double dx = this.centerX - x;
double dy = this.centerY - y;
return ((dx*dx + dy*dy) < (this.radius * this.radius));
}
/** Returns true if this Balloon is touching the other balloon, and false otherwise
* Returns false if either balloon is popped. */
public boolean isTouching(Balloon other){
if (this.popped || other.popped) return false;
double dx = other.centerX - this.centerX;
double dy = other.centerY - this.centerY;
double dist = other.radius + this.radius;
return (Math.hypot(dx,dy) < dist);
}
/** Calculates and returns the area of the balloon
* Returns it in "centi-pixels" (ie, number of pixels/100)
* to keep them in a reasonable range.
* Returns a negative size if it is popped.*/
public int size(){
int s = (int) ((this.radius * this.radius * Math.PI)/100);
if (popped) { s = 0 - s; }
return s;
}
}
is this right?
for (int j = 1; j <this.balloons.length; j++) {
doesn't it allow i and j to be equal, so you end up asking whether a balloon is touching itself? Do you mean j = i + 1 ?
If you are now getting null pointer exceptions, get into a debugger and step through until you see where. My guess is that you are visiting array items that have been popped, and hence are null.
if (this.balloons[i].isTouching(this.balloons[j]) && this.balloons[j] != null)
You are testing this.balloons[j] for null after you are using it. I'd put some null checks before trying to work with each item.
Related
I'm new to ListNodes and nodes in general. I have successfully built the starting snake for my game and it runs as planned. I am stuck trying to figure out how to add additional links to the snake. I also think there are some places in my code where I can significantly simplify things. Any advice on places to simplify or better create this application would be greatly appreciated.
`
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.animation.AnimationTimer;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
/**
* A Snake game. A snake consisting of a sequence of circles moves around on the board.
* The user controls the direction of motion with the arrow keys. The snake sometimes
* grows a new segment. If the snake tries to move outside the board, or if the head
* of snake collides with one of its body segments, then the game is over.
*/
public class Snake extends Application {
private final int SQUARE_SIZE = 15; // The size of each snake segment; the size of one snake segment.
private final int ROWS = 40, COLUMNS = 50; /* The board is made up of squares that can each hold
one snaked segment. These constants give the number of
rows and the number of columns of squares. The snake head
moves one full square up, down, left, or right in each frame
of the animation. */
private Canvas canvas; // The canvas that fills the entire window, where all drawing is done.
private GraphicsContext g; // A graphics context for drawing on the canvas.
private boolean running; // Set to true when the animation is running (and the snake is moving.)
private boolean gameover; // Set to true when the game ends because of a crash.
private AnimationTimer animator; // Timer that drives the animation.
private int direction = 1; // Current direction of motion. 1, 2, 3, 4 for RIGHT, LEFT, UP, DOWN.
private int frameNumber; // Frame number of the current frame; starts at 0 when a game is started.
private static class ListNode {
int atX;
int atY;
ListNode next;
ListNode prev;
public ListNode(int x, int y) {
atX = x;
atY = y;
next = null;
}
public ListNode(int x, int y, ListNode n) {
atX = x;
atY = y;
next = n;
}
}
private static ListNode head;
/**
* Set up for a new game of Snake. A new snake is created, in its initial position. Animation is turned off.
* The gameover variable is false. A temporary message, "Press space bar to start", is displayed on the canvas.
*/
private void startGame() {
int x = ROWS/2;
int y = (COLUMNS/2);
head = new ListNode(x,y-4);
ListNode b = new ListNode(x,y-3,head);
head = b;
ListNode c = new ListNode(x,y-2,head);
head = c;
ListNode d = new ListNode(x,y-1,head);
head = d;
ListNode e = new ListNode(x,y,head);
head = e;
draw();
g.setFill(Color.RED.darker());
g.setFont(Font.font(20));
g.fillText("Press space bar to start.", canvas.getWidth()/2, canvas.getHeight()/2+60);
gameover = false;
direction = 1;
frameNumber = 0;
}
/**
* This method is called when a crash occurs and the game has to end. The animation is stopped,
* The gameover variable becomes true. A temporary message is added to the canvas saying "GAME OVER".
*/
private void gameOver() {
animator.stop();
running = false;
gameover = true;
g.setFill(Color.YELLOW);
g.setFont(Font.font(80));
g.fillText("GAME OVER", 40, canvas.getHeight() - 40);
g.strokeText("GAME OVER", 40, canvas.getHeight() - 40);
}
/**
* This method is called in each frame to test whether moving the snake head to
* the given row and column will cause a crash. A crash happens if the given position
* is outside the limits of the game board, or if the the given position is already
* occupied by one of the segments of the snake.
*/
private boolean crash(int row, int col) {
if (row < 0 || row >= ROWS || col < 0 || col >= COLUMNS) { // position outside the board
return true;
}
return false;
}
/**
* Move the snake forward one square. The snake head should be moved to the given newRow
* and newCol. (These values are one square horizontally or vertically away from the
* current position of the snake head.) All the other segments of the snake move up
* one position. Sometimes a new snake segment is added at the end of the snake; this
* allows the snake to grow with time.
*/
private void moveSnake(int newRow, int newCol) {
int aX = head.next.next.next.next.atX;
int aY = head.next.next.next.next.atY;
int bX = head.next.next.next.atX;
int bY = head.next.next.next.atY;
int cX = head.next.next.atX;
int cY = head.next.next.atY;
int dX = head.next.atX;
int dY = head.next.atY;
int eX = head.atX;
int eY = head.atY;
head.atX = newRow;
head.atY = newCol;
head.next.atX = eX;
head.next.atY = eY;
head.next.next.atX = dX;
head.next.next.atY = dY;
head.next.next.next.atX = cX;
head.next.next.next.atY = cY;
head.next.next.next.next.atX = bX;
head.next.next.next.next.atY = bY;
draw();
}
/**
* Fills the canvas with white, then draws the snake in its current position.
*/
private void draw() {
GraphicsContext g = canvas.getGraphicsContext2D();
g.setFill(Color.WHITE);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
g.setFill(Color.BLUE);
g.fillOval(head.atY*SQUARE_SIZE, head.atX*SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);
for (ListNode runner = head.next; runner != null; runner = runner.next) {
g.setFill(Color.PINK);
g.fillOval(runner.atY*SQUARE_SIZE, runner.atX*SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);
}
}
/**
* This method is called for each frame of the animation. It moves the snake
* by one square in the current direction of motion, unless doing so would
* cause a crash. It a crash would occur, the game is terminated instead.
*/
private void doFrame() {
if (!running)
return;
frameNumber++; // Frame number goes up by 1 for each frame.
int newRow = head.atX; // Next row for the position of the snake head.
int newCol = head.atY; // Next column for the position of the snake head.
switch (direction) {
case 1: // right
newCol++;
break;
case 2: // left
newCol--;
break;
case 3: // up
newRow--;
break;
case 4: // down
newRow++;
break;
}
if (crash(newRow,newCol)) {
// Moving the snake would cause a crash. End the game.
gameOver();
}
else {
moveSnake(newRow,newCol);
}
}
/**
* This method is called when the user presses any keyboard key.
* Arrow keys change the direction of motion of the snake.
* The space key starts and stops the game action (but can only
* be started if the game is not over). The escape key can
* be used to close the window and end the program. Note: Arrow
* keys are ignored if the game is not running. Also, an arrow key
* will not reverse the direction of motion, since that would lead
* to an immediate crash when the head hits the 2nd snake segment.
*/
private void doKeyEvent(KeyEvent evt) {
KeyCode code = evt.getCode();
if (code == KeyCode.RIGHT) {
if (running && direction != 2)
direction = 1;
}
else if (code == KeyCode.LEFT) {
if (running && direction != 1)
direction = 2;
}
else if (code == KeyCode.UP) {
if (running && direction != 4)
direction = 3;
}
else if (code == KeyCode.DOWN) {
if (running && direction != 3)
direction = 4;
}
else if (code == KeyCode.SPACE) {
if (running) { // Pause the action.
animator.stop();
running = false;
}
else if ( !gameover ) { // Start animation, but only if gameover is false.
animator.start();
running = true;
}
}
else if (code == KeyCode.ESCAPE) {
Platform.exit(); // End the program.
}
}
/**
* Start method creates the window content and configures event listening.
*/
public void start(Stage stage) {
canvas = new Canvas(COLUMNS*SQUARE_SIZE, ROWS*SQUARE_SIZE);
g = canvas.getGraphicsContext2D();
Pane root = new Pane(canvas);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Snake Games");
stage.setResizable(false);
scene.setOnKeyPressed( evt -> doKeyEvent(evt) );
animator = new AnimationTimer( ) {
int tickCount = 0;
public void handle(long time) {
tickCount++;
if (tickCount == 5) {
// There will only be 12 frames per second.
doFrame();
tickCount = 0;
}
}
};
startGame();
stage.show();
} // end start()
public static void main(String[] args) {
launch(args);
}
} // end class Snake`
It doesn't look like you have to use your own implementation of linked list. So, you could use LinkedList or some other JDK collection.
public class DemoApplication {
public static class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public Point(Point other) {
this.x = other.x;
this.y = other.y;
}
// Getters and setters
}
public static void main(String[] args) {
LinkedList<Point> list = new LinkedList<>();
// Add points to the list
list.addFirst(new Point(4,5));
list.addLast(new Point(1,2));
// Get head
list.getFirst();
// Get point by index
list.get(0).getX();
list.get(1).getY();
}
}
One more advice. You needn't to write full program in just one class. It's poor design solution. You have to decompose your code. At least, move all the game logic code to one (or more) class and UI code to anothers. Look to the MVC design pattern, it may be helpful.
I have been trying to code a half built simple java game i was given to by a friend (I wanted to try learning coding so he gave me this old assignment from his 1st year of programming class).
He told me to try and build a game using the 2 classes he gave me, a main class and a sub class.
So far I have got everything to work as intended except for finding a way to get my enemy class to move towards the goal regardless of its position. I can get it to always move in a fixed direction towards the goal if I set it up perfectly but I want to be able to find the position of the goal regardless and make its way towards it.
The code need to be written in the peformAction method under Enemy.
If anyone would be kind enough to explain how I can go about this, it would be much appropriated, or even if you can just link me to another post that explains how to do what I'm trying to do.
Thank you in advance.
GameManager (main) Class:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
public class GameManager extends JFrame implements KeyListener {
private int canvasWidth;
private int canvasHeight;
private int borderLeft;
private int borderTop;
private BufferedImage canvas;
private Stage stage;
private Enemy[] enemies;
private Player player;
private Goal goal;
private Graphics gameGraphics;
private Graphics canvasGraphics;
private int numEnemies;
private boolean continueGame;
public static void main(String[] args) {
// During development, you can adjust the values provided in the brackets below
// as needed. However, your code must work with different/valid combinations
// of values.
GameManager managerObj = new GameManager(1920, 1280, 30);
}
public GameManager(int preferredWidth, int preferredHeight, int maxEnemies) {
this.borderLeft = getInsets().left;
this.borderTop = getInsets().top;
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
if (screenSize.width < preferredWidth)
this.canvasWidth = screenSize.width - getInsets().left - getInsets().right;
else
this.canvasWidth = preferredWidth - getInsets().left - getInsets().right;
if (screenSize.height < preferredHeight)
this.canvasHeight = screenSize.height - getInsets().top - getInsets().bottom;
else
this.canvasHeight = preferredHeight - getInsets().top - getInsets().bottom;
setSize(this.canvasWidth, this.canvasHeight);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
addKeyListener(this);
Random rng = new Random(2);
this.canvas = new BufferedImage(this.canvasWidth, this.canvasHeight, BufferedImage.TYPE_INT_RGB);
// Create a Stage object to hold the background images
this.stage = new Stage();
// Create a Goal object with its initial x and y coordinates
this.goal = new Goal(this.canvasWidth / 2, Math.abs(rng.nextInt()) % this.canvasHeight);
// Create a Player object with its initial x and y coordinates
this.player = new Player(this.canvasWidth - (Math.abs(rng.nextInt()) % (this.canvasWidth / 2)),
(Math.abs(rng.nextInt()) % this.canvasHeight));
// Create the Enemy objects, each with a reference to this (GameManager) object
// and their initial x and y coordinates.
this.numEnemies = maxEnemies;
this.enemies = new Enemy[this.numEnemies];
for (int i = 0; i < this.numEnemies; i++) {
this.enemies[i] = new Enemy(this, Math.abs(rng.nextInt()) % (this.canvasWidth / 4),
Math.abs(rng.nextInt()) % this.canvasHeight);
}
this.gameGraphics = getGraphics();
this.canvasGraphics = this.canvas.getGraphics();
this.continueGame = true;
while (this.continueGame) {
updateCanvas();
}
this.stage.setGameOverBackground();
updateCanvas();
}
public void updateCanvas() {
long start = System.nanoTime();
// If the player is alive, this should move the player in the direction of the
// key that has been pressed
// Note: See keyPressed and keyReleased methods in the GameManager class.
this.player.performAction();
// If the enemy is alive, the enemy must move towards the goal. The goal object
// is obtained
// via the GameManager object that is given at the time of creating an Enemy
// object.
// Note: The amount that the enemy moves by must be much smaller than that of
// the player above
// or else the game becomes too hard to play.
for (int i = 0; i < this.numEnemies; i++) {
this.enemies[i].performAction();
}
if ((Math.abs(this.goal.getX() - this.player.getX()) < (this.goal.getCurrentImage().getWidth() / 2))
&& (Math.abs(this.goal.getY() - this.player.getY()) < (this.goal.getCurrentImage().getWidth() / 2))) {
for (int i = 0; i < this.numEnemies; i++) {
// Sets the image of the enemy to the "dead" image and sets its status to
// indicate dead
this.enemies[i].die();
}
// Sets the image of the enemy to the "dead" image and sets its status to
// indicate dead
this.goal.die();
// Sets the background of the stage to the finished game background.
this.stage.setGameOverBackground();
this.continueGame=false;
}
// If an enemy is close to the goal, the player and goal die
int j=0;
while(j<this.numEnemies) {
if ((Math.abs(this.goal.getX() - this.enemies[j].getX()) < (this.goal.getCurrentImage().getWidth() / 2))
&& (Math.abs(this.goal.getY() - this.enemies[j].getY()) < (this.goal.getCurrentImage().getWidth()/ 2))) {
this.player.die();
this.goal.die();
this.stage.setGameOverBackground();
j=this.numEnemies;
this.continueGame=false;
}
j++;
}
try {
// Draw stage
this.canvasGraphics.drawImage(stage.getCurrentImage(), 0, 0, null);
// Draw player
this.canvasGraphics.drawImage(player.getCurrentImage(),
this.player.getX() - (this.player.getCurrentImage().getWidth() / 2),
this.player.getY() - (this.player.getCurrentImage().getHeight() / 2), null);
// Draw enemies
for (int i = 0; i < this.numEnemies; i++) {
this.canvasGraphics.drawImage(this.enemies[i].getCurrentImage(),
this.enemies[i].getX() - (this.enemies[i].getCurrentImage().getWidth() / 2),
this.enemies[i].getY() - (this.enemies[i].getCurrentImage().getHeight() / 2), null);
}
// Draw goal
this.canvasGraphics.drawImage(this.goal.getCurrentImage(),
this.goal.getX() - (this.goal.getCurrentImage().getWidth() / 2),
this.goal.getY() - (this.goal.getCurrentImage().getHeight() / 2), null);
} catch (Exception e) {
System.err.println(e.getMessage());
}
// Draw everything.
this.gameGraphics.drawImage(this.canvas, this.borderLeft, this.borderTop, this);
long end = System.nanoTime();
this.gameGraphics.drawString("FPS: " + String.format("%2d", (int) (1000000000.0 / (end - start))),
this.borderLeft + 50, this.borderTop + 50);
}
public Goal getGoal() {
return this.goal;
}
public void keyPressed(KeyEvent ke) {
// Below, the setKey method is used to tell the Player object which key is
// currently pressed.
// The Player object must keep track of the pressed key and use it for
// determining the direction
// to move.
if (ke.getKeyCode() == KeyEvent.VK_LEFT)
this.player.setKey('L', true);
if (ke.getKeyCode() == KeyEvent.VK_RIGHT)
this.player.setKey('R', true);
if (ke.getKeyCode() == KeyEvent.VK_UP)
this.player.setKey('U', true);
if (ke.getKeyCode() == KeyEvent.VK_DOWN)
this.player.setKey('D', true);
if (ke.getKeyCode() == KeyEvent.VK_ESCAPE)
this.continueGame = false;
}
#Override
public void keyReleased(KeyEvent ke) {
// Below, the setKey method is used to tell the Player object which key is
// currently released.
// The Player object must keep track of the pressed key and use it for
// determining the direction
// to move.
if (ke.getKeyCode() == KeyEvent.VK_LEFT)
this.player.setKey('L', false);
if (ke.getKeyCode() == KeyEvent.VK_RIGHT)
this.player.setKey('R', false);
if (ke.getKeyCode() == KeyEvent.VK_UP)
this.player.setKey('U', false);
if (ke.getKeyCode() == KeyEvent.VK_DOWN)
this.player.setKey('D', false);
}
#Override
public void keyTyped(KeyEvent ke) {
}
}
My Enemy (sub) Class:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Enemy {
//variables
private BufferedImage imageRunning;
private BufferedImage imageOver;
private BufferedImage imageCurrent;
int valX = 200;
int valY = 200;
public Enemy (GameManager gameManager, int x, int y) {
try {
this.imageRunning = ImageIO.read(new File("C:/Users/HUS/Desktop/Assigment222/images/enemy-alive.png"));
this.imageOver = ImageIO.read(new File("C:/Users/HUS/Desktop/Assigment222/images/enemy-dead.png"));
} catch (IOException e) {
e.printStackTrace();
}
this.imageCurrent = this.imageRunning;
}
public BufferedImage getCurrentImage() {
return this.imageCurrent;
}
public void performAction() {
valX += 1;
valY += 1;
return;
}
public void die() {
// TODO Auto-generated method stub
this.imageCurrent = this.imageOver;
}
public int getX() {
// TODO Auto-generated method stub
return this.valX;
}
public int getY() {
// TODO Auto-generated method stub
return this.valY;
}
}
My Goal (sub) class:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Goal {
private BufferedImage imageRunning;
private BufferedImage imageOver;
private BufferedImage imageCurrent;
int posX = 500;
int poxY = 500;
public Goal(int x, int y) {
try {
this.imageRunning = ImageIO.read(new File("C:/Users/HUS/Desktop/Assigment222/images/goal-alive.png"));
this.imageOver = ImageIO.read(new File("C:/Users/HUS/Desktop/Assigment222/images/goal-dead.png"));
} catch (IOException e) {
e.printStackTrace();
}
this.imageCurrent = this.imageRunning;
}
public BufferedImage getCurrentImage() {
return this.imageCurrent;
}
public int getX() {
return this.posX;
}
public int getY() {
return this.poxY;
}
public void die() {
this.imageCurrent = this.imageOver;
}
}
It's impossible with the current code if you want to restrict the modifications only to the performAction method.
1) The Enemy has no way of accessing (seeing) the coordinates of the goal. This means the code can't figure out the way to the goal because it doesn't have the X and Y of where the destination is.
2) To fix that you would need Enemy to be able to see the X and Y of the Goal. The quickest way would be to use getters and setters in the GameManager class to return the stored Goal object. Then you can modify your Enemy constructor to store the reference to the GameManager that created it.
GameManager:
public Goal getGoal() {
return goal;
}
Enemy:
private GameManager gameManager;
public Enemy (GameManager gameManager, int x, int y) {
...
this.gameManager = gameManager;
}
The last step would be getting the Goal object from the stored gameManager, and using its X and Y to do some logic (this would be performed inside the performAction method):
int goalX = gameManager.getGoal().getX();
int goalY = gameManager.getGoal().getY();
...some logic to calculate the direction here...
I'm sure you'll know what to do with the numbers and figure out how to move towards the goal from there.
#Edit
If not - one way would be comparing Xs and Ys of both the Enemy and the Goal and then incrementing/decrementing valX and/or valY accordingly.
A very simple implementation:
public void performAction()
{
valX += Math.signum(gameManager.getGoal().getX() - valX);
valY += Math.signum(gameManager.getGoal().getX() - valX);
}
In your method performAction you are currently changing the x and y position respectively by incrementing them by 1 each time the method gets called.
This will move your enemy exactly 1 unit in direction x and 1 unit in direction y.
You instead want to move your character in the direction of the Goal. That's more of a math problem than a programming issue but let's give it a go:
You have 2 positions:
Your Enemy has a position of form and the same goes for your goal with position
You now want to move your Enemy in the direction of the goal. So you calculate a vector connecting these two points with
You then change the length of the vector to the speed your enemy can move with (maybe define this with a field?) by normalizing the vector and doing a scalar multiplication with your speed.
So with a given speed S this gives you the full formula:
Now let us transform this into code. In the constructor of your Enemy class save the GameManager as a field so you can call the getGoal method on it.
So we add a field private GameManager gameManager and in the Constructor we do this.gameManager = gameManager to store the reference.
Also we adda field private double speed = 2 for our calculation.
And then we change the performAction method to implement our formula. Something like this:
public void performAction()
{
// Calculate our P_g - P_e
double dX = gameManager.getGoal().getX() - valX;
double dY = gameManager.getGoal().getX() - valX;
// Calculate its length to normalize it
double divider = Math.sqrt(dX * dX + dY * dY);
// Normalize it
dX /= divider;
dY /= divider;
// Do a scalar multiplication with our speed
dX *= speed;
dY *= speed;
valX += dX;
valY += dY;
}
The only problem we are running into here is that we are adding double values to our integer values valX and valY so probably change those to double values generally or else you might run into problems where your enemy will never move at all.
public class Physics {
private int weight;
private int durability;
private int xPos;
private int yPos;
private int x;
private int y;
private Level level;
int tileWidth = Main.WIDTH / 16;
int tileHeight = Main.HEIGHT / 16;
public Physics(Level level) {
this.level = level;
}
private void timer(int time) {//this is a subroutine that creates a slight pause
long currTime = System.currentTimeMillis();//this is getting the current time in miliseconds
long target = currTime + time;//this gets the current time and sets the target pause time to the current time plus the time you want to pause for in miliseconds
while(System.currentTimeMillis() < target);//this makes the program idle for the target amount of time
}
public void gravity(int weight,int durability, int xPos, int yPos, Tile tile, TileType tileTypes, boolean newblock){//this is the physics subroutine for when you place a block
if(tile.getType() == TileType.SKY) {//if the tile you clicked on is a sky tile
tile.setType(tileTypes, weight, durability);//it sets the new tiles type weight and durability to the ones that were entered dependant on which block the user chose from the pallet
timer(50);//this is the pause time between the block falling one y coordinate down
tile.setType(TileType.SKY, 0, 0);//once the tile is drawn one tile down it changeds the tile above it back to a sky tile
yPos++;//this the increases the y possition by one to foxus on the next block down
gravity(weight,durability, xPos, yPos, level.tiles[xPos][yPos],tileTypes,true);//this then calls the subroutine within itself and this keeps going until it hits the bottom
}
//if (newblock == true) {
if(tile.getType() != TileType.SKY){//once the tile below the tile we're focusing on is not a sky tile this if statement is run
Tile tile1 = level.tiles[xPos][yPos-1];//it sets the tile were focusing on to the tile above the non skytile
tile1.setType(tileTypes, weight, durability);//it then sets this tile to the tile you chose from the pallet
weight = tile1.getWeight();
durability = tile1.getDurability();
System.out.println(weight+"kg, "+durability);
}
//}
//if (newblock == false) {
//}
}
public void blockDestruction(Tile tile) {
}
public void tick(int x, int y) {
Tile tile = level.tiles[x][y];
Tile tile1 = level.tiles[x][y-1];
TileType tilecheck = tile1.getType();
if(tile1.getType() != TileType.SKY && tile1.getType() != TileType.GRASS) {
gravity(tile.getWeight(),tile.getDurability(),x,y,tile,tile.getType(),false);
y = y-1;
/*for (int i = y-1; i>0;i--) {
if (tilecheck != TileType.SKY) {
tile1 = level.tiles[x][i];
tilecheck = tile1.getType();
}else
tile1 = level.tiles[x][i+1];
tile1.setType(TileType.SKY);
}*/
}
}
}
the tick method at the end there is what I'm struggling with what im trying to create is a method that i would be able to put inside of my tick method in my main class which will check all blocks to see if there is a block underneath them and if not then it will apply the gravity method to that block. Sorry for the code being such a mess ive been trying loads of solutions and have gotten completely stuck so it is currently a bit of a mess if anything need explaining then please let me know.
this is what the program looks like
I'm making a small balloon game in java and trying to write my code, so that when I create new 'balloon' objects they don't overlap on the screen.
The code I have so far is:
public void newGame(){
UI.clearGraphics();
this.currentScore = 0;
this.totalPopped = 0;
for (int i = 0; i < this.balloons.length-1; i++) {
this.balloons[i] = new Balloon(50 + Math.random()*400, 50 + Math.random()*400);
for (int j = i + 1; j < this.balloons.length; j++) {
if (this.balloons[i] !=null && this.balloons[j] != null && this.balloons[i].isTouching(balloons[j])) {
this.balloons[j] = new Balloon(50 + Math.random()*400, 50+ Math.random()*400);
}
}
this.balloons[i].draw();
}
UI.printMessage("New game: click on a balloon. High score = "+this.highScore);
}
using the draw and isTouching methods:
public void draw(){
UI.setColor(color);
UI.fillOval(centerX-radius, centerY-radius, radius*2, radius*2);
if (!this.popped){
UI.setColor(Color.black);
UI.drawOval(centerX-radius, centerY-radius, radius*2, radius*2);
}
}
/** Returns true if this Balloon is touching the other balloon, and false otherwise
* Returns false if either balloon is popped. */
public boolean isTouching(Balloon other){
if (this.popped || other.popped) return false;
double dx = other.centerX - this.centerX;
double dy = other.centerY - this.centerY;
double dist = other.radius + this.radius;
return (Math.hypot(dx,dy) < dist);
}
how can I write this, so that when the balloons are created, none of them are touching each other?
Right now you have two loops. In the first loop the balloons are created. In the second loop, every balloon is tested against every other loop. Do this test in the first loop: after creating a new balloon, test it against all already existing balloons.
I keep getting this error when I compile and run my Java program using the ACM library
Exception in thread "Thread-3" java.lang.NullPointerException
at SpaceTravel.getBlackHoleDistance(SpaceTravel.java:148)
at SpaceTravel.gameOverBlackHole(SpaceTravel.java:132)
at BlackHole.oneTimeStep(BlackHole.java:84)
at BlackHole.run(BlackHole.java:45)
at java.lang.Thread.run(Unknown Source)
This is the game class:
import acm.graphics.*;
import acm.program.*;
import acm.util.*;
import java.awt.*;
public class SpaceTravel extends GraphicsProgram {
// specify the size of the window
public static int APPLICATION_WIDTH = 1280;
public static int APPLICATION_HEIGHT = 600;
// class constants
private static final double
PLANET_SIZE = 80,
BLACK_HOLE_SIZE = 100,
STAR_SIZE = 2;
// instance variables
private GOval ice, fire, iron;
private GPoint lastPoint;
private boolean isDragging = false;
private GLabel gameOverText, goal, win;
private BlackHole blackhole1, blackhole2;
private RandomGenerator rand = new RandomGenerator();
// init method, draw the graphics objects
public void init() {
setBackground(Color.BLACK);
// call the randomly colored stars method
drawStars();
// call 1 instance of BlackHole class
blackhole1 = new BlackHole(BLACK_HOLE_SIZE, 5, 2, this);
add(blackhole1, APPLICATION_WIDTH-400, APPLICATION_HEIGHT/2 );
new Thread(blackhole1).start();
// call 1 instance of BlackHole class, but name it differently
blackhole2 = new BlackHole(BLACK_HOLE_SIZE, 3, -4, this);
add(blackhole2, APPLICATION_WIDTH-200, APPLICATION_HEIGHT/2 );
new Thread(blackhole2).start();
// draw fire planet
fire = drawCircleCentered(100, 400, PLANET_SIZE);
add(fire);
fire.setFilled(true);
fire.setColor(Color.RED);
// draw ice planet
ice = drawCircleCentered(100, 100, PLANET_SIZE);
add(ice);
ice.setFilled(true);
ice.setColor(Color.BLUE);
// draw iron planet
iron = drawCircleCentered(100, 250, PLANET_SIZE);
add(iron);
iron.setFilled(true);
Color grey = new Color(34, 34, 34);
iron.setColor(grey);
// game over text
gameOverText = new GLabel ("GAME OVER", APPLICATION_WIDTH/2 - 250, APPLICATION_HEIGHT/2);
gameOverText.setColor(Color.RED);
gameOverText.setFont(new Font("DEFAULT_FONT", Font.BOLD, 90));
// goal text
goal = new GLabel ("GOAL", APPLICATION_WIDTH-150, APPLICATION_HEIGHT/2);
goal.setColor(Color.RED);
goal.setFont(new Font("DEFAULT_FONT", Font.BOLD, 20));
add(goal);
// win text
win = new GLabel ("WINRAR IS YOU!", APPLICATION_WIDTH/2 - 350, APPLICATION_HEIGHT/2);
win.setColor(Color.RED);
win.setFont(new Font("DEFAULT_FONT", Font.BOLD, 90));
}
// checker method if the ice and fire plantes touch, call the game over method below.
private void checkFireIce(GOval fire, GOval ice) {
if(getDistance(fire, ice) < PLANET_SIZE ) {
gameOver(fire);
}
}
// checker method for when fire planet gets to the goal text, call the game winning method below
private void checkPlanetsGoal() {
if(fire.getBounds().intersects(goal.getBounds())) {
winGame();
}
}
// start dragging if the ball is pressed
public void mousePressed(GPoint point) {
if (ice.contains(point)) {
isDragging = true;
lastPoint = point;
}
}
// move the ball when it is dragged, and call checking methods for game winning or game over conditions
public void mouseDragged(GPoint point) {
checkFireIce(fire, ice);
checkPlanetsGoal();
if (isDragging) {
ice.move(point.getX()-lastPoint.getX(),
point.getY()-lastPoint.getY());
lastPoint = point;
// bump the planets
bump(ice, iron);
bump(iron, fire);
}
}
// checking method for if any of the planets have touched an instance of black hole
public void gameOverBlackHole(BlackHole blackhole) {
double a = getBlackHoleDistance(fire, blackhole);
double b = getBlackHoleDistance(ice, blackhole);
double c = getBlackHoleDistance(iron, blackhole);
if(a < BLACK_HOLE_SIZE/2 + PLANET_SIZE/2) {
gameOver(fire);
}
if(b < BLACK_HOLE_SIZE/2 + PLANET_SIZE/2) {
gameOver(ice);
}
if(c < BLACK_HOLE_SIZE/2 + PLANET_SIZE/2) {
gameOver(iron);
}
}
// get distance between a black hole instance and a planet
private double getBlackHoleDistance(GOval planet, BlackHole blackhole) {
return GMath.distance(planet.getX()+PLANET_SIZE/2, planet.getY()+PLANET_SIZE/2,
blackhole.getX(), blackhole.getY());
}
// bump helper method, calculates how much to move a tangent planet by when it is being bumped by another
private void bump(GOval planet1, GOval planet2) {
double offset = PLANET_SIZE+1.5 - getDistance(planet1, planet2);
if (offset > 0) {
planet2.move(offset*(planet2.getX()-planet1.getX())/PLANET_SIZE,
offset*(planet2.getY()-planet1.getY())/PLANET_SIZE);
}
}
// a helper method, compute the distance between the centers of the balls
private double getDistance(GOval planet1, GOval planet2) {
return GMath.distance(planet1.getX()+PLANET_SIZE/2, planet1.getY()+PLANET_SIZE/2,
planet2.getX()+PLANET_SIZE/2, planet2.getY()+PLANET_SIZE/2);
}
// a helper method, draw a circle centered at the given location
private GOval drawCircleCentered(double centerX, double centerY, double size) {
return new GOval(centerX-size/2, centerY-size/2, size, size);
}
// a helper method, draw randomly colored stars
private void drawStars() {
for (int i = 0; i < 1000; i++) {
GOval star = drawCircleCentered(rand.nextDouble(0, APPLICATION_WIDTH), rand.nextDouble(0, APPLICATION_HEIGHT), STAR_SIZE);
add(star);
star.setFilled(true);
star.setColor(rand.nextColor());
}
}
// helper method to switch dragging off when mouse is released from window
public void mouseReleased(GPoint point) {
isDragging = false;
}
// helper method to switch dragging off, remove the planet that touched the goal, and display game over text
public void gameOver(GOval planet) {
isDragging = false;
remove(planet);
add(gameOverText);
}
// helper method to switch dragging off, remove planets, and display win text
private void winGame() {
isDragging = false;
add(win);
remove(fire);
remove(ice);
remove(iron);
remove(goal);
}
}
And this is the class that creates blackholes which can end the game.
// import libraries
import acm.program.*;
import acm.graphics.*;
import acm.util.*;
import java.awt.*;
import java.util.*;
public class BlackHole extends GCompound implements Runnable {
// instance variables
private double size, xSpeed, ySpeed;
private SpaceTravel game;
private boolean stopHammerTime = false;
// constructor for BlackHole
public BlackHole(double size, double xSpeed, double ySpeed, SpaceTravel game) {
// save the parameters size, xSpeed, ySpeed, centerX, centerY, and game
this.size = size;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.game = game;
// call method drawBlackhole
drawBlackhole(0, 0, size, 3, 40);
}
// run method, move the black hole until it hits a planet || stopHammerTime = true
public void run() {
while(!stopHammerTime) {
oneTimeStep();
}
}
// helper method, creates a black hole
private void drawBlackhole(double centerX, double centerY, double size, double gap, int layers) {
for (int i = 0; i < layers; i++) {
// this gradient color will lighten each time the for loop completes
Color gradient = new Color(0 + 5*i, 0 + 5*i, 0 + 5*i);
GOval ring = drawCircleCentered(centerX, centerY, size-gap*2*i);
add(ring);
ring.setFilled(true);
ring.setColor(gradient);
}
}
// a helper method, draw a circle centered at the given location
private GOval drawCircleCentered(double centerX, double centerY, double size) {
return new GOval(centerX-size/2, centerY-size/2, size, size);
}
// a helper method, move the blackHole in oneTimeStep
private void oneTimeStep() {
double x = getX();
double y = getY();
// if the black hole hits the left or the right wall, reverse the x-speed
if (x < size/2 || x+size/2 > game.getWidth()) xSpeed = -xSpeed;
// if the black hole hits the top or the bottom wall, reverse the y-speed
if (y < size/2 || y+size/2 > game.getHeight()) ySpeed = -ySpeed;
// move the black hole by a small interval, incorporating changes from if statements
move(xSpeed, ySpeed);
// check if a planet has touched a blackhole
game.gameOverBlackHole(this);
// delay
pause(20);
}
}
I'm pretty sure that I have the calling of the classes correct, but for some reason, the blackhole2 that is called just crashes.
Exception in thread "Thread-3" java.lang.NullPointerException
It is NullPointerException happened while executing thread "Thread-3"
At line 148 in SpaceTravel.java file.
It seems while Thread-3 executing, line 148 is trying to do some operation on null reference, which results in NullPointerException.
The code that apparently crashes looks like this:
return GMath.distance(planet.getX()+PLANET_SIZE/2, planet.getY()+PLANET_SIZE/2,
blackhole.getX(), blackhole.getY());
Most likely either planet or blackhole parameter is null. Use debugger or println() to figure out which one. This piece of code is called from three places:
double a = getBlackHoleDistance(fire, blackhole);
double b = getBlackHoleDistance(ice, blackhole);
double c = getBlackHoleDistance(iron, blackhole);
Your line numbers are a bit off but I dare to say it's the first line, so either fire or blackhole is null.
A Quick Analysis:
gameOverBlackHole method gets called form BlackHole class this is causing problems because remove happens inside that method. If remove happens while one thread is executing then there will NPE
This a can be a example of what might go wrong if you share state of object between multiple threads