17 int init_starfield(void);
18 void draw_starfield(void);
20 static int init(void);
21 static void cleanup(void);
22 static void start(void);
23 static void stop(void);
24 static void update(float dt);
25 static void draw(void);
26 static void draw_block(int block, const int *pos, int rot, float sat, float alpha);
27 static void drawpf(void);
28 static void reshape(int x, int y);
29 static void keyboard(int key, int pressed);
30 static void mouse(int bn, int pressed, int x, int y);
31 static void motion(int x, int y);
32 static void wheel(int dir);
34 static void update_cur_block(void);
35 static void addscore(int nlines);
36 static int spawn(void);
37 static int collision(int block, const int *pos);
38 static void stick(int block, const int *pos);
39 static void erase_completed(void);
41 struct game_screen game_screen = {
58 static struct cmesh *blkmesh, *wellmesh;
59 static unsigned int tex_well;
61 static float cam_theta, cam_phi, cam_dist = 30;
62 static int bnstate[16];
63 static int prev_mx, prev_my;
65 static long tick_interval;
67 /* dimensions of the playfield */
73 #define PF_VIS_SHIFT 9
75 static unsigned int pfield[PF_ROWS * PF_COLS];
77 static int pos[2], next_pos[2];
78 static int cur_block, next_block, prev_block;
79 static int cur_rot, prev_rot;
80 static int complines[4];
81 static int num_complines;
84 static int score, level, lines;
85 static int just_spawned;
88 static const long level_speed[NUM_LEVELS] = {
89 887, 820, 753, 686, 619, 552, 469, 368, 285, 184,
90 167, 151, 134, 117, 107, 98, 88, 79, 69, 60, 50
93 static const float blkcolor[][4] = {
104 #define GAMEOVER_FILL_RATE 50
107 static int init(void)
109 if(init_starfield() == -1) {
113 if(!(blkmesh = cmesh_alloc()) || cmesh_load(blkmesh, "data/noisecube.obj") == -1) {
114 error_log("failed to load block mesh\n");
118 if(!(wellmesh = cmesh_alloc()) || cmesh_load(wellmesh, "data/well.obj") == -1) {
119 error_log("failed to load well mesh\n");
123 if(!(tex_well = img_gltexture_load("data/grid.png"))) {
124 error_log("failed to load well texture\n");
131 static void cleanup(void)
134 cmesh_free(wellmesh);
135 glDeleteTextures(1, &tex_well);
138 static void start(void)
145 score = level = lines = 0;
146 tick_interval = level_speed[0];
149 next_block = rand() % NUM_BLOCKS;
151 memset(pfield, 0, PF_COLS * PF_ROWS * sizeof *pfield);
153 ginp_repeat(500, 75, GINP_LEFT | GINP_RIGHT | GINP_DOWN);
156 static void stop(void)
162 #define CHECK_BUTTON(idx, gbn) \
163 if(joy_bnstate & (1 << idx)) { \
164 ginp_bnstate |= gbn; \
167 static void update_input(float dtsec)
172 if((num_vr_sticks = goatvr_num_sticks()) > 0) {
175 goatvr_stick_pos(0, p);
177 if(fabs(p[0]) > fabs(joy_axis[GPAD_LSTICK_X])) {
178 joy_axis[GPAD_LSTICK_X] = p[0];
180 if(fabs(p[1]) > fabs(joy_axis[GPAD_LSTICK_Y])) {
181 joy_axis[GPAD_LSTICK_Y] = p[1];
184 #endif /* BUILD_VR */
189 if(joy_axis[GPAD_LSTICK_X] >= JTHRES) {
190 ginp_bnstate |= GINP_RIGHT;
191 } else if(joy_axis[GPAD_LSTICK_X] <= -JTHRES) {
192 ginp_bnstate |= GINP_LEFT;
195 if(joy_axis[GPAD_LSTICK_Y] >= JTHRES) {
196 ginp_bnstate |= GINP_DOWN;
197 } else if(joy_axis[GPAD_LSTICK_Y] <= -JTHRES) {
198 ginp_bnstate |= GINP_UP;
201 CHECK_BUTTON(GPAD_LEFT, GINP_LEFT);
202 CHECK_BUTTON(GPAD_RIGHT, GINP_RIGHT);
203 CHECK_BUTTON(GPAD_UP, GINP_UP);
204 CHECK_BUTTON(GPAD_DOWN, GINP_DOWN);
205 CHECK_BUTTON(GPAD_A, GINP_ROTATE);
206 CHECK_BUTTON(GPAD_START, GINP_PAUSE);
210 if(GINP_PRESS(GINP_LEFT)) {
211 game_keyboard('a', 1);
213 if(GINP_PRESS(GINP_RIGHT)) {
214 game_keyboard('d', 1);
216 if(GINP_PRESS(GINP_DOWN)) {
217 game_keyboard('s', 1);
219 if(GINP_PRESS(GINP_UP)) {
220 game_keyboard('\t', 1);
222 if(GINP_PRESS(GINP_ROTATE)) {
223 game_keyboard('w', 1);
225 if(GINP_PRESS(GINP_PAUSE)) {
226 game_keyboard('p', 1);
230 static void update(float dtsec)
232 static long prev_tick;
238 prev_tick = time_msec;
241 dt = time_msec - prev_tick;
244 int i, row = PF_ROWS - gameover;
247 if(dt < GAMEOVER_FILL_RATE) {
252 ptr = pfield + row * PF_COLS;
253 for(i=0; i<PF_COLS; i++) {
254 *ptr++ = PF_VIS | PF_FULL | 7;
258 prev_tick = time_msec;
264 /* lines where completed, we're in blinking mode */
265 int i, j, blink = dt >> 8;
273 for(i=0; i<num_complines; i++) {
274 unsigned int *ptr = pfield + complines[i] * PF_COLS;
275 for(j=0; j<PF_COLS; j++) {
276 *ptr = (*ptr & ~PF_VIS) | ((blink & 1) << PF_VIS_SHIFT);
284 while(dt >= tick_interval) {
287 next_pos[0] = pos[0] + 1;
288 if(collision(cur_block, next_pos)) {
289 next_pos[0] = pos[0];
290 stick(cur_block, next_pos);
302 prev_tick = time_msec;
308 static void draw(void)
310 static const int nextblk_pos[] = {0, 0};
311 static const float lpos[] = {-1, 1, 6, 1};
314 glTranslatef(0, 0, -cam_dist);
315 glRotatef(cam_phi, 1, 0, 0);
316 glRotatef(cam_theta, 0, 1, 0);
318 glLightfv(GL_LIGHT0, GL_POSITION, lpos);
322 glPushAttrib(GL_ENABLE_BIT);
323 glBindTexture(GL_TEXTURE_2D, tex_well);
324 glEnable(GL_TEXTURE_2D);
325 glDisable(GL_LIGHTING);
327 cmesh_draw(wellmesh);
330 /* center playfield */
332 glTranslatef(-PF_COLS / 2 + 0.5, PF_ROWS / 2 - 0.5, 0);
336 draw_block(cur_block, pos, cur_rot, 1.0f, 1.0f);
340 glPushAttrib(GL_ENABLE_BIT);
342 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
345 t = (float)time_msec / 1000.0f;
346 glTranslatef(-PF_COLS / 2 + 0.5 + PF_COLS + 3, PF_ROWS / 2 - 0.5, 0);
347 glTranslatef(1.5, -1, 0);
348 glRotatef(cos(t) * 8.0f, 1, 0, 0);
349 glRotatef(sin(t * 1.2f) * 10.0f, 0, 1, 0);
350 glTranslatef(-1.5, 1, 0);
351 draw_block(next_block, nextblk_pos, 0, 0.25f, 0.75f);
357 static const float blkspec[] = {0.85, 0.85, 0.85, 1};
359 static void draw_block(int block, const int *pos, int rot, float sat, float alpha)
362 unsigned char *p = blocks[block][rot];
363 float col[4], hsv[3];
365 rgb_to_hsv(blkcolor[block][0], blkcolor[block][1], blkcolor[block][2],
366 hsv, hsv + 1, hsv + 2);
367 hsv_to_rgb(hsv[0], hsv[1] * sat, hsv[2], col, col + 1, col + 2);
370 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, col);
371 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
372 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
375 int x = pos[1] + BLKX(*p);
376 int y = pos[0] + BLKY(*p);
382 glTranslatef(x, -y, 0);
388 static void drawpf(void)
391 unsigned int *sptr = pfield;
393 for(i=0; i<PF_ROWS; i++) {
394 for(j=0; j<PF_COLS; j++) {
395 unsigned int val = *sptr++;
397 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blkcolor[val & 7]);
398 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
399 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
401 if((val & (PF_FULL | PF_VIS)) == (PF_FULL | PF_VIS)) {
403 glTranslatef(j, -i, 0);
412 static void reshape(int x, int y)
416 static void keyboard(int key, int pressed)
426 next_pos[1] = pos[1] - 1;
427 if(collision(cur_block, next_pos)) {
428 next_pos[1] = pos[1];
438 next_pos[1] = pos[1] + 1;
439 if(collision(cur_block, next_pos)) {
440 next_pos[1] = pos[1];
452 cur_rot = (cur_rot + 1) & 3;
453 if(collision(cur_block, next_pos)) {
463 /* ignore drops until the first update after a spawn */
464 if(cur_block >= 0 && !just_spawned && !pause) {
465 next_pos[0] = pos[0] + 1;
466 if(collision(cur_block, next_pos)) {
467 next_pos[0] = pos[0];
469 stick(cur_block, next_pos); /* stick immediately */
477 if(!pause && cur_block >= 0) {
478 next_pos[0] = pos[0] + 1;
479 while(!collision(cur_block, next_pos)) {
484 stick(cur_block, next_pos); /* stick immediately */
491 if(score && is_highscore(score)) {
492 name = name_screen(score);
494 save_score(name, score, lines, level);
496 /* TODO: pop screen */
504 if(score && is_highscore(score)) {
505 name = name_screen(score);
507 save_score(name, score, lines, level);
509 /* TODO: pop screen */
517 static void mouse(int bn, int pressed, int x, int y)
519 bnstate[bn] = pressed;
524 static void motion(int x, int y)
526 float dx = x - prev_mx;
527 float dy = y - prev_my;
532 cam_theta += dx * 0.5;
535 if(cam_phi < -90) cam_phi = -90;
536 if(cam_phi > 90) cam_phi = 90;
539 cam_dist += dy * 0.1;
540 if(cam_dist < 0) cam_dist = 0;
544 static void wheel(int dir)
548 static void update_cur_block(void)
550 if(cur_block < 0) return;
552 memcpy(pos, next_pos, sizeof pos);
556 static void addscore(int nlines)
558 static const int stab[] = {40, 100, 300, 1200}; /* bonus per line completed */
562 score += stab[nlines - 1] * (level + 1);
566 if(level > NUM_LEVELS - 1) level = NUM_LEVELS - 1;
568 tick_interval = level_speed[level];
571 static int spawn(void)
576 r = rand() % NUM_BLOCKS;
577 } while(tries-- > 0 && (r | prev_block | next_block) == prev_block);
579 cur_block = next_block;
582 prev_rot = cur_rot = 0;
583 pos[0] = block_spawnpos[cur_block][0];
584 next_pos[0] = pos[0] + 1;
585 pos[1] = next_pos[1] = PF_COLS / 2 + block_spawnpos[cur_block][1];
587 if(collision(cur_block, next_pos)) {
595 static int collision(int block, const int *pos)
598 unsigned char *p = blocks[block][cur_rot];
601 int x = pos[1] + BLKX(*p);
602 int y = pos[0] + BLKY(*p);
607 if(x < 0 || x >= PF_COLS || y >= PF_ROWS) return 1;
608 if(pfield[y * PF_COLS + x] & PF_FULL) return 1;
614 static void stick(int block, const int *pos)
617 unsigned int *pfline;
618 unsigned char *p = blocks[block][cur_rot];
621 prev_block = cur_block; /* used by the spawn routine */
625 int x = pos[1] + BLKX(*p);
626 int y = pos[0] + BLKY(*p);
629 pfline = pfield + y * PF_COLS;
630 pfline[x] = PF_FULL | PF_VIS | block;
633 for(j=0; j<PF_COLS; j++) {
634 if(!(pfline[j] & PF_FULL)) {
640 complines[num_complines++] = y;
647 addscore(num_complines);
651 static void erase_completed(void)
653 int i, j, srow, drow;
654 unsigned int *pfstart = pfield;
657 /* sort completed lines from highest to lowest row number */
658 for(i=0; i<num_complines-1; i++) {
659 for(j=i+1; j<num_complines; j++) {
660 if(complines[j] > complines[i]) {
661 int tmp = complines[j];
662 complines[j] = complines[i];
668 srow = drow = PF_ROWS - 1;
669 dptr = pfstart + drow * PF_COLS;
671 for(i=0; i<PF_ROWS; i++) {
672 for(j=0; j<num_complines; j++) {
673 if(complines[j] == srow) {
679 for(j=0; j<PF_COLS; j++) {
683 } else if(srow != drow) {
684 unsigned int *sptr = pfstart + srow * PF_COLS;
685 memcpy(dptr, sptr, PF_COLS * sizeof *dptr);