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