14 int init_starfield(void);
15 void draw_starfield(void);
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);
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);
38 struct game_screen game_screen = {
55 static struct cmesh *blkmesh, *wellmesh;
56 static unsigned int tex_well;
58 static float cam_theta, cam_phi, cam_dist = 30;
59 static int bnstate[16];
60 static int prev_mx, prev_my;
62 static long tick_interval;
64 /* dimensions of the playfield */
70 #define PF_VIS_SHIFT 9
72 static unsigned int pfield[PF_ROWS * PF_COLS];
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;
81 static int score, level, lines;
82 static int just_spawned;
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
90 static const float blkcolor[][4] = {
101 #define GAMEOVER_FILL_RATE 50
104 static int init(void)
106 if(init_starfield() == -1) {
110 if(!(blkmesh = cmesh_alloc()) || cmesh_load(blkmesh, "data/noisecube.obj") == -1) {
111 error_log("failed to load block mesh\n");
115 if(!(wellmesh = cmesh_alloc()) || cmesh_load(wellmesh, "data/well.obj") == -1) {
116 error_log("failed to load well mesh\n");
120 if(!(tex_well = img_gltexture_load("data/grid.png"))) {
121 error_log("failed to load well texture\n");
128 static void cleanup(void)
131 cmesh_free(wellmesh);
132 glDeleteTextures(1, &tex_well);
135 static void start(void)
142 score = level = lines = 0;
143 tick_interval = level_speed[0];
146 next_block = rand() % NUM_BLOCKS;
148 memset(pfield, 0, PF_COLS * PF_ROWS * sizeof *pfield);
150 ginp_repeat(500, 75, GINP_LEFT | GINP_RIGHT | GINP_DOWN);
153 static void stop(void)
159 #define CHECK_BUTTON(idx, gbn) \
160 if(joy_bnstate & (1 << idx)) { \
161 ginp_bnstate |= gbn; \
164 static void update_input(float dtsec)
168 if((num_vr_sticks = goatvr_num_sticks()) > 0) {
171 goatvr_stick_pos(0, p);
173 if(fabs(p[0]) > fabs(joy_axis[GPAD_LSTICK_X])) {
174 joy_axis[GPAD_LSTICK_X] = p[0];
176 if(fabs(p[1]) > fabs(joy_axis[GPAD_LSTICK_Y])) {
177 joy_axis[GPAD_LSTICK_Y] = p[1];
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;
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;
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);
205 if(GINP_PRESS(GINP_LEFT)) {
206 game_keyboard('a', 1);
208 if(GINP_PRESS(GINP_RIGHT)) {
209 game_keyboard('d', 1);
211 if(GINP_PRESS(GINP_DOWN)) {
212 game_keyboard('s', 1);
214 if(GINP_PRESS(GINP_UP)) {
215 game_keyboard('\t', 1);
217 if(GINP_PRESS(GINP_ROTATE)) {
218 game_keyboard('w', 1);
220 if(GINP_PRESS(GINP_PAUSE)) {
221 game_keyboard('p', 1);
225 static void update(float dtsec)
227 static long prev_tick;
233 prev_tick = time_msec;
236 dt = time_msec - prev_tick;
239 int i, row = PF_ROWS - gameover;
242 if(dt < GAMEOVER_FILL_RATE) {
247 ptr = pfield + row * PF_COLS;
248 for(i=0; i<PF_COLS; i++) {
249 *ptr++ = PF_VIS | PF_FULL | 7;
253 prev_tick = time_msec;
259 /* lines where completed, we're in blinking mode */
260 int i, j, blink = dt >> 8;
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);
279 while(dt >= tick_interval) {
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);
297 prev_tick = time_msec;
303 static void draw(void)
305 const float lpos[] = {-1, 1, 6, 1};
307 glTranslatef(0, 0, -cam_dist);
308 glRotatef(cam_phi, 1, 0, 0);
309 glRotatef(cam_theta, 0, 1, 0);
311 glLightfv(GL_LIGHT0, GL_POSITION, lpos);
315 glPushAttrib(GL_ENABLE_BIT);
316 glBindTexture(GL_TEXTURE_2D, tex_well);
317 glEnable(GL_TEXTURE_2D);
318 glDisable(GL_LIGHTING);
320 cmesh_draw(wellmesh);
323 /* center playfield */
325 glTranslatef(-PF_COLS / 2 + 0.5, PF_ROWS / 2 - 0.5, 0);
329 draw_block(cur_block, pos, cur_rot);
335 static const float blkspec[] = {0.85, 0.85, 0.85, 1};
337 static void draw_block(int block, const int *pos, int rot)
340 unsigned char *p = blocks[block][rot];
342 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blkcolor[block]);
343 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
344 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
347 int x = pos[1] + BLKX(*p);
348 int y = pos[0] + BLKY(*p);
354 glTranslatef(x, -y, 0);
360 static void drawpf(void)
363 unsigned int *sptr = pfield;
365 for(i=0; i<PF_ROWS; i++) {
366 for(j=0; j<PF_COLS; j++) {
367 unsigned int val = *sptr++;
369 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blkcolor[val & 7]);
370 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
371 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
373 if((val & (PF_FULL | PF_VIS)) == (PF_FULL | PF_VIS)) {
375 glTranslatef(j, -i, 0);
384 static void reshape(int x, int y)
388 static void keyboard(int key, int pressed)
398 next_pos[1] = pos[1] - 1;
399 if(collision(cur_block, next_pos)) {
400 next_pos[1] = pos[1];
410 next_pos[1] = pos[1] + 1;
411 if(collision(cur_block, next_pos)) {
412 next_pos[1] = pos[1];
424 cur_rot = (cur_rot + 1) & 3;
425 if(collision(cur_block, next_pos)) {
435 /* ignore drops until the first update after a spawn */
436 if(cur_block >= 0 && !just_spawned && !pause) {
437 next_pos[0] = pos[0] + 1;
438 if(collision(cur_block, next_pos)) {
439 next_pos[0] = pos[0];
441 stick(cur_block, next_pos); /* stick immediately */
449 if(!pause && cur_block >= 0) {
450 next_pos[0] = pos[0] + 1;
451 while(!collision(cur_block, next_pos)) {
456 stick(cur_block, next_pos); /* stick immediately */
463 if(score && is_highscore(score)) {
464 name = name_screen(score);
466 save_score(name, score, lines, level);
468 /* TODO: pop screen */
476 if(score && is_highscore(score)) {
477 name = name_screen(score);
479 save_score(name, score, lines, level);
481 /* TODO: pop screen */
489 static void mouse(int bn, int pressed, int x, int y)
491 bnstate[bn] = pressed;
496 static void motion(int x, int y)
498 float dx = x - prev_mx;
499 float dy = y - prev_my;
504 cam_theta += dx * 0.5;
507 if(cam_phi < -90) cam_phi = -90;
508 if(cam_phi > 90) cam_phi = 90;
511 cam_dist += dy * 0.1;
512 if(cam_dist < 0) cam_dist = 0;
516 static void wheel(int dir)
520 static void update_cur_block(void)
522 if(cur_block < 0) return;
524 memcpy(pos, next_pos, sizeof pos);
528 static void addscore(int nlines)
530 static const int stab[] = {40, 100, 300, 1200}; /* bonus per line completed */
534 score += stab[nlines - 1] * (level + 1);
538 if(level > NUM_LEVELS - 1) level = NUM_LEVELS - 1;
540 tick_interval = level_speed[level];
543 static int spawn(void)
548 r = rand() % NUM_BLOCKS;
549 } while(tries-- > 0 && (r | prev_block | next_block) == prev_block);
551 cur_block = next_block;
554 prev_rot = cur_rot = 0;
555 pos[0] = block_spawnpos[cur_block][0];
556 next_pos[0] = pos[0] + 1;
557 pos[1] = next_pos[1] = PF_COLS / 2 + block_spawnpos[cur_block][1];
559 if(collision(cur_block, next_pos)) {
567 static int collision(int block, const int *pos)
570 unsigned char *p = blocks[block][cur_rot];
573 int x = pos[1] + BLKX(*p);
574 int y = pos[0] + BLKY(*p);
579 if(x < 0 || x >= PF_COLS || y >= PF_ROWS) return 1;
580 if(pfield[y * PF_COLS + x] & PF_FULL) return 1;
586 static void stick(int block, const int *pos)
589 unsigned int *pfline;
590 unsigned char *p = blocks[block][cur_rot];
593 prev_block = cur_block; /* used by the spawn routine */
597 int x = pos[1] + BLKX(*p);
598 int y = pos[0] + BLKY(*p);
601 pfline = pfield + y * PF_COLS;
602 pfline[x] = PF_FULL | PF_VIS | block;
605 for(j=0; j<PF_COLS; j++) {
606 if(!(pfline[j] & PF_FULL)) {
612 complines[num_complines++] = y;
619 addscore(num_complines);
623 static void erase_completed(void)
625 int i, j, srow, drow;
626 unsigned int *pfstart = pfield;
629 /* sort completed lines from highest to lowest row number */
630 for(i=0; i<num_complines-1; i++) {
631 for(j=i+1; j<num_complines; j++) {
632 if(complines[j] > complines[i]) {
633 int tmp = complines[j];
634 complines[j] = complines[i];
640 srow = drow = PF_ROWS - 1;
641 dptr = pfstart + drow * PF_COLS;
643 for(i=0; i<PF_ROWS; i++) {
644 for(j=0; j<num_complines; j++) {
645 if(complines[j] == srow) {
651 for(j=0; j<PF_COLS; j++) {
655 } else if(srow != drow) {
656 unsigned int *sptr = pfstart + srow * PF_COLS;
657 memcpy(dptr, sptr, PF_COLS * sizeof *dptr);