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