[Assembler] Gra Snake (wąż) w asemblerze pod procesor 8086 (architektura x86)
Gra Snake napisana w asemblerze pod procesor 8086. Gra ta wykorzystuje tryb tekstowy 80x25 gdzie rysujemy znaki ASCII po pamięci. Gra była kompilowana TASM i linkowana TLINK. W związku z tym, że gra korzysta z trybu DOSa to należy ją odpalić pod emulatorem Dosbox http://www.dosbox.com/ Po zainstalowaniu Dosbox tworzymy sobie na dysku katalog: C:Assembler Wrzucamy do niego kod naszej gry w pliku snake.asm oraz program TASM i TLINK. TASMa i TLINK musimy sobie pobrać z Internetu.
Gra Snake napisana w asemblerze pod procesor 8086. Gra ta wykorzystuje tryb tekstowy 80x25 gdzie rysujemy znaki ASCII po pamięci.
Gra była kompilowana TASM i linkowana TLINK.
W związku z tym, że gra korzysta z trybu DOSa to należy ją odpalić pod emulatorem Dosbox http://www.dosbox.com/
Po zainstalowaniu Dosbox tworzymy sobie na dysku katalog: C:Assembler Wrzucamy do niego kod naszej gry w pliku snake.asm oraz program TASM i TLINK. TASMa i TLINK musimy sobie pobrać z Internetu.
Uruchamiamy emulator Dosbox. Musimy zamontować katalog z naszą grą wpisując poniższe polecenia: Każda nowa linia to kolejne polecenie które wpisujemy w oknie Dosbox. Ostatnie polecenie uruchamia naszego snake’a. [code=plain]mount C C:Assembler C: tasm snake tlink snake snake[/code]
Gra oferuje różne tryby szybkości poruszania się węża oraz możliwość losowania dodatkowych owoców na planszy. Standardowo na planszy cały czas są dwa czerwone owoce. Klawiszem 4 zwiększamy o jeden liczbę owoców na planszy. Klawisz 5 dodaje nam pięć owoców na planszę. Pozycja każdego kolejnego owocu jest losowana przy kolejnym ruchu węża o jedno pole. Klawisz 6 ustawia nam liczbę owoców na standardową czyli na dwa. Oznacza to, że dopiero gdy snake zje owoce znajdujące się na planszy to ponownie będą losowane kolejne owoce tak aby zawsze na planszy znajdowały się max dwa owoce.
Gra jest szczegółowo opisana komentarzami także nie powinno być problemów ze zrozumieniem kodu. Ogólnie mechanika gry opiera się na tym, że snake zapisywany jest w tablicy będącej kolejką. Każda nowa pozycja głowy węża jest dopisywana na początku kolejki zaś z jej końca pobierany jest adres ogona węża w którego miejsce wstawiany jest znak podłoża po którym porusza się wąż.
Gra kończy się w momencie gdy dotkniemy żółtej ramki planszy lub gdy wąż wjedzie na samego siebie. A także w momencie gdy zawrócimy węża o 180 stopni.
Poniżej kod gry który należy zapisać do pliku [b]snake.asm[/b] [code=plain] ;################################################################################################################### ;# Copyright (c) 2011 Webook.pl ;# The MIT License ;# Gra: Snake 2011 ;################################################################################################################### ;# Notacja w kodzie: ;# ;# pNR__NazwaProcedury ;# * p - Każda nazwa procedury zaczyna się od litery p ;# * NR - oznacza numer procedury ;# * __ - wymagane dwa podkreślenia w nazwie procedury ;# * NazwaProcedury - nazwa naszej procedury ;# ;# pNR1_petla_NR2 ;# * pNR1 - numer procedury w której występuje pętla ;# * petla - stałe oznaczenie, że to pętla ;# * NR2 - numer pętli występującej w procedurze pNR1 ;# ;# petla_Nazwa [Są to oznaczenia dla pętli umieszczonych globalnie] ;# * petla_ - oznaczenie ze to pętla ;# * Nazwa - nazwa dla pętli ;# ;# e_pNR1_NR2 ;# * e_ - oznacza, że to etykieta ;# * pNR1 - numer pętli w której występuje etykieta ;# * NR2 - numer etykiety występującej w pętli pNR1 ;# ;# e_Nazwa [Są to oznaczenie dla etykiet globalnych] ;# * e_ - oznaczenie, że to etykieta ;# * Nazwa - nazwa naszej etykiety ;# ;# z_Nazwa ;# * z_ - prefix oznaczający zmienną ;# * Nazwa - nazwa naszej zmiennej ;# ;###################################################################################################################
; Stos stos1 segment STACK dw 1024 dup(?) ; podwójne słowo, dup-duplikat ?-brak określonej wartości, pojemność 1024 szczyt label word stos1 ends
dane segment PUBLIC ; PUBLIC - segmenty o tej samej nazwie łączone są w jeden ciągły segment. Wszystkie adresy w segmencie są łączone względem jego początku.
;=== Zmienne
; 0Dh, 0Ah - znaki nowej linii rn
z_txt_intro db "Webook.pl | 2011 Gra Snake", 0Dh, 0Ah, 0Dh, 0Ah
db "ESC - wyjscie", 0Dh, 0Ah
db "Strzalki - start gry, ruch wezem w dana strone", 0Dh, 0Ah, 0Dh, 0Ah
db " _______ _ _______ _ _______ ", 0Dh, 0Ah
db "( ____ ( ( /| ( ___ ) | / ( ____ ", 0Dh, 0Ah
db "| ( / | ( | | ( ) | | / / | ( /", 0Dh, 0Ah
db "| (_____ | | | | (___) | | (_/ / | (__ ", 0Dh, 0Ah
db "(_____ ) | ( ) | | ___ | | _ ( | __) ", 0Dh, 0Ah
db " ) | | | | | ( ) | | ( | ( ", 0Dh, 0Ah
db "/____) | | ) | | ) ( | | / | (____/", 0Dh, 0Ah
db "_______) |/ )_) |/ | |_/ / (_______/", 0Dh, 0Ah, 0Dh, 0Ah
db "1 - mala predkosc weza", 0Dh, 0Ah
db "2 - srednia predkosc weza", 0Dh, 0Ah
db "3 - duza predkosc weza", 0Dh, 0Ah
db "4 - zwieksz limit owocow o jeden", 0Dh, 0Ah
db "5 - zwieksz limit owocow o 5", 0Dh, 0Ah
db "6 - ustaw limit owocow na 2", 0Dh, 0Ah, 0Dh, 0Ah
db "Aby rozpoczac nacisnij dowolny klawisz...$"
; Długośc węża. Typ dw czyli słowo (16 bajtów ponieważ może wąż urosnąć do maksymalnie 1794 (cała plansza zarysowana. Hipotetyczna sytuacja))
z_dlugosc_weza dw 1 ; Domyślnie jeden ponieważ na początku taką długość ma wąż
z_odczekaj dw 0 ; Zmienna przechowujaca czas odczekania
z_kierunek db 0 ; Kierunek ruchu węża
; Kody strzałek: 75-lewo 77-prawo 72-gora 80-dol
z_lewo db 75d
z_prawo db 77d
z_gora db 72d
z_dol db 80d
z_esc db 27d
z_wynik db 0Dh, 0Ah, "KONIEC", 0Dh, 0Ah, "Zdobyles punktow: $"
z_szybkosc db 4 ; określa szybkość węża. Im mniejsza tym szybciej się wąż porusza
z_owocow_na_planszy dw 0 ; informuje ile jest owocow na planszy. Gdy będą 2 to nie będziemy dalej losować kolejnych
z_losowa1 dw 0 ; losowa liczba
z_losowa2 dw 0
z_limit_owocow dw 2 ; Ustalamy ile owocow moze byc na planszy jednoczesnie
;=== Stałe
s_kolor_ramki equ 14d ; żółty kolor dla ramki planszy
s_kolor_podloza equ 10d ; zielony jasny
s_kolor_weza equ 15d ; biały
s_kolor_owoc equ 12d ; czerwony
s_ascii_podloze equ 176d ; kod ASCII podłoża (trawa)
s_ascii_waz equ 219d ; kod ASCII kratka zamalowana
;=== tablice
; Tablica będzie przechowywała położenie każdego z klocków będących częścią węża.
; (80-2)*(25-2)=1796 Maksymalne zarysowanie planszy przez węża. Pomijamy ramki czyli odejmujemy dwa
; Tablice wypełniamy zerami.
; Pierwszy element tablicy to głowa węża, każdy kolejny to
s_tab_limit equ 1794d ; Stały limit wielkosci tablicy
tab_waz dw s_tab_limit dup(0)
; UWAGA: Opis działania
; Tablica będzie działać jak kolejka. Podczas ruchu węża w miejsce indexu 0 wstawiany będzie numer położenia głowy węża na ekranie.
; Dlasze części węża będą po prostu przesunięte w tablicy o jeden element w górę. Ostatni element będzie kasowany przy każdym kolejnym ruchu.
; Naprościej mówiąc przy każdym kolejnym ruchu węża elementy jego położenia będą się przesuwać o jeden index w górę.
; Na index 0 w tablicy wchodzi element do kolejki, a z ostatniego indexu wychodzi element z kolejki.
; W przypadku gdy wąż zje owoc to nie będzie kasowany ostatni element tablicy dzięki czemu wąż urośnie o jeden element.
; Do kontrolowania długości węża wykorzystamy zmienną pomocniczą z_dlugosc_weza
; UWAGA##### Tablica typu dw dlatego drugi element jest pod indexem 2. Będziemy skakać co dwa indexy!!
dane ends
; AT - segmenty z tym połączeniem są ładowane ; od adresu wskazanego przez wyrażenie numeryczne ; w naszym przypadku pod adresem 0B8000h znajduje ; się początek pamięci ekranu ekran segment AT 0B800h ek db ? atr db ? ekran ends
; Segment kodu code segment ; PRIVATE
assume cs:code,ds:dane,es:ekran,ss:stos1
start:
mov ax,seg dane ;ładujemy rejestry segmentowe DS i ES
mov ds,ax
mov ax,seg ekran ;pamięć ekranu do ES
mov es,ax
;mov ax,seg code
mov ax,stos1
mov ss,ax
mov sp,offset szczyt
MOV AH,0 ;wybór trybu pracy monitora
MOV AL,3 ;text 80x25 kolor
INT 10H ;obsługa ekranu
; Ukrywamy migotający kursor
mov ah, 1
mov ch, 2bh
mov cl, 0bh
int 10h
mov dx,offset z_txt_intro
mov ah,9h ; wypisanie tekstu intro
int 21h
call p2__KolorujIntro ; wywołanie procedury kolorującej napis SNAKE w intro
; Czeka na naciśnięcie dowolnego klawisza
mov ah, 00h
int 16h
; Rysujemy plansze
call p1__RysujPlansze
; Rysujemy węża w początkowym miejscu
mov ek[2000],219d ; Znak ASCII blok zamalowany
mov atr[2000],s_kolor_weza
call p4__LosujOwoc
;### Naciśnięcie ESC spowoduje wyjście z gry
;### Nacisnięcie strzałek powoduje start gry
;### Jeśli naciśniemy inny klawisz to gra czeka aż naciśniemy któryś z wyżej wymienionych
e_UruchomGre:
mov ah,08h ; Pobiera znak z klawiatury. Będzie zapisany w AL
int 21h ; przerwanie
cmp al,z_esc ; jeśli znak to ESC to wychodzimy z programu
; Tutaj musilismy wykonać trik z stworzeniem pomostu w skoku. Ponieważ skok "je" i inne mu podobne są krótkimi skokami
; Powodowało to że nie można było z tego miejsca skoczyć do końca programu (relative jump out of range)
; Dlatego wykonano pomost i użyto skoki jmp
je e_pomost_jmp
jmp e_pomin_pomost
e_pomost_jmp:
jmp e_KoniecProgramu
e_pomin_pomost:
; Kody strzałek: 75-lewo 77-prawo 72-gora 80-dol
cmp al,z_lewo ; strzłka w lewo
je e_StartWeza
cmp al,z_prawo ; strzłka w prawo
je e_StartWeza
cmp al,z_gora ; strzłka w górę
je e_StartWeza
cmp al,z_dol ; strzłka w dół
je e_StartWeza
; Jeśli doszliśmy do tego kroku to znaczy że nie przechwycono klawisza ESC ani strzałek zatem ponownie oczekujemy na naciśnięcie klawisza
jmp e_UruchomGre
;### Wąż zaczyna ruch. Ten blok wykona się tylko przy rozpoczęciu gry
e_StartWeza:
; Jeśli wskoczylismy tu z góry gdzie mamy wybór kierunku ruchu węża przy rozpoczęciu gry to znaczy że w AL mamy wartość strzałki określającej
; w którą stronę zaczyna ruch wąż. Zatem ładujemy do z_kierunek wartość wybranej strzałki
mov z_kierunek,al
; Do pierwszego elementu tablicy ładujemy położenie początkowe naszego węża
mov tab_waz[0],2000d ; Jest to położenie głowy. Głowa zawsze jest pod indexem 0 w tablicy
e_RuchWeza: ; etykieta do której skaczemy gdy wąż jest już w ruchu
mov di,tab_waz[0] ; di zawiera numer pozycji w ktorej narysowana jest głowa węża
mov al,z_kierunek ; ładujemy z_kierunek do AL żeby móc wykonac porównanie.
cmp al,z_lewo
jne e_pomin1 ; Jeśli nie wciśnięto lewo to skaczemy do następnego porównania
; Ten blok się wykona jeśli wciśnieto lewo
sub di,2 ;
e_pomin1:
cmp al,z_prawo
jne e_pomin2
add di,2
e_pomin2:
cmp al,z_gora
jne e_pomin3
sub di,160 ; odejmujemy 160 aby przesunąc się o jeden wiersz w górę
e_pomin3:
cmp al,z_dol
jne e_pomin4
add di,160 ; dodajemy 160 aby przesunąc się o jeden wiersz w dół
e_pomin4:
push di
push ax
push cx
; wywołujemy procedure która sprawdzi czy wąż wjechał na niedozwolony element ramke planszy
; lub jeśli wjechał na owoc to powoduje to wydluzenie węża
call p3__SprawdzZdarzenie
; wywołujemy procedure losujaca owoc
call p4__LosujOwoc
pop cx
pop ax
pop di
e_PobierzKlawisz:
mov ah,01h ; Sprawdzamy czy naciśnięto klawisz
int 16h
jz e_NieWybranoKlawisza ; Skok jeśli liczba równa zero, czyli jeśli nie naciśnięto żadnego klawisza
mov ah,00h
int 16h
cmp al,z_esc ; Porównujemy do klawisza ESC
je e_KoniecProgramu ; Jeśli naciśnięto ESC to skaczemy do wyjścia z gry
; Jeśli naciśnięto klawisze 1,2,3 to zmieniamy szybkość węża
cmp al,31h
je e_szyb_1
cmp al,32h
je e_szyb_2
cmp al,33h
je e_szyb_3
; Jeśli naciśnięto 4 to zwiekszamy limit owocow na planszy o jeden
; jeśli 5 to zwiekszamy limit o 5
; jeśli naciśnięto 6 to resetujemy limit owocow na 2
cmp al,34h ;4 - zwiekszamy limit owocow o 1
je e_limit_owocow1
cmp al,35h ;5 - zwiekszamy limit owocow o 5
je e_limit_owocow2
cmp al,36h ;6 - resetujemy limit owocow na 2
je e_limit_owocow3
mov z_kierunek,ah ; ładujemy numer naciśniętego klawisza do zmiennej informującej o kierunku ruchu
jmp e_NieWybranoKlawisza ; skaczemy do etykiety aby pominac ustawienia szybkosci jesli nie skoczono do nich wczesniej
; Obsługa zmiany szybkosci ruchu węża
e_szyb_1:
mov z_szybkosc,4 ; wolno
jmp e_NieWybranoKlawisza
e_szyb_2:
mov z_szybkosc,2 ; średnia prędkość
jmp e_NieWybranoKlawisza
e_szyb_3:
mov z_szybkosc,1 ; szybko
jmp e_NieWybranoKlawisza
; Obsługa zmiany limitu owocow na planszy
e_limit_owocow1:
add z_limit_owocow,1 ; dodajemy jeden
jmp e_NieWybranoKlawisza
e_limit_owocow2:
add z_limit_owocow,5
jmp e_NieWybranoKlawisza
e_limit_owocow3:
mov z_limit_owocow,2
jmp e_NieWybranoKlawisza
e_NieWybranoKlawisza:
; Opóźnienie. Cykl 18,2 na sekundę
mov ah,00h
int 1ah
cmp dx,z_odczekaj
jb e_PobierzKlawisz ; jb - skok gdy jest poniżej (CF=1)
xor cx,cx
mov cl,z_szybkosc ; Dzięki tej zmiennej będziemy mogli zmieniać sobie szybkość węża
add dx,cx
mov z_odczekaj,dx
jmp e_RuchWeza ; Powtarzamy ruch węża skacząc do etykiety e_RuchWeza
;mov dx,offset tekst1
;mov ah,9h ; wypisanie tekstu (DOS)
;int 21h ; funkcja DOS
;int 16h ; Wymaga naciśnięcia dowolnego klawisza aby przejść dalej
e_KoniecProgramu:
mov ax,4c00h;koniec programu (DOS)
int 21h
;###################################################################################################################
;### Procedury
;### ASCII http://www.asciitable.com/
;=== Rysowanie obramowania planszy =================================================================================
p1__RysujPlansze proc
;=== Ustawiamy limit ekranu, limit ile razy się pętla wykona
mov cx,80*25
;=== Rysujemy tło planszy które będzie podłożem (trawa). Tło to będzie później zamalowane przez ramki planszy
mov si,0
p1_petla_1:
mov ek[si],s_ascii_podloze ; kod ASCII dla kropeczek, które będą udawac trawę
mov atr[si],s_kolor_podloza ; zielony jasny
add si,2 ; zwiększamy index o dwa
loop p1_petla_1
;=== Rysujemy górną i dolną linie obramowania planszy
mov si,0 ; zaczynamy rysowanie od indexu zero
p1_petla_2:
; rysowanie górnej poziomej linii
cmp si,160d
jge e_p1_1 ; jeśli rysujemy już powyżej 80 znaku to pomijamy rysowanie (jeśli si jest większe lub równe 160 (dlatego równe bo rysujemy od 0))
mov ek[si],219d ; Znak ASCII blok zamalowany
mov atr[si], s_kolor_ramki ; ładujemy żółty kolor
e_p1_1:
; rysowanie poziomej linii na dole ekranu
cmp si,3840d ; 2*80*25 = 2*2000 = 4000 (od 4000 odjąc jedną linijkę tekstu 80*2 czyli 4000-160=3840)
jnge e_p1_2 ; skok gdy nie jest większy od
mov ek[si],219d ; Znak ASCII blok zamalowany
mov atr[si],s_kolor_ramki ; ładujemy żółty kolor
e_p1_2:
add si,2 ; zwiększamy index o dwa poniważ 1-to znak ASCII a 2-to atrybut
loop p1_petla_2
;=== Rysujemy linie ponową z lewej strony
; 23 ponieważ będziemy rysować pionową linie z pominięciem już narysowanego jednego znaku u góry i dołu ekranu
mov cx,23 ; limit wywołania pętli.
mov si,160
p1_petla_3:
mov ek[si],219d ; Znak ASCII blok zamalowany
mov atr[si],s_kolor_ramki ; ładujemy żółty kolor
add si,160 ; przesuwamy się o jeden cały wiersz
loop p1_petla_3
;=== Rysujemy linie ponową z prawej strony
; 23 ponieważ będziemy rysować pionową linie z pominięciem już narysowanego jednego znaku u góry i dołu ekranu
mov cx,23 ; limit wywołania pętli.
mov si,318 ; zaczynamy rysowanie od 318 czyli od drugiego wiersza i ostatniego znaku w nim. (160 + 158)
p1_petla_4:
mov ek[si],219d ; Znak ASCII blok zamalowany
mov atr[si],s_kolor_ramki ; ładujemy żółty kolor
add si,160 ; przesuwamy się o jeden cały wiersz
loop p1_petla_4
ret ; Powrót
p1__RysujPlansze endp
;=== Rysowanie obramowania planszy =================================================================================
p2__KolorujIntro proc
mov cx,80*8 ; limit wywołania pętli. Ustawiamy na 8 bo chcemy ustawic kolor dla osmiu wierszy (każdy wiersz to 80 znaków czyli 160 bo mamy atrybut)
mov si,5*160 ; zaczynamy rysowanie od 5 wiersza bo tam zaczyna się napis SNAKE
mov al,1d ; ładujemy kolor do AL
p2_petla_1:
; Kolor wiersza 0 jest ustawiony powyżej w AL. Pierwsza linia znaków która rysuje duzy napis SNAKE
cmp si,6*160
je e_p2_kolor_wiersza_1 ; jeśli si równe 6*160 to możemy zmienić kolor przechowywany w al
cmp si,7*160
je e_p2_kolor_wiersza_2
cmp si,8*160
je e_p2_kolor_wiersza_3
cmp si,9*160
je e_p2_kolor_wiersza_4
cmp si,10*160
je e_p2_kolor_wiersza_5
cmp si,11*160
je e_p2_kolor_wiersza_6
cmp si,12*160
je e_p2_kolor_wiersza_7
cmp si,13*160
je e_p2_kolor_wiersza_8
; Przeskakujemy ustawienia kolorów. Jeśli powyżej nie nastąpił skok do zmiany koloru w rejestrze AL to pomijamy zmiane kolorów
; skacząc do e_p2_1 i używając koloru aktualnie przechowywanego w rejestrze AL.
; Dzięki takiemu zabiegowi nie będziemy ustawiać rejestru AL na ten sam kolor przy każdym ponowym ustawianiu atrybutu danego znaku na ekranie
jmp e_p2_1
; Po ustawieniu danego koloru w AL przeskakujemy do jego ustawienia na ekranie
e_p2_kolor_wiersza_1:
mov al,2d
jmp e_p2_1
e_p2_kolor_wiersza_2:
mov al,3d
jmp e_p2_1
e_p2_kolor_wiersza_3:
mov al,4d
jmp e_p2_1
e_p2_kolor_wiersza_4:
mov al,5d
jmp e_p2_1
e_p2_kolor_wiersza_5:
mov al,6d
jmp e_p2_1
e_p2_kolor_wiersza_6:
mov al,7d
jmp e_p2_1
e_p2_kolor_wiersza_7:
mov al,8d
jmp e_p2_1
e_p2_kolor_wiersza_8:
mov al,9d
jmp e_p2_1
e_p2_1:
mov atr[si],al ; ładujemy żółty kolor
add si,2 ; przesuwamy się o dwa czyli o jedną literkę
loop p2_petla_1
ret ; Powrót
p2__KolorujIntro endp
;=== Funkcja sprawdzająca czy dotknięto ściany lub owocu =======================================================================
; Jeśli dotknięto owocu to następuje wydłużenie węża.
; Funkcja ponadto obsługuje ruch węża i zapis jego położenia w kolejce opartej o tablicę tab_waz
p3__SprawdzZdarzenie proc
; W di zapisany jest numer w którym postawiona będzie głowa węża. Sprawdzimy czy uderzy o ścianę
cmp atr[di],s_kolor_ramki
jne p3_pomin1 ; jeśli kolor klocka na który wstawić chcemy węża nie jest koloru ramki to przeskakujemy
jmp p3_uderzono_w_przeszkode ; jesli wjechalismy na ramke
p3_pomin1:
cmp atr[di],s_kolor_weza
jne p3_pomin2 ; jeśli kolor klocka na który wstawić chcemy węża nie jest koloru węża to przeskakujemy
jmp p3_uderzono_w_przeszkode
p3_uderzono_w_przeszkode:
mov dx,offset z_wynik
mov ah,9h ; wypisanie tekstu o końcu gry.
int 21h
xor ax,ax
mov ax,z_dlugosc_weza ; ładujemy do ax dlugosc weza
push ax ; wkładamy na stos ax
call p5__DrukujLiczbe ; wywołujemy procedure wypisujaca wynik liczbowy
; Czeka na naciśnięcie dowolnego klawisza
mov ah, 00h
int 16h
jmp e_KoniecProgramu
p3_pomin2:
; Tutaj napisac sprawdzanie najechania na owoc. Ma to spowodowac ze nie wywola sie kasowanie ogona
xor bx,bx
; w bl będziemy przechowywac informacje czy nastąpiło zjedzenie owoca.
; Jeśli tak to inna będzie wywoływana pętla.
; 0-oznacza ze nie zwiekszono dlugosci weza
mov bl,0
cmp atr[di],s_kolor_owoc
jne p3_pomin3 ; jeśli nie stawiamy głowy węża na owocu to skaczemy do etykiety
; Ten blok wykona się gdy postawiliśmy głowę węża na owocu
; Dodajemy zwiększamy więc długość węża
add z_dlugosc_weza,1
mov bl,1 ; ustawiamy flagę z informacją że zwiększono długość węża bo zjedzono owoc
p3_pomin3:
;############
; Gdy wąż dłuższy niż 1 i nie zwiększono jego długości
cmp z_dlugosc_weza,1
jle e_pomin_war1_; skok gdy jest mniejszy lub rowny 1
jmp e_pomin_war1_blok
e_pomin_war1_: ; pomost aby wykonac dlugi skok
jmp e_pomin_war1
e_pomin_war1_blok:
; Ten blok się wykona gdy wąż ma długość większą od 1
cmp bl,0
jne e_pomin_war2 ; skocz gdy bl nie jest rowne 0
;###### Ten blok wykona się gdy bl=0 czyli gdy nie zjedzono owocu
; Przenosimy elementy w tablicy o jeden index w górę, a w miejsce indexu=0 wstawimy nową głowę węża
xor cx,cx ; czyścimy cx
; ile razy ma się wykonać pętla. Jest to ilość operacji przeniesienia elementów w tablicy która jest naszą kolejką
; Dlatego -1 bo nie przenosimy ostatniego elementu nigdzie dalej gdyz ma byc on nadpisany przez przedostatni element
mov cx,z_dlugosc_weza
sub cx,1
; si będziemy zmniejszać co krok
; -4 poniewaz ostatni element tablicy nie bedzie przenoszony poniewaz jest on kasowany jesli nie najechano na owoc. A minus 4 ponieważ tablica zaczyna się od indexu 0 i skaczemy co dwa indexy aby dostac sie do kolejnego elementu
mov si,z_dlugosc_weza
add si,z_dlugosc_weza ; dwukrotna dlugosc zapisujemy poniewaz idziemy po tablicy co 2 indexy
sub si,4
; Kasowanie ostatniego elementu z planszy
; odbywa się przed operacją przeniesienia elementów w tablicy o jeden index w górę
; ponieważ w czasie przeniesienia tych elementow przedostatni element przykrywa ostatni i nie mielibysmy pozniej mozliwosci odczytania jakie polozenie ma ogon weza
push di
xor di,di
mov di,tab_waz[si+2] ; +2 poniewaz wyzej mielismy -4 i to bylo wybranie przedostatniego elementu a my potrzebujemy ostatni element w tablicy
mov ek[di],s_ascii_podloze ; Znak ASCII podłoża planszy
mov atr[di],s_kolor_podloza
pop di
p3_petla_1:
xor ax,ax
mov ax,tab_waz[si] ; przedostatni element
mov tab_waz[si+2],ax ; ostatni element przyjmuje wartosc poprzedniego
sub si,2
loop p3_petla_1
jmp e_pomin_a ; po zakonczonej operacji skaczemy aby pominac bloki ponizsze
e_pomin_war2:
cmp bl,1
jne e_pomin_a ; skocz gdy bl nie jest rowne 1
;###### Ten blok wykona się gdy bl=1 czyli gdy zjedzono owoc
sub z_owocow_na_planszy,1 ; zjedlismy owoc wiec odejmujemy z licznika
; Przenosimy elementy w tablicy o jeden index w górę, a w miejsce indexu=0 wstawimy nową głowę węża
xor cx,cx ; czyścimy cx
; ile razy ma się wykonać pętla. Jest to ilość operacji przeniesienia elementów w tablicy która jest naszą kolejką
; Dlatego -1 bo nie przenosimy ostatniego elementu nigdzie dalej gdyz ma byc on nadpisany przez przedostatni element
mov cx,z_dlugosc_weza
sub cx,1
; si będziemy zmniejszać co krok
; -4 poniewaz ostatni element tablicy nie bedzie przenoszony poniewaz jest on kasowany jesli nie najechano na owoc. A minus 4 ponieważ tablica zaczyna się od indexu 0
mov si,z_dlugosc_weza
add si,z_dlugosc_weza ; dwukrotna dlugosc zapisujemy poniewaz idziemy po tablicy co 2 indexy
sub si,4
p3_petla_2:
xor ax,ax
mov ax,tab_waz[si] ; przedostatni element
mov tab_waz[si+2],ax ; ostatni element przyjmuje wartosc poprzedniego
sub si,2
loop p3_petla_2
jmp e_pomin_a
e_pomin_war1:
;###############
; Poniższy kod wykonuje się gdy wąż ma długość 1
cmp z_dlugosc_weza,1
jne e_pomin_a ; jeśli nie jest rowne 1
; Ten blok wykona się gdy wąż ma długość 1
push di ; odkladamy di na stos
mov di,tab_waz[0] ; pobieramy numer pozycji starego polozenia glowy weza
; wstawiamy podloze w miejsce starego polozenia glowy
mov ek[di],s_ascii_podloze ; Znak ASCII podłoża planszy
mov atr[di],s_kolor_podloza
pop di ; zdejmujemy ze stosu di, ktory nam wskazuje nowe polozenie głowy węża
mov tab_waz[0],di ; zapisujemy glowe weza w tablicy
jmp e_pomin_b
e_pomin_a:
; do pierwszego elementu tablicy ładujemy nowe położenie glowy weza
mov tab_waz[0],di
e_pomin_b:
; Rysujemy głowe węża
mov ek[di],s_ascii_waz ; Znak ASCII
mov atr[di],s_kolor_weza
ret
p3__SprawdzZdarzenie endp
;=== Funkcja losująca położenie owoca na planszy =======================================================================
; Jeśli wylosowane pole jest już zajęte to ponawiamy losowanie az trafimy
; na puste pole czyli na podloze na ktorym bedziemy mogli postawic owoc
p4__LosujOwoc proc
mov si,z_limit_owocow
cmp z_owocow_na_planszy,si
jge p4_pomin ; jeśli na planszy jest wiecej owocow lub tyle samo ile wynosi limit to pomijamy generowanie kolejnych owocow
p4_start: ; rozpoczynamy losowanie owoca
mov ah,2ch
int 21h ; Pobieramy czas
; CH=godziny CL=minuty DH=sekundy DL=1/100s
xor ax,ax
mov al,dl
; UWAGA tutaj losujemy tylko do 80 poniewaz dl moze max przyjac wartosc 100.
; Pozniej sobie zwiekszymy dwukrotnie ta wartosc
cmp dl,80
jle p4_pomin2; jeśli jest mniejsze lub rowne
; Ten blok wykona sie gdy dl ma wartosc wieksza od 80
; Odejmiemy wtedy 20 aby miec dlugosc jednego wiersza
sub al,20
p4_pomin2:
mov z_losowa1,ax ; w zmiennej zapisalismy liczbe z zakresu od 0 do 80
add ax,z_losowa1 ; teraz ax bedzie dwukrotnoscia losowej liczby wynoszacej max 80
mov z_losowa1,ax ; ladujemy do losowej liczby dwukrotnosc jej
mov ah,2ch
int 21h ; Pobieramy czas
xor ax,ax
mov al,dl
; UWAGA tutaj zastosowalismy odrazu 50 czyli 2x25 poniewaz dzieki temu mozemy odjac 50 od wylosowanej wiekszej liczby niz 50 i mamy pewnosc ze liczba ta jest wieksza od zera
cmp dl,50
jle p4_pomin3; jeśli jest mniejsze lub rowne
; Ten blok wykona sie gdy dl ma wartosc wieksza od 50
; Odejmiemy wtedy 50 aby miec losowy numer kolumny
sub al,50
p4_pomin3:
mov z_losowa2,ax ; w zmiennej zapisalismy liczbe z zakresu od 0 do 80
mov ax,z_losowa1
mov bx,z_losowa2
mul bx
; wynik w ax
mov si,ax
cmp atr[si],s_kolor_podloza
jne p4_start ; jeśli nie wylosowalismy podloza to skaczemy do gory. Będziemy tak długo losowac az postawimy owoc na podłożu
; malujemy owoc
mov ek[si],219d ; Znak ASCII blok zamalowany
mov atr[si],s_kolor_owoc
add z_owocow_na_planszy,1 ; gdy juz postawimy owoc to zwiększamy flagę o jeden
p4_pomin:
ret
p4__LosujOwoc endp
;=== Procedura wypisujaca liczbe na ekran =======================================================================
p5__DrukujLiczbe PROC
ARG liczba:BYTE = PARAMETR
push bp ; zapisujemy stara wartosc bp na stosie
mov bp,sp ; do bp ladujemy sp
xor ax,ax ; czyscimy
xor cx,cx
mov cl,10 ; ustawiamy na 10 ponieważ chcemy wyświetlić wynik w liczbach dziesiętnych
mov di,0 ;
mov al,liczba
p5_skok:
div cl ; AL=(AX div cl), AH=(AX mod cl)
xor dh,dh
mov dl,ah
add dl,30h ; dodajemy 30h aby były to liczby ASCII
push dx ; dx wrzucamy na stos.
inc di ; zwiększamy o jeden
xor ah,ah
cmp al,0
jne p5_skok ; jeśli nie jest równe 0 to skaczemy do góry
mov cx,di
p5_skok2:
pop dx ; zdejmujemy dx ze stosu. Będziemy go drukować
mov ah,2 ; Funkcja 2 powoduje wypisanie wyniku na standardowe wyjście
int 21h ; przerwanie 21h
loop p5_skok2
pop bp ; zdejmujemy wartosc ze stosu i ladujemy do bp
ret PARAMETR
p5__DrukujLiczbe ENDP
code ends
end start
[/code]