A subset of Psychedelia ported to Javascript and adapted for the big screen.
See also Psychedelia.js
Specifically this is an adaptation in Javascript of the version of Psychedelia that appeared as a type-in listing in 'Popular Computing Magazine' in December 1984:
Like all type-in listings of its day this consisted of a small BASIC program that loads the raw machine code into memory. The juice of the program therefore is not in the short human-readable preamble at the start of the listing:
10 REM *** A JEFF MINTER PRODUCTION ***
20 REM *** BASIC PROG. BY KEVIN BERGIN ***
30 REM *** ENTER THE FOLLOWING IN DIREC T MODE BEFORE YOU START!! ***
40 REM *** POKE 43,1:POKE 44,44:POKE 16 384,0:CLR:NEW ***
50 PRINT"HANG ON A SEC."
60 POKE53281 ,0:POKE53280,0:FORA=2049TO3457
70 READX: J=J+X:POKEA,X
80 NEXT: IFJ<>98035THEN180
90 PRINT" WONDERFUL COLOURS COMING..."
100 PRINT"&& BUT FIRST HAVE YOU SAVED THIS ?"
110 GETA$: IFA$<>"V"ANDA$<>"N"THEN110
120 IFA$="N"THENPRINT"SAVE IT NOW THEN": STOP
130 PRINT "TO SAVE THE CODE": PRINT"PRESS RUN/STOP & RESTORE"
140 PRINT"THEN SAVE AS NORMAL (THAT IS AFTER YOU EXIT THIS PROG"
150 POKE198,0:PRINT"PRESS A KEY TO START. ":WAIT198,1
160 PRINT"CLR: NEW": PRINT" S2PP43, 11P (744,8":PRINT"ERUN" 3 CHRS (19)
170 POKE631,13: POKE632,13: POKE633,13:POKE198,3:END
180 PRINT"DATA ERROR TRY AGAIN!"
This just performs the mundane task of reading in all of the numbers in the rest of the listing and, once done, executing those numbers as a machine code program. Rather what we're interested in is the assembly instructions that underlie that long list of numbers. For example:
To give you an idea of how we get from raw numbers to an assembly program in practice I'll show you how
a little routine I call PaintPixel
appears in the listing. Here it is:
410 DATA 165,2
420 DATA 41,128,208,249,165,2,201
430 DATA 40,16,243,165,3,41,128
440 DATA 208,237,165,3,201,24,16
450 DATA 231,32,92,64,177,5,41
460 DATA 7,162,0,221,84,64,240
470 DATA 5,232,224,8,208,246,138
480 DATA 133,253,166,4,232,228,253
490 DATA 240,3,16,1,96,166,4
500 DATA 189,84,64,145,5,96
If you read left to right below, you can see the decimal values given in the listing translated to hexadecimal, then to the meaning of those hexadecimal values in 6502 assembly language.
Decimal Pretty Assembly
Data Hex Assembly with labels
------- -------- ------------ ------------------------------------------------
PaintPixel
165 2 a5 02 lda $02 LDA pixelXPosition
41 128 29 80 and #$80 AND #$80 ; Detect if has moved off left of screen
208 249 d0 f9 bne $089f BNE ReturnEarly
165 2 a5 02 lda $02 LDA pixelXPosition
201 40 c9 28 cmp #$28 CMP #NUM_COLS
16 243 10 f3 bpl $089f BPL ReturnEarly
165 3 a5 03 lda $03 LDA pixelYPosition
41 128 29 80 and #$80 AND #$80 ; Detect if has moved off top of screen.
208 237 d0 ed bne $089f BNE ReturnEarly
165 3 a5 03 lda $03 LDA pixelYPosition
201 24 c9 18 cmp #$18 CMP #NUM_ROWS
16 231 10 e7 bpl $089f BPL ReturnEarly
32 145 8 20 91 08 jsr $0891 JSR LoadXAndYPosition
177 5 b1 05 lda ($05),y LDA (currentLineForPixelInColorRamLoPtr),Y
41 7 29 07 and #$07 AND #COLOR_MAX
162 0 a2 00 ldx #$00 LDX #$00
221 137 8 dd 89 08 cmp $0889,x b408C CMP presetColorValuesArray,X
240 5 f0 05 beq $08cb BEQ b4096
232 e8 inx INX
224 8 e0 08 cpx #$08 CPX #COLOR_MAX + 1
208 246 d0 f6 bne $08c1 BNE b408C
138 8a txa b4096 TXA
133 253 85 fd sta $fd STA indexOfCurrentColor
166 4 a6 04 ldx $04 LDX colorIndexForCurrentPixel
232 e8 inx INX
228 253 e4 fd cpx $fd CPX indexOfCurrentColor
240 3 f0 03 beq $08d8 BEQ ActuallyPaintPixel
16 1 10 01 bpl $08d8 BPL ActuallyPaintPixel
96 60 rts RTS
ActuallyPaintPixel
166 4 a6 04 ldx $04 LDX colorIndexForCurrentPixel
189 137 8 bd 89 08 lda $0889,x LDA presetColorValuesArray,X
145 5 91 05 sta ($05),y STA (currentLineForPixelInColorRamLoPtr),Y
96 60 rts RTS
And this is what PaintPixel
looks like when we port it from assembly to Javascript:
6502 Assembly Language Javascript
;----------------------- -------------
PaintPixel function paintPixel(pixelXPos, pixelYPos, colorIndexForCurrentPixel) {
LDA pixelXPosition if (pixelXPos < 0) {
AND #$80 ; Detect if has moved off left of screen return;
BNE ReturnEarly }
LDA pixelXPosition if (pixelXPos >= NUM_COLS) {
CMP #NUM_COLS return;
BPL ReturnEarly }
LDA pixelYPosition if (pixelYPos < 0) {
AND #$80 ; Detect if has moved off top of screen. return;
BNE ReturnEarly }
LDA pixelYPosition if (pixelYPos >= NUM_ROWS) {
CMP #NUM_ROWS return;
BPL ReturnEarly }
JSR LoadXAndYPosition const x = (pixelYPos * NUM_COLS) + pixelXPos;
; Y now contains the pixelXPosition const currentColorForPixel = pixel_matrix[x] & COLOR_MAX;
LDA (currentLineForPixelInColorRamLoPtr),Y
; Make sure the color we get is addressable by
; presetColorValuesArray.
AND #COLOR_MAX
LDX #$00 const indexOfCurrentColor = presetColorValuesArray.indexOf(currentColorForPixel);
b408C
CMP presetColorValuesArray,X
BEQ b4096
INX
CPX #COLOR_MAX + 1
BNE b408C
b4096
TXA let cx = colorIndexForCurrentPixel + 1;
STA indexOfCurrentColor
LDX colorIndexForCurrentPixel if (cx < indexOfCurrentColor) {
INX return;
CPX indexOfCurrentColor }
BEQ ActuallyPaintPixel
BPL ActuallyPaintPixel
RTS
ActuallyPaintPixel
LDX colorIndexForCurrentPixel const newColor = presetColorValuesArray[colorIndexForCurrentPixel];
LDA presetColorValuesArray,X
STA (currentLineForPixelInColorRamLoPtr),Y pixel_matrix[x] = newColor;
RTS return;
}
The core of Psychedelia is contained in this routine and just two others: LoopThroughPatternAndPaint
and PaintPixelForCurrentSymmetry
. They are called in
the following order:
-> `LoopThroughPatternAndPaint`
-> `PaintPixelForCurrentSymmetry`
-> `PaintPixel`
PaintPixelForCurrentSymmetry
is the simplest of the three, it figures out how many copies of the current pattern to paint. For example, if the current symmetry setting is 'Quad' it will paint 4 copies
of the pattern perpendicular to each other. Despite its simplicity it's verbose compared to the Javascript implementation. It gives you a real sense of the detail management required
when writing assembly.
PaintPixelForCurrentSymmetry function PaintPixelForCurrentSymmetry(pixelXPosition, pixelYPosition, colorIndexForCurrentPixel) {
; First paint the normal pattern without any
; symmetry.
LDA pixelXPosition
PHA
LDA pixelYPosition
PHA
JSR PaintPixel paintPixel(pixelXPosition, pixelYPosition, colorIndexForCurrentPixel);
LDA currentSymmetrySettingForStep
BNE HasSymmetry
CleanUpAndReturnFromSymmetry if (!currentSymmetrySettingForStep) {
PLA return;
STA pixelYPosition }
PLA
STA pixelXPosition
RTS
HasSymmetry
CMP #X_AXIS_SYMMETRY
BEQ XAxisSymmetry
; Has a pattern to paint on the Y axis
; symmetry so prepare for that.
LDA #$27
SEC
SBC pixelXPosition
STA pixelXPosition
; If it has X_Y_SYMMETRY then we just
; need to paint that, and we're done.
LDY currentSymmetrySettingForStep
CPY #X_Y_SYMMETRY
BEQ XYSymmetry
; If we're here it's either Y_AXIS_SYMMETRY
; or QUAD_SYMMETRY so we can paint a pattern const symmPixelXPosition = NUM_COLS - pixelXPosition;
; on the Y axis. paintPixel(symmPixelXPosition, pixelYPosition, colorIndexForCurrentPixel);
JSR PaintPixel
; If it's Y_AXIS_SYMMETRY we're done and can
; return.
LDA currentSymmetrySettingForStep if (currentSymmetrySettingForStep == 0x01) {
CMP #Y_AXIS_SYMMETRY return;
BEQ CleanUpAndReturnFromSymmetry }
; Has QUAD_SYMMETRY so the remaining steps are
; to paint two more: one on our X axis and one
; on our Y axis.
; First we do the Y axis.
LDA #$17
SEC
SBC pixelYPosition
STA pixelYPosition const symmPixelYPosition = NUM_ROWS - pixelYPosition;
JSR PaintPixel paintPixel(pixelXPosition, symmPixelYPosition, colorIndexForCurrentPixel);
; Paint one on the X axis.
PaintXAxisPixelForSymmetry
PLA
TAY
PLA
STA pixelXPosition
TYA
PHA
JSR PaintPixel
PLA
STA pixelYPosition
RTS
XAxisSymmetry
LDA #$17
SEC
SBC pixelYPosition
STA pixelYPosition
JMP PaintXAxisPixelForSymmetry
XYSymmetry if (currentSymmetrySettingForStep == 0x02) {
LDA #$17 return;
SEC }
SBC pixelYPosition
STA pixelYPosition paintPixel(symmPixelXPosition, symmPixelYPosition, colorIndexForCurrentPixel);
JSR PaintPixel }
PLA
STA pixelYPosition
PLA
STA pixelXPosition
RTS
Take a look at the rest of the code.