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.
 
 
 
 

430 lines
17 KiB

#include <stdio.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_rotozoom.h>
#include <unistd.h> /* exit */
#include <math.h> /* sin, cos */
#include "api.h"
#include "gui.h"
/*#define rant // spout messages if the machine is too slow */
#define null_die(instruction, error) do if(!(instruction)) { fprintf(stderr, "(fatal) %s%s", error, "\n"); exit(1); } while(0)
SDL_Surface *IMG_LoadBoost(const char *path) {
SDL_Surface *tmp, *ret;
null_die(tmp = IMG_Load(path), "cannot load img");
ret = SDL_DisplayFormatAlpha(tmp);
SDL_FreeSurface(tmp);
return ret;
}
#define SCREEN_X 800
#define SCREEN_Y 600
struct gui_resource_t display_start() {
struct gui_resource_t res;
null_die(SDL_Init(SDL_INIT_VIDEO) != -1, "SDL initialization failed");
null_die(TTF_Init() != -1, "TTF initialization failed");
/* création fenêtre */
res.screen = SDL_SetVideoMode(SCREEN_X, SCREEN_Y, 32, SDL_HWSURFACE);
SDL_WM_SetCaption("跳棋", NULL);
/* chargement images */
res.title_img = IMG_LoadBoost("img/title.png");
res.bgnd_img = IMG_LoadBoost("img/background.png");
res.board_img = IMG_LoadBoost("img/board.png");
/* cas spécial pour la variation de transparence */
SDL_Surface *srcimg;
null_die(srcimg = IMG_Load("img/msgboard.png"), "cannot load message board img");
res.msgboard_img = SDL_DisplayFormat(srcimg);
SDL_FreeSurface(srcimg);
res.nameboard_img = IMG_LoadBoost("img/nameboard.png");
res.pawn_img[0] = IMG_LoadBoost("img/pawn0-void.png");
res.pawn_img[1] = IMG_LoadBoost("img/pawn1-yellow.png");
res.pawn_img[2] = IMG_LoadBoost("img/pawn2-black.png");
res.pawn_img[3] = IMG_LoadBoost("img/pawn3-blue.png");
res.pawn_img[4] = IMG_LoadBoost("img/pawn4-red.png");
res.pawn_img[5] = IMG_LoadBoost("img/pawn5-green.png");
res.pawn_img[6] = IMG_LoadBoost("img/pawn6-purple.png");
/* chargement police de caractères */
null_die(res.font_big = TTF_OpenFont("img/cmunbmr.otf", 34), "cannot load font");
null_die(res.font_small = TTF_OpenFont("img/cmunbmr.otf", 19), "cannot load font");
return res;
}
void draw_nameboard(const struct gui_resource_t *res, const char *name, SDL_Surface *destbuffer, double hide) {
SDL_Surface *nbbuffer = SDL_ConvertSurface(res->nameboard_img, res->nameboard_img->format, res->nameboard_img->flags);
/* rendu du nom */
SDL_Color text_color = {20, 18, 17, 0}; /* R, G, B, unused TODO: use parm color */
SDL_Surface *text_img = TTF_RenderUTF8_Blended(res->font_small, name, text_color);
SDL_Rect textclip = { nbbuffer->w/2 - text_img->w/2, nbbuffer->h/2 - text_img->h/2, 0, 0 };
/* copie sur le fond */
SDL_BlitSurface(text_img, NULL, nbbuffer, &textclip); /* dessin du texte */
SDL_FreeSurface(text_img);
/* copie du tout dans le buffer de destination */
SDL_Rect destclip = { SCREEN_X - nbbuffer->w + (short)(hide*(double)nbbuffer->w), 0, 0, 0 };
SDL_BlitSurface(nbbuffer, NULL, destbuffer, &destclip);
SDL_FreeSurface(nbbuffer);
}
SDL_Rect coord_rotate(SDL_Rect coord, double sin, double cos) {
SDL_Rect res;
res.x = (short)round( cos * ((double)coord.x) + sin * ((double)coord.y) );
res.y = (short)round( cos * ((double)coord.y) - sin * ((double)coord.x) );
return res;
}
/* calcul des positions des pions en pixels, avec rotation */
struct pawns_pxcoords_t { SDL_Rect p[121]; };
struct pawns_pxcoords_t calc_pawns_pxcoords(double angle) {
/* positions pour les images de pion */
SDL_Rect pawn_pos;
struct pawns_pxcoords_t pawns_pos_rot;
/* joueur courant en bas de l'écran */
angle += M_PI;
/* pour ne les calculer qu'une fois et pas 121 */
double sinA = sin(angle), cosA = cos(angle);
/* dessin des pions */
int index_lines[] = {1, 3, 6, 10, 23, 35, 46, 56, 65, 75, 86, 98, 111, 115, 118, 120};
int index_line_offsets[] = {0, 0, 2, 2, 8, 11, 11, 9, 9, 8, 10, 10, 12, 7, 3, 1, 1};
int line = 0;
#define PAWNSIZE 28
#define FUZZSPACING 19
#define HORIZSPACING 10
#define VERTSPACING 5
pawn_pos.x = SCREEN_X/2 - PAWNSIZE/2 - (PAWNSIZE+HORIZSPACING);
pawn_pos.y = PAWNSIZE/2 + 8;
int i;
for(i = 0; i < 121; i++) {
if(i == index_lines[line]) {
line++;
pawn_pos.y += PAWNSIZE + VERTSPACING;
pawn_pos.x += ((line%2)?-1:1)*FUZZSPACING - index_line_offsets[line]*(PAWNSIZE+HORIZSPACING);
} else {
pawn_pos.x += PAWNSIZE+HORIZSPACING;
}
/* matrix rotation on pawn_pos using cos & sin of param angle */
pawns_pos_rot.p[i].x = pawn_pos.x + PAWNSIZE/2 - SCREEN_X/2;
pawns_pos_rot.p[i].y = pawn_pos.y + PAWNSIZE/2 - SCREEN_Y/2;
pawns_pos_rot.p[i] = coord_rotate(pawns_pos_rot.p[i], sinA, cosA);
pawns_pos_rot.p[i].x -= PAWNSIZE/2 - SCREEN_X/2;
pawns_pos_rot.p[i].y -= PAWNSIZE/2 - SCREEN_Y/2;
}
return pawns_pos_rot;
}
/* permet de sortir des positions en pixels à partir d'indices de plateau */
struct pos_export_t { int pos[2]; SDL_Rect pixpos[2]; };
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) {
if(buffer == NULL)
buffer = res->screen;
/* dessin du fond */
SDL_BlitSurface(res->bgnd_img, NULL, buffer, NULL);
SDL_BlitSurface(res->title_img, NULL, buffer, NULL);
if(angle == 0.0)
/* pas besoin de tourner */
SDL_BlitSurface(res->board_img, NULL, buffer, NULL); /* dessin du plateau */
else {
/* rotation plateau */
SDL_Surface *rotated_board_img = rotozoomSurface(res->board_img, angle*180/M_PI, 1.0, antialias);
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};
SDL_BlitSurface(rotated_board_img, NULL, buffer, &clip); /* dessin du plateau */
SDL_FreeSurface(rotated_board_img);
}
/* calcul de toutes les coordonnées en pixels des pions à dessiner */
struct pawns_pxcoords_t pawns_pxcoords = calc_pawns_pxcoords(angle);
/* dessin des pions */
int i;
for(i = 0; i < 121; i++)
SDL_BlitSurface(res->pawn_img[gamestate->board[i]], NULL, buffer, &pawns_pxcoords.p[i]);
/* export des coordonnées pour réutilisation (animation de saut) si demandé */
if(export) {
/* 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 */
if(export->pos[0] >= 0 && export->pos[0] < 121) /* évite le segfault au passage */
export->pixpos[0] = pawns_pxcoords.p[export->pos[0]];
if(export->pos[1] >= 0 && export->pos[1] < 121)
export->pixpos[1] = pawns_pxcoords.p[export->pos[1]];
}
}
void display_render_board(const struct gui_resource_t *res, const struct game_state_t *gamestate, enum hole_t currentplayer, const char *playername) {
null_die(res, "ヌルポ。。。ガ! (gui_resource)");
null_die(gamestate, "ヌルポ。。。ガ! (game_state)");
draw_all(res, gamestate, NULL, ((((double)currentplayer)-1)*M_PI)/3, 1/*antialias*/, NULL);
draw_nameboard(res, playername, res->screen, 0);
}
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) {
null_die(res, "ヌルポ。。。ガ! (gui_resource)");
null_die(gamestate, "ヌルポ。。。ガ! (game_state)");
#define ANIFRAMETIME 40 /* 40ms → 25fps */
#define ANIMTIME 2000 /* 2000ms → 2s */
uint32_t time1, time2; /* mesure du temps de rendu de frame */
SDL_Event event;
int frame;
for(frame = 0; frame <= ANIMTIME/ANIFRAMETIME; frame++) {
time1 = SDL_GetTicks();
while(SDL_PollEvent(&event))
if(event.type == SDL_QUIT)
return 1;
double progress = ((double)frame)/((double)(ANIMTIME/ANIFRAMETIME));
/* progrès non-linéaire (cosinus au cours du temps) */
double anim_progress = ( ((double)nextplayer) - ((double)currentplayer) ) * ( cos(M_PI*progress + M_PI) + 1 ) / 2;
double angle = ( anim_progress + (((double)currentplayer)-1) ) * M_PI / 3;
draw_all(res, gamestate, NULL, angle, frame == ANIMTIME/ANIFRAMETIME, NULL); /* antialiasing si dernière frame, sinon pas */
draw_nameboard(res, (progress < 0.5)?playername:nextplayername, res->screen, sin(progress*M_PI));
SDL_Flip(res->screen);
/* attente du temps restant dans la frame : stabilisation de la vitesse d'exécution */
time2 = SDL_GetTicks();
if(time2-time1 < ANIFRAMETIME)
SDL_Delay(ANIFRAMETIME - (time2-time1));
#ifdef rant
else
fprintf(stderr,"(rant) animation: the machine is slow, frame took %dms\n", time2-time1);
#endif
}
return 0;
}
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) {
null_die(res, "ヌルポ。。。ガ! (gui_resource)");
#define PANIFRAMETIME 40 /* 40ms → 25fps */
#define PANIMTIME 1000 /* 1000ms → 1s */
enum hole_t pawn = gamestate.board[startpos]; /* conserve la couleur du pion qui sera déplacé */
gamestate.board[startpos] = none; /* on dessine un trou dans le fond, car le pion va en partir */
SDL_Surface *buffer = SDL_ConvertSurface(res->bgnd_img, res->bgnd_img->format, res->bgnd_img->flags);
struct pos_export_t pos_import = { {startpos, endpos}, {{0, 0, 0, 0}, {SCREEN_X, SCREEN_Y, 0, 0}} };
draw_all(res, &gamestate, buffer, ((((double)currentplayer)-1)*M_PI)/3, 1/*antialias*/, &pos_import);
draw_nameboard(res, playername, buffer, 0);
uint32_t time1, time2; /* mesure du temps de rendu de frame */
SDL_Event event;
SDL_Rect pawn_pos;
int frame;
for(frame = 0; frame <= PANIMTIME/PANIFRAMETIME; frame++) {
time1 = SDL_GetTicks();
while(SDL_PollEvent(&event))
if(event.type == SDL_QUIT)
frame = PANIMTIME/PANIFRAMETIME + 2;
/* interpolation de position pour obtenir pawn_pos */
double cos_progress = ( cos(M_PI*((double)frame)/((double)(PANIMTIME/PANIFRAMETIME))+M_PI) + 1 ) / 2;
pawn_pos.x = pos_import.pixpos[0].x + (short)round( ((double)(pos_import.pixpos[1].x - pos_import.pixpos[0].x)) * cos_progress);
pawn_pos.y = pos_import.pixpos[0].y + (short)round( ((double)(pos_import.pixpos[1].y - pos_import.pixpos[0].y)) * cos_progress);
SDL_BlitSurface(buffer, NULL, res->screen, NULL);
SDL_BlitSurface(res->pawn_img[pawn], NULL, res->screen, &pawn_pos);
SDL_Flip(res->screen);
/* attente du temps restant dans la frame */
time2 = SDL_GetTicks();
if(time2-time1 < PANIFRAMETIME)
SDL_Delay(PANIFRAMETIME - (time2-time1));
#ifdef rant
else
fprintf(stderr,"(rant) pawn animation: the machine is slow, frame took %dms\n", time2-time1);
#endif
}
SDL_FreeSurface(buffer);
return frame != PANIMTIME/PANIFRAMETIME + 1;
}
int find_pawn_index(Uint16 click_x, Uint16 click_y, const struct pawns_pxcoords_t *pawns_pxcoords) {
int i, res = -1;
for(i = 0; i < 121; i++) {
/* si le clic est dans le carré d'un emplacement de pion */
if(pawns_pxcoords->p[i].x < click_x && click_x < pawns_pxcoords->p[i].x+PAWNSIZE
&& pawns_pxcoords->p[i].y < click_y && click_y < pawns_pxcoords->p[i].y+PAWNSIZE)
{
res = i;
break;
}
}
return res;
}
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) {
null_die(res, "ヌルポ。。。ガ! (gui_resource)");
null_die(gamestate, "ヌルポ。。。ガ! (game_state)");
null_die(move, "ヌルポ。。。ガ! (move)");
null_die(move, "ヌルポ。。。ガ! (moremoves)");
/* positions en pixels des emplacements des pions */
struct pawns_pxcoords_t pawns_pxcoords = calc_pawns_pxcoords( ((((double)currentplayer)-1)*M_PI)/3 );
SDL_Event event;
int quit = 0;
while(!quit) {
/* (ré)affichage du plateau dans son état d'avant le mouvement */
display_render_board(res, gamestate, currentplayer, playername);
SDL_Flip(res->screen);
/* attente du début de glisser-déposer */
int press = 0;
while(!quit && !press) {
while(SDL_WaitEvent(&event)) {
if(event.type == SDL_MOUSEBUTTONDOWN) {
press = 1;
break;
} else if(event.type == SDL_QUIT) {
quit = 1;
break;
}
}
}
if(quit)
break;
/* si c'est le dernier mouvement de mon tour, clic droit */
if(event.button.button == SDL_BUTTON_RIGHT)
return *moremoves = 0;
/* tentons de trouver un index de pion aux coordonnées du clic enfoncé */
move->startPos = find_pawn_index(event.button.x, event.button.y, &pawns_pxcoords);
if (move->startPos == -1 || gamestate->board[move->startPos] == none) /* si clic en dehors d'un emplacement de pion ou pas de pion dedans */
continue; /* l'utilisateur a cliqué n'importe où, on recommence au début */
/* mise en buffer du fond pour performances */
SDL_Surface *bgbufferdrag = SDL_ConvertSurface(res->bgnd_img, res->bgnd_img->format, res->bgnd_img->flags);
struct game_state_t tmp_gamestate = *gamestate; /* copie du plateau */
tmp_gamestate.board[move->startPos] = none; /* on enlève le pion en déplacement */
draw_all(res, &tmp_gamestate, bgbufferdrag, ((((double)currentplayer)-1)*M_PI)/3, 1/*antialias*/, NULL); /* dessin du plateau sans pion en buffer */
draw_nameboard(res, playername, bgbufferdrag, 0);
/* attente de la fin de glisser-déposer */
int release = 0;
while(!quit && !release){
while(SDL_WaitEvent(&event)) {
if(event.type == SDL_MOUSEBUTTONUP) {
release = 1;
break;
} else if(event.type == SDL_QUIT) {
quit = 1;
break;
} else if(event.type == SDL_MOUSEMOTION) {
/* rendu du plateau avec le pion flottant à la position de la souris */
SDL_BlitSurface(bgbufferdrag, NULL, res->screen, NULL); /* copie du fond, rendu en cache */
SDL_Rect pawn_clip = {event.motion.x - PAWNSIZE/2, event.motion.y - PAWNSIZE/2, 0, 0};
SDL_BlitSurface(res->pawn_img[gamestate->board[move->startPos]], NULL, res->screen, &pawn_clip); /* rendu du pion par-dessus */
SDL_Flip(res->screen);
}
}
}
SDL_FreeSurface(bgbufferdrag); /* libération du buffer */
if(quit)
break;
/* tentons de trouver un index de pion aux coordonnées du relâchement du clic */
move->endPos = find_pawn_index(event.button.x, event.button.y, &pawns_pxcoords);
if (move->endPos == -1) {
// animation de retour du pion à startPos/ (au pixel, obligé) TODO
continue;
} else if(gamestate->board[move->endPos] != none) {
/* animation de retour */
//display_animove_pawn(res, *gamestate, currentplayer, move->endPos, move->startPos); ce serait mieux au pixel TODO
continue;
}
/* rendu ici, le mouvement est plausible (un pion vers une case vide), on sort */
break;
}
/* ce n'est pas le dernier mouvement de mon tour */
*moremoves = 1;
return quit;
}
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) {
null_die(res, "ヌルポ。。。ガ! (gui_resource)");
null_die(gamestate, "ヌルポ。。。ガ! (game_state)");
#define MANIFRAMETIME 40 /* 40ms → 25fps */
/* mise en buffer de l'image de fond */
SDL_Surface *bgbuffer = SDL_ConvertSurface(res->bgnd_img, res->bgnd_img->format, res->bgnd_img->flags);
draw_all(res, gamestate, bgbuffer, ((((double)currentplayer)-1)*M_PI)/3, 1/*antialias*/, NULL);
draw_nameboard(res, playername, bgbuffer, 0);
/* mise en buffer de l'image du message */
SDL_Surface *msgbuffer = SDL_ConvertSurface(res->msgboard_img, res->msgboard_img->format, res->msgboard_img->flags);
SDL_Color text_color = {20, 18, 17, 0}; /* R, G, B, unused */
SDL_Surface *text_img = TTF_RenderUTF8_Blended(res->font_big, msg, text_color);
SDL_Rect textclip = { msgbuffer->w/2 - text_img->w/2, msgbuffer->h/2 - text_img->h/2, 0, 0 };
SDL_BlitSurface(text_img, NULL, msgbuffer, &textclip); /* dessin du texte */
SDL_FreeSurface(text_img);
uint32_t time1, time2; /* mesure du temps de rendu de frame */
SDL_Event event;
unsigned int frame;
for(frame = 0; frame <= duration/MANIFRAMETIME; frame++) {
time1 = SDL_GetTicks();
while(SDL_PollEvent(&event))
if(event.type == SDL_QUIT)
frame = duration/MANIFRAMETIME + 2;
SDL_BlitSurface(bgbuffer, NULL, res->screen, NULL); /* dessin du fond */
double progress = ((double)frame)/((double)(duration/MANIFRAMETIME)); /* 0→1 */
double sqprogress = (progress-1)*(progress-1);
SDL_Surface *zoomed_msg_img = rotozoomSurface(msgbuffer, sqprogress*10/M_PI, sqprogress+0.6, 1/*antialias*/);
SDL_SetAlpha(zoomed_msg_img, SDL_SRCALPHA|SDL_RLEACCEL, (int)round(sin(sqprogress*M_PI)*255));
SDL_Rect clip = { SCREEN_X/2 - zoomed_msg_img->w/2, SCREEN_Y/2 - zoomed_msg_img->h/2, 0, 0 };
SDL_BlitSurface(zoomed_msg_img, NULL, res->screen, &clip); /* dessin du message */
SDL_FreeSurface(zoomed_msg_img);
SDL_Flip(res->screen);
/* attente du temps restant dans la frame */
time2 = SDL_GetTicks();
if(time2-time1 < MANIFRAMETIME)
SDL_Delay(MANIFRAMETIME - (time2-time1));
#ifdef rant
else
fprintf(stderr,"(rant) text animation: the machine is slow, frame took %dms\n", time2-time1);
#endif
}
SDL_FreeSurface(bgbuffer);
SDL_FreeSurface(msgbuffer);
return frame != duration/MANIFRAMETIME + 1;
}
void display_close(struct gui_resource_t *res) {
null_die(res, "ヌルポ。。。ガ! (gui_resource)");
/* Libération mémoire */
SDL_FreeSurface(res->screen);
SDL_FreeSurface(res->bgnd_img);
SDL_FreeSurface(res->title_img);
SDL_FreeSurface(res->board_img);
SDL_FreeSurface(res->msgboard_img);
SDL_FreeSurface(res->nameboard_img);
int i;
for( i = 0; i < 7; i++)
SDL_FreeSurface(res->pawn_img[i]);
/* Fermeture fenêtre */
SDL_Quit();
TTF_Quit();
}