The agent has updated the game's core logic, including player movement, enemy AI, collision detection, and level loading. New sprites and sound effects have been integrated, along with the addition of a Chess game. The replit.nix file has been configured to include jdk21 and neovim. Replit-Commit-Author: Agent Replit-Commit-Session-Id: f6819c21-e85d-45ac-acde-604db2cfa4fe Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 32bc3e83-133e-4485-96b6-ff3d548bcbfd Replit-Helium-Checkpoint-Created: true
591 lines
17 KiB
Java
591 lines
17 KiB
Java
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.awt.image.*;
|
|
import java.io.*;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Random;
|
|
import javax.sound.sampled.*;
|
|
import javax.swing.*;
|
|
|
|
public class Platformer extends JPanel implements KeyListener, ActionListener {
|
|
|
|
// constants
|
|
static final int GRAVITY = 1;
|
|
static final Color playerColor = Color.RED;
|
|
static final Color tileColor = Color.BLUE;
|
|
static final Color SKY = new Color(135, 206, 235);
|
|
static final Color NIGHT_SKY = new Color(4, 26, 64);
|
|
static final int FRICTION = 1;
|
|
static final int MAXYVELO = 15;
|
|
static final int MAXXVELO = 5;
|
|
static final int totalLevels = 10;
|
|
static int[] numAm = {4, 5, 10, 18, 6, 6, 5, 5, 6, 23};
|
|
|
|
// game objects
|
|
Player player;
|
|
ArrayList<Collidable> collidables;
|
|
ArrayList<Collectable> collectables;
|
|
Flag flag;
|
|
ArrayList<Enemy> enemies;
|
|
ArrayList<Projectile> projectiles;
|
|
|
|
// game vars
|
|
int boardWidth;
|
|
int boardHeight;
|
|
int tileSize;
|
|
int enemiesKilled;
|
|
Timer gameTimer;
|
|
HashMap<Integer, Boolean> pressedKeys;
|
|
boolean jumpPressed;
|
|
int cameraX, cameraY;
|
|
int currentLevel;
|
|
boolean allCollected;
|
|
boolean gameOver;
|
|
boolean gameStarted;
|
|
Image heart, emptyHeart, slash, amendmentImg, powerImg, pressRImg, endImg, winImg, titleImg;
|
|
ArrayList<Image> numbers;
|
|
Clip jumpSound, shootSound, hitSound, collectSound;
|
|
|
|
public Platformer(int boardWidth, int boardHeight, int tileSize) {
|
|
// setup game
|
|
this.boardWidth = boardWidth;
|
|
this.boardHeight = boardHeight;
|
|
this.tileSize = tileSize;
|
|
setPreferredSize(new Dimension(this.boardWidth, this.boardHeight));
|
|
addKeyListener(this);
|
|
this.setFocusable(true);
|
|
this.setLayout(null);
|
|
|
|
pressedKeys = new HashMap<>();
|
|
jumpPressed = false;
|
|
gameOver = false;
|
|
gameStarted = false;
|
|
setBackground(SKY);
|
|
|
|
// setup objects
|
|
heart = new ImageIcon("Sprites/Hearts/heart.png").getImage();
|
|
titleImg = new ImageIcon("Sprites/Title.png").getImage();
|
|
emptyHeart = new ImageIcon("Sprites/Hearts/emptyHeart.png").getImage();
|
|
endImg = new ImageIcon("Sprites/end.png").getImage();
|
|
pressRImg = new ImageIcon("Sprites/PressR.png").getImage();
|
|
winImg = new ImageIcon("Sprites/win.png").getImage();
|
|
jumpSound = loadClip("Sounds/jump.wav");
|
|
shootSound = loadClip("Sounds/shoot.wav");
|
|
hitSound = loadClip("Sounds/hit.wav");
|
|
collectSound = loadClip("Sounds/collect.wav");
|
|
gameTimer = new Timer(15, this);
|
|
player = new Player(-20, 0, tileSize, tileSize);
|
|
collidables = new ArrayList<>();
|
|
collectables = new ArrayList<>();
|
|
enemies = new ArrayList<>();
|
|
projectiles = new ArrayList<>();
|
|
flag = new Flag(-20, 0, tileSize, tileSize);
|
|
cameraX = 0;
|
|
cameraY = 0;
|
|
currentLevel = 0;
|
|
enemiesKilled = 0;
|
|
numbers = new ArrayList<>();
|
|
for (int i = 0; i < 10; i++)
|
|
numbers.add((new ImageIcon("Sprites/Numbers/" + i + ".png")).getImage());
|
|
slash = new ImageIcon("Sprites/Numbers/Slash.png").getImage();
|
|
amendmentImg = new ImageIcon("Sprites/Amendment.png").getImage();
|
|
powerImg = new ImageIcon("Sprites/Powerup1.png").getImage();
|
|
|
|
gameTimer.start();
|
|
|
|
// if i wanna add a button
|
|
/*
|
|
* JButton gameStart = new JButton("Start Game");
|
|
* gameStart.addActionListener(e -> {
|
|
* loadLevel(currentLevel);
|
|
* gameTimer.start();
|
|
* this.remove(gameStart);
|
|
* this.revalidate();
|
|
* this.repaint();
|
|
* });
|
|
* gameStart.setBorderPainted(false);
|
|
* gameStart.setFocusPainted(false);
|
|
* gameStart.setBounds(193,200,114,15);
|
|
* gameStart.setForeground(new Color(52, 152, 219));
|
|
* this.add(gameStart);
|
|
*/
|
|
}
|
|
|
|
// gameloop
|
|
public void gameLoop() {
|
|
if (currentLevel > totalLevels) return;
|
|
|
|
// lvl 3 arena
|
|
if (currentLevel == 4 || currentLevel == 10) {
|
|
boolean empty = currentLevel == 4 ? enemies.isEmpty() : enemies.size() <= 5;
|
|
if (enemiesKilled < 100 * (currentLevel == 4 ? 1 : 4) && empty) {
|
|
Random rand = new Random();
|
|
int xOff = rand.nextInt(700);
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
xOff = rand.nextInt(700);
|
|
if (xOff + 80 <= player.x && player.x <= xOff + 140) xOff += 60;
|
|
enemies.add(new Enemy(100 + xOff, 280, 20, 20, currentLevel));
|
|
}
|
|
|
|
} else if (enemiesKilled >= 100 * (currentLevel == 4 ? 1 : 4)) {
|
|
collidables.removeIf(c -> c.y == 300 && c.x >= 440 && c.x <= 520);
|
|
}
|
|
}
|
|
|
|
// camera
|
|
cameraX = player.x - boardWidth / 2;
|
|
cameraX = Math.max(0, cameraX);
|
|
cameraY = player.y - boardHeight / 2;
|
|
// cameraY = Math.max(0, cameraY);
|
|
|
|
// win
|
|
allCollected = player.numAmendments >= numAm[currentLevel - 1];
|
|
if (player.collidesWith(flag) && allCollected) {
|
|
currentLevel++;
|
|
player.health = 3;
|
|
if (currentLevel > totalLevels) {
|
|
gameTimer.stop();
|
|
System.out.println("You win!");
|
|
return;
|
|
} else {
|
|
loadLevel(currentLevel);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// keys
|
|
if (Math.abs(player.xVelo) < MAXXVELO) {
|
|
if (isKeyPressed(KeyEvent.VK_D) || isKeyPressed(KeyEvent.VK_RIGHT)) {
|
|
player.xVelo += 1;
|
|
} else if (isKeyPressed(KeyEvent.VK_A) || isKeyPressed(KeyEvent.VK_LEFT)) {
|
|
player.xVelo -= 1;
|
|
}
|
|
}
|
|
if (!isKeyPressed(KeyEvent.VK_D)
|
|
&& !isKeyPressed(KeyEvent.VK_RIGHT)
|
|
&& !isKeyPressed(KeyEvent.VK_A)
|
|
&& !isKeyPressed(KeyEvent.VK_LEFT)) { // friction
|
|
if (player.xVelo > 0) {
|
|
player.xVelo = Math.max(0, player.xVelo - FRICTION);
|
|
} else if (player.xVelo < 0) {
|
|
player.xVelo = Math.min(0, player.xVelo + FRICTION);
|
|
}
|
|
}
|
|
|
|
// jump
|
|
boolean jumpKeyDown = (isKeyPressed(KeyEvent.VK_W) || isKeyPressed(KeyEvent.VK_UP));
|
|
if (jumpKeyDown && !jumpPressed) {
|
|
if (player.onGround) {
|
|
playClip(jumpSound);
|
|
player.yVelo = -15;
|
|
player.onGround = false;
|
|
jumpPressed = true;
|
|
player.airJumps = 0;
|
|
} else if (player.curPower == 1 && player.airJumps < 1) {
|
|
playClip(jumpSound);
|
|
player.yVelo = -15;
|
|
player.airJumps++;
|
|
jumpPressed = true;
|
|
}
|
|
}
|
|
if (!jumpKeyDown) {
|
|
jumpPressed = false;
|
|
}
|
|
|
|
// gravity
|
|
if (player.yVelo < MAXYVELO) {
|
|
player.yVelo += GRAVITY;
|
|
}
|
|
|
|
// fall out of world
|
|
if (player.y > 1400) { // 1400/20 = 70 rows to work with per level
|
|
loadLevel(currentLevel);
|
|
player.health--;
|
|
}
|
|
|
|
for (Collectable c : collectables) {
|
|
if (c instanceof Powerup) {
|
|
Powerup pu = (Powerup) c;
|
|
if (pu.yVelo < MAXYVELO && !pu.onGround) {
|
|
pu.yVelo += GRAVITY;
|
|
}
|
|
pu.moveY(pu.yVelo);
|
|
for (Collidable col : collidables) {
|
|
if (pu.collidesWith(col)) {
|
|
pu.yVelo = 0;
|
|
pu.onGround = true;
|
|
pu.y = ((Tile) col).y - pu.height;
|
|
pu.rect.y = pu.y;
|
|
}
|
|
}
|
|
if (player.collidesWith(pu)) {
|
|
player.curPower = pu.id;
|
|
player.powerTimer = Player.POWER_DURATION;
|
|
}
|
|
} else if (c instanceof Amendment) {
|
|
Amendment am = (Amendment) c;
|
|
if (player.collidesWith(am)) {
|
|
player.numAmendments++;
|
|
playClip(collectSound);
|
|
}
|
|
}
|
|
}
|
|
collectables.removeIf(c -> player.collidesWith(c));
|
|
|
|
// update x
|
|
player.moveX(player.xVelo);
|
|
|
|
// collision with all tiles x
|
|
for (Collidable c : collidables) {
|
|
if (player.collidesWith(c)) {
|
|
player.onCollideX(c);
|
|
}
|
|
}
|
|
|
|
// update y
|
|
player.moveY(player.yVelo);
|
|
// assume not on ground
|
|
player.onGround = false;
|
|
|
|
// collision with all tiles y
|
|
for (Collidable c : collidables) {
|
|
if (player.collidesWith(c)) {
|
|
player.onCollideY(c, collectables);
|
|
}
|
|
}
|
|
|
|
// Powerup timer
|
|
if (player.curPower > 0) {
|
|
player.powerTimer--;
|
|
if (player.powerTimer <= 0) {
|
|
player.curPower = 0;
|
|
}
|
|
}
|
|
|
|
// update facing
|
|
if (player.xVelo > 0) player.facing = 1;
|
|
else if (player.xVelo < 0) player.facing = -1;
|
|
|
|
// shoot cooldown
|
|
if (player.shootCooldown > 0) player.shootCooldown--;
|
|
|
|
// invincibility timer
|
|
if (player.invincibleTimer > 0) player.invincibleTimer--;
|
|
|
|
// shoot
|
|
// projectiles
|
|
if (isKeyPressed(KeyEvent.VK_SPACE) && player.shootCooldown == 0) {
|
|
playClip(shootSound);
|
|
int projX = player.facing == 1 ? player.x + player.width : player.x - 10;
|
|
projectiles.add(new Projectile(projX, player.y, tileSize, 10, currentLevel, player.facing));
|
|
player.shootCooldown = Player.SHOOT_COOLDOWN;
|
|
}
|
|
|
|
projectiles.removeIf(p -> p.x < -50 + cameraX || p.x > boardWidth + cameraX + 200);
|
|
for (Projectile p : new ArrayList<>(projectiles)) {
|
|
p.move();
|
|
// projectile hits tile
|
|
for (Collidable c : collidables) {
|
|
if (p.collidesWith(c)) {
|
|
projectiles.remove(p);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// update enemies
|
|
for (Enemy e : enemies) {
|
|
e.patrol(collidables);
|
|
// enemy hits player
|
|
if (player.collidesWith(e)) {
|
|
player.takeDamage();
|
|
playClip(hitSound);
|
|
}
|
|
}
|
|
|
|
// die
|
|
if (player.health <= 0) {
|
|
System.out.print("Game Over - You Died!");
|
|
gameOver = true;
|
|
gameTimer.stop();
|
|
}
|
|
|
|
// projectile hits enemy
|
|
for (Projectile p : new ArrayList<>(projectiles)) {
|
|
for (Enemy e : new ArrayList<>(enemies)) {
|
|
if (p.collidesWith(e)) {
|
|
enemies.remove(e);
|
|
enemiesKilled++;
|
|
projectiles.remove(p);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
enemies.removeIf(e -> !e.alive);
|
|
}
|
|
|
|
public void loadLevel(int level) {
|
|
projectiles.clear();
|
|
enemiesKilled = 0;
|
|
try {
|
|
LevelLoader.load(tileSize, collidables, collectables, enemies, flag, player, level);
|
|
player.reset();
|
|
player.setLevel(level);
|
|
cameraX = 0;
|
|
cameraY = 0;
|
|
} catch (IOException e) {
|
|
System.out.println("Could not load level " + level);
|
|
}
|
|
}
|
|
|
|
// paintComponent
|
|
public void paintComponent(Graphics g) {
|
|
super.paintComponent(g);
|
|
draw(g);
|
|
}
|
|
|
|
// draw function
|
|
public void draw(Graphics g) {
|
|
// gameover screen
|
|
if (gameOver) {
|
|
g.drawImage(endImg, boardWidth / 2 - 100, boardHeight / 2 - 150, null);
|
|
g.drawImage(pressRImg, boardWidth / 2 - 80, boardHeight / 2, null);
|
|
return;
|
|
}
|
|
|
|
if (currentLevel > totalLevels) {
|
|
g.drawImage(winImg, boardWidth / 2 - 100, boardHeight / 2 - 50, null);
|
|
g.drawImage(pressRImg, boardWidth / 2 - 80, boardHeight / 2 + 60, null);
|
|
return;
|
|
}
|
|
|
|
if (currentLevel == 6) {
|
|
this.setBackground(NIGHT_SKY);
|
|
}
|
|
if (currentLevel != 6) {
|
|
this.setBackground(SKY);
|
|
}
|
|
|
|
g.translate(-cameraX, -cameraY);
|
|
|
|
player.draw(g);
|
|
for (Collidable c : collidables) c.draw(g);
|
|
for (Collectable c : collectables) c.draw(g);
|
|
for (Enemy e : enemies) e.draw(g);
|
|
for (Projectile p : projectiles) p.draw(g);
|
|
flag.draw(g);
|
|
if (currentLevel == 2) {
|
|
g.drawString("Take a leap of faith....", flag.x - 25, flag.y - 400);
|
|
}
|
|
if (currentLevel == 4 || currentLevel == 10) {
|
|
g.drawString("Kill " + 100 * (currentLevel == 4 ? 1 : 4) + " of them...", 220, 200);
|
|
}
|
|
|
|
if (currentLevel == 10) {
|
|
g.drawString("Final Level.... Time for the gauntlet", 80, 80);
|
|
}
|
|
// flag counter
|
|
int amOnes = player.numAmendments % 10;
|
|
int amTens = player.numAmendments / 10;
|
|
|
|
if (currentLevel > 0 && player.numAmendments < numAm[currentLevel - 1]) {
|
|
int lvlAmOnes = numAm[currentLevel - 1] % 10;
|
|
int lvlAmTens = numAm[currentLevel - 1] / 10;
|
|
if (amTens > 0) g.drawImage(numbers.get(amTens), flag.x - 20, flag.y - 30, null);
|
|
g.drawImage(numbers.get(amOnes), flag.x + 5, flag.y - 30, null);
|
|
g.drawImage(slash, flag.x + 28, flag.y - 32, null);
|
|
if (lvlAmTens > 0) {
|
|
g.drawImage(numbers.get(lvlAmTens), flag.x + 55, flag.y - 30, null);
|
|
g.drawImage(numbers.get(lvlAmOnes), flag.x + 80, flag.y - 30, null);
|
|
g.drawImage(amendmentImg, flag.x + 110, flag.y - 30, null);
|
|
|
|
} else {
|
|
g.drawImage(numbers.get(lvlAmOnes), flag.x + 55, flag.y - 30, null);
|
|
g.drawImage(amendmentImg, flag.x + 85, flag.y - 30, null);
|
|
}
|
|
}
|
|
|
|
g.translate(cameraX, cameraY);
|
|
|
|
if (currentLevel == 8) {
|
|
BufferedImage darkness =
|
|
new BufferedImage(boardWidth, boardHeight, BufferedImage.TYPE_INT_ARGB);
|
|
Graphics2D g2 = darkness.createGraphics();
|
|
|
|
// fill entire overlay with fully opaque black
|
|
g2.setColor(new Color(0, 0, 0, 255));
|
|
g2.fillRect(0, 0, boardWidth + 1000, boardHeight + 1000);
|
|
|
|
// player's position in screen coordinates
|
|
int screenX = (player.x + player.width / 2) - cameraX;
|
|
int screenY = (player.y + player.height / 2) - cameraY;
|
|
|
|
// cut circle centered on player
|
|
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
|
|
g2.fillOval(screenX - 80, screenY - 100, 160, 160);
|
|
|
|
g2.dispose();
|
|
|
|
// draw overlay in screen space (after translate reset)
|
|
g.drawImage(darkness, 0, 0, null);
|
|
}
|
|
|
|
int modAmt = 2000;
|
|
int curTime = (int) System.currentTimeMillis() % modAmt;
|
|
curTime = Math.abs(curTime);
|
|
|
|
// start screen
|
|
int startTime = 0;
|
|
if (curTime >= 0 && curTime <= modAmt / 4 - 1) startTime = 0;
|
|
else if (curTime >= modAmt / 4 && curTime <= modAmt / 2 - 1) startTime = 1;
|
|
else if (curTime >= modAmt / 2 && curTime <= modAmt * 3 / 4 - 1) startTime = 2;
|
|
else if (curTime >= modAmt * 3 / 4 && curTime <= modAmt - 1) startTime = 3;
|
|
|
|
if (currentLevel == 0) {
|
|
g.drawImage(titleImg, boardWidth / 2 - 150, boardHeight / 2 - 200, null);
|
|
|
|
g.drawString("An American Identity Project", boardWidth / 2 - 75, boardHeight / 2 - 40);
|
|
String text = "Press P to Start!";
|
|
int xBase = 340;
|
|
int yBase = 400;
|
|
int spacing = 8;
|
|
for (int i = 0; i < text.length(); i++) {
|
|
char c = text.charAt(i);
|
|
|
|
// x moves linearly
|
|
int x = xBase + (i * spacing);
|
|
|
|
// y uses a sine wave
|
|
// Math.sin takes radians. We use (startTime + i) to give each letter a
|
|
// different phase.
|
|
double waveOffset = Math.sin(startTime + i * 0.5) * 10;
|
|
int y = yBase + (int) waveOffset;
|
|
|
|
g.drawString(String.valueOf(c), x, y);
|
|
}
|
|
}
|
|
|
|
if (currentLevel > 0) {
|
|
|
|
// draw hearts:
|
|
int heartTime = 0;
|
|
if (curTime >= 0 && curTime <= modAmt / 2 - 1) heartTime = 1;
|
|
else if (curTime >= modAmt / 2 && curTime <= modAmt - 1) heartTime = 0;
|
|
for (int i = 0; i < player.health; i++) {
|
|
g.drawImage(heart, (((i + 1) * 20) - 10) + heartTime, 10 + heartTime * 2, null);
|
|
}
|
|
for (int i = 0; i < 3 - player.health; i++) {
|
|
g.drawImage(emptyHeart, (50 - (i * 20)) + heartTime, 10 + heartTime * 2, null);
|
|
}
|
|
|
|
// draw amendments counter in top right
|
|
|
|
if (amTens > 0) g.drawImage(numbers.get(amTens), 315, 10, null);
|
|
g.drawImage(numbers.get(amOnes), 340, 10, null);
|
|
int lvlAmOnes = numAm[currentLevel - 1] % 10;
|
|
int lvlAmTens = numAm[currentLevel - 1] / 10;
|
|
g.drawImage(slash, 363, 12, null);
|
|
if (lvlAmTens > 0) {
|
|
g.drawImage(numbers.get(lvlAmTens), 390, 10, null);
|
|
g.drawImage(numbers.get(lvlAmOnes), 415, 10, null);
|
|
|
|
g.drawImage(amendmentImg, 445, 10, null);
|
|
} else {
|
|
g.drawImage(numbers.get(lvlAmOnes), 390, 10, null);
|
|
g.drawImage(amendmentImg, 420, 10, null);
|
|
}
|
|
}
|
|
|
|
// draw powerup timer
|
|
if (player.curPower == 1) {
|
|
int secs = player.powerTimer / 66;
|
|
int tens = secs / 10;
|
|
int ones = secs % 10;
|
|
if (tens > 0) {
|
|
g.drawImage(numbers.get(tens), 184, 10, null);
|
|
}
|
|
g.drawImage(numbers.get(ones), 205, 10, null);
|
|
g.drawImage(powerImg, 220, 10, null);
|
|
}
|
|
}
|
|
|
|
// is key pressed
|
|
public boolean isKeyPressed(int key) {
|
|
return pressedKeys.getOrDefault(key, false);
|
|
}
|
|
|
|
// every tick
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (gameStarted) {
|
|
gameLoop();
|
|
}
|
|
repaint();
|
|
}
|
|
|
|
// check for key presses
|
|
@Override
|
|
public void keyPressed(KeyEvent e) {
|
|
if (e.getKeyCode() == KeyEvent.VK_P) {
|
|
if (currentLevel == 0) {
|
|
currentLevel = 1;
|
|
loadLevel(currentLevel);
|
|
gameStarted = true;
|
|
}
|
|
gameTimer.start();
|
|
return;
|
|
}
|
|
|
|
if (e.getKeyCode() == KeyEvent.VK_R) {
|
|
if (gameOver) {
|
|
gameTimer.stop();
|
|
loadLevel(1);
|
|
currentLevel = 1;
|
|
jumpPressed = false;
|
|
player.health = 3;
|
|
gameOver = false;
|
|
repaint();
|
|
gameTimer.start();
|
|
gameStarted = true;
|
|
}
|
|
}
|
|
|
|
if (e.getKeyCode() == KeyEvent.VK_O) {
|
|
currentLevel++;
|
|
loadLevel(currentLevel);
|
|
}
|
|
|
|
pressedKeys.put(e.getKeyCode(), true);
|
|
}
|
|
|
|
@Override
|
|
public void keyReleased(KeyEvent e) {
|
|
pressedKeys.put(e.getKeyCode(), false);
|
|
}
|
|
|
|
// dont need
|
|
@Override
|
|
public void keyTyped(KeyEvent e) {}
|
|
|
|
public Clip loadClip(String path) {
|
|
try {
|
|
AudioInputStream audio = AudioSystem.getAudioInputStream(new File(path));
|
|
Clip clip = AudioSystem.getClip();
|
|
clip.open(audio);
|
|
return clip;
|
|
} catch (Exception e) {
|
|
System.out.println("Could not load sound: " + path);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void playClip(Clip clip) {
|
|
if (clip != null) {
|
|
clip.setFramePosition(0); // rewind to start
|
|
clip.start();
|
|
}
|
|
}
|
|
}
|