From a554ee7edee8e10b38c6899ad8556daf58ca3afe Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 7 Feb 2023 14:33:53 -0700 Subject: [PATCH] cmd: Add 2048 game Add the 2048 game, a good demo of ANSI sequences and a way to waste a little time. Bring it it from Barebox, modified for code style. Signed-off-by: Simon Glass --- cmd/2048.c | 397 ++++++++++++++++++++++++++++++++++++++ cmd/Kconfig | 11 ++ cmd/Makefile | 1 + configs/sandbox_defconfig | 1 + 4 files changed, 410 insertions(+) create mode 100644 cmd/2048.c diff --git a/cmd/2048.c b/cmd/2048.c new file mode 100644 index 0000000000..fa60aa94aa --- /dev/null +++ b/cmd/2048.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2014 Maurits van der Schee + +/* Console version of the game "2048" for GNU/Linux */ + +#include +#include +#include +#include +#include + +#define SIZE 4 +static uint score; + +static void getColor(uint value, char *color, size_t length) +{ + u8 original[] = { + 8, 255, 1, 255, 2, 255, 3, 255, + 4, 255, 5, 255, 6, 255, 7, 255, + 9, 0, 10, 0, 11, 0, 12, 0, 13, + 0, 14, 0, 255, 0, 255, 0}; + u8 *scheme = original; + u8 *background = scheme + 0; + u8 *foreground = scheme + 1; + + if (value > 0) { + while (value >>= 1) { + if (background + 2 < scheme + sizeof(original)) { + background += 2; + foreground += 2; + } + } + } + snprintf(color, length, "\033[38;5;%d;48;5;%dm", *foreground, + *background); +} + +static void drawBoard(u16 board[SIZE][SIZE]) +{ + int x, y; + char color[40], reset[] = "\033[0m"; + + printf("\033[H"); + printf("2048.c %17d pts\n\n", score); + + for (y = 0; y < SIZE; y++) { + for (x = 0; x < SIZE; x++) { + getColor(board[x][y], color, 40); + printf("%s", color); + printf(" "); + printf("%s", reset); + } + printf("\n"); + for (x = 0; x < SIZE; x++) { + getColor(board[x][y], color, 40); + printf("%s", color); + if (board[x][y] != 0) { + char s[8]; + s8 t; + + snprintf(s, 8, "%u", board[x][y]); + t = 7 - strlen(s); + printf("%*s%s%*s", t - t / 2, "", s, t / 2, ""); + } else { + printf(" · "); + } + printf("%s", reset); + } + printf("\n"); + for (x = 0; x < SIZE; x++) { + getColor(board[x][y], color, 40); + printf("%s", color); + printf(" "); + printf("%s", reset); + } + printf("\n"); + } + printf("\n"); + printf(" ←, ↑, →, ↓ or q \n"); + printf("\033[A"); +} + +static int8_t findTarget(u16 array[SIZE], int x, int stop) +{ + int t; + + /* if the position is already on the first, don't evaluate */ + if (x == 0) + return x; + for (t = x - 1; t >= 0; t--) { + if (array[t]) { + if (array[t] != array[x]) { + /* merge is not possible, take next position */ + return t + 1; + } + return t; + } + + /* we should not slide further, return this one */ + if (t == stop) + return t; + } + /* we did not find a */ + return x; +} + +static bool slideArray(u16 array[SIZE]) +{ + bool success = false; + int x, t, stop = 0; + + for (x = 0; x < SIZE; x++) { + if (array[x] != 0) { + t = findTarget(array, x, stop); + /* + * if target is not original position, then move or + * merge + */ + if (t != x) { + /* + * if target is not zero, set stop to avoid + * double merge + */ + if (array[t]) { + score += array[t] + array[x]; + stop = t + 1; + } + array[t] += array[x]; + array[x] = 0; + success = true; + } + } + } + return success; +} + +static void rotateBoard(u16 board[SIZE][SIZE]) +{ + s8 i, j, n = SIZE; + int tmp; + + for (i = 0; i < n / 2; i++) { + for (j = i; j < n - i - 1; j++) { + tmp = board[i][j]; + board[i][j] = board[j][n - i - 1]; + board[j][n - i - 1] = board[n - i - 1][n - j - 1]; + board[n - i - 1][n - j - 1] = board[n - j - 1][i]; + board[n - j - 1][i] = tmp; + } + } +} + +static bool moveUp(u16 board[SIZE][SIZE]) +{ + bool success = false; + int x; + + for (x = 0; x < SIZE; x++) + success |= slideArray(board[x]); + + return success; +} + +static bool moveLeft(u16 board[SIZE][SIZE]) +{ + bool success; + + rotateBoard(board); + success = moveUp(board); + rotateBoard(board); + rotateBoard(board); + rotateBoard(board); + return success; +} + +static bool moveDown(u16 board[SIZE][SIZE]) +{ + bool success; + + rotateBoard(board); + rotateBoard(board); + success = moveUp(board); + rotateBoard(board); + rotateBoard(board); + return success; +} + +static bool moveRight(u16 board[SIZE][SIZE]) +{ + bool success; + + rotateBoard(board); + rotateBoard(board); + rotateBoard(board); + success = moveUp(board); + rotateBoard(board); + return success; +} + +static bool findPairDown(u16 board[SIZE][SIZE]) +{ + bool success = false; + int x, y; + + for (x = 0; x < SIZE; x++) { + for (y = 0; y < SIZE - 1; y++) { + if (board[x][y] == board[x][y + 1]) + return true; + } + } + + return success; +} + +static int16_t countEmpty(u16 board[SIZE][SIZE]) +{ + int x, y; + int count = 0; + + for (x = 0; x < SIZE; x++) { + for (y = 0; y < SIZE; y++) { + if (board[x][y] == 0) + count++; + } + } + return count; +} + +static bool gameEnded(u16 board[SIZE][SIZE]) +{ + bool ended = true; + + if (countEmpty(board) > 0) + return false; + if (findPairDown(board)) + return false; + rotateBoard(board); + if (findPairDown(board)) + ended = false; + rotateBoard(board); + rotateBoard(board); + rotateBoard(board); + + return ended; +} + +static void addRandom(u16 board[SIZE][SIZE]) +{ + int x, y; + int r, len = 0; + u16 n, list[SIZE * SIZE][2]; + + for (x = 0; x < SIZE; x++) { + for (y = 0; y < SIZE; y++) { + if (board[x][y] == 0) { + list[len][0] = x; + list[len][1] = y; + len++; + } + } + } + + if (len > 0) { + r = rand() % len; + x = list[r][0]; + y = list[r][1]; + n = ((rand() % 10) / 9 + 1) * 2; + board[x][y] = n; + } +} + +static int test(void) +{ + u16 array[SIZE]; + u16 data[] = { + 0, 0, 0, 2, 2, 0, 0, 0, + 0, 0, 2, 2, 4, 0, 0, 0, + 0, 2, 0, 2, 4, 0, 0, 0, + 2, 0, 0, 2, 4, 0, 0, 0, + 2, 0, 2, 0, 4, 0, 0, 0, + 2, 2, 2, 0, 4, 2, 0, 0, + 2, 0, 2, 2, 4, 2, 0, 0, + 2, 2, 0, 2, 4, 2, 0, 0, + 2, 2, 2, 2, 4, 4, 0, 0, + 4, 4, 2, 2, 8, 4, 0, 0, + 2, 2, 4, 4, 4, 8, 0, 0, + 8, 0, 2, 2, 8, 4, 0, 0, + 4, 0, 2, 2, 4, 4, 0, 0 + }; + u16 *in, *out; + u16 t, tests; + int i; + bool success = true; + + tests = (sizeof(data) / sizeof(data[0])) / (2 * SIZE); + for (t = 0; t < tests; t++) { + in = data + t * 2 * SIZE; + out = in + SIZE; + for (i = 0; i < SIZE; i++) + array[i] = in[i]; + slideArray(array); + for (i = 0; i < SIZE; i++) { + if (array[i] != out[i]) + success = false; + } + if (!success) { + for (i = 0; i < SIZE; i++) + printf("%d ", in[i]); + printf(" = > "); + for (i = 0; i < SIZE; i++) + printf("%d ", array[i]); + printf("expected "); + for (i = 0; i < SIZE; i++) + printf("%d ", in[i]); + printf(" = > "); + for (i = 0; i < SIZE; i++) + printf("%d ", out[i]); + printf("\n"); + break; + } + } + if (success) + printf("All %u tests executed successfully\n", tests); + + return !success; +} + +static int do_2048(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cli_ch_state cch_s, *cch = &cch_s; + u16 board[SIZE][SIZE]; + bool success; + + if (argc == 2 && strcmp(argv[1], "test") == 0) + return test(); + + score = 0; + + printf("\033[?25l\033[2J\033[H"); + + memset(board, 0, sizeof(board)); + addRandom(board); + addRandom(board); + drawBoard(board); + cli_ch_init(cch); + while (true) { + int c; + + c = cli_ch_process(cch, 0); + if (!c) { + c = getchar(); + c = cli_ch_process(cch, c); + } + switch (c) { + case CTL_CH('b'): /* left arrow */ + success = moveLeft(board); + break; + case CTL_CH('f'): /* right arrow */ + success = moveRight(board); + break; + case CTL_CH('p'):/* up arrow */ + success = moveUp(board); + break; + case CTL_CH('n'): /* down arrow */ + success = moveDown(board); + break; + default: + success = false; + } + if (success) { + drawBoard(board); + mdelay(150); + addRandom(board); + drawBoard(board); + if (gameEnded(board)) { + printf(" GAME OVER \n"); + break; + } + } + if (c == 'q') { + printf(" QUIT \n"); + break; + } + } + + printf("\033[?25h"); + + return 0; +} + +U_BOOT_CMD( + 2048, 2, 1, do_2048, + "The 2048 game", + "Use your arrow keys to move the tiles. When two tiles with " + "the same number touch, they merge into one!" +); diff --git a/cmd/Kconfig b/cmd/Kconfig index 091a0ee21a..e45b8847ae 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1941,6 +1941,17 @@ endif menu "Misc commands" +config CMD_2048 + bool "Play 2048" + help + This is a simple sliding block puzzle game designed by Italian web + developer Gabriele Cirulli. The game's objective is to slide numbered + tiles on a grid to combine them to create a tile with the number + 2048. + + This needs ANSI support on your terminal to work. It is not fully + functional on a video device. + config CMD_BMP bool "Enable 'bmp' command" depends on VIDEO diff --git a/cmd/Makefile b/cmd/Makefile index 054ef42017..6c37521b4e 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -12,6 +12,7 @@ obj-y += panic.o obj-y += version.o # command +obj-$(CONFIG_CMD_2048) += 2048.o obj-$(CONFIG_CMD_ACPI) += acpi.o obj-$(CONFIG_CMD_ADDRMAP) += addrmap.o obj-$(CONFIG_CMD_AES) += aes.o diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 3a1f14c60f..ca95b2c5d2 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -340,3 +340,4 @@ CONFIG_TEST_FDTDEC=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_CMD_2048=y -- 2.39.5