Python на Assembler (Tasm)

Сегодня напишем в текстовом режиме с использованием прерываний BIOS и DOS змейку на Assembler. Для этого нужно знать основы, уметь ассемблировать (Tasm) и компоновать (Tlink) код.

Для начала напишем основу — змейку, которая перемещается в одном направлении по игровому полю. Змейка будет состоять из символа "*", координаты каждого символа хранятся в памяти.



Посмотреть код
model	small

.data					;Сегмент данных. Храним координаты тела змейки
snake	dw 0000h
		dw 0001h
		dw 0002h
		dw 0003h
		dw 0004h
		dw 7CCh dup('?')

.stack 100h

.code
;В начале сегмента кода будем размещать процедуры
delay proc
        push cx
	mov ah,0
	int 1Ah 
	add dx,3
	mov bx,dx
repeat:   
	int 1Ah
	cmp dx,bx
	jl repeat
	pop cx
	ret
delay endp

start:
	mov ax,@data
	mov ds,ax
	mov es,ax

	mov ax,0003h
	int	10h 			;Очищаем игровое поле

	mov cx,5
	mov ax,0A2Ah
	int 10h 			;Выводим змейку из 5 символов "*"


	mov si,8			;Индекс координаты символа головы
	xor di,di			;Индекс координаты символа хвоста
	mov cx,0001h		;Регистр cx используем для управления головой. При сложении от значения cx будет изменяться координата x или y

main:				;Основной цикл
	call delay
        xor bh,bh
	mov ax,[snake+si]		;Берем координату головы из памяти
	add ax,cx		        ;Изменяем координату x
	inc si				
	inc si
	mov [snake+si],ax		;Заносим в память новую координату головы змеи
	mov dx,ax			
	mov ax,0200h
	int 10h 			;Вызываем прерывание. Перемещаем курсор
	mov ah,02h
	mov dl,002Ah
	int 21h			;Прерывание выводит символ '*'
	
	mov ax,0200h 		
	mov dx,[snake+di]
	int 10h
	mov ax,0200h
	mov dl,0020h
	int 21h			;Выводим пробел, тем самым удаляя хвост
	inc di
	inc di
jmp main
end	start        


Добавим процедуру «key_press» обработки нажатия клавиши и присваивания значения регистру CX, отвечающему за направление головы.

Управление стрелками.


key_press
key_press proc
	mov ax, 0100h
	int 16h
	jz en 			;Без нажатия выходим
	xor ah, ah
	int 16h
	cmp ah, 50h
	jne up
	cmp cx,0FF00h		;Сравниваем чтобы не пойти на себя
	je en
	mov cx,0100h
	jmp en
up:	cmp ah,48h
	jne left
	cmp cx,0100h
	je en
	mov cx,0FF00h
	jmp en
left: cmp ah,4Bh
	jne right
	cmp cx,0001h
	je en
	mov cx,0FFFFh
	jmp en
right: cmp cx,0FFFFh
	je en
	mov cx,0001h
en:
	ret
key_press endp


Вызовем её сразу после вызова процедуры delay:

main:
	call delay
    call key_press


Накормим змейку, создаём процедуру «add_food». Эта процедура будет на игровом поле размещать еду, символы "$". В качестве случайных чисел будем брать время.
add_food
add_food proc
sc:	
	inc bl              ;В регистре BL рандомное число
	cmp bx,50h    ;Проверяем границу числа
	jng ex
	shr bl,1           ;Если больше, делим на 2 логическим сдвигом
	jmp sc
ex:
	mov dl,bl         ;Запись координаты
sc2:	
	cmp bx,19h
	jng ex2
	shr bl,2
	jmp sc2
ex2:
	mov dh,bl         ;Запись координаты
	mov ax,0200h
	int 10h
	mov ax,0800h
	int 10h
	cmp al,2Ah       ;Проверяем пустое ли место
	je sc
    cmp al,40h      
    je sc                  ;Если нет повторяем
	mov ax,0200h
	mov dl,0024h
	int 21h
	ret
add_food endp



Вызовем 1 раз в начале.

    mov bl,51h
    call add_food
main:


Делаем проверку, съела змея еду или нет. Если съела, вызываем процедуру «add_food» и не удаляем хвост.

Проверку добавляем в код перед выводом символа головы:

        mov ah,02h
	int 10h 			;Вызываем прерывание. Перемещаем курсор

	mov ax,0800h
	int 10h                     ;Читает символ 
	mov dh,al

	mov ah,02h
	mov dl,002Ah
	int 21h 			;Прерывание выводит символ '*'

	cmp dh,24h
	jne next
	call add_food
	jmp main
next:	


Усложним игру. После того, как питон съест 5 символов, в хвосте будет появляться символ "@". Пишем счетчик и вывод символа:


shit
;В сегмент данных добавим строчку
.data
tick	dw 0			;Счетчик
--------------------------------------------------------------------

	cmp dh,24h
	jne next

	push cx				;В стек регистр
	mov cx,[tick]
	inc cx
	cmp cx,5
	jne exl
	xor cx,cx
	mov ax,0200h 		
	mov dx,[snake+di-2]
	int 10h
	mov ax,0200h
	mov dl,0040h
	int 21h
exl:mov [tick],cx
	pop cx

	call add_food
	jmp main
next:	


Какая игра без Game Over. Пишем процедуру проверки границы поля, а также врезание в себя и символ "@".
game_over
game_over proc
;Проверяем границы
	cmp dl,50h
	je exit
	cmp dl,0
	jl exit
	cmp dh,0
	jl exit
	cmp dh,19h
	je exit
;Проверяем символы
	cmp al,2Ah
	je exit
	cmp al,40h
	je exit
	jmp good
exit: 
    mov ax,4c00h
    int 21h
good:
	ret
game_over endp


Вызываем её после считывания символа:

	mov ax,0800h
	int 10h 			;Считываем символ

	call game_over

	mov dh,al


Немного магии добавляем после инкремента индексов.
magic
	inc si				
	inc si
	cmp si,7CAh
	jne nex
	xor si,si
nex:	

---------------------------------------------------------------------

	inc di
	inc di
	cmp di,7CCh
	jne main
	xor di,di



Ну вот и всё, также можно добавить меню с выбором уровня, паузу, заставку Game Over, счет очков.

По ссылке архив с исходным кодом, exe'шником и DosBox для тех, у кого не запустится.

С прошедшим днём программиста!
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 16

    +66
    Заголовок вообще жжёт, я как питонист очень оценил ;)
      +4
      Да лучше было бы Змейка на assembler, хотя так будет больше читателей.
        +1
        Я, заходя, надеялся увидеть, как мега крутой чувак пишет интерпретатор питона на асме)
        0
        Курсор можно прятать, позиционируя за пределами окна вывода.

        А delay можно было через BIOS tick counter сделать, без дергания прерываний. Счетчик лежит в слове 0:46Ch вроде.
          0
          Позабыл уже какое именно, но есть какая-то функция у какого-то прерывания чтоб просто тупо спрятать курсор.
          Это чтобы спрятать при начале игры, а не при каждой отрисовке. Можно сэкономить несколько тактов, да ;-)
          Ну и восстановить при выходе не забыть.
            0
            Я уже мало что помню. Больше 10 лет прошло со времен bios и т.п. кодинга. Может и была такая функция…

            p.s. Да и если уж гнаться за тактами, то вообще лучше сразу в память писать.
              0
              Ниже ответил. Нашел в бекапах школьных проектов уже 13ти летней давности вставки на асме в паскалевских проектах)))
              +1
              Нашел =)
              Для включения:
              mov ax,0100h
              mov cx,0506h
              int 33

              Для выключения:
              mov ax,0100h
              mov cx,2607h
              int 10

                0
                33h 01h — GRTMOUSE. это какая-то прям специфика.
                  0
                  Действительно у меня в старом коде какая-то то ли ошибка, то ли опечатка. Должно быть (судя по вики)
                  Для включения:
                  mov ax,0100h
                  mov cx,0607h
                  int 10

                  Спасибо.
              +1
              Вообще курсор вырубается 10h прерыванием, в регистре CX принимает размеры. Прочитал комменты ниже, да, от Godless правильное решение.
              +1
              А теперь то же самое в консоли на винапи :)
                +2
                Еще проще, имхо, и читабельнее получится.
                +16
                Месье знает толк в заголовках!
                  0
                  Черт у самого уже черти сколько валяется недописанная змейка на NASM (Win32), спасибо что напомнили — допишу.
                    +1
                    10 лет назад мы с братом соревновались, кто напишет меньшую по размерам змейку, ползущую по диагонали и отражающуюся от краёв экрана. Я победил. :)

                    Код
                    .286
                    CSEG Segment
                    ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
                    ORG 100h
                    Program:
                        mov  ax,0500h    ; Set video
                        int  10h
                        mov  ah,02h
                        mov  dl,' '
                        mov  cx,80*50
                    Clean:
                        int  21h
                        loop Clean
                    
                        mov  ax,351Ch    ; Set timer
                        int  21h
                        push es
                        push bx
                        mov  bh,0
                        xor  bp,bp
                        mov  ah,25h
                        mov  dx,offset Timer
                        int  21h
                    
                        mov  ah,0        ; Waiting for a key
                        int  16h
                    
                        mov  ax,251Ch    ; Kill timer
                        pop  dx
                        pop  ds
                        int  21h
                        ret
                    
                    
                    ;    --==< Drawing Interrupting Function >==--
                    
                    Timer:
                        cli
                        push cs
                        pop  ds
                        push cs
                        pop  es
                        mov  cx,5
                        mov  si,offset Coord5
                        mov  di,offset Line
                    
                    DrawSnake:
                        mov  ah,2        ; Set coordinates
                        mov  dx,cs:[si]
                        int  10h
                        dec  si
                        dec  si
                        mov  ah,0Ah      ; Draw character
                        mov  al,cs:[di]
                        push cx
                        mov  cl,1
                        int  10h
                        pop  cx
                        inc  di
                        loop DrawSnake
                    
                        mov  cx,4        ; Trail tail
                        mov  si,offset Coord4
                        mov  di,offset Coord5
                        std
                        rep  movsw
                    
                        cmp  byte ptr Coord1,0  ; Horizontal/vertical rebounce
                        je   HorFlip
                        cmp  byte ptr Coord1,79
                        je   HorFlip
                        jmp  NoHorFlip
                    HorFlip:
                        xor  bp,01h
                    NoHorFlip:
                        cmp  byte ptr Coord1+1,0
                        je   VerFlip
                        cmp  byte ptr Coord1+1,24
                        je   VerFlip
                        jmp  NoVerFlip
                    VerFlip:
                        xor  bp,10h
                    NoVerFlip:
                        test bp,01h      ; Move head
                        jz   HorInc
                        dec  byte ptr Coord1
                        jmp  HorMoved
                    HorInc:
                        inc  byte ptr Coord1
                    HorMoved:
                        test bp,10h
                        jz   VerInc
                        dec  byte ptr Coord1+1
                        jmp  VerMoved
                    VerInc:
                        inc  byte ptr Coord1+1
                    VerMoved:
                        mov  ah,0
                        iret
                    
                        Line   db ' ░▒▓█'
                        Coord1 dw 0108h
                        Coord2 dw 0007h
                        Coord3 dw 0007h
                        Coord4 dw 0007h
                        Coord5 dw 0007h
                    CSEG ends
                    END  Program
                    

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое