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

board.c (17047B)


      1 /* board.c (gfx) - dibujo del tablero en pixeles, geometria y deteccion de clicks. */
      2 #include "catan_gfx.h"
      3 
      4 /* colores de Catan; el verde (de la expansion 5-6) en lugar del blanco, que casi no se ve.
      5  * Con 5-6 jugadores se suman el blanco y el marron de la expansion. */
      6 #ifdef CATAN_SMALL
      7 #define PLAYER_COLORS RGB(214, 40, 40), RGB(40, 108, 226), RGB(30, 190, 110), RGB(255, 140, 0)
      8 #else
      9 #define PLAYER_COLORS RGB(214, 40, 40), RGB(40, 108, 226), RGB(30, 190, 110), RGB(255, 140, 0), \
     10                       RGB(236, 236, 236), RGB(140, 84, 44)
     11 #endif
     12 const Color PLAYER_COLOR_DEF[MAXP] = { PLAYER_COLORS };
     13 Color PLAYER_COLOR[MAXP] = { PLAYER_COLORS };
     14 const Color TERRAIN_COLOR[T__COUNT] = {
     15     RGB(190, 92, 48),    /* cerro */
     16     RGB(38, 122, 54),    /* bosque */
     17     RGB(134, 200, 84),   /* pasto */
     18     RGB(232, 192, 64),   /* campo */
     19     RGB(142, 142, 152),  /* montana */
     20     RGB(222, 200, 150),  /* desierto */
     21     RGB(40, 104, 170),   /* mar */
     22     RGB(176, 128, 36),   /* oro: ocre oscuro, que no se confunda con el campo */
     23 };
     24 
     25 static const int8_t CX[6] = { 0, 1, 1, 0, -1, -1 };
     26 static const int8_t CY[6] = { -2, -1, 1, 2, 1, -1 };
     27 static const int PIPS[13] = { 0, 0, 1, 2, 3, 4, 5, 0, 5, 4, 3, 2, 1 };
     28 
     29 /* coordenadas de topologia -> 1/16 px */
     30 static int32_t tx16(const BoardGeom *g, int32_t X) { return g->cx * 16 + 8 + X * g->R * 887 / 64; }
     31 static int32_t ty16(const BoardGeom *g, int32_t Y) { return g->cy * 16 + 8 + Y * g->R * 8; }
     32 
     33 /* El tablero mas los puertos entra en el rectangulo: limites de los vertices (x en
     34  * sqrt3/2 R, y en R/2) mas el margen de los puertos; centrado en su punto medio. Con el
     35  * clasico da lo mismo de siempre (10,4R x 9,6R). */
     36 void geom_fit(BoardGeom *g, const Game *gm, int x, int y, int w, int h)
     37 {
     38     const Topo *t = game_topo(gm);
     39     int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
     40     for (int v = 0; v < t->nvert; v++) {
     41         if (!v || t->vx[v] < x0) x0 = t->vx[v];
     42         if (!v || t->vx[v] > x1) x1 = t->vx[v];
     43         if (!v || t->vy[v] < y0) y0 = t->vy[v];
     44         if (!v || t->vy[v] > y1) y1 = t->vy[v];
     45     }
     46     int r1 = w * 1000 / ((x1 - x0) * 866 + 1740), r2 = h * 1000 / ((y1 - y0) * 500 + 1600);
     47     g->t = t;
     48     g->R = r1 < r2 ? r1 : r2;
     49     if (g->R < 2) g->R = 2;
     50     g->cx = x + w / 2 - (x0 + x1) * g->R * 887 / 64 / 32;
     51     g->cy = y + h / 2 - (y0 + y1) * g->R / 4;
     52 }
     53 
     54 void geom_vert(const BoardGeom *g, int v, int *x, int *y)
     55 {
     56     const Topo *t = g->t;
     57     *x = tx16(g, t->vx[v]) / 16;
     58     *y = ty16(g, t->vy[v]) / 16;
     59 }
     60 
     61 void geom_hex(const BoardGeom *g, int h, int *x, int *y)
     62 {
     63     const Topo *t = g->t;
     64     *x = tx16(g, t->hx[h]) / 16;
     65     *y = ty16(g, t->hy[h]) / 16;
     66 }
     67 
     68 void geom_edge(const BoardGeom *g, int e, int *x, int *y)
     69 {
     70     int x0, y0, x1, y1;
     71     const Topo *t = g->t;
     72     geom_vert(g, t->edge_v[e][0], &x0, &y0);
     73     geom_vert(g, t->edge_v[e][1], &x1, &y1);
     74     *x = (x0 + x1) / 2;
     75     *y = (y0 + y1) / 2;
     76 }
     77 
     78 void geom_port(const BoardGeom *g, int i, int *x, int *y)
     79 {
     80     int mx, my, e = g->t->port_edge[i];
     81     geom_edge(g, e, &mx, &my);
     82     /* el disco va del lado del agua, perpendicular a la arista: con mar, hacia el hexagono
     83      * de agua; sin mar, alejandose del hexagono de tierra (la direccion desde el centro
     84      * del tablero caia a veces sobre un vertice o adentro de la casilla) */
     85     int dx = mx - g->cx, dy = my - g->cy;
     86     for (int s = 0; s < 2; s++) {
     87         int h = g->t->edge_hex[e][s], hx, hy;
     88         if (h < 0) continue;
     89         geom_hex(g, h, &hx, &hy);
     90         if (g->t->region[h] < 0) { dx = hx - mx; dy = hy - my; break; }
     91         dx = mx - hx; dy = my - hy;
     92     }
     93     int len = isqrt(dx * dx + dy * dy);
     94     if (!len) len = 1;
     95     *x = mx + dx * g->R * 55 / 100 / len;
     96     *y = my + dy * g->R * 55 / 100 / len;
     97 }
     98 
     99 /* hexagono de radio r (en % de R) */
    100 static void hex_poly(Surface *s, const BoardGeom *g, int h, int pct, Color c)
    101 {
    102     const Topo *t = g->t;
    103     int32_t xs[6], ys[6], cx = tx16(g, t->hx[h]), cy = ty16(g, t->hy[h]);
    104     for (int k = 0; k < 6; k++) {
    105         xs[k] = cx + (int32_t)CX[k] * g->R * 887 / 64 * pct / 100;
    106         ys[k] = cy + (int32_t)CY[k] * g->R * 8 * pct / 100;
    107     }
    108     gfx_poly16(s, xs, ys, 6, c);
    109 }
    110 
    111 /* poligono relativo a (x,y) en unidades u/8, con contorno */
    112 static void shape(Surface *s, int x, int y, int u, const int8_t *pts, int n, Color fill, Color outline)
    113 {
    114     int32_t xs[12], ys[12];
    115     static const int8_t OX[4] = { -1, 1, 0, 0 }, OY[4] = { 0, 0, -1, 1 };
    116     for (int o = 0; o < 4; o++) {
    117         for (int i = 0; i < n; i++) {
    118             xs[i] = (x + OX[o]) * 16 + 8 + pts[2 * i] * u * 2;
    119             ys[i] = (y + OY[o]) * 16 + 8 + pts[2 * i + 1] * u * 2;
    120         }
    121         gfx_poly16(s, xs, ys, n, outline);
    122     }
    123     for (int i = 0; i < n; i++) {
    124         xs[i] = x * 16 + 8 + pts[2 * i] * u * 2;
    125         ys[i] = y * 16 + 8 + pts[2 * i + 1] * u * 2;
    126     }
    127     gfx_poly16(s, xs, ys, n, fill);
    128 }
    129 
    130 static const int8_t HOUSE[] = { -8, 7, 8, 7, 8, -2, 0, -10, -8, -2 };
    131 static const int8_t CITY[]  = { -12, 8, 12, 8, 12, -3, 2, -3, 2, -8, -5, -14, -12, -8 };
    132 
    133 static void boat(Surface *s, int x, int y, int u, Color hull, Color sail);
    134 
    135 /* ficha suelta para los paneles: 0 pueblo, 1 ciudad, 2 ruta, 3 barco; (x, y) es el centro */
    136 void gfx_piece(Surface *s, int x, int y, int u, int kind, Color fill)
    137 {
    138     if (kind == 3) boat(s, x, y, u * 3 / 4 > 1 ? u * 3 / 4 : 1, fill, C_WHITE);
    139     else if (kind == 0) shape(s, x, y, u, HOUSE, 5, fill, C_OUTLINE);
    140     else if (kind == 1) shape(s, x, y, u, CITY, 7, fill, C_OUTLINE);
    141     else {
    142         gfx_rect(s, x - u - 1, y - 2, 2 * u + 3, 5, C_OUTLINE);
    143         gfx_rect(s, x - u, y - 1, 2 * u + 1, 3, fill);
    144     }
    145 }
    146 static const int8_t PAWN[]  = { -7, 9, 7, 9, 4, 2, 3, -3, -3, -3, -4, 2 };
    147 static const int8_t HULL[]  = { -10, 2, 10, 2, 6, 8, -6, 8 };       /* barco: casco y vela */
    148 static const int8_t SAIL[]  = { -1, -10, -1, 0, 7, 0 };
    149 
    150 /* barco en (x, y): de un jugador sobre una arista, o el pirata (negro) */
    151 static void boat(Surface *s, int x, int y, int u, Color hull, Color sail)
    152 {
    153     shape(s, x, y, u, HULL, 4, hull, C_OUTLINE);
    154     shape(s, x, y, u, SAIL, 3, sail, C_OUTLINE);
    155 }
    156 static const int8_t TREE[]  = { 0, -8, 6, 4, 1, 4, 1, 7, -1, 7, -1, 4, -6, 4 };
    157 static const int8_t PEAK[]  = { -8, 5, 0, -7, 8, 5 };
    158 static const int8_t CAP[]   = { -3, -2, 0, -7, 3, -2 };
    159 
    160 static void detail(Surface *s, const BoardGeom *g, int h, int ter)
    161 {
    162     int cx, cy, R = g->R;
    163     geom_hex(g, h, &cx, &cy);
    164     int spots[3][2] = { { 0, -R * 55 / 100 }, { -R * 42 / 100, R * 42 / 100 }, { R * 42 / 100, R * 42 / 100 } };
    165     int u = R / 6 > 2 ? R / 6 : 2;
    166     for (int i = 0; i < 3; i++) {
    167         int x = cx + spots[i][0], y = cy + spots[i][1];
    168         switch (ter) {
    169         case T_FOREST:
    170             shape(s, x, y, u, TREE, 7, RGB(18, 84, 36), RGB(12, 60, 24));
    171             break;
    172         case T_MOUNTAINS:
    173             shape(s, x, y, u, PEAK, 3, RGB(96, 96, 108), RGB(70, 70, 80));
    174             shape(s, x, y, u, CAP, 3, RGB(240, 240, 250), RGB(240, 240, 250));
    175             break;
    176         case T_PASTURE:
    177             gfx_disc(s, x, y, u * 6 / 8, RGB(250, 250, 250));
    178             gfx_disc(s, x + u * 5 / 8, y - u / 4, u * 3 / 8, RGB(40, 40, 40));
    179             break;
    180         case T_FIELDS:
    181             for (int k = -1; k <= 1; k++)
    182                 gfx_line(s, x + k * u * 5 / 8, y + u * 6 / 8, x + k * u * 5 / 8 + k * u / 4, y - u * 6 / 8, u / 4 + 1, RGB(186, 136, 28));
    183             break;
    184         case T_HILLS:
    185             gfx_rect(s, x - u, y - u / 2, u, u / 2 + 1, RGB(140, 56, 28));
    186             gfx_rect(s, x + 1, y - u / 2, u, u / 2 + 1, RGB(140, 56, 28));
    187             gfx_rect(s, x - u / 2, y + 1, u, u / 2 + 1, RGB(140, 56, 28));
    188             break;
    189         case T_DESERT:
    190             gfx_disc(s, x, y, u / 3, RGB(200, 176, 120));
    191             gfx_disc(s, x + u, y + u / 3, u / 4, RGB(200, 176, 120));
    192             break;
    193         case T_SEA:                           /* olitas */
    194             gfx_line(s, x - u, y, x - u / 3, y - u / 3, 1, RGB(90, 150, 210));
    195             gfx_line(s, x - u / 3, y - u / 3, x + u / 3, y, 1, RGB(90, 150, 210));
    196             gfx_line(s, x + u / 3, y, x + u, y - u / 3, 1, RGB(90, 150, 210));
    197             break;
    198         case T_GOLD:                          /* monedas grandes y brillantes */
    199             gfx_disc(s, x, y, u + 1, RGB(90, 60, 10));
    200             gfx_disc(s, x, y, u, RGB(255, 222, 60));
    201             gfx_disc(s, x - u / 4, y - u / 4, u / 3 > 0 ? u / 3 : 1, RGB(255, 250, 200));
    202             break;
    203         }
    204     }
    205 }
    206 
    207 static void token(Surface *s, const BoardGeom *g, int h, int num)
    208 {
    209     int cx, cy;
    210     geom_hex(g, h, &cx, &cy);
    211     /* los puntos de probabilidad entran con una ficha de radio 10 (en los tableros grandes
    212      * de Navegantes el hexagono queda en ~20 px); la Pico (R 24) tambien los lleva ahora */
    213     bool pips = g->R >= 20;
    214     int r = g->R * 32 / 100;
    215     const Font *f = g->R >= 30 ? &font_bold : &font_normal;
    216     if (r < (pips ? 10 : f->h * 3 / 4)) r = pips ? 10 : f->h * 3 / 4;
    217     gfx_disc(s, cx, cy, r + 1, C_OUTLINE);
    218     gfx_disc(s, cx, cy, r, C_TOKEN);
    219     char txt[3] = { 0 };
    220     if (num >= 10) { txt[0] = '1'; txt[1] = (char)('0' + num - 10); }
    221     else txt[0] = (char)('0' + num);
    222     Color c = (num == 6 || num == 8) ? C_RED : C_OUTLINE;
    223     int tw = gfx_text_w(f, txt);
    224     int ty = cy - f->h / 2 - (pips ? 2 : 0);
    225     gfx_text(s, f, cx - tw / 2, ty, txt, c);
    226     if (pips) {
    227         int n = PIPS[num], py = cy + r / 2;      /* dentro del disco tambien con r = 9 */
    228         for (int i = 0; i < n; i++) gfx_rect(s, cx - n * 3 / 2 + i * 3 + 1, py, 2, 2, c);
    229     }
    230 }
    231 
    232 /* los muelles van debajo de rutas y pueblos; el disco con el tipo, encima (port_disc) */
    233 static void port(Surface *s, const BoardGeom *g, int i)
    234 {
    235     const Topo *t = g->t;
    236     int px, py, x, y;
    237     geom_port(g, i, &px, &py);
    238     for (int k = 0; k < 2; k++) {
    239         geom_vert(g, t->edge_v[t->port_edge[i]][k], &x, &y);
    240         gfx_line(s, x, y, px, py, g->R / 10 + 1, RGB(120, 84, 44));
    241     }
    242 }
    243 
    244 static void port_disc(Surface *s, const BoardGeom *g, const Game *gm, int i, int flags)
    245 {
    246     int px, py;
    247     geom_port(g, i, &px, &py);
    248     int r = g->R * 28 / 100;
    249     if (r < 2) r = 2;
    250     int kind = gm->port[i];
    251     Color c = kind == PORT_ANY ? C_WHITE : TERRAIN_COLOR[kind];
    252     gfx_disc(s, px, py, r + 1, C_OUTLINE);
    253     gfx_disc(s, px, py, r, c);
    254     if ((flags & GB_TEXT) && g->R >= 30) {
    255         const char *txt = kind == PORT_ANY ? "3:1" : "2:1";
    256         gfx_text(s, &font_small, px - 7, py - 3, txt, C_OUTLINE);
    257     } else if ((flags & GB_TEXT) && kind == PORT_ANY) {
    258         gfx_text(s, &font_small, px - 2, py - 3, "3", C_OUTLINE);   /* chico: solo "3" en los 3:1 */
    259     }
    260 }
    261 
    262 void gfx_board(Surface *s, const BoardGeom *g, const Game *gm, const Ctrl *c, int flags)
    263 {
    264     const Topo *t = g->t;
    265     int R = g->R;
    266 
    267     for (int h = 0; h < g->t->nhex; h++)              /* el mar va entero, sin borde de arena */
    268         if (ter_land(gm->terrain[h])) hex_poly(s, g, h, 106, C_SAND);
    269     for (int h = 0; h < g->t->nhex; h++)
    270         hex_poly(s, g, h, ter_land(gm->terrain[h]) ? 94 : 101, TERRAIN_COLOR[gm->terrain[h]]);
    271     if ((flags & GB_DETAIL) && R >= 16)
    272         for (int h = 0; h < g->t->nhex; h++) detail(s, g, h, gm->terrain[h]);
    273     if (flags & GB_PORTS)
    274         for (int i = 0; i < g->t->nport; i++) port(s, g, i);
    275     if (flags & GB_TOKENS)
    276         for (int h = 0; h < g->t->nhex; h++)
    277             if (gm->num[h]) token(s, g, h, gm->num[h]);
    278 
    279     bool hl = (flags & GB_HILITE) && c;
    280     int cur = hl ? ctrl_cursor_pos(c) : -1;
    281     Color ccol = hl && ctrl_cursor_target(c) >= 0 ? C_CURSOR : C_DIM;   /* gris: ahi no se puede */
    282 
    283     if (hl && c->mode == CM_HEX) {
    284         for (int i = 0; i < c->ntargets; i++) {
    285             int x, y;
    286             geom_hex(g, c->targets[i], &x, &y);
    287             gfx_ring(s, x, y, R * 45 / 100, R >= 20 ? 2 : 1, C_WHITE);
    288         }
    289     }
    290 
    291     if (flags & GB_ROADS)
    292         for (int e = 0; e < g->t->nedge; e++) {
    293             if (gm->eown[e] < 0) continue;
    294             int x0, y0, x1, y1;
    295             geom_vert(g, t->edge_v[e][0], &x0, &y0);
    296             geom_vert(g, t->edge_v[e][1], &x1, &y1);
    297             if (gm->ship[e]) {                /* barco: un barquito en el medio de la arista */
    298                 int u = R / 5 > 1 ? R / 5 : 1;
    299                 boat(s, (x0 + x1) / 2, (y0 + y1) / 2 - u / 2, u, PLAYER_COLOR[gm->eown[e]], C_WHITE);
    300                 continue;
    301             }
    302             int ax = x0 + (x1 - x0) * 14 / 100, ay = y0 + (y1 - y0) * 14 / 100;
    303             int bx = x1 - (x1 - x0) * 14 / 100, by = y1 - (y1 - y0) * 14 / 100;
    304             int th = R / 6 > 2 ? R / 6 : 2;
    305             if (R >= 12) gfx_line(s, ax, ay, bx, by, th + 2, C_OUTLINE);
    306             gfx_line(s, ax, ay, bx, by, th, PLAYER_COLOR[gm->eown[e]]);
    307         }
    308 
    309     if (hl && c->mode == CM_EDGE) {
    310         for (int i = 0; i < c->ntargets; i++) {
    311             int e = c->targets[i], x, y;
    312             if (e == cur) continue;
    313             geom_edge(g, e, &x, &y);
    314             gfx_disc(s, x, y, R / 10 + 1, C_OUTLINE);
    315             gfx_disc(s, x, y, R / 10, C_WHITE);
    316         }
    317         if (cur >= 0) {
    318             int x0, y0, x1, y1;
    319             geom_vert(g, t->edge_v[cur][0], &x0, &y0);
    320             geom_vert(g, t->edge_v[cur][1], &x1, &y1);
    321             int th = R / 5 > 3 ? R / 5 : 3;
    322             gfx_line(s, x0, y0, x1, y1, th + 2, C_OUTLINE);
    323             gfx_line(s, x0, y0, x1, y1, th, ccol);
    324         }
    325     }
    326 
    327     if (flags & GB_ROBBER) {
    328         int x, y;
    329         geom_hex(g, gm->robber, &x, &y);
    330         int u = R / 7 > 1 ? R / 7 : 1;
    331         int rx = x - R * 52 / 100, ry = y;
    332         if (gm->terrain[gm->robber] == T_DESERT) rx = x;
    333         shape(s, rx, ry, u, PAWN, 6, RGB(52, 52, 58), RGB(200, 200, 200));
    334         gfx_disc(s, rx, ry - u * 5 / 8 * 2, u * 5 / 8 + 1, RGB(200, 200, 200));
    335         gfx_disc(s, rx, ry - u * 5 / 8 * 2, u * 5 / 8, RGB(52, 52, 58));
    336         if (gm->pirate < g->t->nhex) {         /* el pirata: barco negro */
    337             geom_hex(g, gm->pirate, &x, &y);
    338             boat(s, x, y, R / 4 > 1 ? R / 4 : 1, RGB(30, 30, 34), RGB(60, 60, 66));
    339         }
    340     }
    341 
    342     if (flags & GB_BUILD)
    343         for (int v = 0; v < g->t->nvert; v++) {
    344             int o = gm->vown[v];
    345             if (o < 0) continue;
    346             int x, y, u = R / 5 > 2 ? R / 5 : 2;
    347             geom_vert(g, v, &x, &y);
    348             if (gm->vlev[v] == 2) shape(s, x, y, u, CITY, 7, PLAYER_COLOR[o], C_OUTLINE);
    349             else shape(s, x, y, u, HOUSE, 5, PLAYER_COLOR[o], C_OUTLINE);
    350         }
    351 
    352     if (c) {                             /* colocacion en borrador (todavia sin mandar) */
    353         int dv, de;
    354         ctrl_draft(c, &dv, &de);
    355         Color pc = c->actor >= 0 ? PLAYER_COLOR[c->actor] : C_WHITE;
    356         if (de >= 0) {
    357             int x0, y0, x1, y1, th = R / 6 > 2 ? R / 6 : 2;
    358             geom_vert(g, t->edge_v[de][0], &x0, &y0);
    359             geom_vert(g, t->edge_v[de][1], &x1, &y1);
    360             int ax = x0 + (x1 - x0) * 14 / 100, ay = y0 + (y1 - y0) * 14 / 100;
    361             int bx = x1 - (x1 - x0) * 14 / 100, by = y1 - (y1 - y0) * 14 / 100;
    362             gfx_line(s, ax, ay, bx, by, th + 2, C_OUTLINE);
    363             gfx_line(s, ax, ay, bx, by, th, pc);
    364         }
    365         if (dv >= 0) {
    366             int x, y, u = R / 5 > 2 ? R / 5 : 2;
    367             geom_vert(g, dv, &x, &y);
    368             shape(s, x, y, u, HOUSE, 5, pc, C_OUTLINE);
    369         }
    370     }
    371 
    372     if (hl && c->mode == CM_VERT) {
    373         int r = R / 6 > 2 ? R / 6 : 2;
    374         for (int i = 0; i < c->ntargets; i++) {
    375             int v = c->targets[i], x, y;
    376             geom_vert(g, v, &x, &y);
    377             if (v == cur) continue;
    378             gfx_ring(s, x, y, r + 1, 1, C_OUTLINE);
    379             gfx_ring(s, x, y, r, 1, C_WHITE);
    380         }
    381         if (cur >= 0) {
    382             int x, y;
    383             geom_vert(g, cur, &x, &y);
    384             gfx_ring(s, x, y, r + 4, 3, C_OUTLINE);
    385             gfx_ring(s, x, y, r + 3, 2, ccol);
    386         }
    387     }
    388     if (hl && c->mode == CM_HEX && cur >= 0) {
    389         int x, y;
    390         geom_hex(g, cur, &x, &y);
    391         gfx_ring(s, x, y, R * 50 / 100 + 1, 4, C_OUTLINE);
    392         gfx_ring(s, x, y, R * 50 / 100, 3, ccol);
    393     }
    394     if (flags & GB_PORTS)                    /* el tipo de puerto siempre visible, encima de todo */
    395         for (int i = 0; i < g->t->nport; i++) port_disc(s, g, gm, i, flags);
    396 }
    397 
    398 int gfx_board_hit(const BoardGeom *g, const Ctrl *c, int x, int y)
    399 {
    400     if (!c || (c->mode != CM_VERT && c->mode != CM_EDGE && c->mode != CM_HEX)) return -1;
    401     /* con el dedo se acepta mas lejos (gana el objetivo valido mas cercano igual) */
    402     int best = -1, bd = 0, lim = c->mode == CM_HEX ? g->R * 85 / 100 : g->R * (gfx_touch ? 70 : 45) / 100;
    403     for (int i = 0; i < c->ntargets; i++) {
    404         int id = c->targets[i], tx, ty;
    405         if (c->mode == CM_VERT) geom_vert(g, id, &tx, &ty);
    406         else if (c->mode == CM_EDGE) geom_edge(g, id, &tx, &ty);
    407         else geom_hex(g, id, &tx, &ty);
    408         int d = (tx - x) * (tx - x) + (ty - y) * (ty - y);
    409         if (d <= lim * lim && (best < 0 || d < bd)) { bd = d; best = id; }
    410     }
    411     return best;
    412 }