Semi-Random thoughts on life, computers, sports, and what not....but mostly programming.
Monday, December 28, 2009
TrExMa 0.8.6
It's available at Google Code as version 0.8.6.
Tuesday, November 17, 2009
Simple Web Tool Building With Maven (The Woot-Off Tracker Revisited)
One of my favorite tools of the past year was the Woot-Off tracker I developed just over a year ago. In my mind, one of the problems this tool had was that I needed to start up a proxy server to host make calls on behalf of the AJAX Javascript. I needed to find some way to make this easier. Additionally, I had run into an issue when I actually tried to run the tool behind a firewall proxy.
Since discovering the power of Maven, I thought I'd look into seeing what was available for the Winstone servlet engine I used on the first iteration of this tool. Conviently, there was the winstone-maven-plugin, a mojo which, among other things, built an executable jar for winstone with your war already deployed.
This was perfect for my needs. So (along with converting to apache's http-client) I set out on a new maven adventure. My pom looked like this:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.twofourone</groupId>
<artifactId>woot.tracker.proxy</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>woot.tracker.proxy</name>
<url>http://twofourone.blogspot.coom</url>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>net.sf.alchim</groupId>
<artifactId>winstone-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>embed</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>net.sf.alchim</groupId>
<artifactId>winstone-maven-plugin</artifactId>
<version>1.2</version>
<configuration>
<cmdLineOptions>
<property>
<name>httpPort</name>
<value>8480</value>
</property>
<property>
<name>ajp13Port</name>
<value>-1</value>
</property>
<property>
<name>controlPort</name>
<value>-1</value>
</property>
<property>
<name>directoryListings</name>
<value>false</value>
</property>
<property>
<name>useInvoker</name>
<value>false</value>
</property>
</cmdLineOptions>
</configuration>
<executions>
<execution>
<goals>
<goal>embed</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
<finalName>woot.tracker.proxy</finalName>
</build>
</project>
We have two setups for winstone. The first goal, embed, tells Maven to stick winstone into the jar during the package phase. By default, this step will create a jar with the same name as your project and throw -standalone on the end. The second setup tells winstone what port it should run on, etc. These could potentially be in a single setup, just haven't tried it yet.
In order to run the proxy, you simply execute the jar from a command prompt and you'll have a war deployed to port 8480.
How has our proxy changed? In order to support a firewall call, I decided to use Apache's HttpClient. Adding the dependency for HttpClient to the pom automagically downloads all dependencies of HttpClient for use by the war. Our new code looks like this:
package com.twofourone.woot.tracker.proxy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
/**
* Proxy Servlet to redirect requests to the Woot site.
* @author mcornell
*/
public class WootProxy extends HttpServlet {
static final long serialVersionUID = 1L;
private HttpClient httpClient = new DefaultHttpClient();
private HttpGet wootGet;
/**
* Sets up the HttpClient with proxy info if provided. Sets up the URL for Woot.
*/
@Override
public void init() throws ServletException {
String proxyHost = System.getProperty("proxyHost", "");
String proxyPort = System.getProperty("proxyPort", "");
if (proxyHost.length() > 0 && proxyPort.length() > 0) {
HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort));
httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
}
String contentObj = "http://www.woot.com/salerss.aspx";
wootGet = new HttpGet(contentObj);
}
/**
* Processes requests for both HTTPGET
andPOST
methods.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpResponse wootResponse = httpClient.execute(wootGet);
HttpEntity wootEntity = wootResponse.getEntity();
response.setContentType(wootEntity.getContentType().toString());
// Get and read the input stream.
StringBuffer buffer = new StringBuffer();
BufferedReader din =
new BufferedReader(new InputStreamReader(wootEntity.getContent()));
String s;
while ((s = din.readLine()) != null) {
buffer.append(s);
}
din.close();
// Now write the bytes out to the client.
byte[] contentBytes = buffer.toString().getBytes();
OutputStream out = response.getOutputStream();
out.write(contentBytes, 0, contentBytes.length);
out.flush();
out.close();
}
/**
* Handles the HTTPGET
method.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Handles the HTTPPOST
method.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Returns a short description of the servlet.
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Woot Tracker Proxy";
}
}
The code has changed to setup the client once and initialize it once. Additionally, there is the code provided by the HttpClient project that adds a proxy to the client. One additional change needs to be the ability to track a Kids.Woot-Off URL in case it's a different kind of woot-off.
The HTML/Javascript remains the same.
Friday, October 30, 2009
Converting to the Ultimate Java Build Stack
During my time at the client, I setup Hudson, Nexus, and Sonar as outlined in Christopher Judd's blog post. We had actually already decided upon Hudson and Nexus through discovery before Judd published his post. Sonar was a nice discovery we added to the mix a bit later. Having setup this stack, I thought I'd offer some thoughts on my experiences.
I cannot stress how important having Nexus (or Artifactory) is to keeping your Maven projects running smoothly and effectively. Having a repository management tool and proxy allows for all of your developers (and Hudsons) to simply point at the proxy and download everything required for a build quickly. It also provides a place for you to host your third party jars from apps that have been purchased by your organization. Not everything is open source of course.
The steps are simple enough, install Nexus and setup your settings.xml file to point to it. For developers, they can use their own settings.xml file. For Hudson, you may want to modify the settings.xml in your Maven install (especially if you're running on Windows). You might need to add a mirror for Java.net's Maven 2 repository. You might not. It depends upon how up to date Maven's central repository is.
Sonar was a nice surprise. Providing PMD, Checkstyle and Cobertura results in a nice visual manner that allowed for quick feedback was surprisingly effective. As a developer, I found it to be my favorite part of the switch to Maven. Sure, Maven provides this information as part of its site goal, however, it doesn't present it like this.
Obviously there are huge gains to be made by integrating Hudson and getting it to build upon version control commits, but I found more intriguing was its ability to trigger downstream builds. When I built the commons library for the app, every maven artifact that used the commons library was triggered for build. Numerous times, I discovered that I or someone else had broken something in a downstream library due to an API fix. That instant feedback (through RSS feeds) was an invaluable time saver.
What about Maven itself? What does it gain you? In the case I was working on, tremendous build granularity. The original build used Ant, and was built in a monolithic manner. Each component was built by the single script and bundled together. One could argue it wasn't the best ant script in the world, and they'd be right. Maven allowed us to break these dependencies apart. So now, instead of having one monolithic code base which included what were really five separate artifacts, we now had five separate projects, each self contained using dependencies to draw in the needed artifacts. As such the WAR and EAR builds ended up being very simple where they just drew in their needed dependencies and just worked.
Monday, September 14, 2009
Wierdness with Maven's site-deploy and Hudson
So, for the past five months or so, I've been working on porting some Java projects to Maven and the Ultimate Enterprise Java Build System.
A quick aside, Sonar is a very underrated tool. You should try it. I find the reporting very easy to use and very handy for working through potential coding mistakes in the legacy code I've been working with.
One of those Java projects is a large multi-module Maven build. It has five different modules, three Jar modules, a War module and *gasp* an Ear module. It's been a bit of a beast to get going, but it seems to be working just fine.
On Monday, I began to perform releases to move from Maven SNAPSHOTs to actual real version numbers. Ah, the joys of SCM release management. Thankfully, all of this pain ends up being worth it to easily tag and lock the code down using the maven-release-plugin. There were a couple of bumps, but it wasn't bad.
The fun began when I built my Hudson jobs to perform release builds. These jobs simply pulled the tagged release and performed the following goals:
clean install site-deploy
Obviously, I used Hudson's deploy step for the artifacts rather than Maven's. And everything was fine, until I got this:
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to resolve artifact.
Oops, what's that about? A little more background. This particular maven build was a multimodule build to create jar, war and ear files. The artifact it couldn't resolve was the first jar it built! Scanning the log file, it became clear that the job was not installing the built jar to the repository.
I don't know why that is, and am not certain if it's even worth opening a trouble ticket since the fix ended up being very simple. It seems that site-deploy was causing issues with the flow of the job within Hudson. By removing site-deploy the build ended up being successful. The jar was compiled, tested and installed before the next module's build was invoked.
I still wanted to build the site, so I investigated the M2 Extra Steps Plugin. This tool allows you to add multiple maven steps before and after the main job. I added the site-deploy goal to run after the main install goal and the sites were generated!
Monday, July 27, 2009
Trexma for Firefox 3.5
Tuesday, February 10, 2009
US Mexico Tomorrow (the Mexi-whore edition)
As with most big sporting events that actually allow in the inebriated public, the stands can be an interesting place. I stood in about 2 rows from the top of the North End, directly behind the goal. It was from this angle that I was able to see some mighty entertaining things.
The first was the Mexi-whore. Someone, obviously during a highly energetic brainstorming session fueled by Jager or Firewater, decided to purchase an adult doll. You know the kind of doll I'm talking about. The owner then acquired an undergarment for the doll, a thong of some nature, as well as a Mexican jersey. At various times during the match, the doll was held aloft by a leg while a cohort proceeded to spank the doll.
Needless to say, the presence of this doll has reached legendary status. But today I have found proof! In this snap from YouTube, you can see the head of the doll circled. It did exist. I can only hope it will make a return.
Other entertaining items were the signs. This sign would not made it through today's PC filters. The text of it read: Red Card, Yellow Card, Mexican
Yep, definitely wouldn't be "allowed" today. It also shows you how different things were eight years ago. Most everyone had a good time...some had too much of one. There was that one drunken guy who thought it would be a good idea to take taunting to the next level and to moon a bunch of Mexican fans in the parking lot. It might have been harmless had their not been a slew of vulgarity and elementary school kids in the car. Needless to say, he almost got his ass kicked.
The full YouTube video for your enjoyment:
At any rate, the hijinks tomorrow should be outstanding. If you're attending, I hope to see you there. If you're watching at home, hopefully you'll see some antics on TV or YouTube in the days to come. I'm hoping for a big US win, in the slop and wind that will be Columbus Crew Stadium. And maybe the return of the Mexi-whore.
Friday, January 30, 2009
TrExMa 0.8.5 is out
If all goes well, this could be the final "preview" before deprecating 0.6.2.
Thursday, January 22, 2009
The Hungarian Algorithm in Javascript
Recently, I got to tackle an old school problem. Only I didn't know it was old school at the time when I started. I suppose that's because I've not been doing hardcore Computer Science type problems in quite some time.
Here's the problem: In TrophyManager, you field a soccer team of 11 players, 10 field players and a keeper. TrExMa calculates a skill for each player on your roster to try to give the user an idea of what the best possible lineup would be. Selecting the keeper is easy. Take the guy with the highest value. Selecting the field players, not so much.
Each player has 14 "visible" attributes. Early players of the game used the instruction manual to determine which attribute is most important for the position the player is in. For example, a Forward must be good at Finishing and Heading, etc. Additionally, each player has a favorite position. When they play "away" from their position, their skill drops. So a player may have a high finishing skill, but since they like to play left back, they're not going to be a very good striker and would have their skill reduced anywhere from 4-40% depending upon a hidden adaptability attribute.
Anyway, how does one figure out the best team? I can set the adaptablity factor from 1-10% and we can then caclulate the skill for every position on the field for every player on the team. Sounds good, right? But how to pick it?
I first looked at the original TrExMa algorithm within Excel. The original app had a drop down to determine the depth of the search. Once I looked at the code it was confirmed. TrExMa used a series of nested loops to build the lineup. If I'm not mistaken it could run from O(n^5) to O(n^10) depending upon the complexity you chose. Wow, no wonder it got very slow. I needed something else. I also realized that the only way this algorithm would guaruntee success would be to run it at O(n^10) setting. Using Javascript, this was simply unacceptable. I couldn't get away with something running for so long.
My first instinct was to use an algorithm similar to Fit First. In this case, find the highest value on the board, and place the player there. cross him out and cross out the position. Do it again until you've filled the positions. Supposedly this runs in O(n log n). Of course, 10 is pretty small for me, as I've only 10 boxes to fill. It's pretty good, but doesn't guaruntee the best match of players to positions. Why? Let's say you've got a player who is adept at a midfield and wing position. And another player who is only good at midfield. If the former is slightly better at midfield, he'll be placed there, and the other player will have to play somewhere else where he won't be as successful.
How do you correct this? I soon realized that this had to be a common problem, I just couldn't figure out what to call it? Eventually I discovered that it's known as an "assignment problem". It's an obvious name, now that I know what it is. The classical use of this problem is to determine the least overall cost of doing multiple things with multiple resources. It's very similar.
Soon I discovered the Hungarian Algorithm. It runs in O(n^4) (potentially O(n^3)). So it's not fast, but it's not insanely slow. It's not exponential, and it runs faster than the Excel app.
So what does the Hungarian Algorithm do? It uses a method of starring and priming to determine when a player is in its ideal place. Essentially, if two players are "best" at a position, it will not accept that assignment until it validates the best organization of those players. It's explained very well in the Wikipedia page.
There are many implementations of the Hungarian Algorithm on the net but none in Javascript. So here's one. I've pulled out some of the specifics for my implementation. You'll want to implement your own loadMatrix and getSolution methods. One thing to note, is that with my problem, we had to reverse the matrix. This is required because the Hungarian Algorithm uses small values to determine the best player (worker) for a position (job). I hope you find this useful.
/* Implementation of the Hungarian Algorithm to determine
* "best" squad. This is a "reverse" implementation.
* References:
* http://en.wikipedia.org/wiki/Hungarian_algorithm
* http://www.ams.jhu.edu/~castello/362/Handouts/hungarian.pdf (Example #2)
* http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html // Non-square
*/
var Hungarian = window.Hungarian = window.HG = {
/* 2 dimension arrays */
skillMatrix: null,
matrix: null,
stars: null,
/* Single arrays */
rCov: [],
cCov: [],
rows: 0,
cols: 0,
dim: 0,
solutions: 0, // "k"
FORBIDDEN_VALUE: -999999,
/* Rows MUST BE the Formation (Jobs)
* Columns MUST BE the Squad (Workers)
* Therefore, the Rows MUST BE PADDED
*/
hungarianAlgortithm: function(formation, squad) {
HG.init(formation, squad);
// Step 1
HG.matrix = HG.subtractRowMins(HG.matrix);
// Step 2
HG.findZeros(HG.matrix);
var done = false;
while (!done) {
// Step 3
var covCols = HG.coverColumns(HG.matrix);
if (covCols > HG.solutions -1) {
done = true;
}
if (!done) {
// Step 4 (calls Step 5)
done = HG.coverZeros(HG.matrix);
while (done) {
// Step 6
var smallest = HG.findSmallestUncoveredVal(HG.matrix);
HG.matrix = HG.uncoverSmallest(smallest, HG.matrix);
done = HG.coverZeros(HG.matrix);
}
}
}
return HG.getSolution(formation, squad)
},
init: function(formation, squad) {
HG.cols = squad.players.length;
HG.rows = formation.length;
HG.dim = Math.max(HG.rows, HG.cols);
HG.solutions = HG.dim;
HG.skillMatrix = HG.initMatrix(HG.rows, HG.cols);
HG.matrix = HG.initMatrix(HG.dim, HG.dim);
HG.stars = HG.initMatrix(HG.dim, HG.dim);
HG.matrix = HG.loadMatrix(squad, formation, HG.matrix, true);
HG.skillMatrix = HG.loadMatrix(squad, formation, HG.skillMatrix, false);
HG.rCov = new Array(HG.dim);
HG.cCov = new Array(HG.dim);
HG.initArray(HG.cCov, 0); // Zero it out
HG.initArray(HG.rCov, 0);
},
initMatrix: function(sizeX, sizeY) {
var matrix = new Array(sizeX);
for (var i=0; i<sizeX; i++) {
matrix[i] = new Array(sizeY);
HG.initArray(matrix[i], 0);
}
return matrix;
},
// Takes an array of positions as a formation.
// Takes a squad which contains an array of players
loadMatrix: function(squad, formation, matrix, reverse) {
matrix = loadYourMatrix(squad, formation, matrix); // I've removed my implementation here. Far too much stuff
if (reverse) {
// This reverses the matrix. We need to to create a cost based solution.
matrix = HG.reverseMatrix(HG.findMaxValue(matrix), matrix);
}
return matrix;
},
findMaxValue: function(matrix) {
var max = 0.0;
for (var i = 0; i < matrix.length; i ++) {
for (var j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] > max) {
max = matrix[i][j];
}
}
}
return Number(max);
},
reverseMatrix: function(max, matrix) {
for (var i = 0; i < matrix.length; i ++) {
for (var j = 0; j < matrix[i].length; j++) {
matrix[i][j] = (Number(max) - Number(matrix[i][j])).toFixed(0);
}
}
return matrix;
},
subtractRowMins: function(matrix) {
for (var i = 0; i < matrix.length; i ++) {
var min = Number.MAX_VALUE;
for (var j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] < min) {
min = Number(matrix[i][j]);
}
}
for (var k = 0; k < matrix[i].length; k++) {
matrix[i][k] = matrix[i][k] - min;
}
}
return matrix;
},
subtractColMins: function(matrix) {
for (var j = 0; j < matrix[0].length; j ++) {
var min = Number.MAX_VALUE;
for (var i = 0; i < matrix.length; i++) {
if (matrix[i][j] < min) {
min = Number(matrix[i][j]);
}
}
for (var k = 0; k < matrix[0].length; k++) {
matrix[k][j] = matrix[k][j] - min;
}
}
return matrix;
},
findZeros: function(matrix) {
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] == 0) {
if (HG.rCov[i] == 0 && HG.cCov[j] == 0) {
HG.stars[i][j] = 1;
HG.cCov[j] = 1;
HG.rCov[i] = 1;
}
}
}
}
// Clear Covers
HG.initArray(HG.cCov,0);
HG.initArray(HG.rCov,0);
},
initArray: function(theArray, initVal) {
for (var i = 0; i < theArray.length; i++) {
theArray[i] = Number(initVal);
}
},
coverColumns: function(matrix) {
var count = 0;
for (var i=0; i < matrix.length; i++) {
for (var j=0; j < matrix[i].length; j++) {
if (HG.stars[i][j] == 1) {
HG.cCov[j] = 1;
}
}
}
for (var k=0; k < HG.cCov.length; k++) {
count = Number(HG.cCov[k]) + Number(count);
}
return count;
},
/**
* step 4
* Cover all the uncovered zeros one by one until no more
* cover the row and uncover the column
*/
coverZeros: function(matrix) {
var retVal = true;
var zero = HG.findUncoveredZero(matrix); // Returns a Coords object..
while (zero.row > -1 && retVal == true) {
HG.stars[zero.row][zero.col] = 2 //Prime it
var starCol = HG.foundStarInRow(zero.row, matrix);
if (starCol > -1) {
HG.rCov[zero.row] = 1;
HG.cCov[starCol] = 0;
} else {
HG.starZeroInRow(zero); // Step 5
retVal = false;
}
if (retVal == true) {
zero = HG.findUncoveredZero(matrix);
}
}
return retVal;
},
findUncoveredZero: function(matrix) {
var coords = new HgCoords();
for (var i=0; i< matrix.length; i++) {
for (var j=0; j < matrix[i].length; j++) {
if (matrix[i][j] == 0 && HG.rCov[i] == 0 && HG.cCov[j] == 0) {
coords.row = i;
coords.col = j;
j = matrix[i].length;
i = matrix.length - 1;
}
}
}
return coords;
},
foundStarInRow: function(zeroRow, matrix) {
var retVal = -1;
for (var j = 0; j < matrix[zeroRow].length; j++) {
if (HG.stars[zeroRow][j] == 1) {
retVal = j;
j = matrix[zeroRow].length;
}
}
return retVal;
},
/**
* step 5
* augmenting path algorithm
* go back to step 3
*/
starZeroInRow: function(zero) { // Takes a Coords Object
TrU.log("Step 5: Uncovered Zero:" + zero.row + "," + zero.col, TrU.DEBUG );
var done = false;
var count = 0;
var path = HG.initMatrix(HG.dim*2, 2);
path[count][0] = zero.row;
path[count][1] = zero.col;
while (!done) {
var row = HG.findStarInCol(path[count][1]);
if (row > -1) {
count++;
path[count][0] = row;
path[count][1] = path[count - 1][1];
} else {
done = true;
}
if (!done) {
var col = HG.findPrimeInRow(path[count][0]);
count++;
path[count][0] = path[count - 1][0];
path[count][1] = col;
}
}
HG.convertPath(path, count);
// Clear Covers
HG.initArray(HG.cCov,0);
HG.initArray(HG.rCov,0);
HG.erasePrimes();
},
findStarInCol: function(col) {
var retVal = -1;
for (var i = 0; i < HG.stars.length; i++) {
if (HG.stars[i][col] == 1) {
retVal = i;
i = HG.stars.length;
}
}
return retVal;
},
findPrimeInRow: function(row) {
var retVal = -1;
for (var j=0; j< HG.stars[row].length; j++) {
if (HG.stars[row][j] == 2) {
retVal = j;
j = HG.stars[row].length;
}
}
return retVal;
},
/* Should convert all primes to stars and reset all stars.
* Count is needed to be sure we look at all items in the path
*/
convertPath: function(path, count) {
HG.logMatrix(path, "Step 5: Converting Path. Count = " + count);
for (var i=0; i < count+1; i++) {
var x = path[i][0];
var y = path[i][1];
if (HG.stars[x][y] == 1) {
HG.stars[x][y] = 0;
} else if (HG.stars[x][y] == 2) {
HG.stars[x][y] = 1;
}
}
},
erasePrimes: function() {
for (var i=0; i<HG.stars.length; i++) {
for (var j=0; j < HG.stars[i].length; j++){
if (HG.stars[i][j] == 2) {
HG.stars[i][j] = 0;
}
}
}
},
findSmallestUncoveredVal: function(matrix) {
var min = Number.MAX_VALUE;
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
if (HG.rCov[i] == 0 && HG.cCov[j] == 0) {
if (min > matrix[i][j]) {
min = matrix[i][j];
}
}
}
}
return min;
},
/**
* step 6
* modify the matrix
* if the row is covered, add the smallest value
* if the column is not covered, subtract the smallest value
*/
uncoverSmallest: function(smallest, matrix) {
TrU.log("Uncover Smallest: "+ smallest);
HG.logMatrix(matrix, "B4 Smallest uncovered");
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
if (HG.rCov[i] == 1) {
matrix[i][j] += smallest;
}
if (HG.cCov[j] == 0) {
matrix[i][j] -= smallest;
}
}
}
HG.logMatrix(matrix, "Smallest uncovered");
return matrix;
},
getSolution: function(formation, squad) {
var total = 0;
var lineup = [];
// Changed from length of stars, since we must ignore some rows due to padding.
for (var i = 0; i < HG.rows; i++) {
for (var j = 0; j < HG.cols; j++) {
if (HG.stars[i][j] == 1) {
/* the player (worker) at index j is the best player
* for poisition (job) at index i in your initial arrays.
*/
lineup.push(getThePlayer(i,j));
}
}
}
return lineup;
}
}
function HgCoords() {
this.row = -1;
this.col = -1;
}
Tuesday, January 06, 2009
New TrExMa Preview Release
This bug exists in all versions of the plugin as the Transfer List uses slightly different HTML to display starred skills. The starred skills were getting skipped over and treated as zero. Sheffield FC also picked up on this bug and reported to me as I was in the midst of adding some additional features for this release. So thanks to him for noticing the problem!
Additional features include the introduction of skill summaries, Attack, Defending, etc.
You can find the new plugin: http://code.google.com/p/trexma-for-firefox/
Thanks!