845 lines
19 KiB
C
Executable File
845 lines
19 KiB
C
Executable File
/*
|
|
* 2048, by afrangry
|
|
* Copyright (c) 2023 Afrangry Hill <donotcalllist@privacyrequired.com>
|
|
* This work is free. You can redistribute it and/or modify it under the
|
|
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
|
* as published by Sam Hocevar. See the COPYING file for more details.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
|
|
#if defined(MSDOS) || defined(_WIN16)
|
|
#define HAVE_CONIO_H 1
|
|
#elif defined(_WIN32) || defined(_WIN64)
|
|
#define HAVE_WINDOWS_H 1
|
|
#elif defined(__unix) || defined(__unix__) || defined(__APPLE__) || \
|
|
defined(__MACH__)
|
|
#include <unistd.h>
|
|
#define HAVE_TERMIOS_H 1
|
|
#include <termios.h>
|
|
static struct termios old = { 0, 0, 0, 0, '\0', 0, 0, 0 };
|
|
static struct termios new = { 0, 0, 0, 0, '\0', 0, 0, 0 };
|
|
#define HAVE_ANSI_ESCAPE_CODE 1
|
|
#endif
|
|
|
|
#if defined(HAVE_CONIO_H)
|
|
#include <conio.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_WINDOWS_H)
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#define N 4
|
|
typedef struct arr_t {
|
|
int x; /* position on x-axis from left to right */
|
|
int y; /* position on y-axis from top to bottom */
|
|
} arr_t; /* position in a 2D map with x and y value */
|
|
enum ctrl { UP, LEFT, DOWN, RIGHT, QUIT, UNDEFINED };
|
|
|
|
static int hiscore = 0; /* highest score of user */
|
|
static int hinum = 0; /* highest number of user */
|
|
static int score = 0; /* score of current game */
|
|
static int number = 0; /* highest number of current game */
|
|
|
|
static int getindex(int *, int, int *) /*@*/;
|
|
static int *arr2ptr(/*@returned@*/ int *, arr_t) /*@*/;
|
|
static arr_t ptr2arr(int *, int *) /*@*/;
|
|
/*@unused@*/ static int
|
|
tty_break(void) /*@globals old, new@*/ /*@modifies new@*/;
|
|
static int tty_getch(void) /*@*/;
|
|
/*@unused@*/ static int tty_fix(void) /*@globals old*/ /*@modifies nothing@*/;
|
|
static void tty_cls(void) /*@globals old, new@*/ /*@modifies new@*/;
|
|
static void map_init(int *map) /*@modifies *map@*/;
|
|
static void cpymap(int *dst, int *) /*@modifies *dst@*/;
|
|
static void trans(int *map, int) /*@modifies *map@*/;
|
|
static void prtwelcome(void) /*@globals fileSystem, hiscore, hinum@*/
|
|
/*@modifies fileSystem@*/;
|
|
static int moveup1(int *map, int *ptr) /*@modifies *map, *ptr@*/;
|
|
static void asgn_if_src_true(int *dst, int) /*@modifies *dst@*/;
|
|
static int moveup(int *map) /*@modifies *map@*/;
|
|
static int eat1(int *map, int *ptr) /*@globals score@*/
|
|
/*@modifies *map, *ptr, score@*/;
|
|
static int eat(int *map) /*@globals score@*/ /*@modifies *map, score@*/;
|
|
static int upeat(int *map) /*@globals score@*/ /*@modifies *map, score@*/;
|
|
static void puttb(void) /*@globals fileSystem@*/ /*@modifies fileSystem@*/;
|
|
static void putm(void) /*@globals fileSystem@*/ /*@modifies fileSystem@*/;
|
|
static void putnumline(int *, int) /*@globals fileSystem@*/
|
|
/*@modifies fileSystem@*/;
|
|
static void prt(int *) /*@globals fileSystem@*/ /*@modifies fileSystem@*/;
|
|
static int genrnd(int *map) /*@globals internalState@*/
|
|
/*@modifies internalState, *map@*/;
|
|
static int calhinum(int *) /*@*/;
|
|
static int same_as_follower(int *, int *) /*@*/;
|
|
static int is_over(int *) /*@*/;
|
|
static enum ctrl input_to_ctrl(char) /*@*/;
|
|
static int ctrl_to_trans(enum ctrl) /*@*/;
|
|
static int turn(int *map) /*@globals internalState, fileSystem, score@*/
|
|
/*@modifies internalState, fileSystem, score, *map@*/;
|
|
static void game(int *map) /*@globals internalState, fileSystem, score@*/
|
|
/*@modifies internalState, fileSystem, score, *map@*/;
|
|
static void gg(int *)
|
|
/*@globals internalState, fileSystem, number, hinum, score, hiscore@*/
|
|
/*@modifies internalState, fileSystem, number, hinum, score, hiscore@*/;
|
|
|
|
/*
|
|
* get index of pointer
|
|
* 'arr': pointer to first element of array
|
|
* 'arrsize': size of array
|
|
* 'ptr': pointer
|
|
*/
|
|
static int /* return index of pinter, -1 when failed */
|
|
getindex(int *arr, int arrsize, int *ptr) /*@*/
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; i < arrsize; i++) {
|
|
if (&arr[i] == ptr) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* convert arr type to pointer in array
|
|
* 'start': pointer to first element of array
|
|
* 'arr': arr to be converted
|
|
*/
|
|
static int * /* return pointer to given arr */
|
|
arr2ptr(/*@returned@*/ int *start, arr_t arr) /*@*/
|
|
{
|
|
return &start[arr.x + N * arr.y];
|
|
}
|
|
|
|
/*
|
|
* convert pointer in array to arr type
|
|
* 'start': pointer to first element of array
|
|
* 'ptr': pointer to be converted
|
|
*/
|
|
static arr_t /* return arr of given pointer */
|
|
ptr2arr(int *start, int *ptr) /*@*/
|
|
{
|
|
arr_t arr = { 0, 0 }; /* arr of given tile */
|
|
int ptrindex =
|
|
getindex(start, N * N, ptr); /* index of pointer in array */
|
|
|
|
arr.x = ptrindex % N;
|
|
arr.y = ptrindex / N;
|
|
return arr;
|
|
}
|
|
|
|
/*
|
|
* changes terminal behavior to not wait for enter, not echo
|
|
*/
|
|
static int /* return 0 on success, -1 otherwise */
|
|
tty_break(void) /*@globals new, old@*/ /*@modifies new@*/
|
|
{
|
|
#if defined(HAVE_CONIO_H)
|
|
return 0;
|
|
#elif defined(HAVE_WINDOWS_H)
|
|
return 0;
|
|
#elif defined(HAVE_TERMIOS_H)
|
|
if (tcgetattr(0, &old) == -1) {
|
|
return -1;
|
|
}
|
|
new = old;
|
|
new.c_lflag &= ~ICANON;
|
|
new.c_lflag &= ~ECHO;
|
|
if (tcsetattr(0, TCSANOW, &new) == -1) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* get single character without waiting for enter or echoing
|
|
* equivalent to getchar() if not supported by system
|
|
*/
|
|
static int /* return input character, EOF on failure */
|
|
tty_getch(void) /*@globals old, new*/ /*@modifies new@*/
|
|
{
|
|
#if defined(HAVE_CONIO_H)
|
|
return getch();
|
|
#elif defined(HAVE_WINDOWS_H)
|
|
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
|
|
INPUT_RECORD irInputRecord;
|
|
DWORD dwEventsRead;
|
|
CHAR cChar;
|
|
|
|
while (ReadConsoleInputA(hStdin, &irInputRecord, 1,
|
|
&dwEventsRead)) /* Read key press */
|
|
if (irInputRecord.EventType == KEY_EVENT &&
|
|
irInputRecord.Event.KeyEvent.wVirtualKeyCode != VK_SHIFT &&
|
|
irInputRecord.Event.KeyEvent.wVirtualKeyCode != VK_MENU &&
|
|
irInputRecord.Event.KeyEvent.wVirtualKeyCode !=
|
|
VK_CONTROL) {
|
|
cChar = irInputRecord.Event.KeyEvent.uChar.AsciiChar;
|
|
ReadConsoleInputA(
|
|
hStdin, &irInputRecord, 1,
|
|
&dwEventsRead); /* Read key release */
|
|
return cChar;
|
|
}
|
|
return EOF;
|
|
#elif defined(HAVE_TERMIOS_H)
|
|
int ch;
|
|
|
|
if (tty_break() == -1) {
|
|
return EOF;
|
|
}
|
|
ch = getchar();
|
|
if (tty_fix() == -1) {
|
|
return EOF;
|
|
}
|
|
return ch;
|
|
#else
|
|
return getchar();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* restore terminal behavior to original state
|
|
*/
|
|
static int /* return 0 on success, -1 otherwise */
|
|
tty_fix(void) /*@globals old*/ /*@modifies nothing@*/
|
|
{
|
|
#if defined(HAVE_CONIO_H)
|
|
return 0;
|
|
#elif defined(HAVE_WINDOWS_H)
|
|
return 0;
|
|
#elif defined(HAVE_TERMIOS_H)
|
|
return tcsetattr(0, TCSANOW, &old);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* clear terminal screen
|
|
*/
|
|
static void
|
|
tty_cls(void) /*@globals fileSystem@*/ /*@modifies fileSystem@*/
|
|
{
|
|
#if defined(HAVE_CONIO_H)
|
|
return clrscr();
|
|
#elif defined(HAVE_WINDOWS_H)
|
|
HANDLE hStdOut;
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
DWORD count;
|
|
DWORD cellCount;
|
|
COORD homeCoords = { 0, 0 };
|
|
|
|
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
if (hStdOut == INVALID_HANDLE_VALUE) {
|
|
return;
|
|
}
|
|
|
|
/* Get the number of cells in the current buffer */
|
|
if (!GetConsoleScreenBufferInfo(hStdOut, &csbi)) {
|
|
return;
|
|
}
|
|
cellCount = csbi.dwSize.X * csbi.dwSize.Y;
|
|
|
|
/* Fill the entire buffer with spaces */
|
|
if (!FillConsoleOutputCharacter(hStdOut, (TCHAR)' ', cellCount,
|
|
homeCoords, &count)) {
|
|
return;
|
|
}
|
|
|
|
/* Fill the entire buffer with the current colors and attributes */
|
|
if (!FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, cellCount,
|
|
homeCoords, &count)) {
|
|
return;
|
|
}
|
|
|
|
/* Move the cursor home */
|
|
SetConsoleCursorPosition(hStdOut, homeCoords);
|
|
#elif defined(HAVE_ANSI_ESCAPE_CODE)
|
|
printf("\033[2J");
|
|
printf("\033[%d;%dH", 1, 1);
|
|
#else
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* fill map with 0
|
|
*/
|
|
static void
|
|
map_init(int *map) /*@modifies *map@*/
|
|
{
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
for (j = 0; j < N; j++) {
|
|
arr_t tmparr = { 0, 0 }; /* temp arr */
|
|
|
|
tmparr.x = j;
|
|
tmparr.y = i;
|
|
*arr2ptr(map, tmparr) = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* copy source map to destination map
|
|
* 'dst': pointer to first element of destination map
|
|
* 'src': pointer to first element of source map
|
|
*/
|
|
static void
|
|
cpymap(int *dst, int *src) /*@modifies *dst@*/
|
|
{
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
for (j = 0; j < N; j++) {
|
|
*dst = *src;
|
|
src++;
|
|
dst++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rotate the map clockwise for a given time, persumes square map
|
|
* 'map': pointer to first element of map
|
|
* 'times': given time to rotate
|
|
*/
|
|
static void
|
|
trans(int *map, int times) /*@modifies *map@*/
|
|
{
|
|
int tmp[N][N] = { { 0 } }; /* temp map */
|
|
int *ptrtmp = &tmp[0][0]; /* pointer to temp map */
|
|
int *curmap = map; /* current pointer to temp map, iteration */
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
if (times == 0) {
|
|
return;
|
|
}
|
|
map_init(ptrtmp);
|
|
for (i = 0; i < N; i++) {
|
|
for (j = 0; j < N; j++) {
|
|
arr_t maparr = ptr2arr(map, curmap); /* current arr of
|
|
map */
|
|
arr_t tmparr = { 0, 0 }; /* current arr of temp map */
|
|
|
|
tmparr.x = N - maparr.y - 1;
|
|
tmparr.y = maparr.x;
|
|
*arr2ptr(ptrtmp, tmparr) = *curmap;
|
|
curmap++;
|
|
}
|
|
}
|
|
cpymap(map, ptrtmp);
|
|
trans(map, times - 1);
|
|
}
|
|
|
|
/*
|
|
* print welcome screen
|
|
*/
|
|
static void
|
|
prtwelcome(void) /*@globals fileSystem, hiscore, hinum@*/
|
|
/*@modifies fileSystem@*/
|
|
{
|
|
(void)puts("-------------------------");
|
|
(void)puts("| 2048 |");
|
|
(void)puts("|-----------------------|");
|
|
(void)printf("| High Score: %8d |\n", hiscore);
|
|
(void)printf("| High Number: %8d |\n", hinum);
|
|
(void)puts("|-----------------------|");
|
|
(void)puts("| w=Up s=Down |");
|
|
(void)puts("| a=Left d=Right q=Quit |");
|
|
(void)puts("-------------------------");
|
|
(void)puts("Press space to continue... ");
|
|
}
|
|
|
|
/*
|
|
* move given tile up until border or another non-empty tile
|
|
* 'map': pointer to first element of map
|
|
* 'ptr': pointer to tile to be moved
|
|
*/
|
|
static int /* return 1 if change is made, 0 otherwise */
|
|
moveup1(int *map, int *ptr) /*@modifies *map, *ptr@*/
|
|
{
|
|
arr_t curarr = ptr2arr(map, ptr); /* arr of ptr */
|
|
arr_t tmp = { 0, 0 }; /* temp arr for tile up 1 of curarr */
|
|
int *ptrtmp = NULL; /* pointer to tile up 1 of curarr */
|
|
|
|
if (curarr.y == 0) {
|
|
return 0;
|
|
}
|
|
if (*ptr == 0) {
|
|
return 0;
|
|
}
|
|
tmp.x = curarr.x;
|
|
tmp.y = curarr.y - 1;
|
|
ptrtmp = arr2ptr(map, tmp);
|
|
if (*ptrtmp > 0) {
|
|
return 0;
|
|
}
|
|
*ptrtmp = *ptr;
|
|
*ptr = 0;
|
|
(void)moveup1(map, ptrtmp);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* assign src to dst if src is true, otherwise do nothing
|
|
* 'dst': pointer to destination to be assigned
|
|
* 'src': source
|
|
*/
|
|
static void
|
|
asgn_if_src_true(int *dst, int src) /*@modifies *dst@*/
|
|
{
|
|
if (src == 1) {
|
|
*dst = src;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* recursively move all tiles up until border or non-empty tile
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static int /* return 1 if change is made, 0 otherwise */
|
|
moveup(int *map) /*@modifies *map@*/
|
|
{
|
|
int flag = 0; /* 1 if change is made, 0 otherwise */
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
for (j = 0; j < N; j++) {
|
|
arr_t tmp = { 0, 0 }; /* temp arr for current tile */
|
|
int *ptrtmp = NULL; /* pointer to current tile */
|
|
|
|
tmp.x = i;
|
|
tmp.y = j;
|
|
ptrtmp = arr2ptr(map, tmp);
|
|
asgn_if_src_true(&flag, moveup1(map, ptrtmp));
|
|
}
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
/*
|
|
* if tile up 1 of given tile is the same as given tile,
|
|
* double tile up 1 and clear given tile
|
|
* 'map': pointer to first element of map
|
|
* 'ptr': pointer to tile to eat up 1
|
|
*/
|
|
static int /* return 1 if change is made, 0 otherwise */
|
|
eat1(int *map, int *ptr) /*@globals score@*/ /*@modifies *map, *ptr, score@*/
|
|
{
|
|
arr_t curarr = ptr2arr(map, ptr); /* arr of ptr */
|
|
int *ptrcur = ptr; /* ptr, redundant */
|
|
arr_t uparr = { 0, 0 }; /* arr of tile up 1 of curarr */
|
|
int *ptrup = NULL; /* pointer tile up 1 of curarr */
|
|
|
|
if (*ptr == 0) {
|
|
return 0;
|
|
}
|
|
uparr.x = curarr.x;
|
|
uparr.y = curarr.y - 1;
|
|
ptrup = arr2ptr(map, uparr);
|
|
if (*ptrcur != *ptrup) {
|
|
return 0;
|
|
}
|
|
*ptrup += *ptrcur;
|
|
*ptrcur = 0;
|
|
score += *ptrup;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* recursively eat all tiles up, tiles that have eaten do not eat again.
|
|
* start from top to bottom
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static int /* return 1 if change is made, 0 otherwise */
|
|
eat(int *map) /*@globals score@*/ /*@modifies *map, score@*/
|
|
{
|
|
int flag = 0; /* 1 if change is made, 0 otherwise */
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
for (j = 1; j < N; j++) {
|
|
arr_t tmp = { 0, 0 }; /* temp arr of current tile */
|
|
int *ptrtmp = NULL; /* temp pointer to current tile */
|
|
|
|
tmp.x = i;
|
|
tmp.y = j;
|
|
ptrtmp = arr2ptr(map, tmp);
|
|
asgn_if_src_true(&flag, eat1(map, ptrtmp));
|
|
}
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
/*
|
|
* one operation of moving up and eating tiles
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static int /* return 1 if change is made, 0 otherwise */
|
|
upeat(int *map) /*@globals score@*/ /*@modifies *map, score@*/
|
|
{
|
|
int haschanged = 0; /* 1 if change is made, 0 otherwise */
|
|
|
|
asgn_if_src_true(&haschanged, moveup(map));
|
|
asgn_if_src_true(&haschanged, eat(map));
|
|
asgn_if_src_true(&haschanged, moveup(map));
|
|
return haschanged;
|
|
}
|
|
|
|
/*
|
|
* print top and bottom border of map
|
|
*/
|
|
static void
|
|
puttb(void) /*@globals fileSystem@*/ /*@modifies fileSystem@*/
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; i < N * 6 + 1; i++) {
|
|
(void)putchar('-');
|
|
}
|
|
(void)putchar('\n');
|
|
}
|
|
|
|
/*
|
|
* print middle border of map
|
|
*/
|
|
static void
|
|
putm(void) /*@globals fileSystem@*/ /*@modifies fileSystem@*/
|
|
{
|
|
int i = 0;
|
|
|
|
(void)putchar('|');
|
|
for (i = 0; i < N * 6 - 1; i++) {
|
|
(void)putchar('-');
|
|
}
|
|
(void)putchar('|');
|
|
(void)putchar('\n');
|
|
}
|
|
|
|
/*
|
|
* print given row of map with numbers
|
|
* 'map': pointer to first element of map
|
|
* 'line': row to print
|
|
*/
|
|
static void
|
|
putnumline(int *map, int line) /*@globals fileSystem@*/
|
|
/*@modifies fileSystem@*/
|
|
{
|
|
int i = 0;
|
|
|
|
(void)putchar('|');
|
|
for (i = 0; i < N; i++) {
|
|
arr_t tmp = { 0, 0 }; /* temp arr of current tile */
|
|
int *ptrtmp = NULL; /* pointer to current tile */
|
|
|
|
tmp.x = i;
|
|
tmp.y = line;
|
|
ptrtmp = arr2ptr(map, tmp);
|
|
if (*ptrtmp != 0) {
|
|
(void)printf("%5d|", *ptrtmp);
|
|
} else {
|
|
(void)printf(" |");
|
|
}
|
|
}
|
|
(void)putchar('\n');
|
|
}
|
|
|
|
/*
|
|
* print map
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static void
|
|
prt(int *map) /*@globals fileSystem@*/ /*@modifies fileSystem@*/
|
|
{
|
|
int i = 0;
|
|
|
|
puttb();
|
|
putnumline(map, 0);
|
|
for (i = 1; i < N; i++) {
|
|
putm();
|
|
putnumline(map, i);
|
|
}
|
|
puttb();
|
|
}
|
|
|
|
/*
|
|
* randomly generate 2 or 4 in empty tile in map, 90% 2, 10% 4
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static int /* return 1 if no empty tiles left, 0 otherwise */
|
|
genrnd(int *map) /*@globals internalState@*/ /*@modifies internalState, *map@*/
|
|
{
|
|
int rnd = rand() % 10 == 0 ? 4 : 2; /* random number, 90% 2 and 10% 4
|
|
*/
|
|
int cnt = 0; /* count of empty tiles in map */
|
|
arr_t empty[N * N] = { { 0, 0 } }; /* array of arr of empty tiles in
|
|
map */
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
for (j = 0; j < N; j++) {
|
|
arr_t tmp = { 0, 0 }; /* temp arr of current tile */
|
|
int *ptrtmp = NULL; /* pointer to current tile */
|
|
|
|
tmp.x = j;
|
|
tmp.y = i;
|
|
ptrtmp = arr2ptr(map, tmp);
|
|
if (*ptrtmp > 0) {
|
|
/*@innercontinue@*/
|
|
continue;
|
|
}
|
|
empty[cnt] = tmp;
|
|
cnt++;
|
|
}
|
|
}
|
|
if (cnt == 0) {
|
|
return 1;
|
|
}
|
|
*arr2ptr(map, empty[rand() % cnt]) = rnd;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* calculate highest number of current game
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static int /* return highest number of current game */
|
|
calhinum(int *map) /*@*/
|
|
{
|
|
int num = 0; /* current highest number */
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
for (j = 0; j < N; j++) {
|
|
arr_t tmp = { 0, 0 }; /* temp arr of current tile */
|
|
int *ptrtmp = NULL; /* temp pointer to current tile */
|
|
|
|
tmp.x = j;
|
|
tmp.y = i;
|
|
ptrtmp = arr2ptr(map, tmp);
|
|
if (*ptrtmp <= num) {
|
|
/*@innercontinue@*/
|
|
continue;
|
|
}
|
|
num = *ptrtmp;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
/*
|
|
* check if given tile is the same number as adjacent right or down tile
|
|
* 'map': pointer to first element of map
|
|
* 'ptr': pointer to given tile
|
|
*/
|
|
static int /* return 1 if the same, 0 otherwise */
|
|
same_as_follower(int *map, int *ptr) /*@*/
|
|
{
|
|
arr_t tmp = ptr2arr(map, ptr); /* temp arr of given tile */
|
|
|
|
if (tmp.x < N - 1) {
|
|
arr_t tmpright = { 0, 0 }; /* temp arr of right of given tile
|
|
*/
|
|
|
|
tmpright.x = tmp.x + 1;
|
|
tmpright.y = tmp.y;
|
|
if (*ptr == *arr2ptr(map, tmpright)) {
|
|
return 1;
|
|
}
|
|
}
|
|
if (tmp.y < N - 1) {
|
|
arr_t tmpdown = { 0, 0 }; /* temp arr of down of given tile */
|
|
|
|
tmpdown.x = tmp.x;
|
|
tmpdown.y = tmp.y + 1;
|
|
if (*ptr == *arr2ptr(map, tmpdown)) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* check if game is over
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static int /* return 1 if game is over, 0 otherwise */
|
|
is_over(int *map) /*@*/
|
|
{
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (i = 0; i < N; i++) {
|
|
for (j = 0; j < N; j++) {
|
|
arr_t tmp = { 0, 0 }; /* temp arr of current tile */
|
|
int *ptrtmp = NULL; /* temp pointer to current tile */
|
|
|
|
tmp.x = j;
|
|
tmp.y = i;
|
|
ptrtmp = arr2ptr(map, tmp);
|
|
if (*ptrtmp == 0) {
|
|
return 0;
|
|
}
|
|
if (same_as_follower(map, ptrtmp) == 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* convert input character to ctrl enum
|
|
* 'input': input character
|
|
*/
|
|
static enum ctrl /* return ctrl */
|
|
input_to_ctrl(char input) /*@*/
|
|
{
|
|
switch (input) {
|
|
case 'w':
|
|
return UP;
|
|
case 'a':
|
|
return LEFT;
|
|
case 's':
|
|
return DOWN;
|
|
case 'd':
|
|
return RIGHT;
|
|
case 'q':
|
|
return QUIT;
|
|
default:
|
|
return UNDEFINED;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* convert ctrl enum to rotation time
|
|
* 'ctrl': input character
|
|
*/
|
|
static int /* return rotation time, -1 if ctrl is UNDEFINED or QUIT */
|
|
ctrl_to_trans(enum ctrl ctrl) /*@*/
|
|
{
|
|
switch (ctrl) {
|
|
case UP:
|
|
return 0;
|
|
case LEFT:
|
|
return 1;
|
|
case DOWN:
|
|
return 2;
|
|
case RIGHT:
|
|
return 3;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* one turn of game
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static int /* return 1 if ended, 0 otherwise */
|
|
turn(int *map) /*@globals internalState, fileSystem, score@*/
|
|
/*@modifies internalState, fileSystem, score, *map@*/
|
|
{
|
|
int haschanged = 0; /* 1 if change is made, 0 otherwise */
|
|
char input = (char)tty_getch(); /* input character */
|
|
enum ctrl tmp = input_to_ctrl(input); /* ctrl enum */
|
|
int times = 0; /* rotation time */
|
|
|
|
if (tmp == UNDEFINED) {
|
|
return 0;
|
|
}
|
|
if (tmp == QUIT) {
|
|
return 1;
|
|
}
|
|
times = ctrl_to_trans(tmp);
|
|
trans(map, times);
|
|
haschanged = upeat(map);
|
|
trans(map, (4 - (int)tmp) % 4);
|
|
if (haschanged == 0) {
|
|
return 0;
|
|
}
|
|
tty_cls();
|
|
(void)genrnd(map);
|
|
prt(map);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* one game
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static void
|
|
game(int *map) /*@globals internalState, fileSystem, score@*/
|
|
/*@modifies internalState, fileSystem, score, *map@*/
|
|
{
|
|
int end = 0; /* 1 if game has ended, 0 otherwise */
|
|
|
|
(void)genrnd(map);
|
|
(void)genrnd(map);
|
|
tty_cls();
|
|
prt(map);
|
|
while (end == 0 && is_over(map) == 0) {
|
|
end = turn(map);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* print gameover screen and update highest number and highest score
|
|
* 'map': pointer to first element of map
|
|
*/
|
|
static void
|
|
gg(int *map)
|
|
/*@globals internalState, fileSystem, number, hinum, score, hiscore@*/
|
|
/*@modifies internalState, fileSystem, number, hinum, score, hiscore@*/
|
|
{
|
|
number = calhinum(map);
|
|
if (number > hinum) {
|
|
hinum = number;
|
|
}
|
|
if (score > hiscore) {
|
|
hiscore = score;
|
|
}
|
|
(void)printf(
|
|
"Score: %8d High Number: %5d Press space to continue...\n",
|
|
score, number);
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
/*@globals internalState, fileSystem, number, hinum, score, hiscore@*/
|
|
/*@modifies internalState, fileSystem, number, hinum, score, hiscore@*/
|
|
{
|
|
int map[N][N] = { { 0 } }; /* map of tiles */
|
|
int *ptrmap = &map[0][0]; /* pointer to first element of map */
|
|
|
|
map_init(ptrmap);
|
|
srand((unsigned int)time(NULL));
|
|
do {
|
|
map_init(ptrmap);
|
|
tty_cls();
|
|
prtwelcome();
|
|
if ((char)tty_getch() != ' ') {
|
|
break;
|
|
}
|
|
game(ptrmap);
|
|
gg(ptrmap);
|
|
} while ((char)tty_getch() == ' ');
|
|
tty_cls();
|
|
return 0;
|
|
}
|