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