#include #include #include #include #include #include #include #include "pieces.h" #include "ui.h" #include "util.h" struct clearfull_data { int rows; int cols; }; struct clearfull_data clearfull(char* map, int mapH, int mapW) { // if a row or column is full, clear it // if a row and a column are full at the same time, clear both // which is why we need to scan all rows/columns before actually clearing them // returns total number of blocks cleared bool* rows_to_clear = malloc(mapH * sizeof(bool)); bool* cols_to_clear = malloc(mapW * sizeof(bool)); if (rows_to_clear == NULL || cols_to_clear == NULL) mallocfail(); int rtc_count = 0; int ctc_count = 0; // scan all rows and columns for (int r = 0; r < mapH; r++) { bool clear = true; for (int c = 0; c < mapW; c++) { if (map[r * mapW + c] == ' ') clear = false; } rows_to_clear[r] = clear; } for (int c = 0; c < mapW; c++) { bool clear = true; for (int r = 0; r < mapH; r++) { if (map[r * mapW + c] == ' ') clear = false; } cols_to_clear[c] = clear; } // clear rows and columns marked true in `{rows,columns}_to_clear` for (int r = 0; r < mapH; r++) { if (rows_to_clear[r]) { rtc_count++; for (int c = 0; c < mapW; c++) map[r * mapW + c] = ' '; } } for (int c = 0; c < mapW; c++) { if (cols_to_clear[c]) { ctc_count++; for (int r = 0; r < mapH; r++) map[r * mapW + c] = ' '; } } free(rows_to_clear); free(cols_to_clear); return (struct clearfull_data) {rtc_count, ctc_count}; } void sirtet(int H, int W, int P) { // init memory and game state char* map = malloc(H * W * sizeof(char)); if (map == NULL) mallocfail(); for (int b = 0; b < H * W; b++) { // fill map with blank blocks map[b] = ' '; } struct piece** hand = malloc(P * sizeof(struct piece*)); if (hand == NULL) mallocfail(); srand(time(NULL)); refillpieces(hand, P); bool over = false; int points = 0; int combo = 0; int max_combo = 0; // initialize ncurses setlocale(LC_ALL, ""); initscr(); clear(); noecho(); cbreak(); keypad(stdscr, true); curs_set(0); // set cursor invisible start_color(); init_pair(1, COLOR_GREEN, COLOR_BLACK); init_pair(2, COLOR_RED, COLOR_BLACK); // begin game loop while (!over) { clear(); printrect(0, 0, H, W); printmap(map, 1, 1, H, W); printhelp(4, 2 * W + 8); printstats(points, combo, 1, 2 * W + 8); // select piece and position bool confirmed = false; int pc_idx; struct piece* pc; for (int i = 0; i < P; i++) { // find first non-null piece if (hand[i] != NULL) { pc_idx = i; pc = hand[i]; break; } } int row = 0; int col = 0; while (!confirmed) { printmap(map, 1, 1, H, W); printpieces(hand, H + 2, 0, P, pc_idx); bool pos_placeable = placeable(map, pc, row, col, H, W); printpiece(pc, row + 1, col * 2 + 1, (pos_placeable ? 1 : 2)); // await user input int ch = getch(); switch (ch) { // enter: confirm choice case 10: if (pos_placeable) confirmed = true; break; // arrow keys: move piece on map case KEY_LEFT: case 'a': case 'h': if (col > 0) col--; break; case KEY_RIGHT: case 'd': case 'l': if (col + pc->w < W) col++; break; case KEY_UP: case 'w': case 'k': if (row > 0) row--; break; case KEY_DOWN: case 's': case 'j': if (row + pc->h < H) row++; break; // [ and ]: switch pieces case '[': for (int i = 0; i < P; i++) { // find previous non-null piece pc_idx = (pc_idx + P - 1) % P; if (hand[pc_idx] != NULL) { pc = hand[pc_idx]; // move piece inside boundary if necessary if (row + (pc->h) > H) row = H - pc->h; if (col + (pc->w) > W) col = W - pc->w; break; } } break; case ']': for (int i = 0; i < P; i++) { // find next non-null piece pc_idx = (pc_idx + 1) % P; if (hand[pc_idx] != NULL) { pc = hand[pc_idx]; if (row + (pc->h) > H) row = H - pc->h; if (col + (pc->w) > W) col = W - pc->w; break; } } break; // q: quit case 'q': confirmed = true; over = true; break; } } place(map, pc, row, col, W); points += pc->points; freepiece(pc); hand[pc_idx] = NULL; // clear full rows and columns struct clearfull_data cleared = clearfull(map, H, W); points += cleared.rows * W + cleared.cols * H - cleared.rows * cleared.cols; if (cleared.rows || cleared.cols) { combo += cleared.rows + cleared.cols; if (combo > max_combo) max_combo = combo; } else { combo = 0; } // if player has no piece left, refill bool pieces_left = false; for (int i = 0; i < P; i++) { if (hand[i] != NULL) { pieces_left = true; break; } } if (!pieces_left) refillpieces(hand, P); // game over if none of the pieces are placeable bool has_placeable = false; for (int i = 0; i < P; i++) { if (hand[i] == NULL) continue; for (int r = 0; r <= H - hand[i]->h; r++) { for (int c = 0; c <= W - hand[i]->w; c++) { if (placeable(map, hand[i], r, c, H, W)) has_placeable = true; } } } if (!has_placeable) { // game over printmap(map, 1, 1, H, W); printstats(points, combo, 1, 2 * W + 8); mvprintw(8, 2 * W + 8, "Game over"); mvprintw(9, 2 * W + 8, "Press any key to exit"); refresh(); while (getch() == 0); over = true; } } // free memory free(map); for (int i = 0; i < P; i++) { if (hand[i] != NULL) freepiece(hand[i]); } free(hand); // end ncurses endwin(); // print game stats printf("SIRTET\n"); printf("You scored %d points\n", points); printf("Max combo: %d\n", max_combo); } int main(int argc, char *argv[]) { int H = 8; // map height int W = 8; // map width int P = 3; // max # of pieces at hand // handle CLI arguments int opt = 0; while (opt != -1) { opt = getopt(argc, argv, "h:w:p:"); switch (opt) { case 'h': sscanf(optarg, "%d", &H); if (H < 5) { printf("Map height cannot be less than 5\n"); return 1; } break; case 'w': sscanf(optarg, "%d", &W); if (W < 5) { printf("Map width cannot be less than 5\n"); return 1; } break; case 'p': sscanf(optarg, "%d", &P); if (P < 1) { printf("You must wield at least one piece\n"); return 1; } break; } } sirtet(H, W, P); return 0; }