Refactor Part 2: Lots of renaming, new classes, hitbox changes

This commit is contained in:
naomi 2025-05-18 03:48:48 +00:00
parent 475af2c03e
commit 98883aa62a
21 changed files with 1549 additions and 790 deletions

View file

@ -0,0 +1,283 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
/**
* The balls that move around the playfield and hit bricks.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class Ball extends Entity {
private boolean isActive = false;
private boolean isInvincible = false;
byte ballColor = 0;
byte speedX = 5;
byte speedY = 5;
private GameState gameState;
public Ball(GameState gameState, int x, int y, int width, int height, int color, boolean active) {
this.gameState = gameState;
this.posX = (short) x;
this.posY = (short) y;
this.width = (short) width;
this.height = (short) height;
this.ballColor = (byte) color;
this.hitBox = new Hitbox(x, y, width, height, 1);
this.isActive = active;
}
public void spawnBall(boolean useLFSR) {
if (useLFSR) {this.spawnBall(((gameState.getPokey().getDecimalValue() / 2) + gameState.getPokey().getShiftedDecimalValue() + 30), (this.posY + (gameState.getPokey().getDecimalValue() / 4)) - 40);}
else {this.spawnBall(this.posX, this.posY);}
}
public void spawnBall(int x, int y) {
this.posX = (short) x;
this.posY = (short) y;
this.isActive = true;
if (gameState.getBalls().size() <= 1) {
gameState.removeLife();
gameState.getTextElements().get(4).setText(String.valueOf(gameState.getLives()));
}
}
public void moveBall() {
this.posX += this.speedX;
this.posY += this.speedY;
this.hitBox.moveTo(this.posX, this.posY, this.width, this.height);
this.checkCollision();
}
/**
* Richochets the ball.
* @param bounceType The code for which direction the ball bounces
*/
public void bounce(int bounceType) {
switch((byte) bounceType) {
case 0:
this.speedY = (byte) -(this.speedY);
byte randByte = (byte) (gameState.getPokey().getDecimalValue() / 16);
if (randByte < 0) {randByte = 0;}
else if (randByte > 15) {randByte = 15;}
switch (randByte) {
case 0: break;
case 1: this.speedX++; break;
case 2: break;
case 3: this.speedX--; break;
case 4: break;
case 5: this.speedY++;break;
case 6: break;
case 7: this.speedY--;break;
case 8: break;
case 9: this.speedY++;speedX++;break;
case 10: break;
case 11: this.speedY--;speedX--;break;
case 12: break;
case 13: this.speedY++;speedY++;break;
case 14: break;
case 15: this.speedY--;speedY--;break;
}
break;
case 1:
this.speedX = (byte) -(this.speedX);
break;
case 2:
if (this.speedX > 0) {
this.speedX = (byte) -(this.speedX);
}
else {
this.speedX -= 4;
}
this.speedY = (byte) -(this.speedY);
break;
case 3:
if (this.speedX < 0) {
this.speedX = (byte) -(this.speedX);
}
else {
this.speedX += 4;
}
this.speedY = (byte) -(this.speedY);
break;
}
if (this.speedX < 2 && this.speedX >= 0) {this.speedX = 2;}
else if (this.speedX > -2 && this.speedX < 0) {this.speedX = -2;}
else if (this.speedX > 11) {this.speedX -= 3;} // Slow down if too fast
if (this.speedY < 4 && this.speedY >= 0) {this.speedY = 4;}
else if (this.speedY > -4 && this.speedY < 0) {this.speedY = -4;}
else if (this.speedY > 11) {this.speedY -= 3;} // Slow down if too fast
}
public void destroyBall() {
this.isActive = false;
this.posX = 32767;
this.posY = 32767;
this.hitBox.setBounds(32766, 32766, 32767, 32767);
if (gameState.getBalls().size() <= 1) {
if (gameState.getLives() <= 0) {
gameState.gameLose();
}
}
}
public void cull() {
this.isActive = false;
this.posX = 32767;
this.posY = 32767;
this.hitBox.setBounds(32766, 32766, 32767, 32767);
}
/**
* Checks to see if the ball is in contact with other Entities and walls.
*/
public synchronized void checkCollision() {
this.setInvincibilityState(true); // Delay bouncing again for a frame
// Check floor
if (this.posY >= gameState.getInternalResY()) {
this.destroyBall();
return;
}
// Check ceiling
else if (this.posY <= 0) {
this.bounce(0);
this.posY = 0;
this.wallBounceAnimation();
return;
}
// Check walls
if (this.posX <= 20) {
this.bounce(1);
this.posX = 22;
this.wallBounceAnimation();
return;
}
else if ((this.posX + this.width) >= (gameState.getInternalResX() - 20 - this.width)) {
this.bounce(1);
this.posX = (short) (gameState.getInternalResX() - 25 - this.width);
this.wallBounceAnimation();
return;
}
// Check bricks
if (gameState.getCurrentLevel() != null) {
if (gameState.getCurrentLevel().brickList != null) {
for (Brick brick : gameState.getCurrentLevel().brickList) {
// Check for left bounce
if (this.hitBox.collides(brick.hitBoxLeft.getBounds())) {
if (this.hitBox.collides(brick.hitBox.getBounds())) {
this.bounce(0);
this.bounce(1);
}
else {
this.bounce(1);
}
brick.breakBrick();
return;
}
// Check for right bounce
else if (this.hitBox.collides(brick.hitBoxRight.getBounds())) {
if (this.hitBox.collides(brick.hitBox.getBounds())) {
this.bounce(0);
this.bounce(1);
}
else {
this.bounce(1);
}
brick.breakBrick();
return;
}
// Check for vertical bounce
else if (this.hitBox.collides(brick.hitBox.getBounds())) {
brick.breakBrick();
this.bounce(0);
return;
}
}
}
}
// Check paddles
for (Paddle paddle : gameState.getPaddles()) {
if (this.hitBox.collides(paddle.hitBox.getBounds())) {
if (this.hitBox.collides(paddle.hitBoxLeft.getBounds())) {
this.bounce(2);
}
else if (this.hitBox.collides(paddle.hitBoxRight.getBounds())) {
this.bounce(3);
}
else {
this.bounce(0);
}
this.posY -= 2;
// Draw particles
Particle particle1, particle2, particle3;
particle1 = new Particle(gameState, 0, this.posX, this.posY - 10, 0, -2, 1, 2, 7, 3);
particle2 = new Particle(gameState, 0, this.posX - 3, this.posY - 10, -2, -4, 1, 2, 6, 3);
particle3 = new Particle(gameState, 0, this.posX + 10, this.posY - 10, 2, -4, 1, 2, 8, 3);
gameState.getParticles().add(particle1);
gameState.getParticles().add(particle2);
gameState.getParticles().add(particle3);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
return; // Prevents bouncing multiple times
}
}
}
private void wallBounceAnimation() {
Particle particle1, particle2, particle3, particle4;
particle1 = new Particle(gameState, 0, this.posX - 3, this.posY - 10, -2, -2, 1, 2, 6, 3);
particle2 = new Particle(gameState, 0, this.posX + 10, this.posY - 10, 2, -2, 1, 2, 8, 3);
particle3 = new Particle(gameState, 0, this.posX - 3, this.posY + 3, 2, 2, 1, 2, 2, 3);
particle4 = new Particle(gameState, 0, this.posX + 10, this.posY + 3, -2, 2, 1, 2, 4, 3);
gameState.getParticles().add(particle1);
gameState.getParticles().add(particle2);
gameState.getParticles().add(particle3);
gameState.getParticles().add(particle4);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
particle4.spawn(5);
}
public boolean getActiveState() {
return this.isActive;
}
public void setActiveState(boolean activeState) {
this.isActive = activeState;
}
public boolean getInvincibilityState() {
return this.isInvincible;
}
public void setInvincibilityState(boolean invincibilityState) {
this.isInvincible = invincibilityState;
}
}

View file

@ -0,0 +1,120 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
/**
* The bricks that appear in each level.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class Brick extends Entity {
private boolean isBroken = false;
byte brickColor = 0;
byte scoreOnBreak = 10;
byte specialAbility = 0;
Hitbox hitBoxLeft;
Hitbox hitBoxRight;
private GameState gameState;
/**
* Constructs a brick.
* @param gameState The GameState in which the Brick will reside.
* @param x The x coordinate of the Brick.
* @param y The y coordinate of the Brick.
* @param isBroken Determines whether the Brick is broken.
* @param brickColor The color code of the Brick.
* @param score The score the Player earns upon breaking the Brick.
* @param specialAbility A special event that occurs upon breaking the Brick.
*/
public Brick(GameState gameState, int x, int y, boolean isBroken, int brickColor, int score, int specialAbility) {
this.width = 40;
this.height = 17;
this.gameState = gameState;
this.posX = (short) x;
this.posY = (short) y;
this.isBroken = isBroken;
this.brickColor = (byte) brickColor;
this.scoreOnBreak = (byte) score;
this.hitBox = new Hitbox(x + 2, y, this.width - 4, this.height, 0);
this.hitBoxLeft = new Hitbox(x, y + 2, 6, this.height - 4, 0);
this.hitBoxRight = new Hitbox(x + this.width - 6, y + 2, 6, this.height - 4, 0);
this.specialAbility = (byte) specialAbility;
}
public void breakBrick() {
if (!isBroken) {
isBroken = true;
gameState.addScore(this.scoreOnBreak + (gameState.getCurrentLevelID() - 1));
gameState.getTextElements().get(3).setText(String.valueOf(gameState.getScore()));
switch(this.specialAbility) {
case 1:
gameState.addLife();
break;
case 2:
while (gameState.getCollectorActiveState()) {}
Ball newBall = new Ball(gameState, 300, 300, 7, 7, 0, true);
this.gameState.getBalls().add(newBall);
newBall.spawnBall((this.posX + (this.width / 2)), (this.posY + (this.height / 2)));
break;
default:
break;
}
// Show score on break
TextElement bruh = new TextElement(this.gameState,
String.valueOf(this.scoreOnBreak),
0.25f,
this.posX + (this.width / 3),
this.posY - 3,
this.brickColor,
1,
300);
bruh.activateTimer();
bruh.setActiveState(true);
//bruh.moveUpOnFrame();
this.gameState.getTextElements().add(bruh);
}
// Draw particles
Particle particle1, particle2, particle3;
particle1 = new Particle(gameState, 0, this.posX + 12, this.posY + 3, 1, 1, this.brickColor, 2, 4, 1);
particle2 = new Particle(gameState, 0, this.posX + 20, this.posY + 13, 1, 1, this.brickColor, 2, 3, 1);
particle3 = new Particle(gameState, 0, this.posX + 32, this.posY + 7, 1, 1, this.brickColor, 2, 2, 1);
gameState.getParticles().add(particle1);
gameState.getParticles().add(particle2);
gameState.getParticles().add(particle3);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
this.posX = 32767;
this.posY = 32767;
this.hitBox.moveTo(32760, 32760, 2, 2);
this.hitBoxLeft.moveTo(32760, 32760, 2, 2);
this.hitBoxRight.moveTo(32760, 32760, 2, 2);
}
public boolean getBrokenState() {
return this.isBroken;
}
public void setBrokenState(boolean brokenState) {
this.isBroken = brokenState;
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright 2025 naomi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.Point;
/**
* Base class for all entities in the game.
* @author Naomi (boyfailure.dev)
* @since 1.0
*/
public class Entity {
/** The horizontal position of the entity, in pixels. */
short posX;
/** The vertical position of the entity, in pixels. */
short posY;
/** The width of the entity, in pixels. */
short width;
/** The height of the entity, in pixels. */
short height;
/** The default hitbox of the entity. */
Hitbox hitBox;
//<editor-fold defaultstate="collapsed" desc=" Accessor methods (get/set) ">
/**
* Returns the position of the entity as a Point.
* @return the position of the entity
*/
public Point getPosition() {
return new Point(this.posX, this.posY);
}
/**
* Returns the horizontal position of the entity.
* @return the horizontal position of the entity
*/
public short getPositionX() {
return this.posX;
}
/**
* Returns the vertical position of the entity.
* @return the vertical position of the entity
*/
public short getPositionY() {
return this.posY;
}
//</editor-fold>
}

View file

@ -0,0 +1,636 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.awt.geom.Line2D;
/**
* The logic to draw the game on the screen.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class GameDisplay extends JComponent {
// Change this to 1 if you build for older versions of Java. Older versions
// will render a leading zero otherwise. (tested on JDK 1.5)
byte iStart = 0;
float beamX = 0; // Horizontal location of "vector beam"
float beamY = 0; // Vertical location of "vector beam"
public float beamScaleX = 1; // Horizontal drawing scale
public float beamScaleY = 1; // Vertical drawing scale
public float beamThicknessScale = 1; // Thickness of lines
private GameState gameState;
public GameDisplay(GameState gameState) {
this.gameState = gameState;
}
public float getBeamScaleX() {
return this.beamScaleX;
}
public float getBeamScaleY() {
return this.beamScaleY;
}
public float getBeamThicknessScale() {
return this.beamThicknessScale;
}
public void setBeamScaleX(float beamScale) {
this.beamScaleX = beamScale;
}
public void setBeamScaleY(float beamScale) {
this.beamScaleY = beamScale;
}
public void setBeamThicknessScale(float beamScale) {
this.beamThicknessScale = beamScale;
}
public synchronized void paint(Graphics gameGraphics) {
super.paint(gameGraphics);
Graphics2D g2 = (Graphics2D) gameGraphics;
// Walls
if (gameState.getGameStartedState()) {
g2.setColor(Color.gray);
g2.setStroke(bt(10));
resetBeam();
moveBeam(15, 0);
g2.draw(drawVec(0, 600));
moveBeam(770, 0);
g2.draw(drawVec(0, -600));
moveBeam(15, -5);
g2.draw(drawVec(-800, 0));
}
// Bricks
if (gameState.getCurrentLevel() != null) {
if (gameState.getCurrentLevel().brickList != null) {
for (Brick brick : gameState.getCurrentLevel().brickList) {
if (!brick.getBrokenState()) {
resetBeam();
g2.setStroke(bt(3));
moveBeam(brick.posX + 1, brick.posY + 1);
g2.setColor(vc(brick.brickColor));
g2.draw(drawVec(brick.width - 2, 0));
g2.draw(drawVec(0, -(brick.height - 2)));
g2.draw(drawVec(-(brick.width - 2), 0));
g2.draw(drawVec(0, brick.height - 2));
}
}
}
}
// Paddles
g2.setColor(Color.white);
for (Paddle paddle : gameState.getPaddles()) {
if (paddle.getActiveState()) {
resetBeam();
g2.setStroke(bt(3));
moveBeam(paddle.posX, paddle.posY);
g2.draw(drawVec(paddle.width, 0));
g2.draw(drawVec(0, -(paddle.height)));
g2.draw(drawVec(-(paddle.width), 0));
g2.draw(drawVec(0, paddle.height));
}
}
// Balls
g2.setStroke(bt(3));
g2.setColor(Color.yellow);
for (Ball ball : gameState.getBalls()) {
if (ball.getActiveState()) {
resetBeam();
moveBeam(ball.posX, ball.posY);
g2.draw(drawVec(ball.width, 0));
g2.draw(drawVec(0, -ball.height));
g2.draw(drawVec(-ball.width, 0));
g2.draw(drawVec(0, ball.height));
}
}
// Particles
for (Particle particle : gameState.getParticles()) {
if (particle.getActiveState()) {
resetBeam();
moveBeam(particle.particleX, particle.particleY);
g2.setStroke(bt(particle.vectorScale));
g2.setColor(vc(particle.particleColor));
g2.draw(drawVec(particle.particleX2, particle.particleY2));
}
}
/*
* TextElements
*
* Note on rendering numbers:
*
* I am aware that I could be using a switch statement for these.
* However, this did not compile when testing in JDK 1.5, as it would
* refuse to accept anything other than numbers in a switch statement.
* Since I am a silly little nerd who wishes to use this on my older
* systems, I used these goofy if/else statements so I could compile
* on the older JDKs and play on my older systems...
*/
for (TextElement element : gameState.getTextElements()) {
if (element.getActiveState() && element.getVisibleState()) {
if (element.movesUpOnFrame()) {element.moveTo(element.getPositionX(), element.getPositionY() - 1);}
resetBeam();
moveBeam(element.getPositionX(), element.getPositionY());
// Save the current beam scale to revert once TextElement is done rendering
float oldBeamScaleX = this.beamScaleX;
float oldBeamScaleY = this.beamScaleY;
// Adjust the beam scale to accomodate the scale of the TextElement
this.beamScaleX *= element.getTextScale();
this.beamScaleY *= element.getTextScale();
// Render the TextElement
g2.setColor(vc(element.getColor()));
g2.setStroke(bt(element.getVectorThickness()));
String[] stringToRender = element.getText().split("");
//<editor-fold defaultstate="collapsed" desc=" Beam routines for letters/numbers ">
for (short i = iStart; i < stringToRender.length; i++) {
if ("1".equals(stringToRender[i])) {
moveBeam(7, 0);
g2.draw(drawVec(0, -24));
moveBeam(11, 24);
}
else if ("2".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, 12));
g2.draw(drawVec(-14, 0));
g2.draw(drawVec(0, 12));
g2.draw(drawVec(14, 0));
moveBeam(4, 0);
}
else if ("3".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, 12));
g2.draw(drawVec(-14, 0));
moveBeam(14, 0);
g2.draw(drawVec(0, 12));
g2.draw(drawVec(-14, 0));
moveBeam(18, 0);
}
else if ("4".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(0, 12));
g2.draw(drawVec(14, 0));
moveBeam(0, -12);
g2.draw(drawVec(0, 24));
moveBeam(4, 0);
}
else if ("5".equals(stringToRender[i]) || "S".equals(stringToRender[i])) {
moveBeam(14, -24);
g2.draw(drawVec(-14, 0));
g2.draw(drawVec(0, 12));
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, 12));
g2.draw(drawVec(-14, 0));
moveBeam(18, 0);
}
else if ("6".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(0, 24));
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, -12));
g2.draw(drawVec(-14, 0));
moveBeam(18, 12);
}
else if ("7".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, 24));
moveBeam(4, 0);
}
else if ("8".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, 24));
g2.draw(drawVec(-14, 0));
moveBeam(0, -12);
g2.draw(drawVec(14, 0));
moveBeam(4, 12);
}
else if ("9".equals(stringToRender[i])) {
moveBeam(14, 0);
g2.draw(drawVec(0, -24));
g2.draw(drawVec(-14, 0));
g2.draw(drawVec(0, 12));
g2.draw(drawVec(14, 0));
moveBeam(4, 12);
}
else if ("0".equals(stringToRender[i]) || "O".equals(stringToRender[i])) {
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, -24));
g2.draw(drawVec(-14, 0));
g2.draw(drawVec(0, 24));
moveBeam(18, 0);
}
// Simulates a newline in a TextElement
else if ("\\".equals(stringToRender[i])) {
moveBeam(-(beamX - element.getPositionX()), 30);
}
else if ("A".equals(stringToRender[i])) {
g2.draw(drawVec(0, -17));
g2.draw(drawVec(7, -7));
g2.draw(drawVec(7, 7));
g2.draw(drawVec(0, 17));
moveBeam(-14, -10);
g2.draw(drawVec(14, 0));
moveBeam(4, 10);
}
else if ("B".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(11, 0));
g2.draw(drawVec(3, 3));
g2.draw(drawVec(0, 6));
g2.draw(drawVec(-3, 3));
g2.draw(drawVec(3, 3));
g2.draw(drawVec(0, 6));
g2.draw(drawVec(-3, 3));
g2.draw(drawVec(-11, 0));
moveBeam(0, -12);
g2.draw(drawVec(11, 0));
moveBeam(7, 12);
}
else if ("C".equals(stringToRender[i])) {
moveBeam(14, -24);
g2.draw(drawVec(-14, 0));
g2.draw(drawVec(0, 24));
g2.draw(drawVec(14, 0));
moveBeam(4, 0);
}
else if ("D".equals(stringToRender[i])) {
g2.draw(drawVec(7, 0));
g2.draw(drawVec(7, -7));
g2.draw(drawVec(0, -10));
g2.draw(drawVec(-7, -7));
g2.draw(drawVec(-7, 0));
g2.draw(drawVec(0, 24));
moveBeam(18, 0);
}
else if ("E".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(14, 0));
moveBeam(-14, 12);
g2.draw(drawVec(14, 0));
moveBeam(-14, 12);
g2.draw(drawVec(14, 0));
moveBeam(4, 0);
}
else if ("F".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(14, 0));
moveBeam(-14, 12);
g2.draw(drawVec(14, 0));
moveBeam(4, 12);
}
else if ("G".equals(stringToRender[i])) {
moveBeam(14, -17);
g2.draw(drawVec(0, -7));
g2.draw(drawVec(-14, 0));
g2.draw(drawVec(0, 24));
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, -8));
g2.draw(drawVec(-8, 0));
moveBeam(12, 8);
}
else if ("H".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
moveBeam(14, 0);
g2.draw(drawVec(0, 24));
moveBeam(-14, -12);
g2.draw(drawVec(14, 0));
moveBeam(4, 12);
}
else if ("I".equals(stringToRender[i])) {
g2.draw(drawVec(14, 0));
moveBeam(-14, -24);
g2.draw(drawVec(14, 0));
moveBeam(-7, 0);
g2.draw(drawVec(0, 24));
moveBeam(11, 0);
}
else if ("J".equals(stringToRender[i])) {
moveBeam(0, -7);
g2.draw(drawVec(7, 7));
g2.draw(drawVec(7, 0));
g2.draw(drawVec(0, -24));
moveBeam(4, 24);
}
else if ("K".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
moveBeam(14, 0);
g2.draw(drawVec(-14, 12));
g2.draw(drawVec(14, 12));
moveBeam(4, 0);
}
else if ("L".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(0, 24));
g2.draw(drawVec(14, 0));
moveBeam(4, 0);
}
else if ("M".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(7, 7));
g2.draw(drawVec(7, -7));
g2.draw(drawVec(0, 24));
moveBeam(4, 0);
}
else if ("N".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(14, 24));
g2.draw(drawVec(0, -24));
moveBeam(4, 24);
}
else if ("P".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, 12));
g2.draw(drawVec(-14, 0));
moveBeam(18, 12);
}
else if ("Q".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, 18));
g2.draw(drawVec(-6, 6));
g2.draw(drawVec(-8, 0));
moveBeam(8, -6);
g2.draw(drawVec(6, 6));
moveBeam(4, 0);
}
else if ("R".equals(stringToRender[i])) {
g2.draw(drawVec(0, -24));
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, 12));
g2.draw(drawVec(-14, 0));
moveBeam(2, 0);
g2.draw(drawVec(12, 12));
moveBeam(4, 0);
}
else if ("T".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(14, 0));
moveBeam(-7, 0);
g2.draw(drawVec(0, 24));
moveBeam(11, 0);
}
else if ("U".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(0, 24));
g2.draw(drawVec(14, 0));
g2.draw(drawVec(0, -24));
moveBeam(4, 24);
}
else if ("V".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(7, 24));
g2.draw(drawVec(7, -24));
moveBeam(4, 24);
}
else if ("W".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(0, 24));
g2.draw(drawVec(7, -7));
g2.draw(drawVec(7, 7));
g2.draw(drawVec(0, -24));
moveBeam(4, 24);
}
else if ("X".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(14, 24));
moveBeam(-14, 0);
g2.draw(drawVec(14, -24));
moveBeam(4, 24);
}
else if ("Y".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(7, 7));
g2.draw(drawVec(7, -7));
moveBeam(-7, 7);
g2.draw(drawVec(0, 17));
moveBeam(11, 0);
}
else if ("Z".equals(stringToRender[i])) {
moveBeam(0, -24);
g2.draw(drawVec(14, 0));
g2.draw(drawVec(-14, 24));
g2.draw(drawVec(14, 0));
moveBeam(4, 0);
}
else if (".".equals(stringToRender[i])) {
moveBeam(7, 0);
g2.draw(drawVec(0, -2));
moveBeam(11, 2);
}
else if (",".equals(stringToRender[i])) {
moveBeam(7, 0);
g2.draw(drawVec(0, -5));
moveBeam(11, 5);
}
else if (":".equals(stringToRender[i])) {
moveBeam(7, 0);
g2.draw(drawVec(0, -2));
moveBeam(0, -10);
g2.draw(drawVec(0, -2));
moveBeam(11, 14);
}
else if (";".equals(stringToRender[i])) {
moveBeam(7, 0);
g2.draw(drawVec(0, -5));
moveBeam(0, -7);
g2.draw(drawVec(0, -2));
moveBeam(11, 14);
}
else if ("!".equals(stringToRender[i])) {
moveBeam(7, 0);
g2.draw(drawVec(0, -2));
moveBeam(0, -10);
g2.draw(drawVec(0, -12));
moveBeam(11, 24);
}
else if ("(".equals(stringToRender[i])) {
moveBeam(10, 0);
g2.draw(drawVec(-6, -6));
g2.draw(drawVec(0, -12));
g2.draw(drawVec(6, -6));
moveBeam(8, 24);
}
else if (")".equals(stringToRender[i])) {
moveBeam(10, 0);
g2.draw(drawVec(6, -6));
g2.draw(drawVec(0, -12));
g2.draw(drawVec(-6, -6));
moveBeam(8, 24);
}
else { // Blank space
moveBeam(18, 0);
}
}
//</editor-fold>
// Revert back to the previous beam scale
this.beamScaleX = oldBeamScaleX;
this.beamScaleY = oldBeamScaleY;
}
}
gameState.incrementActualFrames(); // Count up actual frames rendered
// Debug Menu
if (gameState.getDebugMenuState()) {
g2.setColor(Color.white);
g2.drawString(gameState.getGameName() + " v" + gameState.getGameVersion(), 1, 12);
g2.drawString("Frame: " + gameState.getFrameCounter(), 1, 24);
g2.drawString("Horizontal beamscale: " + beamScaleX, 1, 36);
g2.drawString("Vertical beamscale: " + beamScaleY, 1, 48);
g2.drawString("Paused: " + gameState.getPausedState(), 1, 60);
g2.drawString("Target framerate: " + gameState.getTargetFrameRate(), 150, 24);
g2.drawString("Actual framerate: " + gameState.getActualFrameRate(), 150, 36);
g2.drawString("actualFrames: " + gameState.getActualFrames(), 150, 48);
g2.drawString("LFSR: " + gameState.getPokey().getDecimalValue(), 150, 60);
g2.drawString("Ball count: " + gameState.getBalls().size(), 300, 24);
g2.drawString("Particle count: " + gameState.getParticles().size(), 300, 36);
g2.drawString("Paddle count: " + gameState.getPaddles().size(), 300, 48);
g2.drawString("Text count: " + gameState.getTextElements().size(), 300, 60);
g2.drawString("Brick count: " + gameState.getCurrentLevel().brickList.size(), 300, 72);
short padBoxX, padBoxY, padBoxX2, padBoxY2;
g2.setStroke(new BasicStroke(1));
// Paddles
g2.setColor(vc(10, 0.5f));
for (Paddle paddle : gameState.getPaddles()) {
padBoxX = (short) paddle.hitBox.getBounds().getBounds2D().getMinX();
padBoxY = (short) paddle.hitBox.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) paddle.hitBox.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) paddle.hitBox.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - paddle.height), padBoxX2, padBoxY2);
padBoxX = (short) paddle.hitBoxLeft.getBounds().getBounds2D().getMinX();
padBoxY = (short) paddle.hitBoxLeft.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) paddle.hitBoxLeft.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) paddle.hitBoxLeft.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - paddle.height), padBoxX2, padBoxY2);
padBoxX = (short) paddle.hitBoxRight.getBounds().getBounds2D().getMinX();
padBoxY = (short) paddle.hitBoxRight.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) paddle.hitBoxRight.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) paddle.hitBoxRight.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - paddle.height), padBoxX2, padBoxY2);
}
// Balls
g2.setColor(vc(11, 1.0f));
for (Ball ball : gameState.getBalls()) {
padBoxX = (short) ball.hitBox.getBounds().getBounds2D().getMinX();
padBoxY = (short) ball.hitBox.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) ball.hitBox.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) ball.hitBox.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - ball.height), padBoxX2, padBoxY2);
}
// Bricks
g2.setColor(vc(12, 0.5f));
for (Brick brick : gameState.getCurrentLevel().brickList) {
padBoxX = (short) brick.hitBox.getBounds().getBounds2D().getMinX();
padBoxY = (short) brick.hitBox.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) brick.hitBox.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) brick.hitBox.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - brick.height), padBoxX2, padBoxY2);
padBoxX = (short) brick.hitBoxLeft.getBounds().getBounds2D().getMinX();
padBoxY = (short) brick.hitBoxLeft.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) brick.hitBoxLeft.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) brick.hitBoxLeft.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - brick.height), padBoxX2, padBoxY2);
padBoxX = (short) brick.hitBoxRight.getBounds().getBounds2D().getMinX();
padBoxY = (short) brick.hitBoxRight.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) brick.hitBoxRight.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) brick.hitBoxRight.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - brick.height), padBoxX2, padBoxY2);
}
}
}
public Line2D.Float drawVec(float x, float y) {
float oldBeamX = beamX;
float oldBeamY = beamY;
beamX += (x * beamScaleX);
beamY += (y * beamScaleY);
return new Line2D.Float(oldBeamX, oldBeamY, beamX, beamY);
}
public Color vc(int colorID) {
return vc(colorID, 1.0f);
}
public Color vc(int colorID, float alpha) {
switch ((byte) colorID) {
case 0: return new Color(0, 0, 0, alpha); // black
// default is white but will be written elsewhere as 1
case 2: return new Color(0.66f, 0.66f, 0.66f, alpha); // grey
case 3: return new Color(0.5f, 0.5f, 0.5f, alpha); // dark grey
case 4: return new Color(0.66f, 0, 0, alpha); // dark red
case 5: return new Color(0, 0.66f, 0, alpha); // dark green
case 6: return new Color(0, 0, 0.66f, alpha); // dark blue
case 7: return new Color(1, 0, 0, alpha); // red
case 8: return new Color(0, 1, 0, alpha); // green
case 9: return new Color(0, 0, 1, alpha); // blue
case 10: return new Color(1, 0.5f, 0.5f, alpha); // light red
case 11: return new Color(0.5f, 1, 0.5f, alpha); // light green
case 12: return new Color(0.5f, 0.5f, 1, alpha); // light blue
case 13: return new Color(1, 0.66f, 0, alpha); // orange
case 14: return new Color(1, 1, 0, alpha); // yellow
case 15: return new Color(0, 0.66f, 0.66f, alpha); // cyan
case 16: return new Color(0, 1, 1, alpha); // aqua
case 17: return new Color(0.66f, 0, 0.66f, alpha); // purple
case 18: return new Color(1, 0, 1, alpha); // magenta
case 19: return new Color(1, 0.5f, 0.75f, alpha); // pastel pink
case 20: return new Color(0.5f, 0.66f, 1, alpha); // pastel blue
default: return new Color(1, 1, 1, alpha); // white
}
}
public BasicStroke bt(float size) {
return new BasicStroke(size * beamThicknessScale);
}
public void moveBeam(float x, float y) {
beamX += (x * beamScaleX);
beamY += (y * beamScaleY);
}
public void resetBeam() {
beamX = 0;
beamY = 40 * beamScaleY;
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.Color;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
/**
* The window in which the game is displayed.
* @author Naomi (boyfailure.dev)
* @since 1.0
*/
public class GameFrame extends JFrame {
private GameState gameState;
private GameDisplay gameDisplay;
public GameFrame(GameState gameState, GameDisplay gameDisplay) {
this.gameState = gameState;
this.gameDisplay = gameDisplay;
// Set the game window's properties
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle(this.gameState.getGameName());
this.setSize(this.gameState.getWindowResX(), this.gameState.getWindowResY());
this.getContentPane().setBackground(Color.black);
this.getContentPane().add(this.gameDisplay);
// Add the component adapter to change vector drawing scale when the window is resized
this.addComponentListener(this.getComponentAdapter());
// Add the key listener to get player input
this.addKeyListener(this.gameState.getKeyListener());
}
//<editor-fold defaultstate="collapsed" desc=" Event listeners ">
public ComponentAdapter getComponentAdapter() {
return new GameComponentAdapter();
}
class GameComponentAdapter extends ComponentAdapter {
public void componentResized(ComponentEvent componentEvent) {
GameFrame.this.gameState.setWindowResX((short) GameFrame.this.getBounds().width);
GameFrame.this.gameState.setWindowResY((short) GameFrame.this.getBounds().height);
GameFrame.this.gameDisplay.setBeamScaleX(GameFrame.this.gameState.getWindowResX() / 800f);
GameFrame.this.gameDisplay.setBeamScaleY(GameFrame.this.gameState.getWindowResY() / 600f);
if (GameFrame.this.gameDisplay.getBeamScaleX() <= GameFrame.this.gameDisplay.getBeamScaleY()) {GameFrame.this.gameDisplay.setBeamThicknessScale(GameFrame.this.gameDisplay.getBeamScaleX());}
else {GameFrame.this.gameDisplay.setBeamThicknessScale(GameFrame.this.gameDisplay.getBeamScaleY());}
}
}
//</editor-fold>
}

View file

@ -0,0 +1,546 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
/**
* The embeddable game object which contains the main loop and logic.
* @author Naomi (boyfailure.dev)
* @since 20250511
*/
public class GameState {
/** The name of the game. */
private String gameName = "Vector Breakout";
/** The version of the game. */
private String gameVersion = "1.0";
/** Determines whether the game has been started. */
private boolean isGameStarted = false;
/** Determines whether the game has been paused. */
private boolean isPaused = false;
/** Determines whether the debug menu has been enabled. */
private boolean debugMenuEnabled = false;
/** Determines whether the game has been lost. */
private boolean isLost = false;
/** Determines whether the player is moving left. */
private boolean movingLeft = false;
/** Determines whether the player is moving right. */
private boolean movingRight = false;
/** Determines whether "Confetti Mode" (long-lasting particles) has been enabled. */
private boolean confettiMode = false;
/** Determines whether the collector function is running. */
private boolean collectorActive = false;
/** The number of lives the player has. */
private byte lives = 5;
/** The target frame rate for the game. */
private short targetFrameRate = 60;
private short actualFrames = 0;
private short actualFrameRate = 0;
private byte gameTickRate = 60;
private short internalResX = 800;
private short internalResY = 600;
private short windowResX = 1280;
private short windowResY = 960;
private short level = 1;
private short bricksOnScreen = 0;
private int score = 0;
private long frameCounter = 0;
private Level currentLevel;
private ArrayList<Ball> ballList;
private ArrayList<Particle> particleList;
private ArrayList<Paddle> paddleList;
private ArrayList<TextElement> textElementList;
private ArrayList<Ball> ballCollector;
private ArrayList<Particle> particleCollector;
private ArrayList<Paddle> paddleCollector;
private ArrayList<TextElement> textElementCollector;
private ArrayList<Brick> brickCollector;
private LFSR pokey;
public GameState() {
this.initComponents();
}
//<editor-fold defaultstate="collapsed" desc=" Event listeners ">
public KeyListener getKeyListener() {
return new GameKeyListener();
}
public ActionListener getGameStateUpdateActionListener() {
return new GameStateUpdateListener();
}
public ActionListener getGameTickActionListener() {
return new GameTickActionListener();
}
class GameTickActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
onGameTick();
}
}
class GameKeyListener implements KeyListener {
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case 27: // Escape - Toggle debug menu
toggleDebugMenu();
break;
case 10: // Enter - Pause/unpause
if (GameState.this.getGameStartedState()) {GameState.this.togglePausedState();}
break;
case 35: // End - Start debug level
if (isLost || !GameState.this.getGameStartedState()) {GameState.this.setGameStartedState(true);GameState.this.newGame(true);}
break;
case 32: // Space - Start normal game, spawn ball
if (isLost || !GameState.this.getGameStartedState()) {GameState.this.setGameStartedState(true);GameState.this.newGame();}
else if (!GameState.this.getPausedState()) {
if (GameState.this.getBalls().size() < 1) {
Ball newBall = new Ball(GameState.this, 200, 300, 7, 7, 0, true);
GameState.this.ballList.add(newBall);
newBall.spawnBall(true);
}
}
break;
case 37: // Left - move left
if (GameState.this.getGameStartedState()) {movingLeft = true;}
break;
case 39: // Right - move right
if (GameState.this.getGameStartedState()) {movingRight = true;}
break;
}
}
public void keyReleased(KeyEvent e) {
switch(e.getKeyCode()) {
case 37: // Left
movingLeft = false;
break;
case 39: // Right
movingRight = false;
break;
}
}
public void keyTyped(KeyEvent e) {}
}
class GameStateUpdateListener implements ActionListener {
public synchronized void actionPerformed(ActionEvent e) {
GameState.this.collectorActive = true;
GameState.this.ballCollector = new ArrayList<>();
GameState.this.particleCollector = new ArrayList<>();
GameState.this.paddleCollector = new ArrayList<>();
GameState.this.textElementCollector = new ArrayList<>();
GameState.this.brickCollector = new ArrayList<>();
bricksOnScreen = 0;
actualFrameRate = actualFrames;
actualFrames = 0;
if (GameState.this.currentLevel != null) {
if (GameState.this.currentLevel.brickList != null) {
for (Brick brick : GameState.this.currentLevel.brickList) {
if (brick.getBrokenState()) {
GameState.this.brickCollector.add(brick);
}
else {
bricksOnScreen++;
}
}
}
}
for (Ball ball : GameState.this.ballList) {
if (!ball.getActiveState()) {
GameState.this.ballCollector.add(ball);
}
}
for (Particle particle: GameState.this.particleList) {
if (!particle.getActiveState()) {
GameState.this.particleCollector.add(particle);
}
}
if (bricksOnScreen <= 0 && GameState.this.getGameStartedState() && !isLost) {
level++;
GameState.this.currentLevel = new Level(GameState.this, level);
GameState.this.currentLevel.constructLevel();
}
GameState.this.ballList.removeAll(GameState.this.ballCollector);
GameState.this.paddleList.removeAll(GameState.this.paddleCollector);
GameState.this.particleList.removeAll(GameState.this.particleCollector);
GameState.this.textElementList.removeAll(GameState.this.textElementCollector);
if (GameState.this.currentLevel != null) {
GameState.this.currentLevel.brickList.removeAll(GameState.this.brickCollector);
}
GameState.this.collectorActive = false;
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Accessor methods (get/set) ">
//<editor-fold defaultstate="collapsed" desc=" Version information accessors ">
/**
* Gets the name of the game.
* @return the name of the game
*/
public String getGameName() {
return this.gameName;
}
/**
* Gets the version number of the game.
* @return the version number of the game
*/
public String getGameVersion() {
return this.gameVersion;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Pokey accessors ">
/**
* Gets the current instance of "Pokey", the game's LFSR.
* @return pokey
*/
public LFSR getPokey() {
return this.pokey;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Tick rate accessors ">
/**
* Gets the current game tick rate, in Hz.
* @return the current game tick rate (in Hz)
*/
public byte getGameTickRate() {
return this.gameTickRate;
}
/**
* Sets the current game tick rate, in Hz.
* @param newTickRate the game tick rate, in Hz
*/
public void setGameTickRate(byte newTickRate) {
this.gameTickRate = (byte) newTickRate;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Frame rate accessors ">
/**
* Gets the target game frame rate, in Hz.
* @return the target game frame rate (in Hz)
*/
public short getTargetFrameRate() {
return this.targetFrameRate;
}
/**
* Gets the actual game frame rate, in Hz.
* @return the actual game frame rate (in Hz)
*/
public short getActualFrameRate() {
return this.actualFrameRate;
}
/**
* Increments the frame counter.
*/
public void incrementFrameCounter() {
this.frameCounter++;
}
/**
* Gets the value of the frame counter.
* @return the value of the frame counter
*/
public long getFrameCounter() {
return this.frameCounter;
}
/**
* Gets the actual frames drawn during the last second.
* @return the actual frames drawn during the last second.
*/
public short getActualFrames() {
return this.actualFrames;
}
public void incrementActualFrames() {
this.actualFrames++;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Pause state accessors ">
public boolean getPausedState() {
return this.isPaused;
}
public void togglePausedState() {
this.isPaused = !this.isPaused;
if (this.getPausedState()) {this.textElementList.get(6).getVisibleState(true);}
else {this.textElementList.get(6).getVisibleState(false);}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Debug menu state accessors ">
public boolean getDebugMenuState() {
return this.debugMenuEnabled;
}
public void toggleDebugMenu() {
this.debugMenuEnabled = !this.debugMenuEnabled;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Game start state accessors ">
public boolean getGameStartedState() {
return this.isGameStarted;
}
public void setGameStartedState(boolean startState) {
this.isGameStarted = startState;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Resolution accessors ">
public short getInternalResX() {
return this.internalResX;
}
public short getInternalResY() {
return this.internalResY;
}
public short getWindowResX() {
return this.windowResX;
}
public short getWindowResY() {
return this.windowResY;
}
public void setWindowResX(short resolution) {
this.windowResX = resolution;
}
public void setWindowResY(short resolution) {
this.windowResY = resolution;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Lives accessors ">
public byte getLives() {
return this.lives;
}
public void addLives(int livesAdded) {
if (livesAdded >= 127) {livesAdded = 127;}
else if (livesAdded < 0) {livesAdded = 0;}
this.lives += (byte) livesAdded;
}
public void addLife() {
this.addLives(1);
}
public void removeLife() {
this.lives--;
if (this.lives < 0) {this.lives = 0;}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Score accessors ">
public int getScore() {
return this.score;
}
public void addScore(int scoreAdded) {
this.score += scoreAdded;
}
public void resetScore() {
this.score = 0;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Level accessors ">
public short getCurrentLevelID() {
return this.level;
}
public void setCurrentLevelID(short levelID) {
this.level = levelID;
}
public Level getCurrentLevel() {
return this.currentLevel;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" List accessors ">
public ArrayList<Ball> getBalls() {
return this.ballList;
}
public ArrayList<Particle> getParticles() {
return this.particleList;
}
public ArrayList<Paddle> getPaddles() {
return this.paddleList;
}
public ArrayList<TextElement> getTextElements() {
return this.textElementList;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Collector accessors ">
public boolean getCollectorActiveState() {
return this.collectorActive;
}
//</editor-fold>
//</editor-fold>
public synchronized void onGameTick() {
if (this.getPausedState()) {return;}
// Move player if keys are held
try {
for (Paddle paddle : this.paddleList) {
if (this.movingLeft) {
paddle.movePaddle(false);
}
else if (this.movingRight) {
paddle.movePaddle(true);
}
else {
paddle.paddleSpeed = 0;
}
}
} catch(java.util.ConcurrentModificationException except) {
System.err.println("ConcurrentModificationException, onGameTick() Paddle iteration " + System.currentTimeMillis());
}
// Ball logic (movement, collision checks)
try {
for (Ball ball : this.ballList) {
if (ball.getActiveState()) {
ball.moveBall();
}
}
} catch(java.util.ConcurrentModificationException except) {
System.err.println("ConcurrentModificationException, onGameTick() Ball iteration " + System.currentTimeMillis());
}
// Particles
try {
for (Particle particle : this.particleList) {
if (particle.getActiveState()) {
particle.update();
}
}
} catch(java.util.ConcurrentModificationException except) {
System.err.println("ConcurrentModificationException, onGameTick() Particle iteration " + System.currentTimeMillis());
}
}
public void showTitleScreen() {
textElementList.get(0).activateTimer();
textElementList.get(1).activateTimer();
textElementList.get(2).activateTimer();
}
public synchronized void gameLose() {
if (this.currentLevel != null) {
for (Brick brick : this.currentLevel.brickList) {
brick.setBrokenState(true);
this.brickCollector.add(brick);
}
}
for (Ball ball : this.ballList) {
ball.setActiveState(false);
this.ballCollector.add(ball);
}
for (Paddle paddle : this.paddleList) {
paddle.setActiveState(false);
paddle.cullPaddle();
this.paddleCollector.add(paddle);
}
this.isLost = true;
this.textElementList.get(3).getVisibleState(false);
this.textElementList.get(4).getVisibleState(false);
this.textElementList.get(5).getVisibleState(false);
this.textElementList.get(8).getVisibleState(true);
this.textElementList.get(9).getVisibleState(true);
this.getGameStateUpdateActionListener().actionPerformed(new ActionEvent(this, 0, ""));
}
public void newGame() {
newGame(false);
}
public void newGame(boolean debugLevel) {
lives = 5;
isLost = false;
score = 0;
if (debugLevel) {level = 32767;}
else {level = 1;}
frameCounter = 0;
movingLeft = false;
movingRight = false;
this.paddleList.add(new Paddle(this, 350, 500, 100, 15, 0));
this.textElementList.get(0).getVisibleState(false);
this.textElementList.get(1).getVisibleState(false);
this.textElementList.get(2).getVisibleState(false);
this.textElementList.get(3).getVisibleState(true);
this.textElementList.get(4).getVisibleState(true);
this.textElementList.get(5).getVisibleState(true);
textElementList.get(3).setText(String.valueOf(this.score));
textElementList.get(4).setText(String.valueOf(this.lives));
textElementList.get(5).setText(String.valueOf(this.level));
this.textElementList.get(8).getVisibleState(false);
this.textElementList.get(9).getVisibleState(false);
this.currentLevel = new Level(this, level);
this.currentLevel.constructLevel();
this.updateStaticTextElements();
}
public synchronized void initComponents() {
this.ballList = new ArrayList<>();
this.particleList = new ArrayList<>();
this.paddleList = new ArrayList<>();
this.textElementList = new ArrayList<>();
this.pokey = new LFSR();
// List of indices
textElementList.add(new TextElement(this, "VECTOR BREAKOUT", 2.5f, 67, 280, 15, 2)); // 0 - Title
textElementList.add(new TextElement(this, "MMXXV BOYFAILURE.DEV", 0.75f, 259, 315, 15, 1)); // 1 - Copyright
textElementList.add(new TextElement(this, "PRESS SPACE TO BEGIN", 1, 215, 345, 15, 2)); // 2 - Space
textElementList.add(new TextElement(this, String.valueOf(score), 1, 20, -13, 1, 2)); // 3 - Score
textElementList.add(new TextElement(this, String.valueOf(lives), 1, 200, -13, 1, 2)); // 4 - Lives
textElementList.add(new TextElement(this, String.valueOf(level), 1, 380, -13, 1, 2)); // 5 - Level
textElementList.add(new TextElement(this, "PAUSED", 5, 142, 310, 1, 4)); // 6 - Paused
textElementList.add(new TextElement(this, "Level Name", 1, 24, 480, 15, 2, 3000)); // 7 - Level Name
textElementList.add(new TextElement(this, " GAME OVER ", 2.5f, 67, 280, 10, 2)); // 8 - Lose
textElementList.add(new TextElement(this, "PRESS SPACE TO RETRY", 1, 215, 315, 10, 1)); // 9 - Retry
}
public void updateStaticTextElements() {
textElementList.get(3).setText(String.valueOf(score));
textElementList.get(4).setText(String.valueOf(lives));
textElementList.get(5).setText(String.valueOf(level));
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.geom.Rectangle2D;
/**
* Invisible regions in each object that dictate when it collides with other objects.
* @author Naomi (boyfailure.dev)
* @since 20250327
*/
public class Hitbox {
private Rectangle2D bounds;
byte padding = 0;
public Hitbox(int x, int y, int x2, int y2, int padding) {
this.padding = (byte) padding;
this.bounds = new Rectangle2D.Float(x - padding, y - padding, x2 + padding, y2 + padding);
}
public boolean collides(Rectangle2D hitBox2) {
if (this.bounds.intersects(hitBox2)) {
return true;
}
else {
return false;
}
}
public void moveTo(int x, int y, int x2, int y2) {
this.bounds.setRect(x - this.padding, y - this.padding, x2 + padding, y2 + padding);
}
public Rectangle2D getBounds() {
return this.bounds;
}
public void setBounds(int x, int y, int x2, int y2) {
this.bounds = new Rectangle2D.Float(x - this.padding, y - this.padding, x2 + this.padding, y2 + this.padding);
}
}

View file

@ -0,0 +1,98 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
/**
* A software implementation of a LFSR-based (linear frequency shift register) pseudo-random number generator.
* @author Naomi (boyfailure.dev)
* @since 1.0
*/
public class LFSR {
private static boolean[] originalArray = new boolean[] {true,false,true,true,true,true,true,true,false,true,false,false,false,true,true,false,false};
private boolean[] bitArray;
private int bitCount;
private int bitsReturned;
private int firstXorIndex;
private int secondXorIndex;
private Timer lfsrTimer;
public LFSR(int bitCount, int bitsReturned, int firstXorIndex, int secondXorIndex) {
this.bitCount = bitCount;
this.bitsReturned = bitsReturned;
this.firstXorIndex = firstXorIndex;
this.secondXorIndex = secondXorIndex;
if (bitsReturned > bitCount) {bitsReturned = bitCount;}
bitArray = new boolean[bitCount];
for (int i = 0; i < bitCount; i++) {
if (i < 17) {bitArray[i] = LFSR.originalArray[i];}
else {
bitArray[i] = bitArray[i - 3] ^ bitArray[i - 8];
}
}
this.lfsrTimer = new Timer(1, new ActionListener(){public void actionPerformed(ActionEvent e) {LFSR.this.shift();}});
this.lfsrTimer.start();
}
public LFSR() {
this(17, 8, 11, 16);
}
public int getDecimalValue() {
int regValue = 0;
int bitIndex = this.bitCount - 1;
for (int i = 0; i < this.bitsReturned; i++) {
if (this.bitArray[bitIndex]) {
regValue += Math.pow(2, i);
}
bitIndex--;
}
return regValue;
}
public int getShiftedDecimalValue() {
this.shift();
return this.getDecimalValue();
}
public void shift() {
for (int i = (this.bitCount - 1); i > 0; i--) {
this.bitArray[i] = this.bitArray[i - 1];
}
this.bitArray[0] = this.bitArray[this.firstXorIndex] ^ this.bitArray[this.secondXorIndex];
}
public String getBits() {
String result = "";
for (int i = 0; i < this.bitCount; i++) {
if (this.bitArray[i] == true) {
result += "1";
}
else {
result += "0";
}
}
return result;
}
}

View file

@ -0,0 +1,261 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.util.ArrayList;
/**
* The progressive levels that contain different brick layouts.
* @author Naomi (boyfailure.dev)
* @since 20250325
*/
public class Level {
short levelID;
byte rowHeight = 21;
String levelName = "LEVEL";
byte[] levelLayout; // 19 bricks per row
byte[] colorMap;
ArrayList<Brick> brickList;
private GameState gameState;
public Level(GameState gameState, int id) {
this.gameState = gameState;
this.levelID = (short) id;
switch(this.levelID) {
case 1:
this.levelName = "HELLO WORLD";
this.levelLayout = new byte[]
{4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
this.colorMap = new byte[]
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 2:
this.levelName = "DOUBLE TROUBLE";
this.levelLayout = new byte[]
{4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,
3,3,3,3,3,16,3,3,3,3,3,3,3,16,3,3,3,3,3,
2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,
1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1};
this.colorMap = new byte[]
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,18,13,13,13,13,13,13,13,18,13,13,13,13,13,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 3:
this.levelName = "THE GAP";
this.levelLayout = new byte[]
{5,5,5,5,5, 5,5,5,0,0,0,5,5, 5,5,5,5,5,5,
4,4,4,4,4, 4,4,4,0,0,0,4,4, 4,4,4,4,4,4,
3,3,3,3,3,16,3,3,0,0,0,3,3,16,3,3,3,3,3,
2,2,2,2,2, 2,2,2,0,0,0,2,2, 2,2,2,2,2,2,
1,1,1,1,1, 1,1,1,0,0,0,1,1, 1,1,1,1,1,1};
this.colorMap = new byte[]
{14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,18,13,13,13,13,13,13,13,18,13,13,13,13,13,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 4:
this.levelName = "HOLE IN THE WALL";
this.levelLayout = new byte[]
{4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
3,3,0,0,3,3,3,3,0,0,0,3,3,3,3,0,0,3,3,
2,2,0,0,2,2,2,2,0,0,0,2,2,2,2,0,0,2,2,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
this.colorMap = new byte[]
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 5:
this.levelName = "A SECOND OPINION";
this.levelLayout = new byte[]
{0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0};
this.colorMap = new byte[]
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 6:
this.levelName = "SLICES";
this.levelLayout = new byte[]
{5,5,0,5,5,0,5,5,0,5,5,0,5,5,0,5,5,0,5,
4,0,4,4,0,4,4,0,4,4,0,4,4,0,4,4,0,4,4,
0,3,3,0,3,3,0,3,3,0,3,3,0,3,3,0,3,3,0,
2,2,0,2,2,0,2,2,0,2,2,0,2,2,0,2,2,0,2,
1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,
0,4,4,0,4,4,0,4,4,0,4,4,0,4,4,0,4,4,0,
3,3,0,3,3,0,3,3,0,3,3,0,3,3,0,3,3,0,3,
2,0,2,2,0,2,2,0,2,2,0,2,2,0,2,2,0,2,2,
0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0,1,1,0};
this.colorMap = new byte[]
{14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 1001:
this.levelName = "TRANS RIGHTS";
this.levelLayout = new byte[]
{5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
3,3,3,3,3,3,3,3,3,3,3,3,3,5,3,3,3,3,3,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
this.colorMap = new byte[]
{20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20};
break;
case 1002:
this.levelName = "I USE ARCH BTW";
this.levelLayout = new byte[]
{0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,6,6,6,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,5,5,5,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,4,4,0,4,4,0,0,0,0,0,0,0,
0,0,0,0,0,0,3,3,0,0,0,3,3,0,0,0,0,0,0,
0,0,0,0,0,2,2,2,0,0,0,2,2,2,0,0,0,0,0,
0,0,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,0,0};
this.colorMap = new byte[]
{16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16};
break;
case 32767:
this.levelName = "DEBUG.LEVEL;";
this.levelLayout = new byte[]
{1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,
2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,
3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,
4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,
5,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,
6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,
7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,
1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5};
this.colorMap = new byte[]
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,
1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0,
2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1,
3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2,
4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2, 3,
5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2, 3, 4,
6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2, 3, 4, 5,
7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2, 3, 4, 5, 6};
break;
default:
this.levelName = "LEVEL 404: NOT FOUND (" + this.levelID + ")";
this.levelLayout = new byte[]
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
this.colorMap = new byte[]
{2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2};
break;
}
}
public void constructLevel() {
this.brickList = new ArrayList<>();
// Brick placement
short brickLeftPlacement = 20;
for (int i = 0; i < this.levelLayout.length; i++) {
switch (this.levelLayout[i]) {
case 1:
this.brickList.add(new Brick(gameState, brickLeftPlacement, (((i / 19)) * this.rowHeight) + 50, false, this.colorMap[i], 4, 0));
break;
case 2:
this.brickList.add(new Brick(gameState, brickLeftPlacement, (((i / 19)) * this.rowHeight) + 50, false, this.colorMap[i], 8, 0));
break;
case 3:
this.brickList.add(new Brick(gameState, brickLeftPlacement, (((i / 19)) * this.rowHeight) + 50, false, this.colorMap[i], 16, 0));
break;
case 4:
this.brickList.add(new Brick(gameState, brickLeftPlacement, (((i / 19)) * this.rowHeight) + 50, false, this.colorMap[i], 32, 0));
break;
case 5:
this.brickList.add(new Brick(gameState, brickLeftPlacement, (((i / 19)) * this.rowHeight) + 50, false, this.colorMap[i], 64, 0));
break;
case 6:
this.brickList.add(new Brick(gameState, brickLeftPlacement, (((i / 19)) * this.rowHeight) + 50, false, this.colorMap[i], 8, 0));
break;
case 7:
this.brickList.add(new Brick(gameState, brickLeftPlacement, (((i / 19)) * this.rowHeight) + 50, false, this.colorMap[i], 8, 0));
break;
case 16: // Spawns a ball
this.brickList.add(new Brick(gameState, brickLeftPlacement, (((i / 19)) * this.rowHeight) + 50, false, this.colorMap[i], 8, 2));
break;
default:
break;
}
brickLeftPlacement += 40;
if (brickLeftPlacement >= 760) {brickLeftPlacement = 20;}
}
this.startLevel();
}
public void startLevel() {
gameState.getTextElements().set(7, new TextElement(gameState, this.levelName, 1, 24, 480, 15, 2, 3000));
gameState.getTextElements().get(7).activateTimer();
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.util.ArrayList;
/**
* Customizable game menus.
* @author Naomi (boyfailure.dev)
* @since 1.0
*/
public class Menu {
public String menuTitle = "Menu";
public byte index = 0;
private ArrayList<TextElement> textElementList;
private GameState gameState;
public Menu(GameState gameState) {
this.gameState = gameState;
}
/**
* Adds a TextElement to the Menu.
* @param text The text that will be displayed.
* @param textScale The size of the text on screen.
* @param x The x coordinate of the text's origin point.
* @param y The y coordinate of the text's origin point.
* @param textColor The color of the text.
* @param vectorScale The scale of the text.
*/
public void addTextElement(String text, float textScale, int x, int y, int textColor, float vectorScale) {
textElementList.add(new TextElement(this.gameState, text, textScale, x, y, textColor, vectorScale));
}
/**
* Gets the TextElement at the specified index in the Menu.
* @param index
* @return The TextElement at the index.
*/
public TextElement getTextElement(int index) {
return textElementList.get(index);
}
public void activate() {
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
/**
* The player-controlled paddle that guides balls into bricks.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class Paddle extends Entity {
byte paddleColor;
byte paddleSpeed = 0;
boolean isActive = true;
float speedMultiplier = 1;
Hitbox hitBoxLeft;
Hitbox hitBoxRight;
private GameState gameState;
public Paddle(GameState gameState, int x, int y, int width, int height, int paddleColor) {
this.gameState = gameState;
this.posX = (short) x;
this.posY = (short) y;
this.width = (short) width;
this.height = (short) height;
this.paddleColor = (byte) paddleColor;
this.hitBox = new Hitbox(x, y, width, height, 4);
this.hitBoxLeft = new Hitbox(x, y, width / 5, height, 4);
this.hitBoxRight = new Hitbox(x + ((width / 5) * 4), y, width / 5, height, 4);
}
public void movePaddle(boolean direction) {
if (this.paddleSpeed <= 15) {this.paddleSpeed += 3;}
if (direction) {this.posX += (this.paddleSpeed * this.speedMultiplier);}
else {this.posX -= (this.paddleSpeed * this.speedMultiplier);}
if (this.posX <= 20) {this.posX = 20;}
else if (this.posX >= (gameState.getInternalResX() - this.width - 20)) {this.posX = (short) (gameState.getInternalResX() - this.width - 20);}
this.hitBox.moveTo(this.posX, this.posY, this.width, this.height);
this.hitBoxLeft.moveTo(this.posX, this.posY, this.width / 5, this.height);
this.hitBoxRight.moveTo(this.posX + ((this.width / 5) * 4), this.posY, this.width / 5, this.height);
}
public void cullPaddle() {
isActive = false;
this.posX = 32767;
this.posY = 32767;
this.hitBox.setBounds(32766, 32766, 32767, 32767);
this.hitBoxLeft.setBounds(32766, 32766, 32767, 32767);
this.hitBoxRight.setBounds(32766, 32766, 32767, 32767);
}
public boolean getActiveState() {
return this.isActive;
}
public void setActiveState(boolean activeState) {
this.isActive = activeState;
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
/**
* Small graphical effects that appear on screen.
* @author Naomi (boyfailure.dev)
* @since 20250325
*/
public class Particle {
byte particleID;
short particleX;
short particleY;
short particleX2;
short particleY2;
boolean isActive = true;
byte particleColor;
short particleFrame = 0;
short particleLifetime;
float vectorScale = 1;
byte particleDirection;
byte particleSpeed = 2;
private GameState gameState;
public Particle(GameState gameState, int particleID, int x, int y, int x2, int y2, int particleColor, float vectorScale, int direction, int speed) {
this.gameState = gameState;
this.particleID = (byte) particleID;
this.particleX = (short) x;
this.particleY = (short) y;
this.particleX2 = (short) x2;
this.particleY2 = (short) y2;
this.particleColor = (byte) particleColor;
this.vectorScale = vectorScale;
this.particleDirection = (byte) direction;
this.particleSpeed = (byte) speed;
}
public void spawn(int lifetime) {
this.isActive = true;
// if (gameState.getConfettiMode()) {this.particleLifetime = 400;}
// else {this.particleLifetime = (short) lifetime;}
this.particleLifetime = (short) lifetime;
}
public void update() {
this.particleFrame++;
if (this.particleDirection != 0) {
switch(this.particleDirection) {
case 1:this.particleX += this.particleSpeed;break; // Right
case 2:this.particleX += this.particleSpeed;this.particleY+= this.particleSpeed;break; // Down right
case 3:this.particleY += this.particleSpeed;break; // Down
case 4:this.particleX -= this.particleSpeed;this.particleY+= this.particleSpeed;break; // Down left
case 5:this.particleX -= this.particleSpeed;break; // Left
case 6:this.particleX -= this.particleSpeed;this.particleY-= this.particleSpeed;break; // Up left
case 7:this.particleY -= this.particleSpeed;break; // Up
case 8:this.particleX += this.particleSpeed;this.particleY-= this.particleSpeed;break; // Up right
}
}
if (this.particleFrame >= this.particleLifetime) {
this.isActive = false;
}
}
public boolean getActiveState() {
return this.isActive;
}
}

View file

@ -0,0 +1,247 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.Point;
import java.util.Timer;
import java.util.TimerTask;
/**
* Fields of text that display on screen.
* @author Naomi (boyfailure.dev)
* @since 20250326
*/
public class TextElement {
private String text;
private boolean active = true;
private boolean isVisible = false;
private boolean moveUpOnFrame = false;
private float textScale = 1;
private float vectorThickness = 1;
private byte textColor;
private short x;
private short y;
private short duration = 0;
private long creationTime;
private GameState gameState;
/**
* Text elements.
* @param gameState the GameState in which the TextElement will reside
* @param text the text that will be displayed
* @param textScale the size of the text on screen
* @param x the x coordinate of the text's origin point
* @param y the y coordinate of the text's origin point
* @param textColor the color of the text
* @param vectorThickness the thickness of the vector lines
*/
public TextElement(GameState gameState, String text, float textScale, int x, int y, int textColor, float vectorThickness) {
this.gameState = gameState;
this.text = text;
this.textScale = textScale;
this.x = (short) x;
this.y = (short) y;
this.textColor = (byte) textColor;
this.vectorThickness = vectorThickness;
}
/**
* Text elements with a fadeout timer
* @param gameState the GameState in which the TextElement will reside
* @param text the text that will be displayed
* @param textScale the size of the text on screen
* @param x the x coordinate of the text's origin point
* @param y the y coordinate of the text's origin point
* @param textColor the color of the text
* @param vectorThickness the thickness of the vector lines
* @param duration how long the text will stay on screen, in milliseconds
*/
public TextElement(GameState gameState, String text, float textScale, int x, int y, int textColor, float vectorThickness, int duration) {
this.gameState = gameState;
this.text = text;
this.textScale = textScale;
this.x = (short) x;
this.y = (short) y;
this.textColor = (byte) textColor;
this.vectorThickness = vectorThickness;
this.duration = (short) duration;
this.creationTime = System.currentTimeMillis();
}
//<editor-fold defaultstate="collapsed" desc=" Accessor methods (get/set) ">
//<editor-fold defaultstate="collapsed" desc=" Active state accessors ">
/**
* Gets the active status of the TextElement.
* @return the active status of the TextElement
*/
public boolean getActiveState() {
return this.active;
}
/**
* Sets the active status of the TextElement.
* @param activeState the active status of the TextElement
*/
public void setActiveState(boolean activeState) {
this.active = activeState;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Visibility state accessors ">
/**
* Gets the visibility state of the TextElement.
* @return the visibility state of the TextElement
*/
public boolean getVisibleState() {
return this.isVisible;
}
/**
* Sets the visible state of the TextElement.
* @param visibleState the visible state of the TextElement
*/
public void getVisibleState(boolean visibleState) {
this.isVisible = visibleState;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Color code accessors ">
/**
* Gets the color code of the TextElement.
* @return the color code of the TextElement
*/
public byte getColor() {
return this.textColor;
}
/**
* Sets the color code of the TextElement.
* @param textColor the color code of the TextElement
*/
public void setColor(int textColor) {
this.textColor = (byte) textColor;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Scaling accessors ">
/**
* Returns the thickness of the TextElement's vector lines.
* @return the thickness of the TextElement's vector lines
*/
public float getVectorThickness() {
return this.vectorThickness;
}
/**
* Sets the thickness of the TextElement's vector lines.
* @param vectorThickness the new thickness of the TextElement's vector lines
*/
public void setVectorThickness(float vectorThickness) {
this.vectorThickness = vectorThickness;
}
/**
* Returns the scale of the text.
* @return the scale of the text
*/
public float getTextScale() {
return this.textScale;
}
/**
* Sets the scale of the text.
* @param textScale the new scale of the text
*/
public void setTextScale(float textScale) {
this.textScale = textScale;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Text value accessors ">
/**
* Gets the text in the TextElement.
* @return the text in the TextElement
*/
public String getText() {
return this.text;
}
/**
* Changes the text that the TextElement will display.
* @param text the text that the TextElement will display
*/
public void setText(String text) {
this.text = text;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Moves up on frame accessors ">
public boolean movesUpOnFrame() {
return this.moveUpOnFrame;
}
public void moveUpOnFrame() {
this.moveUpOnFrame = true;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Position accessors ">
public Point getPosition() {
return new Point(this.x, this.y);
}
public short getPositionX() {
return this.x;
}
public short getPositionY() {
return this.y;
}
//</editor-fold>
//</editor-fold>
/**
* Prepares the TextElement for rendering.
*/
public void activateTimer() {
this.setActiveState(true);
long testLong = this.creationTime;
if (this.duration > 0) {
Timer levelTitleGTFO = new Timer();
levelTitleGTFO.schedule(new TimerTask() {
public void run() {
TextElement.this.getVisibleState(false);
}
}, this.duration);
}
this.getVisibleState(true);
}
/**
* Prepares the TextElement for culling.
*/
public void cull() {
this.active = false;
this.x = 32767;
this.y = 32767;
}
/**
* Changes the text that the TextElement will display.
* @param x The x coordinate of the text's origin point.
* @param y The y coordinate of the text's origin point.
*/
public void moveTo(int x, int y) {
this.x = (short) x;
this.y = (short) y;
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
/**
* The initial code to launch the game.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class VectorBreakout {
public static GameState gameState;
public static GameDisplay gameDisplay;
public static GameFrame gameFrame;
/**
* This empty constructor is currently only in here because the main class
* constructor generated in new projects on NetBeans 4. Will investigate to
* see if this is necessary in JDK 1.5 next time I am using those tools.
*/
public VectorBreakout() {}
public static void main(String[] args) {
// Enable OpenGL for hardware acceleration
System.setProperty("sun.java2d.opengl", "true");
// Initialize the components
gameState = new GameState();
gameDisplay = new GameDisplay(gameState);
gameFrame = new GameFrame(gameState, gameDisplay);
Timer gameTick = new Timer((1000 / gameState.getGameTickRate()), gameState.getGameTickActionListener());
gameTick.start();
Timer frameDisplay = new Timer((1000 / gameState.getTargetFrameRate()), new ActionListener() {
public void actionPerformed(ActionEvent e) {
gameState.incrementFrameCounter();
gameFrame.repaint();
}
});
frameDisplay.start();
Timer gameCuller = new Timer(1000, gameState.getGameStateUpdateActionListener());
gameCuller.start();
// Show the game window
gameState.showTitleScreen();
gameFrame.setVisible(true);
}
}