Friday, July 20, 2012

Interlude 10 - Formatting Text in Columns


We will have one last Interlude before we return to Part 2 of the MineSweeper tutorial. One of the areas of functionality that we wanted to add to the game was keeping track of high scores. The code to save and retrieve this was relatively straight forward (and we will cover it in the tutorial) but printing the results in neat columns turned out to be harder than expected.

After struggling with this for a few days we resorted to the Codea forums and dave1707 pointed out our problem. We will get to this shortly, but first some background.

Using the string.format() function, %w.ts will format a fixed column of width w, truncated at t characters for string s. This will be right justified. If you use the left justify flag, %-w.ts the string will be left justified. So in the code below %-10.10s will print "Easy" left justified in a column 10 characters wide and it shouldn't be truncated. The escape character \t will produce a tab between columns.

The truncation field (.t) is optional. If you don't include this (e.g. %10s), then the column width field works as a minimum. In this instance if you had a string which was longer than w then the additional characters would be printed and the columns won't line up.

More generally, the string.format() function uses the same format identifiers as the c printf function. The identifier field needs to be in the following order.

%FlagsMinimum field widthPeriodPrecision. Maximum field widthArgument type
RequiredOptionalOptionalOptionalOptionalRequired

The Flags available are:

   -      Left justify.
   0      Field is padded with 0's instead of blanks.
   +      Sign of number always O/P.
   blank  Positive values begin with a blank.
   #      Various uses:
   %#o (Octal) 0 prefix inserted.
   %#x (Hex)   0x prefix added to non-zero values.
   %#X (Hex)   0X prefix added to non-zero values.
   %#e         Always show the decimal point.
   %#E         Always show the decimal point.
   %#f         Always show the decimal point.
   %#g         Always show the decimal point trailing 
               zeros not removed.
   %#G         Always show the decimal point trailing
               zeros not removed.

Note that the flags must follow the % and where it makes sense you can use more than one flag. Finally the available format identifiers are:

%d %i         Decimal signed integer. 
%o              Octal integer. 
%x %X       Hex integer. 
%u              Unsigned integer. 
%c              Character. 
%s              String. 
%f               double 
%e %E      double. 
%g %G      produces either f, e or d type output depending on the argument.
%q              treats double quotes, newline, embedded zeros and back slash as escaped

BUT there is a trick. As dave1707 so helpfully pointed out, this column formatting trick only works for fixed width fonts (i.e fonts where each character is the same width). Most fonts are proportional and won't be formatted as you would expect using the above technique.

In Codea, there are currently two fixed width fonts available: Inconsolata and the various flavours of Courier.

The following test stub indicates how you can use this to provide the output shown in the image at the top of the Interlude.

function setup()
   displayMode(FULLSCREEN)
end

function draw()

   background(0)

   font("Courier-Bold")
   fill(0,0,255)
   fontSize(72)
   textAlign(CENTER)

   text("High Scores", WIDTH/2, HEIGHT/2 + 220)

   fill(255)
   fontSize(24)

   local str

   str = string.format("%-10.10s\t%-10.10s\t%10d", "Easy", "Player 1", 1000)
   text(str, WIDTH/2, HEIGHT/2 + 40)
   str = string.format("%-10.10s\t%-10.10s\t%10d", "Medium", "Player 2", 2000)
   text(str, WIDTH/2, HEIGHT/2)
   str = string.format("%-10.10s\t%-10.10s\t%10d", "Hard", "Player 3", 3000)
   text(str, WIDTH/2, HEIGHT/2 - 40)

end