Upload files to "/"
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user