20 int init_starfield(void);
21 void draw_starfield(void);
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);
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);
44 struct game_screen game_screen = {
61 static struct cmesh *blkmesh, *wellmesh;
62 static unsigned int tex_well;
64 static struct dtx_font *scorefont;
66 static float cam_theta, cam_phi, cam_dist;
67 static float cam_height;
68 static unsigned int bnstate;
69 static int prev_mx, prev_my;
71 static long tick_interval;
73 /* dimensions of the playfield */
79 #define PF_VIS_SHIFT 9
81 static unsigned int pfield[PF_ROWS * PF_COLS];
83 static int pos[2], next_pos[2];
84 static int cur_block, next_block, prev_block;
85 static int cur_rot, prev_rot;
86 static int complines[4];
87 static int num_complines;
90 static int score, level, lines;
91 static int just_spawned;
94 static int vrbn_a = 0, vrbn_x = 4;
95 static float vrscale = 40.0f;
99 static const long level_speed[NUM_LEVELS] = {
100 887, 820, 753, 686, 619, 552, 469, 368, 285, 184,
101 167, 151, 134, 117, 107, 98, 88, 79, 69, 60, 50
104 static const float blkcolor[][4] = {
107 {0.65, 0.65, 1.0, 1},
115 #define GAMEOVER_FILL_RATE 50
118 static int init(void)
120 if(!(scorefont = dtx_open_font("data/score.font", 0))) {
121 error_log("failed to open score font\n");
124 dtx_prepare_range(scorefont, FONTSZ, 32, 127);
125 dtx_save_glyphmap("foo.ppm", dtx_get_glyphmap(scorefont, 0));
127 if(init_starfield() == -1) {
131 if(!(blkmesh = cmesh_alloc()) || cmesh_load(blkmesh, "data/noisecube.obj") == -1) {
132 error_log("failed to load block mesh\n");
136 if(!(wellmesh = cmesh_alloc()) || cmesh_load(wellmesh, "data/well.obj") == -1) {
137 error_log("failed to load well mesh\n");
141 if(!(tex_well = img_gltexture_load("data/grid.png"))) {
142 error_log("failed to load well texture\n");
149 static void cleanup(void)
152 cmesh_free(wellmesh);
153 glDeleteTextures(1, &tex_well);
154 dtx_close_font(scorefont);
157 static void start(void)
161 glClearColor(0.12, 0.12, 0.18, 1);
166 score = level = lines = 0;
167 tick_interval = level_speed[0];
170 next_block = rand() % NUM_BLOCKS;
172 memset(pfield, 0, PF_COLS * PF_ROWS * sizeof *pfield);
174 ginp_repeat(500, 75, GINP_LEFT | GINP_RIGHT | GINP_DOWN);
176 dtx_use_font(scorefont, FONTSZ);
185 int bn = goatvr_lookup_button("A");
186 if(bn >= 0) vrbn_a = bn;
188 bn = goatvr_lookup_button("X");
189 if(bn >= 0) vrbn_x = bn;
191 /* switch to VR-optimized camera parameters */
198 goatvr_set_units_scale(vrscale);
203 static void stop(void)
205 goatvr_set_units_scale(1.0f);
210 #define CHECK_BUTTON(idx, gbn) \
211 if(joy_bnstate & (1 << idx)) { \
212 ginp_bnstate |= gbn; \
215 static void update_input(float dtsec)
220 if(goatvr_invr() && (num_vr_sticks = goatvr_num_sticks()) > 0) {
223 goatvr_stick_pos(0, p);
224 p[1] *= 0.65; /* drops harder to trigger accidentally */
226 if(fabs(p[0]) > fabs(joy_axis[GPAD_LSTICK_X])) {
227 joy_axis[GPAD_LSTICK_X] = p[0];
229 if(fabs(p[1]) > fabs(joy_axis[GPAD_LSTICK_Y])) {
230 joy_axis[GPAD_LSTICK_Y] = -p[1];
233 if(goatvr_button_state(vrbn_a)) {
234 joy_bnstate |= 1 << GPAD_A;
236 if(goatvr_button_state(vrbn_x)) {
237 joy_bnstate |= 1 << GPAD_START;
239 if(goatvr_action(0, GOATVR_ACTION_TRIGGER) || goatvr_action(1, GOATVR_ACTION_TRIGGER)) {
240 joy_bnstate |= 1 << GPAD_UP;
243 #endif /* BUILD_VR */
248 if(joy_axis[GPAD_LSTICK_X] >= JTHRES) {
249 ginp_bnstate |= GINP_RIGHT;
250 } else if(joy_axis[GPAD_LSTICK_X] <= -JTHRES) {
251 ginp_bnstate |= GINP_LEFT;
254 if(joy_axis[GPAD_LSTICK_Y] >= JTHRES) {
255 ginp_bnstate |= GINP_DOWN;
256 } else if(joy_axis[GPAD_LSTICK_Y] <= -JTHRES) {
257 ginp_bnstate |= GINP_UP;
260 CHECK_BUTTON(GPAD_LEFT, GINP_LEFT);
261 CHECK_BUTTON(GPAD_RIGHT, GINP_RIGHT);
262 CHECK_BUTTON(GPAD_UP, GINP_UP);
263 CHECK_BUTTON(GPAD_DOWN, GINP_DOWN);
264 CHECK_BUTTON(GPAD_A, GINP_ROTATE);
265 CHECK_BUTTON(GPAD_START, GINP_PAUSE);
269 if(GINP_PRESS(GINP_LEFT)) {
270 game_keyboard('a', 1);
272 if(GINP_PRESS(GINP_RIGHT)) {
273 game_keyboard('d', 1);
275 if(GINP_PRESS(GINP_DOWN)) {
276 game_keyboard('s', 1);
278 if(GINP_PRESS(GINP_UP)) {
279 game_keyboard('\t', 1);
281 if(GINP_PRESS(GINP_ROTATE)) {
282 game_keyboard('w', 1);
284 if(GINP_PRESS(GINP_PAUSE)) {
285 game_keyboard('p', 1);
289 memset(joy_axis, 0, sizeof joy_axis);
294 static void update(float dtsec)
296 static long prev_tick;
302 prev_tick = time_msec;
305 dt = time_msec - prev_tick;
308 int i, row = PF_ROWS - gameover;
311 if(dt < GAMEOVER_FILL_RATE) {
316 ptr = pfield + row * PF_COLS;
317 for(i=0; i<PF_COLS; i++) {
318 *ptr++ = PF_VIS | PF_FULL | 7;
322 prev_tick = time_msec;
328 /* lines where completed, we're in blinking mode */
329 int i, j, blink = dt >> 8;
337 for(i=0; i<num_complines; i++) {
338 unsigned int *ptr = pfield + complines[i] * PF_COLS;
339 for(j=0; j<PF_COLS; j++) {
340 *ptr = (*ptr & ~PF_VIS) | ((blink & 1) << PF_VIS_SHIFT);
348 while(dt >= tick_interval) {
351 next_pos[0] = pos[0] + 1;
352 if(collision(cur_block, next_pos)) {
353 next_pos[0] = pos[0];
354 stick(cur_block, next_pos);
366 prev_tick = time_msec;
372 static void draw(void)
374 static const int nextblk_pos[] = {0, 0};
375 static const float lpos[] = {-1, 1, 6, 1};
378 glTranslatef(0, 0, -cam_dist);
379 glRotatef(cam_phi, 1, 0, 0);
380 glRotatef(cam_theta, 0, 1, 0);
381 glTranslatef(0, -cam_height, 0);
383 glLightfv(GL_LIGHT0, GL_POSITION, lpos);
387 glPushAttrib(GL_ENABLE_BIT);
388 glEnable(GL_COLOR_MATERIAL);
389 glBindTexture(GL_TEXTURE_2D, tex_well);
390 glEnable(GL_TEXTURE_2D);
391 //glDisable(GL_LIGHTING);
393 cmesh_draw(wellmesh);
396 /* center playfield */
398 glTranslatef(-PF_COLS / 2 + 0.5, PF_ROWS / 2 - 0.5, 0);
402 draw_block(cur_block, pos, cur_rot, 1.0f, 1.0f);
406 glPushAttrib(GL_ENABLE_BIT);
408 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
411 t = (float)time_msec / 1000.0f;
412 glTranslatef(-PF_COLS / 2 + 0.5 + PF_COLS + 3, PF_ROWS / 2 - 0.5, 0);
413 glTranslatef(1.5, -1, 0);
414 glRotatef(cos(t) * 8.0f, 1, 0, 0);
415 glRotatef(sin(t * 1.2f) * 10.0f, 0, 1, 0);
416 glTranslatef(-1.5, 1, 0);
417 draw_block(next_block, nextblk_pos, 0, 0.25f, 0.75f);
421 glPushAttrib(GL_ENABLE_BIT);
422 glDisable(GL_LIGHTING);
425 glTranslatef(-11, 6, 0);
426 glScalef(0.05, 0.05, 0.05);
430 glTranslatef(0, -dtx_line_height() * 1.5, 0);
432 glScalef(1.5, 1.5, 1.5);
433 dtx_printf("%d", score);
436 glTranslatef(0, -dtx_line_height() * 2, 0);
438 glTranslatef(0, -dtx_line_height() * 1.5, 0);
440 glScalef(1.5, 1.5, 1.5);
441 dtx_printf("%d", level);
444 glTranslatef(0, -dtx_line_height() * 2, 0);
446 glTranslatef(0, -dtx_line_height() * 1.5, 0);
448 glScalef(1.5, 1.5, 1.5);
449 dtx_printf("%d", lines);
456 static const float blkspec[] = {0.85, 0.85, 0.85, 1};
458 static void draw_block(int block, const int *pos, int rot, float sat, float alpha)
461 unsigned char *p = blocks[block][rot];
462 float col[4], hsv[3];
464 rgb_to_hsv(blkcolor[block][0], blkcolor[block][1], blkcolor[block][2],
465 hsv, hsv + 1, hsv + 2);
466 hsv_to_rgb(hsv[0], hsv[1] * sat, hsv[2], col, col + 1, col + 2);
469 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, col);
470 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
471 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
474 int x = pos[1] + BLKX(*p);
475 int y = pos[0] + BLKY(*p);
481 glTranslatef(x, -y, 0);
487 static void drawpf(void)
490 unsigned int *sptr = pfield;
492 for(i=0; i<PF_ROWS; i++) {
493 for(j=0; j<PF_COLS; j++) {
494 unsigned int val = *sptr++;
496 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blkcolor[val & 7]);
497 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
498 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
500 if((val & (PF_FULL | PF_VIS)) == (PF_FULL | PF_VIS)) {
502 glTranslatef(j, -i, 0);
511 static void reshape(int x, int y)
515 static void keyboard(int key, int pressed)
525 next_pos[1] = pos[1] - 1;
526 if(collision(cur_block, next_pos)) {
527 next_pos[1] = pos[1];
537 next_pos[1] = pos[1] + 1;
538 if(collision(cur_block, next_pos)) {
539 next_pos[1] = pos[1];
551 cur_rot = (cur_rot + 1) & 3;
552 if(collision(cur_block, next_pos)) {
562 /* ignore drops until the first update after a spawn */
563 if(cur_block >= 0 && !just_spawned && !pause) {
564 next_pos[0] = pos[0] + 1;
565 if(collision(cur_block, next_pos)) {
566 next_pos[0] = pos[0];
568 stick(cur_block, next_pos); /* stick immediately */
576 if(!pause && cur_block >= 0) {
577 next_pos[0] = pos[0] + 1;
578 while(!collision(cur_block, next_pos)) {
583 stick(cur_block, next_pos); /* stick immediately */
590 if(score && is_highscore(score)) {
591 name = name_screen(score);
593 save_score(name, score, lines, level);
595 /* TODO: pop screen */
603 if(score && is_highscore(score)) {
604 name = name_screen(score);
606 save_score(name, score, lines, level);
608 /* TODO: pop screen */
616 static void mouse(int bn, int pressed, int x, int y)
621 bnstate &= ~(1 << bn);
627 static void motion(int x, int y)
629 float dx = x - prev_mx;
630 float dy = y - prev_my;
635 cam_theta += dx * 0.5;
638 if(cam_phi < -90) cam_phi = -90;
639 if(cam_phi > 90) cam_phi = 90;
642 cam_height += dy * 0.1;
645 cam_dist += dy * 0.1;
646 if(cam_dist < 0) cam_dist = 0;
649 #ifdef DBG_PRINT_VIEW
651 debug_log("Camera params\n");
652 debug_log(" theta: %g phi: %g dist: %g height: %g\n", cam_theta,
653 cam_phi, cam_dist, cam_height);
658 static void wheel(int dir)
660 /* debug code, used to figure out the best scales */
662 vrscale += dir * 0.01;
663 if(vrscale < 0.01) vrscale = 0.01;
664 goatvr_set_units_scale(vrscale);
665 debug_log("VR scale: %g\n", vrscale);
669 static void update_cur_block(void)
671 if(cur_block < 0) return;
673 memcpy(pos, next_pos, sizeof pos);
677 static void addscore(int nlines)
679 static const int stab[] = {40, 100, 300, 1200}; /* bonus per line completed */
683 score += stab[nlines - 1] * (level + 1);
687 if(level > NUM_LEVELS - 1) level = NUM_LEVELS - 1;
689 tick_interval = level_speed[level];
692 static int spawn(void)
697 r = rand() % NUM_BLOCKS;
698 } while(tries-- > 0 && (r | prev_block | next_block) == prev_block);
700 cur_block = next_block;
703 prev_rot = cur_rot = 0;
704 pos[0] = block_spawnpos[cur_block][0];
705 next_pos[0] = pos[0] + 1;
706 pos[1] = next_pos[1] = PF_COLS / 2 + block_spawnpos[cur_block][1];
708 if(collision(cur_block, next_pos)) {
716 static int collision(int block, const int *pos)
719 unsigned char *p = blocks[block][cur_rot];
722 int x = pos[1] + BLKX(*p);
723 int y = pos[0] + BLKY(*p);
728 if(x < 0 || x >= PF_COLS || y >= PF_ROWS) return 1;
729 if(pfield[y * PF_COLS + x] & PF_FULL) return 1;
735 static void stick(int block, const int *pos)
738 unsigned int *pfline;
739 unsigned char *p = blocks[block][cur_rot];
742 prev_block = cur_block; /* used by the spawn routine */
746 int x = pos[1] + BLKX(*p);
747 int y = pos[0] + BLKY(*p);
750 pfline = pfield + y * PF_COLS;
751 pfline[x] = PF_FULL | PF_VIS | block;
754 for(j=0; j<PF_COLS; j++) {
755 if(!(pfline[j] & PF_FULL)) {
761 complines[num_complines++] = y;
768 addscore(num_complines);
772 static void erase_completed(void)
774 int i, j, srow, drow;
775 unsigned int *pfstart = pfield;
778 /* sort completed lines from highest to lowest row number */
779 for(i=0; i<num_complines-1; i++) {
780 for(j=i+1; j<num_complines; j++) {
781 if(complines[j] > complines[i]) {
782 int tmp = complines[j];
783 complines[j] = complines[i];
789 srow = drow = PF_ROWS - 1;
790 dptr = pfstart + drow * PF_COLS;
792 for(i=0; i<PF_ROWS; i++) {
793 for(j=0; j<num_complines; j++) {
794 if(complines[j] == srow) {
800 for(j=0; j<PF_COLS; j++) {
804 } else if(srow != drow) {
805 unsigned int *sptr = pfstart + srow * PF_COLS;
806 memcpy(dptr, sptr, PF_COLS * sizeof *dptr);