From e43ea0e2424687da9bdee95990a2c0a1a9a92de5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:19:53 +0000 Subject: [PATCH 1/2] Improve Derivatiles game: bug fixes, new features, and code cleanup Bug fixes: - Fix String comparison using == instead of .equals() in BoardState - Move correctDerivatives init from constructor (where numRows=0) to startGame - Fix multi-digit coefficient parsing in Differentiate (e.g. x^10 -> 10x^9) - Trim whitespace from function strings to prevent matching issues - Remove unused import (javax.swing.tree.TreeModel) New features: - Visual feedback: green/red text on correct/incorrect answers - Game-over screen with final score, accuracy stats, and letter grade - Streak bonus system: consecutive correct answers earn extra points - Countdown timer (15s per question, -2 pts on timeout) - Wrong answer penalty (-1 pt) - Instructions/help text on the welcome screen - Play Again button on game-over screen Improvements: - Slider minimum changed from 0 to 1 (0 was invalid) - Expanded function bank from 28 to 50 questions - Replaced magic key code numbers with KeyEvent constants - Removed debug System.out.println statements - Fixed duplicate imports in Game.java - Renamed unclear variable (uselessAndOnlyForConstAtBeginningCase -> skipNextSign) - Quit button text changed from 'Get a life' to 'Quit' Co-Authored-By: Anish Lakkapragada --- functions.txt | 28 +++++- src/BoardState.java | 3 +- src/Differentiate.java | 28 +++--- src/Game.java | 209 ++++++++++++++++++++++++++++++++++++----- src/WelcomeScreen.java | 38 +++++--- 5 files changed, 248 insertions(+), 58 deletions(-) diff --git a/functions.txt b/functions.txt index d7e92d0..bdc24ef 100644 --- a/functions.txt +++ b/functions.txt @@ -1,6 +1,6 @@ -x + sin(x) +x + sin(x) 2x^2 + 9x -3x^7 - cos(x) +3x^7 - cos(x) 2x^3 - 9 4x^2 + 6 3x^5 - 21 @@ -25,4 +25,26 @@ x^2 + 2x - 1 9x^4 - 8x 42x^5 + cos(x) 5x^3 + sin(x) -56x^3 - cos(x) \ No newline at end of file +56x^3 - cos(x) +8x^3 + 3x^2 - 7x +12x^2 + 5x + 3 +6x^5 - 4x^3 +7x^4 + 2x^2 - 10 +15x^3 + sin(x) +20x^2 - 7x + 4 +x^3 + x^2 + x +11x^2 - cos(x) +4x^6 - 3x +2x^5 + 8x^3 - 1 +9x^3 + 14x +33x^2 + sin(x) +x^4 - 6x^2 + 9 +7x^3 - 5x + 2 +18x^4 + cos(x) +50x^3 - 25x +6x^2 + 12x - 8 +x^5 - sin(x) +14x^3 + 7x^2 +8x^4 - cos(x) +3x^6 + 2x +100x^2 - 50x + 1 diff --git a/src/BoardState.java b/src/BoardState.java index 445b2fd..05486b6 100644 --- a/src/BoardState.java +++ b/src/BoardState.java @@ -60,7 +60,7 @@ public static String[][] getGrid(String f, int numDerivs, int numOptions) { continue; } - if (correctDerivative == "0") { + if (correctDerivative.equals("0")) { tempChoices.add(Integer.toString((int) (Math.random() * 9) + 1)); continue; } @@ -78,7 +78,6 @@ public static String[][] getGrid(String f, int numDerivs, int numOptions) { } - System.out.println("this is temp choices:" + tempChoices); List choicesShuffle = new ArrayList(tempChoices); Collections.shuffle(choicesShuffle); diff --git a/src/Differentiate.java b/src/Differentiate.java index 22cb108..58231e2 100644 --- a/src/Differentiate.java +++ b/src/Differentiate.java @@ -1,7 +1,6 @@ package src; import java.util.*; -import javax.swing.tree.TreeModel; /** * Performs differentiating calculations. @@ -38,31 +37,27 @@ public String differentiateString(String function) { // "3x^2 + cos(x)" => ["3x^2", "+", "cos(x)"] // "3x^2 - cos(x)" => ["3x^2", "-", "cos(x)"] - String[] terms = function.split(" "); + String[] terms = function.trim().split(" "); String answer = ""; - boolean uselessAndOnlyForConstAtBeginningCase = false; + boolean skipNextSign = false; for (int i = 0; i < terms.length; i++) { String termOrSign = terms[i]; - System.out.println("this is termor sign: " + terms[i]); - // CORNER CASE: ONLY A CONSTANT if (terms.length == 1 && terms[i].indexOf("x") < 0) { - System.out.println("this is terms[i]: " + terms[i]); answer = "0"; break; } - else if (uselessAndOnlyForConstAtBeginningCase) { - uselessAndOnlyForConstAtBeginningCase = false; + else if (skipNextSign) { + skipNextSign = false; if ((termOrSign.equals("+") || termOrSign.equals("-"))) { answer += termOrSign + " "; } continue; } - System.out.println("this is termor sign: " + termOrSign); if (!(termOrSign.equals("+") || termOrSign.equals("-"))) { if (termOrSign.equals("x") && termOrSign.length() == 1) { answer += "1 "; @@ -73,7 +68,7 @@ else if (uselessAndOnlyForConstAtBeginningCase) { if (!termOrSign.contains("x")) { // 4 + x if (answer.length() == 0) { - uselessAndOnlyForConstAtBeginningCase = true; + skipNextSign = true; } // x + 4 of x + 4 + x else if (answer.length() > 0) { @@ -103,12 +98,18 @@ else if (answer.length() > 0) { } int derivCoefficient = 1; - if (!derivativeTerm.substring(0, 1).equals("x")) { - derivCoefficient = Integer.parseInt(derivativeTerm.substring(0, 1)); + // Parse multi-digit derivative coefficients + int derivCoeffEnd = 0; + while (derivCoeffEnd < derivativeTerm.length() + && Character.isDigit(derivativeTerm.charAt(derivCoeffEnd))) { + derivCoeffEnd++; + } + if (derivCoeffEnd > 0) { + derivCoefficient = Integer.parseInt(derivativeTerm.substring(0, derivCoeffEnd)); } String finalCoefficient = (derivCoefficient * preCoefficient) + ""; - String finalExpression = finalCoefficient + derivativeTerm.substring(1); + String finalExpression = finalCoefficient + derivativeTerm.substring(derivCoeffEnd); answer += finalExpression + " "; } @@ -216,7 +217,6 @@ public String[] correctAnswers(String function, int numOrders) { public static void main(String[] args) { Differentiate diff = new Differentiate(); System.out.println(diff.differentiateString("15120 - sin(x)")); - System.out.println("damn memer omment".hashCode()); } } diff --git a/src/Game.java b/src/Game.java index 0f618c7..a282084 100644 --- a/src/Game.java +++ b/src/Game.java @@ -5,14 +5,9 @@ import java.awt.event.KeyEvent; import java.awt.*; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; -import java.net.*; -import java.io.*; -import java.util.*; import java.net.InetAddress; /** @@ -41,6 +36,9 @@ public class Game extends JFrame implements KeyListener, ActionListener { private JButton backButton; // goes back private WelcomeScreen welcomeScreen; private JLabel pointsLabel; // stores the points + private JLabel feedbackLabel; // shows correct/incorrect feedback + private JLabel timerLabel; // shows countdown timer + private JLabel streakLabel; // shows current streak private BoardState boardState; private FunctionsList fl; private TileManager tm; @@ -48,6 +46,11 @@ public class Game extends JFrame implements KeyListener, ActionListener { private String[] correctDerivatives; private Differentiate d = new Differentiate(); private int numRows; + private int streak = 0; + private javax.swing.Timer countdownTimer; + private int timeRemaining = 15; // seconds per question + private int questionsAnswered = 0; + private int questionsCorrect = 0; private static final int numCols = 5; private static final int tileWidth = 200; @@ -64,9 +67,8 @@ public Game() { } - correctDerivatives = new String[numRows]; setFocusable(true); - addKeyListener(this); // WHY IS THE KEY LISTENER IGNORED + addKeyListener(this); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setTitle("Derivatiles"); @@ -88,19 +90,41 @@ public void startGame(int numOrders, boolean isNetworking) { setSize(1200, numOrders * tileHeight + 300); // resize here! numRows = numOrders; + correctDerivatives = new String[numRows]; + streak = 0; + questionsAnswered = 0; + questionsCorrect = 0; + functionLabel = new JLabel("", SwingConstants.CENTER); - // functionLabel.setHorizontalAlignment(JLabel.CENTER); functionLabel.setBounds(350, 50, 500, 45); functionLabel.setFont(new Font("Calibri", Font.BOLD, 25)); - // panel.add(functionLabel); add(functionLabel); // add the points label - pointsLabel = new JLabel("Points: " + 0); + pointsLabel = new JLabel("Points: 0"); pointsLabel.setFont(new Font("Calibri", Font.ITALIC, 20)); - pointsLabel.setBounds(935, 60, 125, 25); + pointsLabel.setBounds(935, 20, 200, 25); add(pointsLabel); + // add the streak label + streakLabel = new JLabel(""); + streakLabel.setFont(new Font("Calibri", Font.BOLD, 16)); + streakLabel.setForeground(new Color(255, 140, 0)); + streakLabel.setBounds(935, 50, 200, 25); + add(streakLabel); + + // add the timer label + timerLabel = new JLabel("Time: 15s"); + timerLabel.setFont(new Font("Calibri", Font.BOLD, 18)); + timerLabel.setBounds(935, 80, 200, 25); + add(timerLabel); + + // add feedback label + feedbackLabel = new JLabel("", SwingConstants.CENTER); + feedbackLabel.setFont(new Font("Calibri", Font.BOLD, 20)); + feedbackLabel.setBounds(350, 10, 500, 30); + add(feedbackLabel); + // add the back button backButton = new JButton("Go Back"); backButton.setFont(new Font("Calibri", Font.ITALIC, 15)); @@ -185,25 +209,163 @@ private void setupGame() { private void updateQuestion(String function) { curRow = numRows - 1; String newQuestion = function; - if (newQuestion == null && fl.hasQuestions()) { + if (newQuestion == null && fl != null && fl.hasQuestions()) { newQuestion = fl.nextFunction(); } + if (newQuestion == null || newQuestion.trim().isEmpty()) { + showGameOver(); + return; + } + + newQuestion = newQuestion.trim(); correctDerivatives = d.correctAnswers(newQuestion, numRows); String[][] gridLabels = BoardState.getGrid(newQuestion, numRows, numCols); tm.setLabels(gridLabels); functionLabel.setText(" f(x) = " + Differentiate.formatSubscript(newQuestion, false) + " "); - tm.setLoc(curRow, (int) (Math.random() * numCols), getGraphics()); // select + tm.setLoc(curRow, (int) (Math.random() * numCols), getGraphics()); + resetTimer(); } private void evaluatePoints(int r, int c) { String answeredDerivative = tm.getFunction(r, c); String correctDerivative = correctDerivatives[numRows - r - 1]; - if (correctDerivative.equals(answeredDerivative)) { - boardState.incrementPoints(3); - pointsLabel.setText("Points: " + boardState.getPoints()); + // Strip HTML tags for comparison + String cleanAnswer = answeredDerivative.replaceAll("<[^>]*>", "").trim(); + String cleanCorrect = correctDerivative.trim(); + if (cleanCorrect.equals(cleanAnswer)) { + int bonus = 3 + streak; + boardState.incrementPoints(bonus); + streak++; + questionsCorrect++; + showFeedback(true, bonus); + } else { + boardState.decrementPoints(1); + streak = 0; + showFeedback(false, -1); + } + questionsAnswered++; + pointsLabel.setText("Points: " + boardState.getPoints()); + updateStreakLabel(); + } + + private void showFeedback(boolean correct, int pointsDelta) { + if (correct) { + feedbackLabel.setForeground(new Color(0, 150, 0)); + String msg = "Correct! +" + pointsDelta + " pts"; + if (streak > 1) { + msg += " (streak x" + streak + "!)"; + } + feedbackLabel.setText(msg); + } else { + feedbackLabel.setForeground(Color.RED); + feedbackLabel.setText("Wrong! -1 pt"); + } + // Clear feedback after 2 seconds + javax.swing.Timer clearTimer = new javax.swing.Timer(2000, evt -> feedbackLabel.setText("")); + clearTimer.setRepeats(false); + clearTimer.start(); + } + + private void updateStreakLabel() { + if (streak >= 2) { + streakLabel.setText("Streak: " + streak + " in a row!"); + } else { + streakLabel.setText(""); + } + } + + private void resetTimer() { + timeRemaining = 15; + timerLabel.setForeground(Color.BLACK); + timerLabel.setText("Time: 15s"); + if (countdownTimer != null) { + countdownTimer.stop(); } + countdownTimer = new javax.swing.Timer(1000, evt -> { + timeRemaining--; + timerLabel.setText("Time: " + timeRemaining + "s"); + if (timeRemaining <= 5) { + timerLabel.setForeground(Color.RED); + } + if (timeRemaining <= 0) { + countdownTimer.stop(); + boardState.decrementPoints(2); + streak = 0; + questionsAnswered++; + pointsLabel.setText("Points: " + boardState.getPoints()); + updateStreakLabel(); + feedbackLabel.setForeground(Color.RED); + feedbackLabel.setText("Time's up! -2 pts"); + javax.swing.Timer clearTimer = new javax.swing.Timer(1500, e2 -> feedbackLabel.setText("")); + clearTimer.setRepeats(false); + clearTimer.start(); + if (!isTogether) { + updateQuestion(null); + } + } + }); + countdownTimer.start(); + } + + private void showGameOver() { + if (countdownTimer != null) { + countdownTimer.stop(); + } + getContentPane().removeAll(); + setSize(600, 500); + + JLabel gameOverLabel = new JLabel("Game Over!", SwingConstants.CENTER); + gameOverLabel.setBounds(150, 30, 300, 60); + gameOverLabel.setFont(new Font("Calibri", Font.BOLD, 40)); + add(gameOverLabel); + + JLabel finalScoreLabel = new JLabel("Final Score: " + boardState.getPoints(), SwingConstants.CENTER); + finalScoreLabel.setBounds(150, 110, 300, 40); + finalScoreLabel.setFont(new Font("Calibri", Font.BOLD, 28)); + finalScoreLabel.setForeground(new Color(0, 100, 200)); + add(finalScoreLabel); + + String accuracy = questionsAnswered > 0 + ? String.format("%.0f%%", (questionsCorrect * 100.0 / questionsAnswered)) + : "N/A"; + JLabel statsLabel = new JLabel( + "
Questions: " + questionsAnswered + + "
Correct: " + questionsCorrect + + "
Accuracy: " + accuracy + "
", + SwingConstants.CENTER); + statsLabel.setBounds(150, 170, 300, 100); + statsLabel.setFont(new Font("Calibri", Font.PLAIN, 20)); + add(statsLabel); + + String grade; + Color gradeColor; + double pct = questionsAnswered > 0 ? (questionsCorrect * 100.0 / questionsAnswered) : 0; + if (pct >= 90) { grade = "A+ - AP Ready!"; gradeColor = new Color(0, 150, 0); } + else if (pct >= 80) { grade = "B - Almost there!"; gradeColor = new Color(0, 100, 200); } + else if (pct >= 70) { grade = "C - Keep practicing!"; gradeColor = new Color(255, 140, 0); } + else { grade = "Needs more practice!"; gradeColor = Color.RED; } + + JLabel gradeLabel = new JLabel(grade, SwingConstants.CENTER); + gradeLabel.setBounds(150, 280, 300, 40); + gradeLabel.setFont(new Font("Calibri", Font.BOLD, 22)); + gradeLabel.setForeground(gradeColor); + add(gradeLabel); + + JButton playAgainButton = new JButton("Play Again"); + playAgainButton.setBounds(200, 350, 200, 50); + playAgainButton.setFont(new Font("Calibri", Font.BOLD, 20)); + playAgainButton.addActionListener(evt -> { + getContentPane().removeAll(); + setSize(1200, 1000); + welcomeScreen = new WelcomeScreen(Game.this); + }); + add(playAgainButton); + + revalidate(); + repaint(); + setVisible(true); } /** @@ -217,7 +379,6 @@ private void evaluatePoints(int r, int c) { public void moveTo(int newRow, int newCol) { if (Math.abs(newCol - tm.curCol()) == 0) { - System.out.println("this is network added points: " + networkAddedPoints); if (newRow < 0 && !this.isTogether) { evaluatePoints(0, newCol); // evaluate at this level updateQuestion(null); // they kinda done now @@ -270,33 +431,31 @@ public void actionPerformed(ActionEvent e) { * A or left arrow means go left (c - 1) */ public void keyPressed(KeyEvent e) { - System.out.println("getting pressed"); - System.out.println("current row, column: " + tm.curRow() + " : " + tm.curCol()); int keyCode = e.getKeyCode(); if (tm == null) { return; } - if (keyCode >= 49 && keyCode <= 48 + numCols) { - moveTo(tm.curRow(), (keyCode - 48) - 1); + if (keyCode >= KeyEvent.VK_1 && keyCode <= KeyEvent.VK_0 + numCols) { + moveTo(tm.curRow(), (keyCode - KeyEvent.VK_0) - 1); return; } switch (keyCode) { - case 65: + case KeyEvent.VK_A: case KeyEvent.VK_LEFT: moveTo(tm.curRow(), tm.curCol() - 1); break; - case 68: + case KeyEvent.VK_D: case KeyEvent.VK_RIGHT: moveTo(tm.curRow(), tm.curCol() + 1); break; - case 87: + case KeyEvent.VK_W: case KeyEvent.VK_UP: - moveTo(tm.curRow() - 1, tm.curCol()); // selects the current column + moveTo(tm.curRow() - 1, tm.curCol()); break; default: diff --git a/src/WelcomeScreen.java b/src/WelcomeScreen.java index c7753f9..d249ffe 100644 --- a/src/WelcomeScreen.java +++ b/src/WelcomeScreen.java @@ -28,38 +28,52 @@ public WelcomeScreen(Game gF) { gameFrame.setSize(600, 600); gameLabel = new JLabel("Derivatiles Game", SwingConstants.CENTER); - gameLabel.setBounds(150, 10, 300, 100); + gameLabel.setBounds(150, 10, 300, 60); gameLabel.setFont(new Font("Calibri", Font.BOLD, 30)); gameFrame.add(gameLabel); + // Instructions panel + JLabel instructionsLabel = new JLabel( + "
How to Play
" + + "Pick the correct derivative for each row!
" + + "A/Left & D/Right to move, W/Up to select
" + + "Or press 1-5 to jump to a column

" + + "+3 pts for correct (+streak bonus)
" + + "-1 pt for wrong, -2 pts if time runs out
" + + "15 seconds per question!
", + SwingConstants.CENTER); + instructionsLabel.setBounds(100, 65, 400, 140); + instructionsLabel.setFont(new Font("Calibri", Font.PLAIN, 14)); + gameFrame.add(instructionsLabel); + numDerivLabel = new JLabel("Select Final Derivative Order", SwingConstants.CENTER); - numDerivLabel.setBounds(150, 110, 300, 50); + numDerivLabel.setBounds(150, 210, 300, 50); numDerivLabel.setFont(new Font("Calibri", Font.BOLD, 20)); gameFrame.add(numDerivLabel); startButton = new JButton("Start Game!"); - startButton.setBounds(200, 325, 200, 50); + startButton.setBounds(200, 375, 200, 50); startButton.setFont(new Font("Calibri", Font.ITALIC, 20)); startButton.addActionListener(this); gameFrame.add(startButton); togetherButton = new JButton("Play with another person"); - togetherButton.setBounds(200, 385, 200, 50); + togetherButton.setBounds(200, 435, 200, 50); togetherButton.setFont(new Font("Calibri", Font.ITALIC, 15)); togetherButton.addActionListener(this); gameFrame.add(togetherButton); - quitButton = new JButton("Get a life"); - quitButton.setBounds(200, 445, 200, 50); + quitButton = new JButton("Quit"); + quitButton.setBounds(200, 495, 200, 50); quitButton.setFont(new Font("Calibri", Font.ITALIC, 20)); quitButton.addActionListener(this); gameFrame.add(quitButton); - numDerivSlider = new JSlider(JSlider.HORIZONTAL, 0, 10, 0); - numDerivSlider.setMajorTickSpacing(2); + numDerivSlider = new JSlider(JSlider.HORIZONTAL, 1, 10, 1); + numDerivSlider.setMajorTickSpacing(1); numDerivSlider.setPaintTicks(true); numDerivSlider.setPaintLabels(true); - numDerivSlider.setBounds(200, 300, 400, 50); + numDerivSlider.setBounds(150, 260, 300, 50); gameFrame.add(numDerivSlider); gameFrame.setVisible(true); @@ -75,12 +89,8 @@ public void actionPerformed(ActionEvent e) { } int selectedOrders = numDerivSlider.getValue(); - if (selectedOrders == 0) { - numDerivLabel.setForeground(Color.RED); - return; - } - else if (e.getSource() == togetherButton) { + if (e.getSource() == togetherButton) { int value = numDerivSlider.getValue(); // remove all the components of the JFrame From 82dc42434100598840c6198cc8c312be3ec86f1d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:25:22 +0000 Subject: [PATCH 2/2] Fix layout rendering: set content pane layout to null for setBounds positioning The JFrame's default BorderLayout was overriding setBounds() calls, causing labels, buttons, and instructions to not render. Added getContentPane().setLayout(null) to match the absolute positioning pattern used throughout the codebase. Co-Authored-By: Anish Lakkapragada --- src/Game.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Game.java b/src/Game.java index a282084..a346cae 100644 --- a/src/Game.java +++ b/src/Game.java @@ -69,6 +69,7 @@ public Game() { setFocusable(true); addKeyListener(this); + getContentPane().setLayout(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setTitle("Derivatiles");