12 int init_starfield(void);
13 void draw_starfield(void);
15 static int init(void);
16 static void cleanup(void);
17 static void start(void);
18 static void stop(void);
19 static void update(float dt);
20 static void draw(void);
21 static void draw_block(int block, const int *pos, int rot);
22 static void drawpf(void);
23 static void reshape(int x, int y);
24 static void keyboard(int key, int pressed);
25 static void mouse(int bn, int pressed, int x, int y);
26 static void motion(int x, int y);
27 static void wheel(int dir);
29 static void update_cur_block(void);
30 static void addscore(int nlines);
31 static int spawn(void);
32 static int collision(int block, const int *pos);
33 static void stick(int block, const int *pos);
34 static void erase_completed(void);
36 struct game_screen game_screen = {
53 static struct cmesh *blkmesh, *wellmesh;
54 static unsigned int tex_well;
56 static float cam_theta, cam_phi, cam_dist = 30;
57 static int bnstate[16];
58 static int prev_mx, prev_my;
60 static long tick_interval;
62 /* dimensions of the playfield */
68 #define PF_VIS_SHIFT 9
70 static unsigned int pfield[PF_ROWS * PF_COLS];
72 static int pos[2], next_pos[2];
73 static int cur_block, next_block, prev_block;
74 static int cur_rot, prev_rot;
75 static int complines[4];
76 static int num_complines;
79 static int score, level, lines;
80 static int just_spawned;
83 static const long level_speed[NUM_LEVELS] = {
84 887, 820, 753, 686, 619, 552, 469, 368, 285, 184,
85 167, 151, 134, 117, 107, 98, 88, 79, 69, 60, 50
88 static const float blkcolor[][4] = {
99 #define GAMEOVER_FILL_RATE 50
102 static int init(void)
104 if(init_starfield() == -1) {
108 if(!(blkmesh = cmesh_alloc()) || cmesh_load(blkmesh, "data/noisecube.obj") == -1) {
109 error_log("failed to load block mesh\n");
113 if(!(wellmesh = cmesh_alloc()) || cmesh_load(wellmesh, "data/well.obj") == -1) {
114 error_log("failed to load well mesh\n");
118 if(!(tex_well = img_gltexture_load("data/grid.png"))) {
119 error_log("failed to load well texture\n");
126 static void cleanup(void)
129 cmesh_free(wellmesh);
130 glDeleteTextures(1, &tex_well);
133 static void start(void)
140 score = level = lines = 0;
141 tick_interval = level_speed[0];
144 next_block = rand() % NUM_BLOCKS;
146 memset(pfield, 0, PF_COLS * PF_ROWS * sizeof *pfield);
149 static void stop(void)
153 static void update(float dtsec)
155 static long prev_tick;
159 prev_tick = time_msec;
162 dt = time_msec - prev_tick;
165 int i, row = PF_ROWS - gameover;
168 if(dt < GAMEOVER_FILL_RATE) {
173 ptr = pfield + row * PF_COLS;
174 for(i=0; i<PF_COLS; i++) {
175 *ptr++ = PF_VIS | PF_FULL | 7;
179 prev_tick = time_msec;
185 /* lines where completed, we're in blinking mode */
186 int i, j, blink = dt >> 8;
194 for(i=0; i<num_complines; i++) {
195 unsigned int *ptr = pfield + complines[i] * PF_COLS;
196 for(j=0; j<PF_COLS; j++) {
197 *ptr = (*ptr & ~PF_VIS) | ((blink & 1) << PF_VIS_SHIFT);
205 while(dt >= tick_interval) {
208 next_pos[0] = pos[0] + 1;
209 if(collision(cur_block, next_pos)) {
210 next_pos[0] = pos[0];
211 stick(cur_block, next_pos);
223 prev_tick = time_msec;
229 static void draw(void)
231 const float lpos[] = {-1, 1, 6, 1};
233 glTranslatef(0, 0, -cam_dist);
234 glRotatef(cam_phi, 1, 0, 0);
235 glRotatef(cam_theta, 0, 1, 0);
237 glLightfv(GL_LIGHT0, GL_POSITION, lpos);
241 glPushAttrib(GL_ENABLE_BIT);
242 glBindTexture(GL_TEXTURE_2D, tex_well);
243 glEnable(GL_TEXTURE_2D);
244 glDisable(GL_LIGHTING);
246 cmesh_draw(wellmesh);
249 /* center playfield */
251 glTranslatef(-PF_COLS / 2 + 0.5, PF_ROWS / 2 - 0.5, 0);
255 draw_block(cur_block, pos, cur_rot);
261 static const float blkspec[] = {0.85, 0.85, 0.85, 1};
263 static void draw_block(int block, const int *pos, int rot)
266 unsigned char *p = blocks[block][rot];
268 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blkcolor[block]);
269 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
270 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
273 int x = pos[1] + BLKX(*p);
274 int y = pos[0] + BLKY(*p);
280 glTranslatef(x, -y, 0);
286 static void drawpf(void)
289 unsigned int *sptr = pfield;
291 for(i=0; i<PF_ROWS; i++) {
292 for(j=0; j<PF_COLS; j++) {
293 unsigned int val = *sptr++;
295 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blkcolor[val & 7]);
296 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, blkspec);
297 glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50.0f);
299 if((val & (PF_FULL | PF_VIS)) == (PF_FULL | PF_VIS)) {
301 glTranslatef(j, -i, 0);
310 static void reshape(int x, int y)
314 static void keyboard(int key, int pressed)
323 next_pos[1] = pos[1] - 1;
324 if(collision(cur_block, next_pos)) {
325 next_pos[1] = pos[1];
334 next_pos[1] = pos[1] + 1;
335 if(collision(cur_block, next_pos)) {
336 next_pos[1] = pos[1];
346 cur_rot = (cur_rot + 1) & 3;
347 if(collision(cur_block, next_pos)) {
356 /* ignore drops until the first update after a spawn */
357 if(cur_block >= 0 && !just_spawned && !pause) {
358 next_pos[0] = pos[0] + 1;
359 if(collision(cur_block, next_pos)) {
360 next_pos[0] = pos[0];
362 stick(cur_block, next_pos); /* stick immediately */
369 if(!pause && cur_block >= 0) {
370 next_pos[0] = pos[0] + 1;
371 while(!collision(cur_block, next_pos)) {
376 stick(cur_block, next_pos); /* stick immediately */
383 if(score && is_highscore(score)) {
384 name = name_screen(score);
386 save_score(name, score, lines, level);
388 /* TODO: pop screen */
396 if(score && is_highscore(score)) {
397 name = name_screen(score);
399 save_score(name, score, lines, level);
401 /* TODO: pop screen */
409 static void mouse(int bn, int pressed, int x, int y)
411 bnstate[bn] = pressed;
416 static void motion(int x, int y)
418 float dx = x - prev_mx;
419 float dy = y - prev_my;
424 cam_theta += dx * 0.5;
427 if(cam_phi < -90) cam_phi = -90;
428 if(cam_phi > 90) cam_phi = 90;
431 cam_dist += dy * 0.1;
432 if(cam_dist < 0) cam_dist = 0;
436 static void wheel(int dir)
440 static void update_cur_block(void)
442 if(cur_block < 0) return;
444 memcpy(pos, next_pos, sizeof pos);
448 static void addscore(int nlines)
450 static const int stab[] = {40, 100, 300, 1200}; /* bonus per line completed */
454 score += stab[nlines - 1] * (level + 1);
458 if(level > NUM_LEVELS - 1) level = NUM_LEVELS - 1;
460 tick_interval = level_speed[level];
463 static int spawn(void)
468 r = rand() % NUM_BLOCKS;
469 } while(tries-- > 0 && (r | prev_block | next_block) == prev_block);
471 cur_block = next_block;
474 prev_rot = cur_rot = 0;
475 pos[0] = block_spawnpos[cur_block][0];
476 next_pos[0] = pos[0] + 1;
477 pos[1] = next_pos[1] = PF_COLS / 2 + block_spawnpos[cur_block][1];
479 if(collision(cur_block, next_pos)) {
487 static int collision(int block, const int *pos)
490 unsigned char *p = blocks[block][cur_rot];
493 int x = pos[1] + BLKX(*p);
494 int y = pos[0] + BLKY(*p);
499 if(x < 0 || x >= PF_COLS || y >= PF_ROWS) return 1;
500 if(pfield[y * PF_COLS + x] & PF_FULL) return 1;
506 static void stick(int block, const int *pos)
509 unsigned int *pfline;
510 unsigned char *p = blocks[block][cur_rot];
513 prev_block = cur_block; /* used by the spawn routine */
517 int x = pos[1] + BLKX(*p);
518 int y = pos[0] + BLKY(*p);
521 pfline = pfield + y * PF_COLS;
522 pfline[x] = PF_FULL | PF_VIS | block;
525 for(j=0; j<PF_COLS; j++) {
526 if(!(pfline[j] & PF_FULL)) {
532 complines[num_complines++] = y;
539 addscore(num_complines);
543 static void erase_completed(void)
545 int i, j, srow, drow;
546 unsigned int *pfstart = pfield;
549 /* sort completed lines from highest to lowest row number */
550 for(i=0; i<num_complines-1; i++) {
551 for(j=i+1; j<num_complines; j++) {
552 if(complines[j] > complines[i]) {
553 int tmp = complines[j];
554 complines[j] = complines[i];
560 srow = drow = PF_ROWS - 1;
561 dptr = pfstart + drow * PF_COLS;
563 for(i=0; i<PF_ROWS; i++) {
564 for(j=0; j<num_complines; j++) {
565 if(complines[j] == srow) {
571 for(j=0; j<PF_COLS; j++) {
575 } else if(srow != drow) {
576 unsigned int *sptr = pfstart + srow * PF_COLS;
577 memcpy(dptr, sptr, PF_COLS * sizeof *dptr);