aboutsummaryrefslogtreecommitdiffstats
path: root/src/render.c
blob: 005fd71c16392c17e0efde904bfcef29535228ba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include "render.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>

#define UPPER "\xe2\x96\x80"   // ▀ U+2580

static struct termios orig_termios;
static bool raw_active = false;

void term_restore(void) {
    if (raw_active) {
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
        raw_active = false;
    }
    // show cursor, leave alt screen, reset colors
    printf("\x1b[0m\x1b[?25h\x1b[?1049l");
    fflush(stdout);
}

void term_init(void) {
    tcgetattr(STDIN_FILENO, &orig_termios);
    struct termios raw = orig_termios;
    raw.c_lflag &= ~(ECHO | ICANON | ISIG);
    raw.c_iflag &= ~(IXON | ICRNL);
    raw.c_cc[VMIN] = 0;
    raw.c_cc[VTIME] = 0;
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
    raw_active = true;
    // enter alt screen, hide cursor, clear
    printf("\x1b[?1049h\x1b[?25l\x1b[2J\x1b[H");
    fflush(stdout);
    atexit(term_restore);
}

// large reusable output buffer
static char outbuf[SCREEN_W * (SCREEN_H/2) * 40 + 4096];

void render_frame(GB *gb) {
    PPU *p = &gb->ppu;
    char *o = outbuf;
    o += sprintf(o, "\x1b[H");
    for (int y = 0; y < SCREEN_H; y += 2) {
        int pr = -1, pg = -1, pb = -1;   // prev fg
        int PR = -1, PG = -1, PB = -1;   // prev bg
        for (int x = 0; x < SCREEN_W; x++) {
            u8 *top = p->fb[y][x];
            u8 *bot = p->fb[y+1][x];
            if (top[0] != pr || top[1] != pg || top[2] != pb) {
                o += sprintf(o, "\x1b[38;2;%d;%d;%dm", top[0], top[1], top[2]);
                pr = top[0]; pg = top[1]; pb = top[2];
            }
            if (bot[0] != PR || bot[1] != PG || bot[2] != PB) {
                o += sprintf(o, "\x1b[48;2;%d;%d;%dm", bot[0], bot[1], bot[2]);
                PR = bot[0]; PG = bot[1]; PB = bot[2];
            }
            memcpy(o, UPPER, 3); o += 3;
        }
        memcpy(o, "\x1b[0m\r\n", 6); o += 6;
    }
    fwrite(outbuf, 1, o - outbuf, stdout);
    fflush(stdout);
}

// ---- input with decay-based "hold" emulation ----
static int hold[8];   // frames remaining for each BTN bit position
#define HOLD_FRAMES 4

static void press(int bit) {
    for (int i = 0; i < 8; i++) if ((1 << i) == bit) hold[i] = HOLD_FRAMES;
}

bool input_poll(GB *gb) {
    unsigned char buf[64];
    int n = read(STDIN_FILENO, buf, sizeof(buf));
    bool quit = false;
    for (int i = 0; i < n; i++) {
        unsigned char ch = buf[i];
        if (ch == 0x1b && i + 2 < n && buf[i+1] == '[') {
            switch (buf[i+2]) {
                case 'A': press(BTN_UP); break;
                case 'B': press(BTN_DOWN); break;
                case 'C': press(BTN_RIGHT); break;
                case 'D': press(BTN_LEFT); break;
            }
            i += 2;
            continue;
        }
        switch (ch) {
            case 'w': press(BTN_UP); break;
            case 's': press(BTN_DOWN); break;
            case 'a': press(BTN_LEFT); break;
            case 'd': press(BTN_RIGHT); break;
            case 'z': case 'j': press(BTN_A); break;
            case 'x': case 'k': press(BTN_B); break;
            case '\r': case '\n': press(BTN_START); break;
            case ' ': press(BTN_SELECT); break;
            case 'q': case 3: quit = true; break;
        }
    }
    // build button state from decay counters
    u8 b = 0;
    for (int i = 0; i < 8; i++) {
        if (hold[i] > 0) { hold[i]--; b |= (1 << i); }
    }
    gb->buttons = b;
    if (b) gb_request_interrupt(gb, INT_JOYPAD);
    return !quit;
}