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