/* The MIT License (MIT) Copyright (c) 2021 Max Breedon 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. */ #include #include #include #include #define W 10 #define H 9 // liberal use of globals & statics makes the compiled rom smaller! // also believe it or not, making COLORS a #define 6 actually takes up more rom space const unsigned char COLORS = 6; const unsigned char actualcolours[] = {2, 4, 6, 7, 11, 13}; unsigned char grid[W][H]; unsigned char g_multiplier; unsigned int g_time, g_score; unsigned char g_setting_up; unsigned char fire, lastfire; void slowpoke(int a, unsigned char v) { POKE(a, v); } void draw_single_char_xy(unsigned char x, unsigned y, unsigned char tile) { static unsigned char uc_lo, uc_hi; int lo = (int)y * 32 + (int)x; int hi = lo / 256; lo = lo - hi * 256; uc_lo = (unsigned char) lo; uc_hi = (unsigned char) hi; // tell the VDU where to draw the character by writing to the register at 0x3001 __asm__("sei"); POKE(0x3001, 0x00 + uc_lo); POKE(0x3001, 0x10 + uc_hi | 0x40); // 0x40 sets write __asm__("cli"); // the character to write is written to 0x3000 POKE(0x3000, tile); } void draw_tile(unsigned char x, unsigned char y, unsigned char tile) { // this also updates the grid static int c; c = tile; c = 8 * (c / 8) + 2; // need to strp off cursor and underline! grid[x][y] = (unsigned char) c; x += 3; y++; draw_single_char_xy(x * 2, y * 2, tile); draw_single_char_xy(x * 2 + 1, y * 2, tile); draw_single_char_xy(x * 2, y * 2 + 1, tile); draw_single_char_xy(x * 2 + 1, y * 2 + 1, tile); } void delay(int t) { static int y; if (g_setting_up) return; for (y = 0; y < t * 300; y++); } void show_score() { if (g_setting_up) return; gotoxy(0, 23); cprintf("score %d ", g_score); } void show_time() { if (g_setting_up) return; gotoxy(24, 23); cprintf("time %3d", g_time); } void collapse_hoz(unsigned char sy, unsigned char sx1, unsigned char sx2) { static int x, y; for (x = sx1; x <= sx2; x++) { draw_tile(x, sy, grid[x][sy] - 2); // animates the matches with boxcursors delay(2); } delay(2); for (x = sx1; x <= sx2; x++) { draw_tile(x, sy, 160 + ' '); // blanks them out delay(2); } for (y = sy - 1; y >= 0; y--) { // copy row to row below for (x = sx1; x <= sx2; x++) { draw_tile(x, y + 1, grid[x][y]); draw_tile(x, y, 160 + ' '); } delay(2); } // new rand subrow for (x = sx1; x <= sx2; x++) { draw_tile(x, 0, 2 + 8 * (rand() % COLORS)); } } void collapse_vert(unsigned char sx, unsigned char sy1, unsigned char sy2) { static int j, y, togodown, t; for (y = sy1; y <= sy2; y++) { draw_tile(sx, y, grid[sx][y] - 2); // animates the matches with boxcursors delay(2); } for (y = sy1; y <= sy2; y++) { draw_tile(sx, y, 160 + ' '); // blanks them out delay(2); } // animates tiles moving down togodown = sy2 - sy1 + 1; for (y = sy1 - 1; y >= 0; y--) { t = grid[sx][y]; for (j = 1; j <= togodown; j++) { draw_tile(sx, y + j - 1, 160 + ' '); draw_tile(sx, y + j, t); delay(1); } } // new tiles fall down for (y = 0; y < togodown; y++) { t = 2 + 8 * (rand() % COLORS); for (j = 0; j < togodown - y; j++) { if (j > 0) draw_tile(sx, j - 1, 160 + ' '); draw_tile(sx, j, t); delay(2); } } } unsigned char look_for_matches() { static unsigned char rc, x, y, ox, oy; rc = 0; // hoz matches for (y = 0; y < H; y++) { for (x = 0; x < W - 2; x++) { ox = 1; // is actually number matched, inc self while (1 && x + ox < W && grid[x][y] == grid[x + ox][y]) { ox++; } if (ox >= 3) { collapse_hoz(y, x, x + ox - 1); rc = 1; x += ox; g_score += ox * g_multiplier; g_time++; show_score(); show_time(); } } } // vert matches for (x = 0; x < W; x++) { for (y = 0; y < H - 2; y++) { oy = 1; // is actually number matched, inc self while (1 && y + oy < H && grid[x][y] == grid[x][y + oy]) { oy++; } if (oy >= 3) { collapse_vert(x, y, y + oy - 1); rc = 1; y += oy; g_score += oy * g_multiplier; g_time++; show_score(); show_time(); } } } return rc; } int adjacent_selection(int x, int y, int a, int b) { return abs(x - a) == 1 && y == b || x == a && abs(y - b) == 1; } void setup_video() { static unsigned char i, j; clrscr(); // this is unreliable on real hardware! // this sets up the colours to be used __asm__("sei"); // for (i = 0; i < COLORS; i++) { POKE(0x3001, 0x00 + i); // $1800 start at POKE(0x3001, 0x18 | 0x40); // 0x40 sets write POKE(0x3000, 1 + 0x10 * actualcolours[i]); // i is the fg, black (1) is the bg } // custom characters / patterns at 0x0000 // you can't just define 3 custom chars (ie blank, selected and cursored) ... you have to // redfinie the chars as many times as you need them in different colours! because you // have to define colours as pre-tinting blocks of 8 characters // eg set ASCII 64 to 71 as red fg & black bg, and then ASCII 72 to 80 as green fg & white bg, etc slowpoke(0x3001, 0x00); // 0x0000 - char 0 slowpoke(0x3001, 0x00 | 0x40); // 0x40 sets write // if we don't use slow pokes then vdp can't keep up on real hardware! for (i = 0; i < COLORS; i++) { // cursor one has black border slowpoke(0x3000, 0x00); slowpoke(0x3000, 0x7e); slowpoke(0x3000, 0x7e); slowpoke(0x3000, 0x7e); slowpoke(0x3000, 0x7e); slowpoke(0x3000, 0x7e); slowpoke(0x3000, 0x7e); slowpoke(0x3000, 0x00); // selected one has black underline slowpoke(0x3000, 0xff); slowpoke(0x3000, 0xff); slowpoke(0x3000, 0xff); slowpoke(0x3000, 0xff); slowpoke(0x3000, 0xff); slowpoke(0x3000, 0xff); slowpoke(0x3000, 0x00); slowpoke(0x3000, 0x00); // next 6 are filled (doing 6 because colours are attached to blocks of 8 characters!) for (j = 0; j < 48; j++) { // 6*8 slowpoke(0x3000, 0xff); } } __asm__("cli"); // this did not help } void game() { // Quick explainer on tile numbers, and what char to draw // tiles are numbered 0,1,2 then 8,9,10 then 16,17,18 etc // with each set of 3 being one colour. // The first of the 3 (eg 0, or 8 or 16 etc) is draw with black border // (and since it is drawn 4x it looks like a + in a box). // The 2nd of the e (eg 1, 9 or 17) is just a line on the bottom // (and when drawn 4x it looks like =). // And the 3rd of the 3 (eg 2,10,18) is just a blank tile filled with colour. // The tile drawn is: // color*8+2 for blank // color*8+0 for cursor // color*8+1 for underlined (selected) static unsigned char x, y, joy, lastj; static unsigned char cursor_x, cursor_y, currt; static unsigned char sel_x, sel_y; // setup board lastj = 0, lastfire = 0; cursor_x = W / 2, cursor_y = H / 2, currt = 0; sel_x = 99; clrscr(); // yes it appears twice for (y = 0; y < H; y++) { for (x = 0; x < W; x++) { draw_tile(x, y, 2 + 8 * (rand() % COLORS)); } } g_setting_up = 1; while (look_for_matches()); // pre-collapse any matches after board gen g_setting_up = 0; g_score = 0; g_time = 9; currt = grid[cursor_x][cursor_y]; show_score(); show_time(); draw_tile(cursor_x, cursor_y, currt - 2); // -2 is cursor variant // actual game loop while (1) { static int t = 0; t++; if (t == 5000) { t = 0; g_time--; show_time(); } if (g_time == 0) break; // game over joy = PEEK(17); // up=73, d=65, l=77, r=69 // joystick moved if (joy != lastj) { lastj = joy; currt = grid[cursor_x][cursor_y]; if (sel_x != cursor_x || sel_y != cursor_y) draw_tile(cursor_x, cursor_y, currt); // blank out old tile unless its selected if (joy == 73 && cursor_y > 0) cursor_y--; if (joy == 65 && cursor_y < H - 1) cursor_y++; if (joy == 77 && cursor_x > 0) cursor_x--; if (joy == 69 && cursor_x < W - 1) cursor_x++; currt = grid[cursor_x][cursor_y]; if (sel_x != cursor_x || sel_y != cursor_y) draw_tile(cursor_x, cursor_y, currt - 2); // don't draw over selected tile (tile #1) } fire = PEEK(22); // 34 is pressed, 0 nothing if (fire != lastfire) { lastfire = fire; if (fire == 34) { if (sel_x == 99) { // nothing selected yet currt = grid[cursor_x][cursor_y]; draw_tile(cursor_x, cursor_y, currt - 1); // convert square to underline sel_x = cursor_x; sel_y = cursor_y; } else { // try match if (sel_x == cursor_x && sel_y == cursor_y) { // unselect sel_x = 99; currt = grid[cursor_x][cursor_y]; draw_tile(cursor_x, cursor_y, currt - 2); // turns underline %8==1 into cursor %8==0 } else if (adjacent_selection(cursor_x, cursor_y, sel_x, sel_y)) { // swap them static unsigned char A, B, found; found=0; A = grid[sel_x][sel_y]; B = grid[cursor_x][cursor_y]; draw_tile(cursor_x, cursor_y, A); // remove cursor draw_tile(sel_x, sel_y, B); // not selected anymore // now look everywhere for matches g_multiplier = 1; while (1) { static unsigned char f; f = look_for_matches(); found += f; if (!f) break; g_multiplier++; } if (found) { A = grid[cursor_x][cursor_y]; draw_tile(cursor_x, cursor_y, A - 2); // redraw cursor sel_x = 99; } else { // no match, reset it A = grid[sel_x][sel_y]; B = grid[cursor_x][cursor_y]; draw_tile(cursor_x, cursor_y, A - 2); draw_tile(sel_x, sel_y, B - 1); } } // set selection to latest click else { draw_tile(sel_x, sel_y, grid[sel_x][sel_y]); currt = grid[cursor_x][cursor_y]; draw_tile(cursor_x, cursor_y, currt - 1); // convert square to underline sel_x = cursor_x; sel_y = cursor_y; } } } } } gotoxy(23, 23); cprintf("game over"); while (1) { fire = PEEK(22); if (fire != lastfire) { lastfire = fire; if (fire == 34) return; } } } int main() { static unsigned char x; setup_video(); gotoxy(0, 23); cprintf("press fire"); // seed rng while (1) { x++; fire = PEEK(22); if (fire != lastfire) { lastfire = fire; if (fire == 34) break; } } srand(x); while(1) { game(); } }