u-boot/cmd/2048.c
Simon Glass a554ee7ede 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 <sjg@chromium.org>
2023-04-06 19:10:08 -04:00

398 lines
7.3 KiB
C

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2014 Maurits van der Schee
/* Console version of the game "2048" for GNU/Linux */
#include <common.h>
#include <cli.h>
#include <command.h>
#include <rand.h>
#include <linux/delay.h>
#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!"
);