(back)

What follows is a chronological list of programs written for Důvěřivý. Note that many of these programs benefit from increasing the speed of the emulator; do this by decreasing the Δt and increasing the cpu cycles per frame.

Addition Program

This was the first program that used the scheme where you have two variables for the input, INPUT and OLDINPUT, and you only perform an action if the input has changed. This allows for keypresses to cause single movements. It also uses XOR operations to modify the bit selected by the cursor.

;
;     ADDITION PROGRAM
;     By blah 2016-11
;     For Důvěřivý
;
; This program allows the user to enter two numbers and add them.
;
; Use of screen space:
;  Row 0 contains the cursor.
;  Row 1 contains operand A.
;  Row 2 contains operand B.
;  Row 3 contains the result.
;  All other rows are unused.
;
; Controls:
;  LEFT: Rotate cursor left.
;  RIGHT: Rotate cursor right.
;  UP: Toggle bit of operand A which is selected by the cursor.
;  DOWN: Same as UP, but with operand B.
;  SELECT: Calculate A+B.
;  BACK: Clear.
;

CURSOR: 01
OPA: 0
OPB: 0
RESULT: 0
INPUT: 0
OLDINPUT: 0
XOR0: 0 ; XOR0 and 1 are required to compute XOR using only NORs
XOR1: 0

:START
  MOV [INPUT] OLDINPUT ; OLDINPUT exists to ensure that keypresses are only registered once
  GIN INPUT
  EQU [INPUT] [OLDINPUT]
  CGO START

  EQU [INPUT] 00000001
  CGO DOWN
  EQU [INPUT] 00000010
  CGO LEFT
  EQU [INPUT] 00000100
  CGO UP
  EQU [INPUT] 00001000
  CGO RIGHT
  EQU [INPUT] 00010000
  CGO SELECT
  EQU [INPUT] 00100000
  CGO BACK
  JMP START

:LEFT ; Rotates the cursor left
  ROL [CURSOR] CURSOR
  SVM 0 [CURSOR]
  JMP START

:RIGHT ; Rotates the cursor right
  ROR [CURSOR] CURSOR
  SVM 0 [CURSOR]
  JMP START

:UP ; Some instructions to simulate XOR with the NOR instruction; (A AND B) NOR (A NOR B) = A XOR B
  NOR [CURSOR] 0 XOR0
  NOR [OPA] 0 XOR1
  NOR [XOR0] [XOR1] XOR0
  NOR [CURSOR] [OPA] XOR1
  NOR [XOR0] [XOR1] OPA
  SVM 1 [OPA]
  JMP START

:DOWN ; Same thing.
  NOR [CURSOR] 0 XOR0
  NOR [OPB] 0 XOR1
  NOR [XOR0] [XOR1] XOR0
  NOR [CURSOR] [OPB] XOR1
  NOR [XOR0] [XOR1] OPB
  SVM 2 [OPB]
  JMP START

:SELECT ; Adds the operands and writes them.
  ADD [OPA] [OPB] RESULT
  SVM 3 [RESULT]
  JMP START

:BACK ; Clears everything.
  MOV 0 OPA
  MOV 0 OPB
  MOV 0 RESULT
  SVM 1 0
  SVM 2 0
  SVM 3 0
  JMP START

Prime Numbers (2016)

This was an optimised version of a smaller program. It breaks down around P=128 because the candidate multiples (QM) start overflowing and this breaks the logic.

; Unsimple Prime Number Program

P: 1 ; candidate prime
Q: 1 ; candidate divisor
QM: 1
TRASH: 0
PAT: 0

:START
  ADD [P] 2 P
  MOV 1 Q
  :NEWDIVISOR
    ADD [Q] 2 Q
    SUB [Q] [P] TRASH
    CGO PRIMEGET
    ; Repetition of the modulus loop code, but for special case of 2Q, where if QM > P, P is prime
    ADD [Q] [Q] QM
    EQU [P] [QM]
    CGO START
    SUB [QM] [P] TRASH
    CGO PRIMEGET
    :MODULUS
      ADD [QM] [Q] QM
      EQU [QM] [P]
      CGO START
      SUB [QM] [P] TRASH
      CGO NEWDIVISOR
      JMP MODULUS

:PRIMEGET
  SVM [PAT] [P]
  ADD [PAT] 1 PAT
  JMP START

Elementary Cellular Automata (2016)

Rule 90

; Simulating Rule 90 with bitwise operations
; This one's cool to watch.

STATE: 00001000
T: 0
SL: 0
SR: 0
XOR0: 0
XOR1: 0

:START
  SVM [T] [STATE]
  LSL [STATE] SL
  LSR [STATE] SR
  ; XOR code respectfully plagiarised from my addition program
  NOR [SL] 0 XOR0
  NOR [SR] 0 XOR1
  NOR [XOR0] [XOR1] XOR0
  NOR [SL] [SR] XOR1
  NOR [XOR0] [XOR1] STATE
  ADD [T] 1 T
  JMP START

Rule 54

; Rule 54

STATE: 00001001
T: 0
SL: 0
SR: 0
XOR0: 0
XOR1: 0
ORHOLD: 0

:START
  SVM [T] [STATE]
  ROL [STATE] SL
  ROR [STATE] SR

  NOR [SL] [SR] ORHOLD
  NOR [ORHOLD] 0 ORHOLD

  NOR [STATE] 0 XOR0
  NOR [ORHOLD] 0 XOR1
  NOR [XOR0] [XOR1] XOR0
  NOR [STATE] [ORHOLD] XOR1
  NOR [XOR0] [XOR1] STATE

  ADD [T] 1 T
  JMP START

Drawing Program

;
;     DRAWING PROGRAM
;     By blah 2018-05
;     For Důvěřivý
;
; This program allows the user to draw on the screen using a flashing cursor.
;
; It contains the first implementation of an idea I had a while ago: to have a
; function manually implemented in assembly. to call it, you simply set the
; variable RET to the location where the flow should go to after the function
; has finished, and then JMP XORFUNC.
;
; Controls:
;  LEFT, RIGHT, UP, DOWN: move cursor one pixel.
;  SELECT: Toggle selected pixel.
;  BACK: Clear.
;
X: 10000000 ; This specifies the horizontal location of the cursor. It is
            ; rotated with ROL and ROR to move the cursor horizontally.
Y: 0 ; Contains the vertical position of the cursor, as an actual integer. Only
     ; the least significant 3 bits are used, as such the cursor goes down the
     ; bottom and back out the top, or vice versa.
OLDIN: 0
IN: 0

XORIN: 0 ; Multiple temporary variables are required to simulate XOR with NORs.
XOR0: 0
XOR1: 0
RET: 0
:XORFUNC
  GVM [Y] XORIN
  NOR [XORIN] [X] XOR0    ; Simulating XOR with NORs.
  NOR [XOR0] [X] XOR1     ; If you ever make a computer, here's some advice:
  NOR [XOR0] [XORIN] XOR0 ; Don't just have one logical bitwise operation.
  NOR [XOR0] [XOR1] XOR0  ; Really.
  NOR [XOR0] [XOR0] XORIN ; It sucks.
  SVM [Y] [XORIN]
  JMP [RET]

:START
  MOV PNT1 RET ; We call the XOR function twice to flash the cursor.
  JMP XORFUNC
  :PNT1
  MOV PNT2 RET
  JMP XORFUNC
  :PNT2

  GIN IN ; We get the input overall
  EQU [IN] [OLDIN] ; We then check to see if it has actually changed
  CGO START
  MOV [IN] OLDIN

  EQU [IN] 00000001
  CGO DOWN
  EQU [IN] 00000010
  CGO LEFT
  EQU [IN] 00000100
  CGO UP
  EQU [IN] 00001000
  CGO RIGHT
  EQU [IN] 00010000
  CGO SELECT
  EQU [IN] 00100000
  CGO BACK
  JMP START

:DOWN
  ADD 1 [Y] Y
  JMP START
:LEFT
  ROL [X] X
  JMP START
:UP
  SUB [Y] 1 Y
  JMP START
:RIGHT
  ROR [X] X
  JMP START
:SELECT
  MOV START RET
  JMP XORFUNC ; Normally XORFUNC is called twice at the beginning of the main
              ; loop, to flash the cursor. However, calling it outside of that
              ; context will actually change the permanent value of this pixel.
:BACK
  SVM 0 0 ; Write 0 to all rows of VRAM.
  SVM 1 0
  SVM 2 0
  SVM 3 0
  SVM 4 0
  SVM 5 0
  SVM 6 0
  SVM 7 0
  JMP START

Duck Game

The most elaborate program I have written so far. It was based on a game Jack Eisenmann made for the DUO 128 ELITE, which can be found here. This is when I started using tabs. To get an appropiate speed to run this program at using the emulator, lower the Δt to 10ms and then set cpu cycles per frame to at least ~30.

;
;     DUCK GAME
;     By blah 2018-06
;     For Důvěřivý
;
; An adaptation of a game that I saw implemented by Jack Eisenmann for the DUO
; 128 Elite. Run at ~6KHz.
;
; Move up and down to collect food. At the end of the game, you will see a
; screen with a face and two numbers displayed in binary on the top and bottom.
; The number on the top is how many food items you collected. The number on the
; bottom is your high score. If you collected all of them, the face will be
; smiling, otherwise it won't be.
;
; To increase the difficulty, you can simply run the simulation faster.
;
; Controls:
;  UP, DOWN: Move duck.
;  SELECT: Start new game.
;  BACK (after game has ended): Reset highscore.
;

; Variables, roughly ordered by how temporary they are.
TEMP: 0
IN: 0
OLDIN: 0
T: 0
DRAWFOODRET: 0 ; Return pointer for DRAWFOOD function

FOOD: 0
DUCKPOS: 1
FOODPOS: 1

RAND: 00100111

FOODSPAWNED: 0
SCORE: 0
HIGHSCORE: 0

	:GAMEEND
		SUB [HIGHSCORE] [SCORE] TEMP
		CGO NONEWHIGHSCORE
		MOV [SCORE] HIGHSCORE
		:NONEWHIGHSCORE
		SVM 0 [SCORE]
		SVM 1 00000000
		SVM 2 00100100
		SVM 3 00000000
		SVM 4 00100100
		SVM 5 00111100
		SVM 6 00000000
		SUB [SCORE] 0F TEMP ; SUB is used here because it sets the flag
		MOV 0 SCORE ; The score will be 0 to start a new game
		:WRITEHIGHSCORE
		SVM 7 [HIGHSCORE]
		CGO GAMEENDLOOP
		SVM 4 00111100
		SVM 5 00100100
		:GAMEENDLOOP
		GIN IN
		EQU [IN] 10
		CGO START
		EQU [IN] 00100000
		CGO RESETHIGHSCORE
		JMP GAMEENDLOOP
		:RESETHIGHSCORE
		MOV 0 HIGHSCORE
		JMP WRITEHIGHSCORE

	:MAINLOOP
		ADD 2 [T] T ; Increment the timer
		GIN IN
		CGO FOODACTION ; If the timer counted all the way up, execute a tick
		EQU [IN] [OLDIN]
		CGO MAINLOOP
		ADD [T] [RAND] RAND ; Use player inputs to aid PRNG
		MOV [IN] OLDIN
		EQU [IN] 00000001
		CGO DOWN
		EQU [IN] 00000100
		CGO UP
		JMP MAINLOOP

	:DOWN
		SVM 0 00000000
		SVM 1 00000000
		SVM 2 00000000
		SVM 3 00000000
		SVM 4 00100000
		SVM 5 00110000
		SVM 6 11100000
		SVM 7 01100000
		MOV 5 DUCKPOS
		JMP CALLDRAWFOODANDGOTOMAINLOOP

	:START
	:UP
		SVM 0 00100000
		SVM 1 00110000
		SVM 2 11100000
		SVM 3 01100000
		SVM 4 00000000
		SVM 5 00000000
		SVM 6 00000000
		SVM 7 00000000
		MOV 1 DUCKPOS
		:CALLDRAWFOODANDGOTOMAINLOOP
		MOV MAINLOOP DRAWFOODRET

	:DRAWFOOD ; Function that draws food, modifies VRAM and flag
		EQU [FOODPOS] [DUCKPOS] ; The flag is set here, and can still be used
		CGO DUCKFOODSAMEFUNC    ; after the function exits
		SVM [FOODPOS] [FOOD]
		JMP [DRAWFOODRET]
		:DUCKFOODSAMEFUNC
		NOR [FOOD] 00110000 TEMP
		NOR [TEMP] 0 TEMP
		SVM [FOODPOS] [TEMP]
		JMP [DRAWFOODRET]

	:FOODACTION
		EQU [FOOD] 0
		CGO NEWFOOD
		:MOVEFOOD
			LSL [FOOD] FOOD
			MOV 84 MOVEFOOD ; Self modifying code
			MOV TICKRET DRAWFOODRET
			JMP DRAWFOOD
			:TICKRET
			CGO DUCKFOODSAME
			JMP MAINLOOP
			:DUCKFOODSAME
			EQU [FOOD] 00010000
			CGO FOODCAUGHT
			JMP MAINLOOP

			:FOODCAUGHT
				ADD 1 [SCORE] SCORE
				MOV 0 FOOD
				JMP MAINLOOP


		:NEWFOOD ; Generate new food item and randomly choose location
			MOV 94 MOVEFOOD ; Self modifying code
			ADD 10 [FOODSPAWNED] FOODSPAWNED
			CGO GAMEEND
			MOV 10000000 FOOD
			ROR [RAND] RAND
			NOR [RAND] 11111011 TEMP
			ADD 1 [TEMP] FOODPOS
			JMP MOVEFOOD

Movie Player (2018)

Uses complicated self-modifying code to display an arbitary amount of animation frames. Here it is used to display the words "THIS IS NOT THE DUO".

00000000
01111100
00010000
00010000
00010000
00010000
00000000
00000000

00000000
01000100
01000100
01111100
01000100
01000100
00000000
00000000

00000000
01111100
00010000
00010000
00010000
01111100
00000000
00000000

00000000
00111100
01000000
00111000
00000100
01111000
00000000
00000000

00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000

00000000
01111100
00010000
00010000
00010000
01111100
00000000
00000000

00000000
00111100
01000000
00111000
00000100
01111000
00000000
00000000

00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000

00000000
01000100
01100100
01010100
01001100
01000100
00000000
00000000

00000000
00111000
01000100
01000100
01000100
00111000
00000000
00000000

00000000
01111100
00010000
00010000
00010000
00010000
00000000
00000000

00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000

00000000
01111100
00010000
00010000
00010000
00010000
00000000
00000000

00000000
01000100
01000100
01111100
01000100
01000100
00000000
00000000

00000000
01111100
01000000
01111000
01000000
01111100
00000000
00000000

00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000

00000000
01111000
01000100
01000100
01000100
01111000
00000000
00000000

00000000
01000100
01000100
01000100
01000100
00111000
00000000
00000000

00000000
00111000
01000100
01000100
01000100
00111000
00000000
00000000

00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000

00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000

00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
:FRAMESEND

FRAMEAT: 0
T: 0
i: 0 ; It seems unnatural to me for the iterator i to be uppercase. \('.')/
:START
	ADD 1 [T] T
	CGO NEXTFRAME ; The flag is set here by ADD, so CGO is triggered by overflow
	JMP START
:NEXTFRAME
	MOV 0 i
	EQU [FRAMEAT] FRAMESEND
	CGO LOOPFRAMES
	:DRAWLOOP
		EQU [i] 8
		CGO START
		ADD 1 [i] i
		MOV [FRAMEAT] D1 ; Very annoying bit of self-modifying code to write.
		:SVMINSTRUCTION
		SVM [FRAMEAT] [0]
		ADD 1 [FRAMEAT] FRAMEAT
		JMP DRAWLOOP

:LOOPFRAMES
	MOV 0 FRAMEAT
	JMP DRAWLOOP