// 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); } }