Retrochallenge, Retrochallenge 2016/01

Retrochallenge 2016/01: Post 1

(This is part one of the chronicle of my Retrochallenge 2016/01 submission, which is to port some classic Star Trek text games to the Mattel Aquarius. To jump to the beginning, go here.)

Let’s Play the HP Time Shared BASIC Version of STTR1

I recorded a “Let’s Play” video of an abbreviated session of STTR1, enough to exercise all of the commands at least once. This was very, very late night recording – my mental acuteness was the opposite of acute.

How/Where to Run the Original STTR1

In preparation for my Retrochallenge, I spent some time in December trying to learn about STTR1 and how to play the original game. Well, I assume the “original” original game, which ran on an SDS Sigma 7 computer is lost to us and now resides in bit heaven. But the HP Time Shared BASIC port survived because of its inclusion in HP’s User-Contributed Library and the success of the HP2000 Family. In the end, I found at least three options for playing the HP version of STTR1:

  1. Locate and restore (2) HP2000 Series computers. One unit is needed for multiplexing terminals and one to run Access/Time Shared BASIC. And don’t forget tape drives or paper tape readers. And the media (magnetic and/or paper tapes). So – this option is impossible.
  2. Download, compile, configure, and run the SimH simulator. If you haven’t heard of SimH, it is to minicomputers what MESS is to microcomputers – only difficult. I did, in fact, have success with this route after several weeks of trying and would like to document my setup and perhaps share some configuration files. But that is a lower priority at the moment. If I fail to document this, I did find everything I needed, (though not in any single, tidy package) at the HP2000 Yahoo Group but you’ll need to join the group to gain access to the necessary files.
  3. Telnet to an already-running instance of a simh simulator. There are two such machines available at the time of this writing due to the generosity of the HP2000 Yahoo Group members :
    mickey.publicvm.com
    hp2000.brighton.ac.uk

The “Too Long, Didn’t Watch” Version

So, assuming these machines are still listening when you read this, the telnet option is the most straight-forward way to try out STTR1.

To Launch STTR1:

  1. Launch a terminal, such as the classic ‘xterm‘, that won’t be confused by the HP’s strange End-of-Line characters.
  2. Do this:
    telnet mickey.publicvm.com or hp2000.brighton.ac.uk
    CTRL+m,CTRL+j
    HELLO-T001,HP2000,1
    GROUPS
    EXECUTE-*STTR1

For your convenience, the instructions for STTR1 have been pulled from the original source and reformatted in my previous post.

To Quit STTR1:

  1. Use Command 7,2
  2. Enter a long string at prompt for using the calculator. This causes a string overflow and breaks out of the program.
  3. BYE

These machines have many other early text games including 1975 version of Oregon Trail. When looking at the list of programs in the output of GROUPS, the programs with a “C” attribute are semi-compiled and may need to be started using the command EXE-*progname, otherwise you should be able to use EXE-progname.

Up Next

Due to incompatibilities between HP’s BASIC and the version of MS BASIC found on the Aquarius, I’m forced to climb into the head of a teenage programmer from 1972.

Retrochallenge, Retrochallenge 2016/01

STTR1 Instructions

Overview

Here are the instructions for the 1972 Star Trek text game as found(*) in STTR1’s BASIC listing.

(*) Modified from strictly upper case

SYMBOL DESCRIPTION
<*> Enterprise
+++ Klingon
>!< Starbase
* Star

Command 0 = Warp Engine Control

‘Course’ is a  circular numerical vector arrangement as shown. Integer and real values may be used. Therefore course 1.5 is half way between 1 and 2.

    4     3     2
      \   ^   /
        \ ^ /
  5 ------------- 1
        / ^ \
      /   ^   \
    6     7     8

     C O U R S E

A vector of 9 is undefined, but values may approach 9.

One ‘warp factor’ is the size of one quadrant. Therefore to get from quadrant 6,5 to 5,5 you would use course 3, warp factor 1.


Command 1 = Short Range Sensor Scan

Prints the quadrant you are currently in, including stars, Klingons, starbases, and the Enterprise; along with other pertinate information.


Command 2 = Long Range Sensor Scan

Shows conditions in space for one quadrant on each side of the Enterprise in the middle of the scan. The scan is coded in the form XXX, where the units digit is the number of stars, the tens digit is the number of starbases, and the hundreds digit is the number of Klingons.


Command 3 = Phaser Control

Allows you to destroy the Klingons by hitting him with suitably large numbers of energy units to deplete his shield power. Keep in mind that when you shoot at him, he gonna do it to you too.


Command  4 = Photon Torpedo Control

Course is the same as used in Warp Engine Control. If you hit the Klingon, he is destroyed and cannot fire back at you. If you miss, he will shoot his phasers at you.

Note: The Library Computer (Command 7) has an option to compute torpedo trajectory for you (Option 2).


Command 5 = Shield Control

Defines number of energy units to be assigned to shields. Energy is taken from the total ship’s energy.


Command 6 = Damage Control Report

Gives state of repairs of all devices. A state of repair less than zero shows that the device is temporarily damaged.


Command 7 = Library Computer

The library computer contains the three options:

Option 0 = Cumulative Galactic Record

Shows computer memory of the results of all previous long range sensor scans.

Option 1 = Status Report

Shows number of Klingons, stardates, and starbases left.

Option 2 = Photon Torpedo Data

Gives trajectory and distance between the Enterprise and all Klingons in your quadrant.

Retrochallenge, Retrochallenge 2016/01

Retrochallenge 2016/01: Prologue

Aquarius_Space_Key
…the final frontier

Goal: Star Trek for the Aquarius

My Retrochallenge for 2016/01 is to correct an error in the space-time continuum and bring several versions of the not-as-ubiquitous-as-I’ve-been-led-to-believe classic Star Trek text game to the Mattel Electronics/Radofin Aquarius (a machine that can confidently claim to have been retro since inception). The target platform is an Aquarius with 16K RAM running Martin v.d. Steenoven’s BootLoader BASIC v2.1.

Todo:

  1. Port Mike Mayfield’s STTR1 (1972) from HP 2000 Time Share BASIC
  2. Port Leedom & Ahl’s Super Star Trek (1975) from Microsoft BASIC
  3. Modify Super Star Trek to take advantage of features of the Aquarius

Along the way I hope to learn more about the history of the game (something that had already passed me by before I got my Atari 400 in 1983) and more about the Aquarius (my first, though sorely neglected, retro-as-a-hobby computer).

The Star Trek Text Game

Through the late sixties, versions of Star Trek-themed computer games bubbled up in academic computer labs such as Carnegie Mellon and Berkley. However it was high school student Mike Mayfield’s 1972 version that would define the genre. Originally played on a teletype terminal connected to an SDS Sigma 7 computer, he later ported the program to HP2000C Time Shared BASIC  in exchange for computer time. From there it was distributed on HP’s official Contributed Software tape library (named as STTR1) exposing it to a much greater audience.

The game was re-implemented and extended on disparate platforms in the years before the micro computer revolution. One of which was David H. Ahl & Mary Cole’s SPACWR found in Ahl’s 101 BASIC Computer Games, a book printed in 1973-75 by Digital Equipment Corporation. Another was Robert C. Leedom’s Super Star Trek that, as publisher of Creative Computing, Ahl included in the magazine’s May-June 1975 issue. The program listing was reprinted in other books and magazine compendiums around 1978, just as micros were becoming easily accessible. Having been ported to Microsoft BASIC, Super Star Trek pollinated to most of the micro computers of the era since that flavor had become the de facto standard.

Once microcomputers became sophisticated enough to incorporate color graphics the game lost its appeal. Atari’s Star Raiders exemplifies this transition from text to video games. At its core, it’s still a Star Trek style game with a quadrant-style map and emphasis on resource management. But now included arcade sequences to settle the battles.

Unfortunately for the Mattel Aquarius, it entered the market after the wave of Star Trek games had come and gone. And lacked the graphical prowess to compete with other home computers.

2015 Retrochallenge Winter Warmup, Retrochallenge

Retrochallenge 2015/01 – Final Post

(This is the final part of the chronicle of my Retrochallenge 2015/01 submission, which is to port the modern-day Apple II game, Structris, to the Atari 8-bit home computer using an obscure language called PL65. The mediocrity starts here.)

Before really jumping into it. Here are links I want to share:

Success! (that’s what I’m claiming)

I couldn’t have cut it any closer to the deadline, but I declare victory over my Retrochallenge 2015/01 project. Since the previous post, I’ve been able to put finishing touches on the game. These include:

  • Level advancement
  • Throttled game loop
  • Scoring
  • Level 11 and beyond
  • Allow movement after collision
  • Loading Time screen

Level Advancement

Each level range (1..10, 11..12, …) has a target number of rows to be collapsed before advancing to the next level. The game now tracks the rows collapsed and when the goal has been reached, play temporarily ends, and the playfield is animated to first drop all the pieces to the bottom, and then collapses the remaining pile. The Tetris well is cleared and re-drawn to be smaller than before. Levels 10, 20, 30, … are the most constrained.

Unfortunately my ongoing PL65 bug occurs during the animation sequence. The bug seems to occur when I use STRING datatypes. From my vantage point the PRIOR/GPRIOR register is being trespassed for some reason and the graphics mode will shift to either 8, 9, or 11 (or if I’m lucky, the desired mode, 10, will appear). It’s beyond my skill to find a solution for this one. I’ve decided to leave the bug in as Noahsoft’s artistic contribution since it does provide a startling exit to the current level and the graphics returns to normal when the game resumes.

Throttled Game Loop

The assembly code version of the “Scroll” routine is way too fast to allow running wide open. I played the original Apple version of the game and measured (crudely) the time it takes for a game piece to fall from top to bottom for a small sampling of levels. I derived a linear equation to variably adjust the game loop to match the timing on the Apple. I wouldn’t be surprised if I’m off and the Apple II’s speed increases with each level on a curved path rather than linearly. All I can say is it’s very playable.

Scoring

Simple. You get one point per row cleared. No bonuses for collapsing multiple rows at a time. Score is displayed in the text frame.

Level 11 and Beyond

Play advances beyond level 10, to match the competition version of Structis. The well returns to its level 1 size but your blip becomes mostly invisible, appearing maybe once every 5 seconds. If you make it to level 20, it’s even more invisible. But who’d go that far?

Allow Movement After Collision

I modified the keyboard routine to allow side-to-side movement after your blip comes into contact with a descending game piece. This feature is found in the Apple version but wasn’t working on my Atari version until now. This allows for some daring escapes.

Load Time Screens

Added the equivalent of the Apple’s version of the boot screen that contains abbreviated instructions and credits. Modified the “title” screen to include my name.

What’s Different?

Here’s some differences I’ve noticed between my version and the Apple version:

  • On the Atari version you can not descend faster than a falling piece. On the Apple version you can out-race a descending piece for an especially skillful escape.
  • The pink “S” shaped piece occasionally misses its cue. You may notice a pause when this piece is supposed to appear. If you stay where you are, it will appear on its second chance.
  • Level “cut-scene” animation glitches – but now it’s a feature.

Thanks, Retrochallenge!

Ton o’ fun. But now I need to get outside.

 

 

2015 Retrochallenge Winter Warmup, Retrochallenge

Retrochallenge 2015/01 – Post 06

Assembly Time

(This is part six of the chronicle of my Retrochallenge 2015/01 submission, which is to port the modern-day Apple II game, Structris, to the Atari 8-bit home computer using an obscure language called PL65. The mediocrity starts here.)

I was able to convert the scroll routine that moves the Tetris pieces down the screen from straight PL65 to 6502 Assembly. Recall that one of the brilliant virtues of Noahsoft’s PL65 is its ability to treat 6502 mnemonics as first-class commands. There are limitations and PL65’s documentation warns against intermingling PL65 and 6502 statements unless you have a fetish for devious bugs.

Though you can’t help but use INC MYVAR instead of MYVAR = MYVAR + 1.

Earlier this month, I was dismayed that PL65, by itself, couldn’t render quickly enough to match speed of the Apple version. The Apple does use a machine language routine to perform the scrolling. It was implemented using Ivan X’s Applesoft extension called Slammer, which embeds monitor or assembly instructions as REM statements. I’m still relatively new to 6502 Assembly, so I wasn’t planning on having to spend time trying to figure that one out.

Holy Crap! I May Finish This Yet.

The performance difference using the 6502 routine is astounding.

This will give me plenty of cycles to spare to help finishing up any lingering features. In fact, if I don’t run out of time, my plan is to throttle the Atari version to animate the pieces to match the speed of the Apple version. In Martin Haye’s Apple II game, as the player advances to the next level, the Tetris well reduces in size. A happy side effect is the computer has fewer pixels to move and therefore the pieces fall faster as the level increases.

But there’s still several features needed before calling this one done:

  1. The calculation of “Rows Cleared” needs to be corrected
  2. Once the goal for rows-cleared is completed, play should advance to the next round
  3. Match the game’s speed exactly to the Apple II version (may vary by level) – this should aid in the player’s blip being more visible.
  4. Add scoring
  5. General cleanup
  6. Stretch 1: Add German/Czech/Polish versions of the instructions)
  7. Stretch 2: Add other instructions to the BeweDOS batch file to better mimic the Apple II version

Code Dump

Here’s the logic for the scroll routine first in PL65 and then re-implemented as 6502 instructions:

!---------------------------------------
! SCROLL
!---------------------------------------
PROC SCROLL()
INT A1, A2
BYTE R, S, CL
BEGIN
  ! CHECK TO SEE IF BOTTOM ROW IS CLEAR
  CL = 0 R = 0 S = 0 A1 = ORIGIN P1 = ORIGIN
  WHILE V1 <> $88 DO
    IF V1 = $00 THEN 
      INC R 
    ELSE  
      INC S 
    ENDIF
    A1 = A1 + 2  P1 = A1
  ENDWHILE

  ! IF CLEARED
  IF R = 0 AND S > 0 THEN
    INC RC INC CL
  ENDIF

  R = CL
  S = HEIGHT2
  A1 = ORIGIN
  A2 = A1 - 32
  P1 = A1 P2 = A2 P3 = A1 + 1 P4 = A2 + 1
  WHILE V1 <> $88 DO
    WHILE S > 0 DO
      IF R = 0 AND V1 = $00 THEN R = 1 ENDIF
      IF R = 1 THEN 
        V1 = V2 V3 = V4
        IF S = 1 THEN V2 = $00 V4 = $00 ENDIF
      ENDIF 
      P1 = A2 P3 = A2 + 1 A2 = A2 - 32 
      P2 = A2 P4 = A2 + 1
      DEC S
    ENDWHILE
    A1 = A1 + 2 A2 = A1 - 32
    P1 = A1 P2 = A2 P3 = A1 + 1 P4 = A2 + 1
    S = 37 - LV
    R = CL
  ENDWHILE
END

CONST DUMMY4 = @
@ = $8800
!-----------------------------
! SCROLL2
!-----------------------------
PROC SCROLL2()
INT A1, A2
BYTE FILL_FL, HGT, CL
BEGIN
  ! CHECK TO SEE IF BOTTOM ROW IS CLEAR
  ! SCAN BOTTOM ROW
  !CL = 0 R = 0 S = 0
  LDA #$00
  STA CL
  STA FILL_FL
  STA HGT

  !A1 = ORIGIN
  LDA ORIGIN+1
  LDY ORIGIN
  STY A1
  STA A1+1

  !P1 = ORIGIN
  STY P1
  STA P1+1

  !WHILE V1 <> $88 DO
  LDY #$00
:wh01
  LDA (P1),Y
  CMP #$88
  BEQ endw01
  CMP #$00
  BNE inc_s
  INC FILL_FL
  GOTO endif01
:inc_s
  INC HGT
:endif01
  INY 
  INY 
  JMP wh01
:endw01

  !IF R = 0 AND S > 0 THEN
  LDA FILL_FL
  BNE endif04
  LDA HGT
  BEQ endif04
  INC RC
  JSR PRINT_ROWS_CLEARED
  INC CL
:endif04

  ! FILL_FL = CL
  LDA CL
  STA FILL_FL

  !HGT = HEIGHT2
  LDA HEIGHT2
  STA HGT

  ! A1 = ORIGIN
  LDA ORIGIN+1
  LDY ORIGIN
  STY A1
  STA A1+1

  !A2 = A1 - 32
  SEC
  LDA A1
  SBC #$20
  STA A2
  LDA A1+1
  SBC #$00
  STA A2+1

  !P1 = A1
  LDA A1+1
  LDY A1
  STA P1+1
  STY P1

  !P2 = A2
  LDA A2+1
  LDY A2
  STA P2+1
  STY P2

  !P3 = A1 + 1
  LDA A1
  LDY A1+1
  STY P3+1
  CLC
  ADC #$01
  STA P3
  BCC add02
  INC P3+1
:add02

  !P4 = A2 + 1
  LDA A2
  LDY A2+1
  STY P4+1
  CLC
  ADC #$01
  STA P4
  BCC add03
  INC P4+1
:add03

  !WHILE V1 <> $88 DO
:wh02
  LDY #$00
  LDA (P1),Y
  CMP #$88
  BNE wh03
  GOTO endw02

    !WHILE HGT > 0 DO
:wh03
  LDA HGT
  BEQ endw03

      !IF FILL_FL = 0 AND V1 = $00 THEN FILL_FL = 1 ENDIF
  LDA FILL_FL
  BNE endif02
  LDY #$00
  LDA (P1),Y
  BNE endif02
  LDA #$01
  STA FILL_FL
:endif02

      !IF FILL_FL = 1 THEN 
  LDA FILL_FL
  BEQ endif03

        !V1 = V2 V3 = V4
  LDY #$00
  LDA (P2),Y
  STA (P1),Y
  LDA (P4),Y
  STA (P3),Y

        !IF HGT = 1 THEN V2 = $00 V4 = $00 ENDIF
  LDA HGT
  CMP #$01
  BNE endif03
  LDA #$00
  STA (P2),Y
  STA (P4),Y
      !ENDIF 
:endif03

      !P1 = A2
  LDA A2+1
  LDY A2
  STA P1+1
  STY P1  

      !P3 = A2 + 1
  LDA A2
  LDY A2+1
  STY P3+1
  CLC
  ADC #$01
  STA P3
  BCC add04
  INC P3+1
:add04

      !A2 = A2 - 32 
  SEC
  LDA A2
  SBC #$20
  STA A2
  LDA A2+1
  SBC #$00
  STA A2+1

      !P2 = A2
  LDA A2+1
  LDY A2
  STA P2+1
  STY P2

      !P4 = A2 + 1
  LDA A2
  LDY A2+1
  STY P4+1
  CLC
  ADC #$01
  STA P4
  BCC add05
  INC P4+1
:add05

  DEC HGT
    !ENDWHILE
  JMP wh03
:endw03

    !A1 = A1 + 2
    CLC
    LDA A1
    ADC #$02
    STA A1
    BCC add01
    INC A1+1
:add01

!    A2 = A1 - 32
    SEC
    LDA A1
    SBC #$20
    STA A2
    LDA A1+1
    SBC #$00
    STA A2+1

    !P1 = A1
    LDA A1+1
    LDY A1
    STA P1+1
    STY P1

    !P2 = A2
    LDA A2+1
    LDY A2
    STA P2+1
    STY P2

   ! P3 = A1 + 1
    LDA A1
    LDY A1+1
    STY P3+1
    CLC
    ADC #$01
    STA P3
    BCC add06
    INC P3+1
:add06

    !P4 = A2 + 1
    LDA A2
    LDY A2+1
    STY P4+1
    CLC
    ADC #$01
    STA P4
    BCC add07
    INC P4+1
:add07

    !S = HEIGHT2
    LDA HEIGHT2
    STA HGT

    !R = CL
    LDA CL
    STA FILL_FL

    JMP wh02

  !ENDWHILE
:endw02
END
@ = DUMMY4

 

2015 Retrochallenge Winter Warmup, Retrochallenge

Retrochallenge 2015/01 – Post 05

Collapse

(This is part five of the chronicle of my Retrochallenge 2015/01 submission, which is to port the modern-day Apple II game, Structris, to the Atari 8-bit home computer using an obscure language called PL65. The mediocrity starts here.)

Only a minor difference in this version versus the previous. Now for each completed row, the pile of pieces will collapse. I was surprised that it didn’t take much additional code in the scroll routine to implement. The algorithm scans the pixels along the bottom of the well. If no $00 (background) pixels are found then the scroll routine performs the “copy pixel from above” from the bottom of the pile (and up) instead of the top of the pile (and up).

Common sense should have me working some easy wins to see this game nearly functionally equivalent to its Apple II hero. This would be keeping track of the rows completed and advancing to the next level. Instead I’m consumed with trying to convert the scroll routine from PL65 to 6502 assembly, for which I have no skill. But then again, common sense should have me working on something entirely different.

Up Next

Despair

2015 Retrochallenge Winter Warmup, Retrochallenge

Retrochallenge 2015/01 – Post 04

Pieces Finally Coming Together

(This is part four of the chronicle of my Retrochallenge 2015/01 submission, which is to port the modern-day Apple II game, Structris, to the Atari 8-bit home computer using an obscure language called PL65. The mediocrity starts here.)

This week I added a lot of code that couldn’t easily be tested individually. It’s the code that derives which Tetris piece will be generated based on the profile underneath the player’s current position. For a long while, just part of a single Tetris piece would appear at the top of the playfield and smear down the screen.

A late Friday/early Saturday debugging session delivered magical results. One correction to a variable assignment made things go from “nothing works” to “everything works” (well, everything I’ve put in up to now).

From watching the video, several annoyances are immediately apparent:

  • The scroll routine is still unoptimized and the pieces shear as the pixels are moved down the screen
  • The player’s blip is barely visible. The scroll routine is taking too long and the other routines seem to be running very quickly. I’m following Martin’s original algorithm which has the player’s blip invisible during the scroll routine and visible during the “derive next piece” routines.

Mind If I Crash Here

Earlier in the week, it was looking very dark when my project was causing the Atari to immediately crash. I started commenting out blocks of code in an attempt to narrow down the culprit. Nothing I changed in the code would alleviate the error. At one point my wife was reminding me that I didn’t *have* to finish the project. And I was beginning to listen.

The whole time I was assuming something was wrong in my code, but it turned out something on the floppy disk image or with BeweDOS had gotten corrupted. I’m sure I’m to blame. I’d gotten into the habit of rebooting the emulator whenever I saw a compilation error appear. I realize now, this is probably a bad thing to do when the Atari is writing to the disk.

Code Dump

Here’s the current state of my source code. With the intent of keeping the feel of the original Apple II game and (selfishly) to help finish this in a short timeframe, many of the critical algorithms were lifted straight from Martin Haye’s Applesoft BASIC source code.

INCLUDE D:GRAPHICS.LIB
INCLUDE D:PEEKPOKE.LIB

! MEMORY MAP
BYTE DL[490]          = $8DA0
BYTE FRAME_TEXT[120]  = $9F86
BYTE FRAME_GR[5120]   = $A000

! GLOBALS
INT SCORE, LV, OX
INT ORIGIN
BYTE WELL, FLOOR
BYTE W, HEIGHT, MH, CX, CY, M, X, Y
BYTE H0, HL, HR, HB
BYTE RG, RC, RD
BYTE CI, BL, B0, BR, SL, SX, SR
BYTE H[22]

POINTER P1, P2, P3, P4
BYTE V1 BASED P1
BYTE V2 BASED P2
BYTE V3 BASED P3
BYTE V4 BASED P4
BYTE KEYCH  = $02FC

! SYMBOLS
CONST GRWIDTH = $20

CONST LMARGN = $52
CONST ROWCRS = $54
CONST COLCRS = $55
CONST SAVMSC = $58
CONST CRSINH = $02F0
CONST SDMCTL = $022F
CONST SDLIST = $0230
CONST GPRIOR = $026F
CONST PCOLR0 = $02C0
CONST PCOLR1 = $02C1
CONST PCOLR2 = $02C2
CONST PCOLR3 = $02C3
CONST COLOR0 = $02C4
CONST COLOR1 = $02C5
CONST COLOR2 = $02C6
CONST COLOR3 = $02C7
CONST COLOR4 = $02C8
!CONST KEYCH  = $02FC

! HARDWARE REGISTERS
CONST COLPM0 = $D012
CONST COLPM1 = $D013
CONST COLPM2 = $D014
CONST COLPM3 = $D015
CONST COLPF0 = $D016
CONST COLPF1 = $D017
CONST COLPF2 = $D018
CONST COLPF3 = $D019
CONST COLBK  = $D01A
CONST PRIOR  = $D01B
CONST WSYNC  = $D40A

! COLORS
CONST BLACK  = $00
CONST GRAY   = $08
CONST WHITE  = $0F
CONST YELLOW = $2C
CONST ORANGE = $36
CONST PURPLE = $54
CONST PINK   = $5C
CONST DKBLUE = $64
CONST LTBLUE = $8C
CONST BLUE   = $84
CONST GREEN  = $C6

!---------------------------------------
! DISPLAY LIST INTERRUPTS
!---------------------------------------
CONST DUMMY=@
@=$8B00
INTERRUPT DLINT1()
INT VDSLST = $0200
BEGIN
  STA WSYNC
  LDA $84     STA PRIOR
  LDA #ORANGE STA COLPF1
  LDA #YELLOW STA COLPF2
  LDA #GRAY   STA COLBK
  VDSLST = $8C00
END

@=$8C00
INTERRUPT DLINT2()
INT VDSLST = $0200
BEGIN
  STA WSYNC
  LDA #$00   STA PRIOR
  LDA #WHITE STA COLPF1
  LDA #BLACK STA COLPF2 STA COLBK
  VDSLST = $8B00
END
@=DUMMY

!---------------------------------------
! DISPLAY INTERRUPT ENABLE
!---------------------------------------
PROC DLI_ENABLE()
INT  VDSLST = $0200
BYTE NMIEN  = $D40E
BEGIN
  VDSLST = $8B00
  NMIEN  = $C0
END

!---------------------------------------
! DISPLAY INTERRUPT DISABLE
!---------------------------------------
PROC DLI_DISABLE()
BYTE NMIEN  = $D40E
BEGIN
  NMIEN  = $00
END

!---------------------------------------
! RND returns a random 16 bit number
!---------------------------------------
FUNC RND*()
BYTE RANDOM=$D20A
BEGIN
  LDA RANDOM
  LDY RANDOM
  JSR PUSH
END

!---------------------------------------
! RAND returns a random number
!      in the range 0 to range-1
!---------------------------------------
FUNC RAND(BYTE range)
BEGIN
END RND()/(65535/range)

!---------------------------------------
! SET COLORS
!---------------------------------------
PROC SET_COLORS()
BEGIN
  POKE(PCOLR0, BLACK)
  POKE(PCOLR1, PURPLE)
  POKE(PCOLR2, BLUE)
  POKE(PCOLR3, DKBLUE)
  POKE(COLOR0, PINK)
  POKE(COLOR1, ORANGE)
  POKE(COLOR2, YELLOW)
  POKE(COLOR3, GREEN)
  POKE(COLOR4, GRAY)
END

!---------------------------------------
! SET DISPLAY LIST
!---------------------------------------
PROC SET_DL()
INT  I, J, K, GR_ADR
BEGIN
  POKE(SDMCTL,$00)
  DLI_DISABLE()
  GR_ADR = .FRAME_GR

  ! 2x 8 BLANK SCANLINES 
  ! 1x 8 BLANK SCANLINES & DLI
  DL[0] = $70
  DL[1] = $70
  DL[2] = $F0

  ! 160 LINES OF MODE 10
  ! EACH SET OF 4 SCANLINES 
  ! POINT TO SAME MEMORY RANGE
  K = 3
  FOR I = 0 TO 39 DO
    FOR J = 0 TO 3 DO
      DL[K] = $4F
      DOKE(.DL+K+1, GR_ADR)
      K = K + 3
    NEXT
    GR_ADR  = GR_ADR + $20
  NEXT 

  ! OVERWRITE LAST MODE F ADD DLI
  ! TO ENABLE NORMAL TEXT AT BOTTOM
  DL[K-3] = $CF

  ! TEXT FRAME
  DL[K+0] = $42
  DOKE(.DL+K+1, .FRAME_TEXT)
  DL[K+3] = $02
  DL[K+4] = $02
  DL[K+5] = $02

  ! JVB
  DL[K+6] = $41
  DOKE(.DL+K+7, .DL)

  ! INSTALL NEW DISPLAY LIST
  ! 1. TURN OFF ANTIC
  ! 2. POINT TO NEW DISPLAY_LIST
  ! 3. TURN ON ANTIC
  POKE(SDMCTL,$00)
  DOKE(SDLIST,.DL)
  POKE(SDMCTL,$21)

  ! ENABLE GTIA MODE 10
  POKE(GPRIOR,$84)
  POKE(PRIOR,$84)

  DLI_ENABLE()
END

!---------------------------------------
! GET KEY
!---------------------------------------
FUNC GET_KEY()
BYTE KEY
BEGIN
  CLOSE(1)
  OPEN(1,4,0,"K:")
  KEY = GET(1)
  CLOSE(1)
END KEY

!---------------------------------------
! MYSTR
!---------------------------------------
FUNC MYSTR$(INT NUM)
STRING SBUFF$[16]
BYTE A,B
BYTE BASE
BEGIN
  BASE = 10
  SBUFF$[15] = "0"; A = 16
  REPEAT
    DEC A
    B = NUM MOD BASE + 48
    IF B >= 58 THEN
      B = B + 7
    ENDIF
    NUM = NUM/BASE
    P1 = .SBUFF+A
    V1 = B
  UNTIL NUM = 0
END SBUFF$[A]

!---------------------------------------
! SET LEVEL
!---------------------------------------
PROC SET_LEVEL(INT L)
BEGIN
  LV = L
  WELL = LV MOD 10
  IF WELL = 0 THEN  WELL = 10 ENDIF
  FLOOR = 39 - 2 * WELL
END

!---------------------------------------
! HOME
!---------------------------------------
PROC HOME()
BEGIN
  WRTLN(CHR$(125))
END

!---------------------------------------
! HELP SCREEN
!---------------------------------------
PROC HELP_SCREEN()
BYTE KEY
BEGIN
  SETCOLOR(2,0,0)
  POKE(CRSINH,$FF)
  POKE(LMARGN,0) CR()
  HOME()
  WRTLN("WELCOME TO STRUCTRIS!")
  WRTLN("BY MARTIN HAYE, INTRO'D KFEST 2010")
  CR() CR()
  WRTLN("(FANCY OPENING SCREEN GOES HERE)")
  CR() CR()
  WRTLN("KEYS:")
  WRTLN("  I: UP")
  WRTLN("  J: LEFT")
  WRTLN("  K: RIGHT (ALTERNATE: L)")
  WRTLN("  M: DOWN") CR()
  WRTLN("BUILD UP THE TETRIS BLOCKS. YOU CANNOT")
  WRTLN("ROTATE THEM. EVIL PROGRAMMER LAUNCHES")
  WRTLN("BLOCKS WHERE YOU ARE TO TRAP YOU. DON'T")
  WRTLN("GET TRAPPED. FINISHED ROWS FALL AWAY.")
  CR()
  WRTLN("CLEAR ENOUGH ROWS: NEXT LEVEL! HARDER!")
  CR()
  WRTLN("HIT A KEY TO BEGIN THE TORTURE.")
  KEY = GET_KEY()
  POKE(CRSINH,0)
END

!---------------------------------------
! INIT
!---------------------------------------
PROC INIT()
INT I
BEGIN
  SET_LEVEL(1)
  DLI_ENABLE()
  SET_COLORS()
  SET_DL()
END

!---------------------------------------
! CLEAR WELL
!---------------------------------------
PROC CLEAR_WELL()
INT I, J
BEGIN
  P1 = .FRAME_GR
  FOR I = 0 TO 31 DO
    FOR J = 0 TO FLOOR DO
      V1 = $00
      P1 = P1 + 1
    NEXT
  NEXT
END

!---------------------------------------
! LPLOT
!---------------------------------------
PROC LPLOT (BYTE X, Y, C)
BEGIN
  P1 = .FRAME_GR + $20 * Y + X
  V1 = C
END

!---------------------------------------
! SCRN
!---------------------------------------
FUNC SCRN (BYTE X, Y)
BEGIN
  P1 = .FRAME_GR + $20 * Y + X
END V1

!---------------------------------------
! HLIN - PLOT HORIZ LINE AT Y=0
!---------------------------------------
PROC HLIN (BYTE X1, X2, C)
BEGIN
  WHILE X1 <= X2 DO
    LPLOT(X1,1,C)
    INC X1
  ENDWHILE 
END

!---------------------------------------
! LPRINT
!---------------------------------------
PROC LPRINT(BYTE X, Y INT ADDR BYTE LEN)
BEGIN
  P1 = .FRAME_TEXT + $20 * Y + X
  P2 = ADDR
  WHILE LEN > 0 DO
    V1 = V2 - $20
    P1 = P1 + 1
    P2 = P2 + 1
    DEC LEN
  ENDWHILE
END

!---------------------------------------
! PRINT LEVEL
!---------------------------------------
PROC PRINT_LEVEL()
BYTE D, L
BEGIN
  LPRINT(12,0,"LEVEL:     ")

  L = 1
  IF LV >= 10 THEN L = 2 ENDIF
  IF LV >= 99 THEN L = 3 ENDIF

  D = LV MOD 10 + $10
  POKE(.FRAME_TEXT + 17 + L, D)

  IF LV >= 10 THEN
    D = LV /  10 + $10  
    POKE(.FRAME_TEXT + 17 + L - 1, D)
  ENDIF

  IF LV >= 100 THEN
    D = (LV MOD 100) / 10 + $10
    POKE(.FRAME_TEXT + 17 + L - 1, D)

    D = LV / 100 + $10
    POKE(.FRAME_TEXT + 17 + L - 2, D)
  ENDIF
END

!---------------------------------------
! DRAW WELL
!---------------------------------------
PROC DRAW_WELL()
BYTE X1, X2, Y1, Y2
INT I
BEGIN
  X1 = WELL
  X2 = 31 - WELL
  Y1 = 41 - 2 * WELL - 1
  Y2 = Y1 + 1

  CLEAR_WELL()

  ! DRAW WALLS
  FOR I = 0 TO Y2 + 1 DO
    LPLOT(X1, I, $88)
    LPLOT(X2, I, $88)
  NEXT

  ! DRAW BLACK LINES OVER PREV WALLS
  FOR I = 0 TO Y2 DO
    LPLOT(X1-1, I, $00)
    LPLOT(X2+1, I, $00)
  NEXT

  ! DRAW CHECKBOARD1
  FOR I = X1 + 1 TO X2 - 1 STEP 2 DO
    LPLOT(I, Y1, $88)
    LPLOT(I, Y2, $22)
  NEXT

  ! DRAW CHECKBOARD1
  FOR I = X1 + 2 TO X2 - 1 STEP 2 DO
    LPLOT(I, Y1, $22)
    LPLOT(I, Y2, $88)
  NEXT

  PRINT_LEVEL()
END

!---------------------------------------
! PRINT NUM CLEARED
!---------------------------------------
PROC PRINT_NUM_CLEARED()
BEGIN
  LPRINT (11, 2, "CLEARED ")
END

!---------------------------------------
! LEVEL INIT
!---------------------------------------
PROC LEVEL_INIT()
INT I
BEGIN
  ORIGIN = .FRAME_GR + 1249 - 63 * WELL
  W = 15 - LV
  HEIGHT = 20 - LV
  MH = HEIGHT - 1
  OX = (32 - W * 2) / 2
  M = 0; HB = 0
  H[0] = -9 H[W + 1] = -9
  FOR I = 1 TO W DO H[I] = 0 NEXT
  X = -99
  CX = OX + W; CY = 37 - LV + 1
  RG = 5 + LV * 2; RD = 0; RC = 0 
  DRAW_WELL()
  PRINT_NUM_CLEARED()
END

!---------------------------------------
! SCROLL
!---------------------------------------
PROC SCROLL()
INT A1, A2
BYTE H, F
BEGIN
  F = 0; H = 37 - LV
  A1 = ORIGIN
  A2 = A1 - 32
  P1 = A1; P2 = A2; P3 = A1 + 1; P4 = A2 + 1
  WHILE V1 <> $88 DO
    WHILE H > 0 DO
      IF F = 0 AND V1 = $00 THEN F = 1 ENDIF
      IF F = 1 THEN 
        V1 = V2 V3 = V4
        IF H = 1 THEN V2 = $00 V4 = $00 ENDIF
      ENDIF 
      P1 = A2 P3 = A2 + 1 A2 = A2 - 32 
      P2 = A2 P4 = A2 + 1
      DEC H
    ENDWHILE
    A1 = A1 + 2 A2 = A1 - 32
    P1 = A1 P2 = A2 P3 = A1 + 1 P4 = A2 + 1
    F = 0; H = 37 - LV
  ENDWHILE
END

!---------------------------------------
! KB PROC
!---------------------------------------
FUNC KB_PROC()
BYTE RC
BEGIN
  RC = 1
  LPLOT(CX,CY,$00)
  SCROLL()
  IF SCRN(CX,CY) = $00 THEN 
    CASE KEYCH
      OF $0D DO IF SCRN(CX, CY-1) = $00 AND CY > 1 THEN DEC CY ENDIF ENDOF
      OF $01 DO IF SCRN(CX-1, CY) = $00            THEN DEC CX ENDIF ENDOF
      OF $05 DO IF SCRN(CX, CY+1) = $00            THEN INC CY ENDIF ENDOF
      OF $44 DO IF SCRN(CX, CY+1) = $00            THEN INC CY ENDIF ENDOF
      OF $00 DO IF SCRN(CX+1, CY) = $00            THEN INC CX ENDIF ENDOF
    ENDCASE
    KEYCH = $FF
  ELSE
    IF SCRN(CX,CY+1) <> $00 THEN 
      RC = 0
    ENDIF 
    INC CY
  ENDIF
  LPLOT(CX,CY,$66)
END RC

!---------------------------------------
! PICK_X_COORD
!---------------------------------------
PROC PICK_X_COORD()
BYTE I
BEGIN
  IF X > 128 THEN X = (CX - OX) / 2 + 1 ENDIF
  IF X > 128 OR X < 1 THEN X = -9 RETURN ENDIF
  IF X > W THEN X = -9 RETURN ENDIF
  IF X > 1 AND H[X-1] < H[X] THEN DEC X
  ELSE
    IF X < W AND H[X+1] < H[X] THEN INC X ENDIF
  ENDIF
  IF H[X] - HB < MH THEN INC M ENDIF
END

!---------------------------------------
! CALC_SHAPE_CONSTRAINTS
!---------------------------------------
PROC CALC_SHAPE_CONSTRAINTS()
BEGIN
  H0 = H[X]
  HL = H[X-1] - H0
  HR = H[X+1] - H0
  IF HR > 3   THEN HR = 3 ENDIF
  IF HL > 3   THEN HL = 3 ENDIF
  INC M
END

!---------------------------------------
! APPLY CHOSEN COORD
!---------------------------------------
PROC APPLY_CHOSEN_COORD()
BEGIN
  H[X-1] = H[X-1] + BL
  H[X]   = H[X]   + B0
  H[X+1] = H[X+1] + BR
  SX = (X - 1) * 2 + OX
  IF BL = 0 THEN SL = 0 GOTO L745 ENDIF
  BL = BL * 3; SL = HL * 3
:L745
  IF BR = 0 THEN GOTO L755 ENDIF
  BR = BR * 3; SR = HR * 3
:L755
  B0 = B0 * 3 
  INC M
END

!---------------------------------------
! CHOOSE_SHAPE
!---------------------------------------
PROC CHOOSE_SHAPE()
BYTE I
BEGIN
  INC M
  I =  HL * 4 + HR
  CASE I
    OF 0 DO 
      CASE RAND(4) 
        OF 0 DO CI = $11 BL = 1 B0 = 1 BR = 2 ENDOF
        OF 1 DO CI = $22 BL = 2 B0 = 1 BR = 1 ENDOF
        OF 2 DO CI = $66 BL = 1 B0 = 2 BR = 1 ENDOF
        OF 3 DO CI = $77 BL = 1 B0 = 1 BR = 1 ENDOF
      ENDCASE
    ENDOF
    OF 1 DO     CI = $44 BL = 1 B0 = 2 BR = 1 ENDOF
    OF 2 DO     CI = $22 BL = 0 B0 = 3 BR = 1 ENDOF
    OF 3 DO
      CASE RAND(3)
        OF 0 DO CI = $11 BL = 3 B0 = 1 BR = 0 ENDOF
        OF 1 DO CI = $22 BL = 1 B0 = 3 BR = 0 ENDOF
        OF 2 DO CI = $33 BL = 2 B0 = 2 BR = 0 ENDOF
      ENDCASE
    ENDOF
    OF 4 DO     CI = $55 BL = 1 B0 = 2 BR = 1 ENDOF
    OF 5 DO     CI = $66 BL = 1 B0 = 2 BR = 1 ENDOF
    OF 6 DO
      CASE RAND(2)
        OF 0 DO CI = $44 BL = 2 B0 = 2 BR = 0 ENDOF
        OF 0 DO CI = $66 BL = 1 B0 = 3 BR = 0 ENDOF
      ENDCASE
    ENDOF
    OF 7 DO
      CASE RAND(2)
        OF 0 DO CI = $44 BL = 2 B0 = 2 BR = 0 ENDOF
        OF 0 DO CI = $66 BL = 1 B0 = 3 BR = 0 ENDOF
      ENDCASE
    ENDOF
    OF 8 DO     CI = $11 BL = 1 B0 = 3 BR = 0 ENDOF
    OF 9 DO     CI = $11 BL = 1 B0 = 3 BR = 0 ENDOF
    OF 10 DO
      CASE RAND(2)
        OF 0 DO CI = $11 BL = 1 B0 = 3 BR = 0 ENDOF
        OF 1 DO CI = $22 BL = 0 B0 = 3 BR = 1 ENDOF
      ENDCASE
    ENDOF
    OF 11 DO    CI = $11 BL = 1 B0 = 3 BR = 0 ENDOF
    OF 12 DO
      CASE RAND(3)
        OF 0 DO CI = $11 BL = 0 B0 = 3 BR = 1 ENDOF
        OF 1 DO CI = $22 BL = 0 B0 = 1 BR = 3 ENDOF 
        OF 2 DO CI = $33 BL = 0 B0 = 2 BR = 2 ENDOF
      ENDCASE
    ENDOF
    OF 13 DO
      CASE RAND(2)
        OF 0 DO CI = $55 BL = 0 B0 = 2 BR = 2 ENDOF
        OF 1 DO CI = $66 BL = 0 B0 = 3 BR = 1 ENDOF
      ENDCASE
    ENDOF
    OF 14 DO    CI = $22 BL = 0 B0 = 3 BR = 1 ENDOF
    OF 15 DO    CI = $77 BL = 0 B0 = 3 BR = 0 ENDOF
  ENDCASE
END

!---------------------------------------
! PLOT_A_LINE
!---------------------------------------
PROC PLOT_A_LINE()
BEGIN
  IF BL = 0 THEN GOTO L835 ENDIF
  IF SL > 0 AND SL < 128 THEN 
      DEC SL
      GOTO L835
  ENDIF
  DEC BL
  HLIN(SX-2,SX-1,CI)
  IF SL <> 0 THEN LPRINT (0,0,"WHOA!") ENDIF
:L835
  IF B0 = 0 THEN GOTO L850 ENDIF
  DEC B0
  HLIN(SX,SX+1,CI)
:L850
  IF BR = 0 THEN GOTO L870 ENDIF
  IF SR > 0 AND SR < 128 THEN
    DEC SR
    GOTO L870
  ENDIF
  DEC BR
  HLIN(SX+2,SX+3,CI)
:L870
  IF BL + B0 + BR = 0 THEN
    X = -99
    M = 0
  ENDIF
END

!---------------------------------------
! MYDEBUG
!---------------------------------------
PROC MYDEBUG()
BEGIN
  LPRINT(0,0,"CX:  ") LPRINT(3,0,MYSTR$(CX))
  LPRINT(0,1,"CY:  ") LPRINT(3,1,MYSTR$(CY))
  LPRINT(0,2,"OX:  ") LPRINT(3,2,MYSTR$(OX))
  LPRINT(0,3,"W:   ") LPRINT(3,3,MYSTR$(W))

  LPRINT(6,0,"X:  ") LPRINT(9,0,MYSTR$(X))
  LPRINT(6,1,"HL: ") LPRINT(9,1,MYSTR$(HL))
  LPRINT(6,2,"H0: ") LPRINT(9,2,MYSTR$(H0))
  LPRINT(6,3,"HR: ") LPRINT(9,3,MYSTR$(HR))

  LPRINT(12,0,"M:  ") LPRINT(15,0,MYSTR$(M))
  LPRINT(12,1,"BL:  ") LPRINT(15,1,MYSTR$(BL))
  LPRINT(12,2,"B0:  ") LPRINT(15,2,MYSTR$(B0))
  LPRINT(12,3,"BR:  ") LPRINT(15,3,MYSTR$(BR))

  LPRINT(17,0,"SL:  ") LPRINT(20,0,MYSTR$(SL))
  LPRINT(17,1,"SR:  ") LPRINT(20,1,MYSTR$(SR))
  LPRINT(17,2,"SX:  ") LPRINT(20,2,MYSTR$(SX))
END

!---------------------------------------
! GOTCHA
!---------------------------------------
FUNC GOTCHA()
BYTE KEY
BYTE R,N,Q
BEGIN
  KEY = 0
  KEYCH = $FF
  R = ASC("R")
  N = ASC("N")
  Q = ASC("Q")
  LPRINT(9,0,"    OOPS!     ")
  LPRINT(0,2,"R)ESTART, N)EW, OR Q)UIT?") 
  OPEN(1,4,0,"K:")
  WHILE KEY <> R AND KEY <> N AND KEY <> Q DO
    KEY = GET(1)
    LPRINT(26,2,.KEY,1)
  ENDWHILE
  CLOSE(1)
END KEY

!---------------------------------------
! BYE_NOW
!---------------------------------------
PROC BYE_NOW()
INT I
BEGIN
  I = 3000 
  WHILE I > 0 DO
    I = I - 1
  ENDWHILE
  GRAPHICS(0) HOME() WRTLN("BYE NOW.")
END

!---------------------------------------
! MAIN
!---------------------------------------
MAIN()
BYTE LOOP
BEGIN
  HELP_SCREEN() HOME()
:_init
  INIT() 
:_level_init
  LEVEL_INIT()
  LOOP = 1
  WHILE LOOP = 1 DO
    CASE M
      OF 0 DO PICK_X_COORD()            ENDOF
      OF 1 DO CALC_SHAPE_CONSTRAINTS()  ENDOF
      OF 2 DO CHOOSE_SHAPE()            ENDOF
      OF 3 DO APPLY_CHOSEN_COORD()      ENDOF
      OF 4 DO PLOT_A_LINE()             ENDOF
    ENDCASE
    LOOP = KB_PROC()
  ENDWHILE
  LOOP = GOTCHA()
  CASE LOOP
    OF ASC("R") DO GOTO _level_init ENDOF
    OF ASC("N") DO GOTO _init       ENDOF
    OF ASC("Q") DO BYE_NOW()        ENDOF
  ENDCASE
END

Up Next (Yikes! Less than a week left)

  1. Add row clearing
  2. Implement level advancement
  3. Re-write scrolling subroutine in 6502