/* jeu-bubblet.c : un jeu de bubblet * * Edouard.Thiel@lif.univ-mrs.fr - 24/07/2011 - version 1.2 * * Compilation sous Unix : * gcc -Wall jeu-bubblet.c ez-draw.c -o jeu-bubblet -lX11 -lXext -L/usr/X11R6/lib * Compilation sous Windows : * gcc -Wall jeu-bubblet.c ez-draw.c -o jeu-bubblet.exe -lgdi32 * * This program is free software under the terms of the * GNU Lesser General Public License (LGPL) version 2.1. */ #include "ez-draw.h" /*------------------------- D E F I N I T I O N S ---------------------------*/ #define GRILLE_H 12 #define GRILLE_L 11 #define BULLES_N 5 #define ANIME_NB 12 #define DELAY1 30 /* La grille est entouree d'un bord d'epaisseur 1 */ typedef int Grille[GRILLE_H+2][GRILLE_L+2]; typedef struct { Grille bulles, blocs, bulles_prec; int nbloc; int undo_possible; int taille[GRILLE_H*GRILLE_L]; int max_taille; int clic_i, clic_j, cur_bloc; int score, gain, bonus, score_prec; enum { E_DEBUT, E_JEU, E_SEL1, E_SEL2, E_ANIM1, E_FIN } etat; int anim_etape; Ez_uint32 coul_inter[BULLES_N+1], coul_bord[BULLES_N+1]; } App; #define CASE_XB 25 #define CASE_YB 40 #define CASE_YC 35 #define CASE_XM 30 #define CASE_YM 30 #define WIN1_H (CASE_YB+CASE_YM*GRILLE_H+CASE_YC) #define WIN1_L (CASE_XB+CASE_XM*GRILLE_L+CASE_XB) typedef struct { App *app; Ez_window win1; } Gui; /*------------------------------- M O D E L E -------------------------------*/ void generer_bulles (Grille bulles) { int i, j; for (i = 0; i < GRILLE_H+2; i++) for (j = 0; j < GRILLE_L+2; j++) bulles[i][j] = (i == 0 || i == GRILLE_H+1 || j == 0 || j == GRILLE_L+1 ) ? 0 : 1 + ez_random (BULLES_N*100) % BULLES_N; } int marquer_bloc (Grille bulles, Grille blocs, int id_bloc, int i, int j) { int dj[] = { 1, 0, -1, 0 }, di[] = { 0, -1, 0, 1 }; int pi[GRILLE_H*GRILLE_L], pj[GRILLE_H*GRILLE_L], pn; int k, ki, kj, val_bulle, taille; pi[0] = i; pj[0] = j; pn = 1; blocs[i][j] = id_bloc; taille = 1; val_bulle = bulles[i][j]; while (pn > 0) { pn--; i = pi[pn]; j = pj[pn]; for (k = 0; k < 4; k++) { ki = i + di[k]; kj = j + dj[k]; if (bulles[ki][kj] == val_bulle && blocs[ki][kj] == 0) { pi[pn] = ki; pj[pn] = kj; pn++; blocs[ki][kj] = id_bloc; taille++; } } } return taille; } void lister_blocs (App *app) { int i, j, taille; for (i = 0; i < GRILLE_H+2; i++) for (j = 0; j < GRILLE_L+2; j++) app->blocs[i][j] = 0; app->nbloc = 0; app->max_taille = 0; for (i = 1; i < GRILLE_H+1; i++) for (j = 1; j < GRILLE_L+1; j++) if (app->bulles[i][j] != 0 && app->blocs[i][j] == 0) { app->nbloc++; taille = marquer_bloc (app->bulles, app->blocs, app->nbloc, i, j); app->taille[app->nbloc] = taille; if (app->max_taille < taille) app->max_taille = taille; } } int chercher_bloc_ij (App *app, int i, int j) { if (i >= 1 && i < GRILLE_H+1 && j >= 1 && j < GRILLE_L+1) return app->blocs[i][j]; return -1; } int chercher_taille_bloc (App *app, int id_bloc) { if (id_bloc >= 0 && id_bloc < GRILLE_H*GRILLE_L) return app->taille[id_bloc]; return 0; } int chercher_gain_bloc (App *app, int id_bloc) { int taille = chercher_taille_bloc (app, id_bloc), gain = taille * (taille-1); return gain; } int calculer_bonus (App *app) { int n, i, j; n = 0; for (i = 1; i < GRILLE_H+1; i++) for (j = 1; j < GRILLE_L+1; j++) if (app->bulles[i][j] != 0) n++; return (n > 5) ? 0 : (n > 1) ? (6-n)*20 : n == 1 ? 200 : 1000; } void supprimer_bloc (Grille bulles, Grille blocs, int id_bloc) { int i, j; for (i = 1; i < GRILLE_H+1; i++) for (j = 1; j < GRILLE_L+1; j++) if (blocs[i][j] == id_bloc) bulles[i][j] = 0; } int bulles_vont_descendre (Grille bulles, Grille blocs, int boum) { int i, j, k; for (j = 1; j < GRILLE_L+1; j++) { for (i = k = GRILLE_H; i >= 1; i--) if (bulles[i][j] != 0 && blocs[i][j] != boum) { if (i != k) return 1; k--; } } return 0; } void tasser_bulles_en_bas (Grille bulles) { int i, j, k; for (j = 1; j < GRILLE_L+1; j++) { for (i = k = GRILLE_H; i >= 1; i--) if (bulles[i][j] != 0) bulles[k--][j] = bulles[i][j]; for ( ; k >= 1; k--) bulles[k][j] = 0; } } void tasser_bulles_a_droite (Grille bulles) { int i, j, k; for (j = k = GRILLE_L; j >=1 ; j--) { if (j != k) for (i = 1; i < GRILLE_H+1; i++) bulles[i][k] = bulles[i][j]; if (bulles[GRILLE_H][j] != 0) k--; } for ( ; k >= 1; k--) for (i = 1; i < GRILLE_H+1; i++) bulles[i][k] = 0; } void nouvelle_partie (App *app) { generer_bulles (app->bulles); lister_blocs (app); app->clic_i = app->clic_j = app->cur_bloc = -1; app->score = app->gain = app->bonus = 0; } void copier_grille (Grille dest, Grille src) { int i, j; for (i = 0; i < GRILLE_H+2; i++) for (j = 0; j < GRILLE_L+2; j++) dest[i][j] = src[i][j]; } void memoriser_pour_undo (App *app) { copier_grille (app->bulles_prec, app->bulles); app->score_prec = app->score; app->undo_possible = 1; } int faire_undo (App *app) { if (app->undo_possible == 0) return 0; copier_grille (app->bulles, app->bulles_prec); app->score = app->score_prec; app->undo_possible = 0; return 1; } /*------------------------------ C O U L E U R S ----------------------------*/ void couleurs_init (App *app) { double s = 0.70, v1 = 0.99, v2 = 0.8; double h[BULLES_N+1] = { 0, 220, 290, 120, 0, 60}; int i; for (i = 1; i <= BULLES_N; i++) { app->coul_inter[i] = ez_get_HSV (h[i], s, v1); app->coul_bord [i] = ez_get_HSV (h[i], s, v2); } } /*------------------------------ D E S S I N S ------------------------------*/ /* Coordonnees pixels x,y --> coordonnees grille j,i */ int grille_get_x (int j) { return CASE_XB + (j-1)*CASE_XM; } int grille_get_y (int i) { return CASE_YB + (i-1)*CASE_YM; } /* Coordonnes grille j,i --> coordonnees pixels x,y */ int grille_get_j (int x) { return 1 + (x - CASE_XB) / CASE_XM - (x < CASE_XB); } int grille_get_i (int y) { return 1 + (y - CASE_YB) / CASE_YM - (y < CASE_YB); } void dessin_limites_bloc (Ez_window win, App *app, int id_bloc) { int i, j, x, y; ez_set_color (ez_black); /* Traits verticaux */ for (i = 1; i < GRILLE_H+1; i++) for (j = 0; j < GRILLE_L+1; j++) if ( (app->blocs[i][j] == id_bloc && app->blocs[i][j+1] != id_bloc) || (app->blocs[i][j] != id_bloc && app->blocs[i][j+1] == id_bloc) ) { x = grille_get_x (j+1); y = grille_get_y (i); ez_draw_line (win, x, y, x, y+CASE_YM); } /* Traits horizontaux */ for (i = 0; i < GRILLE_H+1; i++) for (j = 1; j < GRILLE_L+1; j++) if ( (app->blocs[i][j] == id_bloc && app->blocs[i+1][j] != id_bloc) || (app->blocs[i][j] != id_bloc && app->blocs[i+1][j] == id_bloc) ) { x = grille_get_x (j); y = grille_get_y (i+1); ez_draw_line (win, x, y, x+CASE_XM, y); } } void dessin_bulle_xy (Ez_window win, App *app, int x, int y, int val) { int x1 = x+2, y1 = y+2, x2 = x+CASE_XM-2, y2 = y+CASE_YM-2; if (val <= 0 || val > BULLES_N) return; ez_set_color (app->coul_inter[val]); ez_fill_circle (win, x1, y1, x2-1, y2-1); ez_set_color (app->coul_bord[val]); ez_draw_circle (win, x1, y1, x2, y2); } void dessin_boum_xy (Ez_window win, int x, int y) { int d = (CASE_XM+CASE_YM)/10, x1 = x+d, y1 = y+d, x2 = x+CASE_XM/2, y2 = y+CASE_YM/2, x3 = x+CASE_XM-d, y3 = y+CASE_YM-d; ez_set_color (ez_yellow); ez_set_thick (3); ez_draw_line (win, x1, y2, x3, y2); ez_draw_line (win, x1, y1, x3, y3); ez_draw_line (win, x1, y3, x3, y1); ez_draw_line (win, x2, y1, x2, y3); ez_set_thick (1); ez_set_color (ez_white); ez_fill_rectangle (win, x2-3, y2-3, x2+3, y2+3); } void dessin_bulles (Ez_window win, App *app) { int i, j, x, y; for (i = 1; i < GRILLE_H+1; i++) for (j = 1; j < GRILLE_L+1; j++) { x = grille_get_x (j); y = grille_get_y (i); dessin_bulle_xy (win, app, x, y, app->bulles[i][j]); } } void dessin_bulles_boum (Ez_window win, App *app, int boum) { int i, j, x, y; for (i = 1; i < GRILLE_H+1; i++) for (j = 1; j < GRILLE_L+1; j++) { x = grille_get_x (j); y = grille_get_y (i); if (app->bulles[i][j] != 0 && app->blocs[i][j] == boum) dessin_boum_xy (win, x, y); else dessin_bulle_xy (win, app, x, y, app->bulles[i][j]); } } void dessin_bulles_descente (Ez_window win, App *app, int boum, int etape) { int i, j, k, x, y, y1, y2; for (j = 1; j < GRILLE_L+1; j++) { x = grille_get_x (j); for (i = k = GRILLE_H; i >= 1; i--) if (app->bulles[i][j] != 0 && app->blocs[i][j] != boum) { y1 = grille_get_y (i); y2 = grille_get_y (k); y = (y2*etape + y1*(ANIME_NB-etape))/ANIME_NB; dessin_bulle_xy (win, app, x, y, app->bulles[i][j]); k--; } } } /*-------------------- E V E N E M E N T S W I N 1 ----------------------*/ void win1_onExpose (Ez_event *ev) { Gui *gui = ez_get_data (ev->win); App *app = gui->app; if (app->etat == E_DEBUT) { ez_set_color (ez_green); ez_set_thick (2); ez_draw_circle (ev->win, WIN1_L*1/4, 50-20, WIN1_L*3/4, 50+20); ez_set_nfont (2); ez_set_color (ez_blue); ez_set_thick (1); ez_draw_text (ev->win, EZ_MC, WIN1_L/2, 50, "BUBBLET"); ez_set_color (ez_green); ez_draw_rectangle (ev->win, 10, WIN1_H*3/12-10, WIN1_L-10, WIN1_H*3/12+220); ez_set_nfont (1); ez_set_color (ez_blue); ez_draw_text (ev->win, EZ_TC, WIN1_L/2, WIN1_H*3/12+15, "Cliquez sur un bloc (d'au moins deux bulles\n" "de meme couleur) pour afficher le gain.\n\n" "Cliquez une seconde fois dessus pour\n" "le supprimer et empocher le gain.\n\n" "Le jeu est fini quand il n'y a plus de bloc.\n\n" "Plus le bloc est gros, plus il rapporte ...\n" "Maximisez votre score !!" ); ez_set_color (ez_grey); ez_set_nfont (0); ez_draw_text (ev->win, EZ_TC, WIN1_L/2, WIN1_H*4/5, "Le programme jeu-bubblet.c fait partie de EZ-Draw :\n" "http://pageperso.lif.univ-mrs.fr/~edouard.thiel/ez-draw"); } if (app->etat == E_SEL1 || app->etat == E_SEL2 || app->etat == E_ANIM1) { int gain = chercher_gain_bloc (app, app->cur_bloc); ez_set_nfont (2); ez_set_color (ez_red); ez_draw_text (ev->win, EZ_TR, WIN1_L-10, 10, gain > 0 ? "Gain : %d" : "Pas glop !", gain); } if (app->etat == E_JEU || app->etat == E_SEL1 || app->etat == E_SEL2 || app->etat == E_ANIM1) { ez_set_nfont (2); ez_set_color (ez_blue); ez_draw_text (ev->win, EZ_TL, 10, 10, "Score : %d", app->score); } if (app->etat == E_SEL2) dessin_bulles_boum (ev->win, app, app->cur_bloc); else if (app->etat == E_JEU || app->etat == E_SEL1 || app->etat == E_FIN) dessin_bulles (ev->win, app); else if (app->etat == E_ANIM1) dessin_bulles_descente (ev->win, app, app->cur_bloc, app->anim_etape); if (app->etat == E_SEL1) dessin_limites_bloc (ev->win, app, app->cur_bloc); if (app->etat == E_FIN) { ez_set_color (ez_white); ez_fill_rectangle (ev->win, WIN1_L*1/6, WIN1_H*1/3, WIN1_L*5/6, WIN1_H*2/3); ez_set_color (ez_blue); ez_draw_rectangle (ev->win, WIN1_L*1/6, WIN1_H*1/3, WIN1_L*5/6, WIN1_H*2/3); ez_set_nfont (3); ez_set_color (ez_red); ez_draw_text (ev->win, EZ_MC, WIN1_L/2, WIN1_H/2, "Bonus %d\n\nScore final %d", app->bonus, app->score); } ez_set_nfont (0); ez_set_color (ez_black); ez_draw_text (ev->win, EZ_BL, 10, WIN1_H-10, "n : nouvelle partie"); ez_draw_text (ev->win, EZ_BR, WIN1_L-10, WIN1_H-10, "q : quitter"); if ((app->etat == E_JEU || app->etat == E_SEL1 || app->etat == E_SEL2) && app->undo_possible) ez_draw_text (ev->win, EZ_BC, WIN1_L/2, WIN1_H-10, "u : defaire"); } void win1_onMotionNotify (Ez_event *ev) { (void) ev; /* Parametre inutilise' */ } void win1_onButtonPress (Ez_event *ev) { Gui *gui = ez_get_data (ev->win); App *app = gui->app; int id_bloc; if (app->etat != E_JEU && app->etat != E_SEL1) return; app->clic_i = grille_get_i (ev->my); app->clic_j = grille_get_j (ev->mx); id_bloc = chercher_bloc_ij (app, app->clic_i, app->clic_j); switch (app->etat) { case E_JEU : if (id_bloc > 0) app->etat = E_SEL1; break; case E_SEL1 : if (app->cur_bloc == id_bloc) app->etat = E_SEL2; else if (id_bloc > 0) app->etat = E_SEL1; else app->etat = E_JEU; break; default : break; } app->cur_bloc = id_bloc; ez_send_expose (ev->win); } void win1_onButtonRelease (Ez_event *ev) { Gui *gui = ez_get_data (ev->win); App *app = gui->app; if (app->etat != E_SEL1 && app->etat != E_SEL2) return; switch (app->etat) { case E_SEL1 : { int gain = chercher_gain_bloc (app, app->cur_bloc); if (gain == 0) { app->etat = E_JEU; app->cur_bloc = -1; } } break; case E_SEL2 : app->etat = E_ANIM1; app->anim_etape = 0; ez_start_timer (ev->win, DELAY1); break; default : break; } ez_send_expose (ev->win); } void win1_onKeyPress (Ez_event *ev) { Gui *gui = ez_get_data (ev->win); App *app = gui->app; switch (ev->key_sym) { case XK_q : ez_quit (); break; case XK_n : if (app->etat != E_DEBUT && app->etat != E_FIN) memoriser_pour_undo (app); else app->undo_possible = 0; nouvelle_partie (app); app->etat = E_JEU; ez_send_expose (ev->win); break; case XK_u : if (app->etat == E_JEU || app->etat == E_SEL1 || app->etat == E_SEL2) if (faire_undo (app)) { app->etat = E_JEU; lister_blocs (app); app->clic_i = app->clic_j = app->cur_bloc = -1; ez_send_expose (ev->win); } break; } } void win1_onTimerNotify (Ez_event *ev) { Gui *gui = ez_get_data (ev->win); App *app = gui->app; if (app->etat != E_ANIM1) return; if (app->anim_etape == 0 && ! bulles_vont_descendre (app->bulles, app->blocs, app->cur_bloc)) app->anim_etape = ANIME_NB; /* l'animation est inutile */ else app->anim_etape ++; if (app->anim_etape >= ANIME_NB) { /* fin animation : on realise le coup */ int gain; memoriser_pour_undo (app); gain = chercher_gain_bloc (app, app->cur_bloc); app->score += gain; supprimer_bloc (app->bulles, app->blocs, app->cur_bloc); tasser_bulles_en_bas (app->bulles); tasser_bulles_a_droite (app->bulles); lister_blocs (app); app->clic_i = app->clic_j = app->cur_bloc = -1; app->etat = E_JEU; if (app->max_taille <= 1) { /* il n'y a plus de bloc */ app->bonus = calculer_bonus (app); app->score += app->bonus; app->etat = E_FIN; } } if (app->etat == E_ANIM1) ez_start_timer (ev->win, DELAY1); ez_send_expose (ev->win); } void win1_event (Ez_event *ev) /* Appele'e a chaque evenement sur win1 */ { switch (ev->type) { case Expose : win1_onExpose (ev); break; case MotionNotify : win1_onMotionNotify (ev); break; case ButtonPress : win1_onButtonPress (ev); break; case ButtonRelease : win1_onButtonRelease (ev); break; case KeyPress : win1_onKeyPress (ev); break; case TimerNotify : win1_onTimerNotify (ev); break; } } /*------------------------ I N I T G E N E R A L E ------------------------*/ void app_init (App *app) { app->etat = E_DEBUT; couleurs_init (app); } void gui_init (Gui *gui, App *app) { gui->app = app; gui->win1 = ez_window_create (WIN1_L, WIN1_H, "jeu Bubblet", win1_event); ez_window_dbuf (gui->win1, 1); ez_set_data (gui->win1, gui); } /*------------------ P R O G R A M M E P R I N C I P A L ------------------*/ int main () { App app; Gui gui; if (ez_init() < 0) exit(1); app_init (&app); gui_init (&gui, &app); ez_main_loop (); exit(0); }