Friday, April 25, 2014

Stratagus and Wargus Binaries

You can download the binaries for Stratagus with my updates here.

In order to run them, you will also need to install libpng.  Follow the instructions in Appendix D of the final version of my thesis, soon to be posted in this blog.

Stratagus and Wargus Source Code with BARC

Here are the locations where you can download the full source code with my BARC modifications made.

Stratagus Download

Wargus Download

Wednesday, April 16, 2014

Saturday, March 29, 2014

Thesis Progress Report 3/29: Switching Languages and Redoing BARC

I discovered that in order for LuaJava to work properly, the Lua file must be started from the Java class (ie, it would not let me start a Java program from a Lua file).  Since I was intending to use Lua to start up the Java file, I can no longer use LuaJava as the interface between BARC and Lua.  As such, I am converting BARC to C++, which can be used directly with Lua.  I will write the BARC functionality all in C++, and the AI Lua files will call on the BARC methods at the end of the tactic files.

To accomplish this, I had to compile Stratagus from source code.  During the steps of exploring how I can accomplish linking the Lua and C++, I found I had to uncomment 3 lines which were blocking the Lua os, io, and package libraries from being loaded.  With these now included, I can execute command line commands and write out to files, allowing me to create an ai_log.txt file to log how my ai is working.  I was also able to compile it with the debug option on, so I now have a debug log to follow the flow of the program.

Saturday, March 22, 2014

Thesis Progress Report 3/22: Changes to the BARC/Wargus Interface

Some new information has come to light which changes how I have to handle the BARC/Wargus interface.  Originally, I thought I could just write out a Lua file during the run to control the AI, and have the Wargus engine execute it.  This file would be overwritten on each state change.  I then discovered that all of the AI Lua files are loaded on startup, so this idea wouldn't work.   I then decided to try having all of the files written out, and then using BARC to select which one was currently executing.  This idea gained some traction, until I discovered that Wargus only executes one AI script per game.

Finally,  I came up with a new way to handle controlling the AI with BARC.  Wargus will launch the AI with a custom script.  This script will tell the AI to execute the starting tactic, and then make a call to an interface.  The interface will take the game information (from the call of the Wargus AI), and then call advanaceState() on BARC.  BARC will return the next tactic to use to the interface.  The interface will then load the next tactic (which will be written out already, but not loaded), leading to Wargus executing the new file.  At the end of this file, the cycle repeats.  When it reaches the states which have nothing following them, the states will loop instead of calling on BARC.

Monday, February 24, 2014

Thesis Progress Report 2/24: BARC Updates Complete

Earlier today I finished adding in XML loading/saving in BARC.  I used XStream to save the building state list, and then reinitialize the list from the the XML file.  I implemented the Logger, and then fixed the performance updating at the end of the game.

The BARC framework is complete, but the real tactics need to be added in (Right now it is just a dummy comment).  I plan on trying to communicate with the authors of Learning To Win to get the tactics they used, but in the mean time I will be working on developing some simple tactics to be used.

The remaining steps are:
1. Explore Wargus APIs
2. Tell Wargus to use scripts produced by BARC
3. Find a way to get Wargus to send BARC the game information
4. Find a way to automate the games, recording game information.

Tuesday, February 18, 2014

Thesis Progress Report 2/18: Retrieval Complete

I have begun implementation of BARC, and I have successfully implemented the retrieval section of it.  It will enter exploration mode at the appropriate times, generate new cases, and select the best matching case given a game description.

Currently the BARC will start up from a text file with a description of the states and starting tactics.  It is able to retrieve cases and add new cases, along with printing out a .lua file.

The next steps in developing BARC are:

1. Add case performance evaluation
2. Add saving/loading case base from xml file
3. Add logger file to aid in debugging
4. Create and add actual tactics instead of placeholders which are currently used.
5. Write up documentation for interfacing with BARC

The next steps outside of BARC development are:
1. Explore Wargus APIs
2. Find a way to have Wargus send game state information to BARC
3. Find a way to have Wargus use lua script provided by BARC
4. Find a way to automate games

Tuesday, February 11, 2014

Thesis Progress Report 2/11: Switching to Custom CBR

After examining the Case-Based Reasoner (CBR) described in the "Learning to Win" article (David Aha et al.), I realized that the jCOLIBRI system would not be able to accomplish everything that I needed it to, and if it could it would take more time figuring out how to implement it than creating a new CBR system would actually take.

The Case-Based Reasoner described in Learning to Win is slightly different than others.  It is built around the idea that the game can be in 20 states.  These states are defined by what buildings have been constructed.  These states are organized in a lattice network based on what states can follow from a previous state.  The following figure is the lattice as shown in the Learning to Win article:
Each state has associated with it a set of tactics to be used in that state.  In the paper, there are 8 different tactics included in this (as indicated by a-h in the figure), each designed to counteract a specific opponent strategy.  I will be starting out with the same number of tactics as there are states which follow from the current state; so for instance state one will have 3 different tactics.  Implementing the other tactics is a later project goal, and I hope to be able to receive these tactics from the original authors.

The CBR has the responsibility of selecting which tactic to use while the game is in the current state.  Only one tactic will be used per state per play through.  Upon entering the state, the CBR must evaluate the current state of the game, and decide which tactic it will use while in the state.  Once it decides, the tactic does not change until it enters the next state.  

When the CBR first begins playing, it will be in an 'exploration mode'.  This means that during this time it will choose each tactic x amount of times to get an idea of how each one performs.  Once the exploration mode has finished, it will begin evaluating each state normally.

The normal workflow of the CBR selecting a tactic for a state is as follows:
  1. The CBR enters a new lattice state.
  2. The CBR receives information about the current state of the game (such as number of workers, combat units, etc.)
  3. The CBR uses the the current game state to query its database of cases associated with the current lattice state, and returns a set of the k closest matches using a k-nearest neighbor algorithm.  It does this by using the information about the game state as an 8 dimensional coordinate, and then checks it against the previous game state cases that were used by using the distance formula.
  4. The CBR selects the case with the highest performance rating.
  5. The CBR implements the tactic in the case.
  6. The CBR moves on to other cases.
  7. At the end of the game, the CBR saves the cases it used by either creating new cases if the exact cases did not exist, or updating the performance of the cases if they did exist.
If in step 4 the CBR finds that none of the nearest neighbors have a performance rating of above .5, the CBR will enter exploration mode for this step and try one of the least used tactics.

The jCOLIBRI CBR was not set up to handle cases in this way, and thus the idea for BARC (Barton CBR) was born.  BARC was designed to fit these criteria:

  1. Organize tactics and cases by state
  2. Save cases for later use
  3. Update the performance of a case when it has been selected
  4. Have an 'exploration mode' for when the CBR first starts.
  5. Enter 'exploration mode' when a none of the retrieved cases have a performance above .5.
  6. Create new cases.
  7. Retrieve k-closest cases from the knowledge base
  8. Select case with best performance from the k-closest cases.
  9. Send selected tactic to game.
  10. Select 1 case per state.
More specific design and implementation notes on BARC will follow in the coming posts.



3 Page Abstract

Here is my original 3 page abstract for my thesis.  Many things have changed over the past few weeks, and these will be updated shortly. To view the original PDF, click here.

Using Case-Based Reasoning to Improve Real-Time Strategy Game AI

Abstract
In recent years using real-time strategy (RTS) games as a test bed for Artificial Intelligence techniques has become increasingly popular.  RTS games allow an environment that is complex enough to present the AI with a sufficiently challenging situation. These games also allow an easy way to evaluate the effectiveness of the technique.  In this paper, I will build on the work of Aha et all, who in [1] used a custom built Case-Based Reasoner to govern an AI in Wargus: an open source RTS engine, that learns to play better as it plays more games.  I will be replicating their experiment with a different Case-Based Reasoner.  I will be working with jCOLIBRI, a freely available Case-Based Reasoner.
Problem Definition
My work will be on addressing four of the problems in RTS game AI: adversarial real-time planning, decision making under uncertainty, spatial and temporal reasoning, and resource management.  These can all be thought of as higher level decisions made by the AI.  My work will not deal with lower level AI, or individual unit AI (troop pathfinding, worker reaction to being attacked, etc).  Instead it will focus on the high level decisions regarding strategy. One of the largest problems with addressing the decision making of the AI is that the decision space, or number of possible actions, grows exponentially with the number of workers and troops.  The decision space can be defined with the following equation [1, p5]:
O(2^W*(A*P) + 2^T*(D+S) + B*(R+C))
Where: 
W = current number of workers 
A = assignments workers can perform 
P = average number of workplaces 
T = number of troops (fighters plus workers) 
D = average number of directions that a unit can move 
S = choice of troop's stance 
B = number of buildings 
R = average choice of research objectives at a building 
C = average choice of units to create at a building 

Shortly into a game, the amount of moves grows too large to even consider all options.  One of the first pieces of the problem of developing a high level AI is limiting the decision space.  

When the decision space is limited, the problem becomes choosing the best option given the current situation. The best option will be defined as the one that will increase the likelihood of AI attaining victory.  Case-Based Reasoning will be used to determine which option is the best at the time by pulling from a knowledge base of other situations faced by the AI.  

In solving the problem of which solution is best the AI would also solve the four problems mentioned above.  It would solve adversarial real-time planning by changing the AI's plan depending on the changing environment because as the game situation changes the AI may choose a different solution.  Decision making under uncertainty would be solved because by selecting a best solution at the time, the AI would be making a decision under uncertainty.  Selecting the best solution also solves spatial and temporal reasoning by learning when the best times to attack are, and what building patterns work best.  Resource management is also taken care of because the AI will select the option that is the best use of it's money at the time.  Thus, the important problem is what is the best decision for the AI to make at this time to put itself in a position to win the game?
Procedure
I will be using the jCOLIBRI Case-Based Reasoner as the system to govern the AI.  This system will be responsible for retrieving cases based on the current game state, proposing a solution, modifying the solution, applying the solution, evaluating the solution, and saving the case for future use.  Most of this will be handled by the built in system, but I will have to write how the cases are retrieved, what how they will be indexed, and what information should be stored.  For much of this, particularly the indexes, I will be referring to the work in [1].  
I will be using Wargus as the game to test my AI.  Wargus is an open source RTS game based on the Stratagus engine.  It is based on the game Warcraft II, and is what was used in [1].  The game is scripted in Lua, and since jCOLIBRI is written in Java, I will be using the LuaJava libraries to communicate between the two.
In [1], the TIELT system was used to communicate between the CBR and the game engine.  This system was able to plug in a game and AI module, and then evaluate it.  The TIELT system is no longer under development, and currently has issues running on my system, so I will be unable to use this.  Instead, I will need to write the interface between jCOLIBRI and Wargus so that Wargus can send state information to jCOLIBRI, and jCOLIBRI can send AI commands to Wargus.  Additionally, I will need a way to automate tests and record the results.
Bibliography
[1] David W. Aha, Matthew Molineaux, Marc Ponsen.  Learning to Win: Case-Based Plan Selection in a Real-Time Strategy Game. 2005.

Friday, January 17, 2014

TextAnalyzer: Adding a Lexicon Using Java's HashMap

This will be part 2 of the TextAnalyzer post.  In this one, we will create a second analysis method.

TextAnalyzer

We will be adding a lexicon method to the TextAnalyzer class.  The lexicon is a useful and interesting tool, as it will analyze the text and return how many times each word is used in it.  For instance, if we called the lexicon method on a file with "hello hello world" in it, the lexicon will return the following:
hello: 2
world: 1

To produce this information we will use Java's HashMap.  The HashMap allows you to associate a key (the word) with a value (number of times the word is used).  We will traverse the ArrayList, and at each item in the list we will see if it is already in the HashMap.  If the word is in the HashMap, we will update it's associated value to be the value + 1 (because we have found it one more time).  If it is not in the HashMap, we will add it in with the value being 1, because it is the first time we have seen it.  The code for the lexicon method is as follows:

public String lexicon(boolean writeOut) {
 //output will be stored in this StringBuffer
 StringBuffer lexStringBuf = new StringBuffer("Lexicon: \n \n");
 HashMap<String, Integer> lexMap = new HashMap<String, Integer>();
 //following for loop iterates through
 //each item in the ArrayList
 for (String s : contents) {
  //remove punctuation except "'" and 
  //convert string to lowercase
  s = s.toLowerCase();
  if (lexMap.containsKey(s)) {
   lexMap.put(s, (lexMap.get(s) + 1) );
  } else {
   lexMap.put(s, 1);
  }
 }
 //now lexMap contains lexicon
 //use an iterator to traverse the map
 //and add to the StringBuffer
 Iterator<String> iterator = lexMap.keySet().iterator();  
  
 while (iterator.hasNext()) {  
  String key = iterator.next();  
  String value = lexMap.get(key).toString();      
  lexStringBuf.append(key + ": " + value + "\n");
 }
 //stringbuffer now has full output
 //create new file if writeout is true
 if (writeOut) {
  try {
   writer = new PrintWriter(fileName + "-lexicon.txt", "UTF-8");
  } catch (FileNotFoundException | UnsupportedEncodingException e) {
   System.out.println("Error: " + fileName + " lexicon failed");
   e.printStackTrace();
  }
  writer.print(lexStringBuf.toString());
  writer.close();
  }
 //return string
 return lexStringBuf.toString();
}

The first portion creates a StringBuffer (making it easy to create a large string by simply calling append()), and the HashMap which will map Strings to Integers.

The for loop goes through each String in the contents ArrayList (from the first TextAnalyzer post), converts it to lowercase (to avoid The and the getting separately matched), and then adds it to the HashMap.  If the word is already in the map, it updates the value to value+1, otherwise it adds it to the map with a value of 1.

With the HashMap filled out, the Iterator portion is where we traverse the HashMap to get all of the key/value pairs and add them to our StringBuffer.  The Iterator is initialized to the Iterator of the key set of our HashMap.  This means that the Iterator will be traversing the key set.  The key set is the set of keys from our HashMap; in this case the set of words in the HashMap.  While there is a key, it grabs the key and the associated value, and then adds it to our StringBuffer.  

The format of writing to the file will be improved in a later post to list the most common words first, and to give the percentage of the total word count that each word makes up.  But for now we will stick with the simple example.

After creating the string, we again face the choice of simply returning it or writing it to a file and returning it, which is again handled by the if (writeOut) statement.  This will complete the lexicon() method for now!


Tester

It's now time to update our Tester.  In the main method, add the following line after the creation of myAnalyzer:

myAnalyzer.lexicon(true);

Now go ahead and run your program and see what you get!
For the Moby Dick example, the output should look something like:

Lexicon: 
brave: 18
morgana: 1
approving: 1
champions: 1
unaccountable: 16
cripple: 3
writerof: 1
jew: 1
...... and so on!

There you have it!
Again, the lexicon will be improved later, but will focus on using Comparators for custom classes.

TextAnalyzer: Reading and Writing Text Files in Java

This will be a quick tutorial on text file input/output in Java.  For this project I will be creating a short program that will take text files and return a file of statistics such as number of times each word occurs, number of words, etc.  The text files I will be using come from Project Gutenberg, an online project which allows you to download many books as text files.

This project will be constructed with 4 classes: Tester, FileTextReader, TextAnalyzer
- Tester will be the class in which we run all of the tests on the other files.
- FileTextReader will be responsible for reading the file and returning an ArrayList of strings of the contents.
- TextAnalyzer will take the file name and call on FileTextReader to get the ArrayList of file contents.  It will then perform the analysis on them, creating a new file summarizing the findings.


FileTextReader

First we will begin with the constructor. The FileTextReader will need a File and a Scanner.  The Scanner will be used to parse the text in the given File.  The File will be initialized in the constructor from a file name like so:

private File fileToRead  = null;
private Scanner myScanner = null; 

public FileTextReader(String fileName) {
fileToRead = new File(fileName);
}

Next we will write the readText() method of the FileTextReader class, which will split the contents of the file up by spaces and return them in an ArrayList of strings:

public ArrayList<String> readText() {
 ArrayList<String> textList = new ArrayList<String>();
 String toAdd = null;
 try {
  //get scanner
  myScanner = new Scanner(fileToRead);
  //while there is a token to take
  while(myScanner.hasNext()) {
   //if token is made of legal chars
   toAdd = myScanner.next().replaceAll("[^a-zA-Z\' ]", "");
   if (toAdd.length() > 0) {
    //add it!
    textList.add(toAdd);
   }
  }
 } catch (FileNotFoundException e) {
  System.out.println("Error: File not found");
  e.printStackTrace();
 } 
 return textList;
}

readText() uses the Scanner to parse through the input file and save the contents to an ArrayList.  The next() method of the Scanner object will return the next sequence of characters up to a space in the input file.  For instance, if the input file was "foo bar", the first call to next() would produce "foo", and the second would produce "bar".  The hasNext() method returns true if there is a token left in the input file, and false otherwise.  Thus we used the hasNext() method as the test case for the while loop, making sure we grabbed each token of the file and placing it into the ArrayList.

replaceAll() is a String method which replaces any characters matching the regex in the first parameter with the character in the second parameter.  In this case, we are replacing any character that is not a letter or an apostrophe with an empty space.  We then check to ensure the length of the string is greater than 0.  This ensures that stray bits of characters such as "----" or "1." do not get added into the text file.  This will keep us from getting false word counts.

This will complete the FileTextReader class.


TextAnalyzer

Next we will write the TextAnalyzer class.  This class will be responsible for analyzing the ArrayList generated in the FileTextReader class.  To begin, we will create the following constructor:

private ArrayList<String> contents = null;
private String fileName = null;
private PrintWriter writer = null;

public TextAnalyzer(String fName) {
 contents = (new FileTextReader(fName)).readText();
 fileName = fName;
}

The TextAnalyzer takes a file name, and then stores the ArrayList of the contents of that file by creating a FileTextReader object and calling readText() on it.  This saves time later, because if we store this information at the beginning, we will not have to recalculate it every time we want to perform a different analysis.  Next, it saves the file name, which will be used to name the text files we will output.

With the constructor complete, it's time to write our first analysis method.  For now, we will make a simple word count.

public String wordCount(boolean writeOut) {
 String count = "Word count: " + contents.size();
 if (writeOut) {
  try {
   writer = new PrintWriter(fileName + "-word-count.txt", "UTF-8");
  } catch (FileNotFoundException | UnsupportedEncodingException e) {
   System.out.println("Error: " + fileName + " word count failed");
   e.printStackTrace();
  }
  writer.println(count);
  writer.close();
 }
 return count;
}

This method begins by storing a string with the current word count.  The method gets the word count by using the size() method of the ArrayList contents which was initialized in the constructor.  The portion that creates a text file output of the word count is encased in an if statement because we may not always want to produce an output file.  For instance, later we will write a method which will perform all of the analysis methods we have created on a given file.  For this use, we do not want all of the files generated; rather we will want all of the information placed within a single output file.

To create the output file, we create a PrintWriter.  The parameters used to create the PrintWriter are the name of the output file (be sure to include the extension), and the format.  Once the PrintWriter has been successfully initialized, we can write to the output file using methods such as print() and println().  The difference between these methods is println() will create a new line at the end of the input, where as print() will not terminate the line after printing the string.

Now that we have a basic method to analyze the text, it's time to create the Tester and make sure everything is good so far.


Tester

The tester will be a simple class that will run our code.  It does not need a constructor; we will only be writing a main() method for it.  We will also need to write a short test text file.  For the initial test, I just made a small file 'test.txt' that contained the text "hello world".  Save this file in the same folder as your code.  The Tester class will only need this in it for now:

public static void main(String[] args) {
 TextAnalyzer myAnalyzer = new TextAnalyzer("test.txt");
 myAnalyzer.wordCount(true);
}

Now when you run the Tester file, you should see a new file  'test.txt-word-count.txt' in your directory with the contents:
Word count: 2

Running this program on a download of Moby Dick from Project Gutenberg gives a word count of 214872.

Congratulations! Our initial text analyzer works.  In following posts more analysis tools will be added, along with the option of analyzing entire folders at a time instead of single files.

Thursday, January 16, 2014

Enable Debugging Settings on Android 4.2 and later

In Android 4.2 and later, the debugging settings options are hidden.  To unlock them, follow these steps:

  1. Go to the settings screen.  
  2. Under the 'more' tab, select 'About Device'
  3. Find 'Build number' and tap it 7 times (after a few times text will pop up telling you how many to go)
  4. Go back to the settings menu, and 'Developer Options' will be right above 'About Device'

Wednesday, January 15, 2014

Setting Up LuaJava to Integrate Lua and Java in Windows

LuaJava is a library used to interface Lua and Java files.  Once installed, Lua files can call on Java files, and vice versa.  This can be useful for scripting and for integrating files that are coded in the two languages.  Installing LuaJava for Windows is very simple.  This tutorial assumes you already have Java and Eclipse installed. I used a 32 bit version of Java (jdk 1.7.0_45).

First, download LuaJava from here.  Select luajava-1.1-win32-lua51.zip.  Once it has been downloaded, extract everything to a new folder.

Next, open Eclipse and create a new Java project.  Name the project whatever you like; I went with 'LuaJavaTest'.  Once you have created your project, right-click on it in the 'Package Explorer' and select 'Properties' (the last item). This will bring up the Properties pane.  On the left side, select 'Java Build Path'.  Now you will have a box with 4 tabs on the right side.  Select the 'Libraries' tab.

There are two things we need to do here.  First, click 'Add External JARs'.  In the dialog that follows, navigate to the location where you extracted the download to and select the 'luajava-1.1' file.

Once you have selected the library, we now will add it to the Native Library Location.  Back on the Properties pane, click the triangle next to 'JRE System Library'.  When it expands, select 'Native library location', and click edit.  In the dialog that follows, click External Folder, and change it to the directory in which you extracted the laujava-1.1.jar.  Confirm these changes and exit out of the Properties pane.


With everything set up, all that's left is to test it!
(This test comes from the LuaJava examples page.)

First things first, we'll make the lua file.  Right-click on the project and create a new file.  Name the file 'hello.lua', and place the following in it:

print("Hello world from lua!")

Next, we will create the Java file.  Create a new class 'hello'.  In hello, put this source code:

public class Hello {

public static void main(String[] args)
 {
   LuaState L = LuaStateFactory.newLuaState();
   L.openLibs();
   
   L.LdoFile("hello.lua");
   
   System.out.println("Hello World from Java!");
 }
}

Now run the project! When the option comes up on whether to run your file or the console in the LuaJava files, choose to run your file.  You should receive the following output on your console in Eclipse:

Hello World from Java!
Hello world from lua!


If you have any questions, feel free to email!

Monday, January 13, 2014

LoggerTest: Java Log Files

Logging is a way to track what is going on in a program throughout execution.  It can be extremely helpful in debugging by showing the order of execution and values at various points in the program.   Java can output the log to xml and text files.  For this project, we will output the log to a text file.

First, we need to create a Formatter to tell Java how to output the log entries.  In this example, we will create a simple class LogFormatter and tell it that we want it formatted with the date, time, level (importance of message), and then the message.

public class LoggerTest {
private static class LogFormatter extends Formatter {
             private DateFormat dateFormat = 
                  new SimpleDateFormat("MM/dd/yyyy HH:mm");
        
             public String format(LogRecord record) {
                     StringBuffer sb = new StringBuffer();

                     //Get date from record 
                     //and append to StringBuffer
                     Date date = new Date(record.getMillis());
                     sb.append(dateFormat.format(date));
                     sb.append(" ");

                     // Get level and append to StringBuffer
                    sb.append(record.getLevel().getName());
                    sb.append(": ");

                    // format message and append to StringBuffer,
                    // then add new line
                    sb.append(formatMessage(record));
                    sb.append("\n");

                     return sb.toString();
            }
       }
}

The StringBuffer will hold the String that we will build and return in the format method.  First, the date is retrieved from the record and formatted by our SimpleDateFormat.  Next, the level name is retrieved and appended onto the String.  Finally, the message is formatted by the default Formatter guidelines, and appended onto the string.  The String is then returned.

Now that we have a Formatter to format each of our entries, it's time to create our Logger.  We will create a Logger data field and initialize it in the constructor for the LoggerTest class.  This is demonstrated below.  The code is to be included in the LoggerTest class:

private Logger captainsLog = null;
public LoggerTest() {
try {
//create file
FileHandler textLog = 
                     new FileHandler("Captain's-Log.txt");
//set formatter
textLog.setFormatter(new LogFormatter());
//set levels which will be recorded in log
textLog.setLevel(Level.ALL);
//get logger
captainsLog = Logger.getLogger("Captain's Log");
//attach FileHandler
captainsLog.addHandler(textLog);
//set Logger level
captainsLog.setLevel(Level.ALL);
} catch (SecurityException | IOException e) {
// TODO Auto-generated catch block
System.out.println("Log creation failed!");
e.printStackTrace();
}
//log an info level message
captainsLog.info("Initializing Captain's Log");
//log a FINE level message
captainsLog.log(Level.FINE, "First entry successful!");
}

The constructor creates the file we will be writing to and then tells it to use the Formatter we created above.  The setLevel call tells the file what importance level to record.  The Level.ALL says that we want to record all messages in this file.  If the FileHandler receives a message which is below the FileHandler's level, it will ignore the message.

Next, we create the Logger. the Logger.getLogger() method creates a new Logger with the given name.  If a Logger already exists with the given name, it returns the already created Logger.  Once we have the Logger, we add our handler to it so that it knows where to write to.  Next, we set what level of messages will be reported by this Logger.  The Logger may have multiple Handlers and will send its messages to all of them simultaneously.  This is a way to filter what messages you want sent to any of the Handlers.  This could be useful if you wanted multiple Handlers for different levels.  You could have the Logger send all messages, and then the individual Handlers could filter which they want to record. 

The final two lines log initial statements saying that the log has been initialized.


There are two quick steps left.  The first is to create a main method in our LoggerTest class so that we can run it, and then to add a few methods to demonstrate using the Logger in separate methods.  

First, we will create the main method.   The main method is the method which will be run whenever you select to run the Java application.  Our main method is as follows:

public static void main(String[] args) {
LoggerTest myTester = new LoggerTest();
}

This is currently very simple.  Right now it just creates an object of type LoggerTest; but since the constructor initializes the Logger and creates the log file, this is enough to ensure that everything is working correctly so far.  Now when you run this file, a "Captain's Log.txt" file should appear in the same directory, and should contain the text:
01/13/2014 18:16 INFO: Initializing Captain's Log
01/13/2014 18:16 FINE: First entry successful!


Now that we see our log file is working successfully, the final step is to update the log in several different methods so that we can ensure that our log works across the whole file.  After all, a log which only works in the constructor isn't much help!

We will create two quick methods: foo and bar.  They will be in the LoggerTest class as follows:

public void bar() {
captainsLog.log(Level.FINE, "In bar method!");
captainsLog.info("Exiting bar...");
}

public void foo() {
captainsLog.log(Level.FINE, "In foo method!");
captainsLog.info("Exiting foo...");
}

Next, we will add a call to these methods in the main method.  Add these lines in after the creation of myTester in the main method:

myTester.foo();
myTester.bar();

Now when you save and run the file, you will see the "Captain's Log.txt" file in the same directory, and its contents will be:
01/13/2014 18:30 INFO: Initializing Captain's Log
01/13/2014 18:30 FINE: First entry successful!
01/13/2014 18:30 FINE: In foo method!
01/13/2014 18:30 INFO: Exiting foo...
01/13/2014 18:30 FINE: In bar method!
01/13/2014 18:30 INFO: Exiting bar...


There you have it! Creating simple text log files in Java.  Feel free to email me for the full source code.

-Dan


Friday, January 3, 2014

Compiling Java from Command Line with Multiple Jars

This blog is largely just a set of stuff to help me remember how I fixed problems in the past when working on my thesis and other projects.  If it helps out anyone else along the way, then that's super chill!

First up, compiling a Java file from the command line on Windows with multiple jar files.

1. Change directory to wherever your java file is located using the cd command like so:
  • >cd path\to\source\folder

2. Once you are in the source folder, use the following command to set the path.  This will allow your computer to find the Java commands in the next step.

  • >set path=%path%; "path\to\java\bin"
On my computer, the specific command is:

  • >set path=%path%; "C:\Program Files\Java\jdk1.7.0_45\bin"
3. With the path set, all that's left is to compile the file! To compile the file, we will use the javac command and set the -classpath parameter  to where our .jar files are (in this case, I have a folder of jar files).  In order to include all .jar files in the folder, the folder path will end in *.

  • >javac -classpath "path\to\jar\folder\*" FileToCompile.java
An example run of this would be:

  • >javac -classpath "C:\Users\owner\jars\*" HelloWorld.java


Hope this helps!