Programming And Problem Solving Mediator Software Guide Srikant Krishna srikant@cs.columbia.edu ----------------------- 1. General Architecture The PPS software requirements consisted of generating simulators and interactive game models for four projects. The projects covered a variety of topics, notably computational geometry. Additionally, the software was to be delivered according to a rigid schedule to enable smooth progression of the course throughout the semester. Some degree of Java proficiency was expected from the students, with the awareness that considerable variance may exist. Given this overall scenario, three primary design features of the software were to be realized: * Flexibility * Code reuse * Minimal player implementation barrier The objective was to develop software components that model a range of different topics (flexibility), with minimal code development (reuse). Additionally, because the primary aim of the course was not to teach advanced Java programming, facile player implementation was incorporated as a design goal. In order to achieve these objectives, each project was subdivided on a source-code level into two packages: a static UI module and a project-specific component. Additionally, a similar player interface was developed and utilized for each project. The totality of the PPS software consists of five packages: * User-Interface (Almost identical across all projects) * Four project-specific packages (Bidding Wars, Mirrors, Rectangles, Obstacles) The subsequent sections will discuss the particulars of these components and others in greater detail. One final, but very important, aspect of the software should be emphasized: it is purely interface-driven. In other words, there is a complete absence of source-level inheritance. This type of architecture is confluent with the stated design goals, as will be evident. 2. User Interface The user interface (UI) was designed to be consistent and virtually identical across the projects. This enables the developer to focus solely on the game model, and avoid reimplementation/testing of basic UI functionality. In order to avail the UI package, game model packages simply import the required class files at a source-code level: import ui.*; Consistent with the style of the software, the user interface itself is specified as a contract (thus, a user-interface interface). The following is the standard UI interface that must be implemented by all user-interface objects. The version that was deployed with the software was a graphical user-interface (GUI), but any type of I/O component (including one that utilizes only the standard streams) that fulfills the contract is a valid UI component. package ui; public interface IFCUI { public void register(IFCModel __model) throws Exception; public void refresh() throws Exception; public void reset() throws Exception; public void configure(IFCConfiguration __config) throws Exception; public void persist(Class __class, Object __obj) throws Exception; public Object persist(Class __class) throws Exception; public void print(String __str) throws Exception; public void println(String __str) throws Exception; public void println() throws Exception; } The UI is the backbone of the entire system and is the main() class. It is reponsible for instantiating the other components of the software, including the game model and a messaging component (MessagePanel). The sequence of events that transpires during the initial execution of the software is: 1) The UI Object is created by the JVM, but does not display anything yet. 2) The UI Object instantiates a Game Model Object (pertaining to a specific Project). 3) A public method in the Game Model Interface, register(), is called, with the initial UI object passed as a parameter. 4) The Game Model associates with the UI, and dispatches the callback method register() on the UI object. 5) The UI Object then proceeds to draw itself and create any other required components. The current game model is also registered with these components (to enable certain features such as the built-in tournament facility). 6) The system is now ready to respond to user events. The GUI deployed with the system presents several primary areas of functionality to the user. First, a large button panel on the top allows easy access to most of the primary functionality of the system, such as stepping through turns in the game model or playing it out. In addition, the ability to toggle a log mode, and to save and restore the full game state can be accomplished through the buttons comprising this panel. A text logging panel is also present which is used as to present text messages to the user. The logging facility can also write to a file, thereby permitting convenient game logs to be generated during development or tournaments. When this log is viewed, the text is prefixed by line numbers. The largest portion of the GUI is the viewing panel, which graphically displays the game state. The configuration panel is the next largest component, displaying the current game setup (such as the number of rounds remaining, player identities, scores, gamefile, etc.). This information can be changed through a tab in the configuration panel in which the appropriate changes are made and then the "New Game" button is pressed. The components of the selection lists are defined through a properties file (see below). The register() methods establishes an association between a game model object (IFCModel) and the UI object. This is necessary so that user-initiated events can be transduced to the game model. The print() and println() methods are delegated to the messaging facility (MessagePanel), but can be rewritten to pipe data to an arbitrary stream. The refresh() method is employed in certain situtations where a full screen update is required (after a series of Swing operations). The persist() methods are utilized to enable persistent (or learning) player objects that are carried over from game to game. 3. Game Model Interface The game model interface is a central component of the software. It is actually a member of the user interface package -- permitting user interface modules to generalize across all game models and allow code reuse. The methods that are to be implemented are very generic in nature. First, several export methods specify graphical components that are to be incorporated in the UI. Notably, the view panel and the control panel are both actually generated and exported by the game model. In addition, a configuration object is also exported. The purpose of the configuration object is to store the game configuration between games. Thus, if a player has setup a game in a certain manner, this information should be stored by the UI module, since the game model is destroyed upon each new game invocation. Thus, the configuration is passed to the ui before the model is destroyed, thereby allowing the user to create a new game with an identical configuration as the previous one. In addition to exporting graphical components and configuration information, a registration method is included to associate the user-interface with the game model. This permits the game model to access exposed methods in the user-interface, such as storing of configuration, players, and user interface methods. The game model also exposes a name method, which simply sets the title of the main UI frme. The final method in the game model interface is the run method, which takes a generic tournament description object as the parameter. Tournaments are more fully described in a section with the identical name below. The following is the game model interface code: package ui; import java.io.Serializable; import javax.swing.*; public interface IFCModel extends Serializable { public JPanel exportViewPanel() throws Exception; public JPanel exportControlPanel() throws Exception; public IFCConfiguration exportConfiguration() throws Exception; public JButton[] exportTools() throws Exception; public JMenu exportMenu() throws Exception; public void register(IFCUI __UI) throws Exception; public String name() throws Exception; public void run(IFCTournament __tournament) throws Exception; } 4. State Engine For many of the projects, it is necessary to transition the game model through a series of discrete states. This is required not just to slow down the simulation (as in say, the BiddingWars project), but rather to enable the user to input moves. Therefore, a state engine is a requirement for those game models in which human input is expected. As an example, consider the states and transitions (in successive lines, by default) permitted in the BiddingWars game model: _CWAITING: Waiting for human input. This is a terminal state unless the user interaction is received. _CSELECTING: The card that will be bid on is being selected from the deck. _CBIDDING: Bids from each player (both human and autonomous) are being gathered. _CPROCESSING_BIDS: An individual bid has been received, and is being processed. Leads to the _CBIDDING or the _CCOMPARING states. _CCOMPARING: The cards are being compared to determine the winner of the bid. _CDECIDING: The scores are being compared to determine the winner of the game. _CFINISHED: Game over. In this model, the _CWAITING state specifies that the game model is waiting for user input. The game model is forever in this state unless user input is received, and other user actions (such as step()) are ignored. The other states in the system exist to facilitate visualization of the game sequence among each of the players. This is a less stringent concern than enabling human input. 5. Inner Classes In Java (and several other object-oriented languages), inner classes, or classes declared within the scope of another class, are permitted. Inner classes have full access to all of the fields and members of the enclosing class, including those with private access. Furthermore, multiple levels of nesting are permitted. Strictly, an inner class is a static nested class, but this distinction is not important for our purposes. As a brief note, suppose there was an encapsulating class called A and a nested class called B. If, inside B, a programmer wished to dispatch the "doSomething()" method in the encapsulating class, it would be achieved through the following code fragment: A.this.doSomething() Therefore, the convention .this. is used to reference the encapsulating class(es). Nested classes is used in several ways to enhance the efficiency and organization of the code. First, in Swing container code, inner classes are used to logically and physically establish a barrier between independent object model entitities. For example, in the Mirrors project, the ViewPanel class is composed of two inner classes, the LightPanel and the AdjustmentPanel. Each of these subclasses contains independently scoped references. However, because of the special property of inner classes, they are both able to share the parent ViewPanel variables and therefore communicate effectively with each other. It should be noted that a similar behavior could have been accomplished through the listener design pattern. Another utility of inner classes is the definition of specific sorting or comparison methods, as is done in the tournament module. Use inner classes beneficially. 6. Constants & Properties Constants appear ubiquitously throughout the software. Virtually all parameters describing the behavior of user-interface entities, game model behavior, and software configuration are implemented as constants. There exist two sets of constants: package-wide constants, which are defined in the interface IFCConstants, and class-scoped constants, which are often of a user-interface nature and are defined locally within the corresponding class. Most of the important game model constants, however, are defined within the main game model class itself -- global constants are used as to facilitate player development by establishing predefined standards. In contrast to behavior parameterized by constants (which are bound at compile time), dynamic runtime behavior is imparted through the use of an external properties file. The properties file is loaded upon system startup, and parsed for relevant information. The contents of the properties file include: * Number of players (if applicable) * Number of rounds (if applicable) * The player classes which are to be loaded and instantiated by the VM. * Any gamefiles which are to be loaded. A properties file permits the easy menu-driven launching of games and associated parameters such as the player identities and game files. For most circumstances in practice, this is much more efficient than having to repetitively select files to load. The following is a sample IFCConstants file: package Obstacles; import java.io.Serializable; public interface IFCConstants { public static final char _CROTATION = 'r'; public static final char _CROTATION_ARBITRARY = 'x'; public static final char _CTRANSLATION = 't'; public static final String _CERROR_STRING = "[Error]"; } 7. Player Interface The player interface was designed to permit rapid development of players with minimal requirements. Therefore, inheritance was abandoned in favor of interfaces. Most of the player interfaces consist of a number of methods that gather property information about the players, such as color, name and so forth. The important methods within the interface, however, are the game model registration and move generation methods. Game model registration is performed by the game model after dynamic instantiation of the player, permitting important exposed methods in the game model (such as the game history or current scores) to be accessed by the player object. The move generation methods are called by the game model when it is the player object's turn. The following is a sample player interface: package Obstacles; import java.io.Serializable; public interface IFCPlayer extends IFCConstants, Serializable { public void register(Obstacles __obstacles) throws Exception; public String name() throws Exception; public boolean interactive() throws Exception; public Vertex[] polygon() throws Exception; public Move[] moves() throws Exception; } Several students wished to design players that "learn" over the course of their experiences. To achieve this, it was necessary to "save" these player objects from the game model (which is destroyed after each game). The storage component is the UI itself. By using a hashtable mapping fully qualified player class names to player objects, "learning" players, or "persistent" players, are preserved throughout the course of a simulation. Of course, the player object stored in the UI can be replaced with a null reference to dispose of the stored entity. Learning players, however, must have information pertaining to the last move and the final outcome of the game (since the next call would be a new game). Therefore, the following method, gameOver(), is used as the final call to a learning player before it is stored and the game object disposed: package Obstacles; public interface IFCPersistentPlayer extends IFCPlayer { public void gameOver() throws Exception; } 8. Tournament Interface Often, many performance/operational characteristics of players under development can only be ascertained through the collection of large amounts of data. To facilitate this process, and to encourage healthy exchange of players among the groups, a built-in tournament module is part of the software package. Using the module, different tournament configurations can be built and games played. In fact, two of the tournaments for the class were executed in entireity within this module. The tournament interface consists of a list of predefined games that have been generated by the tournament randomizer. This list of games is then passed to a gamemodel object by dispatching its run() method. The gamemodel object should proceed to setup each of the game configurations (there could be thousands), and store the results within the game record object itself. After completion of the game queue, the tournament module then performs a basic tabulation/analysis of the results according to several criteria. The columns that are generate are trigger-sorted, such that pressing any row in the column sorts all the list according to that value. Several predefined criteria were included to make it interesting; additional tabulated values can easily be incorporated into the code. It is critical to note a nuance: When a tournament is run, a _new_ game model is constructed, (one for which the graphical panels are _not exported_), and this is used to play the games. This ensures that the games are played with the highest throughput without having to deal with graphics/rendering issues. This is important to note, especially in light of how slow Swing can be. There are three types of tournaments: Round-Robin: 1vs1 for each of the player list except for self-games. Multiplayer: A specified number of players all play in the same game. Multisample: Given the player list, subsets are sampled and played. This is particularly useful when the number of potential players (player list) is significantly larger than the practical limits imposed by the game model itself. The following is the tournament interface which is exchanged between the UI and the game model: package ui; import java.io.Serializable; public interface IFCTournament extends Serializable { public IFCGameRecord[] games() throws Exception; public void setGames(IFCGameRecord[] __games) throws Exception; } The following is the code for the game record interface which is used by the game model to generate and play games. package ui; import java.io.Serializable; public interface IFCGameRecord extends Serializable { public void setPlayers(Class[] __players) throws Exception; public Class[] players() throws Exception; public void setRounds(int __rounds) throws Exception; public int rounds() throws Exception; public void setScores(double[] __scores) throws Exception; public double[] scores() throws Exception; public void setGameFile(String __gamefile) throws Exception; public String gameFile() throws Exception; public void setBatchComplete(boolean __batchcomplete) throws Exception; public boolean batchComplete() throws Exception; } 9. Project-Specific Notes Though the general architecture of the game model and the user interface was quite consistent across the different game models, it is instrumental to highlight some of the major differences: * Bidding Wars: Bidding Wars utilized a state engine with many states. Furthermore, human input was received by incorporating a "_CWAITING" state which was terminal until input was entered. This was performed because interactive play was quite practical in this model. The original tournament interface was also developed using this game model, so many of the features are most applicable to this game model. * Mirrors: Mirrors was by far the most computation-intensive game mode, involving the display of user-created polygons, different kinds of surfaces, and visualization of light sources. A lot of code was optimized for performance, to the inevitable detriment of readability. Additionally, from a design point of view, the inclusion of a unique "Adjustment Panel" required multiple levels of inner class nesting. Along with Obstacles, Mirrors was one of the single-player models. * Rectangles: Rectangles required edge-detection code and graceful failure in response to invalid player moves. This was accomplished in the single method processMoves(). Furthermore, this game model required a counter that pointed to an event in the past (namely, the claiming of territory by a player) as one of several game ending conditions. * Obstacles: Obstacles employed a significant amount of code to detect edge intersections. Some of the code employs linear-algebraic solutions (such as the rotation intersection code), and some knowledge of these procedures would be useful to understand the code. Obstacles was also a single-player model, and used a GUI layout in which the main display area was underneath the other two panels (in contrast with the standard setup, in which the display area was to the left of the other two panels). Obstacles also utilized an artificial delay timer to enable visualizaton of the moves. 10. Deployment The package is to be deployed using a Sun Java VM and SDK with a version greater than 1.4 on UNIX machines. Variations in these requirements cannot be guaranteed to adhere to the "Write Once, Run Anywhere" paradigm as the Swing graphics package is quite variable among different operating systems. Of course, the system paths and classpaths should be set appropriately either in the launching shell or supplied as command-line parameters (the -classpath option for both the Java compiler and interpreter). The included makefile can produce archived (jarred), deployment-ready builds by using the appropriate command-line switches. Additionally, game models requiring external gamefiles should deposit them in the /GameFiles subdirectory from the installation root (which is hardcoded). The current build/deployment procedure produces an archived directory tree which can simply be downloaded as one unit, and uncompressed/archived in the install directory to refresh the installation. 11. Potential Enhancements Though the software served its purposes well, there are a number of potential enhancements to the system, which are listed below: * Documentation of the UI code. Since the UI code is to be utilized across all game modules, full documentation would be nice. However, most of the methods and members are self-explanatory by examining their names. * Additional runtime parameters should be incorporated as properties rather than constants, for the obvious reason that the software need not have to be recompiled. * The UI should be made even more general -- capable of simulating any game model through a menu. * The Save State/Restore State mechanism needs to be debugged. From my evaluation, the problem lies with the actual Java SDK rather than the code, but this should be confirmed and fixed. * Auto-detection/loading of player class files and gamefiles would be useful.