diff --git a/du8/Makefile b/du8/Makefile new file mode 100644 index 0000000..51c416e --- /dev/null +++ b/du8/Makefile @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Wall -Wextra -pedantic -std=c99 +LIBS = -lncurses -lm +TARGET = asteroids +OBJS = main.o game.o world.o + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(LIBS) + +main.o: main.c game.h world.h + $(CC) $(CFLAGS) -c main.c + +game.o: game.c game.h world.h + $(CC) $(CFLAGS) -c game.c + +world.o: world.c world.h + $(CC) $(CFLAGS) -c world.c + +clean: + rm -f $(TARGET) $(OBJS) diff --git a/du8/README.md b/du8/README.md new file mode 100644 index 0000000..7a8a810 --- /dev/null +++ b/du8/README.md @@ -0,0 +1,106 @@ +======================================= + Dokumentácia hry: Ultimate Asteroids +======================================= + + +Predstavenie Hry +------------------ + +Ultimate Asteroids je dynamická 2D vesmírna strieľačka, ktorá berie to najlepšie z klasických arkádových hier ako Space Invaders. Ako pilot osamelej vesmírnej lode je vašou úlohou prežiť nápor neustále prichádzajúcich vĺn nepriateľov a nakoniec sa postaviť tvárou v tvár mocnej Materskej lodi vo finálnom súboji. + +Hra ponúka svieži pohľad na známy žáner a obsahuje: + +* Menu s voľbou obtiažnosti, ktorá priamo ovplyvňuje rýchlosť a agresivitu nepriateľov. +* Štyri unikátne typy nepriateľov s odlišným správaním a odolnosťou. +* Dynamický combo systém, ktorý vás odmení za rýchle a presné zničenie protivníkov. +* Taktické bariéry, ktoré dočasne blokujú strely a menia dynamiku boja. +* Epický súboj s finálnym bossom, ktorý preverí všetky vaše schopnosti svojimi unikátnymi útočnými vzormi. + + +Preklad a Spustenie +--------------------- + +Hra je napísaná v jazyku C a pre zobrazenie a interakciu využíva knižnicu 'ncurses' (prostredníctvom dodanej knižnice 'world'). + +Požiadavky: +* Prekladač 'gcc' +* Nástroj 'make' +* Knižnica 'ncurses' (na serveri sigma je štandardne dostupná) + +Postup prekladu: +1. Umiestnite všetky súbory ('Makefile', 'main.c', 'game.c', 'game.h', 'world.c', 'world.h') do jedného adresára. +2. V termináli prejdite do tohto adresára. +3. Spustite príkaz 'make'. Tento príkaz automaticky skompiluje všetky súbory a vytvorí finálny spustiteľný súbor s názvom 'asteroids'. + + +Spustenie hry: +Hru spustíte z terminálu nasledujúcim príkazom: + + ./asteroids + + +Návod na Hranie +----------------- + +Cieľ hry: +Vaším hlavným cieľom je prežiť čo najdlhšie, nahrať čo najvyššie skóre a po uplynutí približne jednej minúty poraziť finálneho bossa – Materskú loď. + +Ovládanie: +* Šípka vľavo: Pohyb lode doľava. +* Šípka vpravo: Pohyb lode doprava. +* Medzerník: Vystrelenie projektilu. +* q: Okamžité ukončenie hry. + +Herné mechaniky: + +Životy: Začínate s 5 životmi. Život stratíte, ak sa vaša loď zrazí s nepriateľom, alebo ak nepriateľ preletí cez spodný okraj obrazovky a unikne vám. + +Nepriatelia: V hre narazíte na 4 typy nepriateľov: +* 'v' Asteroid: Základný nepriateľ. Letí priamo dole a nepredstavuje veľkú hrozbu osamote. +* 'S' Stíhač: Nevyspytateľný protivník, ktorý sa pohybuje v sínusových vlnách, čo ho robí ťažkým cieľom. +* 'O' Tank: Odolný nepriateľ, ktorý vydrží až dva zásahy, kým ho zničíte. +* '*' Mína: Pasívna hrozba, ktorá po určitom čase sama zmizne. + +Bariéry <===>: V náhodných intervaloch sa na obrazovke objavujú dočasné bariéry. Slúžia ako štít, ktorý blokuje vaše strely, ale pozor – nepriatelia cez ne môžu bez problémov prejsť! + +Combo systém: Za každého nepriateľa zničeného v rýchlom slede sa vám zvyšuje násobič skóre (Combo: x2, x3, ...). Ako špeciálny bonus, po dosiahnutí COMBA x10, vaša loď získa dočasný ŠTÍT, ktorý ju ochráni pred jedným zásahom alebo koliziou. + +Boss fight: Po uplynutí stanoveného času sa na obrazovke zobrazí varovanie a začne súboj s Materskou loďou. Boss sa pohybuje zo strany na stranu a strieda dva útočné vzory: streľbu a vypúšťanie nepriateľov. + +Koniec hry: +* Prehra: Hra končí, keď počet vašich životov klesne na nulu. +* Výhra: Hra končí vaším triumfom po úspešnom zničení Materskej lode. + + +Popis Implementácie +---------------------- + + +Najdôležitejšie štruktúry ('game.h'): +* 'StavHry': Centrálna štruktúra, ktorá drží kompletný stav celej hry – obsahuje polia pre všetky dynamické objekty, informácie o hráčovi a bossovi, a tiež premenné riadiace stav hry ('stav_programu'), skóre, životy a rôzne časovače. +* 'Nepriatel': Reprezentuje jedného nepriateľa. Okrem pozície obsahuje aj 'typ', 'hp' (životy) a 'farbu'. +* 'Boss': Unikátna štruktúra pre finálneho bossa, ktorá si pamätá jeho životy, aktuálny útočný vzor ('aktualny_vzor') a časovače. + +Kľúčové funkcie: +* 'main()' ('main.c'): Vstupný bod programu. Jeho jediná úloha je odovzdať riadenie knižnici 'world' a poskytnúť jej tri kľúčové "callback" funkcie. +* 'world_event()' ('main.c'): Centrálny dispečer udalostí, ktorý reaguje na stlačenie klávesy alebo uplynutie času a volá ďalšie funkcie. +* 'aktualizuj_stav()' ('game.c'): "Srdce" hernej slučky. Volá sa v pravidelných intervaloch a zabezpečuje všetku hernú logiku. +* 'vykresli_stav()' ('game.c'): Zodpovedá za kompletné vykreslenie všetkého na obrazovku. + + + + +Modifikácia knižnice 'world' +------------------------------ + +Pôvodná knižnica 'world' mala definície funkcií umiestnené priamo v hlavičkovom súbore 'world.h', čo spôsobovalo chyby pri kompilácii ("redefinition of function"). + +Riešenie: +Knižnica bola vylepšená tak, aby hlavičkový súbor 'world.h' obsahoval iba DEKLARÁCIE funkcií a zdrojový súbor 'world.c' obsahoval ich IMPLEMENTÁCIU .Týmto sa zabezpečila bezchybná kompilácia. + + +Použité Zdroje +----------------- + +* Základná knižnica 'world': https://github.com/hladek/world (poskytnutá v rámci zadania) +* Inšpirácia: žáner "shoot 'em up" ako Space Invaders a Galaga. diff --git a/du8/asteroids b/du8/asteroids new file mode 100755 index 0000000..2e18ac6 Binary files /dev/null and b/du8/asteroids differ diff --git a/du8/game.c b/du8/game.c new file mode 100644 index 0000000..f2cf2c3 --- /dev/null +++ b/du8/game.c @@ -0,0 +1,563 @@ +#include +#include +#include +#include +#include "game.h" + +// --- Deklaracie funkcii --- +void spusti_hru(StavHry *stav); +void vytvor_nepriatela(StavHry *stav); +void vytvor_barieru(StavHry *stav); +void vystrel(StavHry *stav); +void inicializuj_bossa(StavHry *stav); +void spracuj_logiku_bossa(StavHry *stav); + +// Grafika pre bossa +const char *boss_logo[] = { "<[O|O|O]>", "//---\\\\" }; + + +/********************************************************** +* 1. CAST : VSTUP A ZACIATOCNE FUNKCIE/PARAMETRE ATD. +**********************************************************/ + +/* + * Spracovanie vstupu - stlacenie klaves + * stav -> Ukazovatel na aktualny stav hry + * klavesa -> cod stlacenej klavesy + */ +void spracuj_vstup(StavHry *stav, int klavesa) +{ + // ovladanie v hlavnom menu + if (stav->stav_programu == V_MENU) { + switch (klavesa) { + case KEY_UP: + stav->menu_vyber--; + if (stav->menu_vyber < 0) stav->menu_vyber = 2; + break; + case KEY_DOWN: + stav->menu_vyber++; + if (stav->menu_vyber > 2) stav->menu_vyber = 0; + break; + case KEY_ENTER: + case '\n': + case '\r': + case ' ': + stav->obtiaznost = (Obtiaznost)stav->menu_vyber; + spusti_hru(stav); + break; + } + } + // ovladanie pocas hry a BOSS fightu + else if (stav->stav_programu == HRAJE_SA || stav->stav_programu == BOSS_SOUBOJ) { + switch (klavesa) { + case KEY_LEFT: + stav->hrac.data.x -= 2; + break; + case KEY_RIGHT: + stav->hrac.data.x += 2; + break; + case ' ': + vystrel(stav); + break; + } + // teleport na druhu sstranu + if (stav->hrac.data.x < 0) { + stav->hrac.data.x = stav->sirka_obrazovky - 1; + } + if (stav->hrac.data.x >= stav->sirka_obrazovky) { + stav->hrac.data.x = 0; + } + } +} + +/* + * Rozbehnutie zakladnych veci pri prvom spusteni + */ +void inicializuj_hru(StavHry *stav, int sirka, int vyska) { + stav->sirka_obrazovky = sirka; + stav->vyska_obrazovky = vyska; + stav->stav_programu = V_MENU; + stav->menu_vyber = 0; + game_speed(80); // rychlost hry (menej je rychlejsie) +} + +/* + * Spusti novu hru a resetuje udaje zo starej + */ +void spusti_hru(StavHry *stav) { + // Ulozenie nastaveni, ktore sa maju zachovat (sirka, vyska, obtiaznost) + int sirka = stav->sirka_obrazovky; + int vyska = stav->vyska_obrazovky; + Obtiaznost obt = stav->obtiaznost; + + // odstranenie starych udajov z memory + memset(stav, 0, sizeof(StavHry)); + + // zakladne veci pre hru + stav->sirka_obrazovky = sirka; + stav->vyska_obrazovky = vyska; + stav->obtiaznost = obt; + stav->stav_programu = HRAJE_SA; + stav->zivoty = 5; + + // startovna pozicia mojej lode + stav->hrac.data.x = stav->sirka_obrazovky / 2.0; + stav->hrac.data.y = stav->vyska_obrazovky - 2; + stav->hrac.data.aktivny = 1; + + // spawner barier + switch (stav->obtiaznost) { + case LAHKA: stav->bariera_spawn_casovac = 60; break; + case STREDNA: stav->bariera_spawn_casovac = 50; break; + case TAZKA: stav->bariera_spawn_casovac = 40; break; + } +} + + +/********************************************************** +* 2. CAST : VYTVARANIE LODI +**********************************************************/ + +//vytvaranie enemy ldoi +void vytvor_nepriatela(StavHry *stav) { + for (int i = 0; i < MAX_NEPRIATELIA; i++) { + if (!stav->nepriatelia[i].data.aktivny) { + Nepriatel *n = &stav->nepriatelia[i]; + n->data.aktivny = 1; + n->data.x = rand() % stav->sirka_obrazovky; + n->data.y = 0; + n->pociatocne_x = n->data.x; // Pre sinusovy pohyb + + // randomne urcovanie typu lode + int typ_roll = rand() % 100; + if (typ_roll < 60) n->typ = TYP_ASTEROID; + else if (typ_roll < 85) n->typ = TYP_STIHAC; + else if (typ_roll < 95) n->typ = TYP_TANK; + else n->typ = TYP_MINA; + + // Nastavenie vlastnosti podla typu + switch (n->typ) { + case TYP_ASTEROID: n->hp = 1; n->farba = COLOR_WHITE; break; + case TYP_STIHAC: n->hp = 2; n->farba = COLOR_CYAN; break; + case TYP_TANK: n->hp = 5; n->farba = COLOR_GREEN; break; + case TYP_MINA: n->hp = 3; n->farba = COLOR_RED; n->casovac = 50; break; + } + + // rychlost podla obtiaznosti + float rychlost_mod = 1.0f; + if (stav->obtiaznost == STREDNA) rychlost_mod = 1.2f; + if (stav->obtiaznost == TAZKA) rychlost_mod = 1.6f; + n->data.rychlost_y = (0.08 + (rand() % 10) / 80.0) * rychlost_mod; + return; + } + } +} + +/* + * Vytvaranie barier + */ +void vytvor_barieru(StavHry *stav) { + for (int i = 0; i < MAX_BARIERY; i++) { + if (!stav->bariery[i].data.aktivny) { + Bariera *b = &stav->bariery[i]; + b->data.aktivny = 1; + b->data.x = rand() % (stav->sirka_obrazovky - 5); // 5 je sirka baqriery, toto nemenit + b->data.y = rand() % (stav->vyska_obrazovky - 10) + 5; // na y v strede + b->zivotnost = 25; + return; + } + } +} + +/* + * Vytvaranie strely + */ +void vystrel(StavHry *stav) { + for (int i = 0; i < MAX_STRELY; i++) { + if (!stav->strely_hrac[i].data.aktivny) { + Strela *s = &stav->strely_hrac[i]; + s->data.aktivny = 1; + s->data.x = stav->hrac.data.x; + s->data.y = stav->hrac.data.y - 1; + s->data.rychlost_y = -1; // ide hore :) + return; + } + } +} + +/** + * spawn bossa a priprava na bossfight + */ +void inicializuj_bossa(StavHry *stav) { + stav->stav_programu = BOSS_SOUBOJ; + // despawn enemy lodi + for (int i = 0; i < MAX_NEPRIATELIA; i++) { + stav->nepriatelia[i].data.aktivny = 0; + } + Boss *b = &stav->boss; + b->aktivny = 1; + b->hp = BOSS_HP; + b->data.x = stav->sirka_obrazovky / 2; + b->data.y = 5; + b->data.rychlost_x = 0.15; + b->smer_pohybu = 1; // 1 = doprava, -1 = dolava + b->aktualny_vzor = VZOR_SWEEP; + b->casovac_fazy = 400; // zmena utocneho vzoru + b->casovac_utoku = 30; // rychlost strielanie (mensie je rychlejsie) +} + + +/********************************************************** +* 3. CAST : AKTUALIZACIA LOGIKY +**********************************************************/ + +/** + * Aktualizovanie pozicie vsetkych veci na obrazovke + */ +void pohybuj_objektami(StavHry *stav) +{ + // Pohyb nepriatelov + for (int i = 0; i < MAX_NEPRIATELIA; i++) { + if (stav->nepriatelia[i].data.aktivny) { + Nepriatel *n = &stav->nepriatelia[i]; + + // vsetky enemy lode letia dole + n->data.y += n->data.rychlost_y; + + // Stihac ('S') - uhybne manevre + if (n->typ == TYP_STIHAC) { + n->data.x = n->pociatocne_x + sin(n->data.y * 0.1) * 15; + // teleport na druhu stranu ak pojde za okraj + if (n->data.x < 0) n->data.x = stav->sirka_obrazovky - 1; + if (n->data.x >= stav->sirka_obrazovky) n->data.x = 0; + } + + // Despawn ak prejde za spodny okraj + if (n->data.y > stav->vyska_obrazovky) { + n->data.aktivny = 0; + if (stav->stit_casovac <= 0) { // odcitanie len pri neaktivnom stite + stav->zivoty--; + } + } + } + } + + // Pohyb striel hraca + for (int i = 0; i < MAX_STRELY; i++) { + if (stav->strely_hrac[i].data.aktivny) { + stav->strely_hrac[i].data.y += stav->strely_hrac[i].data.rychlost_y; + if (stav->strely_hrac[i].data.y < 0) { + stav->strely_hrac[i].data.aktivny = 0; + } + } + } + + // Pohyb striel bossa + for (int i = 0; i < MAX_STRELY; i++) { + if (stav->strely_boss[i].data.aktivny) { + stav->strely_boss[i].data.y += stav->strely_boss[i].data.rychlost_y; + if (stav->strely_boss[i].data.y > stav->vyska_obrazovky) { + stav->strely_boss[i].data.aktivny = 0; + } + } + } + + // Pohyb bossa + if (stav->boss.aktivny) { + stav->boss.data.x += stav->boss.data.rychlost_x * stav->boss.smer_pohybu; + if (stav->boss.data.x < 5 || stav->boss.data.x > stav->sirka_obrazovky - 5) { + stav->boss.smer_pohybu *= -1; // Zmeni smer na okraji + } + } + + // casovac zmiznutia bariery + for (int i = 0; i < MAX_BARIERY; i++) { + if (stav->bariery[i].data.aktivny) { + stav->bariery[i].zivotnost--; + if (stav->bariery[i].zivotnost <= 0) { + stav->bariery[i].data.aktivny = 0; + } + } + } +} + +/** + * Spracovanie utokov bossa, meni jeho fazy a vytvara strely a nepriatelov. + */ +void spracuj_logiku_bossa(StavHry *stav) { + if (!stav->boss.aktivny) return; + + stav->boss.casovac_fazy--; + stav->boss.casovac_utoku--; + + // Zmena utocneho vzoru po uplynuti casu + if (stav->boss.casovac_fazy <= 0) { + stav->boss.aktualny_vzor = (BossAttackPattern)(rand() % 2); // random vyber + stav->boss.casovac_fazy = 300 + rand() % 200; + stav->boss.casovac_utoku = 0; // Okamzity utok po zmene fazy + } + + // zautocenie ak uplynul casovac utoku + if (stav->boss.casovac_utoku <= 0) { + switch (stav->boss.aktualny_vzor) { + case VZOR_SWEEP: // strelba pri pohybe + stav->boss.casovac_utoku = 60; + for (int i = 0; i < MAX_STRELY; i++) { + if (!stav->strely_boss[i].data.aktivny) { + Strela *s = &stav->strely_boss[i]; + s->data.aktivny = 1; + s->data.x = stav->boss.data.x; + s->data.y = stav->boss.data.y + 2; + s->data.rychlost_y = 0.4; + return; + } + } + break; + case VZOR_MINIONS: // vytvaranie lodi + stav->boss.casovac_utoku = 120; + vytvor_nepriatela(stav); + vytvor_nepriatela(stav); + break; + } + } +} + +/** + * Oprava kolizii objektov + */ +void ries_kolizie(StavHry *stav) { + // Strely hraca vs Bariera + for (int i = 0; i < MAX_STRELY; i++) { + if (!stav->strely_hrac[i].data.aktivny) continue; + for (int j = 0; j < MAX_BARIERY; j++) { + if (!stav->bariery[j].data.aktivny) continue; + // zasiahla barieru? + if (fabs(stav->strely_hrac[i].data.y - stav->bariery[j].data.y) < 1 && + stav->strely_hrac[i].data.x >= stav->bariery[j].data.x && + stav->strely_hrac[i].data.x <= stav->bariery[j].data.x + 5) { + stav->strely_hrac[i].data.aktivny = 0; // Znicenie strely + break; + } + } + } + + // Strely hraca vs Enemy + for (int i = 0; i < MAX_STRELY; i++) { + if (!stav->strely_hrac[i].data.aktivny) continue; + for (int j = 0; j < MAX_NEPRIATELIA; j++) { + if (!stav->nepriatelia[j].data.aktivny) continue; + // Kontrola, ci strela zasiahla nepriatela + if (fabs(stav->strely_hrac[i].data.x - stav->nepriatelia[j].data.x) < 2 && + fabs(stav->strely_hrac[i].data.y - stav->nepriatelia[j].data.y) < 2) { + stav->strely_hrac[i].data.aktivny = 0; + stav->nepriatelia[j].hp--; + if (stav->nepriatelia[j].hp <= 0) { // Ak nepriatelovi klesne HP na 0 + stav->nepriatelia[j].data.aktivny = 0; + stav->skore += 10 * (1 + stav->combo_pocitadlo); + stav->combo_pocitadlo++; + stav->combo_casovac = 100; // Reset casovaca pre combo + } else { + stav->nepriatelia[j].farba = COLOR_YELLOW; // Vizuálna odozva na zásah + } + break; + } + } + } + + // Strely hraca vs Boss + if (stav->boss.aktivny) { + for (int i = 0; i < MAX_STRELY; i++) { + if (!stav->strely_hrac[i].data.aktivny) continue; + // kONTROLA ci strela hitla bossa + if (fabs(stav->strely_hrac[i].data.x - stav->boss.data.x) < 5 && + fabs(stav->strely_hrac[i].data.y - stav->boss.data.y) < 2) { + stav->strely_hrac[i].data.aktivny = 0; + stav->boss.hp--; + stav->skore += 50; + if (stav->boss.hp <= 0) { + stav->boss.aktivny = 0; + stav->stav_programu = KONIEC_HRY_VYHRA; + } + } + } + } + + // Kolizie s hracom ak nema stit + if (stav->stit_casovac <= 0) { + // Hrac vs Enemy strewtnutie + for (int i = 0; i < MAX_NEPRIATELIA; i++) { + if (stav->nepriatelia[i].data.aktivny && + fabs(stav->hrac.data.x - stav->nepriatelia[i].data.x) < 2 && + fabs(stav->hrac.data.y - stav->nepriatelia[i].data.y) < 2) { + stav->zivoty--; + stav->nepriatelia[i].data.aktivny = 0; + } + } + // Hrac vs strely bossa + for (int i = 0; i < MAX_STRELY; i++) { + if (stav->strely_boss[i].data.aktivny && + fabs(stav->hrac.data.x - stav->strely_boss[i].data.x) < 2 && + fabs(stav->hrac.data.y - stav->strely_boss[i].data.y) < 2) { + stav->zivoty--; + stav->strely_boss[i].data.aktivny = 0; + } + } + } +} + +/** + * !!!!!!!!!!!!!! Hlavna funkcia pre aktualizaciu logiky hry, volana v kazdom cykle. !!!!!!!!!!!!!!!!!!!!! + */ +void aktualizuj_stav(StavHry *stav) { + // V menu / na konci hry neaktualizovat + if (stav->stav_programu == V_MENU || stav->stav_programu == KONIEC_HRY_VYHRA || stav->stav_programu == KONIEC_HRY_PREHRA) { + return; + } + + // boss warning + if (stav->stav_programu == BOSS_VAROVANIE) { + stav->boss_varovanie_casovac--; + if (stav->boss_varovanie_casovac <= 0) { + inicializuj_bossa(stav); + } + return; + } + + stav->casovac_herneho_cyklu++; + + // Aktualizacia casovacov pre combo a stit + if (stav->combo_casovac > 0) stav->combo_casovac--; else stav->combo_pocitadlo = 0; + if (stav->stit_casovac > 0) stav->stit_casovac--; + + // Aktivacia stitu za combo + if (stav->combo_pocitadlo > 0 && stav->combo_pocitadlo % 10 == 0) { + stav->stit_casovac = 250; + stav->combo_pocitadlo++; // aby sa stit neaktivoval pre to iste combo + } + + pohybuj_objektami(stav); + ries_kolizie(stav); + + if (stav->stav_programu == HRAJE_SA) { + // spawn emeny teamu podla obtiaznosati + int spawn_sanca = (stav->obtiaznost == LAHKA) ? 3 : ((stav->obtiaznost == STREDNA) ? 5 : 8); + if (rand() % 100 < spawn_sanca) { + vytvor_nepriatela(stav); + } + + // Vytvaranie novych barier + stav->bariera_spawn_casovac--; + if (stav->bariera_spawn_casovac <= 0) { + vytvor_barieru(stav); + switch (stav->obtiaznost) { // Reset casovaca + case LAHKA: stav->bariera_spawn_casovac = 60; break; + case STREDNA: stav->bariera_spawn_casovac = 50; break; + case TAZKA: stav->bariera_spawn_casovac = 40; break; + } + } + + // Prechod do fazy varovania pred bossom + if (stav->casovac_herneho_cyklu >= CAS_DO_BOSSA && !stav->boss.aktivny) { + stav->stav_programu = BOSS_VAROVANIE; + stav->boss_varovanie_casovac = 150; + } + } + else if (stav->stav_programu == BOSS_SOUBOJ) { + spracuj_logiku_bossa(stav); + } + + // Kontrola prehry + if (stav->zivoty <= 0) { + stav->stav_programu = KONIEC_HRY_PREHRA; + } +} + + +/********************************************************** +* 4. CAST : VYKRESLENIE +**********************************************************/ + + +void vykresli_stav(StavHry *stav) { + clear_screen(); + + // Main menu + if (stav->stav_programu == V_MENU) { + set_message("ULTIMATE ASTEROIDS", stav->sirka_obrazovky / 2 - 10, stav->vyska_obrazovky / 2 - 4); + set_message("Vyber si obtiaznost:", stav->sirka_obrazovky / 2 - 10, stav->vyska_obrazovky / 2 - 2); + char t[3][20]; + sprintf(t[0], "%s Lahka", (stav->menu_vyber == 0) ? ">" : " "); + sprintf(t[1], "%s Stredna", (stav->menu_vyber == 1) ? ">" : " "); + sprintf(t[2], "%s Tazka", (stav->menu_vyber == 2) ? ">" : " "); + for (int i = 0; i < 3; i++) { + set_message(t[i], stav->sirka_obrazovky / 2 - 10, stav->vyska_obrazovky / 2 + i); + } + set_message("Ovladanie: Sipky, Enter", 1, stav->vyska_obrazovky - 2); + return; + } + + // Vyzor hraca + int hrac_farba = (stav->stit_casovac > 0) ? COLOR_CYAN : COLOR_YELLOW; + set_color_cell('^', (int)stav->hrac.data.x, (int)stav->hrac.data.y, hrac_farba, COLOR_BLACK); + + // Vyzor enemy lodi + for (int i = 0; i < MAX_NEPRIATELIA; i++) { + if (stav->nepriatelia[i].data.aktivny) { + char c = '?'; + switch (stav->nepriatelia[i].typ) { + case TYP_ASTEROID: c = 'v'; break; + case TYP_STIHAC: c = 'S'; break; + case TYP_TANK: c = 'O'; break; + case TYP_MINA: c = '*'; break; + } + set_color_cell(c, (int)stav->nepriatelia[i].data.x, (int)stav->nepriatelia[i].data.y, stav->nepriatelia[i].farba, COLOR_BLACK); + } + } + + // Vyzor striel hrac/boss + for (int i = 0; i < MAX_STRELY; i++) { + if (stav->strely_hrac[i].data.aktivny) { + set_color_cell('|', (int)stav->strely_hrac[i].data.x, (int)stav->strely_hrac[i].data.y, COLOR_RED, COLOR_BLACK); + } + if (stav->strely_boss[i].data.aktivny) { + set_color_cell('!', (int)stav->strely_boss[i].data.x, (int)stav->strely_boss[i].data.y, COLOR_MAGENTA, COLOR_BLACK); + } + } + + // Nakreslenie bariery + for (int i = 0; i < MAX_BARIERY; i++) { + if (stav->bariery[i].data.aktivny) { + set_message("<===>", (int)stav->bariery[i].data.x, (int)stav->bariery[i].data.y); + } + } + + // Nakreslenie bossa + if (stav->boss.aktivny) { + for (int y = 0; y < 2; y++) { + set_message(boss_logo[y], stav->boss.data.x - 5, stav->boss.data.y + y); + } + } + + // HUD + char ui_text[120]; + sprintf(ui_text, "Skore: %d | Zivoty: %d | Combo: x%d", stav->skore, stav->zivoty, 1 + stav->combo_pocitadlo); + set_message(ui_text, 1, 1); + + // Vypis HP pri BOSSFIGHTE + if (stav->stav_programu == BOSS_SOUBOJ && stav->boss.hp > 0) { + char boss_hp_text[20]; + sprintf(boss_hp_text, "BOSS HP: %d", stav->boss.hp); + set_message(boss_hp_text, stav->sirka_obrazovky / 2 - 5, 4); + } + + // Vypis sprav + if (stav->stav_programu == BOSS_VAROVANIE) { + set_message("!!! MATERSKA LOD PRICHAZA !!!", stav->sirka_obrazovky / 2 - 15, stav->vyska_obrazovky / 2); + } + if (stav->stav_programu == KONIEC_HRY_PREHRA) { + set_message("GAME OVER", stav->sirka_obrazovky / 2 - 5, stav->vyska_obrazovky / 2); + } + if (stav->stav_programu == KONIEC_HRY_VYHRA) { + set_message("!!! VITAZSTVO !!!", stav->sirka_obrazovky / 2 - 8, stav->vyska_obrazovky / 2); + } +} diff --git a/du8/game.h b/du8/game.h new file mode 100644 index 0000000..45792bd --- /dev/null +++ b/du8/game.h @@ -0,0 +1,37 @@ +#ifndef GAME_H_ +#define GAME_H_ + +#include "world.h" + +typedef enum { V_MENU, HRAJE_SA, BOSS_VAROVANIE, BOSS_SOUBOJ, KONIEC_HRY_PREHRA, KONIEC_HRY_VYHRA } StavProgramu; +typedef enum { LAHKA, STREDNA, TAZKA } Obtiaznost; +typedef enum { TYP_ASTEROID, TYP_STIHAC, TYP_TANK, TYP_MINA } TypNepriatela; +typedef enum { VZOR_SWEEP, VZOR_MINIONS } BossAttackPattern; +#define MAX_NEPRIATELIA 60 +#define MAX_STRELY 50 +#define MAX_BARIERY 15 +#define BOSS_HP 30 +#define CAS_DO_BOSSA 1200 + +typedef struct { double x, y, rychlost_x, rychlost_y; int aktivny; } PohybujuciObjekt; +typedef struct { PohybujuciObjekt data; } Strela; +typedef struct { PohybujuciObjekt data; TypNepriatela typ; int hp, farba, casovac; double pociatocne_x; } Nepriatel; +typedef struct { PohybujuciObjekt data; int zivotnost; } Bariera; +typedef struct { PohybujuciObjekt data; } Hrac; +typedef struct { int aktivny; PohybujuciObjekt data; int hp, smer_pohybu, casovac_utoku, casovac_fazy; BossAttackPattern aktualny_vzor; } Boss; + +typedef struct { + Hrac hrac; Nepriatel nepriatelia[MAX_NEPRIATELIA]; Strela strely_hrac[MAX_STRELY]; + Strela strely_boss[MAX_STRELY]; Bariera bariery[MAX_BARIERY]; Boss boss; + int skore, zivoty, casovac_herneho_cyklu, menu_vyber; + StavProgramu stav_programu; Obtiaznost obtiaznost; + int combo_pocitadlo, combo_casovac, stit_casovac, boss_varovanie_casovac, bariera_spawn_casovac; + int sirka_obrazovky, vyska_obrazovky; +} StavHry; + +void inicializuj_hru(StavHry *stav, int sirka, int vyska); +void spracuj_vstup(StavHry *stav, int klavesa); +void aktualizuj_stav(StavHry *stav); +void vykresli_stav(StavHry *stav); + +#endif diff --git a/du8/game.o b/du8/game.o new file mode 100644 index 0000000..da610fc Binary files /dev/null and b/du8/game.o differ diff --git a/du8/main.c b/du8/main.c new file mode 100644 index 0000000..3a39240 --- /dev/null +++ b/du8/main.c @@ -0,0 +1,39 @@ +#include +#include +#include "world.h" +#include "game.h" + +void* init_game() +{ + StavHry *stav = (StavHry*) malloc(sizeof(StavHry)); + if (stav == NULL) return NULL; + srand(time(NULL)); + return stav; +} + +void destroy_game(void* game) +{ + free(game); +} + +int world_event(struct event* event, void* game) +{ + StavHry *stav = (StavHry*) game; + + if (event->type == EVENT_START) { + inicializuj_hru(stav, event->width, event->height); + } else if (event->type == EVENT_KEY) { + if (event->key == 'q' || event->type == EVENT_ESC) return 1; + spracuj_vstup(stav, event->key); + } else if (event->type == EVENT_TIMEOUT) { + aktualizuj_stav(stav); + } + + vykresli_stav(stav); + return 0; +} + +int main() +{ + return start_world(init_game, world_event, destroy_game); +} diff --git a/du8/main.o b/du8/main.o new file mode 100644 index 0000000..c8dbf6f Binary files /dev/null and b/du8/main.o differ diff --git a/du8/world.c b/du8/world.c new file mode 100644 index 0000000..d1d6b3c --- /dev/null +++ b/du8/world.c @@ -0,0 +1,73 @@ +#include "world.h" +#include +#include +#include +#include +#include + +int TIMEOUT; + +void abort_game(const char* message) { endwin(); puts(message); exit(1); } + +void check_bounds(const char* source, int x, int y) { + char msg[200]; + if (x < 0 || x >= COLS) { sprintf(msg, "%s: width %d out of bounds (0,%d)", source, x, COLS); abort_game(msg); } + if (y < 0 || y >= LINES) { sprintf(msg, "%s: height %d out of bounds (0,%d)", source, y, LINES); abort_game(msg); } +} + +void clear_screen() { erase(); } +void game_speed(int value) { if (value < 0) abort_game("game_speed: negative"); TIMEOUT = value; } +void set_message(const char* message, int x, int y) { mvprintw(y, x, "%s", message); } +void set_cell(int character, int x, int y) { set_color_cell(character, x, y, COLOR_WHITE, COLOR_BLACK); } + +void set_color_cell(int character, int x, int y, short front_color, short back_color) { + check_bounds("set_color_cell", x, y); + if (has_colors()) { + int pair_id = front_color * 8 + back_color + 1; + attron(COLOR_PAIR(pair_id)); + mvaddch(y, x, character); + attroff(COLOR_PAIR(pair_id)); + } else { + mvaddch(y, x, character); + } +} + +int start_world(void* (*init_game)(), int (*world_event)(struct event* event, void* game), void (*destroy_game)(void* game)) { + srand(time(NULL)); + int r = 0; + TIMEOUT = 100; + if (initscr() == NULL) { puts("Curses Error."); return -1; } + noecho(); cbreak(); nodelay(stdscr, TRUE); keypad(stdscr, TRUE); curs_set(FALSE); + + if (has_colors()) { + start_color(); + for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) init_pair(i * 8 + j + 1, i, j); + } + void* game_state = NULL; + if (init_game != NULL) { game_state = init_game(); assert(game_state != NULL); } + + timeout(TIMEOUT); + struct event event; + memset(&event, 0, sizeof(struct event)); + event.height = LINES; event.width = COLS; event.type = EVENT_START; + + r = world_event(&event, game_state); + refresh(); + while (!r) { + memset(&event, 0, sizeof(struct event)); + event.height = LINES; event.width = COLS; + event.key = getch(); + if (event.key == ERR) event.type = EVENT_TIMEOUT; + else if (event.key == KEY_RESIZE) event.type = EVENT_RESIZE; + else { + event.type = EVENT_KEY; + if (event.key == 27) { int k = getch(); if (k == -1) event.type = EVENT_ESC; else { event.key = k; event.alt_key = 1; } } + } + r = world_event(&event, game_state); + refresh(); + timeout(TIMEOUT); + } + if (destroy_game != NULL) destroy_game(game_state); + endwin(); + return r; +} diff --git a/du8/world.h b/du8/world.h new file mode 100644 index 0000000..38962b8 --- /dev/null +++ b/du8/world.h @@ -0,0 +1,21 @@ +#ifndef _WORLD_H_ +#define _WORLD_H_ + +#include + +enum event_type { EVENT_START, EVENT_TIMEOUT, EVENT_KEY, EVENT_RESIZE, EVENT_ESC, EVENT_END }; + +struct event { + int width, height, key, alt_key, mouse_x, mouse_y, mouse_left, mouse_right, mouse_middle; + enum event_type type; + long int time_ms; +}; + +void set_cell(int character, int x, int y); +void set_color_cell(int character, int x, int y, short front_color, short back_color); +int start_world(void* (*init_game)(), int (*world_event)(struct event* event, void* game), void (*destroy_game)(void* game)); +void game_speed(int value); +void set_message(const char* message, int x, int y); +void clear_screen(); + +#endif diff --git a/du8/world.o b/du8/world.o new file mode 100644 index 0000000..1e5528f Binary files /dev/null and b/du8/world.o differ