Goldberg

Have you tried the Tk examples bundled with ActiveState Tcl?

> wish C:\Tcl\demos\Tk8.4\widget.tcl

A few moments in my life where I have to pause the world and garbage-collect: hey, this code is important to absorb, to internalize, to own. Knight’s Tour, Rube Goldberg machine, pendulum graphing, arbitrary canvas graphics.

The wiki discusses continuation and yield, concurrent ideas that I’ve mostly glossed over. Mojolicious supposedly exploits WebSockets; Hypnotoad is a non-blocking server. “Killer Game Programming in Java” uses a game loop that runs in its own thread.

“Runs in its own thread” means the run() method is invoked as an infinite loop off a boolean, and each handed timeslice is a path through. Why is it easy for me to conceptualize procs, but imagining threads is hard? Maybe because threads imply shared memory, and my asynchronous experience has been segregated: separate data, separate scheduling.

upvar

Working through the ActiveState tcl tutorial some more, I think the sub-interpreter portion is more of an evaluation step thing instead of something happening in the substitution phase; otherwise, this wouldn’t be possible:

set a "fifty-two"
set x [string length $a]

Otherwise, how would the child context know the value of $a?

Another thing is “$” notation. Within the calling stack versus upvar referencing two levels above looks confusing. I thought “$” notation was strictly to get the value of a variable; maybe upvar does something special with it.

set x 2

proc this_proc(y) {
  upvar 1 $y z     #' bind z to y
  upvar 2 x a
}
proc that_proc(y) {
  this_proc 3
}

ChatterServer

Brackeen’s sixth chapter is about multiplayer. I used Wireshark to sniff packet traffic and found the router forwards requests to the local machine-as-server. That meant the telnet into ChatterServer using the public-facing IP, on the same machine, sent packets to its own gateway. It gets confusing here, but there are a series of TCP retransmits before the server enters a looping WARN message:

java.io.IOException: An existing connection was forcibly closed by the remote host

The connection is established before this happens, but nothing gets echoed on the telnet screen. The solution was to use the LAN IP of the self-machine.

With port forwarding, I was able to connect from a foreign connection! It was similar to setting up a server in Minecraft.

I used Netbeans to build .jar versions of both client and server. Be sure to copy the lib\ directory when you transfer the client to the test machine, as there is a log4j dependency.

The trickiest part is figuring which handful of IPs is correct. Some hints:

  • Test on local machine using LAN IP. Use ipconfig and cygwin telnet.
  • The client will use the public IP.
  • Use the router IP to set up port forwarding.

ssconvert replaced

Once you can execute VBA macros from the command-line, you can write some short utils that do the same thing or better:

  • open a CSV file and save as a new workbook
  • AutoFit
  • format date and time
  • convert xls to xlsx (SaveAs)
  • create any PivotTable

The next step would be committing macros to source control. Here I’m not sure about matters. If you can process a file line-by-line, any programming language will do: specify your rules and chalk up a language per project. On the other hand, VBA is a really nice language: Scripting.Dictionary for unique elements, a spreadsheet data store, breakpoints and a REPL workflow. On top of that, database callouts, command-line processing, ubiquity of install base…

I would not recommend ssconvert with xlsx files. There is an XML problem that crops up. Instead, use cscript and execute a module from an XLSB workbook.

If the workbook is corrupted on open, use AutoIt SQL ADODB instead.

Further Reading

VBA doesn’t exit early

I was starting to realize some deep thing with enumerations:

Enum ColName
  colA = 1
  colB = 2
  ...
  colO = 15
End Enum

Enum CustomErrorCode
  errSuccess = 0
  errNoSuchThing = 1
End Enum

Inside functions, though, things aren’t what they seem: the function’s return value will take on the last assignment:

Function ReturnValueIs()
  ReturnValueIs = 0
  ReturnValueIs = 1  ' this one!
End Function

So I had to put in an Exit Function thereby.

The Tool Command Language

Like batch scripts, tcl has two phases. In tcl, the phases are substitution and evaluation. During substitution, interpolation of variables within double-quoted strings occurs. This is as in perl, where $var takes on the value assigned via set.

Conceptually, I think of command interpretation as an “interpreter context,” but a sub-context of the running one. Sort of like cmd /c with batch scripts, where variables within the child script have their own initialization and evaluation. I think of the steps like this:

  1. Substitution
    1. Grouping: { } or ” ”
      1. if braces grouping, as-is
      2. if double-quoted grouping, sub-context interpreter
    2. All variables have values
  2. Evaluation

ActiveState tcl is nice because it comes with a language tutorial, reference, manual pages on SQLite and database drivers, using Tk, a spreadsheet-like table widget, graphics, networking, and many other things. After using AutoIt’s built-in documentation, this is a nice continuation of compiled HTML help.

Knowing tcl encourages me to create interactive command-line programs: not only scripts executed as they are, but REPL loops that invite the user to investigate the program’s state. This should help with Expect testing.

Not exactly correct.

The world scrolls with you

Earlier, I mentioned the tile map needs to be sized proportionally to the player sprite’s movement, or it will move off the screen. Actually, I was missing a crucial element: you draw the player sprite by the x_offset and y_offsets too. (gif)

By drawing the player at player.X + x_offset and player.Y + y_offset, the screen scrolls with the sprite. This is desirable: the screen into your world can access all portions of the map.

world-with-you

Now that we can define the world, what kind of world will we make?

Scrolling in 2d

The y_offset is similar to the one calculated for x_offset. (gif)

side-scroll-ii

I kept getting NullPointerException for a while. It’s because the rightmost ordinal represents the width of the map, but internally the tiles are stored in a 0-based array. My workaround was to guard the draw routine by max index:

for ...
  if (i <= 6 && j <= 6) {
    tileMap[i][j].x = tileToPx(i) + x_offset;
    tileMap[i][j].y = tileToPx(j) + y_offset;
    tileMap[i][j].draw(g);
  }

Flush with the ability to consume the world in chunks, there is one other thing: collision detection.

Sidescrolling III

Without bounding the sidescrolling coordinates, the tile ordinals will need constraints in the array loop to draw them – but that won’t prevent out-of-bound exceptions. Specifying limits on the offsets lets the player move around without scrolling off the map.

x_offset = screen_width - player.x;
x_offset = Math.min(x_offset, 0);
x_offset = Math.max(x_offset, screen_width - map_width_in_pixels);

Math.min() ensures that the first tile of the tile map is returned during the ordinal calculations if x_offset is positive or zero. This is true in the bad case where the player wanders too far left: player.x will be negative; flipped, it becomes positive; added to screen_width, x_offset becomes positive. Then,

int first_tile_ordinal = -x_offset / TILE_WIDTH; // negative index!

The right side is constrained with Math.max(): player.x effectively approaches map_width_in_pixels as the player moves right; beyond it, player.x > map_width_in_pixels, resulting in a more negative number. If this is passed in to the ordinal calculation, the last tile will be out of bounds. I have so far neglected to mention that calculation, but here it is:

lastTileOrdinal = first_tile_ordinal + toOrdinal(screen_width) + 1

If your first_tile_ordinal is off due to player too far right, the index window is shifted right too.

Everything is tables

Three-dimensional arrays, eh? If you use Excel, you use them daily. A database export is essentially a multidimensional array. Let’s see:

array  row  col value
0      0    0   0
0      0    1   1
0      1    0   2
0      1    1   3
1      0    0   4
1      0    1   5
1      1    0   6
1      1    1   7

What else does this look like? Truth tables:

q p r
T T T 
T T F
T F T
T F F
F T T
F T F
F F T
F F F

Some values:

  • array[0][1][1]: 3 (value in second row, second column of first array)
  • array[1][1][0]: 6 (value in second row, first column of second array)