PHP код:
var figureLanded: byte absolute 0x3659 ; 0/FF
var figureCoordinates: word absolute 0x365A
var currentRotation: byte absolute 0x365C
var figureAddress: word absolute 0x365D
var figureBuffer: byte[6] absolute 0x365F
var nextFigureData: byte[6] absolute 0x3665
var rotationPreviewBuffer: byte[6] absolute 0x366B
var remainingDelay: byte absolute 0x3671 ; Remaining delay until next game tick occurs (when space is pressed - ticks go faster)
var difficulty: byte absolute 0x3672
var randomSeed: byte absolute 0x3673
var nextFigureIndex: byte absolute 0x3674 ; 1 based
var currentFigureIndex: byte absolute 0x3675 ; 1 based
var figureX: byte absolute 0x3676
var figureY: byte absolute 0x3677
var completedLines: byte absolute 0x3678
var fullLineFlag: byte absolute 0x3679
var notSkipDrawing: byte absolute 0x367A ; Bypass drawing, only calculations (0x01 - draw, 0x00 - bypass)
var gameBuffer: byte[64*25] absolute 0x367F
#define CURSOR_LEFT 0x08 ; Переместить курсор на позицию левее
#define RESET_CURSOR 0x0c ; Переместить курсор в начало экрана (верхний левый угол)
#define CURSOR_RIGHT 0x18 ; Переместить курсор на позицию правее
#define CURSOR_UP 0x19 ; Переместить курсор на строку выше
#define CURSOR_DOWN 0x1a ; Переместить курсор на строку ниже
#define CLS 0x1f ; Очистить экран
#define CURSOR_TO(x, y) 0x1b, 0x59, 0x20 + (y), 0x20 + (x)
extern proc biosPrintChar(ch: C) absolute 0xF809
extern proc biosKeyboardReadChar() absolute 0xF803
extern proc biosKeyboardCheck() absolute 0xF812 ; -> A=0 - не нажата, A=FF - нажата
extern proc biosExitToMonitor() absolute 0xF800
#define GLASS_CHAR '#' ;':'
#define FIGURE_CHAR '▉';'[';'▉';'#'
#define EMPTY_CHAR '.'
#define FIELD_WIDTH 10
#define FIELD_HEIGHT 20
inline proc waitCycleBC() {
cycle:
BC--
A = B
A |= C
jnz cycle
}
$encoding("ut88")
$output("tetris-out.rkr")
$org(0x3000)
proc init() {
notSkipDrawing = A = 1
SP = 0x3F7F
}
proc startGame() {
call printChar(CLS)
call printMessage(msg_keys_info)
HL = 0x1F10
saveregs (HL) {
A = GLASS_CHAR
loop (B = FIELD_HEIGHT+1) {
call drawBlock(y: H, x: L, ch: A)
H--
}
H++
loop (B = FIELD_WIDTH+2) {
call drawBlock(y: H, x: L, ch: A)
L++
}
L--
loop (B = FIELD_HEIGHT+1) {
call drawBlock(y: H, x: L, ch: A)
H++
}
}
L++
A = EMPTY_CHAR
loop (C = FIELD_HEIGHT) {
saveregs (HL) {
loop (B = FIELD_WIDTH) {
call drawBlock(y: H, x: L, ch: A)
L++
}
}
H--
}
select_level:
call printMessage(msg_select_level)
call readKeyboard
call printChar(A)
A -= 0x30
jc select_level
cpi 8
jnc select_level
cma
A &= 0x07
difficulty = ++A
BC = 0x4000
lbl_307D:
BC--
A = B
A |= C
jnz lbl_307D
call printMessage(msg_press_any_key)
lbl_3089:
call checkAndReadKeyboard
A |= A
HL++
jm lbl_3089
H = A
A ^= L
B = mem[HL]
A ^= M
L = A
C = mem[HL]
lbl_3097:
HL += BC
C--
jp lbl_3097
randomSeed = A = H ^ L
completedLines = A = 0
figureLanded = --A
call printMessage(msg_full_lines)
jmp gameLoop
}
proc collapseLines() {
figureLanded = A = 0
remainingDelay = A = difficulty
HL = 0x0C11
B = FIELD_HEIGHT
lbl_30C1:
fullLineFlag = A = 0xFF
loop (C = FIELD_WIDTH) {
call getCell
if (A == FIGURE_CHAR) goto lbl_30D4
fullLineFlag = A = 0
lbl_30D4:
L++
}
L = 0x11
A = fullLineFlag
A |= A
jp lbl_314F
A = difficulty-1
A = ~A
A &= 0x07
A <<= 1
D = A
A <<= 1
A += D + 5
A <<= 1
D = A
A = completedLines + 1
cmp D
cz increaseDifficulty
if (A != 0x64) goto lbl_3100
A = 0
lbl_3100:
completedLines = A
C = 0
lbl_3105:
C++
A -= 0x0A
jnc lbl_3105
A += 0x3A
xchg
HL = 0x1E0B
call drawBlock(y: H, x: L, ch: A)
L--
call drawBlock(y: H, x: L, ch: ' ')
A = C + 0x2F ; '/'
call printChar(A)
xchg
saveregs (HL) {
lbl_3123:
D = H + 1
loop (C = FIELD_WIDTH) {
E = L
xchg
call getCell
xchg
call drawBlock(y: H, x: L, ch: A)
L++
}
H++
L = 0x11
A = H
if (A != 0x1F) goto lbl_3123
HL = 0x1F11
A = EMPTY_CHAR
loop (C = FIELD_WIDTH) {
call drawBlock(y: H, x: L, ch: A)
L++
}
}
H--
lbl_314F:
H++
B--
jnz lbl_30C1
figureAddress = HL = figureBuffer
xchg
HL = nextFigureData
loop (B = sizeof(nextFigureData)) {
mem[DE++] = A = mem[HL++]
}
}
proc gameLoop() {
HL = 0x3447
DE = 0x0018
call randomValue
loop (A) {
HL += DE
}
DE = nextFigureData
saveregs (DE) {
loop (B = sizeof(nextFigureData)) {
mem[DE++] = A = mem[HL++]
}
}
HL = 0x0F06
lbl_3188:
call drawBlock(y: H, x: L, ch: ' ')
call printChar(' ')
A = ++L
if (A != 0x0C) goto lbl_3188
L = 6
A = ++H
if (A != 0x11) goto lbl_3188
call drawFigure(coords: 0x1007, address: DE, ch: FIGURE_CHAR)
A = figureLanded
A |= A
jnz collapseLines
currentRotation = A
figureCoordinates = HL = 0x1F15
figureX = A
figureY = A
call checkCollisions
jc gameOver
call drawCurrentFigureAt(FIGURE_CHAR)
}
proc nextCycle() {
B = A = remainingDelay
C = 1
}
proc keyInputLoop() {
call checkAndReadKeyboard
A |= A
jm noKeyInput()
if (A == '6') goto handleSpeedUp ; Ускорение
if (A == '7') goto handleLeft ; Влево
if (A == '8') goto handleRotate ; Вращение
if (A == '9') goto handleRight ; Вправо
if (A == 0x03) goto gameOver ; Ctrl + C
if (A == 'S') goto handlePause ; Пауза
DE = 0x3671
if (A == ' ') goto handleSpace
}
proc noKeyInput() {
BC--
A = B
A |= C
jnz keyInputLoop
figureX = A
figureY = --A
call calcMove
jnc nextCycle
jmp collapseLines
}
proc gameOver() {
call printMessage(msg_game_over_repeat)
call readKeyboard
if (A == 'Y') goto startGame
if (A == 'N') goto biosExitToMonitor
nop
nop
nop
nop
nop
nop
nop
nop
}
proc handlePause() {
saveregs (HL) {
call printMessage(msg_pause)
saveregs (BC) {
BC = 0
lbl_323A:
BC--
A = B
A |= C
jnz lbl_323A
}
waitKey:
call checkAndReadKeyboard
A |= A
jm waitKey
call printMessage(msg_clear_message)
}
jmp noKeyInput()
}
proc printMessage(msg: HL) {
loop {
A = mem[HL]
A |= A
rz
C = A
HL++
call printChar
}
}
proc printChar(ch: C) {
saveregs (HL, DE, BC) {
@printCharInternal:
saveregs (PSW) {
call biosPrintChar(C)
}
}
ret
}
proc ckeckKeyboard() {
saveregs (HL, DE, BC) {
call biosKeyboardCheck
}
ret
}
proc readKeyboard() {
saveregs (HL, DE, BC) {
call biosKeyboardReadChar
}
ret
}
proc checkAndReadKeyboard() {
call ckeckKeyboard
A |= A
jnz readKeyboard
A |= 0xFF
ret
}
; Clear current figure at current coordinates
;
; The function will clear cells under figure's blocks with '.'
proc clearCurrentFigure() {
C = EMPTY_CHAR
}
; Draw current figure at current coordinates
;
; Draw the currently falling figure at its coordinates stored in 0x365a
; The function uses 0x365d currently falling figure buffer
; C - symbol to draw the figure with
proc drawCurrentFigure(ch: C) {
HL = figureCoordinates
}
; Draw current figure
;
; Draw the currently falling figure at HL logic coordinates.
; The function uses 0x365d currently falling figure buffer
; C - symbol to draw the figure with
proc drawCurrentFigureAt(ch: C) {
xchg
HL = figureAddress
xchg
}
; Draw the figure
;
; Arguments:
; HL - logic coordinates of the figure
; DE - address of the figure buffer
; C - symbol to draw the figure with
proc drawFigure(coords: HL, address: DE, ch: C) {
call drawBlock(y: H, x: L, ch: C)
saveregs (HL) {
loop (B = 3) {
L = A = mem[DE++] + L
H = A = mem[DE++] + H
call drawBlock(y: H, x: L, ch: C)
}
call printChar(RESET_CURSOR)
}
ret
}
; Generate next random value in range 1-7.
proc randomValue() {
currentFigureIndex = A = nextFigureIndex
randomSeed = A = randomSeed + 0xBB
saveregs (BC) {
B = A
A >>= 3
A ^= B
}
A &= 0x07
jz randomValue
nextFigureIndex = A
ret
}
proc increaseDifficulty() {
saveregs (PSW) {
A = difficulty - 1
cpi 1
jnc lbl_32D4
A = 1
lbl_32D4:
difficulty = A
remainingDelay = A
A--
A = ~A
A &= 0b111
A += 0x30
saveregs (HL) {
HL = 0x1F0B
call drawBlock(y: H, x: L, ch: A)
}
}
ret
}
proc calcMove() {
saveregs (BC) {
saveregs (PSW) {
notSkipDrawing = A = 0
}
saveregs (HL) {
call proc_3312
saveregs (PSW) {
notSkipDrawing = A = 1
}
jc lbl_3305
}
call proc_3312
push HL
lbl_3305:
pop HL
figureCoordinates = HL
saveregs (PSW) {
call drawCurrentFigureAt(FIGURE_CHAR)
}
}
ret
}
; Clear the figure at current location, and then check for collisions
proc proc_3312() {
call clearCurrentFigure
}
; Check the falling figure collisions
; Carry flag set if a collision is detected
proc checkCollisions() {
HL = figureCoordinates
L = A = figureX + L
H = A = figureY + H
saveregs (HL) {
call getCell
if (A != EMPTY_CHAR) goto collision
xchg
HL = figureAddress
xchg
loop (B = 3) {
L = A = mem[DE++] + L
H = A = mem[DE++] + H
call getCell
if (A != EMPTY_CHAR) goto collision
}
}
A |= A
ret
collision:
pop HL
L = A = ~figureX + 1 + L
H = A = ~figureY + 1 + H
stc
ret
}
proc handleLeft() {
A = -1
}
proc handleMotion(dx: A) {
figureX = A
figureY = A = 0
call calcMove
DE = 0x3676 ; &figureX
}
proc handleSpace() {
B = A = remainingDelay*2
C = 1
cycle:
BC--
A = B
A |= C
jnz cycle
mem[DE] = A
BC++
jmp noKeyInput()
}
proc handleRight() {
jmp handleMotion(dx: 1)
}
proc handleRotate() {
currentRotation = A = (currentRotation + 1) & 0b11
push BC
call clearCurrentFigure
HL = 0x3441 ; Base address of all figures TODO ???????
DE = 4*6 ; Single figure size (for all rotations)
loop (A = currentFigureIndex) {
HL += DE
}
DE = 6
loop (A = currentRotation + 1) {
HL += DE
}
DE = rotationPreviewBuffer
xchg
figureAddress = HL
loop (B = sizeof(rotationPreviewBuffer)) {
mem[HL++] = A = mem[DE++]
}
figureX = A = 0
figureY = A
call checkCollisions
figureAddress = HL = figureBuffer
jnc applyRotation
currentRotation = A = (currentRotation - 1) & 0b11
jmp rotationExit
}
proc applyRotation() {
DE = rotationPreviewBuffer
loop (B = sizeof(rotationPreviewBuffer)) {
mem[HL++] = A = mem[DE++]
}
}
proc rotationExit() {
call drawCurrentFigure(FIGURE_CHAR)
pop BC
jmp proc_33F0
}
proc handleSpeedUp() {
call increaseDifficulty
}
proc proc_33F0() {
jmp handleMotion(dx: 0)
}
; Блок имеет ширину в два символа. Координаты отсчитываются от нижнего левого угла
;
; The function does 2 things:
; - Updates the block on the screen
; - Stores the new value in the game buffer
; Arguments:
; H - logical Y coordinate of the block
; L - logical X coordinate of the block
; A - Character to print as a block
proc drawBlock(y: H, x: L, ch: A) {
saveregs (HL, DE, BC) {
B = A
call calculateBlockAddress
mem[HL] = B
A = notSkipDrawing
A |= A
jnz draw
A = B
}
ret
draw:
; курсор в точку (E, D)
call printChar(0x1B)
call printChar(0x59)
call printChar(D)
call printChar(E)
C = B
A = B
cpi FIGURE_CHAR
cz printChar
if (A != EMPTY_CHAR) goto skip
call printChar(' ')
C = B
skip:
cpi GLASS_CHAR
cz printChar
A = B
jmp printCharInternal
}
; Return the cell value
;
; Arguments: HL - logical coordinates of the cell
; Return: value in A
proc getCell(xy: HL) {
saveregs (HL, DE) {
call calculateBlockAddress
A = mem[HL]
}
ret
}
; Calculate address of the given block
;
; Arguments:
; H - block vertical coordinate (bottom to top)
; L - block horizontal coordinate (left to right)
;
; Result:
; D - block vertical screen coordinate (top to bottom) + 0x20 (can be passed to Move cursor sequence)
; E - block horizontal screen coordinate + 0x20 (can be passed to Move cursor sequence)
; HL - address of the block byte in the game buffer
;
; Algorithm:
; Y = 0x20 - H ; logic coordinates
; X = L
; D = 0x20 + Y ; Screen coordinates
; E = 0x20 + 2*X
; HL = Y*0x40 + X + 0x367f ; Block address in the buffer
proc calculateBlockAddress(x: L, y: H) {
saveregs (BC) {
B = A = 0x20 - H
C = A = L*2
HL = 0x2020 + BC
xchg
HL = gameBuffer
A >>= 1
C = A
B = A = B >> 2
C = A = (A & 0xC0) | C
B = A = B & 0x3F
HL += BC
}
ret
}
byte[] {
0, 0, 0
; 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
}
;reset_cursor:
;byte[] {
; CURSOR_TO(0, 0), 0
;}
//$org(0x345F)
; Описаний фигур и их вращения.
; Каждая фигура состоит из 4х блоков. Один блок считается центром и имеет координаты (0, 0)
; Положение остальных блоков задаются в виде приращения координат от предыдущего блока.
; Записи из 6 байт (три пары приращений кооридинат) представляют собой все 4 варианта вращения.
figure_I:
byte[] {
-1, 0, 2, 0, 1, 0, ; 1x23 ; 1 ; 1x23 ; 1 ;
0, -1, 0, 2, 0, 1, ; ; x ; ; x ;
-1, 0, 2, 0, 1, 0, ; ; 2 ; ; 2 ;
0, -1, 0, 2, 0, 1 ; ; 3 ; ; 3 ;
}
figure_L:
byte[] {
-1, 0, 2, 0, 0, -1, ; 3 ; 1 ; 2x1 ; 31 ;
0, -1, 0, 2, 1, 0, ; 1x2 ; x ; 3 ; x ;
1, 0, -2,0, 0, 1, ; ; 23 ; ; 2 ;
0, 1, 0,-2, -1,0 ; ; ; ; ;
}
figure_J:
byte[] {
1, 0, -2, 0, 0, -1, ; 3 ; 23 ; ; 1 ;
0, 1, 0, -2, 1, 0, ; 2x1 ; x ; 1x2 ; x ;
-1,0, 2, 0, 0, 1, ; ; 1 ; 3 ; 32 ;
0,-1, 0, 2, -1, 0 ; ; ; ; ;
}
figure_Z:
byte[] {
1, 0, -1, -1, -1, 0, ; 32 ; 3 ; 32 ; 3 ;
0, 1, 1, -1, 0, -1, ; x1 ; x2 ; x1 ; x2 ;
1, 0, -1, -1, -1, 0, ; ; 1 ; ; 1 ;
0, 1, 1, -1, 0, -1 ; ; ; ; ;
}
figure_S:
byte[] {
-1, 0, 1, -1, 1, 0, ; 23 ; 3 ; 23 ; 3 ;
0, 1, -1, -1, 0, -1, ; 1x ; 2x ; 1x ; 2x ;
-1, 0, 1, -1, 1, 0, ; ; 1 ; ; 1 ;
0, 1, -1, -1, 0, -1 ; ; ; ; ;
}
fugure_T:
byte[] {
-1, 0, 2, 0, -1, -1, ; 3 ; 1 ; ; 2 ;
0, -1, 0, 2, 1, -1, ; 1x2 ; x3 ; 2x1 ; 3x ;
1, 0, -2, 0, 1, 1, ; ; 2 ; 3 ; 1 ;
0, 1, 0, -2, -1, 1 ; ; ; ; ;
}
figure_O:
byte[] {
0, -1, 1, 0, 0, 1, ; 12 ; 12 ; 12 ; 12 ;
0, -1, 1, 0, 0, 1, ; x3 ; x3 ; x3 ; x3 ;
0, -1, 1, 0, 0, 1, ; ; ; ; ;
0, -1, 1, 0, 0, 1 ; ; ; ; ;
}
msg_keys_info:
byte[] {
;0x1B, 0x59, 0x24, 0x20,
CURSOR_TO(0, 4),
0x0E, "..7.. ..8.. ..9..", 0x0D, 0x0A,
"ВЛЕВО ВРАЩАТЬ ВПРАВО", 0x0D, 0x0A, 0x0A,
0x75, 0x77, 0x65,
"УВЕЛИЧИТЬ СКОРОСТЬ ..6..", 0x0D, 0x0A, 0x0A,
" ..PROBEL.. СБРОСИТЬ", 0x0D, 0x0A, 0x0F, 0x0A,
" ..S.. ", 0x0E, "ПАУЗА", 0
}
msg_select_level:
byte[] {
CURSOR_TO(0, 1),
"ВАШ УРОВЕНЬ(0-7) ? ", CURSOR_LEFT, 0
}
msg_press_any_key:
byte[] {
CURSOR_TO(0, 2),
"НАЖМИТЕ ЛЮБУЮ КЛАВИШУ !", 0
}
msg_full_lines:
byte[] {
CURSOR_TO(0, 2),
"ЧИСЛО ПОЛНЫХ СТРОК 00", 0x0D, 0
}
msg_game_over_repeat:
byte[] {
CURSOR_TO(8, 0x16),
"ИГРА ЗАКОНЧЕНА, ЖЕЛАЕТЕ ПОВТОРИТЬ?", 0x0F, " (Y/N)", 0
}
msg_pause:
byte[] {
CURSOR_TO(8, 0x16),
"ВОДИТЕЛЬ, УСТАЛ - ОТДОХНИ !", 0
}
msg_clear_message:
byte[] {
CURSOR_TO(8, 0x16),
" ", 0
}
byte[] {
0, 0, 0, 0, 0, 0
}