Upload files to "/"
This commit is contained in:
57
CreditsPanel.java
Normal file
57
CreditsPanel.java
Normal file
@@ -0,0 +1,57 @@
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class CreditsPanel extends JPanel {
|
||||
private JList<String> creditsList;
|
||||
private JScrollPane scrollPane;
|
||||
private Timer timer;
|
||||
private int scrollPos = 0;
|
||||
|
||||
public CreditsPanel(String[] data, Runnable onComplete) {
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(Color.BLACK);
|
||||
|
||||
//JList
|
||||
creditsList = new JList<>(data);
|
||||
creditsList.setBackground(Color.BLACK);
|
||||
creditsList.setForeground(Color.WHITE);
|
||||
creditsList.setFont(new Font("SansSerif", Font.BOLD, 20));
|
||||
creditsList.setFixedCellHeight(40); // Consistent spacing
|
||||
|
||||
DefaultListCellRenderer renderer = (DefaultListCellRenderer) creditsList.getCellRenderer();
|
||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
//ScrollPane
|
||||
scrollPane = new JScrollPane(creditsList);
|
||||
scrollPane.setBorder(null);
|
||||
scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(0, 0)); // Hidden bar
|
||||
add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
//Animation
|
||||
timer = new Timer(30, e -> {
|
||||
JScrollBar vertical = scrollPane.getVerticalScrollBar();
|
||||
scrollPos++;
|
||||
vertical.setValue(scrollPos);
|
||||
|
||||
// Add a buffer (e.g., 100 pixels) to the finish check
|
||||
// to keep it on screen longer
|
||||
if (scrollPos >= (vertical.getMaximum() - vertical.getVisibleAmount()) + 100) {
|
||||
timer.stop();
|
||||
onComplete.run();
|
||||
}
|
||||
});
|
||||
|
||||
// Skip Button
|
||||
JButton skip = new JButton("SKIP");
|
||||
skip.addActionListener(e -> {
|
||||
timer.stop();
|
||||
onComplete.run();
|
||||
});
|
||||
add(skip, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
scrollPos = 0;
|
||||
timer.start();
|
||||
}
|
||||
}
|
||||
785
GameFields.java
Normal file
785
GameFields.java
Normal file
@@ -0,0 +1,785 @@
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.awt.event.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.ActionMap;
|
||||
import javax.swing.InputMap;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
public class GameFields extends JPanel implements ActionListener, KeyListener {
|
||||
|
||||
// ── Instance Fields ────────────────────────────────────────────────────
|
||||
private final int PANEL_WIDTH = 640;
|
||||
private final int PANEL_HEIGHT = 360;
|
||||
private final int MAX_Y = PANEL_HEIGHT - 40; // bottom bound for players
|
||||
|
||||
public int gustWidth = 80;
|
||||
public int gustHeight = 200;
|
||||
|
||||
// Menus / navigation
|
||||
public JPanel container;
|
||||
public CardLayout cardLayout;
|
||||
public String playerName1;
|
||||
public String playerName2;
|
||||
|
||||
// Element collections
|
||||
public ArrayList<Fluid> fluids;
|
||||
public ArrayList<Gust> windBoxes;
|
||||
public ArrayList<Rock> rocks;
|
||||
public ArrayList<Wall> walls;
|
||||
|
||||
// Game-state flags
|
||||
private boolean isRunning = false;
|
||||
private boolean gameOver = false;
|
||||
|
||||
// Physics
|
||||
private int bearVelocityY = 0; // separate Y-velocities so players don't share state
|
||||
private int sealVelocityY = 0;
|
||||
private final int gravity = 1;
|
||||
private final int JUMP_FORCE = -12; // negative = upward in screen-space
|
||||
|
||||
// UI
|
||||
private JProgressBar bearYProgress;
|
||||
private JProgressBar sealYProgress;
|
||||
private JLabel timerLabel; // shows countdown on screen
|
||||
|
||||
// Timers
|
||||
private Timer gameTimer;
|
||||
private Timer playTimer;
|
||||
private AtomicInteger secondsRemaining; // tracked so we can read it in paintComponent
|
||||
|
||||
// Players
|
||||
public Player bear;
|
||||
public Player seal;
|
||||
private int animationCounter = 0;
|
||||
|
||||
public Image[] bearSprites;
|
||||
public Image[] sealSprites;
|
||||
|
||||
// ── Win zone (goal door) ───────────────────────────────────────────────
|
||||
// Both players must reach their respective door to win.
|
||||
// bearDoor is safe for bear; sealDoor is safe for seal.
|
||||
private Rectangle bearDoor;
|
||||
private Rectangle sealDoor;
|
||||
private boolean bearWon = false;
|
||||
private boolean sealWon = false;
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Inner classes
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/** A controllable character. player==false → bear; player==true → seal. */
|
||||
public class Player {
|
||||
public int x, y;
|
||||
public int speed = 5;
|
||||
public Rectangle hitbox = new Rectangle(5, 10, 20, 30);
|
||||
public boolean player = false; // false = bear, true = seal
|
||||
public boolean onGround = false;
|
||||
|
||||
// Animation
|
||||
public Image[] walkFrames;
|
||||
public int currentFrame = 0;
|
||||
|
||||
// Input flags
|
||||
public boolean left, right, up;
|
||||
|
||||
public Player(Image[] frames, boolean isSeal) {
|
||||
this.walkFrames = frames;
|
||||
this.player = isSeal;
|
||||
}
|
||||
|
||||
public Image getCurrentImage() {
|
||||
if (walkFrames == null || walkFrames.length == 0) return null;
|
||||
return walkFrames[currentFrame];
|
||||
}
|
||||
}
|
||||
|
||||
public class Fluid {
|
||||
public int x, y;
|
||||
public int width = 80;
|
||||
public int height = 30;
|
||||
public Image img;
|
||||
public boolean player = false; // false → dangerous to bear; true → dangerous to seal
|
||||
|
||||
public Fluid(Image img) { this.img = img; }
|
||||
}
|
||||
|
||||
public class Gust {
|
||||
public int x, y;
|
||||
public int width = gustWidth;
|
||||
public int height = gustHeight;
|
||||
public Image img;
|
||||
|
||||
public Gust(Image img) { this.img = img; }
|
||||
}
|
||||
|
||||
/** Pushable rock obstacle. */
|
||||
public class Rock {
|
||||
public Image img;
|
||||
public boolean collision;
|
||||
public Rectangle hitbox;
|
||||
public int velocityY = 0; // rocks obey gravity too
|
||||
|
||||
public Rock() {
|
||||
this.img = null;
|
||||
this.collision = false;
|
||||
this.hitbox = new Rectangle();
|
||||
}
|
||||
|
||||
public Rock(Image img, int x, int y, int width, int height) {
|
||||
this.img = img;
|
||||
this.collision = false;
|
||||
this.hitbox = new Rectangle(x, y, width, height);
|
||||
}
|
||||
|
||||
public int getX() { return hitbox.x; }
|
||||
public int getY() { return hitbox.y; }
|
||||
public int getWidth() { return hitbox.width; }
|
||||
public int getHeight() { return hitbox.height; }
|
||||
|
||||
public void setPosition(int x, int y) { hitbox.setLocation(x, y); }
|
||||
|
||||
public void draw(Graphics g) {
|
||||
if (img != null) {
|
||||
g.drawImage(img, hitbox.x, hitbox.y, hitbox.width, hitbox.height, null);
|
||||
} else {
|
||||
g.setColor(new Color(139, 90, 43));
|
||||
g.fillRect(hitbox.x, hitbox.y, hitbox.width, hitbox.height);
|
||||
g.setColor(new Color(100, 60, 20));
|
||||
g.drawRect(hitbox.x, hitbox.y, hitbox.width, hitbox.height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Rock{hitbox=" + hitbox + ", collision=" + collision + "}";
|
||||
}
|
||||
}
|
||||
|
||||
/** Static impassable wall / platform. */
|
||||
public class Wall {
|
||||
public Image img;
|
||||
public boolean collision;
|
||||
public Rectangle hitbox;
|
||||
|
||||
public Wall() {
|
||||
this.img = null;
|
||||
this.collision = false;
|
||||
this.hitbox = new Rectangle();
|
||||
}
|
||||
|
||||
public Wall(Image img, int x, int y, int width, int height) {
|
||||
this.img = img;
|
||||
this.collision = false;
|
||||
this.hitbox = new Rectangle(x, y, width, height);
|
||||
}
|
||||
|
||||
public int getX() { return hitbox.x; }
|
||||
public int getY() { return hitbox.y; }
|
||||
public int getWidth() { return hitbox.width; }
|
||||
public int getHeight() { return hitbox.height; }
|
||||
|
||||
public void setPosition(int x, int y) { hitbox.setLocation(x, y); }
|
||||
|
||||
public void draw(Graphics g) {
|
||||
if (img != null) {
|
||||
g.drawImage(img, hitbox.x, hitbox.y, hitbox.width, hitbox.height, null);
|
||||
} else {
|
||||
g.setColor(Color.GRAY);
|
||||
g.fillRect(hitbox.x, hitbox.y, hitbox.width, hitbox.height);
|
||||
g.setColor(Color.DARK_GRAY);
|
||||
g.drawRect(hitbox.x, hitbox.y, hitbox.width, hitbox.height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Wall{hitbox=" + hitbox + ", collision=" + collision + "}";
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Constructor
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
Image bearIdle = new ImageIcon("bearIdle.png").getImage();
|
||||
Image bearWalk1 = new ImageIcon("bearWalk1.png").getImage();
|
||||
Image bearWalk2 = new ImageIcon("bearWalk2.png").getImage();
|
||||
Image bearWalk3 = new ImageIcon("bearWalk3.png").getImage();
|
||||
|
||||
Image sealIdle = new ImageIcon("sealIdle.png").getImage();
|
||||
Image sealWalk1 = new ImageIcon("sealWalk1.png").getImage();
|
||||
Image sealWalk2 = new ImageIcon("sealWalk2.png").getImage();
|
||||
|
||||
public GameFields() {
|
||||
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
|
||||
setFocusable(true);
|
||||
setBackground(new Color(30, 30, 60));
|
||||
this.addKeyListener(this);
|
||||
setFocusTraversalKeysEnabled(false);
|
||||
|
||||
// Progress bars (show height progress)
|
||||
bearYProgress = new JProgressBar(0, MAX_Y);
|
||||
sealYProgress = new JProgressBar(0, MAX_Y);
|
||||
bearYProgress.setForeground(new Color(139, 90, 43)); // brown for bear
|
||||
sealYProgress.setForeground(new Color(70, 130, 180)); // steel blue for seal
|
||||
bearYProgress.setPreferredSize(new Dimension(100, 14));
|
||||
sealYProgress.setPreferredSize(new Dimension(100, 14));
|
||||
|
||||
timerLabel = new JLabel("Time: ");
|
||||
timerLabel.setForeground(Color.WHITE);
|
||||
timerLabel.setFont(new Font("Arial", Font.BOLD, 16));
|
||||
|
||||
JPanel hud = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 2));
|
||||
hud.setOpaque(false);
|
||||
hud.add(new JLabel("Bear:") {{ setForeground(Color.WHITE); }});
|
||||
hud.add(bearYProgress);
|
||||
hud.add(timerLabel);
|
||||
hud.add(new JLabel("Seal:") {{ setForeground(Color.WHITE); }});
|
||||
hud.add(sealYProgress);
|
||||
setLayout(new BorderLayout());
|
||||
add(hud, BorderLayout.NORTH);
|
||||
|
||||
// Pre-allocate element pools
|
||||
fluids = new ArrayList<>();
|
||||
windBoxes = new ArrayList<>();
|
||||
rocks = new ArrayList<>();
|
||||
walls = new ArrayList<>();
|
||||
|
||||
// 6 fluid slots (indices 0-2 = bear-fluid; 3-5 = seal-fluid)
|
||||
for (int i = 0; i < 6; i++) {
|
||||
Fluid f = new Fluid(null);
|
||||
f.player = (i >= 3); // 0-2 dangerous to bear (false), 3-5 dangerous to seal (true)
|
||||
f.x = -9999; f.y = -9999; // park off-screen until placed
|
||||
fluids.add(f);
|
||||
}
|
||||
// 5 wall slots
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Wall w = new Wall(null, -9999, -9999, 120, 20);
|
||||
walls.add(w);
|
||||
}
|
||||
// 3 rock slots
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Rock r = new Rock(null, -9999, -9999, 30, 30);
|
||||
rocks.add(r);
|
||||
}
|
||||
// 2 gust slots
|
||||
for (int i = 0; i < 2; i++) {
|
||||
Gust g = new Gust(null);
|
||||
g.x = -9999; g.y = -9999;
|
||||
windBoxes.add(g);
|
||||
}
|
||||
|
||||
// Players
|
||||
bearSprites = new Image[4];
|
||||
bearSprites[0] = bearIdle;
|
||||
bearSprites[1] = bearWalk1;
|
||||
bearSprites[2] = bearWalk2;
|
||||
bearSprites[3] = bearWalk3;
|
||||
sealSprites = new Image[3];
|
||||
sealSprites[0] = sealIdle;
|
||||
sealSprites[1] = sealWalk1;
|
||||
sealSprites[2] = sealWalk2;
|
||||
|
||||
bear = new Player(bearSprites, false);
|
||||
seal = new Player(sealSprites, true);
|
||||
|
||||
//setupKeyBindings();
|
||||
|
||||
// 60 fps game loop
|
||||
gameTimer = new Timer(1000 / 60, this);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Navigation helpers
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
public void setContainer(JPanel container, CardLayout layout) {
|
||||
this.container = container;
|
||||
this.cardLayout = layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a level button in LvlManager / MainMenu.
|
||||
* Resets state, assigns player names, and switches to the chosen card.
|
||||
*/
|
||||
public void levelStart(String name1, String name2, String level) {
|
||||
playerName1 = name1;
|
||||
playerName2 = name2;
|
||||
gameOver = false;
|
||||
bearWon = false;
|
||||
sealWon = false;
|
||||
bearVelocityY = 0;
|
||||
sealVelocityY = 0;
|
||||
isRunning = true;
|
||||
|
||||
gameTimer.start();
|
||||
cardLayout.show(container, level);
|
||||
|
||||
// Ensure the panel can receive focus and request it
|
||||
this.setFocusable(true);
|
||||
this.requestFocus();
|
||||
this.requestFocusInWindow();
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Input
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/*private void setupKeyBindings() {
|
||||
InputMap im = this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
ActionMap am = this.getActionMap();
|
||||
|
||||
// BEAR — Arrow keys
|
||||
bindKey(im, am, KeyEvent.VK_LEFT, "bear_left", bear);
|
||||
bindKey(im, am, KeyEvent.VK_RIGHT, "bear_right", bear);
|
||||
bindKey(im, am, KeyEvent.VK_UP, "bear_up", bear);
|
||||
|
||||
// SEAL — WASD
|
||||
bindKey(im, am, KeyEvent.VK_A, "seal_left", seal);
|
||||
bindKey(im, am, KeyEvent.VK_D, "seal_right", seal);
|
||||
bindKey(im, am, KeyEvent.VK_W, "seal_up", seal);
|
||||
}
|
||||
|
||||
private void bindKey(InputMap im, ActionMap am, int keyCode, String name, Player p) {
|
||||
// 1. Create KeyStrokes for both Press and Release
|
||||
KeyStroke press = KeyStroke.getKeyStroke(keyCode, 0, false);
|
||||
KeyStroke release = KeyStroke.getKeyStroke(keyCode, 0, true);
|
||||
|
||||
// 2. Put them in the InputMap with UNIQUE action keys
|
||||
im.put(press, name + "_P");
|
||||
im.put(release, name + "_R");
|
||||
|
||||
// 3. Map those action keys to your ToggleMovementAction in the ActionMap
|
||||
// name.split("_")[1] extracts "left", "right", or "up" from "bear_left"
|
||||
String direction = name.split("_")[1];
|
||||
am.put(name + "_P", new ToggleMovementAction(p, direction, true));
|
||||
am.put(name + "_R", new ToggleMovementAction(p, direction, false));
|
||||
|
||||
/*im.put(KeyStroke.getKeyStroke(keyCode, 0, false), name + "_press");
|
||||
am.put(name + "_press", new ToggleMovementAction(p, name.split("_")[1], true));
|
||||
im.put(KeyStroke.getKeyStroke(keyCode, 0, true), name + "_release");
|
||||
am.put(name + "_release", new ToggleMovementAction(p, name.split("_")[1], false));
|
||||
}
|
||||
|
||||
public class ToggleMovementAction extends AbstractAction {
|
||||
private final Player player;
|
||||
private final String direction;
|
||||
private final boolean pressed;
|
||||
|
||||
public ToggleMovementAction(Player player, String direction, boolean pressed) {
|
||||
this.player = player;
|
||||
this.direction = direction;
|
||||
this.pressed = pressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
switch (direction) {
|
||||
case "left" -> player.left = pressed;
|
||||
case "right" -> player.right = pressed;
|
||||
case "up" -> player.up = pressed;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Game loop
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (isRunning) {
|
||||
move();
|
||||
repaint();
|
||||
if (gameOver) {
|
||||
gameTimer.stop();
|
||||
if (playTimer != null) playTimer.stop();
|
||||
}
|
||||
// Check win condition
|
||||
if (bearWon && sealWon) {
|
||||
gameOver = true;
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Physics & movement
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
public void move() {
|
||||
if (bear == null || seal == null) return;
|
||||
|
||||
updatePlayerPhysics(bear);
|
||||
updatePlayerPhysics(seal);
|
||||
|
||||
// Sync progress bars (invert: higher up = lower y value = more progress)
|
||||
bearYProgress.setValue(MAX_Y - Math.max(0, bear.y));
|
||||
sealYProgress.setValue(MAX_Y - Math.max(0, seal.y));
|
||||
|
||||
// Fluid collision — wrong fluid type kills the player
|
||||
for (Fluid f : fluids) {
|
||||
if (checkCollision(bear, f)) { gameOver = true; return; }
|
||||
if (checkCollision(seal, f)) { gameOver = true; return; }
|
||||
}
|
||||
|
||||
// Gust — push player upward
|
||||
for (Gust g : windBoxes) {
|
||||
if (checkCollision(bear, g)) bearVelocityY = Math.min(bearVelocityY, -8);
|
||||
if (checkCollision(seal, g)) sealVelocityY = Math.min(sealVelocityY, -8);
|
||||
}
|
||||
|
||||
// Win-door checks
|
||||
if (bearDoor != null && bear.hitbox.intersects(bearDoor)) bearWon = true;
|
||||
if (sealDoor != null && seal.hitbox.intersects(sealDoor)) sealWon = true;
|
||||
}
|
||||
|
||||
private void updatePlayerPhysics(Player p) {
|
||||
int oldX = p.x;
|
||||
int oldY = p.y;
|
||||
|
||||
// ── Horizontal movement ──
|
||||
boolean moved = false;
|
||||
if (p.left) { p.x -= p.speed; moved = true; System.out.println("left key pressed");}
|
||||
if (p.right) { p.x += p.speed; moved = true; }
|
||||
|
||||
p.hitbox.setLocation(p.x + 5, p.y + 10); // offset to keep hitbox centred in sprite
|
||||
|
||||
if (checkSolidCollision(p)) {
|
||||
p.x = oldX;
|
||||
p.hitbox.setLocation(p.x + 5, p.y + 10);
|
||||
}
|
||||
|
||||
// ── Vertical movement (gravity + jump) ──
|
||||
int vel = (p == bear) ? bearVelocityY : sealVelocityY;
|
||||
|
||||
vel += gravity;
|
||||
|
||||
// Jump: only allowed when standing on something
|
||||
if (p.up && p.onGround) {
|
||||
vel = JUMP_FORCE;
|
||||
p.onGround = false;
|
||||
}
|
||||
|
||||
p.y += vel;
|
||||
p.hitbox.setLocation(p.x + 5, p.y + 10);
|
||||
|
||||
p.onGround = false;
|
||||
if (checkSolidCollision(p)) {
|
||||
if (vel > 0) {
|
||||
// Landing on top of a surface
|
||||
p.y = oldY;
|
||||
vel = 0;
|
||||
p.onGround = true;
|
||||
} else {
|
||||
// Bumped head on ceiling
|
||||
p.y = oldY;
|
||||
vel = 0;
|
||||
}
|
||||
p.hitbox.setLocation(p.x + 5, p.y + 10);
|
||||
}
|
||||
|
||||
// Store velocity back
|
||||
if (p == bear) bearVelocityY = vel;
|
||||
else sealVelocityY = vel;
|
||||
|
||||
// Screen boundary clamping
|
||||
p.x = Math.max(0, Math.min(p.x, PANEL_WIDTH - 30));
|
||||
p.y = Math.max(0, Math.min(p.y, PANEL_HEIGHT - 40));
|
||||
if (p.y >= PANEL_HEIGHT - 40) {
|
||||
p.onGround = true;
|
||||
if (p == bear) bearVelocityY = 0;
|
||||
else sealVelocityY = 0;
|
||||
}
|
||||
p.hitbox.setLocation(p.x + 5, p.y + 10);
|
||||
|
||||
// ── Animation ──
|
||||
if (moved && p.walkFrames != null && p.walkFrames.length > 1) {
|
||||
animationCounter++;
|
||||
if (animationCounter % 10 == 0)
|
||||
p.currentFrame = (p.currentFrame + 1) % p.walkFrames.length;
|
||||
} else {
|
||||
p.currentFrame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Solid-collision helpers ────────────────────────────────────────────
|
||||
|
||||
public boolean checkSolidCollision(Player p) {
|
||||
for (Wall w : walls) {
|
||||
if (w.hitbox.x < -1000) continue; // parked off-screen
|
||||
if (p.hitbox.intersects(w.hitbox)) return true;
|
||||
}
|
||||
for (Rock r : rocks) {
|
||||
if (r.hitbox.x < -1000) continue;
|
||||
if (p.hitbox.intersects(r.hitbox)) {
|
||||
// Try to push the rock horizontally
|
||||
int moveX = 0;
|
||||
if (p.left) moveX = -p.speed;
|
||||
if (p.right) moveX = p.speed;
|
||||
if (moveX != 0 && canRockMove(r, moveX, 0)) {
|
||||
r.hitbox.x += moveX;
|
||||
return false; // rock moved, player not blocked
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean canRockMove(Rock r, int dx, int dy) {
|
||||
Rectangle future = new Rectangle(r.hitbox.x + dx, r.hitbox.y + dy,
|
||||
r.hitbox.width, r.hitbox.height);
|
||||
for (Wall w : walls) {
|
||||
if (w.hitbox.x < -1000) continue;
|
||||
if (future.intersects(w.hitbox)) return false;
|
||||
}
|
||||
for (Rock other : rocks) {
|
||||
if (other != r && other.hitbox.x > -1000 && future.intersects(other.hitbox))
|
||||
return false;
|
||||
}
|
||||
return future.x >= 0 && future.x + future.width <= PANEL_WIDTH;
|
||||
}
|
||||
|
||||
// ── Collision checkers ─────────────────────────────────────────────────
|
||||
|
||||
/** Fluid is dangerous only when the player type matches the fluid type. */
|
||||
public boolean checkCollision(Player a, Fluid b) {
|
||||
if (a.hitbox == null || b.x < -1000) return false;
|
||||
// player==false (bear) is harmed by fluid.player==false; same for seal
|
||||
if (a.player != b.player) return false;
|
||||
Rectangle fluidRect = new Rectangle(b.x, b.y, b.width, b.height);
|
||||
return a.hitbox.intersects(fluidRect);
|
||||
}
|
||||
|
||||
public boolean checkCollision(Player a, Gust b) {
|
||||
if (b.x < -1000) return false;
|
||||
Rectangle gustRect = new Rectangle(b.x, b.y, b.width, b.height);
|
||||
return a.hitbox.intersects(gustRect);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Placement helpers (used by LvlManager)
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
public void placePlayer(Player play, int x, int y) {
|
||||
play.x = x; play.y = y;
|
||||
play.hitbox.setLocation(x + 5, y + 10);
|
||||
}
|
||||
|
||||
public void placeFluid(Fluid flu, int x, int y) {
|
||||
flu.x = x; flu.y = y;
|
||||
}
|
||||
|
||||
public void placeGust(Gust gus, int x, int y) {
|
||||
gus.x = x; gus.y = y;
|
||||
}
|
||||
|
||||
public void placeRock(Rock rock, int x, int y) { rock.setPosition(x, y); }
|
||||
|
||||
public void placeWall(Wall wall, int x, int y) { wall.setPosition(x, y); }
|
||||
|
||||
/** Optionally place win doors (call from LvlManager per level). */
|
||||
public void placeDoors(int bx, int by, int sx, int sy) {
|
||||
bearDoor = new Rectangle(bx, by, 30, 50);
|
||||
sealDoor = new Rectangle(sx, sy, 30, 50);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Difficulty / timer
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
public void setDifficulty(String in) {
|
||||
if (in.equals("Easy")) CountDown(new AtomicInteger(150));
|
||||
else if (in.equals("Medium")) CountDown(new AtomicInteger(120));
|
||||
else CountDown(new AtomicInteger(100));
|
||||
}
|
||||
|
||||
/** Starts (or restarts) the countdown and updates timerLabel each second. */
|
||||
public void CountDown(AtomicInteger secs) {
|
||||
secondsRemaining = secs;
|
||||
if (playTimer != null) playTimer.stop();
|
||||
playTimer = new Timer(1000, e -> {
|
||||
if (secondsRemaining.get() > 0) {
|
||||
timerLabel.setText("Time: " + secondsRemaining.getAndDecrement());
|
||||
System.out.println(secondsRemaining);
|
||||
} else {
|
||||
gameOver = true;
|
||||
((Timer) e.getSource()).stop();
|
||||
timerLabel.setText("Time: 0");
|
||||
}
|
||||
});
|
||||
playTimer.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
int code = e.getKeyCode();
|
||||
|
||||
// BEAR Controls
|
||||
if (code == KeyEvent.VK_LEFT) {
|
||||
bear.left = true;
|
||||
System.out.println("left pressed");
|
||||
}
|
||||
if (code == KeyEvent.VK_RIGHT) bear.right = true;
|
||||
if (code == KeyEvent.VK_UP) bear.up = true;
|
||||
|
||||
// SEAL Controls
|
||||
if (code == KeyEvent.VK_A) seal.left = true;
|
||||
if (code == KeyEvent.VK_D) seal.right = true;
|
||||
if (code == KeyEvent.VK_W) seal.up = true;
|
||||
|
||||
// Global Commands
|
||||
if (code == KeyEvent.VK_ESCAPE) cardLayout.show(container, "MENU");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
int code = e.getKeyCode();
|
||||
|
||||
// BEAR Controls
|
||||
if (code == KeyEvent.VK_LEFT) bear.left = false;
|
||||
if (code == KeyEvent.VK_RIGHT) bear.right = false;
|
||||
if (code == KeyEvent.VK_UP) bear.up = false;
|
||||
|
||||
// SEAL Controls
|
||||
if (code == KeyEvent.VK_A) seal.left = false;
|
||||
if (code == KeyEvent.VK_D) seal.right = false;
|
||||
if (code == KeyEvent.VK_W) seal.up = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e) {
|
||||
// Not needed for movement, but required by interface
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// Rendering
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
draw(g);
|
||||
|
||||
// HUD overlay
|
||||
if (isRunning) {
|
||||
g.setFont(new Font("Arial", Font.BOLD, 14));
|
||||
// Player-name tags above their characters
|
||||
g.setColor(new Color(210, 140, 60));
|
||||
if (bear != null)
|
||||
g.drawString(playerName1 != null ? playerName1 : "Bear", bear.x - 5, bear.y - 4);
|
||||
g.setColor(new Color(70, 160, 220));
|
||||
if (seal != null)
|
||||
g.drawString(playerName2 != null ? playerName2 : "Seal", seal.x - 5, seal.y - 4);
|
||||
}
|
||||
|
||||
if (gameOver) {
|
||||
// Semi-transparent overlay
|
||||
g.setColor(new Color(0, 0, 0, 160));
|
||||
g.fillRect(0, 0, PANEL_WIDTH, PANEL_HEIGHT);
|
||||
g.setFont(new Font("Arial", Font.BOLD, 36));
|
||||
if (bearWon && sealWon) {
|
||||
g.setColor(new Color(255, 215, 0));
|
||||
drawCentered(g, "YOU WIN!", PANEL_HEIGHT / 2 - 20);
|
||||
} else {
|
||||
g.setColor(new Color(255, 80, 80));
|
||||
drawCentered(g, "GAME OVER", PANEL_HEIGHT / 2 - 20);
|
||||
}
|
||||
g.setFont(new Font("Arial", Font.PLAIN, 18));
|
||||
g.setColor(Color.WHITE);
|
||||
drawCentered(g, "Press R to restart or ESC for menu", PANEL_HEIGHT / 2 + 20);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCentered(Graphics g, String text, int y) {
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
g.drawString(text, (PANEL_WIDTH - fm.stringWidth(text)) / 2, y);
|
||||
}
|
||||
|
||||
protected void draw(Graphics g) {
|
||||
// Background
|
||||
g.setColor(new Color(30, 30, 60));
|
||||
g.fillRect(0, 0, PANEL_WIDTH, PANEL_HEIGHT);
|
||||
|
||||
// Win doors
|
||||
if (bearDoor != null) {
|
||||
g.setColor(new Color(210, 140, 60, 180));
|
||||
g.fillRect(bearDoor.x, bearDoor.y, bearDoor.width, bearDoor.height);
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawRect(bearDoor.x, bearDoor.y, bearDoor.width, bearDoor.height);
|
||||
g.setFont(new Font("Arial", Font.BOLD, 9));
|
||||
g.drawString("BEAR", bearDoor.x, bearDoor.y - 3);
|
||||
}
|
||||
if (sealDoor != null) {
|
||||
g.setColor(new Color(70, 160, 220, 180));
|
||||
g.fillRect(sealDoor.x, sealDoor.y, sealDoor.width, sealDoor.height);
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawRect(sealDoor.x, sealDoor.y, sealDoor.width, sealDoor.height);
|
||||
g.setFont(new Font("Arial", Font.BOLD, 9));
|
||||
g.drawString("SEAL", sealDoor.x, sealDoor.y - 3);
|
||||
}
|
||||
|
||||
// Walls and rocks
|
||||
for (Wall w : walls) if (w.hitbox.x > -1000) w.draw(g);
|
||||
for (Rock r : rocks) if (r.hitbox.x > -1000) r.draw(g);
|
||||
|
||||
// Gusts (semi-transparent column)
|
||||
g.setColor(new Color(200, 255, 200, 60));
|
||||
for (Gust gust : windBoxes) {
|
||||
if (gust.x > -1000) {
|
||||
g.fillRect(gust.x, gust.y, gust.width, gust.height);
|
||||
g.setColor(new Color(150, 255, 150, 120));
|
||||
g.drawRect(gust.x, gust.y, gust.width, gust.height);
|
||||
g.setColor(new Color(200, 255, 200, 60));
|
||||
}
|
||||
}
|
||||
|
||||
// Fluids
|
||||
for (Fluid f : fluids) {
|
||||
if (f.x > -1000) {
|
||||
// bear-fluid = brown/red; seal-fluid = blue
|
||||
if (!f.player) g.setColor(new Color(180, 80, 0, 200));
|
||||
else g.setColor(new Color(0, 120, 220, 200));
|
||||
g.fillRect(f.x, f.y, f.width, f.height);
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawRect(f.x, f.y, f.width, f.height);
|
||||
}
|
||||
}
|
||||
|
||||
// Players
|
||||
if (bear != null) {
|
||||
if (bear.getCurrentImage() != null) {
|
||||
g.drawImage(bear.getCurrentImage(), bear.x, bear.y, 30, 40, null);
|
||||
} else {
|
||||
// Fallback: coloured rectangle
|
||||
g.setColor(new Color(210, 140, 60));
|
||||
g.fillRoundRect(bear.x, bear.y, 30, 40, 8, 8);
|
||||
g.setColor(Color.WHITE);
|
||||
g.setFont(new Font("Arial", Font.BOLD, 9));
|
||||
g.drawString("BEAR", bear.x, bear.y + 22);
|
||||
}
|
||||
}
|
||||
if (seal != null) {
|
||||
if (seal.getCurrentImage() != null) {
|
||||
g.drawImage(seal.getCurrentImage(), seal.x, seal.y, 30, 40, null);
|
||||
} else {
|
||||
g.setColor(new Color(70, 160, 220));
|
||||
g.fillRoundRect(seal.x, seal.y, 30, 40, 8, 8);
|
||||
g.setColor(Color.WHITE);
|
||||
g.setFont(new Font("Arial", Font.BOLD, 9));
|
||||
g.drawString("SEAL", seal.x, seal.y + 22);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
191
LvlManager.java
Normal file
191
LvlManager.java
Normal file
@@ -0,0 +1,191 @@
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* LvlManager
|
||||
*
|
||||
* Builds each level as its own GameFields panel and registers them
|
||||
* with the root CardLayout container. The level-select screen lives
|
||||
* here; clicking a level button in MainMenu calls
|
||||
* gm.levelStart(name1, name2, "LEVEL1") which tells CardLayout to
|
||||
* flip to that card.
|
||||
*/
|
||||
public class LvlManager extends JPanel {
|
||||
|
||||
public JPanel container;
|
||||
private CardLayout cardLayout;
|
||||
|
||||
public LvlManager(GameFields gm) {
|
||||
|
||||
setLayout(null);
|
||||
setPreferredSize(new Dimension(640, 360));
|
||||
setBackground(new Color(20, 20, 40));
|
||||
|
||||
// ── "Go Back" button ─────────────────────────────────────────────
|
||||
JButton back = new JButton("Go Back");
|
||||
back.setFont(new Font("Arial", Font.BOLD, 12));
|
||||
back.setForeground(Color.WHITE);
|
||||
back.setBackground(new Color(60, 60, 100));
|
||||
back.setBorder(BorderFactory.createLineBorder(new Color(120, 120, 200), 1));
|
||||
back.setFocusPainted(false);
|
||||
back.setBounds(275, 318, 90, 28);
|
||||
add(back);
|
||||
back.addActionListener(e -> cardLayout.show(container, "MENU"));
|
||||
|
||||
// ── Build individual level panels ─────────────────────────────────
|
||||
// Each level is a fresh GameFields; we configure its elements here,
|
||||
// add it to the shared container under its card name, and wire up
|
||||
// the setContainer call so the in-game "back" path works too.
|
||||
|
||||
buildLevel1(gm);
|
||||
buildLevel2(gm);
|
||||
buildLevel3(gm);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// LEVEL 1 — "The Crossing" (Easy)
|
||||
// =========================================================================
|
||||
private void buildLevel1(GameFields gm) {
|
||||
GameFields level1 = new GameFields();
|
||||
|
||||
// Players
|
||||
level1.placePlayer(level1.bear, 50, 270);
|
||||
level1.placePlayer(level1.seal, 560, 270);
|
||||
|
||||
// Walls (x, y — default 120×20 size assigned in GameFields constructor)
|
||||
level1.placeWall(level1.walls.get(0), 0, 318); // left ground
|
||||
level1.placeWall(level1.walls.get(1), 340, 318); // right ground (gap at 260-340)
|
||||
level1.placeWall(level1.walls.get(2), 270, 250); // center ledge
|
||||
level1.placeWall(level1.walls.get(3), 80, 220); // left raised platform
|
||||
level1.placeWall(level1.walls.get(4), 490, 220); // right raised platform
|
||||
|
||||
// Rocks
|
||||
level1.placeRock(level1.rocks.get(0), 258, 288); // bridges the ground gap
|
||||
|
||||
// Fluids (index 0-2 = bear-fluid / dangerous to bear;
|
||||
// index 3-5 = seal-fluid / dangerous to seal)
|
||||
level1.placeFluid(level1.fluids.get(0), 430, 298); // bear-fluid on right path
|
||||
level1.placeFluid(level1.fluids.get(3), 140, 298); // seal-fluid on left path
|
||||
|
||||
// Gust
|
||||
level1.placeGust(level1.windBoxes.get(0), 60, 130);
|
||||
|
||||
// Win doors (bear door on right side, seal door on left side — they must cross)
|
||||
level1.placeDoors(580, 240, 20, 240);
|
||||
|
||||
// Wire up navigation so level1 can also switch cards
|
||||
level1.addKeyListener(new java.awt.event.KeyAdapter() {
|
||||
@Override public void keyPressed(java.awt.event.KeyEvent e) {
|
||||
if (e.getKeyCode() == java.awt.event.KeyEvent.VK_ESCAPE)
|
||||
level1.cardLayout.show(level1.container, "MENU");
|
||||
if (e.getKeyCode() == java.awt.event.KeyEvent.VK_R)
|
||||
buildLevel1Restart(level1);
|
||||
}
|
||||
});
|
||||
|
||||
// Will be registered with the container in setContainer()
|
||||
// Store reference so setContainer can wire it up
|
||||
storeLevel(level1, "LEVEL1");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// LEVEL 2 — "Double Danger" (Medium)
|
||||
// =========================================================================
|
||||
private void buildLevel2(GameFields gm) {
|
||||
GameFields level2 = new GameFields();
|
||||
|
||||
level2.placePlayer(level2.bear, 100, 290);
|
||||
level2.placePlayer(level2.seal, 520, 290);
|
||||
|
||||
level2.placeWall(level2.walls.get(0), 0, 330); // bottom floor
|
||||
level2.placeWall(level2.walls.get(1), 30, 240); // left mid platform
|
||||
level2.placeWall(level2.walls.get(2), 490, 240); // right mid platform
|
||||
level2.placeWall(level2.walls.get(3), 240, 150); // center upper platform
|
||||
level2.placeWall(level2.walls.get(4), 308, 260); // center divider
|
||||
|
||||
level2.placeRock(level2.rocks.get(0), 70, 300);
|
||||
level2.placeRock(level2.rocks.get(1), 500, 300);
|
||||
level2.placeRock(level2.rocks.get(2), 290, 122);
|
||||
|
||||
level2.placeFluid(level2.fluids.get(0), 400, 315); // bear-fluid lower right
|
||||
level2.placeFluid(level2.fluids.get(1), 460, 222); // bear-fluid mid-right ledge edge
|
||||
level2.placeFluid(level2.fluids.get(2), 255, 132); // bear-fluid on finish ledge
|
||||
level2.placeFluid(level2.fluids.get(3), 160, 315); // seal-fluid lower left
|
||||
level2.placeFluid(level2.fluids.get(4), 100, 222); // seal-fluid mid-left ledge edge
|
||||
|
||||
level2.placeGust(level2.windBoxes.get(0), 50, 60);
|
||||
level2.placeGust(level2.windBoxes.get(1), 560, 60);
|
||||
|
||||
level2.placeDoors(570, 130, 20, 130);
|
||||
|
||||
storeLevel(level2, "LEVEL2");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// LEVEL 3 — "The Gauntlet" (Hard)
|
||||
// =========================================================================
|
||||
private void buildLevel3(GameFields gm) {
|
||||
GameFields level3 = new GameFields();
|
||||
|
||||
level3.placePlayer(level3.bear, 20, 290);
|
||||
level3.placePlayer(level3.seal, 590, 290);
|
||||
|
||||
level3.placeWall(level3.walls.get(0), 0, 330); // left floor strip
|
||||
level3.placeWall(level3.walls.get(1), 530, 330); // right floor strip
|
||||
level3.placeWall(level3.walls.get(2), 150, 230); // wide center platform
|
||||
level3.placeWall(level3.walls.get(3), 30, 140); // upper-left ledge
|
||||
level3.placeWall(level3.walls.get(4), 540, 140); // upper-right ledge
|
||||
|
||||
level3.placeRock(level3.rocks.get(0), 300, 202);
|
||||
level3.placeRock(level3.rocks.get(1), 80, 300);
|
||||
level3.placeRock(level3.rocks.get(2), 520, 300);
|
||||
|
||||
// Bear-fluid (index 0-2)
|
||||
level3.placeFluid(level3.fluids.get(0), 120, 310);
|
||||
level3.placeFluid(level3.fluids.get(1), 340, 212);
|
||||
level3.placeFluid(level3.fluids.get(2), 560, 122);
|
||||
// Seal-fluid (index 3-5)
|
||||
level3.placeFluid(level3.fluids.get(3), 460, 310);
|
||||
level3.placeFluid(level3.fluids.get(4), 240, 212);
|
||||
level3.placeFluid(level3.fluids.get(5), 50, 122);
|
||||
|
||||
level3.placeGust(level3.windBoxes.get(0), 30, 50);
|
||||
level3.placeGust(level3.windBoxes.get(1), 570, 50);
|
||||
|
||||
level3.placeDoors(560, 100, 10, 100);
|
||||
|
||||
storeLevel(level3, "LEVEL3");
|
||||
}
|
||||
|
||||
// ── Small helpers ─────────────────────────────────────────────────────
|
||||
|
||||
/** Holds level panels until setContainer() is called and we can register them. */
|
||||
private final java.util.LinkedHashMap<String, GameFields> pendingLevels = new java.util.LinkedHashMap<>();
|
||||
|
||||
private void storeLevel(GameFields lv, String key) {
|
||||
pendingLevels.put(key, lv);
|
||||
}
|
||||
|
||||
/** Resets level1 positions in-place (called on 'R' key). */
|
||||
private void buildLevel1Restart(GameFields lv) {
|
||||
lv.placePlayer(lv.bear, 50, 270);
|
||||
lv.placePlayer(lv.seal, 560, 270);
|
||||
lv.placeRock(lv.rocks.get(0), 258, 288);
|
||||
}
|
||||
|
||||
// ── setContainer ──────────────────────────────────────────────────────
|
||||
|
||||
public void setContainer(JPanel container, CardLayout layout) {
|
||||
this.container = container;
|
||||
this.cardLayout = layout;
|
||||
|
||||
// Register every pending level built in the constructor
|
||||
for (String key : pendingLevels.keySet()) {
|
||||
GameFields lv = pendingLevels.get(key);
|
||||
lv.setContainer(container, layout);
|
||||
container.add(lv, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
321
MainMenu.java
Normal file
321
MainMenu.java
Normal file
@@ -0,0 +1,321 @@
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.geom.*;
|
||||
import java.awt.image.*;
|
||||
|
||||
public class MainMenu extends JPanel
|
||||
{
|
||||
public JPanel container;
|
||||
private CardLayout cardLayout;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// LevelButton — a custom JButton that paints its own normal / rollover icon.
|
||||
//
|
||||
// Normal state : small rounded tile, muted colour, level number centred.
|
||||
// Hover state : tile scales up (~1.4×), bright glow ring appears around
|
||||
// the tile, the label changes from "Level N" to the full
|
||||
// level name, and the background darkens so the button pops.
|
||||
// The change is purely painted — no external image files needed.
|
||||
// -------------------------------------------------------------------------
|
||||
private static class LevelButton extends JButton
|
||||
{
|
||||
private static final int BASE_SIZE = 100; // icon canvas (px)
|
||||
private static final int INNER = 80; // tile size at rest
|
||||
private static final int INNER_HOV = 110; // tile size on hover (drawn into same canvas)
|
||||
|
||||
private final int level;
|
||||
private final String levelName;
|
||||
private final Color tileColour;
|
||||
private final Color hoverColour;
|
||||
private boolean hovered = false;
|
||||
|
||||
LevelButton(int level, String levelName, Color tile, Color hover)
|
||||
{
|
||||
this.level = level;
|
||||
this.levelName = levelName;
|
||||
this.tileColour = tile;
|
||||
this.hoverColour = hover;
|
||||
|
||||
setContentAreaFilled(false);
|
||||
setBorderPainted(false);
|
||||
setFocusPainted(false);
|
||||
setOpaque(false);
|
||||
|
||||
// Build and set the two icons
|
||||
setIcon(buildIcon(false));
|
||||
setRolloverEnabled(true);
|
||||
setRolloverIcon(buildIcon(true));
|
||||
|
||||
// Size the button to comfortably hold the larger hover icon + text
|
||||
setPreferredSize(new Dimension(160, 160));
|
||||
setSize(new Dimension(160, 160));
|
||||
|
||||
// Track hover so we can repaint the button text colour
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override public void mouseEntered(MouseEvent e) { hovered = true; repaint(); }
|
||||
@Override public void mouseExited (MouseEvent e) { hovered = false; repaint(); }
|
||||
});
|
||||
|
||||
// Text sits below the icon, painted by paintComponent
|
||||
setVerticalTextPosition(SwingConstants.BOTTOM);
|
||||
setHorizontalTextPosition(SwingConstants.CENTER);
|
||||
setFont(new Font("Arial", Font.BOLD, 13));
|
||||
setForeground(Color.WHITE);
|
||||
}
|
||||
|
||||
// Paints the button — lets us switch label text and colour dynamically
|
||||
@Override
|
||||
protected void paintComponent(Graphics g)
|
||||
{
|
||||
super.paintComponent(g);
|
||||
Graphics2D g2 = (Graphics2D) g.create();
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// Dynamic label below the icon
|
||||
String label = hovered ? levelName : "Level " + level;
|
||||
Color fg = hovered ? hoverColour.brighter().brighter() : Color.LIGHT_GRAY;
|
||||
|
||||
FontMetrics fm = g2.getFontMetrics(getFont());
|
||||
int tx = (getWidth() - fm.stringWidth(label)) / 2;
|
||||
int ty = getHeight() - 8;
|
||||
|
||||
g2.setFont(getFont());
|
||||
// Drop shadow
|
||||
g2.setColor(Color.BLACK);
|
||||
g2.drawString(label, tx + 1, ty + 1);
|
||||
g2.setColor(fg);
|
||||
g2.drawString(label, tx, ty);
|
||||
|
||||
g2.dispose();
|
||||
}
|
||||
|
||||
// ── Icon factory ──────────────────────────────────────────────────────
|
||||
private ImageIcon buildIcon(boolean hover)
|
||||
{
|
||||
int canvasSize = BASE_SIZE + 40; // 140 px canvas so hover tile fits
|
||||
BufferedImage img = new BufferedImage(canvasSize, canvasSize,
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = img.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
int cx = canvasSize / 2;
|
||||
int cy = canvasSize / 2;
|
||||
|
||||
if (hover) {
|
||||
// ── HOVER: large glowing tile ─────────────────────────────
|
||||
|
||||
// Outer glow rings (three, fading outward)
|
||||
for (int i = 3; i >= 1; i--) {
|
||||
int ringR = INNER_HOV / 2 + i * 10;
|
||||
float alpha = 0.15f * i;
|
||||
g.setColor(new Color(
|
||||
hoverColour.getRed() / 255f,
|
||||
hoverColour.getGreen()/ 255f,
|
||||
hoverColour.getBlue() / 255f,
|
||||
alpha));
|
||||
g.fillOval(cx - ringR, cy - ringR, ringR * 2, ringR * 2);
|
||||
}
|
||||
|
||||
// Tile background (bright)
|
||||
int hs = INNER_HOV;
|
||||
g.setColor(hoverColour);
|
||||
g.fillRoundRect(cx - hs/2, cy - hs/2, hs, hs, 22, 22);
|
||||
|
||||
// Bright border stroke
|
||||
g.setColor(Color.WHITE);
|
||||
g.setStroke(new BasicStroke(3f));
|
||||
g.drawRoundRect(cx - hs/2, cy - hs/2, hs, hs, 22, 22);
|
||||
|
||||
// Big level number (white, centred in tile)
|
||||
g.setColor(Color.WHITE);
|
||||
g.setFont(new Font("Arial", Font.BOLD, 42));
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
String num = String.valueOf(level);
|
||||
g.drawString(num,
|
||||
cx - fm.stringWidth(num) / 2,
|
||||
cy + fm.getAscent() / 2 - 2);
|
||||
|
||||
// Stars drawn in upper-right corner of tile (difficulty indicator)
|
||||
drawStars(g, cx + hs/2 - 28, cy - hs/2 + 4, level);
|
||||
|
||||
} else {
|
||||
// ── NORMAL: small muted tile ──────────────────────────────
|
||||
|
||||
int ns = INNER;
|
||||
g.setColor(tileColour);
|
||||
g.fillRoundRect(cx - ns/2, cy - ns/2, ns, ns, 16, 16);
|
||||
|
||||
// Subtle border
|
||||
g.setColor(tileColour.brighter());
|
||||
g.setStroke(new BasicStroke(1.5f));
|
||||
g.drawRoundRect(cx - ns/2, cy - ns/2, ns, ns, 16, 16);
|
||||
|
||||
// Level number
|
||||
g.setColor(Color.WHITE);
|
||||
g.setFont(new Font("Arial", Font.BOLD, 30));
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
String num = String.valueOf(level);
|
||||
g.drawString(num,
|
||||
cx - fm.stringWidth(num) / 2,
|
||||
cy + fm.getAscent() / 2 - 2);
|
||||
|
||||
// Stars (smaller)
|
||||
drawStars(g, cx + ns/2 - 22, cy - ns/2 + 2, level);
|
||||
}
|
||||
|
||||
g.dispose();
|
||||
return new ImageIcon(img);
|
||||
}
|
||||
|
||||
/** Draw N filled gold stars in a row, starting at (x, y). */
|
||||
private void drawStars(Graphics2D g, int x, int y, int count)
|
||||
{
|
||||
g.setColor(new Color(255, 220, 50));
|
||||
int starSize = 10;
|
||||
for (int i = 0; i < count; i++) {
|
||||
drawStar(g, x - i * (starSize + 2), y, starSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawStar(Graphics2D g, int cx, int cy, int size)
|
||||
{
|
||||
int pts = 5;
|
||||
double outerR = size / 2.0;
|
||||
double innerR = outerR * 0.4;
|
||||
int[] xs = new int[pts * 2];
|
||||
int[] ys = new int[pts * 2];
|
||||
for (int i = 0; i < pts * 2; i++) {
|
||||
double angle = Math.PI / pts * i - Math.PI / 2;
|
||||
double r = (i % 2 == 0) ? outerR : innerR;
|
||||
xs[i] = (int) (cx + r * Math.cos(angle));
|
||||
ys[i] = (int) (cy + r * Math.sin(angle));
|
||||
}
|
||||
g.fillPolygon(xs, ys, pts * 2);
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// MainMenu constructor
|
||||
// =========================================================================
|
||||
public MainMenu(GameFields gm)
|
||||
{
|
||||
setLayout(null);
|
||||
setPreferredSize(new Dimension(640, 360));
|
||||
setBackground(new Color(20, 20, 40)); // dark navy background
|
||||
|
||||
// ── Title label ──────────────────────────────────────────────────────
|
||||
JLabel title = new JLabel("SELECT A LEVEL", SwingConstants.CENTER);
|
||||
title.setFont(new Font("Arial", Font.BOLD, 28));
|
||||
title.setForeground(Color.WHITE);
|
||||
title.setBounds(0, 20, 640, 40);
|
||||
add(title);
|
||||
|
||||
// ── Three level buttons ───────────────────────────────────────────────
|
||||
// Colours: Level 1 = teal/cyan, Level 2 = orange, Level 3 = red
|
||||
LevelButton lvl1 = new LevelButton(1, "The Crossing",
|
||||
new Color(40, 100, 110), // muted teal (normal)
|
||||
new Color(0, 200, 220)); // bright cyan (hover)
|
||||
|
||||
LevelButton lvl2 = new LevelButton(2, "Double Danger",
|
||||
new Color(120, 70, 20), // muted orange (normal)
|
||||
new Color(255, 150, 0)); // bright orange (hover)
|
||||
|
||||
LevelButton lvl3 = new LevelButton(3, "The Gauntlet",
|
||||
new Color(110, 20, 20), // muted red (normal)
|
||||
new Color(255, 60, 60)); // bright red (hover)
|
||||
|
||||
// Position buttons evenly across the panel (panel is 640 wide)
|
||||
// Each button is 160 px wide; 3 buttons = 480 px; padding = 80 px each side
|
||||
lvl1.setBounds(80, 100, 160, 160);
|
||||
lvl2.setBounds(240, 100, 160, 160);
|
||||
lvl3.setBounds(400, 100, 160, 160);
|
||||
|
||||
add(lvl1);
|
||||
add(lvl2);
|
||||
add(lvl3);
|
||||
|
||||
// Each button starts the corresponding level via levelStart()
|
||||
lvl1.addActionListener(e -> gm.levelStart(gm.playerName1, gm.playerName2, "LEVEL1"));
|
||||
lvl2.addActionListener(e -> gm.levelStart(gm.playerName1, gm.playerName2, "LEVEL2"));
|
||||
lvl3.addActionListener(e -> gm.levelStart(gm.playerName1, gm.playerName2, "LEVEL3"));
|
||||
|
||||
|
||||
// ── Difficulty radio buttons (unchanged from original) ────────────────
|
||||
JPanel difficultyPanel = new JPanel();
|
||||
difficultyPanel.setOpaque(false);
|
||||
difficultyPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 0));
|
||||
|
||||
JRadioButton easy = new JRadioButton("Easy", true);
|
||||
JRadioButton medium = new JRadioButton("Medium");
|
||||
JRadioButton hard = new JRadioButton("Hard");
|
||||
|
||||
for (JRadioButton rb : new JRadioButton[]{easy, medium, hard}) {
|
||||
rb.setForeground(Color.WHITE);
|
||||
rb.setOpaque(false);
|
||||
}
|
||||
|
||||
ButtonGroup difficultyGroup = new ButtonGroup();
|
||||
difficultyGroup.add(easy);
|
||||
difficultyGroup.add(medium);
|
||||
difficultyGroup.add(hard);
|
||||
|
||||
difficultyPanel.add(new JLabel("Difficulty:") {{
|
||||
setForeground(Color.LIGHT_GRAY);
|
||||
}});
|
||||
difficultyPanel.add(easy);
|
||||
difficultyPanel.add(medium);
|
||||
difficultyPanel.add(hard);
|
||||
difficultyPanel.setBounds(160, 278, 320, 30);
|
||||
add(difficultyPanel);
|
||||
|
||||
easy .addActionListener(e -> gm.setDifficulty("Easy"));
|
||||
medium.addActionListener(e -> gm.setDifficulty("Medium"));
|
||||
hard .addActionListener(e -> gm.setDifficulty("Hard"));
|
||||
|
||||
// ── Quit button ───────────────────────────────────────────────────────
|
||||
JButton quit = new JButton("QUIT");
|
||||
quit.setFont(new Font("Arial", Font.BOLD, 12));
|
||||
quit.setForeground(Color.WHITE);
|
||||
quit.setBackground(new Color(80, 20, 20));
|
||||
quit.setBorder(BorderFactory.createLineBorder(new Color(180, 60, 60), 2));
|
||||
quit.setFocusPainted(false);
|
||||
quit.setBounds(275, 75, 90, 28);
|
||||
add(quit);
|
||||
|
||||
quit.addActionListener(e -> {
|
||||
String[] lines = {"", "", "", // Top padding
|
||||
"GAME !!", "",
|
||||
"Developed by","Aimee Azzahra","Ayan Mishra", "Jennifer Phan",
|
||||
"Character Art by Jennifer Phan",
|
||||
"Background Art by Aimee Azzahra",
|
||||
"other credits ig idk",
|
||||
"Thanks for playing!",
|
||||
"", "", "", "","",""}; //bottom padding
|
||||
CreditsPanel credits = new CreditsPanel(lines, () -> System.exit(0));
|
||||
container.add(credits, "CREDITS");
|
||||
cardLayout.show(container, "CREDITS");
|
||||
credits.start();
|
||||
});
|
||||
}
|
||||
|
||||
// Paint the dark gradient background
|
||||
@Override
|
||||
protected void paintComponent(Graphics g)
|
||||
{
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
GradientPaint gp = new GradientPaint(0, 0, new Color(10, 10, 30),
|
||||
0, getHeight(), new Color(30, 10, 50));
|
||||
g2.setPaint(gp);
|
||||
g2.fillRect(0, 0, getWidth(), getHeight());
|
||||
super.paintComponent(g); // paint children
|
||||
}
|
||||
|
||||
public void setContainer(JPanel container, CardLayout layout)
|
||||
{
|
||||
this.container = container;
|
||||
this.cardLayout = layout;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user