FitNesse. UserGuide. SliM.
TableTable [add child]

Table:Bowling
3 5 4 / X   X   3 4 6 / 7 2 3 4 9 - 4 / 3
  8   28   51   68   75   92   101   108   117     130

The "Table" table allows you to write a fixture that accepts an arbitrary table, and returns a table of results. The table of results has a similar geometry to the input table (without the first row). Each cell of the result table can be one of the following codes:

Comment
pass The original contents will be colored green.
pass:<message> The original contents will be replaced with <message> and colored green.
fail The original contents will be colored red.
fail:<message> The original contents will be replaced with <message> and colored red.
ignore The original contents will be colored grey.
ignore:<message> The original contents will be replaced with <message> and colored grey.
report:<message> The original contents will be replaced with <message>.
<empty string> or no change The corresponding cell will be unchanged
error:<message> The corresponding cell will be colored yellow and its contents will be <message>
<anything else> The corresponding cell will be colored red, and its contents will be <anything else>

The fixture is written with a doTable method. This method takes a List argument and returns a List. The incomming list is a list of rows. Each row is a list of strings. The returned list has a similar structure except that it does not have the first row. If any row of the returned list is longer than the corresponding row of the incomming list, then the extra columns will be added to the colored table. If there are extra rows, then they will be added too. So the returned table can be larger, horizontally and vertically. It cannot be smaller!

See Report Tables to see how to use the different geometries to create reports.

Here is the fixture for the above table.

package fitnesse.slim.test;

import static fitnesse.util.ListUtility.*;

import java.util.List;

public class Bowling {
public List doTable(List<List<String>> table) {
Game g = new Game();
List rollResults = list("","","","","","","","","","","","","","","","","","","","","");
List scoreResults = list("","","","","","","","","","","","","","","","","","","","","");
rollBalls(table, g);
evaluateScores(g, table.get(1), scoreResults);
return list(rollResults, scoreResults);
}

private void evaluateScores(Game g, List<String> scoreRow, List scoreResults) {
for (int frame=0; frame<10; frame++) {
int actualScore = g.score(frame+1);
int expectedScore = Integer.parseInt(scoreRow.get(frameCoordinate(frame)));
if (expectedScore == actualScore)
scoreResults.set(frameCoordinate(frame), "pass");
else
scoreResults.set(frameCoordinate(frame), String.format("Was:%d, expected:%s.", actualScore, expectedScore));
}
}

private int frameCoordinate(int frame) {
return frame < 9 ? frame*2+1 : frame*2+2;
}

private void rollBalls(List<List<String>> table, Game g) {
List<String> rollRow = table.get(0);
for (int frame = 0; frame < 10; frame++) {
String firstRoll = rollRow.get(frame * 2);
String secondRoll = rollRow.get(frame * 2 + 1);
if (firstRoll.equalsIgnoreCase("X"))
g.roll(10);
else {
int firstRollInt = 0;
if (firstRoll.equals("-"))
g.roll(0);
else {
firstRollInt = Integer.parseInt(firstRoll);
g.roll(firstRollInt);
}
if (secondRoll.equals("/"))
g.roll(10 - firstRollInt);
else if (secondRoll.equals("-"))
g.roll(0);
else
g.roll(Integer.parseInt(secondRoll));
}
}
}

private class Game {
int rolls[] = new int[21];
int currentRoll = 0;

public void roll(int pins) {
rolls[currentRoll++] = pins;
}

public int score(int frame) {
int score = 0;
int firstBall = 0;
for (int f=0; f<frame; f++) {
if (isStrike(firstBall)) {
score += 10 + nextTwoBallsForStrike(firstBall);
firstBall += 1;
} else if (isSpare(firstBall)) {
score += 10 + nextBallForSpare(firstBall);
firstBall += 2;
} else {
score += twoBallsInFrame(firstBall);
firstBall += 2;
}
}
return score;
}

private int twoBallsInFrame(int firstBall) {
return rolls[firstBall] + rolls[firstBall+1];
}

private int nextBallForSpare(int firstBall) {
return rolls[firstBall+2];
}

private int nextTwoBallsForStrike(int firstBall) {
return rolls[firstBall+1] + rolls[firstBall+2];
}

private boolean isSpare(int firstBall) {
return rolls[firstBall] + rolls[firstBall+1] == 10;
}

private boolean isStrike(int firstBall) {
return rolls[firstBall] == 10;
}
}
}