Skip to content

Instantly share code, notes, and snippets.

@bonzini
Created January 17, 2012 08:11
Show Gist options
  • Save bonzini/1625597 to your computer and use it in GitHub Desktop.
Save bonzini/1625597 to your computer and use it in GitHub Desktop.
Rotozooming game of life, with blur in 256 bytes

; Roto-life (rotozooming game of life, with blur -- in 256 bytes!)
; Game of life algorithms by INT-E
; Decyphering and adaptation of game of life, and everything else by Bonz

	org	100h

; The first two lines of the matrix are filled with crap because of the
; algorithm, so we skip them when plotting the cells

NX	equ	512 / 8
NY	equ	272
EXTRA	equ	128 / 8 + NX * 8	; So many bytes are not displayed
CELLS	equ	NX * NY			; Also the origin of cells table

; Constants for the rotozooming table

table		equ	01000h		; Pointer to base of table
tabentries	equ	200h		; Number of entries in the table
tabsize		equ	tabentries * 4	; Size in bytes of table
tabmask	equ	table | (tabsize - 1)	; Mask for addresses in the table

; Macro for referencing FPU constants

work		equ	200h		; Base of work area

%define var(x) word [byte si+x-work]

; Setup video mode and palette -----------------------------------------

	fninit
	mov	al,13h
	int	10h

	; The palette is set to a blue...white gradient with this setup:
	; each bit represents whether the pixel was set 0..7 generations
	; ago: bit 7 is set if a pixel appeared in the most recent generation
	; and decreases the red and green components represents by 8,
	; while bit 0 is set if a pixel appeared 7 generations ago and
	; decreases the components by 1.
	; This allows one to plot a pixel with a simple BT+RCR pair
	; (BT on the game of life matrix, RCR on VRAM)

	mov	dx,03c8h
	xor	ax,ax
	out	dx,al
	inc	dx
setpal:
	mov	al, 63
	jns	setpal2			; SF has sign of AH
	mov	al, 40			; If cell alive, draw it more blue
setpal2:
	mov	cl, 8
bit:
	rol	ah, 1			; extract a bit into carry
	jnc	nodec
	sub	al, cl			; and accumulate the weight
nodec:
	loop	bit
	out	dx, al			; Output red/green
	out	dx, al
	mov	al, 255			; Setting AL=255...
	out	dx, al			; ...is the same as 63 (full blue)...
	inc	ax			; ...but INC AX increments AH!
	jnz	setpal			; Do next color

; Compute sines and cosines ------------------------------------------

	mov	ch, tabentries >> 8	; 256 entries in the table
	mov	di, table		; Pointer to base of table
%if tabentries == work
	mov	si, cx			; Pointer to base of data
%else
	mov	si, work
%endif

calctab:	
	mov	word [si], cx
	fild	word [si]		; ang
	fldpi				; pi ang
	fmulp	st1			; ang*pi
	fidiv	var(shift)		; ang*pi/128
	fsincos				; sin cos
	fld	st0			; sin cos cos
	call	calc			; sin cos
	call	calc			; sin
	fstp	st0
	loop	calctab

; Main cycle ---------------------------------------------------------

	push	word 0a000h
	pop	fs

frame:
	pusha				; Save CX=0, DI=angle
	mov	bp, CELLS		; number of bytes in the cells matrix
	mov	di, bp			; di=cells matrix
	lea	si, [bp+di]		; si=workspace (at 2*CELLS)

life:
	push	bp			; save count of remaining bytes
	mov	bp,NX * 2		; start with the line below
	mov	al,byte [di+bp]		; Copy screen to buffer for
	mov	byte [si+bp],al		; next line

line:					; WARNING: black magic follows
	mov	ax,[si+bp-NX]		; read 16 cells from buffer
	push	ax
	ror	ax,1			; AX=89ABCDEF 01234567
	rcl	al,1			; -> 789ABCDE 01234567
	call	subtract		; do 16 subtracts in parallel
	pop	ax			; (8 for this byte and 8 for the next)
	rol	ax,1
	mov	ah,0			; -> -------- 12345678
	call	subtract		; do 8 subtracts in parallel
	sub	bp,byte NX
	jns	line			; Work on the three neighboring lines

	; Here is the Karnaugh map for the "cell is alive" function:
	; CBA = ~count of neighbors, X=cell is alive
	;
	;   XC \ BA |  00   01  11  10
	;   ----`---+-------------------
	;    00     |   0    0   0   0
	;    01     |  _0___|1|  0   0     X' = C ~B A + X C ~B
	;    11     | |_1___|1|  0   0        = C ~B (A + X)
	;    10     |   0    0   0   0
	;
	; In the code, AL = X, CL = A, DL = B, BX = C

	lodsb				; Decide destiny of these 8 cells
	or	al, cl			; according to the above formula
	not	dl
	and	al, dl
	and	ax, bx
	stosb				; and store in the cells buffer

	mov	cl, 8			; shift sums by 8 bits
	shr	dx, cl
	shr	bx, cl
	shr	cx, cl

	pop	bp
	dec	bp
	jnz	life			; do NX * NY times
	popa
	
	and	di, tabmask		; Constrain DI in the table
	mov	si, di			; For LODS
	lodsw				; cosine in BP
	xchg	bp, ax
	lodsw				; AX=sine, SI=new angle
	xchg	si, ax			; SI=sine, AX=new angle
	xchg	di, ax			; SI=sine, DI=new angle

	; We don't need to zero out BX and DX because their
	; values are constant across frames

	pusha
	xor	di, di			; Base of VRAM
	mov	ax, 320			; Common value
	mov	cl, 200			; 200 rows
yplot:
	pusha
	xchg	cx,ax			; CX=320
xplot:

	push	bx			; Save Y index
	mov	al, dh			; AL = X index
	mov	bl, al			; Combine Y and X in BX
	and	al, 7			; Isolate bit index in AX
	shr	bl, 1			; Discard it in bx
	shr	bx, 2
	bt	[bx + CELLS+EXTRA], ax	; bx = 00YYYYYYYY0XXXXX
	rcr	byte [fs:di], 1		; Rotate cell status into VRAM
	pop	bx			; Pop Y index
	inc	di			; Next pixel
	add	bx, si			; Update for next column
	add	dx, bp
	loop	xplot			; and draw it

	popa
	add	di, ax			; Next line (AX=320)
	sub	bx, bp			; Update for next line
	add	dx, si
	loop	yplot			; and draw it

	popa
	mov	ah,1			; ESC pressed?
	int	16h
	jz	near frame		; If not, next frame please

	mov	ax,3			; Else back in text mode
	int	10h			; and use the RET from this subroutine

	mov	ah,9
	mov	dx,text
	int	21h
	
	; This routine subtracts the 16 bits in AX from sixteen 3-bit counters
	; spread across CX, DX and BX.
	;
	; First of all, notice that even though 0-9 require 4 bits,
	; we can get by with 3 because 0-1 and 8-9 neighboring cells
	; have the same behavior (i.e. die)
	;
	; The formulas for subtracting X to CBA are very nice:
	;	A' = A ^ X
	;	B' = B ^ (X & ~A)      = B ^ (X & A')
	;	C' = C ^ (X & ~A & ~B) = C ^ (X & A' & ~B) = C ^ (X & A' & B')
	;
	; (X & ~A implies X, but if X then A' = ~A; and similarly,
	; X & ~A & ~B implies X & ~A, but if X & ~A then B' = ~B)

subtract:
	xor	cx,ax			; cx: bit 0 counter
	and	ax,cx
	xor	dx,ax			; dx: bit 1 counter
	and	ax,dx
	xor	bx,ax			; bx: bit 2 counter
	ret

; Compute st1*(cos(angle)*30+50), assuming st0=cos(angle)
; Stack balance: -1

calc:
	fimul	var(scale)		; st1 cos*scale
	fiadd	var(base)		; st1 cos*scale+base
	fmul	st1			; st1 st1*(cos*scale+base)
	fistp	word [di]		; st1
	scasw
	ret

shift:
	dw	tabentries / 2		; values for the math coprocessor
scale:
	dw	30
base:
	dw	60
	
text:
	db	'LIFE!-Bonz$'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment