Notes and Talks/UJEP/UPC/09
MakefileCC = cc
CFLAGS = -std=c11 -Wall -Wextra
LDLIBS = -lncurses

.PHONY: all run clean

all: hello1 hello2 snakes

hello1: hello1.o
	$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)

hello2: hello2.o
	$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)

snakes: snakes.o
	$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)

%.o: %.c %.h
	$(CC) $(CFLAGS) -c -o $@ $<

run: snakes
	./snakes

clean:
	rm -f hello1 hello2 snakes *.o a.out
hello1.c// apt install libncurses-dev
#include <ncurses.h>
#include <string.h>

typedef unsigned int uint;

#define FOR(i, n) for (uint i = 0; i < (n); ++i)

#define SZ(arr) (sizeof(arr) / sizeof(arr[0]))
#define FOR_SZ(i, arr) FOR(i, SZ(arr))

void hello() {
    initscr(); // initialize ncurses mode

    int rows, cols;
    getmaxyx(stdscr, rows, cols); // get terminal size

    char message[] = "Hello, World!";

    // center the message
    int x = (cols - (int) strlen(message)) / 2;
    int y = rows / 2;

    mvprintw(y, x, "Hello, World!");

    refresh(); // update the screen

    getch(); // wait for a key press

    endwin(); // restore terminal
}

int main() {
    hello();
    return 0;
}
hello2.c#include <ncurses.h>
#include <string.h>

typedef unsigned int uint;

#define FOR(i, n) for (uint i = 0; i < (n); ++i)

#define SZ(arr) (sizeof(arr) / sizeof(arr[0]))
#define FOR_SZ(i, arr) FOR(i, SZ(arr))

void print_ctr(char* msg, int row_offset) {
    int rows, cols;
    getmaxyx(stdscr, rows, cols);

    int x = (cols - (int) strlen(msg)) / 2;
    int y = rows / 2 + row_offset;

    mvprintw(y, x, "%s", msg);
}

void draw() {
    char message[] = "Hello, World!";
    erase(); // clear the screen

    print_ctr(message, 0);
    print_ctr("Press 'q' to quit.", 1);

    refresh(); // update the screen
}

void hello() {
    initscr();

    cbreak();             // disable line buffering
    noecho();             // don't echo key presses
    keypad(stdscr, TRUE); // enable special keys (like arrow keys)

    draw();

    for (;;) {
        int key = getch();

        if (key == 'q' || key == 'Q') {
            break;
        }

        if (key == KEY_RESIZE) {
            draw();
        }
    }

    endwin();
}

int main() {
    hello();
    return 0;
}
snakes.c#include <ncurses.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>

typedef enum { UP, DOWN, LEFT, RIGHT } Dir;

// color pairs
#define CP_HEAD 1
#define CP_FOOD 2

typedef struct {
    int y, x;
} Pos;

#define MAX_LEN 512
typedef struct {
    Pos body[MAX_LEN];
    int len;
    Dir dir;
} Snake;

typedef struct {
    Snake snake;
    Pos food;
    int rows, cols;
    int score;
} Game;

// == helpers ==

static Pos rand_pos(Game* g) {
    return (Pos) {1 + rand() % (g->rows - 2), 1 + rand() % (g->cols - 2)};
}

static inline bool pos_equal(Pos a, Pos b) {
    return a.y == b.y && a.x == b.x;
}

static inline int min(int a, int b) {
    return a < b ? a : b;
}

static bool on_snake(Snake* s, Pos p) {
    Pos* body = s->body;
    int len = s->len;

    for (int i = 0; i < len; ++i) {
        if (pos_equal(p, body[i])) {
            return true;
        }
    }

    return false;
}

static void place_food(Game* g) {
    Pos p;
    do {
        p = rand_pos(g);
    } while (on_snake(&g->snake, p));
    g->food = p;
}

static void init_game(Game* g) {
    getmaxyx(stdscr, g->rows, g->cols);

    Snake* s = &g->snake;
    s->len = 3;
    s->dir = RIGHT;
    Pos* body = s->body;

    for (int i = 0; i < s->len; ++i) {
        body[i].y = g->rows / 2;
        body[i].x = g->cols / 2 - i;
    }

    g->score = 0;
    place_food(g);
}

// == drawing ==

static void draw(Game* g) {
    erase();
    box(stdscr, 0, 0);
    mvprintw(0, 2, " Score: %d  (q = quit) ", g->score);

    attron(COLOR_PAIR(CP_FOOD));
    mvaddch(g->food.y, g->food.x, '*');
    attroff(COLOR_PAIR(CP_FOOD));

    Snake* s = &g->snake;
    Pos* body = s->body;

    attron(COLOR_PAIR(CP_HEAD));
    mvaddch(body[0].y, body[0].x, 'O');
    attroff(COLOR_PAIR(CP_HEAD));

    for (int i = 1; i < s->len; i++)
        mvaddch(body[i].y, body[i].x, 'o');

    refresh();
}

// == game logic ==

// Returns true on success, false on collision (game over)
static bool step(Game* g) {
    Snake* s = &g->snake;
    Pos* body = s->body;
    Pos next = body[0];

    switch (s->dir) {
    case UP:
        next.y--;
        break;
    case DOWN:
        next.y++;
        break;
    case LEFT:
        next.x--;
        break;
    case RIGHT:
        next.x++;
        break;
    }

    // wall collision
    if (next.y <= 0 || next.y >= g->rows - 1 || next.x <= 0 ||
        next.x >= g->cols - 1)
        return false;

    // self collision (skip tail – it will move away)
    for (int i = 0; i < s->len - 1; i++) {
        if (pos_equal(next, body[i]))
            return false;
    }

    int ate = pos_equal(next, g->food);
    int new_len = min(g->snake.len + ate, MAX_LEN);

    // shift body backwards, insert new head
    for (int i = new_len - 1; i > 0; i--)
        body[i] = body[i - 1];
    s->body[0] = next;
    s->len = new_len;

    if (ate) {
        g->score++;
        place_food(g);
    }
    return true;
}

static void show_game_over(Game* g) {
    int y = g->rows / 2;
    int x = g->cols / 2 - 12;
    if (x < 1)
        x = 1;

    mvprintw(y, x, "  GAME OVER!  Score: %d  ", g->score);
    mvprintw(y + 1, x, "  Press any key to exit.  ");
    refresh();

    timeout(-1); // wait for key press
    getch();
}

// == entry point ==

void game(void) {
    srand((unsigned) time(NULL));

    initscr();
    start_color();
    init_pair(CP_HEAD, COLOR_RED, COLOR_BLACK);
    init_pair(CP_FOOD, COLOR_GREEN, COLOR_BLACK);
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    curs_set(0);  // hide cursor
    timeout(150); // getch() returns ERR after delay (ms) → drives the game tick

    Game g;
    init_game(&g);

    draw(&g);

    Dir* dir = &g.snake.dir;

    for (;;) {
        int key = getch(); // ERR on timeout, key code otherwise

        if ('q' == key || 'Q' == key)
            break;

        if (key == KEY_RESIZE)
            init_game(&g);

        // change direction; disallow reversals
        switch (key) {
        case KEY_UP:
            if (*dir != DOWN)
                *dir = UP;
            break;
        case KEY_DOWN:
            if (*dir != UP)
                *dir = DOWN;
            break;
        case KEY_LEFT:
            if (*dir != RIGHT)
                *dir = LEFT;
            break;
        case KEY_RIGHT:
            if (*dir != LEFT)
                *dir = RIGHT;
            break;
        }

        bool res = step(&g);
        draw(&g);
        if (!res) {
            show_game_over(&g);
            break;
        }
    }

    endwin();
}

int main(void) {
    game();
    return 0;
}