commit 6a32bca017c1a06c3af9e394207e028e2d43c06b
Author: Claudio Alessi <smoppy@gmail.com>
Date: Sun, 25 Jun 2017 17:44:00 +0200
hello globox
Diffstat:
A | LICENSE | | | 21 | +++++++++++++++++++++ |
A | Makefile | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README.md | | | 3 | +++ |
A | TODO | | | 1 | + |
A | arg.h | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
A | config.def.h | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | config.mk | | | 16 | ++++++++++++++++ |
A | globox.c | | | 763 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | levels.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 ________ _________________ ________)"
+};