(In case you haven't guessed already, you'll need Flash Player to view the examples on this page. They should work with Flash Player 6 or greater.)
Since I'm too frugal (read: "too much of a cheapskate") to spend all that money on a product I would only rarely use, I had long assumed that Flash animations were a game I would never get a chance to play. But then I discovered SWFTools, a set of open-source utilities for creating and manipulating Flash. SWFTools includes swfc, the SWF compiler, which reads a script from a text file and converts it to Flash. (I've been using a development snapshot, but I think the code shown here ought to work on the latest official release too.)
I've become a SWFTools fan, and I don't want to discourage people from using it, but it must be admitted that the journey has not always been smooth. First, there's the problem of documentation--there's no real manual for the swfc language. The SWFTools distribution includes a file showing examples of supported commands, but it's old and out of date. The SWFTools web site has a swfc manual, but it's really a tutorial rather than a manual, and its examples, though helpful, don't tell the whole story. I studied the examples, and other examples from other web sites (links can be found on the SWFTools links page), but in the end I had to use the swfc source code as my reference manual.
Second, the Flash Player environment iself throws roadblocks in the way of certain kinds of programming. I'll have more to say about that below.
My first efforts with swfc were mostly just variations on the examples shown in the swfc manual, and aren't really worth showing off here. Then, having learned to dog-paddle, I decided to dive into the deep end by arming myself with Adobe's ActionScript documentation and trying out some real Flash programming.
My first ActionScript success was a variation on an old Apple II demo called Brian's Theme. I've kept the original moire affect, but I've left out the agonizingly slow drawing of the original. This was a very straightforward translation of the original BASIC code, with no significant pitfalls along the way--so straightforward, in fact, that I was unable to resist enhancing it by adding some randomly-chosen color gradients.
Here's the original Applesoft BASIC source code, as printed in the Applesoft BASIC reference manual:
80 HOME : REM CLEAR THE TEXT AREA 100 VTAB 24: REM MOVE THE CURSOR TO BOTTOM LINE 120 HGR : REM SET HIGH-RESOLUTION GRAPHICS MODE 140 A = RND (1) * 279: REM PICK AN X FOR "CENTER" 160 B = RND (1) * 159: REM PICK A Y FOR "CENTER" 180 I% = ( RND (1) * 4) + 2: REM PICK A STEP SIZE 200 HTAB 15: PRINT "STEPPING BY ";I%; 220 FOR X = 0 TO 278 STEP I%: REM STEP THRU X VALUES 240 FOR S = 0 TO 1: REM 2 LINES, FROM X AND X+1 260 HCOLOR= 3 * S: REM FIRST LINE BLACK, NEXT WHITE 280 REM DRAW LINE THROUGH "CENTER" TO OPPOSITE SIDE 300 HPLOT X + S,0 TO A,B TO 279 - X - S,159 320 NEXT S,X 340 FOR Y = 0 TO 158 STEP I%: REM STEP THRU Y VALUES 360 FOR S = 0 TO 1: REM 2 LINES, FROM Y AND Y+1 380 HCOLOR= 3 * S: REM FIRST LINE BLACK, NEXT WHITE 400 REM DRAW LINE THROUGH "CENTER" TO OPPOSITE SIDE 420 HPLOT 279,Y + S TO A,B TO 0,159 - Y - S 440 NEXT S,Y 460 FOR PAUSE = 1 TO 1500: NEXT PAUSE: REM DELAY 480 GOTO 120: REM DRAW A NEW PATTERN
And here's the swfc translation:
.flash bbox=280x200 filename=brian.swf export=0 .font f filename=/usr/X11/lib/X11/fonts/TTF/luximr.ttf glyphs=" 2345Sbeginpty" .edittext stepby font=f size=12pt width=280 height=14 noselect align=center color=white readonly .put stepby 140 180 pin=center .action: var scrn = this; function randcomp() { return Math.floor(Math.random()*192)+64; } function rgb(r, g, b) { return (r<<16)+(g<<8)+b; } function runBriansTheme() { var x, rn, gn, bn; var a = Math.floor(Math.random()*279); /* center X */ var b = Math.floor(Math.random()*159); /* center Y */ var s = Math.floor(Math.random()*4)+2; /* step */ var r1 = randcomp(); /* color 1 */ var g1 = randcomp(); var b1 = randcomp(); var r2 = randcomp(); /* color 2 */ var g2 = randcomp(); var b2 = randcomp(); var rn = r1; /* current color */ var gn = g1; var bn = b1; var rs = (r2-r1)*s/280; /* color step */ var gs = (g2-g1)*s/280; var bs = (b2-b1)*s/280; scrn.clear(); stepby.text = "Stepping by "+s; for(x = 0; x<279; x += s) { scrn.lineStyle(1, 0); scrn.moveTo(x, 0); scrn.lineTo(a, b); scrn.lineTo(279-x, 159); scrn.lineStyle(1, rgb(rn, gn, bn)); scrn.moveTo(x+1, 0); scrn.lineTo(a, b); scrn.lineTo(278-x, 159); rn += rs; gn += gs; bn += bs; } rn = r2; gn = g2; bn = b2; rs = (r1-r2)*s/160; gs = (g1-g2)*s/160; bs = (b1-b2)*s/160; for(x = 0; x<159; x += s) { scrn.lineStyle(1, 0); scrn.moveTo(279, x); scrn.lineTo(a, b); scrn.lineTo(0, 159-x); scrn.lineStyle(1, rgb(rn, gn, bn)); scrn.moveTo(279, x+1); scrn.lineTo(a, b); scrn.lineTo(0, 158-x); rn += rs; gn += gs; bn += bs; } } setInterval(runBriansTheme, 5000); runBriansTheme(); .end .end
Next I tried another Apple II demo, Rod's Color Pattern, a.k.a. Kaleidoscope, which I've already tackled a couple of times before in 6502 machine language and in Java. Since Brian's Theme went so smoothly, I expected this one to be no different. I was in for a rude awakening.
My first attempt was a straight translation of the code from the Java version. It eventually drew the final pattern, but during the several minutes it took to reach the final pattern, it drew absolutely nothing at all, not even the black background.
The reason for this appears to be that when Flash Player is running ActionScript, it's not doing anything else, not even refreshing the display. In Java, I could put the drawing into its own thread, but that doesn't seem to be an option in ActionScript.
Fixing this required totally rewriting the code. The original uses three
nested loops, the inner one executing a total of 18,240 times, which
is far more that Flash Player can deal with comfortably. After a bit of
banging my head against some walls, I eventually came up with a routine
that runs the inner loop just once, and then updates the loop indices
for the next time, and then returns to let Flash Player do its redrawing
and event handling. I used setInterval()
with a short time
interval to make sure it gets called repeatedly.
At first it looked like this approach was working. But as I watched the patterns draw, I realized that it was gradually getting slower, and s.l.o.w.e.r, and s..l..o..w..e..r, etc. I never did figure out the real reason for this, but whatever the cause was, it turned out that my block-drawing code was triggering it. Originally I drew the blocks by simply drawing and filling a rectangle. The solution turned out to be to make each block a separate character (to use Macromedia/Adobe's terminology) on the display, and change its color as necessary, rather than drawing new colors on top of old colors.
Perhaps someobdy more familiar than I with Flash's guts could tell me exactly why this is an issue. My theory is that Flash Player is trying to do some sort of buffering, saving the previous contents of each block in case it might get uncovered again later, but I'm not sure.
Anyway, here's how I finally got it to work:
.flash bbox=280x200 filename=kaleid.swf export=0 .box idle 50 20 color=white fill=black .box hover 50 20 color=white fill=#555555 .box press 50 20 color=white fill=#AAAAAA .outline tri: M -4 7 L 4 0 L -4 -7 L -4 7 .end .filled ftri outline=tri color=white fill=green .button gobut .show idle as=area .show idle as=idle .show hover as=hover .show press as=pressed .on_release: runRCP(); .end .end .sprite scrn .end .put gobut 140 180 pin=center .put ftri 140 180 .put scrn .action: /* classic NTSC colors, from Linards Ticmanis: */ /* var pal = [0, 0x8A2140, 0x3C22A5, 0xC847E4, 0x07653E, 0x7B7E80, 0x308FE3, 0xB9A9FD, 0x3B5107, 0xC77028, 0x7B7E80, 0xF39AC2, 0x2FB81F, 0xB9D060, 0x6EE1C0, 0xFFFFFF]; */ /* IIGS RGB colors, from IIGS Technical Note #63: */ var pal = [0, 0xDD0033, 0x000099, 0xDD22DD, 0x007722, 0x555555, 0x2222FF, 0x66AAFF, 0x885500, 0xFF6600, 0xAAAAAA, 0xFF9988, 0x11DD00, 0xFFFF00, 0x44FF99, 0xFFFFFF]; var intid = null; var w, i, i40, j, k, k40, iw, iw12, s, dp = 1; s = new Array(40); for(i = 0; i<40; i++) s[i] = new Array(40); function plot(x, y, c) { var t; if(s[x][y] == undefined) { t = new Object; t.b = scrn.createEmptyMovieClip("b"+dp, dp); dp++; t.b._x = x*7; t.b._y = y*4; t.b.beginFill(0); t.b.moveTo(0, 0); t.b.lineTo(7, 0); t.b.lineTo(7, 4); t.b.lineTo(0, 4); t.b.lineTo(0, 0); t.b.endFill(); t.c = new Color(t.b); s[x][y] = t; } s[x][y].c.setRGB(c); } function doOne() { var c = pal[Math.floor(j*3/(i+3))+iw12&15]; plot(i, k, c); plot(k, i, c); plot(i40, k40, c); plot(k40, i40, c); plot(k, i40, c); plot(i40, k, c); plot(i, k40, c); plot(k40, i, c); j++; k++; k40--; if(j>19) { j = 0; i++; i40--; iw += w; if(i>19) { i = 1; i40 = 39; w++; iw = w; if(w>50) { if(intid!=null) { clearInterval(intid); intid = null; } return; } } k = i; k40 = 40-k; iw12 = Math.floor(iw/12); } updateAfterEvent(); } function runRCP() { if(intid!=null) clearInterval(intid); for(i = 0; i<40; i++) for(j = 0; j<40; j++) plot(i, j, 0); w = 3; i = 1; i40 = 39; j = 0; k = 1; k40 = 39; iw = 3; iw12 = 0; intid = setInterval(doOne, 10); } _quality = "LOW"; runRCP(); .end .end
The point of the 6502 version was to make
it run quickly. The point of the Flash version ended up being just to
get it running somehow...there doesn't seem to be much than can be done
to speed it up. Decreasing the setInterval()
time might
help on really fast machines, but on my computer (a 2 GHz Core 2 Duo),
the current value (10) has already reached the point of diminishing
returns, with a total running time of about 4 minutes and 10 seconds.
That's about all I have to show off for now, but as as the mood strikes me to come up with new examples, I'll add them to this page.
Original: October 20, 2008
Modified: November 10, 2008--Fixed title; fancier colors for Brian's
Theme; minor update to Rod's Color Pattern.