block colors
[vrtris] / src / gamescr.c
1 #include <stdlib.h>
2 #include <time.h>
3 #include <assert.h>
4 #include <imago2.h>
5 #include "opengl.h"
6 #include "game.h"
7 #include "screen.h"
8 #include "cmesh.h"
9 #include "blocks.h"
10 #include "logger.h"
11
12 static int init(void);
13 static void cleanup(void);
14 static void start(void);
15 static void stop(void);
16 static void update(float dt);
17 static void draw(void);
18 static void draw_block(int block, const int *pos, int rot);
19 static void drawpf(void);
20 static void reshape(int x, int y);
21 static void keyboard(int key, int pressed);
22 static void mouse(int bn, int pressed, int x, int y);
23 static void motion(int x, int y);
24 static void wheel(int dir);
25
26 static void update_cur_block(void);
27 static void addscore(int nlines);
28 static int spawn(void);
29 static int collision(int block, const int *pos);
30 static void stick(int block, const int *pos);
31 static void erase_completed(void);
32
33 struct game_screen game_screen = {
34         "game",
35         1,      /* opaque */
36         0,      /* next */
37         init,
38         cleanup,
39         start,
40         stop,
41         update,
42         draw,
43         reshape,
44         keyboard,
45         mouse,
46         motion,
47         wheel
48 };
49
50 static struct cmesh *blkmesh, *wellmesh;
51 static unsigned int tex_well;
52
53 static float cam_theta, cam_phi, cam_dist = 30;
54 static int bnstate[16];
55 static int prev_mx, prev_my;
56
57 static long tick_interval;
58
59 /* dimensions of the playfield */
60 #define PF_ROWS         18
61 #define PF_COLS         10
62
63 #define PF_FULL         0x100
64 #define PF_VIS          0x200
65 #define PF_VIS_SHIFT    9
66
67 static unsigned int pfield[PF_ROWS * PF_COLS];
68
69 static int pos[2], next_pos[2];
70 static int cur_block, next_block, prev_block;
71 static int cur_rot, prev_rot;
72 static int complines[4];
73 static int num_complines;
74 static int gameover;
75 static int pause;
76 static int score, level, lines;
77 static int just_spawned;
78
79 #define NUM_LEVELS      21
80 static const long level_speed[NUM_LEVELS] = {
81         887, 820, 753, 686, 619, 552, 469, 368, 285, 184,
82         167, 151, 134, 117, 107, 98, 88, 79, 69, 60, 50
83 };
84
85 static const float blkcolor[][4] = {
86         {1.0, 0.65, 0.0, 1},
87         {0.16, 1.0, 0.0, 1},
88         {0.65, 0.65, 1.0, 1},
89         {1.0, 1.0, 0.0, 1},
90         {0.0, 1.0, 1.0, 1},
91         {1.0, 0.5, 1.0, 1},
92         {1.0, 0.25, 0.0, 1}
93 };
94
95
96 static int init(void)
97 {
98         if(!(blkmesh = cmesh_alloc()) || cmesh_load(blkmesh, "data/noisecube.obj") == -1) {
99                 error_log("failed to load block mesh\n");
100                 return -1;
101         }
102
103         if(!(wellmesh = cmesh_alloc()) || cmesh_load(wellmesh, "data/well.obj") == -1) {
104                 error_log("failed to load well mesh\n");
105                 return -1;
106         }
107
108         if(!(tex_well = img_gltexture_load("data/grid.png"))) {
109                 error_log("failed to load well texture\n");
110                 return -1;
111         }
112
113         return 0;
114 }
115
116 static void cleanup(void)
117 {
118         cmesh_free(blkmesh);
119 }
120
121 static void start(void)
122 {
123         srand(time(0));
124
125         pause = 0;
126         gameover = 0;
127         num_complines = 0;
128         score = level = lines = 0;
129         tick_interval = level_speed[0];
130         cur_block = -1;
131         prev_block = 0;
132         next_block = rand() % NUM_BLOCKS;
133
134         memset(pfield, 0, PF_COLS * PF_ROWS * sizeof *pfield);
135 }
136
137 static void stop(void)
138 {
139 }
140
141 static void update(float dtsec)
142 {
143         static long prev_tick;
144         long dt;
145
146         if(pause) {
147                 prev_tick = time_msec;
148                 return;
149         }
150         dt = time_msec - prev_tick;
151
152         /*
153         if(gameover) {
154                 int i, row = PF_ROWS - gameover;
155                 int *ptr;
156
157                 if(dt < GAMEOVER_FILL_RATE) {
158                         return;
159                 }
160
161                 if(row >= 0) {
162                         ptr = pfield + row * PF_COLS;
163                         for(i=0; i<PF_COLS; i++) {
164                                 *ptr++ = TILE_GAMEOVER;
165                         }
166
167                         gameover++;
168                         prev_tick = msec;
169                 }
170                 return;
171         }
172         */
173
174         if(num_complines) {
175                 /* lines where completed, we're in blinking mode */
176                 int i, j, blink = dt >> 8;
177
178                 if(blink > 6) {
179                         erase_completed();
180                         num_complines = 0;
181                         return;
182                 }
183
184                 for(i=0; i<num_complines; i++) {
185                         unsigned int *ptr = pfield + complines[i] * PF_COLS;
186                         for(j=0; j<PF_COLS; j++) {
187                                 *ptr = (*ptr & ~PF_VIS) | ((blink & 1) << PF_VIS_SHIFT);
188                         }
189                 }
190                 return;
191         }
192
193         /* fall */
194         while(dt >= tick_interval) {
195                 if(cur_block >= 0) {
196                         just_spawned = 0;
197                         next_pos[0] = pos[0] + 1;
198                         if(collision(cur_block, next_pos)) {
199                                 next_pos[0] = pos[0];
200                                 stick(cur_block, next_pos);
201                                 return;
202                         }
203                 } else {
204                         /* respawn */
205                         if(spawn() == -1) {
206                                 gameover = 1;
207                                 return;
208                         }
209                 }
210
211                 dt -= tick_interval;
212                 prev_tick = time_msec;
213         }
214
215         update_cur_block();
216 }
217
218 static void draw(void)
219 {
220         const float lpos[] = {-1, 1, 6, 1};
221
222         glTranslatef(0, 0, -cam_dist);
223         glRotatef(cam_phi, 1, 0, 0);
224         glRotatef(cam_theta, 0, 1, 0);
225
226         glLightfv(GL_LIGHT0, GL_POSITION, lpos);
227
228         glPushAttrib(GL_ENABLE_BIT);
229         glBindTexture(GL_TEXTURE_2D, tex_well);
230         glEnable(GL_TEXTURE_2D);
231         glDisable(GL_LIGHTING);
232         glColor3f(1, 1, 1);
233         cmesh_draw(wellmesh);
234         glPopAttrib();
235
236         /* center playfield */
237         glPushMatrix();
238         glTranslatef(-PF_COLS / 2 + 0.5, PF_ROWS / 2 - 0.5, 0);
239
240         drawpf();
241         if(cur_block >= 0) {
242                 draw_block(cur_block, pos, cur_rot);
243         }
244
245         glPopMatrix();
246 }
247
248 static const float blkspec[] = {1, 1, 1, 1};
249
250 static void draw_block(int block, const int *pos, int rot)
251 {
252         int i;
253         unsigned char *p = blocks[block][rot];
254
255         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blkcolor[block]);
256         glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
257         glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
258
259         for(i=0; i<4; i++) {
260                 int x = pos[1] + BLKX(*p);
261                 int y = pos[0] + BLKY(*p);
262                 p++;
263
264                 if(y < 0) continue;
265
266                 glPushMatrix();
267                 glTranslatef(x, -y, 0);
268                 cmesh_draw(blkmesh);
269                 glPopMatrix();
270         }
271 }
272
273 static void drawpf(void)
274 {
275         int i, j;
276         unsigned int *sptr = pfield;
277
278         for(i=0; i<PF_ROWS; i++) {
279                 for(j=0; j<PF_COLS; j++) {
280                         unsigned int val = *sptr++;
281
282                         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blkcolor[val & 7]);
283                         glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
284                         glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
285
286                         if(val & PF_FULL) {
287                                 glPushMatrix();
288                                 glTranslatef(j, -i, 0);
289                                 cmesh_draw(blkmesh);
290                                 glPopMatrix();
291                         }
292                 }
293         }
294 }
295
296
297 static void reshape(int x, int y)
298 {
299 }
300
301 static void keyboard(int key, int pressed)
302 {
303         /*char *name = 0;*/
304
305         if(!pressed) return;
306
307         switch(key) {
308         case 'a':
309                 if(!pause) {
310                         next_pos[1] = pos[1] - 1;
311                         if(collision(cur_block, next_pos)) {
312                                 next_pos[1] = pos[1];
313                         } else {
314                                 /*snd_shift();*/
315                         }
316                 }
317                 break;
318
319         case 'd':
320                 if(!pause) {
321                         next_pos[1] = pos[1] + 1;
322                         if(collision(cur_block, next_pos)) {
323                                 next_pos[1] = pos[1];
324                         } else {
325                                 /*snd_shift();*/
326                         }
327                 }
328                 break;
329
330         case 'w':
331                 if(!pause) {
332                         prev_rot = cur_rot;
333                         cur_rot = (cur_rot + 1) & 3;
334                         if(collision(cur_block, next_pos)) {
335                                 cur_rot = prev_rot;
336                         } else {
337                                 /*snd_rot();*/
338                         }
339                 }
340                 break;
341
342         case 's':
343                 /* ignore drops until the first update after a spawn */
344                 if(cur_block >= 0 && !just_spawned && !pause) {
345                         next_pos[0] = pos[0] + 1;
346                         if(collision(cur_block, next_pos)) {
347                                 next_pos[0] = pos[0];
348                                 update_cur_block();
349                                 stick(cur_block, next_pos);     /* stick immediately */
350                         }
351                 }
352                 break;
353
354         case '\n':
355         case '\t':
356                 if(!pause && cur_block >= 0) {
357                         next_pos[0] = pos[0] + 1;
358                         while(!collision(cur_block, next_pos)) {
359                                 next_pos[0]++;
360                         }
361                         next_pos[0]--;
362                         update_cur_block();
363                         stick(cur_block, next_pos);     /* stick immediately */
364                 }
365                 break;
366
367         case 'p':
368                 if(gameover) {
369                         /*
370                         if(score && is_highscore(score)) {
371                                 name = name_screen(score);
372                         }
373                         save_score(name, score, lines, level);
374                         */
375                         /* TODO: pop screen */
376                 } else {
377                         pause ^= 1;
378                 }
379                 break;
380
381         case '\b':
382                 /*
383                 if(score && is_highscore(score)) {
384                         name = name_screen(score);
385                 }
386                 save_score(name, score, lines, level);
387                 */
388                 /* TODO: pop screen */
389                 break;
390
391         default:
392                 break;
393         }
394 }
395
396 static void mouse(int bn, int pressed, int x, int y)
397 {
398         bnstate[bn] = pressed;
399         prev_mx = x;
400         prev_my = y;
401 }
402
403 static void motion(int x, int y)
404 {
405         float dx = x - prev_mx;
406         float dy = y - prev_my;
407         prev_mx = x;
408         prev_my = y;
409
410         if(bnstate[0]) {
411                 cam_theta += dx * 0.5;
412                 cam_phi += dy * 0.5;
413
414                 if(cam_phi < -90) cam_phi = -90;
415                 if(cam_phi > 90) cam_phi = 90;
416         }
417         if(bnstate[2]) {
418                 cam_dist += dy * 0.1;
419                 if(cam_dist < 0) cam_dist = 0;
420         }
421 }
422
423 static void wheel(int dir)
424 {
425 }
426
427 static void update_cur_block(void)
428 {
429         if(cur_block < 0) return;
430
431         memcpy(pos, next_pos, sizeof pos);
432         prev_rot = cur_rot;
433 }
434
435 static void addscore(int nlines)
436 {
437         static const int stab[] = {40, 100, 300, 1200}; /* bonus per line completed */
438
439         assert(nlines < 5);
440
441         score += stab[nlines - 1] * (level + 1);
442         lines += nlines;
443
444         level = lines / 10;
445         if(level > NUM_LEVELS - 1) level = NUM_LEVELS - 1;
446
447         tick_interval = level_speed[level];
448 }
449
450 static int spawn(void)
451 {
452         int r, tries = 2;
453
454         do {
455                 r = rand() % NUM_BLOCKS;
456         } while(tries-- > 0 && (r | prev_block | next_block) == prev_block);
457
458         cur_block = next_block;
459         next_block = r;
460
461         prev_rot = cur_rot = 0;
462         pos[0] = block_spawnpos[cur_block][0];
463         next_pos[0] = pos[0] + 1;
464         pos[1] = next_pos[1] = PF_COLS / 2 + block_spawnpos[cur_block][1];
465
466         if(collision(cur_block, next_pos)) {
467                 return -1;
468         }
469
470         just_spawned = 1;
471         return 0;
472 }
473
474 static int collision(int block, const int *pos)
475 {
476         int i;
477         unsigned char *p = blocks[block][cur_rot];
478
479         for(i=0; i<4; i++) {
480                 int x = pos[1] + BLKX(*p);
481                 int y = pos[0] + BLKY(*p);
482                 p++;
483
484                 if(y < 0) continue;
485
486                 if(x < 0 || x >= PF_COLS || y >= PF_ROWS) return 1;
487                 if(pfield[y * PF_COLS + x] & PF_FULL) return 1;
488         }
489
490         return 0;
491 }
492
493 static void stick(int block, const int *pos)
494 {
495         int i, j, nblank;
496         unsigned int *pfline;
497         unsigned char *p = blocks[block][cur_rot];
498
499         num_complines = 0;
500         prev_block = cur_block; /* used by the spawn routine */
501         cur_block = -1;
502
503         for(i=0; i<4; i++) {
504                 int x = pos[1] + BLKX(*p);
505                 int y = pos[0] + BLKY(*p);
506                 p++;
507
508                 pfline = pfield + y * PF_COLS;
509                 pfline[x] = PF_FULL | PF_VIS | block;
510
511                 nblank = 0;
512                 for(j=0; j<PF_COLS; j++) {
513                         if(!(pfline[j] & PF_FULL)) {
514                                 nblank++;
515                         }
516                 }
517
518                 if(nblank == 0) {
519                         complines[num_complines++] = y;
520                 }
521         }
522
523         /*snd_stick();*/
524
525         if(num_complines) {
526                 addscore(num_complines);
527         }
528 }
529
530 static void erase_completed(void)
531 {
532         int i, j, srow, drow;
533         unsigned int *pfstart = pfield;
534         unsigned int *dptr;
535
536         /* sort completed lines from highest to lowest row number */
537         for(i=0; i<num_complines-1; i++) {
538                 for(j=i+1; j<num_complines; j++) {
539                         if(complines[j] > complines[i]) {
540                                 int tmp = complines[j];
541                                 complines[j] = complines[i];
542                                 complines[i] = tmp;
543                         }
544                 }
545         }
546
547         srow = drow = PF_ROWS - 1;
548         dptr = pfstart + drow * PF_COLS;
549
550         for(i=0; i<PF_ROWS; i++) {
551                 for(j=0; j<num_complines; j++) {
552                         if(complines[j] == srow) {
553                                 srow--;
554                         }
555                 }
556
557                 if(srow < 0) {
558                         for(j=0; j<PF_COLS; j++) {
559                                 dptr[j] &= ~PF_FULL;
560                         }
561
562                 } else if(srow != drow) {
563                         unsigned int *sptr = pfstart + srow * PF_COLS;
564                         memcpy(dptr, sptr, PF_COLS * sizeof *dptr);
565                 }
566
567                 srow--;
568                 drow--;
569                 dptr -= PF_COLS;
570         }
571 }
572