MAJOR COMMIT. big refactor(did it with AI help). no changes in game logic, just moved things around.

This commit is contained in:
Carlo 2025-11-10 11:02:50 +01:00
parent 3c03c8a31e
commit a988017110
23 changed files with 438 additions and 321 deletions

26
Makefile Normal file
View file

@ -0,0 +1,26 @@
CC = gcc
CFLAGS = -Wall -Wextra -Iinclude -std=c11
LDFLAGS = -lncurses
SRC = $(wildcard src/*.c)
OBJ = $(patsubst src/%.c, build/%.o, $(SRC))
TARGET = build/space_shooter
all: $(TARGET)
build:
mkdir -p build
build/%.o: src/%.c | build
$(CC) $(CFLAGS) -c $< -o $@
$(TARGET): $(OBJ) | build
$(CC) $(OBJ) -o $@ $(LDFLAGS)
clean:
rm -rf build
run: all
./$(TARGET)
.PHONY: all clean run

View file

@ -4,4 +4,4 @@
> cd space-shooter/
> gcc -o /build/shooter src/main.c src/gui.c -lncurses
> make

BIN
build/game.o Normal file

Binary file not shown.

BIN
build/gui.o Normal file

Binary file not shown.

BIN
build/input.o Normal file

Binary file not shown.

BIN
build/logic.o Normal file

Binary file not shown.

BIN
build/main.o Normal file

Binary file not shown.

BIN
build/objects.o Normal file

Binary file not shown.

BIN
build/space_shooter Executable file

Binary file not shown.

BIN
build/utils.o Normal file

Binary file not shown.

9
include/game.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef GAME_H
#define GAME_H
#include "objects.h"
/* Run the main game. Returns 0 on clean exit. */
int game_run(void);
#endif /* GAME_H */

View file

@ -2,23 +2,26 @@
#define GUI_H
#include <ncurses.h>
#include <stdio.h>
#include <string.h>
#include "objects.h"
#include "utils.h"
// Aspect constatnts
#define TEXTLINES 4
#define BORDERLINES 2
/* color pair ids for gui.c */
enum {
ENEMY_PAIR = 1,
TITLE_PAIR,
GMOVR_PAIR,
BONUS_PAIR,
WINSC_PAIR
};
#define ENEMY_PAIR 1
#define BONUS_PAIR 2
#define TITLE_PAIR 3
#define GMOVR_PAIR 4
#define WINSC_PAIR 5
void init_colors();
/* initialization and screens */
void init_colors(void);
void print_menu(WINDOW* win);
void print_gameover(WINDOW* win);
void print_win_screen(WINDOW* win);
/* status bar (draws into stdscr or another window) */
void print_status_bar(WINDOW* main_win, int y, int x, struct status game_status);
#endif /* GUI_H */
#endif

18
include/input.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef INPUT_H
#define INPUT_H
#include <ncurses.h>
#include "objects.h"
#include "utils.h"
/* Process a single key code and apply to game state.
Returns 1 if game should continue, 0 if quit (player inactive). */
int process_input(int input,
int *pause,
int *auto_shoot,
struct game_obj *player,
struct game_obj bullets[],
int bullet_attivi,
int *next_bullet);
#endif /* INPUT_H */

16
include/logic.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef LOGIC_H
#define LOGIC_H
#include "objects.h"
#include "utils.h"
#include <ncurses.h>
/* Logic functions used by the game loop */
void update_bullets(struct game_obj bullets[]);
void handle_collisions(struct game_obj bullets[], struct game_obj enemies[], struct status *game_status, WINDOW *win);
void spawn_enemies(struct game_obj enemies[], int width);
void update_enemies(struct game_obj enemies[], struct status *game_status, int height, WINDOW *win);
void count_bullets(const struct game_obj bullets[], int *bullet_attivi, int *next_bullet, struct status *game_status);
#endif /* LOGIC_H */

31
include/objects.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef OBJECTS_H
#define OBJECTS_H
#include "utils.h"
/* speed, game_obj, status definitions */
struct speed {
int slowness;
int count;
};
struct game_obj {
char symbol;
int x, y;
int active;
struct speed y_speed;
};
struct status {
int lives;
int score;
int top_score;
int remaining_bullets;
};
/* constructors / initializers */
void init_player(struct game_obj *player, int win_width, int win_height);
void init_bullets(struct game_obj bullets[]);
void init_enemies(struct game_obj enemies[]);
#endif /* OBJECTS_H */

18
include/utils.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef UTILS_H
#define UTILS_H
/* Window/constants shared across modules */
#define DELAY 30000
#define ENEMY_N 5
#define BULLET_N 3
#define LIVES_N 5
#define WIN_PTS 100
#define WIDTH 52
#define HEIGHT 30
#define MIN_COLS 110
#define TEXTLINES 4
#define BORDERLINES 2
#endif /* UTILS_H */

127
src/game.c Normal file
View file

@ -0,0 +1,127 @@
#include "../include/game.h"
#include "../include/gui.h"
#include "../include/input.h"
#include "../include/logic.h"
#include "../include/objects.h"
#include "../include/utils.h"
#include <ncurses.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
/* main game runner */
int game_run(void) {
WINDOW *game_window = NULL;
int height, width, win_startx, win_starty;
char *pause_msg = "PAUSA: premi q per uscire dal gioco...";
int pause = 0;
int auto_shoot = 0;
struct game_obj player = {};
int input = 0;
struct status game_status = {};
game_status.top_score = 0;
game_status.score = 0;
game_status.lives = LIVES_N;
game_status.remaining_bullets = BULLET_N;
struct game_obj bullet[BULLET_N] = {};
int bullet_attivi = 0;
int next_bullet = -1;
struct game_obj enemy[ENEMY_N] = {};
/* seed */
srand((unsigned)time(NULL));
/* Dimensioni finestra di gioco */
height = HEIGHT;
width = WIDTH - (WIDTH%4);
win_startx = (COLS - width)/2;
win_starty = (LINES - height)/2;
if(COLS < MIN_COLS || LINES < HEIGHT) {
printf("Gioco terminato.\nSchermo troppo piccolo\n");
return 0;
}
/* show menu */
print_menu(stdscr);
getch();
werase(stdscr);
mvprintw(1, 1, "p per pausa, q per uscire");
refresh();
/* create windows */
game_window = newwin(height, width, win_starty, win_startx);
nodelay(game_window, TRUE);
keypad(game_window, TRUE);
box(game_window, 0, 0);
wrefresh(game_window);
/* setup objects */
init_player(&player, width, height);
init_bullets(bullet);
init_enemies(enemy);
while (player.active) {
input = wgetch(game_window);
if (!process_input(input, &pause, &auto_shoot, &player, bullet, bullet_attivi, &next_bullet)) {
player.active = 0;
break;
}
if (!pause) update_bullets(bullet);
if (!pause) handle_collisions(bullet, enemy, &game_status, stdscr);
if (!pause && game_status.score >= WIN_PTS) {
player.active = 0;
print_win_screen(stdscr);
break;
}
if (!pause) spawn_enemies(enemy, width);
if (!pause) update_enemies(enemy, &game_status, height, stdscr);
if (!pause && game_status.lives <= 0) {
player.active = 0;
print_gameover(stdscr);
break;
}
count_bullets(bullet, &bullet_attivi, &next_bullet, &game_status);
/* draw */
werase(game_window);
box(game_window, 0, 0);
if (pause) {
wattron(game_window, A_REVERSE);
mvwprintw(game_window, height/2, (width - (int)strlen(pause_msg)) / 2, "%s", pause_msg);
wattroff(game_window, A_REVERSE);
} else {
mvwprintw(game_window, player.y, player.x, "%c", player.symbol);
for(int i = 0; i < BULLET_N; i++)
if (bullet[i].active) mvwprintw(game_window, bullet[i].y, bullet[i].x, "%c", bullet[i].symbol);
wattron(game_window, COLOR_PAIR(ENEMY_PAIR));
for(int i = 0; i < ENEMY_N; i++)
if (enemy[i].active) mvwprintw(game_window, enemy[i].y, enemy[i].x, "%c", enemy[i].symbol);
wattroff(game_window, COLOR_PAIR(ENEMY_PAIR));
print_status_bar(stdscr, win_starty, (win_startx + width), game_status);
}
wrefresh(game_window);
usleep(DELAY);
}
if (game_window) delwin(game_window);
/* note: no endwin(), no curs_set(1) here */
return 0;
}

View file

@ -1,4 +1,5 @@
#include "../include/gui.h"
#include <string.h>
/************** FUNZIONI GUI ***************/
@ -45,76 +46,78 @@ void init_colors() {
}
void print_menu(WINDOW* win) {
// Ottieni parametri della finestra
int height;
int width;
getmaxyx(win, height, width);
// Scritta colorata
wattron(win, COLOR_PAIR(TITLE_PAIR));
for(int i = 0; i < (TEXTLINES+BORDERLINES); i++) {
mvwprintw(win, i+1, (width-(strlen(title[0])))/2, "%s", title[i]);
}
wattroff(win, COLOR_PAIR(TITLE_PAIR));
// Scritta che blinka
wattron(win, A_BLINK);
mvwprintw(win, (TEXTLINES+BORDERLINES)+1, (width-(strlen(menu_desc)))/2, "%s", menu_desc);
wattroff(win, A_BLINK);
// stampa a video
refresh();
wrefresh(win);
return;
}
void print_gameover(WINDOW* win) {
// Parametri della finestra e pulisci tutto
int height;
int width;
int ch;
wclear(win);
getmaxyx(win, height, width);
// Stampa della scritta colorata
wattron(win, COLOR_PAIR(GMOVR_PAIR));
for(int i = 0; i < TEXTLINES; i++) {
mvwprintw(win, i+1, (width-(strlen(gameover[0])))/2, "%s", gameover[i]);
}
wattroff(win, COLOR_PAIR(GMOVR_PAIR));
// Stampa della scritta che blinka
wattron(win, A_BLINK);
mvwprintw(win, TEXTLINES+1, (width-(strlen(gameover_desc)))/2, "%s", gameover_desc);
wattroff(win, A_BLINK);
// Stampa a video e attendi input
refresh();
wrefresh(win);
while((ch = wgetch(win)) != 'q');
return;
}
void print_win_screen(WINDOW* win) {
// Parametri della finestra e pulisci tutto
int height;
int width;
int ch;
wclear(win);
getmaxyx(win, height, width);
// Stampa della scritta colorata
wattron(win, COLOR_PAIR(WINSC_PAIR));
for(int i = 0; i < TEXTLINES; i++) {
mvwprintw(win, i+1, (width-(strlen(win_screen[0])))/2, "%s", win_screen[i]);
}
wattroff(win, COLOR_PAIR(WINSC_PAIR));
// Stampa della scritta che blinka
wattron(win, A_BLINK);
mvwprintw(win, TEXTLINES+1, (width-(strlen(win_desc)))/2, "%s", win_desc);
wattroff(win, A_BLINK);
// Stampa a video e attendi input
refresh();
wrefresh(win);
while((ch = wgetch(win)) != 'q');
return;
}
void print_status_bar(WINDOW* main_win, int y, int x, struct status game_status) {
int status_x = x + 1;
int status_y = y;
status_x++;
mvwprintw(main_win, status_y, status_x, "LIVES:\t%d", game_status.lives);
mvwprintw(main_win, status_y+1, status_x, "SCORE:\t%d", game_status.score);
mvwprintw(main_win, status_y+2, status_x, "TOP SCORE:\t%d", game_status.top_score);
mvwprintw(main_win, status_y+3, status_x, "BULLETS:\t%d", game_status.remaining_bullets);
wrefresh(main_win);
return;
}

41
src/input.c Normal file
View file

@ -0,0 +1,41 @@
#include "../include/input.h"
#include <ncurses.h>
/* Returns 1 to continue, 0 if quit requested (player inactive). */
int process_input(int input,
int *pause,
int *auto_shoot,
struct game_obj *player,
struct game_obj bullets[],
int bullet_attivi,
int *next_bullet) {
if (input == KEY_RIGHT && !(*pause) && player->x < (WIDTH - 2)) {
player->x += 2;
} else if (input == KEY_LEFT && !(*pause) && player->x > 2) {
player->x -= 2;
} else if (!(*auto_shoot) && input == ' ' && bullet_attivi < BULLET_N && !(*pause)) {
if (*next_bullet != -1) {
bullets[*next_bullet].x = player->x;
bullets[*next_bullet].y = player->y - 1;
bullets[*next_bullet].active = 1;
}
} else if (input == 'p') {
*pause = !(*pause);
} else if (input == 'q') {
/* signal to caller to quit by marking player inactive */
return 0;
} else if (input == 'g') {
*auto_shoot = !(*auto_shoot);
}
if (*auto_shoot && bullet_attivi < BULLET_N && !(*pause)) {
if (*next_bullet != -1) {
bullets[*next_bullet].x = player->x;
bullets[*next_bullet].y = player->y - 1;
bullets[*next_bullet].active = 1;
}
}
return 1;
}

73
src/logic.c Normal file
View file

@ -0,0 +1,73 @@
#include "../include/logic.h"
#include <stdlib.h>
void update_bullets(struct game_obj bullets[]) {
for(int i = 0; i< BULLET_N; i++) {
if (bullets[i].active) {
bullets[i].y--;
if (bullets[i].y < 1) {
bullets[i].active = 0;
}
}
}
}
void handle_collisions(struct game_obj bullets[], struct game_obj enemies[], struct status *game_status, WINDOW *win) {
(void)win; /* kept parameter for parity with original code */
for(int j = 0; j < BULLET_N; j++) {
if(bullets[j].active) {
for(int i = 0; i < ENEMY_N; i++) {
if(enemies[i].active && bullets[j].x == enemies[i].x && bullets[j].y == enemies[i].y) {
enemies[i].active = 0;
bullets[j].active = 0;
game_status->score++;
}
}
}
}
}
void spawn_enemies(struct game_obj enemies[], int width) {
for(int i = 0; i < ENEMY_N; i++) {
if(!enemies[i].active) {
enemies[i].y = 2;
enemies[i].x = (rand()%((width-4)/2))*2 + 2;
enemies[i].active = 1;
enemies[i].y_speed.count = 0;
break;
}
}
}
void update_enemies(struct game_obj enemies[], struct status *game_status, int height, WINDOW *win) {
(void)win;
for(int i = 0; i < ENEMY_N; i++) {
if(enemies[i].active) {
enemies[i].y_speed.count++;
if(enemies[i].y_speed.count > enemies[i].y_speed.slowness) {
enemies[i].y++;
enemies[i].y_speed.count = 0;
}
if(enemies[i].y > height - 2) {
enemies[i].active = 0;
game_status->lives--;
}
}
}
}
/* Recomputes bullet_attivi and next_bullet, and updates remaining_bullets */
void count_bullets(const struct game_obj bullets[], int *bullet_attivi, int *next_bullet, struct status *game_status) {
*bullet_attivi = 0;
*next_bullet = -1;
for (int i = 0; i < BULLET_N; i++) {
if (bullets[i].active) {
(*bullet_attivi)++;
} else {
if (*next_bullet == -1 || i < *next_bullet) {
*next_bullet = i;
}
}
}
game_status->remaining_bullets = BULLET_N - (*bullet_attivi);
}

View file

@ -1,308 +1,25 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ncurses.h>
#include <time.h>
#include <curses.h>
#include <menu.h>
#include "../include/game.h"
#include "../include/gui.h"
/************* STATIC DEFINITIONS ************/
// Game constants
#define ENEMY_N 5
#define BULLET_N 3
#define LIVES_N 5
#define WIN_PTS 100
// Window constants
#define DELAY 30000
#define WIDTH 52
#define HEIGHT 30
#define MIN_COLS 110
/************* GAME STRUCTURES **************/
struct speed {
int slowness;
int count;
};
struct game_obj {
char symbol;
int x, y;
int active;
struct speed y_speed;
};
struct status {
int lives;
int score;
int top_score;
int remaining_bullets;
};
void print_status_bar(WINDOW* main_win, int y, int x, struct status game_status) {
int status_x = x + 1;
int status_y = y;
status_x++;
// lives
mvwprintw(main_win, status_y, status_x, "LIVES:\t%d", game_status.lives);
// score
mvwprintw(main_win, status_y+1, status_x, "SCORE:\t%d", game_status.score);
// top score
mvwprintw(main_win, status_y+2, status_x, "TOP SCORE:\t%d", game_status.top_score);
// bullets
mvwprintw(main_win, status_y+3, status_x, "BULLETS:\t%d", game_status.remaining_bullets);
// refresh window
wrefresh(main_win);
return;
}
int main() {
/************ VARIABILI **************/
WINDOW *game_window;
int height, width, win_startx, win_starty;
char *pause_msg = "PAUSA: premi q per uscire dal gioco...";
int pause = 0;
int auto_shoot = 0;
// Giocatore
struct game_obj player = {};
int input;
// Status
struct status game_status = {};
game_status.top_score = 0;
game_status.score = 0;
game_status.lives = LIVES_N;
game_status.remaining_bullets = BULLET_N;
// Proiettile
struct game_obj bullet[BULLET_N] = {};
int bullet_attivi = 0;
int next_bullet = 0;
// Nemici
struct game_obj enemy[ENEMY_N] = {};
/**************** INIZIALIZZAZIONE ***************/
int main(void) {
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
curs_set(0);
srand(time(NULL));
// Colori
init_colors();
// Dimensioni finestra di gioco e controlli
// calcolo delle misure della finestra
height = HEIGHT;
width = WIDTH - (WIDTH%4);
win_startx = (COLS - width)/2;
win_starty = (LINES - height)/2;
game_run();
// Schermo troppo piccolo ?
if(COLS < MIN_COLS || LINES < HEIGHT) {
curs_set(1);
endwin();
printf("Gioco terminato.\n");
printf("Schermo troppo piccolo \n");
return 0;
}
// MENU
print_menu(stdscr);
getch();
werase(stdscr);
mvprintw(1, 1, "p per pausa, q per uscire");
refresh();
// GAME WINDOW
game_window = newwin(height, width, win_starty, win_startx);
nodelay(game_window, TRUE); // input non bloccante
keypad(game_window, TRUE);
box(game_window, 0, 0);
wrefresh(game_window);
// setup giocatore
player.symbol = 'A';
player.x = width / 2;
player.y = height - 2;
player.active = 1;
// setup proiettili
for(int i = 0; i < BULLET_N; i++) {
bullet[i].symbol = '*';
bullet[i].active = 0;
}
// setup dei nemici
for(int i = 0; i < ENEMY_N; i++) {
enemy[i].symbol = 'Q';
enemy[i].active = 0;
enemy[i].y_speed.slowness = 7;
enemy[i].y_speed.count = 0;
}
//********************* GAME LOOP ***********************/
while (player.active) {
// TODO if pausa all'inizio e continue
input = wgetch(game_window);
// Gestione input
if (input == KEY_RIGHT && !pause && player.x < width - 2) {
player.x += 2;
} else if (input == KEY_LEFT && !pause && player.x > 2) {
player.x -= 2;
} else if (!auto_shoot && input == ' ' && bullet_attivi < BULLET_N && !pause) {
bullet[next_bullet].x = player.x;
bullet[next_bullet].y = player.y - 1;
bullet[next_bullet].active = 1;
} else if (input == 'p') {
pause = !pause;
} else if (input == 'q') {
player.active = 0;
} else if (input == 'g') {
auto_shoot= !auto_shoot;
}
if (auto_shoot && bullet_attivi < BULLET_N && !pause) {
bullet[next_bullet].x = player.x;
bullet[next_bullet].y = player.y - 1;
bullet[next_bullet].active = 1;
}
// Aggiorna proiettile
for(int i = 0; i< BULLET_N; i++) {
if (bullet[i].active && !pause) {
bullet[i].y--;
}
if (bullet[i].y < 1) {
bullet[i].active = 0;
}
}
// Collisioni 1
for(int j = 0; j < BULLET_N; j++) {
if(bullet[j].active && !pause) {
for(int i = 0; i < ENEMY_N; i++) {
if(bullet[j].x == enemy[i].x && bullet[j].y == enemy[i].y) {
enemy[i].active = 0;
bullet[j].active = 0;
game_status.score++;
if(game_status.score == WIN_PTS) {
player.active = 0;
print_win_screen(stdscr);
}
}
}
}
}
// Spawna nemici
for(int i = 0; !pause && i < ENEMY_N; i++) {
if(!enemy[i].active && !pause) {
enemy[i].y = 2;
enemy[i].x = (rand()%((width-4)/2))*2 + 2;
enemy[i].active = 1;
break;
}
}
// Aggiorna posizione nemici
for(int i = 0; !pause && i < ENEMY_N; i++) {
if(enemy[i].active && !pause) {
enemy[i].y_speed.count++;
if(enemy[i].y_speed.count > enemy[i].y_speed.slowness) {
enemy[i].y++;
enemy[i].y_speed.count = 0;
}
if(enemy[i].y > height - 2) {
enemy[i].active = 0;
game_status.lives--;
if(game_status.lives == 0) {
player.active = 0;
print_gameover(stdscr);
}
}
}
}
// Collisioni 2
for(int j = 0; j < BULLET_N; j++) {
if(bullet[j].active && !pause) {
for(int i = 0; i < ENEMY_N; i++) {
if(bullet[j].x == enemy[i].x && bullet[j].y == enemy[i].y) {
enemy[i].active = 0;
bullet[j].active = 0;
game_status.score++;
if(game_status.score == WIN_PTS) {
player.active = 0;
print_win_screen(stdscr);
}
}
}
}
}
// Conteggio proiettili
bullet_attivi = 0;
next_bullet = -1;
for (int i = 0; i < BULLET_N; i++) {
if (bullet[i].active) {
bullet_attivi++;
} else if (i < next_bullet || next_bullet == -1) {
next_bullet = i;
}
}
game_status.remaining_bullets = BULLET_N - bullet_attivi;
// Disegna scena
werase(game_window);
box(game_window, 0, 0);
if (pause) {
wattron(game_window, A_REVERSE);
mvwprintw(game_window, height/2, (width - strlen(pause_msg)) / 2, "%s", pause_msg);
wattroff(game_window, A_REVERSE);
} else {
// Giocatore
mvwprintw(game_window, player.y, player.x, "%c", player.symbol);
// Proiettile
for(int i = 0; i < BULLET_N; i++) {
if (bullet[i].active) mvwprintw(game_window, bullet[i].y, bullet[i].x, "%c", bullet[i].symbol);
}
// Nemici
for(int i = 0; i < ENEMY_N; i++) {
wattron(game_window, COLOR_PAIR(1));
if (enemy[i].active) mvwprintw(game_window, enemy[i].y, enemy[i].x, "%c", enemy[i].symbol);
wattroff(game_window, COLOR_PAIR(1));
}
// Status Bar
print_status_bar(stdscr, win_starty, (win_startx + width), game_status);
}
wrefresh(game_window);
usleep(DELAY);
}
/***************** CHIUSURA *****************/
delwin(game_window);
/* cleanup terminal */
curs_set(1);
endwin();
printf("Gioco terminato.\n");
return 0;
}

33
src/objects.c Normal file
View file

@ -0,0 +1,33 @@
#include "objects.h"
#include <stdlib.h>
void init_player(struct game_obj *player, int win_width, int win_height) {
player->symbol = 'A';
player->x = win_width / 2;
player->y = win_height - 2;
player->active = 1;
player->y_speed.slowness = 0;
player->y_speed.count = 0;
}
void init_bullets(struct game_obj bullets[]) {
for (int i = 0; i < BULLET_N; i++) {
bullets[i].symbol = '*';
bullets[i].active = 0;
bullets[i].x = 0;
bullets[i].y = 0;
bullets[i].y_speed.slowness = 0;
bullets[i].y_speed.count = 0;
}
}
void init_enemies(struct game_obj enemies[]) {
for (int i = 0; i < ENEMY_N; i++) {
enemies[i].symbol = 'Q';
enemies[i].active = 0;
enemies[i].y_speed.slowness = 7;
enemies[i].y_speed.count = 0;
enemies[i].x = 0;
enemies[i].y = 0;
}
}

2
src/utils.c Normal file
View file

@ -0,0 +1,2 @@
#include "utils.h"
/* currently no utility functions needed; file exists for future helpers */