diff --git a/src/gameWindow.java b/src/gameWindow.java index c02bd4c..f1c5881 100644 --- a/src/gameWindow.java +++ b/src/gameWindow.java @@ -6,14 +6,15 @@ import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.util.ArrayList; -public class gameWindow implements KeyListener, WindowListener, MouseListener { +public class gameWindow implements KeyListener, WindowListener, MouseListener, MouseMotionListener { private JFrame mainFrame; - private keyboard k = new keyboard(); private variables v = new variables(); + private keyboard k = new keyboard(v); private language l = new language(); mySurface s; @@ -27,6 +28,7 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { mainFrame.addKeyListener(this); mainFrame.addWindowListener(this); mainFrame.getContentPane().addMouseListener(this); + mainFrame.getContentPane().addMouseMotionListener(this); mainFrame.setFocusTraversalKeysEnabled(false); s = new mySurface(); mainFrame.add(s); @@ -94,9 +96,15 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { generateTestStrings(g, wordsFont, (int) (getWidth() * 0.8)); Rectangle lines[] = new Rectangle[v.drawLines]; + v.WPMText = l.wpm + ": " + v.getWPM(); v.ScoreText = l.score + ": " + 0; + g.setColor(v.backgroundColor); + g.fillRect(0,0, getWidth(), getHeight()); + + g.setColor(v.UIColor); + // Draw debug rectangles if(v.debug) { g.drawRect((int) v.rWPM.getX(), (int) v.rWPM.getY(), @@ -107,11 +115,13 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { (int) v.rOptions.getWidth(), (int) v.rOptions.getHeight()); } - g.drawRect((int) v.rTopBar.getX(), (int) v.rTopBar.getY(), - (int) v.rTopBar.getWidth(), (int) v.rTopBar.getHeight()); - g.drawRect((int) (getWidth() * 0.05), + g.drawRoundRect(v.rTopBar.x, v.rTopBar.y, v.rTopBar.width, + v.rTopBar.height, 20, 20); + g.drawRoundRect((int) (getWidth() * 0.05), (int) (getHeight() * 0.025), (int) (getWidth() * 0.90), - UIHeightArea); + UIHeightArea, 20, 20); + + g.setColor(v.textColor); drawCenteredString(g, v.WPMText, v.rWPM, wordsFont); drawCenteredString(g, v.ScoreText, v.rScore, wordsFont); drawCenteredString(g, v.testWords + l.words, v.rOptions, wordsFont); @@ -142,32 +152,43 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { } if(v.testOver) { - Rectangle rStatsWindow = new Rectangle((int) (getWidth() * 0.25), - UIHeight + UITopBarHeightArea * 2, - (int) (getWidth() * 0.5), (int) (UIHeightArea * 0.5)); - Rectangle[] textLines = new Rectangle[4]; - - g.setColor(v.backgroundColor); - g.fillRect((int) rStatsWindow.getX(), (int) rStatsWindow.getY(), - (int) rStatsWindow.getWidth(), (int) rStatsWindow.getHeight()); - g.setColor(v.textColor); - - int counter = (int) (rStatsWindow.getY() / (textLines.length - 1)); - for(int i = 0; i < textLines.length; i++) { - textLines[i] = new Rectangle((int) rStatsWindow.getX(), - (int) rStatsWindow.getY() + counter * i, - (int) rStatsWindow.getWidth(), counter); - } - drawCenteredString(g, l.testOver, textLines[0], wordsFont); - drawCenteredString(g, v.WPMText + l.raw, textLines[1], wordsFont); - drawCenteredString(g, l.accuracy + ": " + v.calculateAccuracy() - + "%", textLines[2], wordsFont); - drawCenteredString(g, l.time + ": " - + v.generateElapsedTimeString(), textLines[3], wordsFont); - + v.calculateEndGameStats(); + String[] endStats = {l.testOver, l.wpm + ": " + v.getRealWPM(), + l.wpm + l.raw + ": " + v.getWPM(), l.accuracy + ": " + + v.getAccuracy() + + "%", l.time + ": " + v.generateElapsedTimeString()}; + drawStatsWindow(g, endStats, wordsFont); } } + private void drawStatsWindow(Graphics g, String[] s, Font f) { + double topBarHeight = 0.075; + int UIHeight = (int) (getHeight() * 0.025); + int UITopBarHeightArea = (int) (getHeight() * topBarHeight); + int UIHeightArea = (int) (getHeight() * 0.9 * 0.5); + Rectangle rStatsWindow = new Rectangle((int) (getWidth() * 0.25), + UIHeight + UITopBarHeightArea * 2, + (int) (getWidth() * 0.5), (int) (UIHeightArea * 0.5)); + + Rectangle[] textLines = new Rectangle[s.length]; + + g.setColor(v.backgroundColor); + g.fillRoundRect(rStatsWindow.x, rStatsWindow.y, rStatsWindow.width, + rStatsWindow.height, 25, 25); + g.setColor(v.textColor); + int counter = (int) (rStatsWindow.getY() / (textLines.length - 1)); + for(int i = 0; i < textLines.length; i++) { + textLines[i] = new Rectangle((int) rStatsWindow.getX(), + (int) rStatsWindow.getY() + counter * i, + (int) rStatsWindow.getWidth(), counter); + drawCenteredString(g, s[i], textLines[i], f); + } + g.setColor(v.UIColor); + g.drawRoundRect(rStatsWindow.x, rStatsWindow.y, rStatsWindow.width, + rStatsWindow.height, 25, 25); + g.drawRoundRect(textLines[0].x, textLines[0].y, textLines[0].width, textLines[0].height, 20, 20); + } + /** * Takes the user's input and converts it into strings the length of * the test lines. @@ -177,7 +198,6 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { * Output: [window enter noon staa, bnelieve expect was, charity t tube] */ private void generateUserInputStrings() { - //v.userString = v.getString(); v.userInputStrings.clear(); int position = 0; v.typedLines = 0; @@ -239,8 +259,8 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { int keySizeY = getHeight()/12; Font keyboardFont = new Font("Dialog", Font.PLAIN, 24); Graphics gr = (Graphics2D) g; - gr.setColor(new Color(0,128,128)); - key keyboard[][] = k.kb; + gr.setColor(v.keyColor); + Key keyboard[][] = k.kb; for(int y = 0; y < keyboard.length; y++) { for(int x = 0; x < keyboard[y].length; x++) { @@ -248,22 +268,36 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { int leftPadding = (getWidth() - xLength)/2; int yLength = keySizeY * keyboard.length; int yPadding = (getHeight() - yLength); + Key key = keyboard[y][x]; + Rectangle keyRect = new Rectangle(leftPadding + x*keySizeX, yPadding + (y-1) + *keySizeY, keySizeX, keySizeY); + boolean specialChar = keyboard[y][x].specialChar; // Font size will be calculated by window size later. - if(keyboard[y][x].specialChar) + if(!specialChar) keyboardFont = new Font("Dialog", Font.PLAIN, 24); else keyboardFont = new Font("Dialog", Font.PLAIN, 9); - - gr.drawRect(leftPadding + x*keySizeX, yPadding + (y-1) - *keySizeY, keySizeX, keySizeY); - drawCenteredString(gr, keyboard[y][x].key, leftPadding + - x*keySizeX, yPadding + (y-1) - * keySizeY, keySizeX, keySizeY, keyboardFont); - if (v.pressedKeys.contains(keyboard[y][x].key)){ - gr.fillRect(leftPadding + x*keySizeX, yPadding + (y-1) - *keySizeY, keySizeX, keySizeY); + + gr.setColor(v.translucentKeyColor); + gr.drawRoundRect(keyRect.x, keyRect.y, keyRect.width, keyRect.height, 25, 25); + if (v.pressedKeys.contains(key.key)){ + gr.fillRoundRect(leftPadding + x*keySizeX, yPadding + (y-1) + *keySizeY, keySizeX, keySizeY, 25, 25); } + + // Checks if game is running + if (!v.timerRunning) { + if(v.isInRect(keyRect, v.mouseLocation) && !specialChar) { + String[] keyStats = key.getStats(l); + drawStatsWindow(g, keyStats, keyboardFont); + gr.setColor(v.translucentKeyColor); + gr.fillRoundRect(leftPadding + x*keySizeX, yPadding + (y-1) + *keySizeY, keySizeX, keySizeY, 25, 25); + } + } + gr.setColor(v.keyColor); + drawCenteredString(gr, key.key, keyRect, keyboardFont); } } } @@ -309,7 +343,7 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { * Sets the color of each letter according to its status */ public void drawTypingPrompt(Graphics g, String testText, - String userText, Rectangle rect, Font font) { + String userText, Rectangle rect, Font font) { FontMetrics metrics = g.getFontMetrics(font); int x = rect.x; int y = rect.y + ((rect.height - metrics.getHeight()) / 2) @@ -317,8 +351,6 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { g.setFont(font); g.drawString(testText, x, y); - g.setColor(new Color(128,128,128)); - char c = ' '; for (int i = 0; i < testText.length(); i++) { if(userText.length() <= i) // User hasn't typed @@ -334,10 +366,6 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { } } - public void drawCenteredRect(Graphics g, Rectangle rect) { - g.drawRect(rect.x, rect.y, rect.width, rect.y); - } - public void paintComponent(Graphics g) { super.paintComponent(g); draw(g); @@ -370,6 +398,7 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { else if (e.getKeyChar() != '\uFFFF' && v.gameRunning){ v.userString += e.getKeyChar(); v.timingHandler(); + checkKey(e.getKeyChar()); } String pressedKey = "" + e.getKeyChar(); if(!v.pressedKeys.contains(pressedKey)) { @@ -382,6 +411,43 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { refreshGraphics(); } + /** + * Checks if the pressed key is the correct key for test stats. + */ + public void checkKey(char input) { + int x = 0, y = 0; + char correctKey = v.getString().charAt(0); + if(v.userString.length() > 0 && v.userString.length() + <= v.getString().length()) + correctKey = v.getString().charAt(v.userString.length() - 1); + + // Finds the location of the correct key in the KB array + for(int i = 0; i < k.kb.length; i++) { + for(int k = 0; k < this.k.kb[i].length; k++) { + if(!this.k.kb[i][k].specialChar && this.k.kb[i][k].key.charAt(0) == correctKey) { + x = k; y = i; + } + } + } + + // Checks if user pressed the correct key + if(v.userString.length() <= v.getString().length()) { + boolean isCorrect = correctKey == input; + if(isCorrect) { + if(v.keyLastPressed == -1) { // If its the first key of the test + k.kb[y][x].update(isCorrect); + } + else { + Long elapsedTime = (System.nanoTime() - v.keyLastPressed)/10000; + k.kb[y][x].update(isCorrect, elapsedTime); + } + v.keyLastPressed = System.nanoTime(); + } + else { // updates key as being incorrectly pressed + k.kb[y][x].update(isCorrect); + } + } + } /** * Handles key released for keyboard graphics */ @@ -462,6 +528,10 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { } v.resetVars(); } + else if (v.isInRect(v.rWPM, e.getPoint())) { + v.darkMode = !v.darkMode; + v.generateThemes(); + } } @Override @@ -475,4 +545,16 @@ public class gameWindow implements KeyListener, WindowListener, MouseListener { // TODO Auto-generated method stub } + + @Override + public void mouseDragged(MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseMoved(MouseEvent e) { + v.mouseLocation = e.getPoint(); + } + } diff --git a/src/keyboard.java b/src/keyboard.java index f3b881b..4974227 100644 --- a/src/keyboard.java +++ b/src/keyboard.java @@ -1,3 +1,4 @@ +import java.util.ArrayList; public class keyboard { @@ -5,36 +6,160 @@ public class keyboard { {"tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\"}, {"caps lock", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "enter"}, {"shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "shift"}}; - public key[][] kb; + public Key[][] kb; + private variables v; - public keyboard() { + public keyboard(variables v) { + this.v = v; generateEngl(); } /** * Creates a Jagged Array of keys + * This later will read from a text file which the user can edit */ public void generateEngl() { - kb = new key[engl.length][]; + kb = new Key[engl.length][]; for (int i = 0; i < engl.length; i++) { - kb[i] = new key[engl[i].length]; + kb[i] = new Key[engl[i].length]; } - + for (int y = 0; y < kb.length; y++) { for (int x = 0; x < kb[y].length; x++) { - kb[y][x] = new key(engl[y][x]); + kb[y][x] = new Key(engl[y][x], v); } } } } -class key { +class Key { public String key; - public int keyID; + public int keyID, dataPoints; public boolean specialChar; + private variables v; + // Backspace needs to count as a miss or some way of preventing + // exploit + // Need to make arraylist of arraylists for the word modes + private ArrayList> correctStrikes = new ArrayList<>(); + private ArrayList> timesToStrike = new ArrayList<>(); + private ArrayList correctStrike = new ArrayList<>(); + private ArrayList timeToStrike = new ArrayList<>(); + int correctStrikeCounter, timeToStrikeCounter; - public key(String text) { + public Key(String text, variables v) { key = text; - specialChar = text.length() == 1; + this.v = v; + specialChar = text.length() != 1; + dataPoints = 100; + // Correct strike will be changed based on gamemode + for(int i = 0; i < v.wordModes.length; i++) { + correctStrikes.add(new ArrayList()); + timesToStrike.add(new ArrayList()); + } + // Initalize arraylists here } + + /** + * @param correctStrike + * @param timeToStrike + * + * Adds data to ArrayList. Checks if ArrayList is at max + * capacity. If so, it rolls over back to zero. + */ + public void update(boolean correctStrike, double timeToStrike) { + this.correctStrike = correctStrikes.get(v.wordMode); + this.timeToStrike = timesToStrike.get(v.wordMode); + timeToStrike = timeToStrike / 100000; // 10^5 Converts to seconds + if(correctStrikeCounter == dataPoints) + correctStrikeCounter = 0; + if(timeToStrikeCounter == dataPoints) + timeToStrikeCounter = 0; + + if(this.correctStrike.size() >= dataPoints) + this.correctStrike.set(correctStrikeCounter, correctStrike); + else + this.correctStrike.add(correctStrike); + + if(this.timeToStrike.size() >= dataPoints) + this.timeToStrike.set(timeToStrikeCounter, timeToStrike); + else + this.timeToStrike.add(timeToStrike); + correctStrikeCounter++; + timeToStrikeCounter++; + correctStrikes.set(v.wordMode, this.correctStrike); + timesToStrike.set(v.wordMode, this.timeToStrike); + } + + public void update(boolean correctStrike) { + this.correctStrike = correctStrikes.get(v.wordMode); + if(correctStrikeCounter == dataPoints) + correctStrikeCounter = 0; + + if(this.correctStrike.size() >= dataPoints) + this.correctStrike.set(correctStrikeCounter, correctStrike); + else + this.correctStrike.add(correctStrike); + + correctStrikeCounter++; + correctStrikes.set(v.wordMode, this.correctStrike); + } + + public String[] getStats(language l) { + correctStrike = correctStrikes.get(v.wordMode); + timeToStrike = timesToStrike.get(v.wordMode); + double counter = 0, accuracy = 100, averageTime = 0; + for(boolean temp : correctStrike) { + counter += temp ? 1 : 0; + } + if(correctStrike.size() > 0) { + accuracy = counter / correctStrike.size() * 100; + accuracy = v.round(accuracy, 2); + } + + counter = 0; + for(double temp: timeToStrike) { + counter += temp; + } + if(timeToStrike.size() > 0) { + averageTime = counter / timeToStrike.size() * 100; + averageTime = v.round(averageTime, 2); + } + + String[] toReturn = {l.keyStats + " - " + v.wordModes[v.wordMode] + " Words - " + key, l.accuracy + ": " + accuracy + "%", l.averageTime + ": " + averageTime + "ms", l.sampleSize + ": " + correctStrike.size()}; + return toReturn; + } + + public ArrayList getCorrectStrike() { + return correctStrike; + } + + public void setCorrectStrike(ArrayList correctStrike) { + if(correctStrike.size() == 100) { + correctStrikeCounter = 0; + + } + else { + correctStrikeCounter = correctStrike.size(); + } + this.correctStrike = correctStrike; + } + + public ArrayList getTimeToStrike() { + return timeToStrike; + } + + public void setTimeToStrike(ArrayList timeToStrike) { + if(timeToStrike.size() == 100) { + timeToStrikeCounter = 0; + + } + else { + timeToStrikeCounter = timeToStrike.size(); + } + this.timeToStrike = timeToStrike; + } + + + + // Need getters and setters for the two arraylists } diff --git a/src/language.java b/src/language.java index f367c76..ad19f1e 100644 --- a/src/language.java +++ b/src/language.java @@ -1,6 +1,7 @@ public class language { - public String score, words, testOver, wpm, raw, accuracy, time; + public String score, words, testOver, wpm, raw, accuracy, time, keyStats, + averageTime, sampleSize; // This will later load from a text file so uses can change languages public language() { @@ -20,5 +21,8 @@ public class language { raw = " (raw)"; accuracy = "Accuracy"; time = "Time"; + keyStats = "Key Stats"; + averageTime = "Average Time"; + sampleSize = "Sample Size"; } } diff --git a/src/runner.java b/src/runner.java index cb77af4..22a46cb 100644 --- a/src/runner.java +++ b/src/runner.java @@ -2,6 +2,6 @@ public class runner { public static void main(String[] args) { // TODO Auto-generated method stub - gameWindow w = new gameWindow(800, 600, "Typing Teacher Alpha v1.0"); + gameWindow w = new gameWindow(800, 600, "Typing Teacher Alpha v1.0.1"); } -} +} \ No newline at end of file diff --git a/src/variables.java b/src/variables.java index ab09154..86b5911 100644 --- a/src/variables.java +++ b/src/variables.java @@ -6,18 +6,20 @@ import java.util.Scanner; import java.awt.*; public class variables { - public int fps = 5, lines = 3; + public int fps = 10, lines = 3; public boolean debug = false; // Updating variables - private double WPM, score; + private double WPM, realWPM, score, accuracy; private String testString; private String[] testWord; private long startTime, endTime, elapsedTime; + public long keyLastPressed; public int typedLines, testWords, drawLines, wordMode; public int wordModes[] = {5, 10, 25, 50, 100}; + public Point mouseLocation; public String userString; public String WPMText, ScoreText; - public boolean gameRunning, testOver; + public boolean darkMode, gameRunning, testOver; public ArrayList pressedKeys = new ArrayList(); public ArrayList words = new ArrayList(); public ArrayList drawStrings = new ArrayList(); @@ -27,7 +29,8 @@ public class variables { // Finish test when last word is spelled right // calculate real WPM and actual WPM boolean repeatWords, timerRunning; - Color typingPromptColors[], backgroundColor, textColor; + Color typingPromptColors[], backgroundColor, textColor, UIColor, keyColor, + translucentKeyColor; Rectangle rOptions, rScore, rWPM, rTopBar; Random r = new Random(); @@ -37,16 +40,20 @@ public class variables { resetVars(); generateNewTest(); generateThemes(); + mouseLocation = new Point(0,0); } public void resetVars() { userString = ""; testString = ""; typedLines = 0; + accuracy = 0; generateNewTest(); timerRunning = false; elapsedTime = -1; + keyLastPressed = -1; WPM = 0; + realWPM = 0; score = 0; drawLines = lines; gameRunning = true; @@ -58,6 +65,7 @@ public class variables { wordMode = 1; testWords = wordModes[wordMode]; repeatWords = false; + darkMode = true; } /** @@ -65,15 +73,38 @@ public class variables { * 0 = not typed * 1 = correctly typed * 2 = incorrectly typed + * This will later load from a file the user can change. + * Values are set manually until feature added. */ + public void generateThemes() { - typingPromptColors = new Color[3]; - typingPromptColors[0] = new Color(128,128,128); - typingPromptColors[1] = new Color(0,0,0); - typingPromptColors[2] = new Color(255,128,128); - backgroundColor = new Color(255,255,255); - textColor = new Color(0,0,0); + if(darkMode) { + typingPromptColors = new Color[3]; + typingPromptColors[0] = new Color(150,150,150); + typingPromptColors[1] = new Color(255,255,255); + typingPromptColors[2] = new Color(255,128,128); + + backgroundColor = new Color(48,44,44); + textColor = new Color(120,236,220); + UIColor = new Color(8,132,132); + + keyColor = new Color(4,220,200); + translucentKeyColor = new Color(4,220,200,128); + } + else { + typingPromptColors = new Color[3]; + typingPromptColors[0] = new Color(128,128,128); + typingPromptColors[1] = new Color(0,0,0); + typingPromptColors[2] = new Color(255,128,128); + + backgroundColor = new Color(255,255,255); + textColor = new Color(0,0,0); + UIColor = new Color(8,132,132); + + keyColor = new Color(0,128,128); + translucentKeyColor = new Color(0,128,128,128); + } } public void loadWords() { @@ -101,6 +132,10 @@ public class variables { for (String x : testWord) testString += x + " "; testString = backspace(testString); + if(debug) { + testString = "aaa"; + testWord[0] = "aaa"; + } } /** @@ -173,25 +208,41 @@ public class variables { calculateGameStats(); } + /** + * Called at the end of a test + */ public void calculateGameStats() { if(timerRunning) elapsedTime = (System.nanoTime() - startTime)/10000; else elapsedTime = (endTime - startTime)/10000; - WPM = (double) userString.length() / 5 / elapsedTime * (60 / 0.00001); - WPM = round(WPM, 2); + if(elapsedTime != 0) { + WPM = (double) userString.length() / 5 / elapsedTime * (60 / 0.00001); + WPM = round(WPM, 2); + } + else + WPM = 0; } - public double calculateAccuracy() { + public void calculateEndGameStats() { int counter = 0; for(int i = 0; i < testString.length(); i++) { if(testString.charAt(i) == userString.charAt(i)) counter++; } - double toReturn = round((double)(counter)/testString.length(), 3); - return toReturn * 100; + accuracy = (double)(counter)/testString.length(); + accuracy = round(accuracy, 3) * 100; + + realWPM = (double) counter / 5 / elapsedTime * (60 / 0.00001); + realWPM = round(realWPM, 2); } + /** + * Rounds to specified decimal place + * @param number number to round + * @param places decimal places + * @return + */ public double round(double number, int places) { long shift = (long) Math.pow(10, places); double shifted = number * shift; @@ -222,6 +273,13 @@ public class variables { public Long getElapsedTime() { return elapsedTime; } + public double getRealWPM() { + return realWPM; + } + + public double getAccuracy() { + return accuracy; + } /** * Checks if a specified point is inside a rectangle