370 lines
8.3 KiB
C
370 lines
8.3 KiB
C
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <xmp.h>
|
|
#include <sys/ioctl.h>
|
|
#include <termios.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <pulse/simple.h>
|
|
#include <pulse/error.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
// defines void* module and long modsize
|
|
#include "music.h"
|
|
|
|
|
|
char* hdr = "ALCHEMY [press A for info]\n-----------------+ CURRENT\n";
|
|
|
|
/* RUNE BITFIELD
|
|
*
|
|
* RRRRFFFB (very conveniently only 1 byte)
|
|
*
|
|
* R - rune type. theres probably not 16 but eh
|
|
* F - foreground color. there are at least 5
|
|
* B - theres only 2 so only 1 bit is needed
|
|
*
|
|
*/
|
|
|
|
char* runes = " &*oImu%/avjyt^@>";
|
|
|
|
#define getbg(B) ((B) & 1)
|
|
#define getfg(B) (((B) >> 1) & 7)
|
|
#define getrune(B) ((B) >> 4)
|
|
uint8_t newrune() {
|
|
uint8_t r = ((rand() & 0xFF) | 1);
|
|
if(!getrune(r) || !getfg(r)) r = newrune();
|
|
if(getrune(r) == 2 || getrune(r) == 1) r = (r & 0xF0) | 0xF;
|
|
else if(getfg(r) == 7) r = newrune();
|
|
return r;
|
|
}
|
|
|
|
uint8_t board[72];
|
|
|
|
struct winsize win;
|
|
struct termios term, term_b;
|
|
|
|
void clrscr() {
|
|
// VERY good practice. will never cause bugs
|
|
printf("\x1B\x5B\x48\x1B\x5B\x4A");
|
|
}
|
|
|
|
int closef = 0;
|
|
void winch(int sig) {
|
|
ioctl(1, TIOCGWINSZ, &win);
|
|
if(win.ws_col < 27 || win.ws_row < 10) {
|
|
tcsetattr(0, TCSANOW, &term_b);
|
|
printf("window too small, exiting\n");
|
|
closef = 1;
|
|
}
|
|
}
|
|
|
|
void posupdate(int x, int y) {
|
|
printf("\033[%d;%dH", ++y, ++x);
|
|
}
|
|
|
|
void render_board(uint8_t* brd, int score, int level, int rune, int stage, int valid) {
|
|
uint8_t* m = brd;
|
|
for(int i = 0; i < 8; i++) {
|
|
for(int c = 0; c < 9; c++) {
|
|
printf(
|
|
"\033[0;9%im", getfg(m[c]));
|
|
|
|
printf("\033[0;%i%im", getbg(m[c]) ? 9 : 10, getbg(m[c]) ? getfg(m[c]) : 7);
|
|
|
|
printf(
|
|
"%c%s", runes[getrune(m[c])],
|
|
(c == 8) ? "\033[0m|" : " "
|
|
);
|
|
if(c == 8) {
|
|
//if(i == 0) printf(" :%c %c", valid + '(', runes[rune]);
|
|
if(i == 0) printf(" :%c \033[0;9%im%c\033[0m", valid + '(', getfg(rune), runes[getrune(rune)]);
|
|
if(i == 2) printf(" SCORE");
|
|
if(i == 3) printf(" %07i", score);
|
|
if(i == 5) {
|
|
printf(" [");
|
|
for(int v = 0; v < level; v++) putchar('#');
|
|
for(int v = 0; v < 3 - level; v++) putchar('-');
|
|
putchar(']');
|
|
}
|
|
if(i == 6) printf(" STAGE");
|
|
if(i == 7) printf(" %02i", stage);
|
|
putchar('\n');
|
|
}
|
|
}
|
|
m += 9;
|
|
}
|
|
}
|
|
|
|
int linecheck(uint8_t* board, int x, int y) {
|
|
int w = 1, h = 1;
|
|
for(int i = 0; i < 9; i++) if(!getrune(board[(y*9) + i])) w = 0;
|
|
for(int i = 0; i < 8; i++) if(!getrune(board[(i*9) + x])) h = 0;
|
|
|
|
if(w) for(int i = 0; i < 9; i++) board[(y*9) + i] = 0x0F;
|
|
if(h) for(int i = 0; i < 8; i++) board[(i*9) + x] = 0x0F;
|
|
|
|
return w | h;
|
|
}
|
|
|
|
int movecheck(uint8_t* board, int x, int y, uint8_t rune) {
|
|
if(getrune(rune) == 1) if(getrune(board[(y*9) + x])) return 1;
|
|
if(getrune(board[(y*9) + x])) return 0;
|
|
int l = x ? board[(y*9) + (x - 1)] : 0x0F,
|
|
r = (x < 8) ? board[(y*9) + (x + 1)] : 0x0F,
|
|
u = y ? board[((y-1)*9) + x] : 0x0F,
|
|
d = (y < 7) ? board[((y+1)*9) + x] : 0x0F;
|
|
if(getrune(rune) == 2) return !!getrune(l|r|u|d);
|
|
|
|
int rnc = getfg(rune);
|
|
int rnt = getrune(rune);
|
|
|
|
int lc = getfg(l) == rnc,
|
|
rc = getfg(r) == rnc,
|
|
uc = getfg(u) == rnc,
|
|
dc = getfg(d) == rnc;
|
|
int lt = getrune(l) == rnt,
|
|
rt = getrune(r) == rnt,
|
|
ut = getrune(u) == rnt,
|
|
dt = getrune(d) == rnt;
|
|
|
|
if(l == 0x2F) lt++;
|
|
if(r == 0x2F) rt++;
|
|
if(u == 0x2F) ut++;
|
|
if(d == 0x2F) dt++;
|
|
|
|
int ac = 0;
|
|
if(!getrune(l)) { lt++; ac++; }
|
|
if(!getrune(r)) { rt++; ac++; }
|
|
if(!getrune(u)) { ut++; ac++; }
|
|
if(!getrune(d)) { dt++; ac++; }
|
|
if(ac == 4) return 0;
|
|
|
|
int ms = 4;
|
|
if(lc || lt) ms--;
|
|
if(rc || rt) ms--;
|
|
if(uc || ut) ms--;
|
|
if(dc || dt) ms--;
|
|
|
|
return !ms;
|
|
}
|
|
|
|
int musplay = 1;
|
|
|
|
void fsig(int sig) {
|
|
if(sig == SIGUSR1) musplay ^= 1;
|
|
else closef = 1;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
// we can just call the signal
|
|
// that is because c is the best
|
|
// make sure window is big enough
|
|
winch(0);
|
|
|
|
pa_simple *s;
|
|
pa_sample_spec ss;
|
|
ss.format = PA_SAMPLE_S16NE;
|
|
ss.channels = 2;
|
|
ss.rate = 44100;
|
|
|
|
int err;
|
|
|
|
long id = getpid();
|
|
long p = fork();
|
|
if(!p) {
|
|
uint8_t aubuf[44100];
|
|
xmp_context xc = xmp_create_context();
|
|
xmp_load_module_from_memory(xc, module, modsize);
|
|
xmp_start_player(xc, 44100, 0);
|
|
s = pa_simple_new(NULL, "alchemy-banal", PA_STREAM_PLAYBACK, NULL, "the game on earth", &ss, NULL, NULL, NULL);
|
|
signal(SIGINT, fsig);
|
|
signal(SIGUSR1, fsig);
|
|
while(1) {
|
|
if(closef || kill(id, 0) == ESRCH) {
|
|
pa_simple_free(s);
|
|
xmp_release_module(xc);
|
|
xmp_free_context(xc);
|
|
return 0;
|
|
} else if(musplay) {
|
|
xmp_play_buffer(xc, aubuf, 44100, 0);
|
|
pa_simple_write(s, aubuf, 44100, &err);
|
|
}
|
|
}
|
|
}
|
|
|
|
// init boardmem
|
|
memset(board, 0xE, 72);
|
|
board[31] = 0x2F;
|
|
|
|
/* bug test a
|
|
for(int x = 1; x < 9; x++) board[x] = 0x2F;
|
|
for(int x = 1; x < 8; x++) board[x*9] = 0x2F;
|
|
*/
|
|
|
|
/* bug test b - edge test r
|
|
for(int x = 0; x < 8; x++) board[x*9] = 0x2F;
|
|
*/
|
|
|
|
/* bug test c - edge test l
|
|
for(int x = 1; x < 9; x++) board[x*9-1] = 0x2F;
|
|
*/
|
|
|
|
/* bug test d - edge test tb
|
|
for(int x = 1; x < 9; x++) {
|
|
board[x] = 0x2F;
|
|
board[9*7-1+x] = 0x2F;
|
|
}
|
|
*/
|
|
|
|
// rng for runes
|
|
srand(time(NULL));
|
|
|
|
// trap window change. other
|
|
// signals not necessary since
|
|
// setting term attributes lets
|
|
// us trap things like ^C
|
|
signal(SIGWINCH, winch);
|
|
|
|
// terminal config (capture all input)
|
|
tcgetattr(0, &term);
|
|
term_b = term;
|
|
term.c_iflag &= ~(ICRNL|IXON);
|
|
term.c_lflag &= ~(ICANON|ECHO|ISIG|IEXTEN);
|
|
tcsetattr(0, TCSANOW, &term);
|
|
setbuf(stdout, NULL);
|
|
fcntl(0, F_SETFL, O_NONBLOCK);
|
|
|
|
// game init complete
|
|
// begin actual game loop
|
|
clrscr();
|
|
unsigned char // peak formatting
|
|
c, // current input
|
|
y = 0, // current cursor x
|
|
x = 0, // current cursor y
|
|
v = 1, // move validity
|
|
stage = 1, // stage
|
|
level = 0, // current goop(?) level
|
|
r = newrune(), // current rune
|
|
mode = 0; // current mode
|
|
|
|
unsigned int score = 0, aframe = 0, vr = 0;
|
|
|
|
printf(hdr);
|
|
render_board(board, 20, 0, r, 1, 1);
|
|
goto eloop;
|
|
while(1) {
|
|
if(closef) goto cleanup;
|
|
if((c = getchar()) != 255) {
|
|
eloop:
|
|
if(mode != 1) switch(c) {
|
|
case ' ':
|
|
// place rune
|
|
if(!getrune(board[(y*9) + x]) && (getrune(r) > 2)) {
|
|
// TODO movecheck
|
|
if(movecheck(board, x, y, r)) board[(y*9) + x] = r;
|
|
else goto nrr;
|
|
} else if((getrune(r) == 2) && !getrune(board[(y*9) + x])) {
|
|
board[(y*9) + x] = r;
|
|
} else if((getrune(r) == 1) && getrune(board[(y*9) + x])) {
|
|
board[(y*9) + x] = 0xF;
|
|
}
|
|
else goto nrr;
|
|
rr:
|
|
vr = 1;
|
|
for(int z = 0; z < 72; z++) if(!getbg(board[z])) vr = 0;
|
|
if(vr) {
|
|
stage++;
|
|
memset(board, 0xE, 72);
|
|
board[31 + (((rand() & 1) ? -1 : 1) * (rand() & 0x7))] = 0x2F;
|
|
}
|
|
|
|
r = newrune();
|
|
score++;
|
|
if(level) level--;
|
|
clrscr();
|
|
printf(hdr);
|
|
if(linecheck(board, x, y)) score += 4;
|
|
render_board(board, score, level, r, stage, v);
|
|
nrr: break;
|
|
case 'x':
|
|
// discard rune
|
|
level++;
|
|
r = newrune();
|
|
clrscr();
|
|
printf(hdr);
|
|
render_board(board, score, level, r, stage, v);
|
|
if(level >> 2) {
|
|
clrscr();
|
|
printf("you lost :/\n");
|
|
printf(hdr);
|
|
render_board(board, score, level, r, stage, v);
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
case 'i':
|
|
// up
|
|
if(y != 0) y--;
|
|
break;
|
|
case 'j':
|
|
// left
|
|
if(x != 0) x--;
|
|
break;
|
|
case 'k':
|
|
// down
|
|
if(y < 7) y++;
|
|
break;
|
|
case 'l':
|
|
// right
|
|
if(x < 8) x++;
|
|
break;
|
|
case 'q':
|
|
// quit
|
|
clrscr();
|
|
printf(hdr);
|
|
render_board(board, score, level, r, stage, v);
|
|
goto cleanup;
|
|
break;
|
|
}
|
|
if(c == 'a') {
|
|
mode ^= 1;
|
|
kill(p, SIGUSR1);
|
|
clrscr();
|
|
if(mode == 1) {
|
|
printf(
|
|
"ALCHEMY BANAL\n"
|
|
"original game by\n"
|
|
" PopCap Games\n"
|
|
"this version by\n"
|
|
" @mothcompute\n"
|
|
"CONTROLS:\n"
|
|
"SPACE to place rune\n"
|
|
" X to discard rune\n"
|
|
" Q to quit\n"
|
|
" IJKL to move cursor\n"
|
|
"& = skull | * = wild"
|
|
);
|
|
} else {
|
|
initbrd:
|
|
printf(hdr);
|
|
render_board(board, score, level, r, stage, movecheck(board, x, y, r));
|
|
}
|
|
}
|
|
if(!mode) {
|
|
clrscr();
|
|
printf(hdr);
|
|
render_board(board, score, level, r, stage, movecheck(board, x, y, r));
|
|
}
|
|
}
|
|
if(!mode) posupdate(x*2, y + 2);
|
|
}
|
|
|
|
cleanup:
|
|
tcsetattr(0, TCSANOW, &term_b);
|
|
kill(p, SIGINT);
|
|
}
|