/* * Expressions.java * * Version: * $Id$ * * Revisions: * $Log$ */ import java.util.Random; import java.util.Scanner; /** * An application that allows people to practice calculating * simple Java expressions with precedence. * */ public class Expressions { // set up the numbers used in expressions // number of segments should always be odd private final static int MAX_SEGMENTS = 9; private final static int MINIMUM_NUMBER_RANGE = 0; private final static int MAXIMUM_NUMBER_RANGE = 9; //Display options private final static int CONTINUE=0, EXPRESSION=1, RESULT=2, SCORE=3, END=4; //Set up score and timer private int score = 0; private long startTime=0, totalTime=0; // variables for the data input private Scanner input = new Scanner( System.in ); // random will be able to choose things randomly for us for the expressions private Random random = new Random(); // Variables used in the program private String expression; private String result = "0"; /** * The main program. * * @param args command line arguments (ignored) */ public static void main( String[] args ) { Expressions expressions = new Expressions(); expressions.runGame(); } /** * In order to start a sequence of expressions this method must * be called. * */ public void runGame( ) { boolean done = false; startTime = System.currentTimeMillis(); while( !done ) { if (!done ) { expression = makeExpression(); display( EXPRESSION ); result = solveExpression( expression ); String userResult = getUserInput(); if (userResult.matches(result)) { score += 10; } // always display the correct result in the end display( RESULT ); } display( CONTINUE ); // we see if we want to go again done = wantToQuit(); } // print out completed information display( END ); } //Prints output based on the type of text that needs to be displayed private void display( int typeToDisplay ) { totalTime = (System.currentTimeMillis() - startTime) / 1000; switch(typeToDisplay){ case 0: System.out.println(); System.out.println("--------------------------------------------------------------------"); System.out.print("Type a 'q' to quit or any other key to continue: "); break; case 1: System.out.println(); System.out.println("--------------------------------------------------------------------"); System.out.println( "Calculate the value of the following expression: " + expression); break; case 2: System.out.println(); System.out.println("The answer is: " + result ); System.out.println("Your score is: " + score + " points in " + totalTime + " seconds."); break; case 3: break; case 4: System.out.println(); System.out.println("--------------------------------------------------------------------"); System.out.println("FINAL SCORE: " + score + " points in " + totalTime + " seconds."); System.out.println("--------------------------------------------------------------------"); break; } } /** * A simple routine to get input from the user. * * @return The answer to an expression. Should be numeric. */ private String getUserInput( ) { System.out.print("Enter your answer: "); return(input.nextLine()); } /** * @return True if the user hits the 'q' key to quit and false otherwise. */ private boolean wantToQuit() { return( input.nextLine().equalsIgnoreCase("q") ); } /** * A method that makes expressions of a certain length with certain predefined * operators from a range of integer numbers. * * @return An expression in string form without spaces separating elements. */ public String makeExpression( ) { // We use a stringbuffer as string will keep making new objects if we change it whereas // stringbuffer allows us to append new characters onto the end of it. StringBuffer segments = new StringBuffer(""); // we keep a count of where we are so we know when to put in operators vs. numbers. int currentSegment = 0; int segmentsForThisExpression = random.nextInt( MAX_SEGMENTS-3 ) +3; if (segmentsForThisExpression % 2 == 0 ) { segmentsForThisExpression++; } // We're not going to handle creating divIndex by zero situations in here. // They will be handled by those that solve the equation. while ( currentSegment < segmentsForThisExpression ) { if ( currentSegment % 2 == 1 ) { // for statements that must take two arguments, odd numbers are always operators // we're only going to put the '-' sign in as a unary operator when // we're generating numbers int operator = random.nextInt( 5 ); String nextOperator = "*"; switch( operator ) { case 1: nextOperator = "/"; break; case 2: nextOperator = "%"; break; case 3: nextOperator = "+"; break; case 4: nextOperator = "-"; break; } segments.append( nextOperator ); } else { // The "" is there to turn the whole expression into a string automagically for us rather // than a number type. String nextNumber = "" + ( random.nextInt( MAXIMUM_NUMBER_RANGE + Math.abs(MINIMUM_NUMBER_RANGE) ) - Math.abs(MINIMUM_NUMBER_RANGE) ); segments.append( nextNumber ); } currentSegment++; } return segments.toString(); } /** * Solves an expression of arbitrary size that consists of operators that take two arguments. * The one operator that doesn't take two operators is the unary "-" operator to indicate * a negative number. Precedence is taken into account in solving the problem. Special consideration * is given to equations that are undefined (ex. divide by zero). * * @return A string representing the solution to the expression. If there is a divide by zero in the * expression then the string will say "undefined". */ public String solveExpression( String expression ) { boolean done = false; // The current string keeps our working version of the answer. String current = expression; // The current and eventually final numerical answer. int result = 0; // The index of the next operator we have to consider. Default is 0 which will mean we // haven't found a real operator since all real operators need arguments on both sides of them. int minimumIndex = -1; int middle = -1; int left = -1; int right = -1; // while we're not done solving... while ( !done ) { middle = -1; if ( current.contains( "*" ) || current.contains( "/" ) || current.contains( "%" ) ){ // get the index in the string of the first occurence of these operators since java will do them // from left to right int multIndex = current.indexOf("*"); int divIndex = current.indexOf("/"); int modIndex = current.indexOf("%"); // if there's a valid index for mult then pick it else take a valid index for div else take one for mod. middle = (multIndex != -1) ? multIndex : ( (divIndex != -1) ? divIndex : ( (modIndex != -1) ? modIndex : -1 ) ) ; // Let's make sure we have a valid index before going further... if (middle != -1) { // One of the indices is valid so let's make sure it's the first if (modIndex > -1 && multIndex > -1 && divIndex > -1) { middle = Math.min(Math.min(modIndex,divIndex),multIndex); } else if ( divIndex > -1 && multIndex > -1 ) { middle = Math.min(multIndex,divIndex); } else if ( modIndex > -1 && multIndex > -1 ) { middle = Math.min(multIndex,modIndex); } else if ( modIndex > -1 && divIndex > -1 ) { middle = Math.min(divIndex,modIndex); } // Now perform arithmetic and check to make sure we're not dividing or mod'ing by 0 if ( current.charAt( middle ) == '*' ) { left = getNumberAt(current, middle - 1); right = getNumberAt(current, middle + 1); result = left * right; } else if ( current.charAt( middle ) == '/' ) { left = getNumberAt(current, middle - 1); right = getNumberAt(current, middle + 1); // since divide by 0 is normally an error condition we're going to return // in the middle of the method if (right == 0) { return "undefined"; } result = left / right; } else if ( current.charAt( middle ) == '%' ) { left = getNumberAt(current, middle - 1); right = getNumberAt(current, middle + 1); // since divide/mod by 0 is normally an error condition we're going to return // in the middle of the method if (right == 0) { return "undefined"; } result = left % right; } } } else if (current.contains( "+" ) || current.contains( "-" )){ // next precedence int a = current.indexOf("+"); int b = getFirstSubtractionIndex(current); middle = a; //NOTE: HELP ON THE MINS IN THIS SECTION WAS OBTAINED FROM GOOSE AND ADAM if (a > -1 && b > -1 ) { middle = Math.min(a,b); } else if ( b > -1 ) { middle = b; } if (middle != -1) { if ( current.charAt( middle ) == '+' ) { left = getNumberAt(current, middle - 1); right = getNumberAt(current, middle + 1); result = left + right; } else if ( current.charAt( middle ) == '-' ) { left = getNumberAt(current, middle - 1); right = getNumberAt(current, middle + 1); result = left - right; } } } minimumIndex = middle; int leftSide = left; int rightSide = right; if (minimumIndex >= 1) { int rightSize = new Integer(rightSide).toString().length(); int leftSize = new Integer(leftSide).toString().length(); if (minimumIndex-leftSize != 0 && minimumIndex+rightSize!=current.length()-1) { current = current.substring(0, minimumIndex- leftSize ) + result + current.substring(minimumIndex + rightSize+1, current.length()); } else if (minimumIndex-leftSize != 0 ) { current = current.substring(0, minimumIndex-leftSize) + result ; } else if (minimumIndex+rightSize!=current.length()-1) { current = result + current.substring(minimumIndex +rightSize+1, current.length()); } else { current = result + ""; } } if (current.length() <= 1 || minimumIndex == -1 ) { done = true; } minimumIndex = -1; } return ""+result; } /** * Returns a number that has an element in index value. The number may * extend after or before the index chosen. * * @param expression The expression to get a number from. * @param index The index of a piece of the number. * @return The actual number obtained from the numeric pieces around and inside of index or * 0 if there was a problem with the expression and index given. */ public int getNumberAt( String expression, int index ) { int number = 0; int startIndex = index; int endIndex = index; try { // find the start of the number for (int i=index; i>=0; i--) { char current = expression.charAt( i ); if ( Character.isDigit( current ) ) { startIndex = i; } else if (current == '-' && isUnary( expression, i ) ) { startIndex = i; break; } else { break; } } // make sure we skip over unary op's at the beginning of a number // in order to get to the end of it if ( expression.charAt(index) == '-' && isUnary( expression, index ) ) { index++; } // find the end of the number for (int i=index; i