/* game.c The functions relevant to the game itself. */ #include #include #include #include #include #include #include "bastet.h" #include "config.h" #include "game.h" #include "main.h" #include "bast.h" #include void bast_clear(); const DOT block_data[BLOCK_TYPES][BLOCK_ORIENTS][BLOCK_DOTS] = { { {{2,0,1},{2,1,1},{2,2,1},{2,3,1}}, /* */ {{0,1,1},{1,1,1},{2,1,1},{3,1,1}}, /* */ {{2,0,1},{2,1,1},{2,2,1},{2,3,1}}, /* XXXX */ {{0,1,1},{1,1,1},{2,1,1},{3,1,1}} /* */ }, { {{1,1,2},{2,1,2},{1,2,2},{2,2,2}}, /* */ {{1,1,2},{2,1,2},{1,2,2},{2,2,2}}, /* XX */ {{1,1,2},{2,1,2},{1,2,2},{2,2,2}}, /* XX */ {{1,1,2},{2,1,2},{1,2,2},{2,2,2}} /* */ }, { {{1,0,3},{1,1,3},{1,2,3},{2,2,3}}, /* */ {{2,0,3},{0,1,3},{1,1,3},{2,1,3}}, /* XXX */ {{0,0,3},{1,0,3},{1,1,3},{1,2,3}}, /* X */ {{0,1,3},{1,1,3},{2,1,3},{0,2,3}} /* */ }, { {{1,0,4},{2,0,4},{1,1,4},{1,2,4}}, /* */ {{0,0,4},{0,1,4},{1,1,4},{2,1,4}}, /* XXX */ {{1,0,4},{1,1,4},{0,2,4},{1,2,4}}, /* X */ {{0,1,4},{1,1,4},{2,1,4},{2,2,4}} /* */ }, { {{1,0,5},{1,1,5},{2,1,5},{2,2,5}}, /* */ {{1,0,5},{2,0,5},{0,1,5},{1,1,5}}, /* XX */ {{1,0,5},{1,1,5},{2,1,5},{2,2,5}}, /* XX */ {{1,0,5},{2,0,5},{0,1,5},{1,1,5}} /* */ }, { {{2,0,6},{1,1,6},{2,1,6},{1,2,6}}, /* */ {{0,0,6},{1,0,6},{1,1,6},{2,1,6}}, /* XX */ {{2,0,6},{1,1,6},{2,1,6},{1,2,6}}, /* XX */ {{0,0,6},{1,0,6},{1,1,6},{2,1,6}} /* */ }, { {{1,0,7},{1,1,7},{2,1,7},{1,2,7}}, /* X */ {{1,0,7},{0,1,7},{1,1,7},{2,1,7}}, /* XXX */ {{1,0,7},{0,1,7},{1,1,7},{1,2,7}}, /* */ {{0,1,7},{1,1,7},{2,1,7},{1,2,7}} /* */ } }; /* This is a pointer to the well data which will be allocated later. The block of data pointed to is a simple (y,x)-coordinate system. At each point is stored a color (0 means there's space for new blocks). */ unsigned char *well_data; /* Delay before block drop one step down (usec). We start at one sec. and then decreases the delay by 23% at each level. */ const int delay[NO_LEVELS] = {999999, 770000, 593000, 457000, 352000, 271000, 208000, 160000, 124000, 95000}; /* Window in which the action takes place. */ WINDOW *well_win; /* Convert (y, x)-coordinates in well to a pointer. */ unsigned char *yx2pointer(int y, int x) { return well_data + (y * WELL_WIDTH) + x; } /* Used to plot the blocks. Each point is two characters wide - this looks better on my terminal. */ void draw_block(WINDOW *win, int y, int x, int type, int orient, char erase) { int i; DOT dot; /* We have BLOCK_DOTS points to paste on the screen. Read each DOT, get color and draw it relative to y and x. */ for (i = 0; i < BLOCK_DOTS; i++) { dot = block_data[type][orient][i]; wattrset(win, COLOR_PAIR(erase? 0 : dot.color)); mvwprintw(win, y + dot.y, 2*(x + dot.x), " "); } /* An erase operation will most likely (always) be followed by a new drawing. To avoid flickering, we don't update the screen in this case. */ if (0 == erase) wrefresh(win); } /* This redraws the well window with data in well_data. */ void update_well(int start, int lines) { int y, x; for (y = start; y < start + lines; y++) { wmove(well_win, y, 0); for (x = 0; x < WELL_WIDTH; x++) { wattrset(well_win, COLOR_PAIR(*yx2pointer(y, x))); mvwprintw(well_win, y, 2 * x, " "); } } wrefresh(well_win); } /* Check if a position for a block is valid, i.e. it doesn't overlap a point which is already occupied or goes outside the well borders. */ int check_block_pos(int y, int x, int type, int orient) { int i; DOT dot; for (i = 0; i < BLOCK_DOTS; i++) { dot = block_data[type][orient][i]; if ((y + dot.y > WELL_HEIGHT - 1) || (x + dot.x < 0) || (x + dot.x > WELL_WIDTH - 1) || (*yx2pointer(y + dot.y, x + dot.x) > 0) ) return 0; } return 1; } /* Put the points a block occupies into well_data and update. */ void set_block(int y, int x, int type, int orient) { int i; DOT dot; for (i = 0; i < BLOCK_DOTS; i++) { dot = block_data[type][orient][i]; *yx2pointer(y + dot.y, x + dot.x) = dot.color; } update_well(y, BLOCK_DOTS); } /* Helper for check_lines(). Visualize a new line by some "nice effects". */ void visualize_lines(int start, short line_stat) { int i, y; short tmp; /* For comparison with line_stat. */ wattrset(well_win, COLOR_PAIR(0)); for (i = 0; i < 6; i++) { tmp = 0x0001; for (y = BLOCK_DOTS - 1; y >= 0; y--) { if (line_stat & tmp) { wmove(well_win, start + y, 0); whline(well_win, i % 2? ' ' : ':', WELL_WIDTH * 2); } tmp <<= 1; } wrefresh(well_win); usleep(500000 / 6); } } /* Another helper function. Remove lines specified in line_stat (relative to start) from well_data. Then update display. */ void remove_lines(int start, short line_stat) { unsigned char *tmp_well; short tmp; /* Used for comparison with line_stat. */ int y; int lines = 0; /* No. of full lines. */ tmp_well=malloc(WELL_HEIGHT * WELL_WIDTH); memset(tmp_well, 0, BLOCK_DOTS * WELL_WIDTH); /* Copy part below the point where thing may change. */ memcpy(tmp_well + (start + BLOCK_DOTS) * WELL_WIDTH, well_data + (start + BLOCK_DOTS) * WELL_WIDTH, (WELL_HEIGHT - start - BLOCK_DOTS) * WELL_WIDTH); /* Copy the lines that are not full. */ /* We check from bottom and upwards because this makes it easier to take care op moving (not full) lines down. */ tmp = 0x0001; for (y = BLOCK_DOTS - 1; y >= 0; y--) { if (!(line_stat & tmp)) { memcpy(tmp_well + (start + y + lines) * WELL_WIDTH, well_data + (start + y) * WELL_WIDTH, WELL_WIDTH); } else lines++; tmp <<= 1; } /* Copy the rest, i.e. the upper part of the well. */ memcpy(tmp_well + (lines * WELL_WIDTH), well_data, WELL_WIDTH * start); memcpy(well_data, tmp_well, WELL_HEIGHT * WELL_WIDTH); update_well(0, WELL_HEIGHT); } /* Check for complete lines. Update display and well_data. Return number of point obtained. We check from line start to start + BLOCK_DOTS - 1. */ POINTS check_lines(int start) { int y, x; short line; short line_stat = 0; /* Each bit indicates the status for a tested line. If set line is full. Lower bit is lower line. */ POINTS points; points.points = 0; points.lines = 0; /* Avoid reading beyond end of well_data. */ if (start > WELL_HEIGHT - BLOCK_DOTS) start = WELL_HEIGHT - BLOCK_DOTS; for (y = start; y < start + BLOCK_DOTS; y++) { line_stat <<= 1; line = TRUE; for (x = 0; x < WELL_WIDTH; x++) if (*yx2pointer(y, x) == 0) { line = FALSE; break; } if (TRUE == line) { if (0 == line_stat) /* First line. */ points.points = 50; else /* Multiply points by two. */ points.points *= 2; points.lines++; line_stat = line_stat | 0x0001; } } if (line_stat) { visualize_lines(start, line_stat); remove_lines(start, line_stat); } return points; } void update_stat(POINTS points, int block) { static int prev_block = 0; /* This is preserved between calls and make us able to erase last block. */ draw_block(stdscr, STAT_Y + 2, STAT_X / 2 + 2 , prev_block, 0, 1); draw_block(stdscr, STAT_Y + 2, STAT_X / 2 + 2 , block, 0, 0); attrset(COLOR_PAIR(COLOR_POINTS)); mvprintw(STAT_Y + BLOCK_DOTS + 3, STAT_X + 2, "Points: %d", points.points); attrset(COLOR_PAIR(COLOR_LINES)); mvprintw(STAT_Y + BLOCK_DOTS + 5, STAT_X + 2, "Lines: %d", points.lines); attrset(COLOR_PAIR(COLOR_LEVEL)); mvprintw(STAT_Y + BLOCK_DOTS + 7, STAT_X + 2, "Level: %d", points.level); prev_block = block; refresh(); } struct timeval mytimeout; /* Drop a block in the well. When done return y-cord. of where block ended. If it's not possible to even start with a new block return -1. */ int drop_block(int type, int level) { int defx = WELL_WIDTH / 2 - BLOCK_DOTS / 2; int y = 0; int x = defx; int orient = 0; int ch; fd_set inputs, test_fds; int sel_ret; if (0 == check_block_pos(y, x, type, orient)) return -1; /* Oh no, game over. */ mytimeout.tv_sec = 0; mytimeout.tv_usec = delay[level]; FD_ZERO(&inputs); FD_SET(0, &inputs); if (ch != ERR) while (getch() != ERR); draw_block(well_win, y, x, type, orient, 0); while(1) { test_fds = inputs; sel_ret = select(FD_SETSIZE, &test_fds, (fd_set *) 0, (fd_set *) 0, &mytimeout); ch = getch(); switch (ch) { case CONTROL_LEFT: if (check_block_pos(y, x - 1, type, orient)) { draw_block(well_win, y, x, type, orient, 1); draw_block(well_win, y, --x, type, orient, 0); } break; case CONTROL_RIGHT: if (check_block_pos(y, x + 1, type, orient)) { draw_block(well_win, y, x, type, orient, 1); draw_block(well_win, y, ++x, type, orient, 0); } break; case CONTROL_ROT_CW: if (check_block_pos(y, x, type, orient + 1 == BLOCK_ORIENTS? 0 : orient + 1)) { draw_block(well_win, y, x, type, orient, 1); ++orient == BLOCK_ORIENTS? orient = 0: 0; draw_block(well_win, y, x, type, orient, 0); } break; case CONTROL_ROT_CCW: if (check_block_pos(y, x, type, orient - 1 == -1? BLOCK_ORIENTS -1 : orient - 1)) { draw_block(well_win, y, x, type, orient, 1); --orient == -1? orient = BLOCK_ORIENTS - 1: 0; draw_block(well_win, y, x, type, orient, 0); } break; case CONTROL_DOWN: sel_ret = 0; break; case CONTROL_REFRESH: /* Refresh well. */ update_screen(); update_well(0, WELL_HEIGHT); draw_block(well_win, y, x, type, orient, 0); break; case CONTROL_DROP: draw_block(well_win,y,x,type,orient,1); while(check_block_pos(y+1,x,type,orient)) y++; set_block(y,x,type,orient); return y; break; case CONTROL_QUIT: return -1; break; case CONTROL_PAUSE: { // int c; wclear(well_win); wattrset(well_win,COLOR_PAIR(0)); mvwaddstr(well_win,WELL_HEIGHT/2,WELL_WIDTH-7,"Press any key"); mvwaddstr(well_win,WELL_HEIGHT/2+1,WELL_WIDTH-6,"to continue"); wrefresh(well_win); nodelay(stdscr,FALSE); getch(); nodelay(stdscr,TRUE); update_well(0,WELL_HEIGHT); draw_block(well_win, y, x, type, orient, 0); wrefresh(well_win); } } /* If time has expired we need to move block down. Check if block-pos. is ok if we move it down. If it's not, paste block in well and return. */ if (0 == sel_ret) { if (check_block_pos(y + 1, x, type, orient)) { draw_block(well_win, y, x, type, orient, 1); draw_block(well_win, ++y, x, type, orient, 0); } else { set_block(y, x, type, orient); return y; } mytimeout.tv_sec = 0; mytimeout.tv_usec = delay[level]; } } } int bl_next=0; /* Plays a single game. That is: Drop a block. Test for new lines. Update score. Repeat until game over. */ POINTS play_game(int level) { POINTS points; POINTS tmp; int cur; int y; /* y-cord. of last-placed block. */ /* Create window used in game. */ well_win = newwin(WELL_HEIGHT, 2 * WELL_WIDTH, WELL_Y, WELL_X); /* Clear and reset. */ well_data = (unsigned char *)malloc(WELL_HEIGHT * WELL_WIDTH); memset(well_data, 0, WELL_HEIGHT * WELL_WIDTH); update_screen(); wclear(well_win); bast_init(); points.points = 0; points.lines = 0; points.level = level; cur=choose_piece(); update_stat(points, bl_next); while(1) { y = drop_block(cur, points.level); if (y >= 0) { tmp = check_lines(y); points.points += (tmp.points + points.level); points.lines += tmp.lines; if (points.lines / 10 > level && points.lines / 10 < NO_LEVELS) points.level = points.lines / 10; cur=choose_piece(); update_stat(points, bl_next); } else { message("Game over!"); break; } } bast_clear(); free(well_data); delwin(well_win); return points; }