The fastest way to test the library on your own machine is to run the
test.PrepareMatch class, providing as parameters the ids of the opponent teams in the database. For more details please consult the user documentation.
In order for the match to run, we need to get several details about the opponents from the database. The JDBC driver is used for connecting to the MySQL database. The database address is defined in the config.txt file, whose sample is included in the project files. Two pairs of queries are executed: each pair is for each team. The convention of naming the teams as “home” and “away” is followed in the source code and in the documentation. This is not, however, to suggest that a team has an advantage over the other. In fact, no home ground advantage is implemented in these early stages of the project. One query retrieves the team details, while the other retrieves the set of player details.
The team details required are just the name of the team, which is given as a parameter to a
core.Team constructor. The (squad) player details retrieval and integration with the library is a little more complicated.
Firstly, there is a difference in how the players of a team are stored in database for this “desktop” demonstration. Naturally, each team has a squad of players and these players are selected for the starting lineup, either from the human manager or the CPU manager. This is how it is implemented in the online demo. To keep things simple (as the desktop demo does not have a GUI) the lineup is actually preselected and only the starting lineups of players are stored in database. What’s more, the semantics of the player positions as stored in database are different and less sophisticated than what the library may support and demonstrated in www.pubsoccermanager.com.
Each player position corresponds to a number: 1 for goakeeper, 2 for defender, 3 for midfielder and 4 for forward. The players positions are used as parameters to determine the team tactics.
There are 3 different classes which are used for mapping the real world entity of player. For this demonstration, we only need the
gameplay.Player class, as this is the one holding data relevant to the match itself. The details to retrieve from database for each player are its shirt number (which is used to identify a player during the match and not the database id which is used in other occasions by default), the full name (for displaying to the user) and its position (for determining tactics and positioning during the match). Last, we need the player attributes. Each player has a set of attributes and a rating is associated with each attribute, determining how skilled a player is. These are also loaded from database and are attached to the gameplay player objects. Now, all the players are ready for the match (their details are loaded).
As mentioned above, the team tactics are determined by the player positions. In the online demo you get to select the tactics separate from the players, leaving you with more freedom. The
Team.defineTactics method is used to create a Tactics object so that tactics are calculated once at the beginning of the match. This is to save cycles but also because we need to define the tactics so that players are placed in the horizontal axis (left, centre or right). Of course, it matters where a player is placed, because player skills differ depending on whether playing on the wings or centre, etc. However, the pre-selected players are ordered in database in such a way that their optimal skills match their horizontal position. A special method is used for this demo:
Team.alingPlayersDesktop, which aligns the players to the horizontal axis based on database ordering. The general case is, of course, more sophisticated and a different method is used for player alignment to horizontal axis positions.
Team.displayLineup method is provided for displaying the team’s lineup. This is used for debugging and its output is not “beautified”. However, it is called by default.
Match object to be initialized we need the two opponents Team objects to be passed as parameters to its constructor. Calling the method
Match.play(int startTime) with
startTime = 0 kicks-off the match. For interruptions to be handled, a match is played in chunks. This demonstration is a simple case so the match will be played at once (no interruptions required), so we only start the match and check whether the match has finished in each chunk.
play method returns a
Signal object. This object’s subtype denotes the outcome of each match chunk, along with the relevant details. We are only interested in signals of
EndOfMatch subtype, so we check whether the
Signal object is of that subtype in each iteration. If the match has not reached the end then we call the
play method with the next time step as parameter.
When the end of match is reached, the after-match processing takes place (e.g. displaying results and statistics to the user). To trigger that to the
Match object, we call the
play method with the full match duration (2 *
halfDuration), to indicate that we only want the match summary and not any other match processing.
This is the first of a (hopefully) ongoing series of posts, regarding developer documentation of OpenFootie. It will, naturally, include source code discussion, but in this early stage it will serve good as a more user-oriented documentation, as well. It is assumed that you have access to the source code. Excerpts will be included where required, but I will generally avoid it, unless the discussion is hard to follow through the source code files themselves.
The subject of this first post is running the OpenFootie project. The initial idea was to have the necessary input in a (MySQL) database and load it from there. The user interaction is restricted to selecting the opponents by providing the database team ids as command line arguments. This approach has the advantage of providing the user with a minimal choice as to which teams will play against each other without needing to tweak the code and recompile.
However, as I was working on some new features, I realized that a hard-coded input would actually add more flexibility in trying different input combinations (of course, this is how I worked myself, but the aspiration of providing a more user-friendly “interface”, made the interaction less flexible). Anyway, by the time you are reading this a sample class which starts the match with hard-coded input will have been added to the trunk. The latter approach will be described in the rest of this post.
Using hard-coded input
A more straightforward way of testing the match engine is the hard-coding of input. The
test.HardcodedMatch is a sample class demonstrating how the ‘raw’ input is provided to the match engine.
The main difference is that all input that would be fetched from database are now hard-coded. We start with the team names: the match is based on the International Philosophy Monty Python sketch, so the opponents are the Greek vs. the German philosophers.
Each team’s starting lineups are represented by an
gameplay.Player objects. The data that define each player and are the constructor parameters to the player objects are the shirt number, first name, surname and position in tactics.
Each player has a set of ratings each corresponding to a specific attribute or skill. Since, these can’t be loaded from the database, they have to be hard-coded. For keeping things simple in the sample file, we assume that all players have “excellent” ability in all attributes. The excellent rating corresponds to a “6” in this file, which is the minimum numerical value for a player with an excellent rating for an attribute.
After the players are defined and added to their team’s lineups, we need to put them in their respective positions in the lineup according to tactics. As in the previous section, this is automatically done by the
alignPlayersDesktop method of the