You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

431 lines
17 KiB

  1. #include <stdio.h>
  2. #include <SDL/SDL.h>
  3. #include <SDL/SDL_image.h>
  4. #include <SDL/SDL_rotozoom.h>
  5. #include <unistd.h> /* exit */
  6. #include <math.h> /* sin, cos */
  7. #include "api.h"
  8. #include "gui.h"
  9. /*#define rant // spout messages if the machine is too slow */
  10. #define null_die(instruction, error) do if(!(instruction)) { fprintf(stderr, "(fatal) %s%s", error, "\n"); exit(1); } while(0)
  11. SDL_Surface *IMG_LoadBoost(const char *path) {
  12. SDL_Surface *tmp, *ret;
  13. null_die(tmp = IMG_Load(path), "cannot load img");
  14. ret = SDL_DisplayFormatAlpha(tmp);
  15. SDL_FreeSurface(tmp);
  16. return ret;
  17. }
  18. #define SCREEN_X 800
  19. #define SCREEN_Y 600
  20. struct gui_resource_t display_start() {
  21. struct gui_resource_t res;
  22. null_die(SDL_Init(SDL_INIT_VIDEO) != -1, "SDL initialization failed");
  23. null_die(TTF_Init() != -1, "TTF initialization failed");
  24. /* création fenêtre */
  25. res.screen = SDL_SetVideoMode(SCREEN_X, SCREEN_Y, 32, SDL_HWSURFACE);
  26. SDL_WM_SetCaption("跳棋", NULL);
  27. /* chargement images */
  28. res.title_img = IMG_LoadBoost("img/title.png");
  29. res.bgnd_img = IMG_LoadBoost("img/background.png");
  30. res.board_img = IMG_LoadBoost("img/board.png");
  31. /* cas spécial pour la variation de transparence */
  32. SDL_Surface *srcimg;
  33. null_die(srcimg = IMG_Load("img/msgboard.png"), "cannot load message board img");
  34. res.msgboard_img = SDL_DisplayFormat(srcimg);
  35. SDL_FreeSurface(srcimg);
  36. res.nameboard_img = IMG_LoadBoost("img/nameboard.png");
  37. res.pawn_img[0] = IMG_LoadBoost("img/pawn0-void.png");
  38. res.pawn_img[1] = IMG_LoadBoost("img/pawn1-yellow.png");
  39. res.pawn_img[2] = IMG_LoadBoost("img/pawn2-black.png");
  40. res.pawn_img[3] = IMG_LoadBoost("img/pawn3-blue.png");
  41. res.pawn_img[4] = IMG_LoadBoost("img/pawn4-red.png");
  42. res.pawn_img[5] = IMG_LoadBoost("img/pawn5-green.png");
  43. res.pawn_img[6] = IMG_LoadBoost("img/pawn6-purple.png");
  44. /* chargement police de caractères */
  45. null_die(res.font_big = TTF_OpenFont("img/cmunbmr.otf", 34), "cannot load font");
  46. null_die(res.font_small = TTF_OpenFont("img/cmunbmr.otf", 19), "cannot load font");
  47. return res;
  48. }
  49. void draw_nameboard(const struct gui_resource_t *res, const char *name, SDL_Surface *destbuffer, double hide) {
  50. SDL_Surface *nbbuffer = SDL_ConvertSurface(res->nameboard_img, res->nameboard_img->format, res->nameboard_img->flags);
  51. /* rendu du nom */
  52. SDL_Color text_color = {20, 18, 17, 0}; /* R, G, B, unused TODO: use parm color */
  53. SDL_Surface *text_img = TTF_RenderUTF8_Blended(res->font_small, name, text_color);
  54. SDL_Rect textclip = { nbbuffer->w/2 - text_img->w/2, nbbuffer->h/2 - text_img->h/2, 0, 0 };
  55. /* copie sur le fond */
  56. SDL_BlitSurface(text_img, NULL, nbbuffer, &textclip); /* dessin du texte */
  57. SDL_FreeSurface(text_img);
  58. /* copie du tout dans le buffer de destination */
  59. SDL_Rect destclip = { SCREEN_X - nbbuffer->w + (short)(hide*(double)nbbuffer->w), 0, 0, 0 };
  60. SDL_BlitSurface(nbbuffer, NULL, destbuffer, &destclip);
  61. SDL_FreeSurface(nbbuffer);
  62. }
  63. SDL_Rect coord_rotate(SDL_Rect coord, double sin, double cos) {
  64. SDL_Rect res;
  65. res.x = (short)round( cos * ((double)coord.x) + sin * ((double)coord.y) );
  66. res.y = (short)round( cos * ((double)coord.y) - sin * ((double)coord.x) );
  67. return res;
  68. }
  69. /* calcul des positions des pions en pixels, avec rotation */
  70. struct pawns_pxcoords_t { SDL_Rect p[121]; };
  71. struct pawns_pxcoords_t calc_pawns_pxcoords(double angle) {
  72. /* positions pour les images de pion */
  73. SDL_Rect pawn_pos;
  74. struct pawns_pxcoords_t pawns_pos_rot;
  75. /* joueur courant en bas de l'écran */
  76. angle += M_PI;
  77. /* pour ne les calculer qu'une fois et pas 121 */
  78. double sinA = sin(angle), cosA = cos(angle);
  79. /* dessin des pions */
  80. int index_lines[] = {1, 3, 6, 10, 23, 35, 46, 56, 65, 75, 86, 98, 111, 115, 118, 120};
  81. int index_line_offsets[] = {0, 0, 2, 2, 8, 11, 11, 9, 9, 8, 10, 10, 12, 7, 3, 1, 1};
  82. int line = 0;
  83. #define PAWNSIZE 28
  84. #define FUZZSPACING 19
  85. #define HORIZSPACING 10
  86. #define VERTSPACING 5
  87. pawn_pos.x = SCREEN_X/2 - PAWNSIZE/2 - (PAWNSIZE+HORIZSPACING);
  88. pawn_pos.y = PAWNSIZE/2 + 8;
  89. int i;
  90. for(i = 0; i < 121; i++) {
  91. if(i == index_lines[line]) {
  92. line++;
  93. pawn_pos.y += PAWNSIZE + VERTSPACING;
  94. pawn_pos.x += ((line%2)?-1:1)*FUZZSPACING - index_line_offsets[line]*(PAWNSIZE+HORIZSPACING);
  95. } else {
  96. pawn_pos.x += PAWNSIZE+HORIZSPACING;
  97. }
  98. /* matrix rotation on pawn_pos using cos & sin of param angle */
  99. pawns_pos_rot.p[i].x = pawn_pos.x + PAWNSIZE/2 - SCREEN_X/2;
  100. pawns_pos_rot.p[i].y = pawn_pos.y + PAWNSIZE/2 - SCREEN_Y/2;
  101. pawns_pos_rot.p[i] = coord_rotate(pawns_pos_rot.p[i], sinA, cosA);
  102. pawns_pos_rot.p[i].x -= PAWNSIZE/2 - SCREEN_X/2;
  103. pawns_pos_rot.p[i].y -= PAWNSIZE/2 - SCREEN_Y/2;
  104. }
  105. return pawns_pos_rot;
  106. }
  107. /* permet de sortir des positions en pixels à partir d'indices de plateau */
  108. struct pos_export_t { int pos[2]; SDL_Rect pixpos[2]; };
  109. void draw_all(const struct gui_resource_t *res, const struct game_state_t *gamestate, SDL_Surface *buffer, double angle, int antialias, struct pos_export_t *export) {
  110. if(buffer == NULL)
  111. buffer = res->screen;
  112. /* dessin du fond */
  113. SDL_BlitSurface(res->bgnd_img, NULL, buffer, NULL);
  114. SDL_BlitSurface(res->title_img, NULL, buffer, NULL);
  115. if(angle == 0.0)
  116. /* pas besoin de tourner */
  117. SDL_BlitSurface(res->board_img, NULL, buffer, NULL); /* dessin du plateau */
  118. else {
  119. /* rotation plateau */
  120. SDL_Surface *rotated_board_img = rotozoomSurface(res->board_img, angle*180/M_PI, 1.0, antialias);
  121. SDL_Rect clip = { res->board_img->w/2 - rotated_board_img->w/2, res->board_img->h/2 - rotated_board_img->h/2, 0, 0};
  122. SDL_BlitSurface(rotated_board_img, NULL, buffer, &clip); /* dessin du plateau */
  123. SDL_FreeSurface(rotated_board_img);
  124. }
  125. /* calcul de toutes les coordonnées en pixels des pions à dessiner */
  126. struct pawns_pxcoords_t pawns_pxcoords = calc_pawns_pxcoords(angle);
  127. /* dessin des pions */
  128. int i;
  129. for(i = 0; i < 121; i++)
  130. SDL_BlitSurface(res->pawn_img[gamestate->board[i]], NULL, buffer, &pawns_pxcoords.p[i]);
  131. /* export des coordonnées pour réutilisation (animation de saut) si demandé */
  132. if(export) {
  133. /* il est déjà prévu un mouvement dans export->pos en cas de position malformée, on n'affecte donc que si c'est un emplacement de pion */
  134. if(export->pos[0] >= 0 && export->pos[0] < 121) /* évite le segfault au passage */
  135. export->pixpos[0] = pawns_pxcoords.p[export->pos[0]];
  136. if(export->pos[1] >= 0 && export->pos[1] < 121)
  137. export->pixpos[1] = pawns_pxcoords.p[export->pos[1]];
  138. }
  139. }
  140. void display_render_board(const struct gui_resource_t *res, const struct game_state_t *gamestate, enum hole_t currentplayer, const char *playername) {
  141. null_die(res, "ヌルポ。。。ガ! (gui_resource)");
  142. null_die(gamestate, "ヌルポ。。。ガ! (game_state)");
  143. draw_all(res, gamestate, NULL, ((((double)currentplayer)-1)*M_PI)/3, 1/*antialias*/, NULL);
  144. draw_nameboard(res, playername, res->screen, 0);
  145. }
  146. int display_anirotate_board(const struct gui_resource_t *res, const struct game_state_t *gamestate, enum hole_t currentplayer, const char *playername, enum hole_t nextplayer, const char *nextplayername) {
  147. null_die(res, "ヌルポ。。。ガ! (gui_resource)");
  148. null_die(gamestate, "ヌルポ。。。ガ! (game_state)");
  149. #define ANIFRAMETIME 40 /* 40ms → 25fps */
  150. #define ANIMTIME 2000 /* 2000ms → 2s */
  151. uint32_t time1, time2; /* mesure du temps de rendu de frame */
  152. SDL_Event event;
  153. int frame;
  154. for(frame = 0; frame <= ANIMTIME/ANIFRAMETIME; frame++) {
  155. time1 = SDL_GetTicks();
  156. while(SDL_PollEvent(&event))
  157. if(event.type == SDL_QUIT)
  158. return 1;
  159. double progress = ((double)frame)/((double)(ANIMTIME/ANIFRAMETIME));
  160. /* progrès non-linéaire (cosinus au cours du temps) */
  161. double anim_progress = ( ((double)nextplayer) - ((double)currentplayer) ) * ( cos(M_PI*progress + M_PI) + 1 ) / 2;
  162. double angle = ( anim_progress + (((double)currentplayer)-1) ) * M_PI / 3;
  163. draw_all(res, gamestate, NULL, angle, frame == ANIMTIME/ANIFRAMETIME, NULL); /* antialiasing si dernière frame, sinon pas */
  164. draw_nameboard(res, (progress < 0.5)?playername:nextplayername, res->screen, sin(progress*M_PI));
  165. SDL_Flip(res->screen);
  166. /* attente du temps restant dans la frame : stabilisation de la vitesse d'exécution */
  167. time2 = SDL_GetTicks();
  168. if(time2-time1 < ANIFRAMETIME)
  169. SDL_Delay(ANIFRAMETIME - (time2-time1));
  170. #ifdef rant
  171. else
  172. fprintf(stderr,"(rant) animation: the machine is slow, frame took %dms\n", time2-time1);
  173. #endif
  174. }
  175. return 0;
  176. }
  177. int display_animove_pawn(const struct gui_resource_t *res, struct game_state_t gamestate, enum hole_t currentplayer, const char *playername, int startpos, int endpos) {
  178. null_die(res, "ヌルポ。。。ガ! (gui_resource)");
  179. #define PANIFRAMETIME 40 /* 40ms → 25fps */
  180. #define PANIMTIME 1000 /* 1000ms → 1s */
  181. enum hole_t pawn = gamestate.board[startpos]; /* conserve la couleur du pion qui sera déplacé */
  182. gamestate.board[startpos] = none; /* on dessine un trou dans le fond, car le pion va en partir */
  183. SDL_Surface *buffer = SDL_ConvertSurface(res->bgnd_img, res->bgnd_img->format, res->bgnd_img->flags);
  184. struct pos_export_t pos_import = { {startpos, endpos}, {{0, 0, 0, 0}, {SCREEN_X, SCREEN_Y, 0, 0}} };
  185. draw_all(res, &gamestate, buffer, ((((double)currentplayer)-1)*M_PI)/3, 1/*antialias*/, &pos_import);
  186. draw_nameboard(res, playername, buffer, 0);
  187. uint32_t time1, time2; /* mesure du temps de rendu de frame */
  188. SDL_Event event;
  189. SDL_Rect pawn_pos;
  190. int frame;
  191. for(frame = 0; frame <= PANIMTIME/PANIFRAMETIME; frame++) {
  192. time1 = SDL_GetTicks();
  193. while(SDL_PollEvent(&event))
  194. if(event.type == SDL_QUIT)
  195. frame = PANIMTIME/PANIFRAMETIME + 2;
  196. /* interpolation de position pour obtenir pawn_pos */
  197. double cos_progress = ( cos(M_PI*((double)frame)/((double)(PANIMTIME/PANIFRAMETIME))+M_PI) + 1 ) / 2;
  198. pawn_pos.x = pos_import.pixpos[0].x + (short)round( ((double)(pos_import.pixpos[1].x - pos_import.pixpos[0].x)) * cos_progress);
  199. pawn_pos.y = pos_import.pixpos[0].y + (short)round( ((double)(pos_import.pixpos[1].y - pos_import.pixpos[0].y)) * cos_progress);
  200. SDL_BlitSurface(buffer, NULL, res->screen, NULL);
  201. SDL_BlitSurface(res->pawn_img[pawn], NULL, res->screen, &pawn_pos);
  202. SDL_Flip(res->screen);
  203. /* attente du temps restant dans la frame */
  204. time2 = SDL_GetTicks();
  205. if(time2-time1 < PANIFRAMETIME)
  206. SDL_Delay(PANIFRAMETIME - (time2-time1));
  207. #ifdef rant
  208. else
  209. fprintf(stderr,"(rant) pawn animation: the machine is slow, frame took %dms\n", time2-time1);
  210. #endif
  211. }
  212. SDL_FreeSurface(buffer);
  213. return frame != PANIMTIME/PANIFRAMETIME + 1;
  214. }
  215. int find_pawn_index(Uint16 click_x, Uint16 click_y, const struct pawns_pxcoords_t *pawns_pxcoords) {
  216. int i, res = -1;
  217. for(i = 0; i < 121; i++) {
  218. /* si le clic est dans le carré d'un emplacement de pion */
  219. if(pawns_pxcoords->p[i].x < click_x && click_x < pawns_pxcoords->p[i].x+PAWNSIZE
  220. && pawns_pxcoords->p[i].y < click_y && click_y < pawns_pxcoords->p[i].y+PAWNSIZE)
  221. {
  222. res = i;
  223. break;
  224. }
  225. }
  226. return res;
  227. }
  228. int display_usermove_pawn(const struct gui_resource_t *res, const struct game_state_t *gamestate, enum hole_t currentplayer, const char *playername, struct move_t *move, int *moremoves) {
  229. null_die(res, "ヌルポ。。。ガ! (gui_resource)");
  230. null_die(gamestate, "ヌルポ。。。ガ! (game_state)");
  231. null_die(move, "ヌルポ。。。ガ! (move)");
  232. null_die(move, "ヌルポ。。。ガ! (moremoves)");
  233. /* positions en pixels des emplacements des pions */
  234. struct pawns_pxcoords_t pawns_pxcoords = calc_pawns_pxcoords( ((((double)currentplayer)-1)*M_PI)/3 );
  235. SDL_Event event;
  236. int quit = 0;
  237. while(!quit) {
  238. /* (ré)affichage du plateau dans son état d'avant le mouvement */
  239. display_render_board(res, gamestate, currentplayer, playername);
  240. SDL_Flip(res->screen);
  241. /* attente du début de glisser-déposer */
  242. int press = 0;
  243. while(!quit && !press) {
  244. while(SDL_WaitEvent(&event)) {
  245. if(event.type == SDL_MOUSEBUTTONDOWN) {
  246. press = 1;
  247. break;
  248. } else if(event.type == SDL_QUIT) {
  249. quit = 1;
  250. break;
  251. }
  252. }
  253. }
  254. if(quit)
  255. break;
  256. /* si c'est le dernier mouvement de mon tour, clic droit */
  257. if(event.button.button == SDL_BUTTON_RIGHT)
  258. return *moremoves = 0;
  259. /* tentons de trouver un index de pion aux coordonnées du clic enfoncé */
  260. move->startPos = find_pawn_index(event.button.x, event.button.y, &pawns_pxcoords);
  261. if (move->startPos == -1 || gamestate->board[move->startPos] == none) /* si clic en dehors d'un emplacement de pion ou pas de pion dedans */
  262. continue; /* l'utilisateur a cliqué n'importe où, on recommence au début */
  263. /* mise en buffer du fond pour performances */
  264. SDL_Surface *bgbufferdrag = SDL_ConvertSurface(res->bgnd_img, res->bgnd_img->format, res->bgnd_img->flags);
  265. struct game_state_t tmp_gamestate = *gamestate; /* copie du plateau */
  266. tmp_gamestate.board[move->startPos] = none; /* on enlève le pion en déplacement */
  267. draw_all(res, &tmp_gamestate, bgbufferdrag, ((((double)currentplayer)-1)*M_PI)/3, 1/*antialias*/, NULL); /* dessin du plateau sans pion en buffer */
  268. draw_nameboard(res, playername, bgbufferdrag, 0);
  269. /* attente de la fin de glisser-déposer */
  270. int release = 0;
  271. while(!quit && !release){
  272. while(SDL_WaitEvent(&event)) {
  273. if(event.type == SDL_MOUSEBUTTONUP) {
  274. release = 1;
  275. break;
  276. } else if(event.type == SDL_QUIT) {
  277. quit = 1;
  278. break;
  279. } else if(event.type == SDL_MOUSEMOTION) {
  280. /* rendu du plateau avec le pion flottant à la position de la souris */
  281. SDL_BlitSurface(bgbufferdrag, NULL, res->screen, NULL); /* copie du fond, rendu en cache */
  282. SDL_Rect pawn_clip = {event.motion.x - PAWNSIZE/2, event.motion.y - PAWNSIZE/2, 0, 0};
  283. SDL_BlitSurface(res->pawn_img[gamestate->board[move->startPos]], NULL, res->screen, &pawn_clip); /* rendu du pion par-dessus */
  284. SDL_Flip(res->screen);
  285. }
  286. }
  287. }
  288. SDL_FreeSurface(bgbufferdrag); /* libération du buffer */
  289. if(quit)
  290. break;
  291. /* tentons de trouver un index de pion aux coordonnées du relâchement du clic */
  292. move->endPos = find_pawn_index(event.button.x, event.button.y, &pawns_pxcoords);
  293. if (move->endPos == -1) {
  294. // animation de retour du pion à startPos/ (au pixel, obligé) TODO
  295. continue;
  296. } else if(gamestate->board[move->endPos] != none) {
  297. /* animation de retour */
  298. //display_animove_pawn(res, *gamestate, currentplayer, move->endPos, move->startPos); ce serait mieux au pixel TODO
  299. continue;
  300. }
  301. /* rendu ici, le mouvement est plausible (un pion vers une case vide), on sort */
  302. break;
  303. }
  304. /* ce n'est pas le dernier mouvement de mon tour */
  305. *moremoves = 1;
  306. return quit;
  307. }
  308. int display_animsg(const struct gui_resource_t *res, const struct game_state_t *gamestate, enum hole_t currentplayer, const char *playername, const char *msg, uint32_t duration) {
  309. null_die(res, "ヌルポ。。。ガ! (gui_resource)");
  310. null_die(gamestate, "ヌルポ。。。ガ! (game_state)");
  311. #define MANIFRAMETIME 40 /* 40ms → 25fps */
  312. /* mise en buffer de l'image de fond */
  313. SDL_Surface *bgbuffer = SDL_ConvertSurface(res->bgnd_img, res->bgnd_img->format, res->bgnd_img->flags);
  314. draw_all(res, gamestate, bgbuffer, ((((double)currentplayer)-1)*M_PI)/3, 1/*antialias*/, NULL);
  315. draw_nameboard(res, playername, bgbuffer, 0);
  316. /* mise en buffer de l'image du message */
  317. SDL_Surface *msgbuffer = SDL_ConvertSurface(res->msgboard_img, res->msgboard_img->format, res->msgboard_img->flags);
  318. SDL_Color text_color = {20, 18, 17, 0}; /* R, G, B, unused */
  319. SDL_Surface *text_img = TTF_RenderUTF8_Blended(res->font_big, msg, text_color);
  320. SDL_Rect textclip = { msgbuffer->w/2 - text_img->w/2, msgbuffer->h/2 - text_img->h/2, 0, 0 };
  321. SDL_BlitSurface(text_img, NULL, msgbuffer, &textclip); /* dessin du texte */
  322. SDL_FreeSurface(text_img);
  323. uint32_t time1, time2; /* mesure du temps de rendu de frame */
  324. SDL_Event event;
  325. unsigned int frame;
  326. for(frame = 0; frame <= duration/MANIFRAMETIME; frame++) {
  327. time1 = SDL_GetTicks();
  328. while(SDL_PollEvent(&event))
  329. if(event.type == SDL_QUIT)
  330. frame = duration/MANIFRAMETIME + 2;
  331. SDL_BlitSurface(bgbuffer, NULL, res->screen, NULL); /* dessin du fond */
  332. double progress = ((double)frame)/((double)(duration/MANIFRAMETIME)); /* 0→1 */
  333. double sqprogress = (progress-1)*(progress-1);
  334. SDL_Surface *zoomed_msg_img = rotozoomSurface(msgbuffer, sqprogress*10/M_PI, sqprogress+0.6, 1/*antialias*/);
  335. SDL_SetAlpha(zoomed_msg_img, SDL_SRCALPHA|SDL_RLEACCEL, (int)round(sin(sqprogress*M_PI)*255));
  336. SDL_Rect clip = { SCREEN_X/2 - zoomed_msg_img->w/2, SCREEN_Y/2 - zoomed_msg_img->h/2, 0, 0 };
  337. SDL_BlitSurface(zoomed_msg_img, NULL, res->screen, &clip); /* dessin du message */
  338. SDL_FreeSurface(zoomed_msg_img);
  339. SDL_Flip(res->screen);
  340. /* attente du temps restant dans la frame */
  341. time2 = SDL_GetTicks();
  342. if(time2-time1 < MANIFRAMETIME)
  343. SDL_Delay(MANIFRAMETIME - (time2-time1));
  344. #ifdef rant
  345. else
  346. fprintf(stderr,"(rant) text animation: the machine is slow, frame took %dms\n", time2-time1);
  347. #endif
  348. }
  349. SDL_FreeSurface(bgbuffer);
  350. SDL_FreeSurface(msgbuffer);
  351. return frame != duration/MANIFRAMETIME + 1;
  352. }
  353. void display_close(struct gui_resource_t *res) {
  354. null_die(res, "ヌルポ。。。ガ! (gui_resource)");
  355. /* Libération mémoire */
  356. SDL_FreeSurface(res->screen);
  357. SDL_FreeSurface(res->bgnd_img);
  358. SDL_FreeSurface(res->title_img);
  359. SDL_FreeSurface(res->board_img);
  360. SDL_FreeSurface(res->msgboard_img);
  361. SDL_FreeSurface(res->nameboard_img);
  362. int i;
  363. for( i = 0; i < 7; i++)
  364. SDL_FreeSurface(res->pawn_img[i]);
  365. /* Fermeture fenêtre */
  366. SDL_Quit();
  367. TTF_Quit();
  368. }