Table of Contents

Snake & Tetris

Tibuleac Andrei
343C4

Introducere

In cadrul proiectului am implementat jocurile clasice snake si tetris folosind microcontroller-ul ATMega16 si un ecran LCD de Nokia3310

Hardware

Componente aditionale placii de baza: - ecran LCD Nokia3310 - condensator 10uF - 4 butoane push

Schema legaturii ecran - microcontroller - butoane in ISIS

Pinii folositi pentru comunicare ecran - microcontroller:

Pinii folositi pentru butoane (snake/tetris: pin)

Software

Am folosit libraria pcd8544 pentru interfatarea cu ecranul LCD Mediul de dezvoltare folosit: Programmer's Notepad

Implementare snake

Pozitiile segmentelor sarpelui si bucatile de hrana sunt retinute intr-o structura de tip:
struct coord { byte x, y; };

Segmentele sarpelui sunt tinute intr-un vector de astfel de structuri.

Functii:

int check_food_pos(): returneaza 1 daca pozitia hranei nu se intersecteaza cu unul din segmentele sarpelui, altfel returneaza 0.

void generate_food(): genereaza aleator o pozitie noua pentru hrana, pana cand aceasta este intr-o pozitie acceptata de functia check_food_pos().

void checButton(int *direction): scrie in direction directia corespunzatoare butonului apasat.

void initSnake(): initializeaza sarpele cu pozitia si dimensiunea initiale.

int checkSnake(): verifica daca sarpele a lovit un zid sau s-a lovit de propriul corp; mai verifica, de asemenea, daca sarpele a ajuns la mancare, caz in care dimensiunea acestuia creste si se semnaleaza faptul ca trebuie generata o nou segment hrana

void showSnake(): afiseaza pe ecran segmentele sarpelui si hrana

void clearSnake(): sterge de pe ecran segmentele sarpelui si hrana

void moveSnake() muta fiecare segment al sarpelui; capul este mutat conform directiei curente a sarpelui, iar fiecare alt segment se muta pe pozitia celui din fata sa

int checkDirection(int old, int current): este apelata dupa apasarea unui buton; verifica daca sarpele poate merge in directia indicata; daca old si current au aceeasi directie sau directii opuse, directia curenta ramane neschimbata

Algoritmul principal (se executa cat timp flag-ul dead nu este 1):

Implementare tetris

Structuri de date:

Pentru piesa in miscare am definit structura:
typedef struct piece
{
sbyte x, y;
sbyte left_clearance, right_clearance;
piece_type type;
int segment_offset_x[3];
int segment_offset_y[3];
}piece_t;

sbyte - tip de date signed char

x, y → pozitia unui segment al piesei (care va fi considerat segmentul principal al piesei)

left_clearance, right_clearance → distanta dintre segmentul principal si cel mai din stanga, respectiv dreapta, segment al piesei (initial le foloseam pentru a pastra piesa in limitele tablei de joc, dar nu le-am mai folosit, optand pentru alta metoda)

type: tipul piesei; in total 19 tipuri: cele 7 piese si rotatiile lor (4 rotatii pentru piesele de tip L, J si T; 2 rotatii pentru piesele de tip I, S si Z; o rotatioe pentru piesa de tip O)

segment_offset_x/y[3]: vectori de 3 elemente ce reprezinta offset-urile pe axa x, respectiv y, ale celorlalte 3 segmente ale piesei fata de segmentul principla

Pentru a retine segmentele deja existente in joc (cele care nu apartin piesei curente in miscare), am folosit o tabela de 25 de elemente (cate unul pentru fiecare rand) de tip short int. Coloanele sunt reprezentate de bitii 0:9 ai acestor elemente.

* initial folosisem o matrice 25×10 de elemente de tip byte, dar am avut bug-uri ciudate

Functii

void set_piece_offsets(piece_t *p, piece_type t): functie ce seteaza offset-urile segmentelor in functie de fiecare tip de piesa in parte

void draw_square3x3(sbyte x, sbyte y): functie ce deseneaza un patrat 3×3 pe ecran cu coltul stanga sus in pozitia data de x si y;

void draw_border(): deseneaza cele doua linii ce delimiteaza zona de joc

void draw_piece(piece_t *p): deseneaza piesa data ca parametru; se deseneaza fiecare segment in parte, folosing functia draw_square3x3()

void init_table(): initializeaza elementele tabelei de joc cu 0 (oarecum redundant, deoarece tabela este declarata global, dar am vrut sa fiu sigur)

void draw_table(): deseneaza segmentele din tabela (conform bitilor din fiecare element al vectorului corespunzator tabelei)

sbyte check_position(piece_t *p): verifica daca piesa data ca parametru se afla intr-o pozitie valida (nici un segment nu este in afara limitelor tabelei si nici un segment nu se intersecteaza cu alt segment din tabela)

void try_rotate(piece_t *p, piece_type rotation): incearca sa transforme piesa primita ca parametru in tipul rotation (ales in asa fel incat sa fie aceeasi piesa dar cu alta rotatie) - creeaza o piesa noua cu aceeasi parametrii ca si p
- atribuie piesei noi tipul rotation
- se verifica piesa cu functia check_position()
- daca piesa noua este intr-o pozitie valida, o inlocuieste pe cea veche
- altfel, se repeta procedura pe aceeasi piesa cu pozitia shiftata pe orizontala la stanga si la dreapta (am ales sa fac aceasta verificare pentru cazurile cand se incearca rotatia cand o piesa este lipita de un perete)

void rotate(piece_t *p): apeleaza try_rotation() cu parametrul rotation ales corespunzator tipului piesei p

sbyte can_advance(piece_t *p): verifica daca piesa mai poate avansa, asemanator cu try_rotate: - creeaza o piesa noua cu aceeasi parametrii ca si p
- decrementeaza atributul y al piesei noi
- verifica piesa cu check_position()
- daca piesa noua este intr-o pozitie valida, o inlocuieste pe cea veche si returneaza 1
- altfel, returneaza 0

void try_move(piece_t *p, sbyte offset): incearca deplasarea piesei pe orizontala cu offset(care poate fi 1 sau -1); asemanator cu functia can_advance

void copy_to_table(piece_t *p): copiaza segmentele piesei trimise ca parametru in tabela jocului

void check_lines(): foloseste o variabila globala, marked_lines, pentru a marca liniile pline (cu cate un patrat pe fiecare pozitie)
- variabila este initializata cu 0
- se verifica fiecare element din tabela daca este o linie completa (daca este egal cu 0x03FF: 10 biti de 1); daca da, bitul corespunzator liniei in marked_lines este setat pe 1

void remove_lines(): foloseste variabila globala marked_lines pentru a elimina liniile pline;

Algoritm principal

Cat timp nu s-a terminat jocul:
- daca nu exista o piesa curenta in joc, piesa curenta devine piesa next, iar piesa next este generata aleator (piesa next este cea afisata in dreapta zonei de joc)
* se verifica piesa noua cu functia can_advance(), daca esueaza testul, inseamna ca nu poate fi generata (s-a umplut ecranul pana la varf) iar jocul se termina
- in intervalul de timp dintre o avansare in jos a piesei, se trateaza eventualele apasari de buton (miscare pe orizontala, rotatie, coborare rapida)
- se verifica folosind functia can_advance() daca piesa se poate misca pe verticala in jos; daca nu, aceasta este copiata in tabela de joc cu copy_to_table() si se eventualele linii pline cu functiile check_lines() si remove_lines()

Rezultate

Am realizat cu succes atat partea hardware, cat si ambele jocuri.

Bibliografie/Resurse

Pentru codul sursa pentru partea de Snake am pornit de la codul din proiectul lui Vlad Berteanu din 2010 caruia i-am adus cateva modificari.

Schema ISIS este, de asemenea, preluata din acelasi proiect.

Download

Codul sursa si schema ISIS:

proiect_pm_tibuleac_andrei_v2.zip