globox

Platform game for the terminal
git clone git://git.bitsmanent.org/globox
Log | Files | Refs | README | LICENSE

commit 6a32bca017c1a06c3af9e394207e028e2d43c06b
Author: Claudio Alessi <smoppy@gmail.com>
Date:   Sun, 25 Jun 2017 17:44:00 +0200

hello globox

Diffstat:
ALICENSE | 21+++++++++++++++++++++
AMakefile | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME.md | 3+++
ATODO | 1+
Aarg.h | 41+++++++++++++++++++++++++++++++++++++++++
Aconfig.def.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.mk | 16++++++++++++++++
Aglobox.c | 763+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alevels.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 1019 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,21 @@ +MIT/X Consortium License + +© 2017 Claudio Alessi <smoppy at gmail dot com> + +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. diff --git a/Makefile b/Makefile @@ -0,0 +1,61 @@ +# globox +# See LICENSE file for copyright and license details. + +include config.mk + +APPNAME=globox +SRC = ${APPNAME}.c +OBJ = ${SRC:.c=.o} + +all: options ${APPNAME} + +options: + @echo ${APPNAME} build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +${APPNAME}: ${OBJ} + @echo CC -o $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + @echo cleaning + @rm -f ${APPNAME} ${OBJ} ${APPNAME}-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p ${APPNAME}-${VERSION} + @cp -R LICENSE Makefile README config.mk \ + ${APPNAME}.1 ${SRC} ${APPNAME}-${VERSION} + @tar -cf ${APPNAME}-${VERSION}.tar ${APPNAME}-${VERSION} + @gzip ${APPNAME}-${VERSION}.tar + @rm -rf ${APPNAME}-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f ${APPNAME} ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/${APPNAME} + @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @sed "s/VERSION/${VERSION}/g" < ${APPNAME}.1 > ${DESTDIR}${MANPREFIX}/man1/${APPNAME}.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/${APPNAME}.1 + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/${APPNAME} + @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/${APPNAME}.1 + +.PHONY: all options clean dist install uninstall diff --git a/README.md b/README.md @@ -0,0 +1,3 @@ +globox - bla bla +================ +globox is... diff --git a/TODO b/TODO @@ -0,0 +1 @@ +- move terminal handling stuff into a library diff --git a/arg.h b/arg.h @@ -0,0 +1,41 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef __ARG_H__ +#define __ARG_H__ + +extern char *argv0; + +#define USED(x) ((void)(x)) + +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char _argc;\ + char **_argv;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (argv[0]++, _argv = argv; argv[0][0];\ + argv[0]++) {\ + if (_argv != argv)\ + break;\ + _argc = argv[0][0];\ + switch (_argc) + +#define ARGEND }\ + USED(_argc);\ + }\ + USED(argv);\ + USED(argc); + +#define EARGF(x) ((argv[1] == NULL)? ((x), abort(), (char *)0) :\ + (argc--, argv++, argv[0])) + +#endif + diff --git a/config.def.h b/config.def.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ + +Object objects[] = { + /* symbol flags ontick argument */ + { VACUUM, OF_OPEN, falling, {0} }, +[1] = {L'o', OF_PLAYER|OF_OPEN|OF_FALL, NULL, {.i = 2} }, +[2] = {L'a', OF_PLAYER|OF_OPEN|OF_FALL, NULL, {.i = 2} }, + {L'_', OF_OPEN|OF_STICK|OF_JUMPFROM, NULL, {0} }, + {L'|', 0, NULL, {0} }, + {L'*', OF_OPEN, earnenergy, {.i = 2} }, + {L')', OF_OPENLEFT|OF_STICK, finish, {0} }, + {L'(', OF_OPENRIGHT|OF_STICK, finish, {0} }, + {L't', 0, cannon, {.v = &objects[11]} }, + {L'j', 0, cannon, {.v = &objects[12]} }, +[11] = {L'⋅', OF_OPEN, cannonball, {.i = +1} }, +[12] = {L'.', OF_OPEN, cannonball, {.i = -1} }, + {L'#', OF_FALL|OF_PUSHABLE, NULL, {0} }, + {L'@', OF_FALL|OF_PUSHABLE, NULL, {0} }, + + /* + {L'>', route, NULL, NULL }, + {L'<', route, NULL, NULL }, + {L'^', route, NULL, NULL }, + {L'v', route, NULL, NULL }, + {L'?', objrand, NULL, NULL }, + {L'%', jumpglue, NULL, NULL }, + {L'~', spring, NULL, NULL }, + {L'-', bump, NULL, NULL }, + {L',', hurt, NULL, {.i = 2}}, + {L':', secret, NULL, NULL }, + */ +}; + +#include "levels.h" + +Level levels[] = { + { "Reach the exit", lev0 }, + { "Reach the exit", lev1 }, + { "Reach the exit", lev2 }, + { "Run", lev3 }, + { "Climb up", lev4 }, + { "Welcome to the arena", arena }, +}; + +/* key definitions */ +static Key keys[] = { + /* key function argument */ + { 'q', quit, {.i = 1} }, + { CTRL('c'), quit, {.i = 0} }, + { 'r', restart, {0} }, + { 'a', walkleft, {.v = &objects[1]} }, + { 'd', walkright, {.v = &objects[1]} }, + { 'w', jump, {.v = &objects[1]} }, + { 'j', walkleft, {.v = &objects[2]} }, + { 'l', walkright, {.v = &objects[2]} }, + { 'i', jump, {.v = &objects[2]} }, +}; diff --git a/config.mk b/config.mk @@ -0,0 +1,16 @@ +# globox +VERSION = 0.1 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=2 -DVERSION=\"${VERSION}\" +CFLAGS = -std=c99 -g -pedantic -Wall -O0 ${CPPFLAGS} +#CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${CPPFLAGS} + +# compiler and linker +CC = cc diff --git a/globox.c b/globox.c @@ -0,0 +1,763 @@ +/* See LICENSE file for copyright and license details. + * + * globox is a platform game for the terminal. + * + * Each symbol displayed on the screen is called a block, including players. + * Blocks are organized in a linked block list which is filled with the objects + * found on the level. + * + * Keys and objects are organized as arrays and defined in config.h. + * + * To understand everything else, start reading main(). +*/ + +#define _BSD_SOURCE +#include <errno.h> +#include <limits.h> +#include <locale.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <time.h> +#include <wchar.h> + +#include "arg.h" +char *argv0; + +/* macros */ +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define DELAY(B, K, D) (++(B)->delays[(K)] < (D) ? 1 : ((B)->delays[(K)] = 0)) +#define ISSET(F, B) ((F) & (B)) +#define TICK 64000 +#define VACUUM L' ' +#define LINESEP L'\n' + +/* VT100 escape sequences */ +#define CLEAR L"\33[2J" +#define CLEARLN L"\33[2K" +#define CLEARRIGHT L"\33[K" +#define CURPOS L"\33[%d;%dH" +#define CURSON L"\33[?25h" +#define CURSOFF L"\33[?25l" + +/* object flags */ +#define OF_OPENUP 1<<1 +#define OF_OPENRIGHT 1<<2 +#define OF_OPENDOWN 1<<3 +#define OF_OPENLEFT 1<<4 +#define OF_JUMPFROM 1<<5 +#define OF_PLAYER 1<<6 +#define OF_STICK 1<<7 +#define OF_FALL 1<<8 +#define OF_PUSHABLE 1<<9 +#define OF_OPEN (OF_OPENUP|OF_OPENRIGHT|OF_OPENDOWN|OF_OPENLEFT) + +/* enums */ +enum { KeyUp = -50, KeyDown, KeyRight, KeyLeft, KeyHome, KeyEnd, KeyDel, KeyPgUp, KeyPgDw }; +enum { DelayFalling, DelayCannon, DelayCannonBall, DelayMax }; + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + char *name; + wchar_t *map; +} Level; + +typedef struct Object Object; +struct Object { + wchar_t sym; + unsigned int flags; + int (*ontick)(Object *); + const Arg arg; +}; + +typedef struct { + const int key; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct Block Block; +struct Block { + Object *o; + int delays[DelayMax]; + int x, y; + int energy; + int _draw; /* used to optimize draw() */ + Block *next; +}; + +typedef struct { + Block *blocks; + int w, h; +} Scene; + +/* function declarations */ +void attach(Block *b); +int cannon(Object *o); +int cannonball(Object *o); +void checkgame(void); +char choose(char *opts, const wchar_t *msgstr, ...); +void cleanup(void); +void detach(Block *b); +void die(const char *errstr, ...); +void draw(void); +void drawbar(void); +int earnenergy(Object *o); +void *ecalloc(size_t nmemb, size_t size); +int falling(Object *o); +int finish(Object *o); +void flow(void); +void freescene(void); +int getkey(void); +void ioblock(int block); +void jump(const Arg *arg); +void keypress(void); +void level(int lev); +int mvprintf(int x, int y, wchar_t *fmt, ...); +Object *objbysym(wchar_t sym); +void objwalk(Object *o, int offset); +void quit(const Arg *arg); +void resize(int x, int y); +void restart(const Arg *arg); +void run(void); +void setup(void); +void sigwinch(int unused); +int sleepu(double usec); +void usage(void); +void walk(Block *blk, int offset); +void walkleft(const Arg *arg); +void walkright(const Arg *arg); + +/* variables */ +Scene *scene; +struct termios origti; +int running = 1, lev; +int rows, cols; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* function implementations */ +void +attach(Block *b) { + b->next = scene->blocks; + scene->blocks = b; +} + +int +cannon(Object *o) { + Block *c, *nb; + + for(c = scene->blocks; c; c = c->next) { + if(c->o->ontick != cannon || DELAY(c, DelayCannon, 128)) + continue; + nb = ecalloc(1, sizeof(Block)); + nb->o = ((Object *)(c->o->arg.v)); + nb->x = c->x; + nb->y = c->y; + attach(nb); + } + return 0; +} + +int +cannonball(Object *o) { + Block *cb, *b, *p, *fb = NULL; + int nx, nblk, offset; + + for(cb = scene->blocks; cb; cb = cb->next) { + if(cb->o->ontick != cannonball || DELAY(cb, DelayCannonBall, 2)) + continue; + if(fb) { + detach(fb); + free(fb); + fb = NULL; + } + offset = cb->o->arg.i; + nx = cb->x + offset; + nblk = 0; + p = NULL; + for(b = scene->blocks; b; b = b->next) { + if(b->x == nx && b->y == cb->y) { + ++nblk; + if(!ISSET(b->o->flags, offset > 0 ? OF_OPENLEFT : OF_OPENRIGHT)) + fb = cb; + } + if(b->x == cb->x && b->y == cb->y && ISSET(b->o->flags, OF_PLAYER)) + p = b; + } + if(!nblk) { + fb = cb; + continue; + } + if(p) { + fb = cb; + p->energy -= 2; + } + cb->x = nx; + } + if(fb) { + detach(fb); + free(fb); + fb = NULL; + } + return 0; +} + +void +checkgame(void) { + Block *fp = NULL, *p; + int np = 0; + + for(p = scene->blocks; p;) { + if(!ISSET(p->o->flags, OF_PLAYER)) { + p = p->next; + continue; + } + if(p->energy > 0) { + ++np; + p = p->next; + } + else { + fp = p; + p = p->next; + detach(fp); + free(fp); + } + } + if(np) + return; + if(choose("yn", L"Level failed. Play again ([y]/n)?") == 'y') + level(lev); + else + running = 0; +} + +char +choose(char *opts, const wchar_t *msgstr, ...) { + va_list ap; + int c; + char *o = NULL; + + va_start(ap, msgstr); + fwprintf(stdout, CURPOS, cols - 1, 0); + vfwprintf(stdout, msgstr, ap); + va_end(ap); + + ioblock(1); + while(!(o && *o) && (c = getchar())) { + if(c == '\n') + o = &opts[0]; + else + for(o = opts; *o; ++o) + if(c == *o) + break; + } + fwprintf(stdout, CURPOS CLEARLN, cols - 1, 0); + ioblock(0); + return *o; +} + +void +cleanup(void) { + freescene(); + ioblock(1); + tcsetattr(0, TCSANOW, &origti); + wprintf(CURSON); +} + +void +detach(Block *b) { + Block **tb; + + for (tb = &scene->blocks; *tb && *tb != b; tb = &(*tb)->next); + *tb = b->next; +} + +void +die(const char *errstr, ...) { + va_list ap; + + if(rows) + cleanup(); + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +draw(void) { + Block *b, *b2, *t; + + for(b = scene->blocks; b; b = b->next) + b->_draw = 1; + for(b = scene->blocks; b; b = b->next) { + if(!b->_draw) + continue; + t = NULL; + for(b2 = scene->blocks; b2; b2 = b2->next) + if(ISSET(b2->o->flags, OF_PLAYER) && b->x == b2->x && b->y == b2->y && (t = b2)) + break; + if(!t) + for(b2 = scene->blocks; b2; b2 = b2->next) + if(!ISSET(b2->o->flags, OF_OPEN) && b->x == b2->x && b->y == b2->y && (t = b2)) + break; + if(!t) + t = b; + for(b2 = scene->blocks; b2; b2 = b2->next) + if(b2 != t && t->x == b2->x && t->y == b2->y) + b2->_draw = 0; + } + for(b = scene->blocks; b; b = b->next) + if(b->_draw) + mvprintf(b->x, b->y + 1, L"%lc", b->o->sym); + drawbar(); +} + +void +drawbar(void) { + Block *p; + int i, len; + + len = mvprintf(1, 1, L"[%d|%dx%d] %s ::", lev, scene->w, scene->h, levels[lev].name); + for(p = scene->blocks; p; p = p->next) { + if(!(p->o->flags & OF_PLAYER)) + continue; + i = mvprintf(len+1, 1, L" %lc(*%d)(%dx%d)", + p->o->sym, p->energy, p->x, p->y); + len += i; + } + mvprintf(len+1, 1, L"%ls", CLEARRIGHT); +} + +int +earnenergy(Object *o) { + Block *b, *p; + + for(b = scene->blocks; b; b = b->next) { + if(b->o != o) + continue; + for(p = scene->blocks; p; p = p->next) + if(ISSET(p->o->flags, OF_PLAYER) && p->x == b->x && p->y == b->y) + break; + if(!p) + continue; + b->o = objbysym(VACUUM); + p->energy += o->arg.i; + break; + } + return 0; +} + +void * +ecalloc(size_t nmemb, size_t size) { + void *p; + + if(!(p = calloc(nmemb, size))) + die("Cannot allocate memory.\n"); + return p; +} + +int +falling(Object *o) { + Block *b, *p; + int ny, valid, skip; + + for(p = scene->blocks; p; p = p->next) { + if(!ISSET(p->o->flags, OF_FALL) || DELAY(p, DelayFalling, 4)) + continue; + valid = 0; + skip = 0; + ny = p->y + 1; + for(b = scene->blocks; b; b = b->next) { + if(b == p || b->x != p->x) + continue; + if(b->y == p->y) { + if(ISSET(b->o->flags, OF_STICK)) + skip = 1; + } + else if(b->y == ny) { + valid = 1; + if(!ISSET(b->o->flags, OF_OPENUP)) + skip = 1; + } + } + if(skip) + continue; + if(!valid) { + p->energy = 0; + continue; + } + p->y = ny; + } + return 0; +} + +int +finish(Object *o) { + Block *b, *p; + int np = 0; + + for(p = scene->blocks; p; p = p->next) { + if(!ISSET(p->o->flags, OF_PLAYER) || p->energy <= 0) + continue; + ++np; + for(b = scene->blocks; b; b = b->next) + if(b->o->ontick == finish && b->x == p->x && b->y == p->y) + break; + if(!b) + return 0; + } + if(!np) + return 0; + if(++lev >= LENGTH(levels)) { + if(choose("yn", L"The game has finished. Play again ([y]/n)?") == 'n') { + running = 0; + return 1; + } + lev = 0; + } + else if(choose("yn", L"Level completed. Play next ([y]/n)?") == 'n') { + running = 0; + return 1; + } + level(lev); + return 1; +} + +void +flow(void) { + int i; + + for(i = 0; i < LENGTH(objects); ++i) + if(objects[i].ontick && objects[i].ontick(&objects[i])) + return; +} + +void +freescene(void) { + Block *b; + + if(!scene) + return; + while(scene->blocks) { + b = scene->blocks; + scene->blocks = scene->blocks->next; + free(b); + } + free(scene); +} + +/* XXX quick'n dirty implementation */ +int +getkey(void) { + int key = getchar(), c; + + if(key != '\x1b' || getchar() != '[') + return key; + switch((c = getchar())) { + case 'A': key = KeyUp; break; + case 'B': key = KeyDown; break; + case 'C': key = KeyRight; break; + case 'D': key = KeyLeft; break; + case 'H': key = KeyHome; break; + case 'F': key = KeyEnd; break; + case '1': key = KeyHome; break; + case '3': key = KeyDel; break; + case '4': key = KeyEnd; break; + case '5': key = KeyPgUp; break; + case '6': key = KeyPgDw; break; + case '7': key = KeyHome; break; + case '8': key = KeyEnd; break; + default: + /* debug */ + mvprintf(1, rows, L"Unknown char: %c (%d)", c, c); + break; + } + return key; +} + +void +ioblock(int block) { + struct termios ti; + int v = block ? 1 : 0; + + tcgetattr(0, &ti); + ti.c_cc[VTIME] = ti.c_cc[VMIN] = v; + tcsetattr(0, TCSANOW, &ti); +} + +void +jump(const Arg *arg) { + Block *b, *p; + int up, down, canjump, upclose, dwclose; + + for(p = scene->blocks; p; p = p->next) { + if(p->o != arg->v) + continue; + up = p->y - 1; + down = p->y + 1; + canjump = 0; + dwclose = 0; + upclose = -1; + for(b = scene->blocks; b; b = b->next) { + if(b->x != p->x) + continue; + if(b->y == p->y) { + if(ISSET(b->o->flags, OF_JUMPFROM)) + canjump = 1; + } + else if(b->y == up) { + if(!ISSET(b->o->flags, OF_OPENDOWN)) + upclose = 1; + else if(upclose == -1) + upclose = 0; + } + else if(b->y == down) { + if(!ISSET(b->o->flags, OF_OPENUP)) + dwclose = 1; + } + } + if(upclose || !(canjump || dwclose)) + continue; + p->y = up; + /* reset delays */ + for(up = 0; up < DelayMax; ++up) + p->delays[up] = 0; + } +} + +void +keypress(void) { + int key = getkey(), i; + + for(i = 0; i < LENGTH(keys); ++i) + if(keys[i].key == key) + keys[i].func(&keys[i].arg); + while(getchar() != EOF); /* discard remaining input */ +} + +void +level(int num) { + Object *o; + Block *b; + int i, len, x, y; + wchar_t *map; + + if(num >= LENGTH(levels)) + die("%s: level %d does not exists.\n", argv0, num); + freescene(); + scene = ecalloc(1, sizeof(Scene)); + map = levels[num].map; + len = wcslen(map); + x = y = 1; + for(i = 0; i < len; ++i) { + if(map[i] == LINESEP) { + ++y; + if(x > scene->w) + scene->w = x; + x = 1; + continue; + } + o = objbysym(map[i]); + if(!o) + die("%s: unknown object '%c' at %dx%d.\n", argv0, map[i], x, y); + b = ecalloc(1, sizeof(Block)); + b->o = o; + b->x = x; + b->y = y; + attach(b); + if(ISSET(o->flags, OF_PLAYER | OF_FALL)) { + o = objbysym(VACUUM); + if(o) { + b = ecalloc(1, sizeof(Block)); + b->o = o; + b->x = x; + b->y = y; + attach(b); + } + } + ++x; + } + scene->h = y + 1; + /* revive the zombies */ + for(b = scene->blocks; b; b = b->next) + if(ISSET(b->o->flags, OF_PLAYER) && !b->energy) + b->energy = b->o->arg.i; + wprintf(CLEAR); +} + +int +mvprintf(int x, int y, wchar_t *fmt, ...) { + va_list ap; + int len; + + wprintf(CURPOS, y, x); + va_start(ap, fmt); + len = vfwprintf(stdout, fmt, ap); + va_end(ap); + return len; +} + +Object * +objbysym(wchar_t sym) { + int i; + + for(i = 0; i < LENGTH(objects); ++i) + if(objects[i].sym == sym) + return &objects[i]; + return NULL; +} + +void +objwalk(Object *o, int offset) { + Block *b; + + for(b = scene->blocks; b; b = b->next) + if(b->o == o) + walk(b, offset); +} + +void +quit(const Arg *arg) { + if(!arg->i || choose("ny", L"Are you sure (y/[n])?") == 'y') + running = 0; +} + +void +resize(int x, int y) { + rows = x; + cols = y; +} + +void +restart(const Arg *arg) { + if(choose("ny", L"Restart the level (y/[n]?") == 'y') + level(lev); +} + +void +run(void) { + while(running) { + while(cols < scene->w || rows < scene->h) { + mvprintf(1, 1, L"Terminal too small."); + sleepu(10000); + } + draw(); + checkgame(); + keypress(); + flow(); + if(sleepu(TICK)) + die("%s: error while sleeping\n", argv0); + } + mvprintf(1, cols, L"%ls", CLEARLN); +} + +void +setup(void) { + struct termios ti; + struct sigaction sa; + struct winsize ws; + + setlocale(LC_CTYPE, ""); + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = sigwinch; + sigaction(SIGWINCH, &sa, NULL); + + tcgetattr(0, &origti); + ti.c_lflag &= ~(ECHO | ICANON); + ti.c_iflag |= ICRNL; + tcsetattr(0, TCSAFLUSH, &ti); + wprintf(CURSOFF); + ioblock(0); + + ioctl(0, TIOCGWINSZ, &ws); + resize(ws.ws_row, ws.ws_col); +} + +void +sigwinch(int unused) { + struct winsize ws; + + ioctl(0, TIOCGWINSZ, &ws); + resize(ws.ws_row, ws.ws_col); + wprintf(CLEAR); +} + +int +sleepu(double usec) { + struct timespec req, rem; + int r; + + req.tv_sec = 0; + req.tv_nsec = usec * 1000; + while((r = nanosleep(&req, &rem)) == -1 && errno == EINTR) + req = rem; + return r; +} + +void +usage(void) { + die("Usage: %s [-v] [-n <level>]\n", argv0); +} + +void +walk(Block *blk, int offset) { + Block *b; + int nx, nblk; + + nx = blk->x + offset; + nblk = 0; + for(b = scene->blocks; b; b = b->next) { + if(b->x != nx || b->y != blk->y) + continue; + ++nblk; + if(!ISSET(b->o->flags, offset > 0 ? OF_OPENLEFT : OF_OPENRIGHT)) { + if(ISSET(b->o->flags, OF_PUSHABLE)) { + walk(b, offset); + if(b->x != nx) + continue; + } + return; + } + } + if(!nblk) + return; + blk->x = nx; +} + +void +walkleft(const Arg *arg) { + objwalk((Object *)arg->v, -1); +} + +void +walkright(const Arg *arg) { + objwalk((Object *)arg->v, +1); +} + +int +main(int argc, char *argv[]) { + ARGBEGIN { + case 'n': lev = atoi(EARGF(usage())); break; + case 'v': die("globox-"VERSION"\n"); + default: usage(); + } ARGEND; + setup(); + level(lev); + run(); + cleanup(); + return 0; +} diff --git a/levels.h b/levels.h @@ -0,0 +1,56 @@ +/* levels */ + +wchar_t lev0[] = { + L" \n" + L" # \n" + L"o _ #)\n" + L"#___| |____" +}; + +wchar_t lev1[] = { + L" # \n" + L" # \n" + L"o __ #)\n" + L"#___| |___" +}; + +wchar_t lev2[] = { + L" # \n" + L" # \n" + L" # _\n" + L"o __ #)\n" + L"#___| |___" +}; + +wchar_t lev3[] = { + L"(_______ j\n" + L" # |\n" + L" # |\n" + L"o #_ |\n" + L"_________| |_|" +}; + +wchar_t lev4[] = { + L"| )|\n" + L"| _ |\n" + L"| _ j\n" + L"| _ _ |\n" + L"t _ |\n" + L"| # o |\n" + L"|_______|" +}; + +wchar_t arena[] = { + L" ______|\n" + L"o _ *|___ |\n" + L"_ _| |____ ____________________j_ |\n" + L" _ __| a ___ # |\n" + L"__ __ _#_______ _ ____@______ |\n" + L" _ | # _ |\n" + L" _| * _ *_|\n" + L" _ | _______ _ _____ ____ ____j\n" + L" _| _ |\n" + L" _ |t _ |\n" + L" * _|*| _ |__\n" + L"t ________ _________________ ________)" +};