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 }