前回のデモのソースコード

前回公開したデモのソースコードを公開。
デバッグ用コードや見た目が地味で没にしたコードもコメント化されて残ってます。

nasm -f bin -o ファイル名.bin ファイル名.asm
でブートセクタ用バイナリを生成
nasm -dMSDOS -f bin -o ファイル名.com ファイル名.asm
でcomファイルを生成

%ifdef MSDOS
	origin equ 0x100
%else
	origin equ 0x7c00
%endif

%if 0
%ifdef MSDOS
	%define DISABLE_VIDEO
%endif
%endif

%ifdef DISABLE_VIDEO
	%define INT0x10
%else
	%define INT0x10 int 0x10
%endif

%ifdef DEBUG
	%macro assert_eq 2
		cmp %1, %2
		je %%skip
		mov ax, __LINE__
		call assert_fail
	%%skip:
	%endmacro
	%macro assert_seg_eq 2
		mov ax, %1
		cmp ax, %2
		je %%skip
		mov ax, __LINE__
		call assert_fail
	%%skip:
	%endmacro
%else
	%macro assert_eq 2
	%endmacro
	%macro assert_seg_eq 2
	%endmacro
%endif

BITS 16		;real mode用のコードを生成.
ORG origin	;プログラムが読み込まれるメモリ位置を指定.

vd_width	equ 320
vd_height	equ 200
vd_area		equ vd_width*vd_height
vd_bpp		equ 1
vd_size		equ vd_area*vd_bpp
ram_segment	equ origin+512/16
stack_begin equ 0x1600					;stackの開始offset.
vd_segment	equ ram_segment+0x1000

position_frac_part equ 6
position_frac equ 64	;2^position_frac_part
speed_frac_part equ 5

start:
	mov ax, ram_segment
	mov ss, ax
	mov sp, stack_begin

	;video mode設定
	;320x200x8bit
	mov ax, 0x13
	INT0x10

	;パレット設定
	xor dx, dx
	mov cx, 0x3f
pallets:
	push cx
;	xchg bx, cx	;xchgのオペランドの一方がax,もう一方がレジスタだと1byteなんだけどな.
	mov bx, cx
	mov dh, bl	;Red
	mov ch, bl	;Green
;	mov cl, bl	;Blue
	mov ax, 0x1010
	INT0x10
	pop cx
	loop pallets

	;internal speakerを初期化する.
	mov al, 182
	out 0x43, al

particles			equ stack_begin ;offset of particles data
bytes_per_particle	equ 2*2*3
					;word position x
					;word position y
					;word speed x
					;word speed y
					;word accel x
					;word accel y
max_num_particles	equ (0x10000-0x1600)/bytes_per_particle
num_particles		equ 0x1000;4992 ;305
particles_size		equ num_particles*bytes_per_particle
%if 0
field_force			equ particles+particles_size
					;word position x
					;word position y
					;field force flag; 0:disable, 1:enable
field_force_size	equ 2+2+1
%endif
average				equ particles+particles_size
old_average			equ average+2
old_beep_freq		equ old_average+2

	;粒子データ初期化の準備
	cld
	mov ax, ram_segment
	mov ds, ax

	mov di, particles
	mov cx, num_particles
	pusha

%if 0
init_particle:
;	mov ax, 4
;	mul cx
	mov ax, cx
	shl ax, 2
	and ax, 0xff
	sal ax, position_frac_part
	mov word [di], ax		;position x
	mov ax, cx
	and ax, 0xffff-0x3f
	shl ax, 3
	mov [di+2], ax		;position y
	xor ax, ax
	mov [di+4], ax		;speed x
	mov [di+6], ax		;speed y
	lea di, [di+bytes_per_particle]
	loop init_particle
%endif

	;粒子の位置とか速度とか加速度は全部0にしとけ.
	mov es, ax
	mov cx, particles_size
	xor ax, ax
	rep stosb

step:
;	mov ax, vd_segment
;	mov es, ax		;esレジスタは常にback surfaceのセグメントを指すようにする.
	push vd_segment
	pop es
	call vd_clear
%if 0
	;set field_force
	mov word [field_force], (vd_width*2/3)*position_frac
	mov word [field_force+2], vd_height/2*position_frac
	xor bl, bl
;	mov ax, [di+2]
;	and al, 255
	xor ah, ah
	int 0x1a	;Real clock count
	and dl, 0x3f 
	jnz	store_field_force
		mov bl, 0x4
	store_field_force:
		mov byte [field_force+4], bl
%endif

	xor ah, ah
	int 0x1a	;クロックカウント読み出し.
	mov fs, dx	;fsは常にクロックカウント値を持つ.

	popa
	assert_eq cx, num_particles
	assert_eq di, particles
	assert_eq sp, stack_begin
	pusha
	;mov cx, num_particles
	;mov di, particles

	mov ax, fs
	shl ax, 3
	and ax, num_particles-1
	sub cx, ax

	;粒子の平均速さもどきを更新.
	xor ax, ax
	xchg ax, [average]
	xchg ax, [old_average]

	for_particles:
%if 0
		push ax
		push ax
		call get_explode_force
		pop ax
		add [di+8], ax
		pop ax
		add [di+10], ax
%endif

		call dynamics

	put:
		push di
		mov dx, (vd_width-1)*position_frac
		call integ_comp
		inc di
		inc di

;床が下から上がってくるやつ
;それほど面白くないのでボツ.
%if 0
		xor ax, ax
		mov bx, fs
	;	add bx, 0x77
		test bl, 0xc0
		jnz .integ
	.mov_wall:
		xchg ax, bx
		and ax, 0x3f
		shl ax, 2

	.integ:
		mov dx, vd_height-1
		sub dx, ax
		shl dx, position_frac_part
%else
		mov dx, (vd_height-1)*position_frac
%endif
		call integ_comp
		pop si
	positioning:
		;vramでの粒子の位置を計算.
		mov ax, [si+2]
		sar ax, position_frac_part
		mov bx, vd_width
		mul bx
		mov bx, [si]
		sar bx, position_frac_part
		add ax, bx
		mov di, ax
		;collision
		mov al, [es:di]
		cmp al, 0x3f
		jne puton
		mov bx, si
		shr bx, 4
		and bx, 0x2
		sub word [bx+si], position_frac ;*2
		;メモリに描画して,後で一気にvramへコピーする.
		;double buffering.
	puton:
		mov al, 0x3f
		stosb
	;	mov byte [bx], 0x3f
		lea di, [si+bytes_per_particle]
		loop for_particles

	call vd_flash

%if 1
	;FPSがべらぼうに高くならないようにwaitする.
	;cx:dx interval in microseconds
	mov ah, 0x86
	xor cx, cx
	mov dx, 0x2000
	int 0x15
%endif

;	call speaker_off
	call music
	jmp short step

;以下にサブルーチンを定義

vd_clear:
	mov ax, fs
	test ax, 0x300
	jnz	.smoke
	;画面を真っ黒にする.
	pusha
	mov cx, vd_area/2
	xor di, di
	xor ax, ax
	rep stosw
	popa
	ret
.smoke:
;画面をクリアしないけど煙たくする.
;各ピクセルについて,下らへんのピクセル値の平均値を設定
	push ds
	push es
	pop ds
	xor bx, bx
	mov cx, vd_height-2
.vert:
	push cx
	mov cx, vd_width ;-1;一番右の垂直ラインがおかしくなるかもしれんが、見た目良ければOK.
.line:
	mov si, cx
	lea si, [bx+si+vd_width]
	lodsb
	shl ax, 8
	lodsb
	add ah, al

	lea si, [si-2+vd_width]
	lodsb
	add ah, al
	lodsb
	add ah, al

	shr ax, 10
	lea di, [si-2-vd_width*2]
	stosb
	loop .line
	add bx, vd_width
	pop cx
	loop .vert
	;一番下のライン2本は黒くしとけ.
	mov cx, vd_width*2
	mov di, bx
	xor al, al
	rep stosb
	pop ds
	ret


;力学計算っぽいことあれこれ.
dynamics:

	;di+10はよく使うからsiに入れてコードサイズ節約.
	lea si, [di+10]
	;渦を作るよ.
	mov ax, fs
	add ax, 0x80
	test ax, 0x100
	jz .skip_vortex
	vortex_force equ 90
	mov bx, vortex_force
;	test ax, 0x80
	neg al
	js .start_vortex
	neg bx
.start_vortex:
	push bx
	mov bx, [di]
	mov dx, [di+2]
	mov ax, bx
	add ax, dx
	cmp bx, dx
;	mov bx, vortex_force
	pop bx
	ja .right_area
	cmp ax, (vd_height-1)*position_frac
	jae .left_bottom
	add word [si], bx;left
	jmp short .break_vortex
.left_bottom:
	add word [di+8], bx
	jmp short .break_vortex
.right_area:
	cmp ax, (vd_width-1)*position_frac
	jae .right_right
	sub word [di+8], bx	;right top
	jmp short .break_vortex
.right_right:
	sub word [si], bx;
	jmp short .break_vortex
.skip_vortex:
	mov byte [si], 24 ;重力を設定.
%if 1
	;地面にぶつかったら飛び跳ねるやつ.
	cmp word [di+2], (vd_height-1)*position_frac
	jb  .break_vortex
;	mov word [di+2], (vd_height-1)*position_frac
;	mov word [di+6], 0
	mov word [si], -5900
%endif

.break_vortex:

	;高速移動
	mov dx, fs
	test dl, 0xff-0x20
;	test dx, 0xff-0x20
	jnz .skip_scatter
	mov ax, [di]
	mov bx, [di+2]
;	sar ax, 2
;	sar bx, 2
	mov [di+4], ax
	mov [di+6], bx
.skip_scatter:

	;回転
	;粒子の速度に垂直な方向へ力を加えれば速度の向きが変わるだろ 物理的に考えて.
%if 1
%if 1
;	mov dx, fs
;	test dl, 0x80
;	jnz .skip_rotation
	neg dl
	jns .skip_rotation
%else
;	cmp byte [old_average+1], 0xef
;	jb .skip_rotation
	test byte [old_average+1], 0x40
	jz .skip_rotation
%endif
	mov ax, [di+4]
	mov bx, [di+6]
	neg bx
	sar ax, 5
	sar bx, 5
	add [di+8], bx
	add [si], ax
.skip_rotation:
%endif

;	mov dx, 2
%if 0
	;矩形コリジョン.
	rect_len_part equ 4
	rect_len equ (1<<rect_len_part)*position_frac

	pusha	;push cx, push di
	mov cx, dx
.collision_detection:
	test word [di], rect_len
	jnz .skip_rect_collision
	inc di
	inc di
	loop .collision_detection
	popa
	pusha
;	mov cx, dx
.avoid_collide:
%if 1
	mov ax, [di]
	mov bx, [di+2]
	add ax, bx
	test ax, rect_len/2
	jnz .upper
;	sub ax, rect_len
	jmp .continue_loop
.upper:
	or ax, rect_len
.continue_loop:
%endif
	and ax, ~(rect_len-1)
	mov [di], ax
;	inc di
;	inc di
;	loop .avoid_collide

%if 0
	or word [di], (rect_len*position_frac)
	or word [di+2], (rect_len*position_frac)
%endif

	xor ax, ax
;	mov word [di+4], ax
	neg word [di+4]
	mov word [di+8], ax
;	mov word [di+6], ax
.skip_rect_collision:
	popa
%endif

	;速度の各成分の絶対値の和を足してくよ.
	pusha
	cmp cx, 0xd0;num_particles-0xd0	;
	jg .skip_average
	mov cx, 2
.comp:
	mov ax, [di+4]
	sar ax, 8
%if 0
	imul al	;速度の2乗だとつまらん音しかでねーよ.
%else
	cmp al, 0
	jnb .not_neg
	neg al
.not_neg:
%endif
	add [average], ax
	inc di
	inc di
	loop .comp
.skip_average:
	popa
	ret

;メインメモリに描画した内容をvramに送る.
vd_flash:
%ifndef DISABLE_VIDEO
	push ds
	push es
	mov cx, vd_area
	xor si, si
	push es
	pop ds
	push 0xa000
	pop es
	xor di, di
	rep movsb
	pop es
	pop ds
%endif
	ret

;加速度から速度を更新し、速度から位置を更新する.
;Δt=1ということにしてΔtを掛けるのは端折る.
;args:
;	di :particle data pointer
;	dx :limit of x or y value
integ_comp:
	;v=v+t*f/m
	;p=p+t*v
	;where m=1, t=1
	lea si, [di+4]	;speed x
	mov ax, [si]
	mov bx, ax		;damping
	sar bx, 6
	sub ax, bx
	add ax, [di+8]	;accel x
	mov word [di+8], 0
	mov [si], ax	;update speed
	mov bx, [di]	;position x
	sar ax, speed_frac_part
	add bx, ax
	jns .check_range
	xor bx, bx
	neg word [si]
.check_range:
	cmp bx, dx
	jbe .store
	mov bx, dx
	neg word [si]
.store:
	mov [di], bx
	ret

music:
%if 0
	mov ax, fs
	and ax, 0x7f
	or  ax, 0x3
	sal ax, 6
%else
	;粒子の平均の速さもどきに比例した周波数の音をだす.
	mov ax, [average]
	neg ax
	add ax, 0xffff
	;音を多少平滑化すれば不快な音もましになるかも・・・.
	add ax, [old_beep_freq]
	shr ax, 1
	mov [old_beep_freq], ax
	or al, 0x1f
%endif
;	call speaker_on
;	ax	:frequency
speaker_on:
	out 0x42, al
	mov al, ah
	out 0x42, al

	in al, 0x61
	or al, 0x3
	out 0x61, al
;	ret
	ret

%if 0
speaker_off:
	in al, 0x61
	and al, 0xfc
	out 0x61, al
	ret
%endif

%if 0
;
;return
; pop :力のx値
; pop :力のx値
get_explode_force:
	mov bp, sp
	pusha
	assert_seg_eq ds, ram_segment
	mov cx, 2
	mov si, field_force
	xor bx, bx
.comp:
	mov ax, [di]	;position of particle
	mov dx, [si]	;position of field force
	sub ax, dx
	sar ax, 8
	push ax
	imul al
	add bx, ax	;bx is |particleの位置-field forceの位置|^2
	inc di
	inc di
	inc si
	inc si
	loop .comp
	shr bx, 8
	inc bx		;0での除算を避ける.
;	or  bx, 32
	mov cl, [field_force+4]
	pop ax
	idiv bl
	imul cx
	mov [bp+4], ax
	pop ax
	idiv bl
	imul cx
	mov [bp+2], ax
	popa
	ret
%endif

%ifdef DEBUG
assert_fail:
	push ax
	mov ax, cs
	mov ds, ax
	mov ah, 9
	mov dx, assert_fail_msg
	int 0x21
	pop ax
	push '$'
	mov bx, 10
	mov di, 3
	mov cx, 4
assert_fail__print_line:
	xor dx, dx
	div bx
	add dx, '0'
	mov byte [assert_fail_line+di], dl
	dec di
	loop assert_fail__print_line
	mov dx, assert_fail_line
	mov ah, 9
	int 0x21
	int 0x20
	ret

assert_fail_msg:
	db 'Assertion failed $'
assert_fail_line:
	db 'xxxx$'
%endif

%ifndef MSDOS
	times 510-($-$$) db 0	;0x1feまでを0で埋めるわけだが,このコードには必要ない!
	db 0x55, 0xaa			;ブートセクタの印.
%endif