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