News:

2026.08.23 The "Files" area is now visible to guests/spiders

Main Menu

TETRIS for Lisa Office System 3.0

Started by ried, September 07, 2026, 07:09:42 AM

Previous topic - Next topic

ried

I'm very happy to share something I've been working on for the last few days: TETRIS for Lisa Office System 3.0.

This is the first piece of software I have ever worked on and feedback is welcome and appreciated. I came into this project with essentially no application development experience, and I came out of it having learned an enormous amount about the Lisa, Pascal, QuickDraw, QuickPort, Lisa Office System tools, serial transfers, linking, keyboard handling, and some of the wonderfully peculiar realities of developing software on a forty-plus-year-old computer.

The project actually began on a Lisa 1 running Pascal Workshop 1.0.

Rather than immediately trying to make a Lisa Office System application, I first wanted to see if the game itself could work on a Lisa. I enlisted ChatGPT to write the Pascal and guide me through the Workshop environment, building the game incrementally as TETRIS1 through TETRIS11. Eleven total builds.

Those versions were essentially the laboratory. Step by step, establishing the core engine: all seven tetrominoes, rotation, collision detection, gravity, stacking, line clearing, score, lines, levels, NEXT-piece display, pause, restart, game over, and eventually sound. By the time TETRIS11 was built late (late) Friday night, there was a complete and reliable Tetris engine running under Workshop 1.0. I was stoked, to say the least.

That turned out to be a very useful path. TETRIS11 served as a known-good reference implementation, so when moving to Lisa Office System development, it had already been confirmed that the game logic itself worked.

The next goal was much more ambitious: make Tetris into a real Lisa Office System 3.0 application, with a native LOS window, Desktop icon, Tool installation, and proper Lisa user-interface behavior. For that work I moved to Workshop 3.0 and QuickPort.

This part of the project involved quite a bit of archaeology. One of the most important discoveries came from stepleton's earlier adventures with ToolKit 3. I learned that some copies of TKLIB and TK2LIB supplied with the ToolKit 3 disks could produce binaries that would not work properly with LOS 3.0, and that the correct versions needed to come from the Workshop/QuickPort installation instead. Knowing that would have saved me almost half a day chasing issues.

Tom's discovery mattered a great deal, because an early mixed Workshop/ToolKit environment produced a LOS crash, exiting to the ROM with a 10201 error. Rather than continuing to fight that installation, I built a fresh Workshop 3.0 environment from Bitsavers' archived Workshop disks and installed only the QuickPort-related material and the appropriate library versions. That was the breakthrough.

A tiny QuickPort test application launched successfully in LOS 3.0, drew into a proper native Lisa window, and behaved normally. From there, I started transplanting the proven TETRIS11 engine into the QuickPort application.

The LOS version grew incrementally, just as the Workshop version had. The final application uses a native LOS Tool installed with InstallTool, with its own T tetromino Desktop icon and the native window title TETRIS.

Some of the things that had to be figured out along the way include:
  • adapting the playfield for the Lisa's non-square pixels
  • settling on 18×15-pixel cells so the pieces look visually correct
  • implementing a dark playfield presentation with outlined white blocks
  • removing the internal grid to improve both appearance and redraw performance
  • implementing differential redraws so moving pieces do not repaint the entire tetromino (redraws are still quite noticeable, though)
  • using Lisa keyboard hardware polling rather than relying entirely on typematic key repeat
  • making K perform a single-row drop when tapped, a fast soft drop when held, and an instant hard drop when rapidly double-tapped
  • preserving pause, score, lines, levels, NEXT, game over, and new-game behavior
  • adding a blinking GAME OVER
  • adding appropriate Lisa speaker sounds for line clears and four-line Tetrises
  • tuning the higher levels so the game becomes genuinely difficult rather than reaching an artificial speed ceiling

One particularly interesting QuickPort lesson came from longer play sessions. QuickPort records graphic operations into a QuickDraw Picture so the contents of a scrolling window can be reconstructed. That Picture has a roughly 32K size limit. Tetris is almost a worst-case application for this mechanism because every moving piece continually adds drawing operations. Eventually QuickPort warned that its Picture was running out of space, and after dismissing the warning I saw what looked almost like my previous game moves being replayed at high speed. That was odd!

ChatGPT's solution was to use QPGrafPicSize to monitor the size of the saved Picture. When it approaches a safe threshold (20K), Tetris erases the entire QuickPort grafPort—which starts a new Picture—and reconstructs the current game screen entirely from its logical state. That works because the board array and piece state are the authoritative game state; the graphics are only a representation of it. The full-window refresh is still visible for a moment, but it keeps the application stable and playable indefinitely. You get used to it after a while, too.

That was one of my favorite lessons from the project because it illustrates how software architecture and the Lisa's own design constraints intersect.

I want to be completely transparent about how this software was created: ChatGPT wrote the Pascal code. All of it. My role was much more like that of the operator, tester, integrator, and increasingly the designer of the application. ChatGPT guided me through Workshop, wrote and revised the source, interpreted compiler errors, helped reconstruct the QuickPort environment, researched Lisa documentation and historical material, and proposed solutions as we encountered problems. I transferred the code to the Lisa, compiled it, generated and linked it, installed Tools, tested everything on the actual hardware, reported what happened, and made the decisions about how I wanted the game to behave and look.

Because this was my first software project, that collaboration became an unexpectedly effective way for me to learn. Rather than simply receiving a finished program, I spent days watching the consequences of individual changes on the actual Lisa and gradually learning what all of these pieces were doing.

The biggest lesson for me is that developing for the Lisa involves much more than simply writing Pascal. You are dealing simultaneously with Pascal language limitations, QuickDraw, QuickPort, LOS Tool installation, intrinsic libraries, keyboard hardware, the Lisa display, storage formats, serial transfer, and development tools from several different generations of the machine.

One tiny example: Lisa Pascal only considers the first eight characters of an identifier significant. I learned that the hard way when variables named gameOver, gameOverVisible, and gameOverBlink produced a spectacular cascade of compiler errors because, as far as the compiler was concerned, they were effectively the same identifier.

Another was discovering that a routine called Tone, which had worked in the Workshop 1.0 version, was not the appropriate interface in the QuickPort environment. The Workshop 3/QuickPort version instead uses the Lisa Hardwareunit's Beep routine.

These are the sorts of details that are nearly invisible when looking at the final application, but they taught me more than I expected. At this point, TETRIS is a fully playable native LOS 3.0 application with:
  • all seven tetrominoes
  • rotation and collision detection
  • gravity and escalating levels
  • line clearing
  • scoring
  • NEXT-piece preview
  • pause
  • new game after game over
  • single-step, soft-drop, and hard-drop controls
  • line-clear and Tetris sounds
  • blinking game-over indication
  • a native LOS Tool icon and window
  • protection against QuickPort's saved-Picture size limit

There may also be a small hidden dedication screen in there somewhere.  :)

For me, though, the most important result is not actually Tetris. It is that a few days ago I had never worked on a piece of software before, and now I understand considerably more about how Lisa software is constructed, compiled, linked, installed, drawn, and executed than I did when I started. I owe a great deal of that education to this community.

sigma7, stepleton, AlexTheCat123, everyone here as a whole have collectively preserved an extraordinary amount of knowledge about these machines. Your products and work, old posts, documentation, tools, and experiments made it possible for someone like me to sit down and actually learn how to build something new for a Lisa.

I'm tremendously grateful. Thank you.

EDIT: I removed the QuickPort build that was attached to this post, though the source file is still attached. Please use the Native ToolKit version attached to this post instead.

P.P.S. If anyone wants that Workshop 1.0 version to see Tetris playing on his or her Twiggy Lisa, let me know  8)

ried

#1
Here's the TETRIS11 source file for Pascal Workshop 1.0 (.zip attached). The final build added sound.

ried

TETRIS for LOS is perfectly at home on the LisaFPGA. Turning on the speed select switches boosts performance dramatically, of course.


sigma7

Quote from: ried on September 07, 2026, 07:09:42 AMI'm very happy to share something I've been working on for the last few days: TETRIS for Lisa.

Wow, very impressive, and thanks for the informative troubleshooting tidbits.

When you say "last few days" about how many hours of your time to accomplish this?

It will be interesting to see how quickly AI becomes more proficient at making LOS apps.

And, I wonder what the community will come up with next...
Warning: Memory errors found. ECC non-functional. Verify comments if accuracy is important to you.

ried

#4
Quote from: sigma7 on September 07, 2026, 03:05:24 PMWhen you say "last few days" about how many hours of your time to accomplish this?

I probably spent 12+ hours each day learning, iterating, and testing. Maybe 30-35 hours in total on the project? Much of that was i.e. waiting for files to transfer between my laptop and the Lisa's serial port at 300 baud to ensure no dropped characters. An experienced software engineer could do all of this in a fraction of the time, and something like the LisaFPGA could make it much, much faster still vs. doing it on original hardware.

In this case, I needed two separate volumes attached to my Lisa 2/5: One LOS 3.0 installation attached to the built-in parallel port, and the other a Workshop 3.0 installation attached to the parallel expansion card. It was a lot of fun.

AlexTheCat123

Quote from: ried on September 07, 2026, 07:09:42 AMI'm very happy to share something I've been working on for the last few days: TETRIS for Lisa Office System 3.0.

Nice work; it's great to see some more people developing LOS apps! It's funny that you chose to make Tetris, because before I (Claude) made Minesweeper, I made a Tetris app as a proof-of-concept. I never released it because I intended it to just be a proof of concept, but maybe I should now. And just like you, I started by getting it running in the Workshop and then ported it to LOS after that was working well. The major difference between our implementations is that you used QuickPort for the LOS version, while I made a native LOS app like with Minesweeper.

Quote from: ried on September 07, 2026, 03:32:37 PMI probably spent 12+ hours each day learning, iterating, and testing. Maybe 30-35 hours in total on the project?

Giving it the right resources and prompt can speed this up quite significantly, and give you something that's pretty close to functional on the first try, with just feature enhancements and UI refinements necessary from there. Whenever I made my apps, I gave Claude the full LOS source code, the boot ROM listing, the LisaFPGA source code, the Lisa Hardware Manual, a several page-long prompt, and several other documents that I think I'm forgetting. All of this data worked wonders, and I'd say that I only spend 6-12 hours on each of my apps because of how many resources it had at its disposal to fact-check its work. I guess I also had a lot of extra knowledge of Lisa programming (like the 8-character rule) that I was able to tell it up-front, so someone who's new to this would take a bit longer, but I bet you could still cut that 30-35 hour number down if you gave it more resources!

Once again, great work! I just tried it on one of my LisaFPGA boards and it runs really well.

stepleton

Congratulations on making this software! Claude's idea about monitoring QPGrafPicSize is something I wish I'd done in LisaMandelbrot Port, which just blanks the screen and redraws every so often based on raw experimentation with raw experimentation.

As for the 2014 post of mine you linked to --- I'd forgotten all about that!

bmwcyclist

Fantastic!

Any ideas on what you're going to work on next?
Using my LISA for writing blogs and other work projects, and for fun and games at home.
Visit my shares on GlobalTalk! LISAGamingZone
Check out the NEW Lisa Software! https://github.com/GyroPilot

ried

Quote from: bmwcyclist on Yesterday at 08:31:04 PMAny ideas on what you're going to work on next?

Oh, that's easy! It's attached. TETRIS was completely rebuilt to move from QuickPort to the Native ToolKit today. No, I did not know how painful this was going to be :P  But I learned a lot!

I ran into my first hiccup last night, a couple of hours into the update. I was still using the original session and I supposed I maxed out the context window... ChatGPT suddenly had to end the chat, and I had to start a new one. That was unexpected, and I lost quite a bit of momentum having to start a new session and bring the AI up to speed again. It had some memory of what I was doing previously, including both the initial Workshop 1.0 version and the QuickPort version for LOS, but the last save state that it had access to was before the QuickPort version was even completed. I had to upload that finished 1.0 source code again to bring it up to speed. The loss of context slowed me down significantly. :(

Anyway, we eventually got it done. The net result is an extremely responsive and fun implementation of TETRIS for the modest 5MHz 68K inside. If you tried the initial QuickPort version, please try this native version instead. It's better in every way. The source code and release notes are attached, along with the 400K .dc42 disk image file.


Here's the recap of challenges and solutions from ChatGPT:

---

## From QuickPort to Native ToolKit: Rebuilding TETRIS 1.0 for Lisa Office System 3

I recently finished moving my TETRIS 1.0 build from a QuickPort-based application to a fully native Lisa ToolKit application for Lisa Office System 3. I thought it might be useful to write up the experience, because several of the hardest parts were not where I expected them to be.

The original QuickPort version was already a complete game: all seven tetrominoes, rotation, collision, gravity, line clearing, scoring, levels, NEXT preview, pause, game-over handling, K-key soft and hard drop behavior, sounds, and a hidden dedication screen. The goal of the native rewrite was therefore not to redesign the game, but to preserve its behavior while replacing the QuickPort machinery underneath it with native ToolKit code.

That distinction became the guiding principle for the whole effort:

> Preserve what TETRIS 1.0 does; replace how QuickPort makes it happen.

### Starting point: Apple's 1Boxer example

The native version was built from Apple's 1Boxer ToolKit demo under Workshop 3.0. That gave us a real ToolKit process, document manager, window, view, selection, phrase file, and MAKE workflow to build from.

The standard build eventually became:

<TK/MAKE(1Boxer,557,PARAPORT)
For development I used Tool ID 557, with Tool 556 remaining the original QuickPort Tetris.

At first, the obvious work was mechanical: replace the Boxer demo behavior with a Tetris board, piece geometry, movement, gravity, scoring, etc. That part actually went quite smoothly. The larger surprises came later.

The first unexpected problem: XRead was missing

Once I needed an extra Pascal unit to isolate low-level keyboard and sound support, I enabled `X1BOXER.TEXT`, which is the ToolKit mechanism for specifying additional units.

The build immediately failed because `TK/XRead` could not be found.

That was confusing because MAKE clearly expected it. After digging through the ToolKit disks, we discovered that `TK/XRead.TEXT` lives on **ToolKit 3.0 Disk 3**, along with several other MAKE support utilities.

Once `TK/XRead.TEXT` was copied into the Workshop environment, the X-file build path worked exactly as Apple intended.

That was an important lesson in itself: a Workshop installation can appear complete enough to build normal ToolKit applications while still be missing one of the helper files needed for less-common MAKE configurations.

The second surprise: HWINT collided with ToolKit

The native game needed physical key-state information for things that ordinary character callbacks cannot reliably provide, especially the special K-key behavior and the hidden Command/Option/T chord.

The natural attempt was to add:

pascal
USES HWINT

to the main ToolKit unit.

That failed with:

text
*** Error 100 *** Identifier declared twice.
*** File #BOOT-TKLIB.OBJ ***

In other words, HWINT and ToolKit introduced namespace collisions.

The solution was to create a tiny wrapper unit, `U1KEYS`, that imports HWINT by itself and exposes only the few services Tetris actually needs:

pascal
FUNCTION KeyDown(keyCode: INTEGER): BOOLEAN;
FUNCTION SecretDown: BOOLEAN;
FUNCTION SecretAny: BOOLEAN;
PROCEDURE PlayTone(waveLen,duration: LONGINT);
PROCEDURE ToneGap(duration: LONGINT);

The main Tetris unit then imports `U1KEYS`, not HWINT directly.

That completely solved the collision and turned out to be a clean architecture anyway.

The big performance failure

The first native version with full K-key controls worked functionally, but performance was terrible.

An untouched piece could take roughly **22 seconds** to reach the bottom. Movement visibly flickered. All four blocks of the active tetromino appeared to redraw on every step. Keyboard response was sluggish enough that the K double-tap hard drop was difficult to trigger reliably.

This was the most important debugging point in the whole project.

The problem was not the game logic. It was the rendering strategy.

The initial native implementation used ToolKit invalidation for normal piece movement. Every move invalidated an area, and the ToolKit `Draw` method then reconstructed the game state inside that region. That included scanning the board and redrawing the active piece.

This was architecturally correct in a conventional GUI sense, but far too expensive for something moving every fraction of a second on a Lisa.

The original QuickPort version gave us the clue. Its movement code did not redraw the world. It erased only cells belonging to the old piece that were no longer occupied, and drew only cells newly occupied by the moved piece.

So we rebuilt the native movement path around exactly that idea.

A `DrawDiff` routine compares the old and new tetromino positions. Ordinary movement goes through a `FastMove` routine which immediately paints those differences directly on all pads:

pascal
SELF.panel.OnAllPadsDo(PaintMove)


No ToolKit invalidation. No board scan. No full piece redraw.

The ordinary J/L/I movement path, K soft drop, gravity, and hard drop all use this direct differential renderer. The normal ToolKit `Draw` method still exists as the authoritative renderer for exposure events, line clears, new games, and other cases where a complete reconstruction is appropriate.

That change transformed the program.

On the real Lisa, the result went from sluggish and flickery to extremely responsive. Input latency essentially disappeared, and the native game finally felt like the QuickPort version.

This was probably the single biggest lesson of the project: on the Lisa, ToolKit invalidation is excellent for maintaining correctness, but it does not necessarily belong in the animation hot path.

Physical keyboard polling also mattered

Another performance issue was more subtle.

An early version called HWINT repeatedly during every idle tick to check keyboard state and timing. Individually those calls seemed harmless, but on the real machine they added up.

The final architecture uses ToolKit's `GetTime`, which is already available in centiseconds, for all gameplay timing.

Physical keyboard polling is sparse. K is only polled while K is known to be held or while the program is waiting for its release. P is only polled when necessary for pause release tracking. The hidden dedication chord is checked at a throttled rate instead of on every idle pass.

So during normal falling-piece animation, the game avoids unnecessary HWINT traffic.

That turned out to matter.

Real hardware calibration beat theoretical timing

The QuickPort source used a nominal gravity interval of 126, but simply copying that number into the native ToolKit version did not reproduce the same feel.

We timed both versions on the actual Lisa.

The released QuickPort game took just over 18 seconds for the first untouched piece to reach the bottom. After calibration, the native version settled on:

pascal
BaseGravity = 104;
GravityStep = 13;
MinGravity  = 7;

That produced approximately the same real-world Level 1 fall time.

This is another case where source-level timing constants do not necessarily transfer directly between architectures. Measuring the user-visible behavior on the real hardware was more useful than assuming equivalent numeric values would produce equivalent timing.

Sound produced another hardware surprise

The line-clear tone was straightforward, but the four-note Tetris jingle initially played as only a single audible high tone.

The code looked reasonable: call `Beep` four times in sequence.

On the real Lisa, though, it became apparent that HWINT `Beep` returns quickly enough that subsequent Beep calls can replace the previous tone before it finishes audibly.

The fix was to explicitly wait for each tone using HWINT `Timer`:

pascal
PROCEDURE PlayTone(waveLen,duration: LONGINT);
BEGIN
    Beep(waveLen,duration);
    ToneGap(duration)
END;

with `ToneGap` spinning on `Timer`.

After that, all four notes played correctly.

It was a good reminder that API behavior that looks synchronous from the call site may not actually behave that way on the machine.

A Lisa Pascal gotcha: significant identifier length [Edit: See what I mean when I said the AI forgets?]

Another issue worth mentioning is Lisa Pascal's effective significant-name limit.

Two method names that looked different to us could collide if their first significant characters were the same. Long descriptive names had to be chosen carefully, and in a few places shorter identifiers were intentionally used.

That also influenced state-field naming during later polish work.

Movable handles imposed another rule

We also ran into the Lisa object system's movable-handle safety checks.

This kind of expression caused an error:

pascal
SELF.gameOver := NOT SELF.PieceFits(...);

because calling a method while assigning through a movable `SELF` handle in the same statement is unsafe.

The safe version was:

pascal
spawnFit := SELF.PieceFits(...);
SELF.gameOver := NOT spawnFit;

That sort of constraint is easy to forget when coming from modern systems, but it is fundamental to writing safe Lisa ToolKit code.

Final polish stayed out of the hot path

Once performance was solved, we added the remaining presentation details: the cathedral pixel art, native sound, blinking GAME OVER, and the hidden dedication screen.

An important design decision was to keep all of that away from the proven movement path.

The cathedral is static and is drawn only by the normal authoritative renderer. GAME OVER blinking uses direct XOR text drawing rather than full invalidation. The dedication screen temporarily takes over the view, but preserves the game state underneath it.

The hidden screen is activated by holding:

text
Command + either Option + T

for one second.

The final cathedral artwork also needed some real-hardware adjustment. Lisa pixels are visually tall, so towers that looked reasonable numerically appeared too thin and vertical on the screen. We deliberately shortened and widened them and made the onion domes more bulbous to compensate for the display geometry.

One final build-system cleanup

Near the end, LisaList2 feedback raised another good point: avoid baking a specific Tool ID into source files.

That led us back into `TK:MAKE.TEXT`.

Apple's MAKE script reads the first two lines of `X1BOXER.TEXT` as **default** Tool Number and Tool Volume values. If those values are supplied explicitly to MAKE, the X-file values are not required.

So the final release version leaves those first two lines blank.

The source therefore has no dependency on Tool ID 557. I happened to build the release as:

text
<TK/MAKE(1Boxer,557,PARAPORT)

but another user can substitute a different free Tool ID without modifying the source.

The matching icon simply follows the same convention:

text
{T<tool-id>}ICON


For my build, that is:

text
{T557}ICON

Final result

The finished application is a native Lisa ToolKit program for Lisa Office System 3, with no QuickPort runtime.

It preserves the behavior of the original TETRIS 1.0 while using native ToolKit process/window/view/selection architecture, native keyboard callbacks, HWINT only through an isolated wrapper, a differential direct-draw animation path, native sound, and standard ToolKit build/install machinery.

The most useful takeaway for me was that the hardest part was not implementing Tetris.

It was learning where the abstractions should end.

ToolKit provides the correct application structure and redraw model, but the animation hot path needed to be much closer to the metal. HWINT was necessary, but had to be isolated and polled sparingly. Source-level timing had to be calibrated against physical behavior. And some of the most important bugs only became obvious on the real Lisa.

That combination—using the native framework where it helps, while respecting the performance characteristics of the actual hardware—is what finally made the native version feel right.

And now we have a native LOS 3 Tetris that is fast, responsive, rebuildable, and independent of QuickPort.