juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

hal_sim.c (7460B)


      1 /* hal_sim.c - simulador de la PicoCalc en la PC (SDL3). El cable se simula con TCP.
      2  *
      3  *   <juego>-picosim [--scale N] [--link-listen PUERTO | --link-connect PUERTO] [--store ARCHIVO]
      4  *
      5  * Para probar el cable: una ventana con --link-listen 9999 y otra con --link-connect 9999. */
      6 #include <SDL3/SDL.h>
      7 #include <arpa/inet.h>
      8 #include <errno.h>
      9 #include <fcntl.h>
     10 #include <netinet/in.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <sys/socket.h>
     15 #include <time.h>
     16 #include <unistd.h>
     17 #include "hal.h"
     18 #include "app_config.h"
     19 
     20 static SDL_Window *win;
     21 static SDL_Renderer *ren;
     22 static SDL_Texture *tex;
     23 static uint16_t fb[320 * 320];
     24 static int scale = 2, link_port = 0, lfd = -1, cfd = -1;
     25 static bool link_listen;
     26 static char store_path[512];
     27 static const char *keys;
     28 
     29 static void span(Surface *s, int x, int y, int n, Color c)
     30 {
     31     (void)s;
     32     uint16_t *p = fb + y * 320 + x;
     33     while (n--) *p++ = c;
     34 }
     35 
     36 void hal_init(void) {}
     37 
     38 uint32_t hal_ms(void)
     39 {
     40     struct timespec ts;
     41     clock_gettime(CLOCK_MONOTONIC, &ts);
     42     return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
     43 }
     44 
     45 void hal_sleep(int ms) { SDL_Delay((Uint32)ms); }
     46 
     47 uint32_t hal_random(void)
     48 {
     49     struct timespec ts;
     50     clock_gettime(CLOCK_REALTIME, &ts);
     51     return (uint32_t)(ts.tv_nsec ^ ts.tv_sec ^ (getpid() << 16));
     52 }
     53 
     54 int hal_battery(void) { return -1; }
     55 void hal_log(const char *line) { fprintf(stderr, "%s\n", line); }
     56 void hal_log_clear(void) {}
     57 
     58 void hal_draw(void (*paint)(Surface *, void *), void *ctx)
     59 {
     60     Surface s;
     61     surf_init(&s, 320, 320, span, 0);
     62     /* igual que en la PicoCalc: por franjas, para probar el recorte */
     63     for (int y = 0; y < 320; y += 40) {
     64         gfx_band(&s, y, y + 40);
     65         paint(&s, ctx);
     66     }
     67     const char *shot = getenv("APP_SHOT");
     68     if (shot) {
     69         FILE *f = fopen(shot, "wb");
     70         if (f) {
     71             fprintf(f, "P6 320 320 255\n");
     72             for (int i = 0; i < 320 * 320; i++) {
     73                 fputc((fb[i] >> 11) << 3, f); fputc(((fb[i] >> 5) & 63) << 2, f); fputc((fb[i] & 31) << 3, f);
     74             }
     75             fclose(f);
     76         }
     77     }
     78     SDL_UpdateTexture(tex, NULL, fb, 640);
     79     SDL_RenderClear(ren);
     80     SDL_RenderTexture(ren, tex, NULL, NULL);
     81     SDL_RenderPresent(ren);
     82 }
     83 
     84 static int map_key(SDL_Keycode k)
     85 {
     86     switch (k) {
     87     case SDLK_UP: return K_UP;
     88     case SDLK_DOWN: return K_DOWN;
     89     case SDLK_LEFT: return K_LEFT;
     90     case SDLK_RIGHT: return K_RIGHT;
     91     case SDLK_RETURN: case SDLK_KP_ENTER: return K_OK;
     92     case SDLK_ESCAPE: return K_BACK;
     93     case SDLK_TAB: return K_TAB;
     94     case SDLK_BACKSPACE: return K_BKSP;
     95     }
     96     return 0;
     97 }
     98 
     99 int hal_key(void)
    100 {
    101     SDL_Event e;
    102     while (SDL_PollEvent(&e)) {
    103         if (e.type == SDL_EVENT_QUIT) exit(0);
    104         if (e.type == SDL_EVENT_KEY_DOWN) { int k = map_key(e.key.key); if (k) return k; }
    105         if (e.type == SDL_EVENT_TEXT_INPUT) { const char *t = e.text.text; int c = utf8_next(&t); if (c) return c; }
    106         if (e.type == SDL_EVENT_WINDOW_EXPOSED) return 0;   /* 0: solo fuerza redibujo */
    107     }
    108     if (keys && *keys) {           /* APP_KEYS: igual que en guikit.c */
    109         int c = (unsigned char)*keys++;
    110         switch (c) {
    111         case '~': return K_OK;
    112         case '<': return K_BACK;
    113         case '>': return K_TAB;
    114         case '^': return K_UP;
    115         case 'v': return K_DOWN;
    116         case '{': return K_LEFT;
    117         case '}': return K_RIGHT;
    118         case '.': SDL_Delay(300); return -1;
    119         case '!': exit(0);
    120         }
    121         return c;
    122     }
    123     return -1;
    124 }
    125 
    126 /* ------------------------------------------------------------ cable por TCP */
    127 
    128 static void link_setup(void)
    129 {
    130     if (!link_port) return;
    131     if (link_listen) {
    132         lfd = socket(AF_INET, SOCK_STREAM, 0);
    133         int one = 1;
    134         setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
    135         struct sockaddr_in a = { .sin_family = AF_INET, .sin_port = htons((uint16_t)link_port) };
    136         a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    137         if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0 || listen(lfd, 1) < 0) { perror("link"); exit(1); }
    138         fcntl(lfd, F_SETFL, O_NONBLOCK);
    139     }
    140 }
    141 
    142 static void link_connect(void)
    143 {
    144     if (cfd >= 0 || !link_port) return;
    145     if (link_listen) {
    146         cfd = accept(lfd, 0, 0);
    147     } else {
    148         static uint32_t last;
    149         if (hal_ms() - last < 1000) return;
    150         last = hal_ms();
    151         cfd = socket(AF_INET, SOCK_STREAM, 0);
    152         struct sockaddr_in a = { .sin_family = AF_INET, .sin_port = htons((uint16_t)link_port) };
    153         a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    154         if (connect(cfd, (struct sockaddr *)&a, sizeof a) < 0) { close(cfd); cfd = -1; return; }
    155     }
    156     if (cfd >= 0) fcntl(cfd, F_SETFL, O_NONBLOCK);
    157 }
    158 
    159 void hal_link_write(const char *buf, int n)
    160 {
    161     link_connect();
    162     if (cfd < 0) return;    /* como un cable desenchufado: se pierde */
    163     while (n > 0) {
    164         ssize_t w = send(cfd, buf, (size_t)n, MSG_NOSIGNAL);
    165         if (w < 0 && errno == EAGAIN) { SDL_Delay(1); continue; }
    166         if (w <= 0) { close(cfd); cfd = -1; return; }
    167         buf += w;
    168         n -= (int)w;
    169     }
    170 }
    171 
    172 int hal_link_read(char *buf, int max)
    173 {
    174     link_connect();
    175     if (cfd < 0 || max <= 0) return 0;
    176     ssize_t r = recv(cfd, buf, (size_t)max, 0);
    177     if (r == 0 || (r < 0 && errno != EAGAIN)) { close(cfd); cfd = -1; return 0; }
    178     return r < 0 ? 0 : (int)r;
    179 }
    180 
    181 /* ------------------------------------------------------------ guardado */
    182 
    183 bool hal_store_save(const char *data, int n)
    184 {
    185     FILE *f = fopen(store_path, "wb");
    186     if (!f) return false;
    187     fwrite(data, 1, (size_t)n, f);
    188     fclose(f);
    189     return true;
    190 }
    191 
    192 int hal_store_load(char *buf, int max)
    193 {
    194     FILE *f = fopen(store_path, "rb");
    195     if (!f) return 0;
    196     int n = (int)fread(buf, 1, (size_t)max, f);
    197     fclose(f);
    198     return n;
    199 }
    200 
    201 int main(int argc, char **argv)
    202 {
    203     const char *home = getenv("HOME");
    204     snprintf(store_path, sizeof store_path, "%s/.local/share/" APP_NAME "/picosim.sav", home ? home : ".");
    205     for (int i = 1; i < argc; i++) {
    206         if (!strcmp(argv[i], "--scale") && i + 1 < argc) scale = atoi(argv[++i]);
    207         else if (!strcmp(argv[i], "--link-listen") && i + 1 < argc) { link_port = atoi(argv[++i]); link_listen = true; }
    208         else if (!strcmp(argv[i], "--link-connect") && i + 1 < argc) link_port = atoi(argv[++i]);
    209         else if (!strcmp(argv[i], "--store") && i + 1 < argc) snprintf(store_path, sizeof store_path, "%s", argv[++i]);
    210         else if (!strcmp(argv[i], "--lang") && i + 1 < argc) g_lang = !strcmp(argv[++i], "en");
    211         else { fprintf(stderr, "uso: %s [--scale N] [--link-listen P | --link-connect P] [--store F] [--lang es|en]\n", argv[0]); return 1; }
    212     }
    213     if (scale < 1) scale = 1;
    214     keys = getenv("APP_KEYS");
    215     if (!SDL_Init(SDL_INIT_VIDEO)) { fprintf(stderr, "SDL: %s\n", SDL_GetError()); return 1; }
    216     char title[64];
    217     snprintf(title, sizeof title, "PicoCalc " APP_TITLE "%s", link_port ? (link_listen ? " (A)" : " (B)") : "");
    218     if (!SDL_CreateWindowAndRenderer(title, 320 * scale, 320 * scale, SDL_WINDOW_RESIZABLE, &win, &ren)) return 1;
    219     SDL_SetRenderLogicalPresentation(ren, 320, 320, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
    220     tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, 320, 320);
    221     SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_NEAREST);
    222     SDL_StartTextInput(win);
    223     link_setup();
    224     app_main();
    225     return 0;
    226 }