#include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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); }