This commit is contained in:
CT
2026-05-14 00:51:15 -05:00
parent dc5cb74e6a
commit 3eae9e6596
7 changed files with 1433 additions and 1386 deletions

View File

@@ -1,41 +1,509 @@
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class TwoPlayerGameDriver
{
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setTitle("Two Player Game");
/**
* GameDriver — application entry point.
*
* <p>Owns the JFrame and the root CardLayout container. All UI screens (name entry, main menu,
* credits) live here as static inner classes so the project stays at four .java files.
*
* <p>Card keys: "NAMES" — JTextField name-entry screen (shown first) "MENU" — level-select /
* difficulty screen "LEVEL1" — in-game (all three share one GamePanel instance) "LEVEL2" "LEVEL3"
* "CREDITS" — scrolling JList credits, added on demand
*/
public class TwoPlayerGameDriver {
// CardLayout
CardLayout layout = new CardLayout();
JPanel container = new JPanel(layout);
public static void main(String[] args) {
SwingUtilities.invokeLater(
() -> {
JFrame frame = new JFrame("Two Player Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
//Panels
GameFields gamePanel = new GameFields();
LvlManager levelMenu = new LvlManager(gamePanel);
MainMenu menuPanel = new MainMenu(gamePanel);
CardLayout layout = new CardLayout();
JPanel container = new JPanel(layout);
//add to cardLayout
container.add(menuPanel, "MENU"); //+ more
container.add(levelMenu, "LEVELS");
container.add(gamePanel, "GAME");
// One shared GamePanel — added under all three level keys
GamePanel gamePanel = new GamePanel();
container.add(gamePanel, "LEVEL1");
container.add(gamePanel, "LEVEL2");
container.add(gamePanel, "LEVEL3");
//switch screens
menuPanel.setContainer(container,layout); //+ more
levelMenu.setContainer(container,layout);
gamePanel.setContainer(container,layout);
// Main menu (level-select)
MainMenu menu = new MainMenu(gamePanel);
container.add(menu, "MENU");
frame.add(container);
frame.pack();
frame.setLocationRelativeTo(null);
//gamePanel.requestFocus();
frame.setVisible(true);
// Name-entry screen — shown first on launch
NameEntry nameEntry = new NameEntry(gamePanel, menu);
container.add(nameEntry, "NAMES");
//frame
frame.setResizable(false);
frame.setSize(640, 360); //<-- change later
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Wire container + layout into every screen that needs to navigate
gamePanel.setContainer(container, layout);
menu.setContainer(container, layout);
nameEntry.setContainer(container, layout);
frame.add(container);
frame.pack();
frame.setSize(640, 360);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// Start on the name-entry screen
layout.show(container, "NAMES");
});
}
// ══════════════════════════════════════════════════════════════════════════
// NameEntry — JTextField-based screen that captures player names
//
// Rubric: "Input text box — players name input"
// ══════════════════════════════════════════════════════════════════════════
static class NameEntry extends JPanel {
private JPanel container;
private CardLayout cardLayout;
private final JTextField field1 = new JTextField("Player 1", 14);
private final JTextField field2 = new JTextField("Player 2", 14);
NameEntry(GamePanel gp, MainMenu menu) {
setLayout(null);
setPreferredSize(new Dimension(640, 360));
setOpaque(false);
// Title
JLabel title = new JLabel("ENTER PLAYER NAMES", SwingConstants.CENTER);
title.setFont(new Font("Arial", Font.BOLD, 26));
title.setForeground(Color.WHITE);
title.setBounds(0, 40, 640, 40);
add(title);
// Sub-labels
JLabel lbl1 = new JLabel("Bear Player (Arrow Keys):", SwingConstants.RIGHT);
lbl1.setForeground(new Color(210, 140, 60));
lbl1.setFont(new Font("Arial", Font.BOLD, 14));
lbl1.setBounds(80, 130, 220, 30);
add(lbl1);
JLabel lbl2 = new JLabel("Seal Player (WASD):", SwingConstants.RIGHT);
lbl2.setForeground(new Color(70, 160, 220));
lbl2.setFont(new Font("Arial", Font.BOLD, 14));
lbl2.setBounds(80, 185, 220, 30);
add(lbl2);
// Text fields — select-all on focus for easy overtype
styleField(field1, new Color(210, 140, 60));
styleField(field2, new Color(70, 160, 220));
field1.setBounds(315, 130, 200, 30);
field2.setBounds(315, 185, 200, 30);
add(field1);
add(field2);
// Confirm button — saves names to GamePanel & GameDriver shared state,
// then flips to the menu
JButton confirm = new JButton("CONTINUE →");
confirm.setFont(new Font("Arial", Font.BOLD, 14));
confirm.setForeground(Color.WHITE);
confirm.setBackground(new Color(30, 80, 30));
confirm.setBorder(BorderFactory.createLineBorder(new Color(80, 200, 80), 2));
confirm.setFocusPainted(false);
confirm.setBounds(220, 255, 200, 36);
add(confirm);
ActionListener go =
e -> {
String n1 = field1.getText().trim();
String n2 = field2.getText().trim();
if (n1.isEmpty()) n1 = "Bear";
if (n2.isEmpty()) n2 = "Seal";
// Push names to GamePanel so startLevel() picks them up
gp.bear.name = n1;
gp.seal.name = n2;
// Also give MainMenu a reference so its buttons can pass them through
menu.pendingName1 = n1;
menu.pendingName2 = n2;
cardLayout.show(container, "MENU");
};
confirm.addActionListener(go);
// Pressing Enter in either field also confirms
field1.addActionListener(go);
field2.addActionListener(go);
// Instruction hint
JLabel hint = new JLabel("Press Enter or click Continue", SwingConstants.CENTER);
hint.setForeground(Color.GRAY);
hint.setFont(new Font("Arial", Font.ITALIC, 11));
hint.setBounds(0, 305, 640, 20);
add(hint);
}
}
void setContainer(JPanel c, CardLayout cl) {
container = c;
cardLayout = cl;
}
private void styleField(JTextField f, Color accent) {
f.setFont(new Font("Arial", Font.PLAIN, 14));
f.setBackground(new Color(30, 30, 55));
f.setForeground(accent);
f.setCaretColor(Color.WHITE);
f.setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(accent, 2),
BorderFactory.createEmptyBorder(2, 6, 2, 6)));
f.addFocusListener(
new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
f.selectAll();
}
});
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(
new GradientPaint(0, 0, new Color(10, 10, 30), 0, getHeight(), new Color(20, 10, 40)));
g2.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
}
// ══════════════════════════════════════════════════════════════════════════
// MainMenu — level-select, difficulty chooser, speed slider, quit
// ══════════════════════════════════════════════════════════════════════════
static class MainMenu extends JPanel {
private JPanel container;
private CardLayout cardLayout;
// Names supplied by NameEntry before the menu is shown
String pendingName1 = "Bear";
String pendingName2 = "Seal";
MainMenu(GamePanel gp) {
setLayout(null);
setPreferredSize(new Dimension(640, 360));
setOpaque(false);
// Title
JLabel title = new JLabel("SELECT A LEVEL", SwingConstants.CENTER);
title.setFont(new Font("Arial", Font.BOLD, 26));
title.setForeground(Color.WHITE);
title.setBounds(0, 18, 640, 36);
add(title);
// "Change names" link back to name entry
JButton changeName = new JButton("← Change Names");
changeName.setFont(new Font("Arial", Font.PLAIN, 11));
changeName.setForeground(Color.LIGHT_GRAY);
changeName.setContentAreaFilled(false);
changeName.setBorderPainted(false);
changeName.setFocusPainted(false);
changeName.setBounds(10, 10, 140, 22);
add(changeName);
changeName.addActionListener(e -> cardLayout.show(container, "NAMES"));
// Level buttons
LevelButton lvl1 =
new LevelButton(1, "The Crossing", new Color(40, 100, 110), new Color(0, 200, 220));
LevelButton lvl2 =
new LevelButton(2, "Double Danger", new Color(120, 70, 20), new Color(255, 150, 0));
LevelButton lvl3 =
new LevelButton(3, "The Gauntlet", new Color(110, 20, 20), new Color(255, 60, 60));
lvl1.setBounds(80, 90, 160, 160);
lvl2.setBounds(240, 90, 160, 160);
lvl3.setBounds(400, 90, 160, 160);
add(lvl1);
add(lvl2);
add(lvl3);
lvl1.addActionListener(e -> gp.startLevel(pendingName1, pendingName2, "LEVEL1"));
lvl2.addActionListener(e -> gp.startLevel(pendingName1, pendingName2, "LEVEL2"));
lvl3.addActionListener(e -> gp.startLevel(pendingName1, pendingName2, "LEVEL3"));
// Difficulty radio buttons
JRadioButton easy = new JRadioButton("Easy", true);
JRadioButton medium = new JRadioButton("Medium");
JRadioButton hard = new JRadioButton("Hard");
ButtonGroup grp = new ButtonGroup();
for (JRadioButton rb : new JRadioButton[] {easy, medium, hard}) {
rb.setForeground(Color.WHITE);
rb.setOpaque(false);
grp.add(rb);
}
easy.addActionListener(e -> gp.setDifficulty("Easy"));
medium.addActionListener(e -> gp.setDifficulty("Medium"));
hard.addActionListener(e -> gp.setDifficulty("Hard"));
JLabel diffLabel = new JLabel("Difficulty:");
diffLabel.setForeground(Color.LIGHT_GRAY);
JPanel diff = new JPanel(new FlowLayout(FlowLayout.CENTER, 8, 0));
diff.setOpaque(false);
diff.add(diffLabel);
diff.add(easy);
diff.add(medium);
diff.add(hard);
diff.setBounds(140, 268, 360, 26);
add(diff);
// Quit → rolling credits → System.exit
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, 58, 90, 26);
add(quit);
quit.addActionListener(e -> showCredits());
}
void setContainer(JPanel c, CardLayout cl) {
container = c;
cardLayout = cl;
}
private void showCredits() {
String[] lines = {
"",
"",
"",
"",
"~ CREDITS ~",
"",
"Developed by:",
"Aimee Azzahra",
"Ayan Mishra",
"Jennifer Phan",
"",
"Character Art — Jennifer Phan",
"Background Art — Aimee Azzahra",
"",
"Programming:",
"Jennifer — Player object, fluid logic, float method",
"Aimee — Wall/Rock collision, TileSet class, tap method",
"Ayan — Main menu, level manager, hold method",
"",
"Thanks for playing!",
"",
"",
"",
"",
""
};
CreditsPanel credits = new CreditsPanel(lines, () -> System.exit(0));
container.add(credits, "CREDITS");
cardLayout.show(container, "CREDITS");
credits.start();
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(
new GradientPaint(0, 0, new Color(10, 10, 30), 0, getHeight(), new Color(30, 10, 50)));
g2.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
}
// ══════════════════════════════════════════════════════════════════════════
// LevelButton — rollover icon changes dramatically (rubric item)
// Normal : small muted rounded tile + level number
// Hover : large bright tile with glow rings, big number, stars
// ══════════════════════════════════════════════════════════════════════════
private static class LevelButton extends JButton {
private static final int CANVAS = 140;
private static final int TILE_NORM = 76;
private static final int TILE_HOV = 112;
private final int level;
private final String levelName;
private final Color tileColor;
private final Color hoverColor;
private boolean hovered = false;
LevelButton(int level, String levelName, Color tile, Color hover) {
this.level = level;
this.levelName = levelName;
this.tileColor = tile;
this.hoverColor = hover;
setContentAreaFilled(false);
setBorderPainted(false);
setFocusPainted(false);
setOpaque(false);
setIcon(buildIcon(false));
setRolloverEnabled(true);
setRolloverIcon(buildIcon(true));
setPreferredSize(new Dimension(160, 160));
setSize(160, 160);
setVerticalTextPosition(SwingConstants.BOTTOM);
setHorizontalTextPosition(SwingConstants.CENTER);
setFont(new Font("Arial", Font.BOLD, 12));
setForeground(Color.WHITE);
addMouseListener(
new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
hovered = true;
repaint();
}
@Override
public void mouseExited(MouseEvent e) {
hovered = false;
repaint();
}
});
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
String label = hovered ? levelName : "Level " + level;
Color fg = hovered ? hoverColor.brighter().brighter() : Color.LIGHT_GRAY;
FontMetrics fm = g2.getFontMetrics(getFont());
int tx = (getWidth() - fm.stringWidth(label)) / 2;
int ty = getHeight() - 6;
g2.setFont(getFont());
g2.setColor(Color.BLACK);
g2.drawString(label, tx + 1, ty + 1);
g2.setColor(fg);
g2.drawString(label, tx, ty);
g2.dispose();
}
private ImageIcon buildIcon(boolean hover) {
BufferedImage img = new BufferedImage(CANVAS, CANVAS, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int cx = CANVAS / 2, cy = CANVAS / 2;
if (hover) {
// Concentric glow rings
for (int i = 4; i >= 1; i--) {
int r = TILE_HOV / 2 + i * 9;
g.setColor(
new Color(
hoverColor.getRed() / 255f,
hoverColor.getGreen() / 255f,
hoverColor.getBlue() / 255f,
0.12f * i));
g.fillOval(cx - r, cy - r, r * 2, r * 2);
}
g.setColor(hoverColor);
g.fillRoundRect(cx - TILE_HOV / 2, cy - TILE_HOV / 2, TILE_HOV, TILE_HOV, 24, 24);
g.setColor(Color.WHITE);
g.setStroke(new BasicStroke(3f));
g.drawRoundRect(cx - TILE_HOV / 2, cy - TILE_HOV / 2, TILE_HOV, TILE_HOV, 24, 24);
drawNumber(g, cx, cy, 44);
drawStars(g, cx + TILE_HOV / 2 - 26, cy - TILE_HOV / 2 + 6, level);
} else {
g.setColor(tileColor);
g.fillRoundRect(cx - TILE_NORM / 2, cy - TILE_NORM / 2, TILE_NORM, TILE_NORM, 16, 16);
g.setColor(tileColor.brighter());
g.setStroke(new BasicStroke(1.5f));
g.drawRoundRect(cx - TILE_NORM / 2, cy - TILE_NORM / 2, TILE_NORM, TILE_NORM, 16, 16);
drawNumber(g, cx, cy, 28);
drawStars(g, cx + TILE_NORM / 2 - 20, cy - TILE_NORM / 2 + 4, level);
}
g.dispose();
return new ImageIcon(img);
}
private void drawNumber(Graphics2D g, int cx, int cy, int fs) {
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.BOLD, fs));
FontMetrics fm = g.getFontMetrics();
String num = String.valueOf(level);
g.drawString(num, cx - fm.stringWidth(num) / 2, cy + fm.getAscent() / 2 - 2);
}
private void drawStars(Graphics2D g, int x, int y, int count) {
g.setColor(new Color(255, 220, 50));
for (int i = 0; i < count; i++) drawStar(g, x - i * 13, y, 10);
}
private void drawStar(Graphics2D g, int cx, int cy, int size) {
int pts = 5;
double or = size / 2.0, ir = or * 0.4;
int[] xs = new int[pts * 2], ys = new int[pts * 2];
for (int i = 0; i < pts * 2; i++) {
double a = Math.PI / pts * i - Math.PI / 2;
double r = (i % 2 == 0) ? or : ir;
xs[i] = (int) (cx + r * Math.cos(a));
ys[i] = (int) (cy + r * Math.sin(a));
}
g.fillPolygon(xs, ys, pts * 2);
}
}
// ══════════════════════════════════════════════════════════════════════════
// CreditsPanel — scrolling JList (rubric item: "Use JList")
// ══════════════════════════════════════════════════════════════════════════
static class CreditsPanel extends JPanel {
private final JScrollPane scrollPane;
private final Timer scrollTimer;
private int scrollPos = 0;
CreditsPanel(String[] lines, Runnable onComplete) {
setLayout(new BorderLayout());
setBackground(Color.BLACK);
JList<String> list = new JList<>(lines);
list.setBackground(Color.BLACK);
list.setForeground(Color.WHITE);
list.setFont(new Font("SansSerif", Font.BOLD, 18));
list.setFixedCellHeight(38);
((DefaultListCellRenderer) list.getCellRenderer())
.setHorizontalAlignment(SwingConstants.CENTER);
scrollPane = new JScrollPane(list);
scrollPane.setBorder(null);
scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(0, 0));
add(scrollPane, BorderLayout.CENTER);
// Scroll animation — reads scrollPane field safely; no lambda self-reference
scrollTimer =
new Timer(
28,
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JScrollBar bar = scrollPane.getVerticalScrollBar();
bar.setValue(++scrollPos);
if (scrollPos >= bar.getMaximum() - bar.getVisibleAmount() + 120) {
((Timer) e.getSource()).stop();
onComplete.run();
}
}
});
JButton skip = new JButton("SKIP");
skip.setFont(new Font("Arial", Font.BOLD, 12));
skip.addActionListener(
e -> {
scrollTimer.stop();
onComplete.run();
});
add(skip, BorderLayout.SOUTH);
}
void start() {
scrollPos = 0;
scrollTimer.start();
}
}
}