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