Merge branch 'devel' into master branch
This commit is contained in:
commit
1c73dbbeda
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/2048*
|
2048*
|
||||||
*~
|
*.o
|
||||||
|
*.core
|
||||||
|
|
40
Makefile
40
Makefile
|
@ -1,25 +1,29 @@
|
||||||
ASCIIDOC ?= a2x
|
CC ?= clang
|
||||||
CC ?= gcc
|
CFLAGS += -g -Wall -Wextra
|
||||||
CFLAGS := -Wall -Wextra $(CFLAGS)
|
LFLAGS +=
|
||||||
LIBS = -lcurses
|
DEFINES := -DVT100 $(shell pkg-config --cflags sdl2)
|
||||||
|
|
||||||
.PHONY: clean man man-nc
|
PROGRAM := 2048
|
||||||
|
C_FILES := $(wildcard src/*.c)
|
||||||
|
O_FILES := $(addprefix obj/,$(notdir $(C_FILES:.c=.o)))
|
||||||
|
|
||||||
2048: src/2048.c
|
all: curses
|
||||||
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) src/2048.c -o 2048
|
|
||||||
|
|
||||||
2048nc: src/2048.c
|
curses: $(O_FILES)
|
||||||
$(CC) -DNO_CURSES=1 $(CFLAGS) $(LDFLAGS) src/2048.c -o 2048nc
|
$(CC) $(filter-out obj/gfx%.o, $(O_FILES)) obj/gfx_curses.o -o $(PROGRAM) -lcurses
|
||||||
|
|
||||||
all: 2048
|
vt100: $(O_FILES)
|
||||||
|
$(CC) $(filter-out obj/gfx%.o, $(O_FILES)) obj/gfx_terminal.o -o $(PROGRAM)
|
||||||
|
|
||||||
|
sdl: $(O_FILES)
|
||||||
|
$(CC) $(filter-out obj/gfx%.o, $(O_FILES)) obj/gfx_sdl.o -o $(PROGRAM) -lSDL2 -lSDL2_ttf
|
||||||
|
|
||||||
|
obj/%.o: src/%.c
|
||||||
|
$(CC) $(DEFINES) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
remake: clean all
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f 2048 2048nc
|
rm -f $(O_FILES) $(PROGRAM)
|
||||||
|
|
||||||
man:
|
.PHONY: clean remake
|
||||||
rm -f man/2048.1
|
|
||||||
$(ASCIIDOC) -d manpage -f manpage man/2048.1.txt
|
|
||||||
|
|
||||||
man-nc:
|
|
||||||
rm -f man/2048nc.1
|
|
||||||
$(ASCIIDOC) -d manpage -f manpage man/2048nc.1.txt
|
|
||||||
|
|
16
README.md
16
README.md
|
@ -1,22 +1,18 @@
|
||||||
#2048-cli
|
#2048-cli
|
||||||
|
|
||||||
A cli version of the game [2048](https://github.com/gabrielecirulli/2048) for your Linux
|
A cli version/engine of the game [2048](https://github.com/gabrielecirulli/2048) for your Linux
|
||||||
terminal.
|
terminal.
|
||||||
|
|
||||||
#####2048_curses.c
|
|
||||||
![Screenshot](http://i.imgur.com/QU7t5mH.png)
|
![Screenshot](http://i.imgur.com/QU7t5mH.png)
|
||||||
|
|
||||||
#####2048_no_curses.c
|
There currently are 3 versions that can be run. These include a straight-forward terminal
|
||||||
![Screenshot](http://i.imgur.com/fwZEvdh.png)
|
based, and two using the ncurses and SDL libraries. To add a new graphical version, simply
|
||||||
|
create a .c file which implements all the functions in gfx.h and add a Makefile entry.
|
||||||
## Installation
|
|
||||||
This requires the ncurses library to link against during compilation. It is available
|
|
||||||
in most package managers. The program creates and uses a file name `.hs2048g` in the
|
|
||||||
working directory. Any file with this name will be modified and replaced.
|
|
||||||
|
|
||||||
### Get
|
### Get
|
||||||
git clone https://github.com/Tiehuis/2048-cli.git
|
git clone https://github.com/Tiehuis/2048-cli.git
|
||||||
make
|
make
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
./2048
|
./2048
|
||||||
|
|
||||||
|
@ -27,5 +23,7 @@ working directory. Any file with this name will be modified and replaced.
|
||||||
-c Enables color support (ncurses version only)
|
-c Enables color support (ncurses version only)
|
||||||
-C Disables color support (ncurses version only)
|
-C Disables color support (ncurses version only)
|
||||||
|
|
||||||
|
Fonts used in SDL version can be found [here](www.openfontlibrary.org).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
This code is licensed under the [MIT License](https://github.com/Tiehuis/2048-cli/blob/master/LICENSE).
|
This code is licensed under the [MIT License](https://github.com/Tiehuis/2048-cli/blob/master/LICENSE).
|
||||||
|
|
150
man/2048.1
150
man/2048.1
|
@ -1,92 +1,58 @@
|
||||||
'\" t
|
.TH 2048 1
|
||||||
.\" Title: \e20\e4\e8
|
|
||||||
.\" Author: [see the "AUTHOR" section]
|
.SH NAME
|
||||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
2048 \- play the game 2048 in your terminal
|
||||||
.\" Date: 12/05/2014
|
|
||||||
.\" Manual: \ \&
|
.SH SYNOPSIS
|
||||||
.\" Source: \ \&
|
.B 2048
|
||||||
.\" Language: English
|
[\fB\-hrcCaA\fR]
|
||||||
.\"
|
[\fB\-s\fR \fISIZE\fR]
|
||||||
.TH "\E20\E4\E8" "1" "12/05/2014" "\ \&" "\ \&"
|
[\fB\-b\fR \fIRATE\fR]
|
||||||
.\" -----------------------------------------------------------------
|
[\fB\-g\fR \fIGOAL\fR]
|
||||||
.\" * Define some portability stuff
|
|
||||||
.\" -----------------------------------------------------------------
|
.SH DESCRIPTION
|
||||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
.B 2048
|
||||||
.\" http://bugs.debian.org/507673
|
is an implementation of the popular game, 2048, designed to be run on a
|
||||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
terminal. It is deisgned to be easy to understand and extend.
|
||||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
.ie \n(.g .ds Aq \(aq
|
.SH CONTROLS
|
||||||
.el .ds Aq '
|
.TP
|
||||||
.\" -----------------------------------------------------------------
|
.BR hjkl " " and " " wasd
|
||||||
.\" * set default formatting
|
Default movement keys. Depending on the graphics implementation, there may be
|
||||||
.\" -----------------------------------------------------------------
|
extra alternatives.
|
||||||
.\" disable hyphenation
|
.TP
|
||||||
.nh
|
.BR \q
|
||||||
.\" disable justification (adjust text to left margin only)
|
Quit the current game.
|
||||||
.ad l
|
|
||||||
.\" -----------------------------------------------------------------
|
.SH OPTIONS
|
||||||
.\" * MAIN CONTENT STARTS HERE *
|
.TP
|
||||||
.\" -----------------------------------------------------------------
|
.BR \-h
|
||||||
.SH "NAME"
|
Print the program usage.
|
||||||
2048 \- The game 2048 for your Linux terminal
|
.TP
|
||||||
.SH "SYNOPSIS"
|
.BR \-c
|
||||||
.sp
|
Turn on color support (default).
|
||||||
\fB2048\fR [\fIOPTIONS\fR]
|
.TP
|
||||||
.SH "DESCRIPTION"
|
.BR \-C
|
||||||
.sp
|
Turn off color support.
|
||||||
A cli version of the game 2048 for your Linux terminal\&.
|
.TP
|
||||||
.SH "OPTIONS"
|
.BR \-a
|
||||||
.PP
|
Turn on animations (default).
|
||||||
\fB\-s\fR \fIsize\fR
|
.TP
|
||||||
.RS 4
|
.BR \-A
|
||||||
Set the grid border length to
|
Turn off animations.
|
||||||
\fIsize\fR\&.
|
.TP
|
||||||
.RE
|
.BR \-s " " \fISIZE\fR
|
||||||
.PP
|
Set the size of the playing field. Default is 4. Maximum value is 16, minimum is 4.
|
||||||
\fB\-b\fR \fIrate\fR
|
.TP
|
||||||
.RS 4
|
.BR \-b " " \fIRATE\fR
|
||||||
Set the block spawn rate to
|
Set the rate at which blocks are spawned. Default is 1.
|
||||||
\fIrate\fR\&.
|
.TP
|
||||||
.RE
|
.BR \-g " " \fIGOAL\fR
|
||||||
.PP
|
Set the target end condition. Default is 2048.
|
||||||
\fB\-r\fR
|
|
||||||
.RS 4
|
.SH AUTHORS
|
||||||
Resets hiscore\&. Will prompt user\&.
|
Originally written by Marc Tiehuis.
|
||||||
.RE
|
All contributions can be found at \fIhttps://github.com/Tiehuis/2048-cli\fR.
|
||||||
.PP
|
|
||||||
\fB\-c\fR
|
.SH COPYRIGHT
|
||||||
.RS 4
|
MIT License (2014)
|
||||||
Enables color support\&.
|
|
||||||
.RE
|
|
||||||
.PP
|
|
||||||
\fB\-C\fR
|
|
||||||
.RS 4
|
|
||||||
Disables color support\&.
|
|
||||||
.RE
|
|
||||||
.PP
|
|
||||||
\fB\-h\fR
|
|
||||||
.RS 4
|
|
||||||
Display usage information\&.
|
|
||||||
.RE
|
|
||||||
.SH "CONTROLS"
|
|
||||||
.PP
|
|
||||||
\fBhjkl or *wasd\fR or \fBarrow keys\fR
|
|
||||||
.RS 4
|
|
||||||
Movement
|
|
||||||
.RE
|
|
||||||
.PP
|
|
||||||
\fBq\fR
|
|
||||||
.RS 4
|
|
||||||
Quit the game\&.
|
|
||||||
.RE
|
|
||||||
.SH "AUTHOR"
|
|
||||||
.sp
|
|
||||||
2048 was originally written by Thiehuis\&. Some people have contributed to it\&.
|
|
||||||
.sp
|
|
||||||
This manpage is Copyright (C) 2014 Björn Esser and licensed under the terms of the MIT License (MIT)\&.
|
|
||||||
.SH "RESOURCES"
|
|
||||||
.sp
|
|
||||||
github: https://github\&.com/Tiehuis/2048\-cli/
|
|
||||||
.SH "COPYING"
|
|
||||||
.sp
|
|
||||||
Copyright (C) 2014 Tiehuis\&. Free use of this software is granted under the terms of the MIT License (MIT)\&.
|
|
||||||
|
|
BIN
res/Anonymous Pro.ttf
Normal file
BIN
res/Anonymous Pro.ttf
Normal file
Binary file not shown.
BIN
res/NotCourierSans.ttf
Normal file
BIN
res/NotCourierSans.ttf
Normal file
Binary file not shown.
596
src/2048.c
596
src/2048.c
|
@ -1,596 +0,0 @@
|
||||||
/*
|
|
||||||
* 2048.c - The game 2048 for your Linux terminal.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2014 Tiehuis
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* Things to do:
|
|
||||||
* - Finish cleaning up code.
|
|
||||||
* - Ask user if they would like to play again instead of exiting immediately.
|
|
||||||
* - Add an actual goal (what happens when we reach 2048?).
|
|
||||||
* - Keep track list of past scores, and store in a better fashion independent
|
|
||||||
* of cwd. Also, store not in plaintext.
|
|
||||||
* */
|
|
||||||
|
|
||||||
#include <stdlib.h> /* for malloc */
|
|
||||||
#include <stdio.h> /* for printf, FILE */
|
|
||||||
#include <string.h> /* for strlen, strrchr */
|
|
||||||
#include <sys/stat.h> /* for mkdir */
|
|
||||||
#include <time.h> /* for time */
|
|
||||||
#include <unistd.h> /* for getopts */
|
|
||||||
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
#include <ncurses.h> /* for ncurses terminal */
|
|
||||||
#else /* ifndef NO_CURSES */
|
|
||||||
#include <termios.h> /* for plain terminal */
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
|
|
||||||
|
|
||||||
/* Constants */
|
|
||||||
#define DATADIR_NAME "2048"
|
|
||||||
#define HIGHSCORE_FILE_NAME "highscore"
|
|
||||||
#define DEFAULT_GRID_SIZE 4
|
|
||||||
#define HISCORE_FILE get_highscore_file()
|
|
||||||
#define USAGE_STR\
|
|
||||||
"Usage:\n"\
|
|
||||||
" ./2048 [options]\n"\
|
|
||||||
"\n"\
|
|
||||||
"Controls:\n"\
|
|
||||||
" hjkl, wasd Movement\n"\
|
|
||||||
" q Quit\n"\
|
|
||||||
"\n"\
|
|
||||||
"Options:\n"\
|
|
||||||
" -s <size> Set the grid border length\n"\
|
|
||||||
" -b <rate> Set the block spawn rate\n"\
|
|
||||||
" -c Enables color support (ncurses version only)\n"\
|
|
||||||
" -C Disables color support (ncurses version only)\n"
|
|
||||||
|
|
||||||
|
|
||||||
/* Globals */
|
|
||||||
int **grid; /* grid pointer */
|
|
||||||
int grid_size; /* grid size */
|
|
||||||
int score; /* score */
|
|
||||||
int score_last; /* Score for last move */
|
|
||||||
int score_high; /* Hiscore */
|
|
||||||
int printwidth; /* maximum length of any value in grid, for printing */
|
|
||||||
|
|
||||||
|
|
||||||
/* Typedefs */
|
|
||||||
typedef enum {
|
|
||||||
DIR_UP,
|
|
||||||
DIR_RIGHT,
|
|
||||||
DIR_DOWN,
|
|
||||||
DIR_LEFT
|
|
||||||
} dir_t;
|
|
||||||
|
|
||||||
|
|
||||||
/* Macros */
|
|
||||||
/* Repeat an expression y, x times */
|
|
||||||
#define ITER(x, expr)\
|
|
||||||
do {\
|
|
||||||
int i;\
|
|
||||||
for (i = 0; i < (x); i++){ expr;}\
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* Allocates a square pointer of array of arrays onto heap */
|
|
||||||
#define CALLOC2D(ptr, sz)\
|
|
||||||
do {\
|
|
||||||
int i;\
|
|
||||||
ptr = calloc((sz), sizeof(*ptr));\
|
|
||||||
for (i = 0; i < (sz); i++)\
|
|
||||||
ptr[i] = calloc((sz), sizeof(*ptr));\
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* Frees a square pointer of arrays to arrays */
|
|
||||||
#define FREE2D(ptr, sz)\
|
|
||||||
do {\
|
|
||||||
int i;\
|
|
||||||
for (i = 0; i < (sz); i++)\
|
|
||||||
free(ptr[i]);\
|
|
||||||
free(ptr);\
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* What occurs during a 'turn' of execution */
|
|
||||||
#define TURN(x) (gravitate(x) + merge(x) + gravitate(x))
|
|
||||||
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
const char* get_highscore_file() {
|
|
||||||
static char buffer[4096];
|
|
||||||
if (getenv("XDG_DATA_HOME") != NULL) {
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s/%s/%s", getenv("XDG_DATA_HOME"), DATADIR_NAME, HIGHSCORE_FILE_NAME);
|
|
||||||
} else if (getenv("HOME") != NULL) {
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s/.local/share/%s/%s", getenv("HOME"), DATADIR_NAME, HIGHSCORE_FILE_NAME);
|
|
||||||
} else {
|
|
||||||
return HIGHSCORE_FILE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create hierarrchy of directories up to highscore file location
|
|
||||||
char* sep = strrchr(buffer, '/');
|
|
||||||
while (sep != NULL) {
|
|
||||||
*sep = '\0';
|
|
||||||
if (strlen(buffer) != 0)
|
|
||||||
mkdir(buffer, 0777);
|
|
||||||
char* tmpsep = sep;
|
|
||||||
sep = strrchr(buffer, '/');
|
|
||||||
*tmpsep = '/';
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Merges adjacent squares of the same value together in a certain direction */
|
|
||||||
int merge(dir_t d)
|
|
||||||
{
|
|
||||||
int moved = 0;
|
|
||||||
int i, j;
|
|
||||||
|
|
||||||
if (d == DIR_LEFT) {
|
|
||||||
/* Move from upper left, across rows until second to second-last elem each row */
|
|
||||||
for (i = 0; i < grid_size; i++) {
|
|
||||||
for (j = 0; j < grid_size - 1; j++) {
|
|
||||||
if (grid[i][j] == grid[i][j + 1]) {
|
|
||||||
grid[i][j] <<= 1;
|
|
||||||
grid[i][j + 1] = 0;
|
|
||||||
score_last += grid[i][j];
|
|
||||||
score += grid[i][j];
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (d == DIR_UP) {
|
|
||||||
/* Move from upper left, across rows until final row which is skipped */
|
|
||||||
for (i = 0; i < grid_size - 1; i++) {
|
|
||||||
for (j = 0; j < grid_size; j++) {
|
|
||||||
if (grid[i][j] == grid[i + 1][j]) {
|
|
||||||
grid[i][j] <<= 1;
|
|
||||||
grid[i + 1][j] = 0;
|
|
||||||
score_last += grid[i][j];
|
|
||||||
score += grid[i][j];
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (d == DIR_RIGHT) {
|
|
||||||
/* Move from lower right, backwards across rows until first elem each row */
|
|
||||||
for (i = grid_size - 1; i >= 0; i--) {
|
|
||||||
for (j = grid_size - 1; j > 0; j--) {
|
|
||||||
if (grid[i][j] == grid[i][j - 1]) {
|
|
||||||
grid[i][j] <<= 1;
|
|
||||||
grid[i][j - 1] = 0;
|
|
||||||
score_last += grid[i][j];
|
|
||||||
score += grid[i][j];
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (d == DIR_DOWN) {
|
|
||||||
/* Move from lower right, across rows until first row which is skipped */
|
|
||||||
for (i = grid_size - 1; i > 0; i--) {
|
|
||||||
for (j = grid_size - 1; j >= 0; j--) {
|
|
||||||
if (grid[i][j] == grid[i - 1][j]) {
|
|
||||||
grid[i][j] <<= 1;
|
|
||||||
grid[i - 1][j] = 0;
|
|
||||||
score_last += grid[i][j];
|
|
||||||
score += grid[i][j];
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return moved;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* move all values in the grid to the edge given by the direction pressed */
|
|
||||||
/* would be nice to generalize this code a little so didn't need four loops */
|
|
||||||
/* if animations are wanted, then need to alter this so it moves each square one at a time */
|
|
||||||
int gravitate(dir_t d)
|
|
||||||
{
|
|
||||||
int moved = 0;
|
|
||||||
int i, j;
|
|
||||||
|
|
||||||
if (d == DIR_LEFT) {
|
|
||||||
for (i = 0; i < grid_size; i++) {
|
|
||||||
for (j = 0; j < grid_size - 1; j++) {
|
|
||||||
if (grid[i][j]) continue;
|
|
||||||
int st = 1;
|
|
||||||
while (j + st < grid_size && !grid[i][j + st]) st++;
|
|
||||||
if (j + st < grid_size) {
|
|
||||||
grid[i][j] = grid[i][j + st];
|
|
||||||
grid[i][j + st] = 0;
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (d == DIR_UP) {
|
|
||||||
for (i = 0; i < grid_size; i++) {
|
|
||||||
for (j = 0; j < grid_size - 1; j++) {
|
|
||||||
if (grid[j][i]) continue;
|
|
||||||
int st = 1;
|
|
||||||
while (j + st < grid_size && !grid[j + st][i]) st++;
|
|
||||||
if (j + st < grid_size) {
|
|
||||||
grid[j][i] = grid[j + st][i];
|
|
||||||
grid[j + st][i] = 0;
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (d == DIR_RIGHT) {
|
|
||||||
for (i = 0; i < grid_size; i++) {
|
|
||||||
for (j = grid_size - 1; j > 0; j--) {
|
|
||||||
if (grid[i][j]) continue;
|
|
||||||
int st = 1;
|
|
||||||
while (j - st >= 0 && !grid[i][j - st]) st++;
|
|
||||||
if (j - st >= 0) {
|
|
||||||
grid[i][j] = grid[i][j - st];
|
|
||||||
grid[i][j - st] = 0;
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (d == DIR_DOWN) {
|
|
||||||
for (i = 0; i < grid_size; i++) {
|
|
||||||
for (j = grid_size - 1; j > 0; j--) {
|
|
||||||
if (grid[j][i]) continue;
|
|
||||||
int st = 1;
|
|
||||||
while (j - st >= 0 && !grid[j - st][i]) st++;
|
|
||||||
if (j - st >= 0) {
|
|
||||||
grid[j][i] = grid[j - st][i];
|
|
||||||
grid[j - st][i] = 0;
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return moved;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return the current highscore */
|
|
||||||
int get_score_high()
|
|
||||||
{
|
|
||||||
int s = 0;
|
|
||||||
FILE *fd = fopen(HISCORE_FILE, "r");
|
|
||||||
if (fd == NULL)
|
|
||||||
fd = fopen(HISCORE_FILE, "w+");
|
|
||||||
|
|
||||||
if (fscanf(fd, "%d", &s) == 1) {};
|
|
||||||
fclose(fd);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* saves hiscore, but only if playing on standard size grid */
|
|
||||||
void save_score_high()
|
|
||||||
{
|
|
||||||
if (score > score_high && grid_size == 4) {
|
|
||||||
score_high = score;
|
|
||||||
FILE *fd = fopen(HISCORE_FILE, "w+");
|
|
||||||
fprintf(fd, "%d", score_high);
|
|
||||||
fclose(fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* returns if there are any possible moves */
|
|
||||||
int moves_available()
|
|
||||||
{
|
|
||||||
int i, j;
|
|
||||||
for (i = 0; i < grid_size; i++)
|
|
||||||
for (j = 0; j < grid_size; j++)
|
|
||||||
if (!grid[i][j]
|
|
||||||
|| ((i + 1 < grid_size) && (grid[i][j] == grid[i + 1][j]))
|
|
||||||
|| ((j + 1 < grid_size) && (grid[i][j] == grid[i][j + 1])))
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* places a random block onto the grid - either a 4, or a 2 with a chance of 1:3 respectively */
|
|
||||||
/* could do this in a much smarter fashion by finding which spaces are free */
|
|
||||||
void rand_block()
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
while (grid[x = rand() % grid_size][y = rand() % grid_size]);
|
|
||||||
grid[x][y] = (rand() & 3) ? 2 : 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* quick floor log2(n) */
|
|
||||||
int flog2(unsigned int n)
|
|
||||||
{
|
|
||||||
int k = 0;
|
|
||||||
while (n)
|
|
||||||
k++, n >>= 1;
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
|
|
||||||
/* draws the grid and fills it with the current values */
|
|
||||||
/* colors just rotate around, works for now, can be confusing when you have some fairly high values on the board */
|
|
||||||
void draw_grid(WINDOW *gamewin)
|
|
||||||
{
|
|
||||||
// mvwprintw will sometimes have a useless arg, this is warned, but doesn't affect the program
|
|
||||||
char *scr = score_last ? "SCORE: %d (+%d)\n" : "SCORE: %d\n";
|
|
||||||
mvwprintw(gamewin, 0, 0, scr, score, score_last);
|
|
||||||
mvwprintw(gamewin, 1, 0, "HISCR: %d\n", score_high);
|
|
||||||
|
|
||||||
ITER(grid_size*(printwidth + 2) + 1, waddch(gamewin, '-'));
|
|
||||||
int i, j, xps = 0, yps = 3;
|
|
||||||
for (i = 0; i < grid_size; i++, xps = 0, yps++) {
|
|
||||||
mvwprintw(gamewin, yps, xps++, "|");
|
|
||||||
for (j = 0; j < grid_size; j++) {
|
|
||||||
if (grid[i][j]) {
|
|
||||||
wattron(gamewin, COLOR_PAIR(flog2(grid[i][j]) % 7));
|
|
||||||
mvwprintw(gamewin, yps, xps, "%*d", printwidth, grid[i][j]);
|
|
||||||
wattroff(gamewin, COLOR_PAIR(flog2(grid[i][j]) % 7));
|
|
||||||
mvwprintw(gamewin, yps, xps + printwidth, " |");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ITER(printwidth + 1, waddch(gamewin, ' '));
|
|
||||||
waddch(gamewin, '|');
|
|
||||||
}
|
|
||||||
xps += (printwidth + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ITER(grid_size*(printwidth + 2) + 1, waddch(gamewin, '-'));
|
|
||||||
wrefresh(gamewin);
|
|
||||||
}
|
|
||||||
|
|
||||||
#else /* ifndef NO_CURSES */
|
|
||||||
|
|
||||||
/* draws the grid and fills it with the current values */
|
|
||||||
void draw_grid()
|
|
||||||
{
|
|
||||||
printf("HISCORE: %d |", score_high);
|
|
||||||
printf("| SCORE: %d ", score);
|
|
||||||
if (score_last) printf("(+%d)", score_last);
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
// alter this grid_size + 1 to match abitrary grid size
|
|
||||||
ITER(grid_size, printf("------"));
|
|
||||||
printf("-\n");
|
|
||||||
int i, j;
|
|
||||||
for (i = 0; i < grid_size; i++) {
|
|
||||||
printf("|");
|
|
||||||
for (j = 0; j < grid_size; j++) {
|
|
||||||
if (grid[i][j])
|
|
||||||
printf("%*d |", 4, grid[i][j]);
|
|
||||||
else
|
|
||||||
printf(" |");
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
ITER(grid_size, printf("------"));
|
|
||||||
printf("-\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* store the terminal settings and call this function on exit to restore */
|
|
||||||
struct termios sattr;
|
|
||||||
void reset_term()
|
|
||||||
{
|
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &sattr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
|
|
||||||
|
|
||||||
/* entry point for the program */
|
|
||||||
/* parses options and stores the main game loop */
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
|
|
||||||
/* init variables */
|
|
||||||
score = 0;
|
|
||||||
score_last = 0;
|
|
||||||
score_high = get_score_high();
|
|
||||||
grid_size = DEFAULT_GRID_SIZE;
|
|
||||||
printwidth = DEFAULT_GRID_SIZE;
|
|
||||||
|
|
||||||
int n_blocks = 1;
|
|
||||||
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
/* init ncurses environment */
|
|
||||||
initscr();
|
|
||||||
cbreak();
|
|
||||||
noecho();
|
|
||||||
curs_set(FALSE);
|
|
||||||
int enable_color = has_colors();
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
|
|
||||||
/* parse options */
|
|
||||||
int c;
|
|
||||||
while ((c = getopt(argc, argv, "rcChs:b:")) != -1) {
|
|
||||||
switch (c) {
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
/* Color support */
|
|
||||||
case 'c':
|
|
||||||
enable_color = 1;
|
|
||||||
break;
|
|
||||||
case 'C':
|
|
||||||
enable_color = 0;
|
|
||||||
break;
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
// different board sizes
|
|
||||||
case 's':;
|
|
||||||
int optint = strtol(optarg, NULL, 10);
|
|
||||||
grid_size = optint > DEFAULT_GRID_SIZE ? optint : DEFAULT_GRID_SIZE;
|
|
||||||
break;
|
|
||||||
// different block spawn rate
|
|
||||||
case 'b':
|
|
||||||
n_blocks = strtol(optarg, NULL, 10);
|
|
||||||
break;
|
|
||||||
// reset hiscores
|
|
||||||
case 'r':
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
endwin();
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
printf("Are you sure you want to reset your highscores? (Y)es or (N)o\n");
|
|
||||||
int response;
|
|
||||||
if ((response = getchar()) == 'y' || response == 'Y') {
|
|
||||||
FILE *fd = fopen(HISCORE_FILE, "w+");
|
|
||||||
fclose(fd);
|
|
||||||
}
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
// help menu
|
|
||||||
case 'h':
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
endwin();
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
printf(USAGE_STR);
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate memory once we actually know amount */
|
|
||||||
CALLOC2D(grid, grid_size);
|
|
||||||
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
int width = grid_size * (printwidth + 2) + 1;
|
|
||||||
int height = grid_size * (printwidth + 2) + 3;
|
|
||||||
|
|
||||||
if (enable_color) {
|
|
||||||
if (!has_colors()) {
|
|
||||||
fprintf(stderr, "Terminal does not support color\n");
|
|
||||||
} else {
|
|
||||||
start_color();
|
|
||||||
init_pair(0, 1, 0);
|
|
||||||
init_pair(1, 2, 0);
|
|
||||||
init_pair(2, 3, 0);
|
|
||||||
init_pair(3, 4, 0);
|
|
||||||
init_pair(4, 5, 0);
|
|
||||||
init_pair(5, 6, 0);
|
|
||||||
init_pair(6, 7, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// might center in middle of screen
|
|
||||||
WINDOW *gamewin = newwin(height, width, 1, 1);
|
|
||||||
keypad(gamewin, TRUE);
|
|
||||||
#else /* ifndef NO_CURSES */
|
|
||||||
|
|
||||||
/* store term settings so we can restore on exit */
|
|
||||||
tcgetattr(STDIN_FILENO, &sattr);
|
|
||||||
atexit(reset_term);
|
|
||||||
|
|
||||||
/* alters terminal stdin to not echo and doesn't need \n before reading getchar */
|
|
||||||
struct termios tattr;
|
|
||||||
tcgetattr(STDIN_FILENO, &tattr);
|
|
||||||
tattr.c_lflag &= ~(ICANON | ECHO);
|
|
||||||
tcsetattr(STDOUT_FILENO, TCSANOW, &tattr);
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
|
|
||||||
/* random seed */
|
|
||||||
srand((unsigned int)time(NULL));
|
|
||||||
ITER(2, rand_block());
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
draw_grid(gamewin);
|
|
||||||
#else /* ifndef NO_CURSES */
|
|
||||||
draw_grid();
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
|
|
||||||
int key, moved;
|
|
||||||
while (1) {
|
|
||||||
/* will goto this if we didn't get a valid keypress */
|
|
||||||
retry:;
|
|
||||||
|
|
||||||
moved = 0;
|
|
||||||
score_last = 0;
|
|
||||||
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
key = wgetch(gamewin);
|
|
||||||
#else /* ifndef NO_CURSES */
|
|
||||||
key = getchar();
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
|
|
||||||
/* should check if anything changed during merge and if not retry */
|
|
||||||
switch (key) {
|
|
||||||
|
|
||||||
case 'h':
|
|
||||||
case 'a':
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
case KEY_LEFT:
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
moved = TURN(DIR_LEFT);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'l':
|
|
||||||
case 'd':
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
case KEY_RIGHT:
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
moved = TURN(DIR_RIGHT);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'j':
|
|
||||||
case 's':
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
case KEY_DOWN:
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
moved = TURN(DIR_DOWN);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'k':
|
|
||||||
case 'w':
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
case KEY_UP:
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
moved = TURN(DIR_UP);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'q':
|
|
||||||
FREE2D(grid, grid_size);
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
erase();
|
|
||||||
refresh();
|
|
||||||
endwin();
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
save_score_high();
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
|
|
||||||
default:
|
|
||||||
goto retry;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!moves_available()) {
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
endwin();
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
printf("\n"
|
|
||||||
"YOU LOSE! - Your score was %d\n", score);
|
|
||||||
save_score_high();
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moved) {
|
|
||||||
ITER(n_blocks, rand_block());
|
|
||||||
#ifndef NO_CURSES
|
|
||||||
draw_grid(gamewin);
|
|
||||||
#else /* ifndef NO_CURSES */
|
|
||||||
draw_grid();
|
|
||||||
#endif /* ifndef NO_CURSES */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
292
src/engine.c
Normal file
292
src/engine.c
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "engine.h"
|
||||||
|
#include "highscore.h"
|
||||||
|
|
||||||
|
/* Utilize block counter to improve some of the functions so they can run
|
||||||
|
* quicker */
|
||||||
|
|
||||||
|
/* This function will move all blocks in the given game the given direction.
|
||||||
|
* The callback function is called after each single move. It can be used to
|
||||||
|
* animate the movement of the board. */
|
||||||
|
static void gravitate(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state *s, struct gamestate *g))
|
||||||
|
{
|
||||||
|
|
||||||
|
#define swap_if_space(xoff, yoff)\
|
||||||
|
do {\
|
||||||
|
if (g->grid[x][y] == 0 && g->grid[x+xoff][y+yoff] != 0) {\
|
||||||
|
g->grid[x][y] = g->grid[x+xoff][y+yoff];\
|
||||||
|
g->grid[x+xoff][y+yoff] = 0;\
|
||||||
|
done = 0;\
|
||||||
|
g->moved = 1;\
|
||||||
|
}\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
int done = 0;
|
||||||
|
|
||||||
|
if (d == dir_left) {
|
||||||
|
while (!done) {
|
||||||
|
done = 1;
|
||||||
|
for (x = 0; x < g->opts->grid_width - 1; ++x) {
|
||||||
|
for (y = 0; y < g->opts->grid_height; ++y) {
|
||||||
|
swap_if_space(1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (callback)
|
||||||
|
callback(s, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (d == dir_right) {
|
||||||
|
while (!done) {
|
||||||
|
done = 1;
|
||||||
|
for (x = g->opts->grid_width - 1; x > 0; --x) {
|
||||||
|
for (y = 0; y < g->opts->grid_height; ++y) {
|
||||||
|
swap_if_space(-1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (callback)
|
||||||
|
callback(s, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (d == dir_down) {
|
||||||
|
while (!done) {
|
||||||
|
done = 1;
|
||||||
|
for (y = g->opts->grid_height - 1; y > 0; --y) {
|
||||||
|
for (x = 0; x < g->opts->grid_width; ++x) {
|
||||||
|
swap_if_space(0, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (callback)
|
||||||
|
callback(s, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (d == dir_up) {
|
||||||
|
while (!done) {
|
||||||
|
done = 1;
|
||||||
|
for (y = 0; y < g->opts->grid_height - 1; ++y) {
|
||||||
|
for (x = 0; x < g->opts->grid_width; ++x) {
|
||||||
|
swap_if_space(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (callback)
|
||||||
|
callback(s, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fatal("Invalid direction passed to gravitate()");
|
||||||
|
/* Not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef swap_if_space
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The merge function will combine adjacent blocks with the same value for
|
||||||
|
* the given direction. Note, left and right merges will merge in a different
|
||||||
|
* order, so they are not identical in all cases.
|
||||||
|
*
|
||||||
|
* Consider 2 2 2 0
|
||||||
|
*
|
||||||
|
* Right merging: 4 0 2 0
|
||||||
|
* Left merging: 2 0 4 0
|
||||||
|
*/
|
||||||
|
static void merge(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state *s, struct gamestate *g))
|
||||||
|
{
|
||||||
|
|
||||||
|
#define merge_if_equal(xoff, yoff)\
|
||||||
|
do {\
|
||||||
|
if (g->grid[x][y] && (g->grid[x][y] == g->grid[x+xoff][y+yoff])) {\
|
||||||
|
g->grid[x][y] += g->grid[x+xoff][y+yoff];\
|
||||||
|
g->grid[x+xoff][y+yoff] = 0;\
|
||||||
|
g->blocks_in_play -= 1;\
|
||||||
|
g->score_last += g->grid[x][y];\
|
||||||
|
g->score += g->grid[x][y];\
|
||||||
|
g->moved = 1;\
|
||||||
|
}\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
g->score_last = 0;
|
||||||
|
|
||||||
|
if (d == dir_left) {
|
||||||
|
for (x = 0; x < g->opts->grid_width - 1; ++x) {
|
||||||
|
for (y = 0; y < g->opts->grid_height; ++y) {
|
||||||
|
merge_if_equal(1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (d == dir_right) {
|
||||||
|
for (x = g->opts->grid_width - 1; x > 0; --x) {
|
||||||
|
for (y = 0; y < g->opts->grid_height; ++y) {
|
||||||
|
merge_if_equal(-1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (d == dir_down) {
|
||||||
|
for (y = g->opts->grid_height - 1; y > 0; --y) {
|
||||||
|
for (x = 0; x < g->opts->grid_width; ++x) {
|
||||||
|
merge_if_equal(0, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (d == dir_up) {
|
||||||
|
for (y = 0; y < g->opts->grid_height - 1; ++y) {
|
||||||
|
for (x = 0; x < g->opts->grid_width; ++x) {
|
||||||
|
merge_if_equal(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fatal("Invalid direction passed to merge()");
|
||||||
|
/* Not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback)
|
||||||
|
callback(s, g);
|
||||||
|
|
||||||
|
#undef merge_if_equal
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scan the current board and determine if an end outcome has been reached.
|
||||||
|
* -1 indicates a lose condition, 1 indicates a win condition, 0 indicates
|
||||||
|
* end has not yet been reached. */
|
||||||
|
int gamestate_end_condition(struct gamestate *g)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
//size_t blocks_counted = 0;
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
for (x = 0; x < g->opts->grid_width; ++x) {
|
||||||
|
for (y = 0; y < g->opts->grid_height; ++y) {
|
||||||
|
if (g->grid[x][y] >= g->opts->goal)
|
||||||
|
return 1;
|
||||||
|
if (!g->grid[x][y] || ((x + 1 < g->opts->grid_width) &&
|
||||||
|
(g->grid[x][y] == g->grid[x+1][y]))
|
||||||
|
|| ((y + 1 < g->opts->grid_height) &&
|
||||||
|
(g->grid[x][y] == g->grid[x][y+1])))
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Place a random block into the current grid */
|
||||||
|
void gamestate_new_block(struct gamestate *g)
|
||||||
|
{
|
||||||
|
/* pick random square, if it is full, then move forward until we find
|
||||||
|
* an empty square. This is biased */
|
||||||
|
|
||||||
|
static int seeded = 0;
|
||||||
|
if (!seeded) {
|
||||||
|
seeded = 1;
|
||||||
|
srand(time(NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix up this random number generator */
|
||||||
|
/* Method:
|
||||||
|
* - Find a non-biased index between 0 and blocks_play, n
|
||||||
|
* - Find the nth 0 element in the array
|
||||||
|
* - insert a random value there
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Error here */
|
||||||
|
#ifdef SKIP
|
||||||
|
size_t block_position = (size_t)rand() % (
|
||||||
|
g->opts->grid_width * g->opts->grid_height - g->blocks_in_play);
|
||||||
|
|
||||||
|
size_t i, ps;
|
||||||
|
for (i = 0, ps = 0; ps < block_position; ++i) {
|
||||||
|
if (!g->grid[i / g->opts->grid_width][i % g->opts->grid_height]) ps++;
|
||||||
|
}
|
||||||
|
|
||||||
|
g->grid[i / g->opts->grid_width][i % g->opts->grid_height]
|
||||||
|
= (rand() & 3) ? g->opts->spawn_value : g->opts->spawn_value * 2;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Use rudimentary for now */
|
||||||
|
int x, y;
|
||||||
|
while (g->grid[x = rand() % g->opts->grid_width][y = rand() % g->opts->grid_height]);
|
||||||
|
g->grid[x][y] = (rand() & 3) ? g->opts->spawn_value : g->opts->spawn_value * 2;
|
||||||
|
g->blocks_in_play += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This returns the number of digits in the base10 rep of n. The ceiling is
|
||||||
|
* taken so this will be one greater than required */
|
||||||
|
static int digits_ceiling(unsigned int n)
|
||||||
|
{
|
||||||
|
int l = 0;
|
||||||
|
while (n) n /= 10, ++l;
|
||||||
|
return l + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return NULL if we couldn't allocate space for the gamestate. The opt
|
||||||
|
* argument can be passed directly via gameoptions_default i.e
|
||||||
|
* *o = gamestate_init(gameoptions_default) is valid, as the delete function
|
||||||
|
* will find the pointer to the gameoptions and delete the data accordingly. */
|
||||||
|
struct gamestate* gamestate_init(struct gameoptions *opt)
|
||||||
|
{
|
||||||
|
if (!opt) return NULL;
|
||||||
|
|
||||||
|
struct gamestate *g = malloc(sizeof(struct gamestate));
|
||||||
|
if (!g) goto gamestate_alloc_fail;
|
||||||
|
g->gridsize = opt->grid_width * opt->grid_height;
|
||||||
|
|
||||||
|
g->grid_data_ptr = calloc(g->gridsize, sizeof(long));
|
||||||
|
if (!g->grid_data_ptr) goto grid_data_alloc_fail;
|
||||||
|
|
||||||
|
g->grid = malloc(opt->grid_height * sizeof(long*));
|
||||||
|
if (!g->grid) goto grid_alloc_fail;
|
||||||
|
|
||||||
|
/* Switch to two allocation version */
|
||||||
|
int i;
|
||||||
|
long **iterator = g->grid;
|
||||||
|
for (i = 0; i < g->gridsize; i += opt->grid_width)
|
||||||
|
*iterator++ = &g->grid_data_ptr[i];
|
||||||
|
|
||||||
|
g->moved = 0;
|
||||||
|
g->score = 0;
|
||||||
|
g->score_high = 0;
|
||||||
|
g->score_last = 0;
|
||||||
|
g->print_width = digits_ceiling(opt->goal);
|
||||||
|
g->blocks_in_play = 0;
|
||||||
|
g->opts = opt;
|
||||||
|
|
||||||
|
highscore_load(g);
|
||||||
|
|
||||||
|
/* Initial 3 random blocks */
|
||||||
|
gamestate_new_block(g);
|
||||||
|
gamestate_new_block(g);
|
||||||
|
gamestate_new_block(g);
|
||||||
|
return g;
|
||||||
|
|
||||||
|
grid_alloc_fail:
|
||||||
|
free(g->grid_data_ptr);
|
||||||
|
grid_data_alloc_fail:
|
||||||
|
free(g);
|
||||||
|
gamestate_alloc_fail:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A tick is a gravitate, merge then gravitate all in the same direction.
|
||||||
|
* the moved variable is set to 0 initially and if the gravitate of merge
|
||||||
|
* functions modify it, we can determine which action to take. */
|
||||||
|
int gamestate_tick(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state*, struct gamestate*))
|
||||||
|
{
|
||||||
|
/* Reset move. Altered by gravitate and merge if we do move */
|
||||||
|
g->moved = 0;
|
||||||
|
gravitate(s, g, d, callback);
|
||||||
|
merge(s, g, d, callback);
|
||||||
|
gravitate(s, g, d, callback);
|
||||||
|
return g->moved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free all data associated with the gamestate */
|
||||||
|
void gamestate_clear(struct gamestate *g)
|
||||||
|
{
|
||||||
|
highscore_save(g);
|
||||||
|
gameoptions_destroy(g->opts);
|
||||||
|
free(g->grid_data_ptr); /* Free grid data */
|
||||||
|
free(g->grid); /* Free pointers to data slots */
|
||||||
|
free(g);
|
||||||
|
}
|
43
src/engine.h
Normal file
43
src/engine.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef ENGINE_H
|
||||||
|
#define ENGINE_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "gfx.h"
|
||||||
|
#include "options.h"
|
||||||
|
|
||||||
|
#define fatal(msg)\
|
||||||
|
do {\
|
||||||
|
fprintf(stderr, "line %d: %s\n", __LINE__, msg);\
|
||||||
|
abort();\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
struct gamestate {
|
||||||
|
/* Game state */
|
||||||
|
long *grid_data_ptr;
|
||||||
|
long **grid;
|
||||||
|
int gridsize;
|
||||||
|
int moved;
|
||||||
|
long score;
|
||||||
|
long score_high;
|
||||||
|
long score_last;
|
||||||
|
int print_width;
|
||||||
|
int blocks_in_play;
|
||||||
|
/* Variable command line options */
|
||||||
|
struct gameoptions *opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
dir_invalid,
|
||||||
|
dir_down,
|
||||||
|
dir_left,
|
||||||
|
dir_right,
|
||||||
|
dir_up
|
||||||
|
};
|
||||||
|
|
||||||
|
int gamestate_end_condition(struct gamestate*);
|
||||||
|
void gamestate_new_block(struct gamestate*);
|
||||||
|
int gamestate_tick(struct gfx_state*, struct gamestate*, int, void (*callback)(struct gfx_state*, struct gamestate*));
|
||||||
|
void gamestate_clear(struct gamestate*);
|
||||||
|
struct gamestate* gamestate_init(struct gameoptions *);
|
||||||
|
|
||||||
|
#endif
|
30
src/gfx.h
Normal file
30
src/gfx.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef GFX_H
|
||||||
|
#define GFX_H
|
||||||
|
|
||||||
|
#include "engine.h"
|
||||||
|
|
||||||
|
/* These can be defined by an implemenation if special codes are used
|
||||||
|
* to represent some keys. e.g. KEY_LEFT in ncurses */
|
||||||
|
#define GFX_RIGHT_EXTRA
|
||||||
|
#define GFX_LEFT_EXTRA
|
||||||
|
#define GFX_DOWN_EXTRA
|
||||||
|
#define GFX_UP_EXTRA
|
||||||
|
|
||||||
|
struct gfx_state;
|
||||||
|
|
||||||
|
/* Initialization of a graphics context */
|
||||||
|
struct gfx_state* gfx_init(struct gamestate *);
|
||||||
|
|
||||||
|
/* Drawing of a game_state onto a graphics context */
|
||||||
|
void gfx_draw(struct gfx_state *, struct gamestate *);
|
||||||
|
|
||||||
|
/* Blocking get character. Should not be buffered for best results */
|
||||||
|
int gfx_getch(struct gfx_state *);
|
||||||
|
|
||||||
|
/* Destruction of a graphics context */
|
||||||
|
void gfx_destroy(struct gfx_state *);
|
||||||
|
|
||||||
|
/* Sleep for a specifed millisecond period */
|
||||||
|
void gfx_sleep(int ms);
|
||||||
|
|
||||||
|
#endif
|
120
src/gfx_curses.c
Normal file
120
src/gfx_curses.c
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "gfx.h"
|
||||||
|
|
||||||
|
#define GFX_EXTRA_UP case KEY_UP:
|
||||||
|
#define GFX_EXTRA_DOWN case KEY_DOWN:
|
||||||
|
#define GFX_EXTRA_RIGHT case KEY_RIGHT:
|
||||||
|
#define GFX_EXTRA_LEFT case KEY_LEFT:
|
||||||
|
|
||||||
|
#define NUMBER_OF_COLORS 7
|
||||||
|
|
||||||
|
#define iterate(n, expression)\
|
||||||
|
do {\
|
||||||
|
int i;\
|
||||||
|
for (i = 0; i < n; ++i) { expression; }\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
struct gfx_state {
|
||||||
|
WINDOW *window;
|
||||||
|
size_t window_height, window_width;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gfx_state* gfx_init(struct gamestate *g)
|
||||||
|
{
|
||||||
|
initscr();
|
||||||
|
cbreak();
|
||||||
|
noecho();
|
||||||
|
curs_set(FALSE);
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
struct gfx_state *s = malloc(sizeof(struct gfx_state));
|
||||||
|
s->window_height = g->opts->grid_height * (g->print_width + 2) + 3;
|
||||||
|
s->window_width = g->opts->grid_width * (g->print_width + 2) + 1;
|
||||||
|
s->window = newwin(s->window_height, s->window_width, 1, 1);
|
||||||
|
keypad(s->window, TRUE);
|
||||||
|
|
||||||
|
if (g->opts->enable_color && has_colors()) {
|
||||||
|
start_color();
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
init_pair(x++, 1, 0);
|
||||||
|
init_pair(x++, 2, 0);
|
||||||
|
init_pair(x++, 3, 0);
|
||||||
|
init_pair(x++, 4, 0);
|
||||||
|
init_pair(x++, 5, 0);
|
||||||
|
init_pair(x++, 6, 0);
|
||||||
|
init_pair(x++, 7, 0);
|
||||||
|
char dummy[x == NUMBER_OF_COLORS ? 1 : -1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int int_log2(int n)
|
||||||
|
{
|
||||||
|
int k = 0;
|
||||||
|
while (n)
|
||||||
|
++k, n /= 2;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_draw(struct gfx_state *s, struct gamestate *g)
|
||||||
|
{
|
||||||
|
if (g->score_last)
|
||||||
|
mvwprintw(s->window, 0, 0, "Score: %d (+%d)\n", g->score, g->score_last);
|
||||||
|
else
|
||||||
|
mvwprintw(s->window, 0, 0, "Score: %d\n", g->score);
|
||||||
|
|
||||||
|
mvwprintw(s->window, 1, 0, " Hi: %d\n", g->score_high);
|
||||||
|
iterate(g->opts->grid_width * (g->print_width + 2) + 1, waddch(s->window, '-'));
|
||||||
|
|
||||||
|
int x, y,
|
||||||
|
xpos = 0,
|
||||||
|
ypos = 3;
|
||||||
|
|
||||||
|
for (y = 0; y < g->opts->grid_height; ++y, ++ypos, xpos = 0) {
|
||||||
|
mvwprintw(s->window, ypos, xpos++, "|");
|
||||||
|
|
||||||
|
for (x = 0; x < g->opts->grid_width; ++x) {
|
||||||
|
if (g->grid[x][y]) {
|
||||||
|
wattron(s->window, COLOR_PAIR(int_log2(g->grid[x][y]) % NUMBER_OF_COLORS));
|
||||||
|
mvwprintw(s->window, ypos, xpos, "%*d", g->print_width, g->grid[x][y]);
|
||||||
|
wattroff(s->window, COLOR_PAIR(int_log2(g->grid[x][y]) % NUMBER_OF_COLORS));
|
||||||
|
mvwprintw(s->window, ypos, xpos + g->print_width, " |");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
iterate(g->print_width + 1, waddch(s->window, ' '));
|
||||||
|
waddch(s->window, '|');
|
||||||
|
}
|
||||||
|
|
||||||
|
xpos += (g->print_width + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iterate(g->opts->grid_height * (g->print_width + 2) + 1, waddch(s->window, '-'));
|
||||||
|
wrefresh(s->window);
|
||||||
|
}
|
||||||
|
|
||||||
|
int gfx_getch(struct gfx_state *s)
|
||||||
|
{
|
||||||
|
int c = wgetch(s->window);
|
||||||
|
|
||||||
|
/* Flush buffer */
|
||||||
|
nodelay(s->window, TRUE);
|
||||||
|
while (wgetch(s->window) != ERR);
|
||||||
|
nodelay(s->window, FALSE);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_sleep(int ms)
|
||||||
|
{
|
||||||
|
usleep(ms * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_destroy(struct gfx_state *s)
|
||||||
|
{
|
||||||
|
free(s);
|
||||||
|
endwin();
|
||||||
|
}
|
181
src/gfx_sdl.c
Normal file
181
src/gfx_sdl.c
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_ttf.h>
|
||||||
|
#include "gfx.h"
|
||||||
|
|
||||||
|
/* Side length of a 'pixel' in pixels */
|
||||||
|
#define TTF_FONT_PATH "res/Anonymous Pro.ttf"
|
||||||
|
#define TTF_FONT_PT 32
|
||||||
|
|
||||||
|
#define iterate(n, expression)\
|
||||||
|
do {\
|
||||||
|
int i;\
|
||||||
|
for (i = 0; i < n; ++i) { expression; }\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
struct gfx_state {
|
||||||
|
SDL_Window *window;
|
||||||
|
SDL_Surface *surface;
|
||||||
|
TTF_Font *font;
|
||||||
|
int side_length;
|
||||||
|
int window_height;
|
||||||
|
int window_width;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gfx_state* gfx_init(struct gamestate *g)
|
||||||
|
{
|
||||||
|
struct gfx_state *s = malloc(sizeof(struct gfx_state));
|
||||||
|
|
||||||
|
SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
TTF_Init();
|
||||||
|
s->font = TTF_OpenFont(TTF_FONT_PATH, TTF_FONT_PT);
|
||||||
|
|
||||||
|
s->side_length = TTF_FontLineSkip(s->font);
|
||||||
|
s->window_height = g->opts->grid_height * (g->print_width + 2) + 3;
|
||||||
|
s->window_width = g->opts->grid_width * (g->print_width + 2) + 1;
|
||||||
|
s->window = SDL_CreateWindow(
|
||||||
|
"2048",
|
||||||
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
|
||||||
|
/* Spacing is inconsistent right now. Need to find width and
|
||||||
|
* height spacing to accurately resize window */
|
||||||
|
s->window_width * TTF_FONT_PT * 0.7,
|
||||||
|
s->window_height * s->side_length * 0.35,
|
||||||
|
SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI
|
||||||
|
);
|
||||||
|
|
||||||
|
s->surface = SDL_GetWindowSurface(s->window);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define rect_set(r, xv, yv, wv, hv)\
|
||||||
|
do {\
|
||||||
|
r.x = xv; r.y = yv; r.w = wv; r.h = hv;\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
void gfx_draw(struct gfx_state *s, struct gamestate *g)
|
||||||
|
{
|
||||||
|
/* This shouldn't ever overflow. Max width effectively is determined by the size
|
||||||
|
* of two integers' text representation */
|
||||||
|
const int buffer_length = 64;
|
||||||
|
char string_buffer[buffer_length];
|
||||||
|
|
||||||
|
/* Set up text object so we can write to sdl window */
|
||||||
|
SDL_Surface *text;
|
||||||
|
SDL_Color text_color = {255, 255, 255, 0};
|
||||||
|
SDL_Rect rect;
|
||||||
|
|
||||||
|
/* Clear screen */
|
||||||
|
SDL_FillRect(s->surface, NULL, SDL_MapRGB(s->surface->format, 0, 0, 0));
|
||||||
|
|
||||||
|
if (g->score_last)
|
||||||
|
snprintf(string_buffer, buffer_length, "Score: %ld (+%ld)", g->score, g->score_last);
|
||||||
|
else
|
||||||
|
snprintf(string_buffer, buffer_length, "Score: %ld", g->score);
|
||||||
|
|
||||||
|
rect_set(rect, 0, 0, 0, 0);
|
||||||
|
text = TTF_RenderText_Solid(s->font, string_buffer, text_color);
|
||||||
|
SDL_BlitSurface(text, NULL, s->surface, &rect);
|
||||||
|
|
||||||
|
snprintf(string_buffer, buffer_length, " Hi: %ld", g->score_high);
|
||||||
|
rect_set(rect, 0, s->side_length * 1, 0, 0);
|
||||||
|
text = TTF_RenderText_Solid(s->font, string_buffer, text_color);
|
||||||
|
SDL_BlitSurface(text, NULL, s->surface, &rect);
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < g->opts->grid_width * (g->print_width + 2) + 1; ++i)
|
||||||
|
string_buffer[i] = '-';
|
||||||
|
string_buffer[i] = '\0';
|
||||||
|
|
||||||
|
rect_set(rect, 0, s->side_length * 2, 0, 0);
|
||||||
|
text = TTF_RenderText_Solid(s->font, string_buffer, text_color);
|
||||||
|
SDL_BlitSurface(text, NULL, s->surface, &rect);
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
char line_buffer[buffer_length];
|
||||||
|
for (y = 0; y < g->opts->grid_height; ++y) {
|
||||||
|
int line_index = 0;
|
||||||
|
line_buffer[line_index++] = '|';
|
||||||
|
|
||||||
|
for (x = 0; x < g->opts->grid_width; ++x) {
|
||||||
|
if (g->grid[x][y]) {
|
||||||
|
snprintf(string_buffer, buffer_length, "%*ld |", g->print_width, g->grid[x][y]);
|
||||||
|
strncpy(line_buffer + line_index, string_buffer, buffer_length - line_index);
|
||||||
|
line_index += strlen(string_buffer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snprintf(string_buffer, buffer_length, "%*s |", g->print_width, "");
|
||||||
|
strncpy(line_buffer + line_index, string_buffer, buffer_length - line_index);
|
||||||
|
line_index += strlen(string_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line_buffer[line_index] = 0;
|
||||||
|
rect_set(rect, 0, s->side_length * (y + 3), 0, 0);
|
||||||
|
text = TTF_RenderText_Solid(s->font, line_buffer, text_color);
|
||||||
|
SDL_BlitSurface(text, NULL, s->surface, &rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < g->opts->grid_height * (g->print_width + 2) + 1; ++i)
|
||||||
|
string_buffer[i] = '-';
|
||||||
|
string_buffer[i] = '\0';
|
||||||
|
|
||||||
|
rect_set(rect, 0, s->side_length * (y + 3), 0, 0);
|
||||||
|
text = TTF_RenderText_Solid(s->font, string_buffer, text_color);
|
||||||
|
SDL_BlitSurface(text, NULL, s->surface, &rect);
|
||||||
|
SDL_UpdateWindowSurface(s->window);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This getch we parse here, and we just return when we get an appropriate
|
||||||
|
* event */
|
||||||
|
int gfx_getch(struct gfx_state *s)
|
||||||
|
{
|
||||||
|
(void)s; /* Supress unused warning */
|
||||||
|
SDL_Event event;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
while (SDL_WaitEvent(&event)) {
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
return 'q';
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_q:
|
||||||
|
return 'q';
|
||||||
|
case SDLK_UP:
|
||||||
|
case SDLK_w:
|
||||||
|
return 'w';
|
||||||
|
case SDLK_DOWN:
|
||||||
|
case SDLK_s:
|
||||||
|
return 's';
|
||||||
|
case SDLK_LEFT:
|
||||||
|
case SDLK_a:
|
||||||
|
return 'a';
|
||||||
|
case SDLK_RIGHT:
|
||||||
|
case SDLK_d:
|
||||||
|
return 'd';
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_sleep(int ms)
|
||||||
|
{
|
||||||
|
SDL_Delay(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_destroy(struct gfx_state *s)
|
||||||
|
{
|
||||||
|
SDL_DestroyWindow(s->window);
|
||||||
|
TTF_Quit();
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
72
src/gfx_terminal.c
Normal file
72
src/gfx_terminal.c
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "gfx.h"
|
||||||
|
|
||||||
|
#define iterate(n, expression)\
|
||||||
|
do {\
|
||||||
|
int i;\
|
||||||
|
for (i = 0; i < n; ++i) { expression; }\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
struct gfx_state {
|
||||||
|
struct termios oldt, newt;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gfx_state* gfx_init(struct gamestate *g)
|
||||||
|
{
|
||||||
|
struct gfx_state *s = malloc(sizeof(struct gfx_state));
|
||||||
|
tcgetattr(STDIN_FILENO, &s->oldt);
|
||||||
|
s->newt = s->oldt;
|
||||||
|
s->newt.c_lflag &= ~(ICANON | ECHO);
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &s->newt);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_draw(struct gfx_state *s, struct gamestate *g)
|
||||||
|
{
|
||||||
|
#ifdef VT100
|
||||||
|
printf("\033[2J\033[H");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (g->score_last)
|
||||||
|
printf("Score: %ld (+%ld)\n", g->score, g->score_last);
|
||||||
|
else
|
||||||
|
printf("Score: %ld\n", g->score);
|
||||||
|
|
||||||
|
printf(" Hi: %ld\n", g->score_high);
|
||||||
|
|
||||||
|
iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); printf("\n");
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
for (y = 0; y < g->opts->grid_width; ++y) {
|
||||||
|
printf("|");
|
||||||
|
|
||||||
|
for (x = 0; x < g->opts->grid_width; ++x) {
|
||||||
|
if (g->grid[x][y])
|
||||||
|
printf("%*zd |", g->print_width, g->grid[x][y]);
|
||||||
|
else
|
||||||
|
printf("%*s |", g->print_width, "");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
iterate((g->print_width + 2) * g->opts->grid_width + 1, printf("-")); printf("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int gfx_getch(struct gfx_state *s)
|
||||||
|
{
|
||||||
|
return getchar();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_sleep(int ms)
|
||||||
|
{
|
||||||
|
usleep(ms * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_destroy(struct gfx_state *t)
|
||||||
|
{
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &t->oldt);
|
||||||
|
free(t);
|
||||||
|
}
|
75
src/highscore.c
Normal file
75
src/highscore.c
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include "engine.h"
|
||||||
|
|
||||||
|
const char *hs_dir_name = "2048";
|
||||||
|
const char *hs_file_name = "highscore";
|
||||||
|
|
||||||
|
static const char* highscore_retrieve_file(void)
|
||||||
|
{
|
||||||
|
static char buffer[4096];
|
||||||
|
|
||||||
|
if (getenv("XDG_DATA_HOME") != NULL) {
|
||||||
|
snprintf(buffer, sizeof(buffer), "%s/%s/%s",
|
||||||
|
getenv("XDG_DATA_HOME"), hs_dir_name, hs_file_name);
|
||||||
|
}
|
||||||
|
else if (getenv("HOME")) {
|
||||||
|
snprintf(buffer, sizeof(buffer), "%s/.local/share/%s/%s",
|
||||||
|
getenv("HOME"), hs_dir_name, hs_file_name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return hs_file_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create file only if it doesn't exist */
|
||||||
|
if (access(buffer, F_OK) != -1)
|
||||||
|
return buffer;
|
||||||
|
|
||||||
|
char *sep = strrchr(buffer, '/');
|
||||||
|
while (sep != NULL) {
|
||||||
|
*sep = '\0';
|
||||||
|
if (strlen(buffer) != 0)
|
||||||
|
mkdir(buffer, 0777);
|
||||||
|
char *tmpsep = sep;
|
||||||
|
sep = strrchr(buffer, '/');
|
||||||
|
*tmpsep = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void highscore_reset(void)
|
||||||
|
{
|
||||||
|
const char *hsfile = highscore_retrieve_file();
|
||||||
|
|
||||||
|
FILE *fd = fopen(hsfile, "w+");
|
||||||
|
fprintf(fd, "%d", 0);
|
||||||
|
fclose(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void highscore_load(struct gamestate *g)
|
||||||
|
{
|
||||||
|
const char *hsfile = highscore_retrieve_file();
|
||||||
|
|
||||||
|
FILE *fd = fopen(hsfile, "r");
|
||||||
|
if (fd == NULL)
|
||||||
|
fd = fopen(hsfile, "w+");
|
||||||
|
|
||||||
|
fscanf(fd, "%ld", &g->score_high);
|
||||||
|
fclose(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void highscore_save(struct gamestate *g)
|
||||||
|
{
|
||||||
|
if (g->score < g->score_high || g->opts->grid_width != 4 || g->opts->grid_height != 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const char *hsfile = highscore_retrieve_file();
|
||||||
|
|
||||||
|
FILE *fd = fopen(hsfile, "w");
|
||||||
|
fprintf(fd, "%ld", g->score);
|
||||||
|
fclose(fd);
|
||||||
|
}
|
8
src/highscore.h
Normal file
8
src/highscore.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef HIGHSCORE_H
|
||||||
|
#define HIGHSCORE_H
|
||||||
|
|
||||||
|
void highscore_reset(void);
|
||||||
|
void highscore_load(struct gamestate *g);
|
||||||
|
void highscore_save(struct gamestate *g);
|
||||||
|
|
||||||
|
#endif
|
65
src/main.c
Normal file
65
src/main.c
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "engine.h"
|
||||||
|
#include "gfx.h"
|
||||||
|
|
||||||
|
void draw_then_sleep(struct gfx_state *s, struct gamestate *g)
|
||||||
|
{
|
||||||
|
gfx_draw(s, g);
|
||||||
|
gfx_sleep(40);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct gameoptions *o = gameoptions_default();
|
||||||
|
struct gamestate *g = gamestate_init(parse_options(o, argc, argv));
|
||||||
|
struct gfx_state *s = gfx_init(g);
|
||||||
|
|
||||||
|
int game_running = true;
|
||||||
|
while (game_running) {
|
||||||
|
gfx_draw(s, g);
|
||||||
|
|
||||||
|
get_new_key:;
|
||||||
|
int direction = dir_invalid;
|
||||||
|
switch (gfx_getch(s)) {
|
||||||
|
case 'h':
|
||||||
|
case 'a':
|
||||||
|
direction = dir_left;
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
case 'd':
|
||||||
|
direction = dir_right;
|
||||||
|
break;
|
||||||
|
case 'j':
|
||||||
|
case 's':
|
||||||
|
direction = dir_down;
|
||||||
|
break;
|
||||||
|
case 'k':
|
||||||
|
case 'w':
|
||||||
|
direction = dir_up;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
game_running = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
goto get_new_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Game will only end if 0 moves available */
|
||||||
|
if (game_running) {
|
||||||
|
/* Maybe change this behaviour so if we don't move, we still generate a block */
|
||||||
|
if (gamestate_tick(s, g, direction, g->opts->animate ? draw_then_sleep : NULL))
|
||||||
|
gamestate_new_block(g);
|
||||||
|
|
||||||
|
if (gamestate_end_condition(g)) {
|
||||||
|
game_running = false;
|
||||||
|
goto game_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game_end:
|
||||||
|
gfx_destroy(s);
|
||||||
|
gamestate_clear(g);
|
||||||
|
return 0;
|
||||||
|
}
|
75
src/options.c
Normal file
75
src/options.c
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "highscore.h"
|
||||||
|
#include "options.h"
|
||||||
|
|
||||||
|
void print_usage(void)
|
||||||
|
{
|
||||||
|
printf("usage: 2048 [-cCaArh] [-s SIZE] [-b RATE] [-g GOAL]\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Initial game options */
|
||||||
|
struct gameoptions* gameoptions_default(void)
|
||||||
|
{
|
||||||
|
struct gameoptions *opt = malloc(sizeof(struct gameoptions));
|
||||||
|
if (!opt) return NULL;
|
||||||
|
|
||||||
|
opt->grid_height = DEFAULT_GRID_HEIGHT;
|
||||||
|
opt->grid_width = DEFAULT_GRID_WIDTH;
|
||||||
|
opt->goal = DEFAULT_GOAL;
|
||||||
|
opt->spawn_value = DEFAULT_SPAWN_VALUE;
|
||||||
|
opt->spawn_rate = DEFAULT_SPAWN_RATE;
|
||||||
|
opt->enable_color = DEFAULT_COLOR_TOGGLE;
|
||||||
|
opt->animate = DEFAULT_ANIMATE_TOGGLE;
|
||||||
|
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gameoptions_destroy(struct gameoptions *opt)
|
||||||
|
{
|
||||||
|
free(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct gameoptions* parse_options(struct gameoptions *opt, int argc, char **argv)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
while ((c = getopt(argc, argv, "aArcChg:s:b:")) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 'a':
|
||||||
|
opt->animate = 1;
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
opt->animate = 0;
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
opt->enable_color = 1;
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
opt->enable_color = 0;
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
opt->goal = strtol(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
case 's':;
|
||||||
|
/* Stick with square for now */
|
||||||
|
int optint = strtol(optarg, NULL, 10);
|
||||||
|
if (optint < CONSTRAINT_GRID_MAX && optint > CONSTRAINT_GRID_MIN) {
|
||||||
|
opt->grid_height = optint;
|
||||||
|
opt->grid_width = optint;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
opt->spawn_rate = strtol(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
highscore_reset();
|
||||||
|
exit(0);
|
||||||
|
case 'h':
|
||||||
|
print_usage();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return opt;
|
||||||
|
}
|
31
src/options.h
Normal file
31
src/options.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef OPTIONS_H
|
||||||
|
#define OPTIONS_H
|
||||||
|
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
#define CONSTRAINT_GRID_MIN 4
|
||||||
|
#define CONSTRAINT_GRID_MAX 20
|
||||||
|
#define DEFAULT_GRID_HEIGHT 4
|
||||||
|
#define DEFAULT_GRID_WIDTH 4
|
||||||
|
#define DEFAULT_GOAL 2048
|
||||||
|
#define DEFAULT_SPAWN_VALUE 2
|
||||||
|
#define DEFAULT_SPAWN_RATE 1
|
||||||
|
#define DEFAULT_COLOR_TOGGLE 0
|
||||||
|
#define DEFAULT_ANIMATE_TOGGLE 1
|
||||||
|
|
||||||
|
struct gameoptions {
|
||||||
|
int grid_height;
|
||||||
|
int grid_width;
|
||||||
|
long goal;
|
||||||
|
long spawn_value;
|
||||||
|
int spawn_rate;
|
||||||
|
int enable_color;
|
||||||
|
int animate;
|
||||||
|
};
|
||||||
|
|
||||||
|
void print_usage(void);
|
||||||
|
struct gameoptions* parse_options(struct gameoptions*, int, char**);
|
||||||
|
struct gameoptions* gameoptions_default(void);
|
||||||
|
void gameoptions_destroy(struct gameoptions *opt);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user