import java.awt.*; import java.awt.event.*; import java.awt.image.*; import javax.swing.*; /** * GameDriver — application entry point. * *

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. * *

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 { public static void main(String[] args) { SwingUtilities.invokeLater( () -> { JFrame frame = new JFrame("Two Player Game"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setResizable(false); CardLayout layout = new CardLayout(); JPanel container = new JPanel(layout); // 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"); // Main menu (level-select) MainMenu menu = new MainMenu(gamePanel); container.add(menu, "MENU"); // Name-entry screen — shown first on launch NameEntry nameEntry = new NameEntry(gamePanel, menu); container.add(nameEntry, "NAMES"); // 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 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(); } } }