diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | 2048.c | 247 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | play.c | 171 |
4 files changed, 435 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7156d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +2048.scores +play +tags diff --git a/2048.c b/2048.c new file mode 100644 index 0000000..8546d4b --- /dev/null +++ b/2048.c @@ -0,0 +1,247 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <curses.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +typedef unsigned uint; + +static uint score; +static uint grid[4][4]; + +static void spawn(void) { + uint y, x; + do { + y = arc4random_uniform(4); + x = arc4random_uniform(4); + } while (grid[y][x]); + grid[y][x] = (arc4random_uniform(10) ? 1 : 2); +} + +static bool slideLeft(void) { + bool slid = false; + for (uint y = 0; y < 4; ++y) { + uint x = 0; + for (uint i = 0; i < 4; ++i) { + if (grid[y][i] != grid[y][x]) slid = true; + if (grid[y][i]) grid[y][x++] = grid[y][i]; + } + while (x < 4) grid[y][x++] = 0; + } + return slid; +} +static bool slideRight(void) { + bool slid = false; + for (uint y = 0; y < 4; ++y) { + uint x = 3; + for (uint i = 3; i < 4; --i) { + if (grid[y][i] != grid[y][x]) slid = true; + if (grid[y][i]) grid[y][x--] = grid[y][i]; + } + while (x < 4) grid[y][x--] = 0; + } + return slid; +} +static bool slideUp(void) { + bool slid = false; + for (uint x = 0; x < 4; ++x) { + uint y = 0; + for (uint i = 0; i < 4; ++i) { + if (grid[i][x] != grid[y][x]) slid = true; + if (grid[i][x]) grid[y++][x] = grid[i][x]; + } + while (y < 4) grid[y++][x] = 0; + } + return slid; +} +static bool slideDown(void) { + bool slid = false; + for (uint x = 0; x < 4; ++x) { + uint y = 3; + for (uint i = 3; i < 4; --i) { + if (grid[i][x] != grid[y][x]) slid = true; + if (grid[i][x]) grid[y--][x] = grid[i][x]; + } + while (y < 4) grid[y--][x] = 0; + } + return slid; +} + +static bool mergeLeft(void) { + bool merged = false; + for (uint y = 0; y < 4; ++y) { + for (uint x = 0; x < 3; ++x) { + if (!grid[y][x]) continue; + if (grid[y][x] != grid[y][x + 1]) continue; + score += 1 << ++grid[y][x]; + grid[y][x + 1] = 0; + merged = true; + } + } + return merged; +} +static bool mergeRight(void) { + bool merged = false; + for (uint y = 0; y < 4; ++y) { + for (uint x = 3; x > 0; --x) { + if (!grid[y][x]) continue; + if (grid[y][x] != grid[y][x - 1]) continue; + score += 1 << ++grid[y][x]; + grid[y][x - 1] = 0; + merged = true; + } + } + return merged; +} +static bool mergeUp(void) { + bool merged = false; + for (uint x = 0; x < 4; ++x) { + for (uint y = 0; y < 3; ++y) { + if (!grid[y][x]) continue; + if (grid[y][x] != grid[y + 1][x]) continue; + score += 1 << ++grid[y][x]; + grid[y + 1][x] = 0; + merged = true; + } + } + return merged; +} +static bool mergeDown(void) { + bool merged = false; + for (uint x = 0; x < 4; ++x) { + for (uint y = 3; y > 0; --y) { + if (!grid[y][x]) continue; + if (grid[y][x] != grid[y - 1][x]) continue; + score += 1 << ++grid[y][x]; + grid[y - 1][x] = 0; + merged = true; + } + } + return merged; +} + +static bool left(void) { + return slideLeft() | mergeLeft() | slideLeft(); +} +static bool right(void) { + return slideRight() | mergeRight() | slideRight(); +} +static bool up(void) { + return slideUp() | mergeUp() | slideUp(); +} +static bool down(void) { + return slideDown() | mergeDown() | slideDown(); +} + +static void curse(void) { + initscr(); + cbreak(); + noecho(); + curs_set(0); + keypad(stdscr, true); + leaveok(stdscr, true); + start_color(); + use_default_colors(); + short bright = (COLORS > 8 ? 8 : 0); + init_pair(1, COLOR_WHITE, COLOR_RED); + init_pair(2, COLOR_WHITE, COLOR_GREEN); + init_pair(3, COLOR_WHITE, COLOR_YELLOW); + init_pair(4, COLOR_WHITE, COLOR_BLUE); + init_pair(5, COLOR_WHITE, COLOR_MAGENTA); + init_pair(6, COLOR_WHITE, COLOR_CYAN); + init_pair(7, COLOR_WHITE, bright + COLOR_RED); + init_pair(8, COLOR_WHITE, bright + COLOR_GREEN); + init_pair(9, COLOR_WHITE, bright + COLOR_YELLOW); + init_pair(10, COLOR_WHITE, bright + COLOR_BLUE); + init_pair(11, COLOR_WHITE, bright + COLOR_MAGENTA); + init_pair(12, COLOR_WHITE, bright + COLOR_CYAN); + init_pair(13, COLOR_WHITE, COLOR_BLACK); +} + +static void addchn(char ch, uint n) { + for (uint i = 0; i < n; ++i) { + addch(ch); + } +} + +enum { + TileHeight = 3, + TileWidth = 7, + GridY = 2, + GridX = 2, + ScoreY = 0, + ScoreX = GridX + 4 * TileWidth - 10, +}; + +static void drawTile(uint y, uint x) { + if (grid[y][x]) { + attr_set(A_BOLD, 1 + (grid[y][x] - 1) % 12, NULL); + } else { + attr_set(A_NORMAL, 13, NULL); + } + + char buf[8]; + int len = snprintf(buf, sizeof(buf), "%d", 1 << grid[y][x]); + if (!grid[y][x]) buf[0] = '.'; + + move(GridY + TileHeight * y, GridX + TileWidth * x); + addchn(' ', TileWidth); + + move(GridY + TileHeight * y + 1, GridX + TileWidth * x); + addchn(' ', (TileWidth - len + 1) / 2); + addstr(buf); + addchn(' ', (TileWidth - len) / 2); + + move(GridY + TileHeight * y + 2, GridX + TileWidth * x); + addchn(' ', TileWidth); +} + +static void draw(void) { + char buf[11]; + snprintf(buf, sizeof(buf), "%10d", score); + + attr_set(A_NORMAL, 0, NULL); + mvaddstr(ScoreY, ScoreX, buf); + + for (uint y = 0; y < 4; ++y) { + for (uint x = 0; x < 4; ++x) { + drawTile(y, x); + } + } +} + +static bool input(void) { + switch (getch()) { + break; case 'h': case KEY_LEFT: if (left()) spawn(); + break; case 'j': case KEY_DOWN: if (down()) spawn(); + break; case 'k': case KEY_UP: if (up()) spawn(); + break; case 'l': case KEY_RIGHT: if (right()) spawn(); + break; case 'q': return false; + } + return true; +} + +uint play2048(void) { + curse(); + spawn(); + spawn(); + do { + draw(); + } while (input()); + return score; +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..162783a --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CFLAGS += -std=c11 -Wall -Wextra +LDLIBS = -lcurses + +OBJS += play.o +OBJS += 2048.o + +all: tags play + +play: $(OBJS) + $(CC) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@ + +tags: *.c + ctags -w *.c diff --git a/play.c b/play.c new file mode 100644 index 0000000..b062a0f --- /dev/null +++ b/play.c @@ -0,0 +1,171 @@ +/* Copyright (C) 2018 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <curses.h> +#include <err.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/file.h> +#include <sysexits.h> +#include <time.h> + +typedef unsigned uint; + +uint play2048(void); + +enum { ScoresLen = 1000 }; +static struct Score { + time_t date; + uint score; + char name[32]; +} scores[ScoresLen]; + +static FILE *scoresOpen(const char *path) { + int fd = open(path, O_RDWR | O_CREAT); + if (fd < 0) err(EX_CANTCREAT, "%s", path); + FILE *file = fdopen(fd, "r+"); + if (!file) err(EX_CANTCREAT, "%s", path); + return file; +} + +static void scoresLock(FILE *file) { + int error = flock(fileno(file), LOCK_EX); + if (error) err(EX_IOERR, "flock"); +} + +static void scoresRead(FILE *file) { + memset(scores, 0, sizeof(scores)); + rewind(file); + fread(scores, sizeof(struct Score), ScoresLen, file); + if (ferror(file)) err(EX_IOERR, "fread"); +} + +static void scoresWrite(FILE *file) { + rewind(file); + fwrite(scores, sizeof(struct Score), ScoresLen, file); + if (ferror(file)) err(EX_IOERR, "fwrite"); +} + +static size_t scoresInsert(struct Score new) { + if (!new.score) return ScoresLen; + for (size_t i = 0; i < ScoresLen; ++i) { + if (scores[i].score > new.score) continue; + memmove( + &scores[i + 1], &scores[i], + sizeof(struct Score) * (ScoresLen - i - 1) + ); + scores[i] = new; + return i; + } + return ScoresLen; +} + +static void curse(void) { + initscr(); + cbreak(); + echo(); + curs_set(1); + keypad(stdscr, true); + leaveok(stdscr, false); + start_color(); + use_default_colors(); + attr_set(A_NORMAL, 0, NULL); + erase(); +} + +static void addchn(char ch, uint n) { + for (uint i = 0; i < n; ++i) { + addch(ch); + } +} + +enum { + ScoresTop = 15, + ScoresY = 0, + ScoresX = 2, + ScoreX = ScoresX + 5, + NameX = ScoreX + 12, + DateX = NameX + 33, + ScoresWidth = DateX + 11 - ScoresX, +}; +static const char ScoresTitle[] = "TOP SCORES"; + +static int scoreY(size_t i) { + if (i < ScoresTop) return ScoresY + 2 + i; + return ScoresY + 3 + ScoresTop; +} + +static void drawScore(size_t i) { + char buf[11]; + snprintf(buf, sizeof(buf), "%3zu.", 1 + i); + mvaddstr(scoreY(i), ScoresX, buf); + + snprintf(buf, sizeof(buf), "%10d", scores[i].score); + mvaddstr(scoreY(i), ScoreX, buf); + + mvaddstr(scoreY(i), NameX, scores[i].name); + + struct tm *time = localtime(&scores[i].date); + if (!time) err(EX_SOFTWARE, "localtime"); + char date[sizeof("YYYY-MM-DD")]; + strftime(date, sizeof(date), "%F", time); + mvaddstr(scoreY(i), DateX, date); +} + +static void draw(size_t new) { + move(ScoresY, ScoresX + (ScoresWidth - sizeof(ScoresTitle) + 2) / 2); + addstr(ScoresTitle); + move(ScoresY + 1, ScoresX); + addchn('=', ScoresWidth); + for (size_t i = 0; i < ScoresTop; ++i) { + if (!scores[i].score) break; + attr_set(i == new ? A_BOLD : A_NORMAL, 0, NULL); + drawScore(i); + } + if (new == ScoresLen) return; + if (new >= ScoresTop) { + attr_set(A_BOLD, 0, NULL); + drawScore(new); + } +} + +int main(void) { + struct Score new = { .date = time(NULL) }; + new.score = play2048(); + FILE *file = scoresOpen("2048.scores"); + scoresRead(file); + size_t index = scoresInsert(new); + + curse(); + draw(index); + if (index < ScoresLen) { + attr_set(A_BOLD, 0, NULL); + mvgetnstr(scoreY(index), NameX, new.name, sizeof(new.name) - 1); + + scoresLock(file); + scoresRead(file); + scoresInsert(new); + scoresWrite(file); + fclose(file); + } + + curs_set(0); + getch(); + endwin(); +} |