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