diff --git a/modules/tetris/Makefile b/modules/tetris/Makefile new file mode 100644 index 000000000..d9a694f48 --- /dev/null +++ b/modules/tetris/Makefile @@ -0,0 +1,7 @@ +# Force format +#$(info $(shell astyle --style=allman tetris.c ptetris.h)) + +TOP_DIR=../.. +MODULE_NAME=tetris +MODULE_OBJS=tetris.o +include $(TOP_DIR)/modules/Makefile.modules diff --git a/modules/tetris/README.rst b/modules/tetris/README.rst new file mode 100644 index 000000000..154a610ce --- /dev/null +++ b/modules/tetris/README.rst @@ -0,0 +1,9 @@ +ML Tetris +================== + +Basic Tetris. Down/Left/Right to move. Up to rotate. +[Q] to quit. + +:Authors: Daniel C +:License: GPL2 +:Summary: The Only Tetris for DSLRs. diff --git a/modules/tetris/ptetris.h b/modules/tetris/ptetris.h new file mode 100644 index 000000000..ea8c3f844 --- /dev/null +++ b/modules/tetris/ptetris.h @@ -0,0 +1,299 @@ +#ifndef PTETRIS_H +#define PTETRIS_H + +#include +#include +#include + +// Max array size +#ifndef PT_MAX_WIDTH + #define PT_MAX_WIDTH 40 + #define PT_MAX_HEIGHT 40 +#endif + +// Darken the color around blocks (optional) +#ifndef PT_COL_DARKEN + #define PT_COL_DARKEN(x) x +#endif + +// Basic random number generator +#ifndef PT_RAND + uint32_t pt_rand_x = 0x12345600; + int pt_rand(int max) { + pt_rand_x += 7; + pt_rand_x *= 0x87654321; + return pt_rand_x % max; + } + + #define PT_RAND(x) pt_rand(x) +#endif + +// Main runtime structure, tweak these in menu +struct PtRuntime { + int width; + int height; + int block_size; + int score; +}pt = { + 16, + 20, + 20, + 0, +}; + +#define BLOCK_LEN 5 + +// uint32_t pt_colors[BLOCK_LEN + 1] is expected to be defined + +struct PtBlock { + int curr; + int x; + int y; + uint8_t b[][4][4]; +}pt_blocks = { + 0, 4, 0, + { + { + {0, 1, 0, 0}, + {0, 1, 0, 0}, + {0, 1, 0, 0}, + {0, 1, 0, 0}, + }, + { + {0, 0, 0, 0}, + {0, 2, 2, 2}, + {0, 0, 2, 0}, + {0, 0, 0, 0}, + }, + { + {0, 3, 0, 0}, + {0, 3, 0, 0}, + {0, 3, 3, 0}, + {0, 0, 0, 0}, + }, + { + {0, 0, 0, 0}, + {0, 4, 4, 0}, + {0, 4, 4, 0}, + {0, 0, 0, 0}, + }, + { + {0, 0, 0, 0}, + {0, 5, 5, 0}, + {5, 5, 0, 0}, + {0, 0, 0, 0}, + }, + { + {0, 0, 0, 0}, + {6, 6, 0, 0}, + {0, 6, 6, 0}, + {0, 0, 0, 0}, + }, + } +}; + +struct PtField { + uint8_t b[PT_MAX_WIDTH][PT_MAX_HEIGHT]; +}; + +struct PtField pt_main_field; + +enum PtShift { + PT_NORMAL = 0, + PT_IMPOSSIBLE = 1, + PT_SIDE_SWIPE = 2, + PT_GAME_OVER = 3, +}; + +enum PtButton { + PT_DOWN, + PT_LEFT, + PT_RIGHT, + PT_ROT, + PT_QUIT, +}; + +void pt_reset() { + memset(&pt_main_field, 0, sizeof(pt_main_field)); + pt_blocks.curr = 0; + pt_blocks.x = 4; + pt_blocks.y = 0; + pt.score = 0; +} + +#ifndef PT_CUSTOM_DRAW_BLOCK +void pt_draw_block(int bx, int by, int col) { + bx *= pt.block_size; + by *= pt.block_size; + + for (int x = 0; x < pt.block_size; x++) { + for (int y = 0; y < pt.block_size; y++) { + if (x == 0 || y == 0 || y == pt.block_size - 1 || x == pt.block_size - 1) { + pt_pixel(bx + x, by + y, PT_COL_DARKEN(pt_colors[col])); + } else { + pt_pixel(bx + x, by + y, pt_colors[col]); + } + } + } +} +#endif + +int pt_field_possible(uint8_t b[4][4], int ox, int oy) { + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + if ((x + ox) >= pt.width || x + ox <= -1) { + return 0; + } + + // Detect impossible overlap + if (b[x + ox][y + oy] != 0 && + pt_main_field.b[x][y] != 0) { + return 0; + } + } + } + + return 1; +} + +int pt_field_rotate() { + uint8_t b[4][4]; + + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + b[x][y] = pt_blocks.b[pt_blocks.curr][3 - y][x]; + } + } + + if (!pt_field_possible(b, pt_blocks.x, pt_blocks.y)) { + return PT_IMPOSSIBLE; + } + + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + pt_blocks.b[pt_blocks.curr][x][y] = b[x][y]; + } + } + + return PT_NORMAL; +} + +int pt_field_shift(int ox, int oy) { + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + if (pt_blocks.b[pt_blocks.curr][x][y] == 0) continue; + + if (pt_blocks.b[pt_blocks.curr][x][y] != 0 && + pt_main_field.b[x + pt_blocks.x][y + pt_blocks.y] != 0) { + return PT_GAME_OVER; + } + + // Detect gravity/hit + if (pt_main_field.b[x + pt_blocks.x][y + pt_blocks.y + oy] != 0 + || (y + pt_blocks.y + oy) >= pt.height) { + return PT_IMPOSSIBLE; + } + + // Detect impossible side swipe + if (pt_main_field.b[pt_blocks.x + x + ox][pt_blocks.y + y + oy] != 0 + || (pt_blocks.x + x + ox) >= pt.width || (pt_blocks.x + x + ox) <= -1) { + pt_blocks.y += oy; + return PT_SIDE_SWIPE; + } + } + } + + pt_blocks.x += ox; + pt_blocks.y += oy; + + return PT_NORMAL; +} + +void pt_render() { + for (int x = 0; x < pt.width; x++) { + for (int y = 0; y < pt.height; y++) { + pt_draw_block(x, y, pt_main_field.b[x][y]); + } + } + + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + if (pt_blocks.b[pt_blocks.curr][x][y] != 0) { + pt_draw_block(x + pt_blocks.x, y + pt_blocks.y, pt_blocks.b[pt_blocks.curr][x][y]); + } + } + } +} + +int pt_handle_input(int key) { + int r; + switch (key) { + case PT_DOWN: + while ((r = pt_field_shift(0, 1)) != PT_IMPOSSIBLE) { + if (r == PT_GAME_OVER) return r; + } + break; + case PT_LEFT: + return pt_field_shift(-1, 0); + case PT_RIGHT: + return pt_field_shift(1, 0); + case PT_ROT: + return pt_field_rotate(); + case PT_QUIT: + return PT_GAME_OVER; + } + return PT_NORMAL; +} + +void pt_merge() { + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + if (pt_blocks.b[pt_blocks.curr][x][y] == 0) continue; + pt_main_field.b[x + pt_blocks.x][y + pt_blocks.y] + = pt_blocks.b[pt_blocks.curr][x][y]; + } + } +} + +void pt_shift_down(int yo) { + for (int x = 0; x < pt.width; x++) { + for (int y = yo - 1; y != 0; y--) { + pt_main_field.b[x][y + 1] = pt_main_field.b[x][y]; + } + } +} + +void pt_check_lines() { + for (int y = 0; y < pt.height; y++) { + int full = 0; + for (int x = 0; x < pt.width; x++) { + if (pt_main_field.b[x][y] != 0) { + full++; + } + } + + if (full == pt.width) { + pt.score++; + pt_shift_down(y); + } + } +} + +int pt_step() { + // Operate gravity + switch (pt_field_shift(0, 1)) { + case PT_IMPOSSIBLE: + pt_merge(); + pt_blocks.curr = PT_RAND(BLOCK_LEN); + pt_blocks.y = 0; + pt_blocks.x = 4; + break; + case PT_GAME_OVER: + return 1; + } + + pt_check_lines(); + return 0; +} + +#endif diff --git a/modules/tetris/tetris.c b/modules/tetris/tetris.c new file mode 100644 index 000000000..8178558da --- /dev/null +++ b/modules/tetris/tetris.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +uint32_t pt_colors[] = +{ + COLOR_WHITE, /* Background color */ + COLOR_BLUE, + COLOR_GREEN1, + COLOR_ORANGE, + COLOR_RED, + COLOR_MAGENTA, + COLOR_LIGHT_BLUE, +}; + +// bmp manually drawing seems very slow +#define PT_CUSTOM_DRAW_BLOCK +void pt_draw_block(int bx, int by, uint32_t col) +{ + bmp_fill(pt_colors[col], 30 + bx * 20, 30 + by * 20, 20, 20); +} + +#include "ptetris.h" + +int running = 0; + +extern int menu_redraw_blocked; + +void tetris_task() +{ + clrscr(); + + pt_reset(); + running = 1; + TASK_LOOP + { + while (!running) + { + msleep(1); + } + + pt_render(); + + bmp_printf(FONT_LARGE, 400, 10, "Score: %d", pt.score); + bmp_printf(FONT_LARGE, 400, 70, "Press [Q] to Quit"); + + msleep(200); + + if (pt_step()) + { + bmp_printf(FONT_LARGE, 400, 70 + 60, "Game Over"); + goto quit; + } + } + +quit: + menu_redraw_blocked = 0; + clrscr(); + running = 0; +} + +unsigned int tetris_keypress(unsigned int key) +{ + if (!running) + { + return 1; + } + + /* Mess with "RNG" */ + pt_rand_x += key; + + /* Tell tetris_task to stop loop when + receiving button input */ + running = 0; + + switch(key) + { + case MODULE_KEY_Q: + clrscr(); + menu_redraw_blocked = 0; + return 0; + case MODULE_KEY_PRESS_LEFT: + pt_handle_input(PT_LEFT); + break; + case MODULE_KEY_PRESS_RIGHT: + pt_handle_input(PT_RIGHT); + break; + case MODULE_KEY_PRESS_UP: + pt_handle_input(PT_ROT); + break; + case MODULE_KEY_PRESS_DOWN: + pt_handle_input(PT_DOWN); + break; + } + + pt_render(); + + running = 1; + return 0; +} + +static MENU_SELECT_FUNC(tetris_start) +{ + menu_redraw_blocked = 1; + task_create("tetris_task", 0x1e, 0x4000, tetris_task, (void*)0); +} + +struct menu_entry tetris_menu[] = +{ + { + .name = "ML Tetris", + .select = tetris_start, + .help = "Tetris on your DSLR", + }, +}; + +unsigned int tetris_init() +{ + menu_add("Games", tetris_menu, COUNT(tetris_menu)); + return 0; +} + +unsigned int tetris_deinit() +{ + return 0; +} + +MODULE_CBRS_START() +MODULE_CBR(CBR_KEYPRESS, tetris_keypress, 0) +MODULE_CBRS_END() + +MODULE_INFO_START() +MODULE_INIT(tetris_init) +MODULE_DEINIT(tetris_deinit) +MODULE_INFO_END() +