diff --git a/gridutils.c b/gridutils.c new file mode 100644 index 0000000..e434c6d --- /dev/null +++ b/gridutils.c @@ -0,0 +1,119 @@ +#include "tetris.h" + +void grid_set(bitgrid_t* grid, int x, int y, bool value){ + if(x < 0 || x >= TOTAL_WIDTH || y < 0 || y >= TOTAL_HEIGHT) return; + + int bitIndex = y * TOTAL_WIDTH + x; + int wordIndex = bitIndex / 32; + int bitPosition = bitIndex % 32; + + if(value){ + grid->bits[wordIndex] |= (1U << bitPosition); + } else { + grid->bits[wordIndex] &= ~(1U << bitPosition); + } +} + +void grid_get(bitgrid_t* grid, int x, int y, bool* value) { + if(x < 0 || x >= TOTAL_WIDTH || y < 0 || y >= TOTAL_HEIGHT) { + *value = false; + return; + } + + int bitIndex = y * TOTAL_WIDTH + x; + int wordIndex = bitIndex / 32; + int bitPosition = bitIndex % 32; + + *value = (grid->bits[wordIndex] & (1U << bitPosition)) != 0; +} + +bool grid_equals(bitgrid_t* a, bitgrid_t* b){ + for(int i = 0; i < GRID_WORDS; i++){ + if(a->bits[i] != b->bits[i]){ + return false; + } + } + return true; +} + +bitgrid_t grid_copy(bitgrid_t* src){ + bitgrid_t result; + for(int i = 0; i < GRID_WORDS; i++){ + result.bits[i] = src->bits[i]; + } + return result; +} + +void bitgrid_bitwise_and(bitgrid_t* dest, bitgrid_t* a, bitgrid_t* b){ + for(int i = 0; i < GRID_WORDS; i++){ + dest->bits[i] = a->bits[i] & b->bits[i]; + } +} + +void bitgrid_bitwise_or(bitgrid_t* dest, bitgrid_t* a, bitgrid_t* b){ + for(int i = 0; i < GRID_WORDS; i++){ + dest->bits[i] = a->bits[i] | b->bits[i]; + } +} + +void bitgrid_bitwise_xor(bitgrid_t* dest, bitgrid_t* a, bitgrid_t* b){ + for(int i = 0; i < GRID_WORDS; i++){ + dest->bits[i] = a->bits[i] ^ b->bits[i]; + } +} + +void bitgrid_bitwise_not(bitgrid_t* dest, bitgrid_t* src){ + for(int i = 0; i < GRID_WORDS; i++){ + dest->bits[i] = ~(src->bits[i]); + } +} + +void bitgrid_bitwise_shift_left(bitgrid_t* dest, bitgrid_t* src, int shift){ + if(shift < 0) return; // Invalid shift + if(shift == 0){ + *dest = *src; + return; + } + + int totalBits = TOTAL_WIDTH * TOTAL_HEIGHT; + if(shift >= totalBits){ + for(int i = 0; i < GRID_WORDS; i++){ + dest->bits[i] = 0; + } + return; + } + + int wordShift = shift / 32; + int bitShift = shift % 32; + + for(int i = GRID_WORDS - 1; i >= 0; i--){ + uint32_t upper = (i - wordShift - 1 >= 0 && bitShift != 0) ? (src->bits[i - wordShift - 1] >> (32 - bitShift)) : 0; + uint32_t lower = (i - wordShift >= 0) ? (src->bits[i - wordShift] << bitShift) : 0; + dest->bits[i] = upper | lower; + } +} + +void bitgrid_bitwise_shift_right(bitgrid_t* dest, bitgrid_t* src, int shift){ + if(shift < 0) return; // Invalid shift + if(shift == 0){ + *dest = *src; + return; + } + + int totalBits = TOTAL_WIDTH * TOTAL_HEIGHT; + if(shift >= totalBits){ + for(int i = 0; i < GRID_WORDS; i++){ + dest->bits[i] = 0; + } + return; + } + + int wordShift = shift / 32; + int bitShift = shift % 32; + + for(int i = 0; i < GRID_WORDS; i++){ + uint32_t lower = (i + wordShift + 1 < GRID_WORDS && bitShift != 0) ? (src->bits[i + wordShift + 1] << (32 - bitShift)) : 0; + uint32_t upper = (i + wordShift < GRID_WORDS) ? (src->bits[i + wordShift] >> bitShift) : 0; + dest->bits[i] = upper | lower; + } +} diff --git a/tetris.c b/tetris.c new file mode 100644 index 0000000..5688f58 --- /dev/null +++ b/tetris.c @@ -0,0 +1,568 @@ +// Tetris in C using raylib +#include "tetris.h" + +// Game state +unsigned char board[BOARD_HEIGHT][BOARD_WIDTH] = {0}; +unsigned int iBoardMask[BOARD_HEIGHT] = {0}; // bitmask for each row +unsigned int iPieceMask[BOARD_HEIGHT + PIECE_BUFFER_OFFSET] = {0}; // bitmask for piece position +bool board_mask[BOARD_HEIGHT][BOARD_WIDTH] = {0}; +bool piece_mask[BOARD_HEIGHT+PIECE_BUFFER_OFFSET][BOARD_WIDTH] = {0}; +char piece_rotation = 0; +// Piece shapes[] = { T_PIECE, O_PIECE, Z_PIECE, S_PIECE, I_PIECE, J_PIECE, L_PIECE}; + + + +// Main function +int main(){ + printf("Let's play some tetris motherfucker"); + InitWindow(BOARD_WIDTH*GRID_SCREEN_PIXELS, BOARD_HEIGHT*GRID_SCREEN_PIXELS, "yaa"); + SetTargetFPS(60); + zero_board(); + init_game(); + while(1){ + BeginDrawing(); + update(); + draw(); + EndDrawing(); + if(WindowShouldClose()) break; + } + cleanup_shapes(); + + CloseWindow(); + return 0; +} + + +// Initialize shapes array properly +Piece shapes[7]; +bool shapes_initialized = false; +Piece* current_piece = NULL; + +void init_shapes() { + if(shapes_initialized) return; + + shapes[0] = T_PIECE; + shapes[1] = O_PIECE; + shapes[2] = Z_PIECE; + shapes[3] = S_PIECE; + shapes[4] = I_PIECE; + shapes[5] = J_PIECE; + shapes[6] = L_PIECE; + + shapes_initialized = true; +} + +void cleanup_shapes() { + for(int i = 0; i < 7; i++) { + free_shape(shapes[i].shape, shapes[i].dims.y); + } +} + +void init_game(){ + init_shapes(); + zero_board(); + zero_board_mask(); + zero_piece_mask(); + spawn_piece(); +} + +// Function implementations + +void zero_board(){ + for(int i = 0; i < BOARD_HEIGHT; i++){ + for(int j = 0; j < BOARD_WIDTH; j++){ + board[i][j] = 0; + } + } +} + +void zero_board_mask(){ + for(int iter_height = 0; iter_height < BOARD_HEIGHT; iter_height++){ + for(int iter_width = 0; iter_width < BOARD_WIDTH; iter_width++){ + board_mask[iter_height][iter_width] = false; + } + } + +} + +void zero_piece_mask(){ + for(int iter_height = 0; iter_height < BOARD_HEIGHT + PIECE_BUFFER_OFFSET; iter_height++){ + for(int iter_width = 0; iter_width < BOARD_WIDTH; iter_width++){ + piece_mask[iter_height][iter_width] = false; + } + } +} + +void populate_board_mask(){ + for(int i = 0; i < BOARD_HEIGHT; i++){ + for(int j = 0; j < BOARD_WIDTH; j++){ + if(board[i][j] != 0){ + board_mask[i][j] = true; + } else { + board_mask[i][j] = false; + } + } + } +} + +bool next_tick_valid(){ + populate_board_mask(); + for(int iter_height = 0; iter_height < BOARD_HEIGHT; iter_height++) + { + for(int iter_width = 0; iter_width < BOARD_WIDTH; iter_width++) + { + if( board_mask[iter_height][iter_width] && + piece_mask[iter_height-1+PIECE_BUFFER_OFFSET][iter_width] ) + { + return false; + } + } + } + return true; +} + +bool piece_tick(){ + + if(!next_tick_valid()){ + return 0; + } + // Check if piece can go down + // We should convert this to a simpler check where the booleans are converted to a single binary value and compared + for(int k = 0; k < BOARD_WIDTH; k++){ + if(piece_mask[BOARD_HEIGHT-1 + PIECE_BUFFER_OFFSET][k]){ + return 0; // piece would go out of bounds + } + } + // Move piece down + for(int j = BOARD_HEIGHT - 1 + PIECE_BUFFER_OFFSET; j >= 0; j--){ + for(int i = 0; i < BOARD_WIDTH; i++){ + if(piece_mask[j][i]){ + piece_mask[j+1][i] = piece_mask[j][i]; // This somehow doesn't crash out of bounds? + piece_mask[j][i] = 0; + } + } + } + return 1; +} + +void place_piece(){ + for(int i = 0; i < BOARD_HEIGHT+PIECE_BUFFER_OFFSET; i++){ + for(int j = 0; j < BOARD_WIDTH; j++){ + if(piece_mask[i][j]){ + board[i-PIECE_BUFFER_OFFSET][j] = current_piece->color; + } + } + } + populate_board_mask(); +} +void spawn_piece(){ + current_piece = &shapes[GetRandomValue(0, 6)]; + piece_rotation = 0; + // position piece at top of board + // calculate piece_mask based on shape and position + Vector2* bounds = get_shape_bounds(); + + int spawn_x = (BOARD_WIDTH / 2) - (int)bounds[0].x; // center horizontally, adjust for shape bounds + int spawn_y = 2 + (int)bounds[0].y; + + + for(int i = 0; i < current_piece->dims.y; i++){ + for(int j = 0; j < current_piece->dims.x; j++){ + if(current_piece->shape[i][j] == 1){ + int board_x = spawn_x + j; + int board_y = spawn_y + i; + if(board_x >= 0 && board_x < BOARD_WIDTH && board_y >= 0 && board_y < BOARD_HEIGHT){ + piece_mask[board_y][board_x] = true; + } + } + } + } +} +// void Vector2Rotate(Vector2* v, float angle){ +// if (v == NULL) return; +// float rad = angle * (3.14159265 / 180.0); +// float cosA = cos(rad); +// float sinA = sin(rad); +// float x_new = v->x * cosA - v->y * sinA; +// float y_new = v->x * sinA + v->y * cosA; +// v->x = x_new; +// v->y = y_new; +// } +void Vector2Rotate(Vector2* v, float angle){ + if (v == NULL) return; + float rad = angle * (PI / 180.0); + float cosA = cosf(rad); + float sinA = sinf(rad); + + // Store original values + float orig_x = v->x; + float orig_y = v->y; + + // Calculate new values using original coordinates + v->x = orig_x * cosA - orig_y * sinA; + v->y = orig_x * sinA + orig_y * cosA; +} + +Vector2 Vector2Subtract(Vector2* v1, Vector2* v2){ + Vector2 result = (Vector2) {0,0}; + result.x = v1->x - v2->x; + result.y = v1->y - v2->y; + return result; +} + +void debug_draw_blocks_on_axis(Vector2 pblock_vecs[4], Color color){ + bool debugDrawing = 1; + int numVecs = sizeof(pblock_vecs) / sizeof(pblock_vecs[0]); + Vector2 block_vecs[numVecs]; + #ifdef NDEBUG + int axis_zero_x = BOARD_WIDTH / 2; + int axis_zero_y = BOARD_HEIGHT / 2; + BeginDrawing(); + // + // Draw axis + for(int i = -5; i <= 5; i++){ + debugDraw(axis_zero_x + i, axis_zero_y, (Color){255,255,0,255}); + debugDraw(axis_zero_x, axis_zero_y + i, (Color){255,255,0,255}); + } + + for(int i = 0; i < numVecs; i++){ + debugDraw((int)(block_vecs[i].x)+axis_zero_x, (int)(block_vecs[i].y+axis_zero_y), (Color) color); + } + + asm("nop"); // debug breakpoint + + draw(); // reset + EndDrawing(); + #endif +} +void debug_draw_blocks(Vector2 block_vecs[1], Color color){ + bool debugDrawing = 1; + int numVecs = sizeof(block_vecs) / sizeof(block_vecs[0]); + #ifdef NDEBUG + BeginDrawing(); + + for(int i = 0; i < numVecs; i++){ + debugDraw((int)(block_vecs[i].x), (int)(block_vecs[i].y), (Color) color); + } + + asm("nop"); // debug breakpoint + + draw(); // reset + EndDrawing(); + #endif +} + + +void turn_piece(bool clockwise){ + if(!current_piece || !current_piece->has_origin){ + return; + } + + Piece* cpiece = current_piece; // Assuming current_piece is already Piece* + Vector2 block_vecs[4] = {{0,0},{0,0},{0,0},{0,0}}; + + // Verified fixed + get_vecs_from_shape(cpiece, block_vecs); + + + asm("nop"); // debug breakpoint + + // rotate blocks from shape data to match currently tracked orientation + // somehow this turns a J piece into a T piece. wtf + // Apply current rotation incrementally to avoid precision errors + for(int rotation_step = 0; rotation_step < piece_rotation; rotation_step++){ + for(int i = 0; i < 4; i++){ + Vector2Rotate(&block_vecs[i], 90); // Always rotate by 90° + block_vecs[i].x = roundf(block_vecs[i].x); // Round instead of truncate + block_vecs[i].y = roundf(block_vecs[i].y); + } + } + + debug_draw_blocks_on_axis(block_vecs, (Color){255,255,255,255}); + + // sort blocks, also broken + sort_blocks(block_vecs); + + debug_draw_blocks_on_axis(block_vecs, (Color){255,255,255,255}); + + asm("nop"); // debug breakpoint + + Vector2 topleftmost_block = find_topleftmost_block_from_buffer(); + Vector2 origin_on_piece_mask = Vector2Subtract(&topleftmost_block,&block_vecs[0]); + + debug_draw_blocks((Vector2[1]){(Vector2){(int)origin_on_piece_mask.x, (int)origin_on_piece_mask.y-PIECE_BUFFER_OFFSET}}, (Color){255,255,255,255}); + // clear piece mask + // try to rotate to new position now + Vector2 test_vecs[4]; + memcpy(test_vecs, block_vecs, sizeof(block_vecs)); + for(int i = 0; i < 4; i++){ + Vector2Rotate(&test_vecs[i], 90 * (clockwise ? 1 : -1)); + int new_y = (float)(int)(test_vecs[i].y + origin_on_piece_mask.y); + int new_x = (float)(int)(test_vecs[i].x + origin_on_piece_mask.x); + if(new_x < 0 || new_x >= BOARD_WIDTH){ + return; // out of bounds + } + if(new_y < 0 || new_y >= BOARD_HEIGHT + PIECE_BUFFER_OFFSET){ + return; // out of bounds + } + if(board_mask[new_y - PIECE_BUFFER_OFFSET][new_x]){ + return; // collision + } + test_vecs[i].x = roundf(test_vecs[i].x); + test_vecs[i].y = roundf(test_vecs[i].y); + } + zero_piece_mask(); + for(int i = 0; i < 4; i++){ + Vector2 vec = test_vecs[i]; + piece_mask[(int)(vec.y + origin_on_piece_mask.y)][(int)(vec.x + origin_on_piece_mask.x)] = true; + } + // update piece rotation tracker + piece_rotation += clockwise ? 1 : -1; + if(piece_rotation > 3) piece_rotation = 0; + if(piece_rotation < 0) piece_rotation = 3; + +} + +Vector2* get_shape_bounds(){ + static Vector2 bounds[2]; // [0] = origin--> top-left, [1] = bottom-right + Piece* cpiece = current_piece; + Vector2* origin = &cpiece->origin; + Vector2* dims = &cpiece->dims; + + bounds[0].x = 0 - origin->x; + bounds[0].y = 0 - origin->y; + bounds[1].x = (dims->x -1) - origin->x; + bounds[1].y = (dims->y -1) - origin->y; + + return bounds; +} + +void get_vecs_from_shape(Piece* cpiece, Vector2* blocks){ + Vector2* dims = get_shape_dimensions(current_piece); + + // Allocate memory for temporary shape copy + int** shape = malloc(dims->y * sizeof(int*)); + for(int i = 0; i < dims->y; i++){ + shape[i] = malloc(dims->x * sizeof(int)); + } + + // Copy the current piece shape + for(int i = 0; i < dims->y; i++){ + memcpy(shape[i], cpiece->shape[i], dims->x *sizeof(int)); + } + + Vector2* origin = ¤t_piece->origin; + + int block_count = 0; + + for(int i = 0; i < dims->y; i++){ + for(int j = 0; j < dims->x; j++){ + if (shape[i][j] == 1 && block_count < 4) { + blocks[block_count].x = j - origin->x; + blocks[block_count].y = i - origin->y; + block_count++; + } + } + } + + return; +} + +// Sort blocks by y, then by x +void sort_blocks(Vector2* blocks){ + for(int i = 0; i < 4; i++){ + for(int j = i + 1; j < 4; j++){ + if(blocks[j].y < blocks[i].y || (blocks[j].y == blocks[i].y && blocks[j].x < blocks[i].x)){ + Vector2 tmp = blocks[i]; + blocks[i] = blocks[j]; + blocks[j] = tmp; + } + } + } +} + +Vector2 find_topleftmost_block_from_buffer(){ + for (int i = 0; i < BOARD_HEIGHT + PIECE_BUFFER_OFFSET; i++){ + for (int j = 0; j < BOARD_WIDTH; j++){ + if(piece_mask[i][j]){ + return (Vector2) {(float)j,(float)i}; + } + } + } +} + + +void game_tick(){ + if(piece_tick()){ + return; + } + place_piece(); + zero_piece_mask(); + populate_board_mask(); + spawn_piece(); +} + +Color resolve_color(unsigned char colorValue){ + switch(colorValue){ + case PC_CYAN: return (Color) {0x00, 0xFF, 0xFF, 0xFF}; + case PC_YELLOW: return (Color) {0xFF, 0xFF, 0x00, 0xFF}; + case PC_PURPLE: return (Color) {0x80, 0x00, 0x80, 0xFF}; + case PC_GREEN: return (Color) {0x00, 0xFF, 0x00, 0xFF}; + case PC_RED: return (Color) {0xFF, 0x00, 0x00, 0xFF}; + case PC_BLUE: return (Color) {0x00, 0x00, 0xFF, 0xFF}; + case PC_ORANGE: return (Color) {0xFF, 0xA5, 0x00, 0xFF}; + default: return (Color) {0xFF, 0xFF, 0xFF, 0xFF}; + } +} + +void debugDraw(int x, int y, Color color){ + #ifndef NDEBUG + return; + #endif + DrawRectangle(x * GRID_SCREEN_PIXELS + 1 * PIXEL_SCALE, y * GRID_SCREEN_PIXELS + 1 * PIXEL_SCALE, BLOCK_SIZE, BLOCK_SIZE, color); +} + +void createSecondWindow(){ + +} + +void draw(){ + ClearBackground(RAYWHITE); + for(int i = 0; i < BOARD_HEIGHT; i++){ + for(int j = 0; j < BOARD_WIDTH; j++){ + if(piece_mask[i+PIECE_BUFFER_OFFSET][j]){ + unsigned char pieceColorValue = current_piece->color; + Color drawColor = resolve_color(pieceColorValue); + DrawRectangle(j * GRID_SCREEN_PIXELS + 1 * PIXEL_SCALE, i * GRID_SCREEN_PIXELS + 1 * PIXEL_SCALE, BLOCK_SIZE, BLOCK_SIZE, drawColor); + continue; + } + if(board[i][j] != 0){ + unsigned char boardColorValue = board[i][j]; + Color drawColor = resolve_color(boardColorValue); + DrawRectangle(j * GRID_SCREEN_PIXELS + 1 * PIXEL_SCALE, i * GRID_SCREEN_PIXELS + 1 * PIXEL_SCALE, BLOCK_SIZE, BLOCK_SIZE, drawColor); + continue; + } + // background + DrawRectangle(j * GRID_SCREEN_PIXELS, i * GRID_SCREEN_PIXELS, GRID_SCREEN_PIXELS, GRID_SCREEN_PIXELS, COLOR_GRID_FG); + DrawRectangle(j * GRID_SCREEN_PIXELS + 1 * PIXEL_SCALE, i * GRID_SCREEN_PIXELS + 1 * PIXEL_SCALE, BLOCK_SIZE, BLOCK_SIZE, COLOR_GRID_BG); + } + } +} +int milis_elapsed = 0; +int next_tick = 0; + + +void move_piece_left(){ + // Perform checks + for(int iter_y = 0; iter_y < BOARD_HEIGHT + PIECE_BUFFER_OFFSET; iter_y++){ + if(piece_mask[iter_y][0]){ + return; // piece would go out of bounds + } + for(int iter_x = 0; iter_x < BOARD_WIDTH; iter_x++){ + bool board_bit = board_mask[iter_y-PIECE_BUFFER_OFFSET][iter_x - 1]; + bool piece_bit = piece_mask[iter_y][iter_x]; + if(board_bit && piece_bit){ + return; // collision + } + } + } + // Move Piece + for(int iter_x = 0; iter_x < BOARD_WIDTH; iter_x++){ + for(int iter_y = 0; iter_y < BOARD_HEIGHT + PIECE_BUFFER_OFFSET; iter_y++){ + if(piece_mask[iter_y][iter_x]){ + piece_mask[iter_y][iter_x-1] = 1; + piece_mask[iter_y][iter_x] = 0; + } + } + } +} + +void move_piece_right(){ + // Perform checks + for(int iter_x = 0; iter_x < BOARD_HEIGHT + PIECE_BUFFER_OFFSET; iter_x++){ + if(piece_mask[iter_x][BOARD_WIDTH - 1]){ + return; // piece would go out of bounds + } + for(int iter_y = 0; iter_y < BOARD_HEIGHT + PIECE_BUFFER_OFFSET; iter_y++){ + bool board_bit = board_mask[iter_y - PIECE_BUFFER_OFFSET][iter_x+1]; + bool piece_bit = piece_mask[iter_y][iter_x]; + if(board_bit && piece_bit){ + return; // collision + } + } + } + // Move piece + for(int j = BOARD_WIDTH - 1; j >= 0; j--){ + for(int i = 0; i < BOARD_HEIGHT + PIECE_BUFFER_OFFSET; i++){ + if(piece_mask[i][j]){ + int new_x = j + 1; + int new_y = i; + if(board_mask[new_y - PIECE_BUFFER_OFFSET][new_x]){ + return; // collision + } + piece_mask[i][j+1] = piece_mask[i][j]; + piece_mask[i][j] = 0; + } + } + } +} + +void update(){ + bool accelerate = 0; + int accel_tick_time = 75; + // Update game state + if (IsKeyPressed(KEY_W)) { + // Rotate the current piece + turn_piece(true); + } + if (IsKeyPressed(KEY_A)) { + // Move piece left + move_piece_left(); + } + if (IsKeyPressed(KEY_D)) { + // Move piece right + move_piece_right(); + } + if (IsKeyDown(KEY_S)) { + // Accelerate piece down + accelerate = 1; + if (next_tick > milis_elapsed + accel_tick_time) + { + game_tick(); + } + } + if(milis_elapsed > next_tick){ + game_tick(); + next_tick = milis_elapsed + (accelerate ? accel_tick_time : 1000); + } + milis_elapsed += GetFrameTime() * 1000; +} + +Vector2* get_shape_dimensions(Piece* piece){ + static Vector2 dims; + dims = piece->dims; + return &dims; +} + + + +// Helper function to create 2D array from 1D data +int** create_shape_from_array(int rows, int cols, int* data) { + int** shape = malloc(rows * sizeof(int*)); + for(int i = 0; i < rows; i++) { + shape[i] = malloc(cols * sizeof(int)); + for(int j = 0; j < cols; j++) { + shape[i][j] = data[i * cols + j]; + } + } + return shape; +} + +void free_shape(int** shape, int rows) { + if(shape) { + for(int i = 0; i < rows; i++) { + free(shape[i]); + } + free(shape); + } +} diff --git a/tetris.h b/tetris.h new file mode 100644 index 0000000..a0882e8 --- /dev/null +++ b/tetris.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "raylib.h" + + +typedef enum { + PC_EMPTY = 0, + PC_CYAN = 1, + PC_BLUE = 3, + PC_ORANGE = 4, + PC_YELLOW = 5, + PC_GREEN = 6, + PC_PURPLE = 7, + PC_RED = 8 +} ColorCode; + + +typedef struct { + int** shape; + bool has_origin; + Vector2 dims; + Vector2 origin; + char color; +} Piece; + + +// Helper function to create shape from static array +int** create_shape_from_array(int rows, int cols, int* data); +void free_shape(int** shape, int rows); + +#define T_PIECE (Piece) { \ + .shape = create_shape_from_array(2, 3, (int[]){ \ + 0,1,0, \ + 1,1,1}), \ + .has_origin = true, \ + .dims = (Vector2) {3, 2}, \ + .origin = (Vector2) {1, 1}, \ + .color = PC_PURPLE \ +} + +#define O_PIECE (Piece) { \ + .shape = create_shape_from_array(2, 2, (int[]){ \ + 1,1, \ + 1,1}), \ + .has_origin = false, \ + .dims = (Vector2) {2, 2}, \ + .origin = (Vector2) {0, 0}, \ + .color = PC_YELLOW \ +} + +#define Z_PIECE (Piece) { \ + .shape = create_shape_from_array(2, 3, (int[]){ \ + 1,1,0, \ + 0,1,1}), \ + .has_origin = true, \ + .dims = (Vector2) {3, 2}, \ + .origin = (Vector2) {1, 1}, \ + .color = PC_RED \ +} + +#define S_PIECE (Piece) { \ + .shape = create_shape_from_array(2, 3, (int[]){ \ + 0,1,1, \ + 1,1,0}), \ + .has_origin = true, \ + .dims = (Vector2) {3, 2}, \ + .origin = (Vector2) {1, 1}, \ + .color = PC_GREEN \ +} + +#define I_PIECE (Piece) { \ + .shape = create_shape_from_array(4, 1, (int[]){ \ + 1, \ + 1, \ + 1, \ + 1}), \ + .has_origin = true, \ + .dims = (Vector2) {1, 4}, \ + .origin = (Vector2) {0, 2}, \ + .color = PC_CYAN \ +} + +#define J_PIECE (Piece) { \ + .shape = create_shape_from_array(2, 3, (int[]){ \ + 1,0,0, \ + 1,1,1}), \ + .has_origin = true, \ + .dims = (Vector2) {3, 2}, \ + .origin = (Vector2) {1, 1}, \ + .color = PC_BLUE \ +} + +#define L_PIECE (Piece) { \ + .shape = create_shape_from_array(2, 3, (int[]){ \ + 0,0,1, \ + 1,1,1}), \ + .has_origin = true, \ + .dims = (Vector2) {3, 2}, \ + .origin = (Vector2) {1, 1}, \ + .color = PC_ORANGE \ +} + +#define COLOR_GRID_BG (Color) { 0x00, 0x00, 0x00, 0xff} +#define COLOR_GRID_FG (Color) { 0xD0, 0xD0, 0xD0, 0xff} + + + +#define BOARD_WIDTH 10 +#define BOARD_HEIGHT 20 + +#define PIECE_BUFFER_OFFSET 4 + +#define BORDERBUFFER_WIDTH 1 +#define BORDERBUFFER_HEIGHT 1 + +#define TOTAL_WIDTH (BOARD_WIDTH + 2 * BORDERBUFFER_WIDTH) +#define TOTAL_HEIGHT (BOARD_HEIGHT + 2 * BORDERBUFFER_HEIGHT + PIECE_BUFFER_OFFSET) +#define GRID_BITS (TOTAL_WIDTH * TOTAL_HEIGHT) + +#define GRID_WORDS ((GRID_BITS + 31) / 32) + +typedef struct { + uint32_t bits[GRID_WORDS]; +} bitgrid_t; + + +#define PIXEL_SCALE 8 +#define BLOCK_SIZE (4 * PIXEL_SCALE) +#define GRID_SCREEN_PIXELS (BLOCK_SIZE + 2 * PIXEL_SCALE) + +#define NDEBUG + + +typedef struct { + int x; + int y; +} IntVector2; + +typedef struct{ + +} BoardBits; + + +// Function prototypes +Color resolve_color(unsigned char colorValue); +void zero_board(); +void zero_board_mask(); +void zero_piece_mask(); +void populate_board_mask(); +bool next_tick_valid(); +bool piece_tick(); +void place_piece(); +void spawn_piece(); +void turn_piece(bool clockwise); +void game_tick(); +void debugDraw(int x, int y, Color color); +void debug_draw_blocks(Vector2 block_vecs[], Color color); +void debug_draw_blocks_on_axis(Vector2 block_vecs[], Color color); +void draw(); +void update(); +void init_game(); +void init_shapes(); +void cleanup_shapes(); +void get_vecs_from_shape(Piece* cpiece, Vector2* blocks); +void sort_blocks(Vector2* blocks); +Vector2 find_topleftmost_block_from_buffer(); +Vector2 get_origin_offset(); +Vector2* get_shape_bounds(); +Vector2* get_shape_dimensions(Piece* piece); \ No newline at end of file