MWENGE'S WEBSITE

GITHUB   EMAIL   SOCIAL   DONATE


Jan 19, 2024
PSYCHEDELIA II

While working on psychedelia syndrome I finally disassembled the Psychedelia sub-game in Batalyx. Eventually I'll fill out a chapter in the book on how it evolved from the original Psychedelia release, but toying with the sub-game, which is really just a glorified pause-mode, gave me an idea of how Psychedelia might be turned into an actual game.

There isn't a whole lot of point in going to the trouble of disassembling C64 binaries if you can't have a bit of fun while you're at it. The Batalyx sub-game is an ideal little base for building something new on. It contains a slightly modified version of the core from the 1984 original with nearly all of the knobs and dials stripped out, so developing a game around it involves a lot more adding my crap than deleting Jeff Minter's precious code.

MY BRIGHT IDEA

My modest little notion is to give the player a simple objective: reach every part of the screen with the assigned pattern. They receive points for every tile they fill and lose points every time the fill the same tile more than once. If you click on the frame below, you should be able to give it a quick go, using CTRL to fire and the arrow keys for moving around. If that doesn't work for you, you can try playing it in a new tab.

Look at it in all its glory..

The game is essentially restful: there is no dying or losing. You never get bumped back to Level 1 again - the only way is forward, as slowly or as quickly as you please. You can lose points sure, but you really would have to zone out completely to find yourself reduced back to zero. Perhaps that's a different challenge: get as far as you can with as few points as possible!

If you're interested in what the source code for such a game looks like, you can take a look at it here. Here are a couple of things in there that are kind of interesting.

SCROLLING THE BACKGROUND

You may have noticed that the unfilled background scrolls away beneath the filled tiles. This is a nice little effect stolen from Gridrunner. This is what the routine looks like:

;-------------------------------------------------------------------------
; PerformRollingGridAnimation                                             
;-------------------------------------------------------------------------
PerformRollingGridAnimation
        INC frameControlCounter
        LDA frameControlCounter
        AND #$01
        BEQ ScrollGrid
        RTS

ScrollGrid
        LDA unpaintedGrid + $0007
        STA rollingGridPreviousChar
        LDX #$07
_Loop   LDA unpaintedGrid - $0001,X
        STA unpaintedGrid,X
        DEX
        BNE _Loop

        LDA rollingGridPreviousChar
        STA unpaintedGrid

        RTS

unpaintedGrid refers to this chap, our 8-byte definition of the character used to display the background:

unpaintedGrid                                                                          
        .BYTE $10,$00,$00,$82,$00,$00,$10,$00   ;.BYTE $10,$00,$10,$BA,$10,$00,$10,$00 
                                                ; CHARACTER $83                        
                                                ; 00010000      *                      
                                                ; 00000000                             
                                                ; 00010000                             
                                                ; 10111010   *     *                   
                                                ; 00010000                             
                                                ; 00000000                             
                                                ; 00010000      *                      
                                                ; 00000000                             

As you can see the trick involves rotating the bytes around one byte at a time. We call this little routine once every time just before the screen is painted (the screen is painted 60 times a second, or 50 times a second if you're in Europe). We manage this by setting a single interrupt in the Raster Interrupt feature in the C64 and doing both this bit of business and checking for joystick input while we're at it.

There are three little routines below. The first one, SetUpInterrupts sets up our interrupt. It tells the C64 to call TitleScreenInterruptHandler whenever it is about to paint a line or lines on the screen that we specify. We specify these lines in UpdateRasterPosition. In here we specify the 256th line (LDA $FF) by storing $FF in register $D012.

;---------------------------------------------------------------
; SetUpInterrupts                                               
;---------------------------------------------------------------
SetUpInterrupts                                                 
        LDA #$7F                                                
        STA $DC0D    ;CIA1: CIA Interrupt Control Register      
        LDA #<TitleScreenInterruptHandler                       
        STA $0314    ;IRQ                                       
        LDA #>TitleScreenInterruptHandler                       
        STA $0315    ;IRQ                                       
                                                                
        JSR UpdateRasterPosition                                
                                                                
        LDA #$01                                                
        STA $D01A    ;VIC Interrupt Mask Register (IMR)         
        RTS                                                     
                                                                
;---------------------------------------------------------------
; UpdateRasterPosition                                          
;---------------------------------------------------------------
UpdateRasterPosition                                            
        LDA $D011    ;VIC Control Register 1                    
        AND #$7F                                                
        STA $D011    ;VIC Control Register 1                    
                                                                
        ; Set the position of the next interrupt                
        LDA #$FF                                                
        STA $D012    ;Raster Position                           
                                                                
        ; Acknowledge the interrupt                             
        LDA #$01                                                
        STA $D019    ;VIC Interrupt Request Register (IRR)      
        RTS                                                     

Finally we have our little routine which is called every time line 256 on the screen is about to be painted roughly 50 times every second. This is where we call PerformRollingGridAnimation:

;---------------------------------------------------------------
; TitleScreenInterruptHandler                                   
;---------------------------------------------------------------
TitleScreenInterruptHandler                                     
        LDA $D019    ;VIC Interrupt Request Register (IRR)      
        AND #$01                                                
        BNE RasterPositionMatchesRequestedInterrupt             
        JMP $EA31                                               
        ; Returns                                               
                                                                
RasterPositionMatchesRequestedInterrupt                         
                                                                
        JSR CheckJoystickAndUpdateCursor                        
        JSR PerformRollingGridAnimation                         
        JSR $FF9F ;$FF9F - scan keyboard                        
                                                                
        JSR UpdateRasterPosition                                
                                                                
        ; Sounds are turned off for now                         
        ;JSR PlaySoundEffects                                   
                                                                
ReturnFromInterrupt                                             
        PLA                                                     
        TAY                                                     
        PLA                                                     
        TAX                                                     
        PLA                                                     
        RTI                                                     
NEW PATTERNS

I've included all the patterns from the original game, but really there's no point in a game being too easy all of the time. For this reason I've sprinkled a few harder-to-fill patterns throughout the levels where it takes a little more deliberation by the player to fill the screen without burning a lot of hard-earned points. Because I'm lazy these are randomly generated by a little Python script.

Psychedlia Syndrome goes into a bit of depth on how the pattern data structure works (I see I've only mentioned my book-in-progress twice already so remedying that here) but the principle is very simple. This is what the pattern data structure for the pattern used in the Batalyx subgame looks like:

; The pattern array data.                    
patternXPosArray                             
        .BYTE $FF,$01,$55    ; 6             
        .BYTE $FE,$02,$55    ;            5  
        .BYTE $FD,$03,$55    ;   4           
        .BYTE $FC,$04,$55    ;          3    
        .BYTE $FB,$05,$55    ;     2         
        .BYTE $FA,$06,$55    ;        1      
        .BYTE $55,$55        ;               
patternYPosArray             ;      1        
        .BYTE $01,$FF,$55    ;         2     
        .BYTE $FE,$02,$55    ;    3          
        .BYTE $03,$FD,$55    ;           4   
        .BYTE $FC,$04,$55    ;  5            
        .BYTE $05,$FB,$55    ;             6 
        .BYTE $FA,$06,$55                    
        .BYTE $55,$55                        

If you're reading this far you're probably the sort that can guess what's going on here without too much assistance. What we have is a pair of arrays, one of which gives the X positions of the pattern's pixels and the other which gives the Y positions. The $55 is a separator or sentinel value, which we've made plainer here by separating each segment of each array onto its own line. Finally, the values are given relative to an origin. So $01 in the first value in patternYPosArray tells us that the pixel is 'plus one' from the origin, i.e. one row down. Meanwhile $FF given as the first value in patternXPosArray might be a little more puzzling. It is in fact 'minus one'. This is because if you subtract 1 from 0 in binary, we rotate around to 255, or $FF. Likewise -2 is given as $FE.

The way Psychedelia works is by processing one 'line' from each of the arrays at a time. The littl ascii diagram to the right of the data structure is my attempt to illustrate this. The '1's in that diagram are painted by the first lines ine each array, the '2's by the second line in ear array, and so on.

If this has whetted your appetite for even more gory detail, can you guess what I recommend? That's right, my omnium gatherum of all things Psychedelia, 'psychedelia syndrome'. It may not be finished but it is already packed with excessive detail.

*