diff options
Diffstat (limited to 'frontends')
| -rw-r--r-- | frontends/curses/curses_ui.c | 258 | ||||
| -rw-r--r-- | frontends/curses/meson.build | 17 | ||||
| -rw-r--r-- | frontends/sdl/cetris_sdl.c | 727 | ||||
| -rw-r--r-- | frontends/sdl/cetris_sdl.h | 112 | ||||
| -rw-r--r-- | frontends/sdl/config.ini | 15 | ||||
| -rw-r--r-- | frontends/sdl/data.zip | bin | 0 -> 1663021 bytes | |||
| -rw-r--r-- | frontends/sdl/meson.build | 14 | ||||
| -rw-r--r-- | frontends/timer.c | 86 |
8 files changed, 1229 insertions, 0 deletions
diff --git a/frontends/curses/curses_ui.c b/frontends/curses/curses_ui.c new file mode 100644 index 0000000..91792f5 --- /dev/null +++ b/frontends/curses/curses_ui.c @@ -0,0 +1,258 @@ +/* very simple curses interface, DAS is not functional because you cant detect + * key up presses on the terminal, there are 2 character sets, ASCII_COMPATIBLE + * for no UTF-8 chars and the normal which will look better on a platform that can + * support UTF-8. Color support is limited because I havent figured out colors yet */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdbool.h> +#ifdef _WIN32 +#include <curses.h> +#include <windows.h> +#else +#include <ncurses.h> +#include <unistd.h> +#include <pthread.h> +#endif +#include <locale.h> + +#define CETRIS_HI_RES 1 +#define CETRIS_ENABLE_DAS 0 +#include <cetris.h> +#include <rules.h> + +#include "../timer.c" + +#define WIN_COL 55 +#define WIN_LINE 25 + +//#define ASCII_COMPATIBLE +#ifdef ASCII_COMPATIBLE +#define BLOCK "[]" +#define PLAY_FIELD_STR " /--------------------\\ /----------------\\ \n"\ + " | | | | \n"\ + " | | \\----------------/ \n"\ + " | | \n"\ + " | | \n"\ + " | | /---------\\ \n"\ + " | | | | \n"\ + " | | | | \n"\ + " | | | | \n"\ + " | | | | \n"\ + " | | \\---------/ \n"\ + " | | \n"\ + " | | \n"\ + " | | \n"\ + " | | \n"\ + " | | \n"\ + " | | \n"\ + " | | \n"\ + " | | \n"\ + " | | \n"\ + " | | \n"\ + " \\--------------------/" +#else +#define BLOCK "[]" +#define PLAY_FIELD_STR " ┏━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━score━━━━━┓ \n"\ + " ┃ ┃ ┃ ┃ \n"\ + " ┃ ┃ ┗━━━━━━━━━━━━━━━┛ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ ┏━━━queue━━━┓ \n"\ + " ┃ ┃ ┃ ┃ \n"\ + " ┃ ┃ ┃ ┃ \n"\ + " ┃ ┃ ┃ ┃ \n"\ + " ┃ ┃ ┃ ┃ \n"\ + " ┃ ┃ ┗━━━━━━━━━━━┛ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┃ ┃ \n"\ + " ┗━━━━━━━━━━━━━━━━━━━━┛ " +#endif + +#define X_OFFSET 8 +#define Y_OFFSET -2 + +ctrs_game game = { 0 }; +bool is_paused = false; + +void curses_init() { + setlocale(LC_CTYPE, ""); + initscr(); + curs_set(0); + noecho(); + keypad(stdscr, TRUE); + timeout(2); + +#ifdef _WIN32 // only resize manually on windows + resize_term(WIN_LINE, WIN_COL); +#endif + + start_color(); + init_pair(MINO_O, COLOR_YELLOW, COLOR_BLACK); + init_pair(MINO_Z, COLOR_RED, COLOR_BLACK); + init_pair(MINO_S, COLOR_GREEN, COLOR_BLACK); + init_pair(MINO_T, COLOR_MAGENTA, COLOR_BLACK); + init_pair(MINO_L, COLOR_WHITE, COLOR_BLACK); // should be orange + init_pair(MINO_I, COLOR_CYAN, COLOR_BLACK); + init_pair(MINO_J, COLOR_BLUE, COLOR_BLACK); + clear(); +} + +#ifdef _WIN32 +DWORD WINAPI game_loop(void* data) { + while(1) { + Sleep((1.0/60.0) * 1000.0); + if (!is_paused) ctrs_update_game_tick(&game); + } + return 0; +} +#else +void *game_loop(void) { + struct timespec start_time, end_time; + clock_gettime(CLOCK_MONOTONIC_RAW, &start_time); + while (1) { + clock_gettime(CLOCK_MONOTONIC_RAW, &end_time); + long nsec_elapsed = (end_time.tv_sec - start_time.tv_sec) * (long)1e9 + (end_time.tv_nsec - start_time.tv_nsec); + game.timer = nsec_elapsed / 1000; + game.tick = nsec_elapsed / 1000000; + if (!ctrs_update_game_tick(&game)) { + break; + } + usleep(1000); + } + return 0; +} +#endif + + +void draw_board() { + mvaddstr(0, 0, PLAY_FIELD_STR); + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if ((game.current.m[y]>>(3 - x))&1) { + int draw_x = X_OFFSET + ((x + game.current.pos.x) * 2); + int draw_y = Y_OFFSET + (y + game.current.pos.y) - game.config.board_visible; + int ghost_y = Y_OFFSET + (y + game.current.ghost_y) - game.config.board_visible; + attron(A_DIM); + mvaddstr(ghost_y, draw_x, BLOCK); + attroff(A_DIM); + attron(COLOR_PAIR(game.current.t) | A_BOLD); + mvaddstr(draw_y, draw_x, BLOCK); + attroff(COLOR_PAIR(game.current.t) | A_BOLD); + } + if ((ctrs_default_matrices[game.piece_queue[game.current_index]][y]>>(3 - x))&1) { + mvaddstr(6 + y, (x * 2) + 36, BLOCK); + } + } + } + + for (int x = 0; x < game.config.board_x; x++) { + for (int y = game.highest_line; y < game.config.board_y; y++) { + int draw_y = y - game.config.board_visible + Y_OFFSET; + int draw_x = x * 2 + X_OFFSET; + if (game.board[x][y] & CTRS_SLOT_OCCUPIED) { + attron(COLOR_PAIR(game.board[x][y] >> 5) | A_BOLD); + if (game.line_remove_tick[y]) { + if (game.tick % 2 == 0) { + mvaddstr(draw_y, draw_x, BLOCK); + } + } else { + mvaddstr(draw_y, draw_x, BLOCK); + } + attroff(COLOR_PAIR(game.board[x][y] >> 5) | A_BOLD); + } + } + } + + attron(A_BOLD); + + char score[50]; + sprintf(score, "%i", game.score); + mvaddstr(1, (39 + X_OFFSET) - strlen(score), score); + + char level[20]; + sprintf(level, "%i", game.level); + mvaddstr(3, 37, "Level"); + mvaddstr(4, 40 - strlen(level), level); + + if (game.game_over) { + mvaddstr(10, 5 + X_OFFSET, "GAME OVER"); + mvaddstr(11, 4 + X_OFFSET, "r to restart"); + } + + if (is_paused) { + mvaddstr(10, 7 + X_OFFSET, "paused"); + } + + attroff(A_BOLD); +} + +int main(void) { + curses_init(); + + game.config = tetris_ds_config; + game.config.levels = &tetris_worlds_levels[0]; + game.config.win_condition = twenty_line_sprint; + game.config.wait_on_clear = 0; + ctrs_init_game(&game); + + ctrs_start_game(&game); + /* +#ifdef _WIN32 + HANDLE thread = CreateThread(NULL, 0, game_loop, NULL, 0, NULL); +#else + pthread_t thread; + pthread_create(&thread, NULL, (void*)game_loop, (void*)0); +#endif +*/ + + int c; + while(1) { + c = getch(); + switch (c) { + case 'q': endwin(); exit(1); + case 27: // esc or alt + is_paused = !is_paused; break; + case 'r': + if (game.game_over) { + ctrs_init_game(&game); + } + break; + } + if (is_paused) continue; // dont allow input if paused + switch (c) { + case KEY_LEFT: + ctrs_move_piece(&game, LEFT); break; + case KEY_RIGHT: + ctrs_move_piece(&game, RIGHT); break; + case KEY_DOWN: + ctrs_move_piece(&game, DOWN); break; + case KEY_UP: + case 'x': + ctrs_move_piece(&game, ROTATE_CW); break; + case '^': + case 'z': + ctrs_move_piece(&game, ROTATE_CCW); break; + case ' ': + ctrs_move_piece(&game, HARD_DROP); break; + case KEY_SLEFT: + case 'c': + ctrs_hold_piece(&game); break; + } + erase(); + draw_board(); + refresh(); + } + return 0; +} + diff --git a/frontends/curses/meson.build b/frontends/curses/meson.build new file mode 100644 index 0000000..46763fd --- /dev/null +++ b/frontends/curses/meson.build @@ -0,0 +1,17 @@ +src = ['curses_ui.c'] + +inc = [cetris_inc] +deps = [] + +if host_machine.system() == 'windows' + deps += compiler.find_library('pdcurses') +else + deps += dependency('ncursesw') + deps += dependency('tinfow') + deps += dependency('threads') +endif + +executable('cetris', src, + dependencies: deps, + include_directories: inc, + install: false) diff --git a/frontends/sdl/cetris_sdl.c b/frontends/sdl/cetris_sdl.c new file mode 100644 index 0000000..3bb6cb8 --- /dev/null +++ b/frontends/sdl/cetris_sdl.c @@ -0,0 +1,727 @@ +#define _XOPEN_SOURCE 500 + +#define CETRIS_HI_RES 1 +#define CETRIS_ENABLE_DAS 1 +#include <cetris.h> +#include <rules.h> +#include "../../ini/ini.h" + +#include "../timer.c" + +#define SDL_MAIN_HANDLED +#include <SDL2/SDL.h> +#include <SDL2/SDL_ttf.h> +#include <SDL2/SDL_image.h> +#ifdef _WIN32 +#define format_str sprintf_s +#else +#define format_str snprintf +#endif +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <sys/stat.h> + +#include "cetris_sdl.h" + +#define W 900 +#define H 720 +#define FRAME_RATE 60 + +void setup_sdl(cetris_ui *ui) { + SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO); + ui->window = SDL_CreateWindow("cetris", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, W, H, SDL_WINDOW_SHOWN); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); + SDL_SetHint(SDL_HINT_RENDER_DRIVER,"opengl"); + ui->render = SDL_CreateRenderer(ui->window, -1, SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_ACCELERATED); + SDL_SetRenderDrawBlendMode(ui->render, SDL_BLENDMODE_BLEND); + SDL_RenderSetLogicalSize(ui->render, W, H); + if (!ui->render) exit(fprintf(stderr, "err: could not create SDL renderer\n")); + + TTF_Init(); + ui->font_count = 0; + + IMG_Init(IMG_INIT_PNG); + + SDL_AudioSpec spec; + + SDL_memset(&spec, 0, sizeof(spec)); + spec.freq = 44100; + spec.format = AUDIO_S16; + spec.channels = 2; + spec.samples = 4096; + spec.callback = NULL; + + ui->audio_device = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0); + if (ui->audio_device == 0) printf("failed to open audio device\n"); + else SDL_PauseAudioDevice(ui->audio_device, 0); +} + +TTF_Font* get_font(cetris_ui *ui, int size) { + for (int i = 0; i < ui->font_count; i++) { + if (ui->fonts[i].size == size) { + return ui->fonts[i].font; + } + } + ui->fonts[ui->font_count].font = TTF_OpenFont("data/OpenSans-Regular.ttf", size); + TTF_SetFontHinting(ui->fonts[ui->font_count].font, TTF_HINTING_MONO); + ui->fonts[ui->font_count].size = size; + return ui->fonts[ui->font_count++].font; +} + +SDL_Texture* load_png(cetris_ui* ui, char* file) { + SDL_Surface* loaded_surface = IMG_Load(file); + if (loaded_surface == NULL) { + fprintf(stderr, "err: failed to load %s\n", file); + return NULL; + } + return SDL_CreateTextureFromSurface(ui->render, loaded_surface); +} + +uint8_t *lock_sound; +uint32_t lock_sound_length; +void load_multiple_audio(char* name, char* dir_name, int *count, audio_clip_t* list) { + char file[120]; + + int index = 0; + for (int i = 0; i < (*count); i++) { + format_str(file, 120, "%s/%s_%i.wav", dir_name, name, i); + + if (SDL_LoadWAV(file, &list[index].wav_spec, + &list[index].wav_buffer, &list[index].wav_length) != NULL) { + SDL_MixAudioFormat(list[index].wav_buffer, lock_sound, list[index].wav_spec.format, lock_sound_length, SDL_MIX_MAXVOLUME); + index++; + } else (*count)--; + } +} + +void load_skin(cetris_ui *ui) { + char *dir_name = malloc(100); + format_str(dir_name, 100, "skins/%s", ui->config.skin_name); + + struct stat sb; + +#ifdef _WIN32 + if (!(stat(dir_name, &sb) == 0 && (sb.st_mode & S_IFDIR))) { +#else + if (!(stat(dir_name, &sb) == 0 && S_ISDIR(sb.st_mode))) { +#endif + dir_name = "skins/default"; + } + + ini_parser p; + + char ini_path[100]; + format_str(ini_path, 100, "%s/skin.ini", dir_name); + + char name[120]; + format_str(name, 120, "%s/lock.wav", dir_name); + SDL_LoadWAV(name, &ui->skin.lock_sound.wav_spec, + &ui->skin.lock_sound.wav_buffer, &ui->skin.lock_sound.wav_length); + lock_sound = ui->skin.lock_sound.wav_buffer; + lock_sound_length = ui->skin.lock_sound.wav_length; + + if (load_ini_file(&p, ini_path)) { + char* random_clear = get_ini_value(&p, "sounds", "random_clear_sound"); + if (random_clear && !strcmp(random_clear, "true")) { + ui->skin.random_audio = true; + } else ui->skin.random_audio = false; + + format_str(name, 120, "%s/playboard.png", dir_name); + ui->skin.game_background = load_png(ui, name); + if (ui->skin.game_background != NULL) { + SDL_SetTextureAlphaMod(ui->skin.game_background, 120); + SDL_SetTextureBlendMode(ui->skin.game_background, SDL_BLENDMODE_BLEND); + } + + format_str(name, 120, "%s/overlay_middle.png", dir_name); + ui->skin.overlay_middle = load_png(ui, name); + if (ui->skin.overlay_middle != NULL) { + SDL_SetTextureBlendMode(ui->skin.overlay_middle, SDL_BLENDMODE_BLEND); + } + + format_str(name, 120, "%s/border.png", dir_name); + ui->skin.border = load_png(ui, name); + if (ui->skin.border != NULL) { + SDL_SetTextureBlendMode(ui->skin.border, SDL_BLENDMODE_BLEND); + } + + format_str(name, 120, "%s/background.png", dir_name); + ui->skin.background = load_png(ui, name); + if (ui->skin.border != NULL) { + SDL_SetTextureBlendMode(ui->skin.background, SDL_BLENDMODE_BLEND); + } + + format_str(name, 120, "%s/blocks.png", dir_name); + ui->skin.blocks = load_png(ui, name); + if (ui->skin.blocks != NULL) { + SDL_SetTextureBlendMode(ui->skin.blocks, SDL_BLENDMODE_BLEND); + } + + char* clear_count = get_ini_value(&p, "sounds", "clear_sound_count"); + if (clear_count) { + ui->skin.clear_sound_count = atoi(clear_count); + free(clear_count); + } + + if (ui->skin.clear_sound_count > 0) { + ui->skin.clear_sound = + (audio_clip_t *)malloc(sizeof(audio_clip_t) * ui->skin.clear_sound_count); + load_multiple_audio("clear", dir_name, + &ui->skin.clear_sound_count, ui->skin.clear_sound); + } + + char* tetris_count = get_ini_value(&p, "sounds", "four_clear_sound_count"); + if (tetris_count) { + ui->skin.tetris_sound_count = atoi(tetris_count); + free(tetris_count); + } + + if (ui->skin.tetris_sound_count > 0) { + ui->skin.tetris_sound = + (audio_clip_t *)malloc(sizeof(audio_clip_t) * ui->skin.tetris_sound_count); + load_multiple_audio("four_clear", dir_name, + &ui->skin.tetris_sound_count, ui->skin.tetris_sound); + } + } +} + +void draw_text(cetris_ui *ui, char* string, int x, int y, TTF_Font* font, SDL_Color color) { + SDL_Surface *surface; + surface = TTF_RenderText_Solid(font, string, color); + if (!surface) return; + + SDL_Rect message; + message.x = x; + message.y = y; + message.w = surface->w; + message.h = surface->h; + SDL_Texture* tex = SDL_CreateTextureFromSurface(ui->render, surface); + + SDL_RenderCopy(ui->render, tex, NULL, &message); + SDL_DestroyTexture(tex); + SDL_FreeSurface(surface); +} + +void draw_image(cetris_ui *ui, SDL_Texture *i, int x, int y, int width, int height) { + SDL_Rect dest = {x, y, width, height}; + SDL_RenderCopy(ui->render, i, NULL, &dest); +} + +void draw_block(cetris_ui *ui, int x, int y, int width, int height, int mino, int alpha, SDL_Color off) { + if (ui->skin.blocks) { + SDL_Rect b = {x, y, width, height}; + SDL_Rect block = {(mino + 2) * 32, 0, 32, 32}; + SDL_SetTextureAlphaMod(ui->skin.blocks, alpha); + SDL_RenderCopy(ui->render, ui->skin.blocks, &block, &b); + } else { + SDL_Rect b = {x + 1, y + 1, width - 1, height - 1}; + SDL_Color c = mino_colors[mino]; + SDL_SetRenderDrawColor(ui->render, c.r, c.g, c.b, alpha); + SDL_RenderFillRect(ui->render, &b); + SDL_RenderDrawRect(ui->render, &b); + SDL_SetRenderDrawColor(ui->render, off.r, off.g, off.b, off.a); + b.y--; b.x--; b.w+=2; b.h+=2; + SDL_RenderDrawRect(ui->render, &b); + } +} + +void draw_board(cetris_ui *ui, SDL_Texture *m, game_board_t* board, int x, int y, int w, int h) { + SDL_SetRenderTarget(ui->render, m); + SDL_RenderClear(ui->render); + + int y_offset = 10; + + SDL_Rect background = {0, 0, w, h + y_offset}; + + if (ui->skin.game_background) { + SDL_RenderCopy(ui->render, ui->skin.game_background, NULL, &background); + if (board->count_down > 0) { + SDL_SetRenderDrawColor(ui->render, + board->scheme.off.r - (int)(fmod((double)board->count_down, (double)1.0) * 50), + board->scheme.off.g, board->scheme.off.b, 170); + + SDL_RenderFillRect(ui->render, &background); + SDL_RenderDrawRect(ui->render, &background); + } + } else { + SDL_SetRenderDrawColor(ui->render, + board->scheme.off.r - (int)(fmod((double)board->count_down, (double)1.0) * 50), + board->scheme.off.g, board->scheme.off.b, board->scheme.off.a); + + SDL_RenderFillRect(ui->render, &background); + SDL_RenderDrawRect(ui->render, &background); + } + + SDL_SetRenderDrawColor(ui->render, board->scheme.main.r, + board->scheme.main.g, board->scheme.main.b, board->scheme.main.a); + + + int board_x = board->game.config.board_x; + int board_y = board->game.config.board_y; + int board_visible = board_y - board->game.config.board_visible; + + int block_width = w / board->game.config.board_x; + int block_height = h / board->game.config.board_visible; + + if (ui->skin.overlay_middle) { + for (int j = 0; j < board->game.config.board_visible + 1; j++) { + if (j == 0) { + SDL_Rect overlay = {0, y_offset - block_height, (block_width * 10), block_height}; + //SDL_Rect cutoff = {0, block_height - y_offset, (block_width * 10), block_height}; + SDL_RenderCopy(ui->render, ui->skin.overlay_middle, NULL, &overlay); + } else { + SDL_Rect overlay = {0, y_offset + ((j - 1) * block_height), (block_width * 10), block_height}; + SDL_RenderCopy(ui->render, ui->skin.overlay_middle, NULL, &overlay); + } + } + } else { + for (int s = 0; s < board_x + 1; s++) { + int rx = (s * block_width); + SDL_RenderDrawLine(ui->render, rx, 1, rx, h + y_offset); + } + + for (int j = 0; j < board->game.config.board_visible + 1; j++) { + int ry = y_offset + (j * block_height); + SDL_RenderDrawLine(ui->render, 0, ry, w, ry); + } + } + + for (int s = board->game.highest_line; s < board_y; s++) { + for (int j = 0; j < board_x; j++) { + if (board->game.board[j][s] & CTRS_SLOT_OCCUPIED) { + if (board->game.line_remove_tick[s]) { + if (board->game.tick % 2 == 0) { + continue; + } + } + + int block_x = (j * block_width); + int block_y = (y_offset + ((s - board_visible) * block_height)); + + draw_block(ui, block_x, block_y, block_width, block_height, + (board->game.board[j][s] >> 5), 255, board->scheme.off); + } + } + } + + if (!board->game.game_over) { + for (int s = 0; s < 4; s++) { + for (int j = 0; j < 4; j++) { + if ((board->game.current.m[s]>>(3 - j))&1) { + int block_x = ((j + board->game.current.pos.x) * block_width); + int block_y = y_offset + ((s + board->game.current.pos.y) - board_visible) * block_height; + + draw_block(ui, block_x, block_y, block_width, block_height, + board->game.current.t, 255, board->scheme.off); + + int ghost_y = y_offset + ((s + board->game.current.ghost_y) - board_visible) * block_height; + if (ghost_y != block_y) { + draw_block(ui, block_x, ghost_y, block_width, block_height, + board->game.current.t, 120, board->scheme.off); + } + } + } + } + } + + if (board->count_down > 0) + board->count_down-=(1.0f/FRAME_RATE); + + SDL_SetRenderTarget(ui->render, NULL); + + SDL_Rect dest = {x, y, w, h + y_offset}; + SDL_RenderCopy(ui->render, m, NULL, &dest); + + if (ui->skin.border) { + SDL_Rect border = {x - 110, y - 35, 380, 600}; + SDL_RenderCopy(ui->render, ui->skin.border, NULL, &border); + } +} + +void draw_held_piece(cetris_ui *ui, SDL_Texture *m, game_board_t* board, int x, int y, int w, int h) { + if (w < 8) return; + if (h < 8) return; + + //SDL_SetRenderTarget(ui->render, m); + //SDL_RenderClear(ui->render); + + int block_width = (w - 4) / 5; + int block_height = (h - 4) / 5; + + // make sure blocks are square + if (block_width > block_height) { + block_width = block_height; + } else { + block_height = block_width; + } + + SDL_SetRenderDrawColor(ui->render, board->scheme.off.r, + board->scheme.off.g, board->scheme.off.b, board->scheme.off.a); + + //SDL_Rect hold = {0, 0, w, h}; + //SDL_RenderFillRect(ui->render, &hold); + //SDL_RenderDrawRect(ui->render, &hold); + + if (board->game.piece_held) { + for (int s = 0; s < 4; s++) { + for (int j = 0; j < 4; j++) { + if ((board->game.held.m[s]>>(3 - j))&1) { + int block_x = 2 + ((j) * block_width); + int block_y = (s * block_height); + if (board->game.held.t == MINO_I) { + block_x += (int)(block_width / 1.5f); + block_y += block_height; + } else { + block_x += block_width; + block_y += (int)(block_height); + } + if (board->game.held.t == MINO_O) { + block_x -= block_width / 2; + } + + draw_block(ui, x + block_x, y + block_y, block_width, block_height, + board->game.held.t, 255, board->scheme.off); + + } + } + } + } + + //SDL_SetRenderTarget(ui->render, NULL); + + //SDL_Rect dest = {x, y, w, h}; + //SDL_RenderCopy(ui->render, m, NULL, &dest); +} + +void draw_piece_queue(cetris_ui* ui, SDL_Texture *m, game_board_t* board, int x, int y, int w, int h) { + if (w < 8) return; + if (h < 32) return; + + //SDL_SetRenderTarget(ui->render, m); + + //SDL_RenderClear(ui->render); + + int block_width = ((w - 4) / 4); + int block_height = ((h - 5) / 5) / 3; + + // make sure blocks are square + if (block_width > block_height) { + block_width = block_height; + } else { + block_height = block_width; + } + + //SDL_SetRenderDrawColor(ui->render, board->scheme.off.r, + // board->scheme.off.g, board->scheme.off.b, board->scheme.off.a); + + //SDL_Rect queue = {0, 0, w, h}; + //SDL_RenderFillRect(ui->render, &queue); + //SDL_RenderDrawRect(ui->render, &queue); + + for (int i = 0; i < 5; i++) { + int index = (board->game.current_index + i); + + uint8_t mino; + if (index <= 6) { + mino = board->game.piece_queue[index]; + } else { + index = index % 7; + mino = board->game.next_queue[index]; + } + + for (int s = 0; s < 4; s++) { + for (int j = 0; j < 4; j++) { + if ((ctrs_default_matrices[mino][s]>>(3 - j))&1) { + int block_x = 2 + ((j) * block_width); + int block_y = (int)(h * (i/5.0)) + (s * block_height); + if (mino == MINO_I) { + block_y += block_height / 2; + } else if (mino != MINO_O) { + block_x += block_width / 2; + } + + draw_block(ui, x + block_x, y + block_y, block_width, block_height, + mino, 255, board->scheme.off); + + } + } + } + } + + //SDL_SetRenderTarget(ui->render, NULL); + + //SDL_Rect dest = {x, y, w, h}; + //SDL_RenderCopy(ui->render, m, NULL, &dest); + +} + +void draw_timer(cetris_ui *ui, game_board_t *board, int x, int y) { + char *buf = malloc(50); + long double second = board->game.timer / 1000000.0f; + if (second > 60.0f) { + int minute = (int)(second / 60.0f); + second -= (minute * 60.0f); + format_str(buf, 50, "%02d:%09.6Lf", minute, second); + } else { + format_str(buf, 50, "%.6Lf", second); + } + draw_text(ui, buf, x, y, get_font(ui, 25), board->scheme.text); + + //format_str(buf, 50, "lines remaining: %i", 20 - g.lines); + //draw_text(buf, 20, H - 60, false); + + free(buf); +} + +void draw(cetris_ui* ui) { + SDL_SetRenderDrawColor(ui->render, ui->colors.main.r, + ui->colors.main.g, ui->colors.main.b, ui->colors.main.a); + + SDL_RenderClear(ui->render); + + if (ui->skin.background) { + int w, h; + SDL_QueryTexture(ui->skin.background, NULL, NULL, &w, &h); + + int ratio = w / W; + SDL_Rect back = {0, 0, ratio * W, ratio * H}; + SDL_RenderCopy(ui->render, ui->skin.background, &back, NULL); + } + + switch (ui->current_game) { + case SOLO: + //draw_image(ui, ui->solo.background, 0, 0, W, H); + draw_board(ui, ui->solo.main, &ui->solo.game_board, (W / 2) - 125, (H / 2) - 250, 250, 500); + draw_held_piece(ui, ui->solo.hold, &ui->solo.game_board, (W / 2) - 230, (H / 2) - 250, 100, 100); + draw_piece_queue(ui, ui->solo.queue, &ui->solo.game_board, (W / 2) + 150, (H / 2) - 250, 100, 450); + draw_timer(ui, &ui->solo.game_board, 20, 20); + break; + } + + SDL_RenderPresent(ui->render); +} + +void load_config(cetris_ui *ui, game_board_t *board) { + + ui->config.keys = (key_bindings_t){ + .key_down = SDLK_DOWN, + .key_right = SDLK_RIGHT, + .key_left = SDLK_LEFT, + .key_rotate_cw = SDLK_UP, + .key_rotate_ccw = 'x', + .key_drop = SDLK_SPACE, + .key_hold = 'c', + .key_restart = 'r' + }; + + board->game.config = tetris_ds_config; + board->game.config.levels = &tetris_worlds_levels[0]; + board->game.config.win_condition = twenty_line_sprint; + board->game.config.wait_on_clear = 0; + + ini_parser p; + if (load_ini_file(&p, "config.ini")) { + char* das = get_ini_value(&p, "das", "das"); + if (das) { + board->game.config.das_das = atoi(das); + free(das); + } + + char* arr = get_ini_value(&p, "das", "arr"); + if (arr) { + board->game.config.das_arr = atoi(arr); + free(arr); + } + + char* drop_delay = get_ini_value(&p, "game", "drop_delay"); + if (drop_delay) { + board->game.config.drop_period = atoi(drop_delay); + free(drop_delay); + } + + char* next_piece_delay = get_ini_value(&p, "game", "next_piece_delay"); + if (next_piece_delay) { + board->game.config.next_piece_delay = atoi(next_piece_delay); + free(next_piece_delay); + } + + char* lock_delay = get_ini_value(&p, "game", "lock_delay"); + if (lock_delay) { + board->game.config.lock_delay = atoi(lock_delay); + free(lock_delay); + } + + char* force_lock = get_ini_value(&p, "game", "force_lock"); + if (force_lock) { + board->game.config.force_lock = atoi(force_lock); + free(force_lock); + } + + char* wait_on_clear = get_ini_value(&p, "game", "wait_on_clear"); + if (wait_on_clear && !strcmp(wait_on_clear, "true")) { + board->game.config.wait_on_clear = true; + free(wait_on_clear); + } else board->game.config.wait_on_clear = false; + + char* line_delay_clear = get_ini_value(&p, "game", "line_clear_delay"); + if (line_delay_clear) { + board->game.config.line_delay_clear = atoi(line_delay_clear); + free(line_delay_clear); + } + + char *dark = get_ini_value(&p, "ui", "dark_mode"); + if (dark) { + if (!strcmp(dark, "true")) { + ui->colors = dark_mode; + } + free(dark); + } else { + ui->colors = light_mode; + } + + char *skin = get_ini_value(&p, "ui", "skin"); + if (skin) { + ui->config.skin_name = skin; + } else ui->config.skin_name = "default"; + } +} + +void load_solo(cetris_ui *ui) { + load_config(ui, &ui->solo.game_board); + ui->solo.game_board.scheme = ui->colors; + + ui->solo.main = SDL_CreateTexture(ui->render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 251, 513); + ui->solo.queue = SDL_CreateTexture(ui->render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 100, 450); + ui->solo.hold = SDL_CreateTexture(ui->render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 100, 100); + SDL_SetTextureBlendMode(ui->solo.hold, SDL_BLENDMODE_BLEND); + + ctrs_init_game(&ui->solo.game_board.game); + ui->solo.game_board.game.waiting = true; + ui->solo.game_board.count_down = 3; +} + +void handle_key(SDL_Event e, cetris_ui *ui, game_board_t* board) { + int sym; + switch (e.type) { + case SDL_QUIT: exit(0); + case SDL_KEYDOWN: + sym = e.key.keysym.sym; + if (sym == ui->config.keys.key_left) { + ctrs_move_piece(&board->game, LEFT); + } else if (sym == ui->config.keys.key_right) { + ctrs_move_piece(&board->game, RIGHT); + } else if (sym == ui->config.keys.key_down) { + ctrs_move_piece(&board->game, DOWN); + } else if (sym == ui->config.keys.key_drop) { + ctrs_move_piece(&board->game, HARD_DROP); + } else if (sym == ui->config.keys.key_hold) { + ctrs_hold_piece(&board->game); + } else if (sym == ui->config.keys.key_rotate_cw) { + ctrs_move_piece(&board->game, ROTATE_CW); + } else if (sym == ui->config.keys.key_rotate_ccw) { + ctrs_move_piece(&board->game, ROTATE_CCW); + } else if (sym == ui->config.keys.key_restart) { + ctrs_init_game(&board->game); + board->game.waiting = true; + board->count_down = 3; + } + break; + case SDL_KEYUP: + sym = e.key.keysym.sym; + if (sym == ui->config.keys.key_left) { + ctrs_unhold_move(&board->game, LEFT); + } else if (sym == ui->config.keys.key_right) { + ctrs_unhold_move(&board->game, RIGHT); + } else if (sym == ui->config.keys.key_down) { + ctrs_unhold_move(&board->game, DOWN); + } else if (sym == ui->config.keys.key_drop) { + ctrs_unhold_move(&board->game, HARD_DROP); + } else if (sym == ui->config.keys.key_rotate_cw) { + ctrs_unhold_move(&board->game, ROTATE_CW); + } else if (sym == ui->config.keys.key_rotate_ccw) { + ctrs_unhold_move(&board->game, ROTATE_CCW); + } + break; + } +} + +void handle_game_events(cetris_ui *ui, game_board_t *board) { + if (board->count_down < 0 && board->game.waiting) { + ctrs_start_game(&board->game); + } + + if (board->game.events.tetris_event > 0) { + if (ui->skin.tetris_sound_count > 0) { + int index = rand() % ui->skin.tetris_sound_count; + SDL_QueueAudio(ui->audio_device, ui->skin.tetris_sound[index].wav_buffer, + ui->skin.tetris_sound[index].wav_length); + } + + board->game.events.tetris_event--; + board->game.events.line_event = 0; + board->game.events.lock_event.processed = true; + } + + if (board->game.events.line_event > 0) { + if (ui->skin.clear_sound_count > 0) { + int index; + if (ui->skin.random_audio) { + index = rand() % ui->skin.clear_sound_count; + } else { + index = board->game.line_combo - 1; + } + if (index >= ui->skin.clear_sound_count) + index = ui->skin.clear_sound_count - 1; + + SDL_QueueAudio(ui->audio_device, ui->skin.clear_sound[index].wav_buffer, + ui->skin.clear_sound[index].wav_length); + } + + board->game.events.line_event = 0; + board->game.events.lock_event.processed = true; + } + + if (!board->game.events.lock_event.processed) { + if (ui->skin.lock_sound.wav_buffer) { + SDL_QueueAudio(ui->audio_device, ui->skin.lock_sound.wav_buffer, + ui->skin.lock_sound.wav_length); + } + + board->game.events.lock_event.processed = true; + } +} + +int main(void) { + cetris_ui ui; + memset(&ui, 0, sizeof(cetris_ui)); + + setup_sdl(&ui); + load_solo(&ui); + load_skin(&ui); + ui.current_game = SOLO; + + SDL_Event e; + for(;;) { + while(SDL_PollEvent(&e)) { + switch (ui.current_game) { + case SOLO: + handle_key(e, &ui, &ui.solo.game_board); + break; + } + } + + switch (ui.current_game) { + case SOLO: + handle_game_events(&ui, &ui.solo.game_board); + break; + } + + draw(&ui); + + SDL_Delay(1000 / FRAME_RATE); + } + + return 0; +} diff --git a/frontends/sdl/cetris_sdl.h b/frontends/sdl/cetris_sdl.h new file mode 100644 index 0000000..26e079e --- /dev/null +++ b/frontends/sdl/cetris_sdl.h @@ -0,0 +1,112 @@ +typedef struct { + SDL_Color main; + SDL_Color off; + SDL_Color text; +} color_scheme_t; + +color_scheme_t dark_mode = { + .main = {40, 40, 40, 255}, + .off = {90, 90, 90, 255}, + .text = {240, 240, 240, 255} +}; + +color_scheme_t light_mode = { + .main = {255, 255, 255, 255}, + .off = {235, 235, 235, 255}, + .text = {10, 10, 10, 255} +}; + +SDL_Color mino_colors[7] = { + {253,253,150,255}, // Yellow + {174,198,207,255}, // Aqua + {255,105,97,255}, // Red + {170,221,119,255}, // Olive + {255,179,71,255}, // Orange + {119,158,203,255}, // Navy + {177,156,217,255} // Purple +}; + +typedef struct { + SDL_AudioSpec wav_spec; + uint32_t wav_length; + uint8_t *wav_buffer; +} audio_clip_t; + +typedef struct { + TTF_Font *font; + int size; +} font_t; + +typedef struct { + bool random_audio; + + int clear_sound_count; + audio_clip_t *clear_sound; + + int tetris_sound_count; + audio_clip_t *tetris_sound; + audio_clip_t lock_sound; + + SDL_Texture *blocks; + SDL_Texture *game_background; + SDL_Texture *overlay_top; + SDL_Texture *overlay_middle; + SDL_Texture *overlay_bottom; + + SDL_Texture *border; + SDL_Texture *background; + +} cetris_skin_t; + +typedef struct { + ctrs_game game; + color_scheme_t scheme; + float count_down; +} game_board_t; + +typedef struct { + SDL_Texture *main; + SDL_Texture *queue; + SDL_Texture *hold; + + game_board_t game_board; +} solo_game_t; + +typedef struct { + int key_down; + int key_right; + int key_left; + int key_rotate_cw; + int key_rotate_ccw; + int key_drop; + int key_hold; + int key_restart; +} key_bindings_t; + +typedef struct { + key_bindings_t keys; + char *skin_name; +} config_t; + +enum { + SOLO +}; + +typedef struct { + int current_game; + solo_game_t solo; + color_scheme_t colors; + + SDL_Renderer* render; + SDL_Window *window; + SDL_Surface *screen; + + int font_count; + font_t fonts[10]; + + SDL_AudioDeviceID audio_device; + + config_t config; + + cetris_skin_t skin; +} cetris_ui; diff --git a/frontends/sdl/config.ini b/frontends/sdl/config.ini new file mode 100644 index 0000000..5947b33 --- /dev/null +++ b/frontends/sdl/config.ini @@ -0,0 +1,15 @@ +[das] +das = 220 +arr = 15 + +[game] +drop_delay = 20 +next_piece_delay = 40 +lock_delay = 300 +force_lock = 3000 +wait_on_clear = 0 +line_clear_delay = 0 + +[ui] +skin = puyo_arles_skin +dark_mode = true diff --git a/frontends/sdl/data.zip b/frontends/sdl/data.zip Binary files differnew file mode 100644 index 0000000..a26e00b --- /dev/null +++ b/frontends/sdl/data.zip diff --git a/frontends/sdl/meson.build b/frontends/sdl/meson.build new file mode 100644 index 0000000..5760408 --- /dev/null +++ b/frontends/sdl/meson.build @@ -0,0 +1,14 @@ +src = ['cetris_sdl.c'] + +inc = [cetris_inc] +deps = [] + +deps += compiler.find_library('m') +deps += dependency('SDL2') +deps += dependency('SDL2_ttf') +deps += dependency('SDL2_image') + +executable('cetris', src, + dependencies: deps, + include_directories: inc, + install: false) diff --git a/frontends/timer.c b/frontends/timer.c new file mode 100644 index 0000000..9ae90ca --- /dev/null +++ b/frontends/timer.c @@ -0,0 +1,86 @@ +#ifdef _WIN32 +#include <windows.h> + +#if CETRIS_HI_RES +static DWORD WINAPI ctrs_game_loop(void* data) { + ctrs_game* game = (ctrs_game*)data; + LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds; + LARGE_INTEGER Frequency; + QueryPerformanceFrequency(&Frequency); + QueryPerformanceCounter(&StartingTime); + while(1) { + if (game->waiting) break; + QueryPerformanceCounter(&EndingTime); + ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; + ElapsedMicroseconds.QuadPart *= 1000000; + ElapsedMicroseconds.QuadPart /= Frequency.QuadPart; + game->timer = ElapsedMicroseconds.QuadPart; + game->tick = ElapsedMicroseconds.QuadPart / 1000; + if (!ctrs_update_game_tick(game)) { + break; + } + Sleep(1); + } + return 0; +} +#else +static DWORD WINAPI ctrs_game_loop(void* data) { + ctrs_game* game = (ctrs_game*)data; + while(1) { + if (game->waiting) break; + game->tick += 16; + if (!ctrs_update_game_tick(game)) { + break; + } + Sleep(16); // little less than 60hz + } +} +#endif +static void ctrs_start_game(ctrs_game *g) { + g->waiting = false; + HANDLE thread = CreateThread(NULL, 0, ctrs_game_loop, g, 0, NULL); +} +#else +#include <pthread.h> +#include <unistd.h> + +#if CETRIS_HI_RES +#include <time.h> +static void *ctrs_game_loop(void* data) { + ctrs_game *game = (ctrs_game*)data; + struct timespec start_time, end_time; + clock_gettime(CLOCK_MONOTONIC_RAW, &start_time); + while (1) { + if (game->waiting) break; + clock_gettime(CLOCK_MONOTONIC_RAW, &end_time); + long nsec_elapsed = (end_time.tv_sec - start_time.tv_sec) * (long)1e9 + (end_time.tv_nsec - start_time.tv_nsec); + game->timer = nsec_elapsed / 1000; + game->tick = nsec_elapsed / 1000000; + if (!ctrs_update_game_tick(game)) { + break; + } + usleep(1000); + } + return 0; +} +#else +static void *ctrs_game_loop(void *data) { + ctrs_game* game = (ctrs_game*)data; + while(1) { + if (game->waiting) break; + game->tick += 16; + if (!ctrs_update_game_tick(game)) { + break; + } + // could be more accurate, keeping + // it consistant with windows + usleep(16000); + } +} +#endif +static void ctrs_start_game(ctrs_game *g) { + g->waiting = false; + pthread_t thread; + pthread_create(&thread, NULL, ctrs_game_loop, (void*)g); +} +#endif |