#include <vectrex.h> const char *version = "VERSION: STEVEDORE - MICROBAN 1.1"; // *MUST* compile with -O1 - other -O flags cause erroneous behaviour /* Help Steve the Stevedore to move the shipping containers around the warehouse until they're all stacked up neatly up on the docking bay (the dark colored ground) ready to be picked up by the dock's crane. Steve isn't strong enough to push two crates at once, or to pull them towards himself - he can only push. This is the game Sokoban, which was created around 1980/1981 by Hiroyuki Imabayashi. ( http://www.sokoban.jp/ ) Sokoban is Japanese for "Warehouse keeper" (???). The game is also known as Pocoman. It was first marketed in 1982 under the "Thinking Rabbit" label and ported to English-speaking countries a few years later by Spectrum Holobyte. There's lots of Sokoban info for people interested in learning about the game at http://sokoban-jd.blogspot.com/2014/05/the-original-collection.html and http://www.erimsever.com/sokoban1.htm Sokoban is a challenging intellectual puzzle, not just a kid's game. New levels can be found at: https://www.sourcecode.se/sokoban/levels or http://www.abelmartin.com/rj/sokobanJS/Skinner/David%20W.%20Skinner%20-%20Sokoban_files/Sasquatch.txt with a description of the level format at http://www.sokobano.de/wiki/index.php?title=Level_format#Level_file_format Because the borders of the play area contribute the greatest number of blocks - more than we can comfortably draw for a reasonably-sized game - we are drawing the borders separately with a custom procedure. Unmovable blocks within the play area are however drawn as blocks, for now. */ // HOW IT WORKS: // // We have a 2D array map[][] which is the max 16x16 play area plus a 1-wide border for walls // all around. (so an 18x18 array) // // map[1][1] is the square in the top left corner, map[16][16] is the bottom right corner. // The outside wall is not drawn as blocks but as a single line, so not a lot of extra space // around the play area is needed. // // All drawing is done with a single display list, which contains a sequence of objects. Each // object is positioned absolutely using a positional scaling factor, then is drawn relative to // that starting point using relative moves and lines, at a separate displaying scale factor. // // The display list is built and not changed in any structural sense, however the positioning // coordinates within the list are writable, so to move a block we simply update the x,y position // of that block in the display list and the block moves on screen. Similarly changing the // appearance of a block is trivial - one of the items in the display list for that object is // a pointer to a constant array containing the vectors to be drawn - so by changing that pointer // for a different one, the object's appearance is changed. // // The map therefore will contain only movable blocks and the target squares. The player himself // will be handled specially and the fixed blocks will be in the map but not as pointers to // display list objects - they will only be there for testing whether moves are possible or blocked. // // There are actually two map arrays - map[][] contains information as to the type of object // at the row,col coordinate, and blockptr[][] is the pointer to (well, an index to) the actual // block object to be drawn on the display. // The info stored in map[][] is one or more of the bit flags: // BOX, PLAYER, TARGET, WALL // Although it would be quite possible to take the code which converts an ascii puzzle description // into a set of vectors for drawing and implement it within this program rather than separately, // there is no point in doing so because the output vectors would have to be stored in RAM. By // making the converter a separate program, we can write those data structures to a C header file // thus ensuring that the data is stored in ROM and therefore keeping valuable RAM space free. // #define strlen mystrlen // avoid problem with gcc6809. TOCOTOX. #define uint8 unsigned int #define int8 int // print a message and resume when any button pressed... #define PAUSE(s) do {Prev_Btn_State = Vec_Btn_State; \ Wait_Recal(); Intensity_5F(); \ Read_Btns(); \ Print_Str_d(0, -40, s "\x80"); \ } while (((Vec_Btn_State & ~Prev_Btn_State)&15) == 0) #ifndef TRUE #define TRUE (0==0) #define FALSE (0!=0) #endif #ifdef W #undef W #endif #ifdef H #undef H #endif #define W 20 #define H -20 #undef IN_ROM #define IN_ROM __attribute__ ((section(".text;"))) static uint8 Prev_Btn_State; //#include "puzzles_0000_0009.h" //#include "puzzles_0010_0019.h" #include "microban.h" #ifndef ZERO_DELAY #define ZERO_DELAY 0 // WAS 5 - but setting this lower doesn't seem to make any difference except // gaining 1700 cycles. #endif typedef struct object { int y; // move to y,x at position_scale int x; signed char *u; // first byte is #vecs, followed by vecs*3 ints in const area // icon is drawn at drawmovedelta_scale // change shape of icon or hide it by replacing pointer } object; #define MAX_OBJECTS 26 // can increase this within limits of ram and screen refresh ability // currently, generator (level.c) limits games to those with <= 30 objects // each OBJECT adds 4 bytes of RAM // May need to re-tune that to <= 26 as we are having problems at 31 typedef struct sokoban_list { uint8 position_scale, drawmovedelta_scale; uint8 num_objects; object icon[MAX_OBJECTS]; } sokoban_list; // [COL][ROW] 0 is the tombstone, 1:16 are the blocks, 17 is a tombstone #define MAX_COL 16L #define MAX_ROW 16L // 1-unit border around map to handle walls. // 100 vs 746 for [1][1] * 2 vs [18][18] * 2 // 100-2 + 324*2 = 98+648 = 746 // This awkward construct is because the 'obvious' construct was generating bad code. #ifdef ORIGINAL static uint8 blockptr_xy[1L+MAX_COL+1L][1L+MAX_ROW+1L]; // blockptr is *ONLY* used with movable boxes #define blockptr_xy(x,y) blockptr_xy[x][y] static uint8 map_xy[1L+MAX_COL+1L][1L+MAX_ROW+1L]; // Supports 16x16 map, with a layer of tombstones (blocks) all around #define map_xy(x,y) map_xy[x][y] #else static uint8 bxy[324L]; #define blockptr_xy(x,y) bxy[(long)((long)y)*18L+(long)((long)x)] static uint8 mxy[324L]; #define map_xy(x,y) mxy[(long)((long)y)*18L+(long)((long)x)] #endif static unsigned int player_block; // if desperately short of ram, could remove these and just use icon[player_block] ... static int player_map_x, player_map_y; #define BOX 1U #define PLAYER 2U #define TARGET 4U #define WALL 8U #ifdef H #undef H #endif #ifdef W #undef W #endif #define H 96 #define W 106 // I need to see these on several vectrexes to be sure the +/-1 tweaks below are legitimate, // and not just handling the idiosyncrasies of one display. Carefully tweaked by inspection // to look right - adjusting for width of line and alignment // [] #define WALL_VECS 4 const signed char sq_wall[1+WALL_VECS*3] __attribute__ ((section(".text;;"))) = { WALL_VECS, -1, H, 0, -1, 0, W, -1, -H, 0, -1, 0, -W}; // X #define PLAYER_VECS 3 const signed char sq_player[1+PLAYER_VECS*3] __attribute__ ((section(".text;;"))) = { PLAYER_VECS, -1, H-2, W, 0, 3, -W, -1, -H+2, W-1}; // + #define TARGET_VECS 4 const signed char sq_target[1+TARGET_VECS*3] __attribute__ ((section(".text;;"))) = { TARGET_VECS, 0, H/2, 0, -1, 0, W, 0, -(H/2), -(W/2)+4, -1, H, 0}; // [X] #define BOX_VECS 7 const signed char sq_box[1+BOX_VECS*3] __attribute__ ((section(".text;;"))) = { BOX_VECS, -1, H, W, -1, 0, -W, -1, -H, W, -1, 0, -W, -1, H, 0, 0, 0, W, -1, -H, 0}; const signed char const sq_empty[1] __attribute__ ((section(".text;;"))) = {0}; static void draw_sokoban(sokoban_list *soko) { signed char *u; object *icon; int num_vectors; uint8 num_objects; uint8 scaleMove, scaleList; // copy these so they are not modified in situ. num_objects = soko->num_objects; scaleMove = soko->position_scale; scaleList = soko->drawmovedelta_scale; while (num_objects --> 0) { // loop over individual objects // (saves 50 cycles by drawing in reverse order) icon = &soko->icon[num_objects]; int x, y; x = icon->x; y = icon->y; // initial absolute positioning at lower-left corner of object: // resync / startsync dp_VIA_shift_reg = 0; // all output is BLANK // move to zero dp_VIA_cntl = (uint8)0xcc; // zero the integrators dp_VIA_port_a = 0; // reset integrator offset dp_VIA_port_b = (int)0b10000010; // delay, till beam is at zero // volatile - otherwise delay loop does not work with -O // Experimenting with removing delays - all seems to still work... // for (volatile signed int b=ZERO_DELAY; b>0; b--); dp_VIA_port_b= (int)0b10000011; // this can be done before the wait loop // since it only fills the latch, not the actual timer! dp_VIA_t1_cnt_lo = scaleMove; // ONLY INITIAL MOVE IS AT scaleMove //if ((u[1]) || (u[2])) { // internal moveTo dp_VIA_port_a = y; // y pos to dac dp_VIA_port_b = 0; // mux enable, dac to -> integrator y (and x) dp_VIA_cntl = (uint8)0xce; // disable zero, disable all blank dp_VIA_port_b = 1; // mux disable, dac only to x dp_VIA_port_a = x; // dac -> x dp_VIA_t1_cnt_hi = 0; // start timer while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes //} // num_vectors is the first item in the 'u' array (really a poor man's struct) u = icon->u; if (u == sq_player) Intensity_7F(); else Intensity_a(0x4f); // overkill but simple num_vectors = *u++; // Weirdly, if you move the two lines above to before "if ((u[1]) || (u[2])) {" // (and uncomment the conditional) it is actually *slower*!!! So don't. while (num_vectors --> 0) { // loop over lines within an object if (u[0] < 0) { // drawrel // draw a vector dp_VIA_port_a = u[1]; // y pos to dac dp_VIA_port_b = 0; // mux enable, dac to -> integrator y (and x asm volatile (" nop"); dp_VIA_port_b = 1; // mux disable, dac only to x dp_VIA_port_a = u[2]; // dac -> x dp_VIA_t1_cnt_lo = scaleList; dp_VIA_t1_cnt_hi = 0; // start timer dp_VIA_shift_reg = 0xff; // draw complete line //if (scaleList>10) { // always is. while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes //} dp_VIA_shift_reg = 0; // all output is BLANK } else if (u[0] == 0) { // moverel if ((u[1]) || (u[2])) { // internal moveTo dp_VIA_port_a = u[1]; // y pos to dac dp_VIA_port_b = 0; // mux enable, dac to -> integrator y (and x) dp_VIA_cntl = (uint8)0xce; // disable zero, disable all blank dp_VIA_port_b = 1; // mux disable, dac only to x dp_VIA_port_a = u[2]; // dac -> x dp_VIA_t1_cnt_lo = scaleList; // Moverel - THESE MOVES ARE AT SAME SCALE AS LINE DRAWING dp_VIA_t1_cnt_hi = 0; // start timer while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes } } else if (u[0] == 1) { // moveabs - repositioning, same as initial position // resync / startsync - uses "scaleMove" dp_VIA_shift_reg = 0; // all output is BLANK // move to zero dp_VIA_cntl = (uint8)0xcc; // zero the integrators dp_VIA_port_a = 0; // reset integrator offset dp_VIA_port_b = (int)0b10000010; // delay, till beam is at zero // volatile - otherwise delay loop does not work with -O // seems to still work if ZERO_DELAY is set to 0 (default was 5) // or even if this delay is removed altogether: // for (volatile signed int b=ZERO_DELAY; b>0; b--); dp_VIA_port_b= (int)0b10000011; // this can be done before the wait loop // since it only fills the latch, not the actual timer! dp_VIA_t1_cnt_lo = scaleMove; } else { // PAUSE("UNKNOWN VECTOR CODE"); // removing this 'break' (which is never executed) slows the code by about 2200 cycles! break; // SERIOUSLY. DO NOT DELETE THIS LINE. } u += 3; } } // end of loop over individual objects } // In grid coordinates, this adjusts to 0,0 #define XOFF 9 #define YOFF 9 #ifdef NEVER // and this centers play areas smaller than 16x16 // except now the generator program (levels.c) creates 16x16 // grids with the area pre-centered. Ish. static unsigned int x_correction, y_correction; #endif static void init_blocks(sokoban_list *everything) { int h, w; for (h = 0; h <= MAX_ROW+1; h++) { for (w = 0; w <= MAX_COL+1; w++) { blockptr_xy(w,h) = 255; // unfortunately block[0] is (currently) potentially a valid block map_xy(w,h) = 0; } } everything->position_scale = 0x7c; everything->drawmovedelta_scale = 0x0c; everything->num_objects = 0; } const signed char const *lookup[16] __attribute__ ((section(".text;;;"))) = { /* 0 */ sq_empty, /* 1 */ sq_box, /* 2 */ sq_player, /* 3 */ sq_empty, /* 4 */ sq_target, /* 5 */ sq_empty, /* 6 */ sq_empty, /* 7 */ sq_empty, /* 8 */ sq_wall, /* 9 */ sq_empty, /* 10 */ sq_empty, /* 11 */ sq_empty, /* 12 */ sq_empty, /* 13 */ sq_empty, /* 14 */ sq_empty, /* 15 */ sq_empty }; static uint8 add_block(sokoban_list *everything, uint8 blocktype, uint8 row, uint8 col) { object *icon; if (everything->num_objects == MAX_OBJECTS) PAUSE("NUM OBJECTS"); icon = &everything->icon[everything->num_objects]; icon->x = (signed char)((col-XOFF/*+x_correction*/)*16); icon->y = (signed char)(((17-row)-YOFF/*+y_correction*/)*16); icon->u = (signed char *)lookup[blocktype&15]; return everything->num_objects++; } typedef struct wall_list { uint8 position_scale, drawmovedelta_scale; uint8 num_objects; object icon[1]; } wall_list; wall_list wall; void add_walls(char *list) { object *icon; icon = &wall.icon[0]; icon->x = (signed char)21; icon->y = (signed char)5; icon->u = (signed char *)list; wall.position_scale = 0x80; wall.drawmovedelta_scale = 0x63; wall.num_objects = 1; } static void add_element_yx(sokoban_list *everything, char blocktype, uint8 row, uint8 col) { // convert from ascii representation to displaylist and map. // row 0 is the top tombstone row // row 1 is the first displayed row of blocks, increasing row numbers going downwards // col 0 is the leftmost tombstone wall /* Level element Character ASCII Code Wall # 0x23 also + - | form outside wall Player @ 0x40 Player on goal square + 0x2b except it isn't :-( Fortunately none of these maps use this Box $ 0x24 Box on goal square * 0x2a also not being used currently Goal square (aka target). 0x2e Floor (Space) 0x20 */ switch (blocktype) { case ' ': return; // free floor space case '|': // circumfusion of blocks forms the surrounding wall case '+': case '-': map_xy(col,row) |= WALL; // tombstones for wall. //add_block(everything, WALL, row, col); // debugging. this will cause flicker return; // boundary wall - set in map, but outer wall not shown on screen as blocks // for efficiency of drawing. Inner blocks have to be drawn however. case '#': (void)add_block(everything, WALL, row, col); map_xy(col,row) |= WALL; return; case '@': player_block = add_block(everything, PLAYER, row, col); map_xy(col,row) |= PLAYER; // Although the player is stored in the displaylist like everything else, // and pointed to via 'player_block', for convenience we are also cacheing // the player's map info separately: #define player_screen_x everything->icon[player_block].x #define player_screen_y everything->icon[player_block].y player_map_x = (int)col; // (otherwise retrieving player x,y is expensive) player_map_y = (int)row; return; case '.': (void)add_block(everything, TARGET, row, col); map_xy(col,row) |= TARGET; // check game over when all boxes are in BOX|TARGET map squares return; case '$': // we only assign movable blocks to blockptr array. blockptr_xy(col,row) = add_block(everything, BOX, row, col); map_xy(col,row) |= BOX; return; default: return; } } #define replace(everything, blockno, blocktype) everything->icon[blockno].u = (signed char *)lookup[blocktype&15] static inline void move_block_yx(sokoban_list *everything, uint8 blockno, uint8 row, uint8 col) { // A proper animation can move 1 unit at a time for 16 frames and would look // much smoother. Will do this after everything else is sorted out... everything->icon[blockno].x = (signed char)((col-XOFF/*+x_correction*/)*16); everything->icon[blockno].y = (signed char)(((17-row)-YOFF/*+y_correction*/)*16); } static void move_box_xy(sokoban_list *everything, uint8 oldx, uint8 oldy, uint8 newx, uint8 newy) { uint8 blockno; blockno = blockptr_xy(oldx,oldy); blockptr_xy(oldx,oldy) = 255; // unused map_xy(oldx,oldy) &= ~BOX; if (blockno != 255) move_block_yx(everything, blockno, newy, newx); // update graphics blockptr_xy(newx,newy) = blockno; map_xy(newx,newy) |= BOX; } static sokoban_list everything; static char tombstone[20]; static char *fade_message; // = &tombstone[1] static unsigned int fade_timer; static uint8 tick; static uint8 thisgame; static uint8 w, h; static int complete; void fade_out(const char *message) { int i; char *fp = fade_message; for (i = 0; i < 11; i++) { if (message[i] == '\0') break; *fp++ = message[i]; } *fp++ = '\x80'; *fp = '\0'; fade_timer = 128U; // must be even } #define RESTART 1 #define ABANDON 2 // static so there are no stack-based surprises. static int action; static int move_left, move_right, move_up, move_down; static int Prev_Joy_1_X, Prev_Joy_1_Y; int main(void) { tombstone[0] = 0; fade_message = &tombstone[1]; tick = 0; #ifdef NEVER x_correction = 0; y_correction = 0; #endif fade_message[0] = '\x80'; init_blocks(&everything); // static part of initialisation regardless of puzzle chosen thisgame = 0; #ifdef NEVER // may not need these any more... x_correction = 0; y_correction = 0; #endif for (h = 0; h < 18; h++) { for (w = 0; w < 18; w++) add_element_yx(&everything, games[thisgame][h*18+w], h, w); } add_walls((char *)walls[thisgame]); Joy_Digital(); Prev_Joy_1_X = Vec_Joy_1_X; Prev_Joy_1_Y = Vec_Joy_1_Y; Read_Btns(); Prev_Btn_State = Vec_Btn_State; fade_out(titles[thisgame]); for (;;) { Prev_Joy_1_X = Vec_Joy_1_X; Prev_Joy_1_Y = Vec_Joy_1_Y; Joy_Digital(); Wait_Recal(); Reset0Ref(); if (*fade_message != '\x80') { // a critical shortage of memory might be indicated by // these messages appearing unexpectedly! fade_timer -= 1U; Intensity_a(fade_timer); Print_Str_d(-120,/*-55*/-20, fade_message); if (fade_timer == 0) *fade_message = '\x80'; Reset0Ref(); } Intensity_7F(); move_left = (Vec_Joy_1_X < 0) && (!(Prev_Joy_1_X < 0)); move_right = (Vec_Joy_1_X > 0) && (!(Prev_Joy_1_X > 0)); move_up = (Vec_Joy_1_Y > 0) && (!(Prev_Joy_1_Y > 0)); move_down = (Vec_Joy_1_Y < 0) && (!(Prev_Joy_1_Y < 0)); Prev_Btn_State = Vec_Btn_State; Read_Btns(); action = 0; if ((Vec_Btn_State & ~Prev_Btn_State)&2) action = ABANDON; if ((Vec_Btn_State & ~Prev_Btn_State)&1) action = RESTART; // Deliberately NOT supporting a single-step undo. You have to go back to the start... if ((Vec_Btn_State&12) == 12) { int delay; char *solution; // RESET FOR SOLUTION DEMO - only allow if game has been reset to start... init_blocks(&everything); // static part of initialisation regardless of puzzle chosen for (h = 0; h < 18; h++) { for (w = 0; w < 18; w++) add_element_yx(&everything, games[thisgame][h*18+w]/*line[w]*/, h, w); } add_walls((char *)walls[thisgame]); // DO SOLUTION ANIMATION solution = (char *)solutions[thisgame]; while (*solution != '\0') { char command = *solution++; map_xy(player_map_x,player_map_y) &= ~PLAYER; switch (command) { case 'L': move_box_xy(&everything, (unsigned int)player_map_x-1U,(unsigned int)player_map_y, ((unsigned int)player_map_x-2U),(unsigned int)player_map_y); case 'l': player_map_x -= 1; break; case 'R': move_box_xy(&everything, (unsigned int)player_map_x+1U,(unsigned int)player_map_y, ((unsigned int)player_map_x+2U),(unsigned int)player_map_y); case 'r': player_map_x += 1; break; case 'U': move_box_xy(&everything, (unsigned int)player_map_x,((unsigned int)player_map_y)-1U, (unsigned int)player_map_x,((unsigned int)player_map_y)-2U); case 'u': player_map_y -= 1; break; case 'D': move_box_xy(&everything, (unsigned int)player_map_x,((unsigned int)player_map_y)+1U, (unsigned int)player_map_x,((unsigned int)player_map_y)+2U); case 'd': player_map_y += 1; break; } map_xy(player_map_x,player_map_y) |= PLAYER; everything.icon[player_block].x = (signed char)((player_map_x-XOFF/*+(int)x_correction*/)*16); everything.icon[player_block].y = (signed char)(((17-player_map_y)-YOFF/*+(int)y_correction*/)*16); // display, delay, and walk away... for (delay = 0; delay < 15; delay++) { Wait_Recal(); Reset0Ref(); Intensity_7F(); draw_sokoban(&everything); draw_sokoban((sokoban_list *)&wall); } } for (delay = 0; delay < 100; delay++) { Wait_Recal(); Reset0Ref(); Intensity_7F(); draw_sokoban(&everything); draw_sokoban((sokoban_list *)&wall); } // RESET FOR PLAY init_blocks(&everything); // static part of initialisation regardless of puzzle chosen for (h = 0; h < 18; h++) { for (w = 0; w < 18; w++) add_element_yx(&everything, games[thisgame][h*18+w]/*line[w]*/, h, w); } add_walls((char *)walls[thisgame]); } if (!action) { map_xy(player_map_x,player_map_y) &= ~PLAYER; if (move_up || move_down || move_left || move_right) { // Vec_Btn_State&1 left if (move_left) { if ((map_xy(player_map_x-1L,player_map_y) & WALL) == 0) { if ((map_xy(player_map_x-1L,player_map_y) & BOX) != 0) { if ((player_map_x >= 2) && ((map_xy(player_map_x-2L,player_map_y) & (BOX|WALL)) == 0)) { // movable box - push it away move_box_xy(&everything, (unsigned int)player_map_x-1U,(unsigned int)player_map_y, ((unsigned int)player_map_x-2U),(unsigned int)player_map_y); player_map_x -= 1; } } else { // empty square - move into it player_map_x -= 1; } } } // Vec_Btn_State&2 right if (move_right) { if ((map_xy(player_map_x+1L,player_map_y) & WALL) == 0) { if ((map_xy(player_map_x+1L,player_map_y) & BOX) != 0) { if ((player_map_x <= 15) && ((map_xy(player_map_x+2L,player_map_y) & (BOX|WALL)) == 0)) { // movable box - push it away move_box_xy(&everything, (unsigned int)player_map_x+1U,(unsigned int)player_map_y, ((unsigned int)player_map_x+2U),(unsigned int)player_map_y); player_map_x += 1; } } else { // empty square - move into it player_map_x += 1; } } } // Vec_Btn_State&4 up if (move_up) { if ((map_xy(player_map_x,player_map_y-1L) & WALL) == 0) { if ((map_xy(player_map_x,player_map_y-1L) & BOX) != 0) { if ((player_map_y >= 2) && ((map_xy(player_map_x,player_map_y-2L) & (BOX|WALL)) == 0)) { // movable box - push it away move_box_xy(&everything, (unsigned int)player_map_x,((unsigned int)player_map_y)-1U, (unsigned int)player_map_x,((unsigned int)player_map_y)-2U); player_map_y -= 1; } } else { // empty square - move into it player_map_y -= 1; } } } // Vec_Btn_State&8 down if (move_down) { if ((map_xy(player_map_x,player_map_y+1L) & WALL) == 0) { if ((map_xy(player_map_x,player_map_y+1L) & BOX) != 0) { if ((player_map_y <= 15) && ((map_xy(player_map_x,player_map_y+2L) & (BOX|WALL)) == 0)) { // movable box - push it away move_box_xy(&everything, (unsigned int)player_map_x,((unsigned int)player_map_y)+1U, (unsigned int)player_map_x,((unsigned int)player_map_y)+2U); player_map_y += 1; } } else { // empty square - move into it player_map_y += 1; } } } complete = TRUE; for (h = 1; h <= MAX_ROW; h++) { for (w = 1; w <= MAX_COL; w++) { if (map_xy(w,h) & BOX) { if (!(map_xy(w,h) & TARGET)) { complete = FALSE; break; } } } if (!complete) break; } } map_xy(player_map_x,player_map_y) |= PLAYER; everything.icon[player_block].x = (signed char)((player_map_x-XOFF/*+(int)x_correction*/)*16); everything.icon[player_block].y = (signed char)(((17-player_map_y)-YOFF/*+(int)y_correction*/)*16); draw_sokoban(&everything); draw_sokoban((sokoban_list *)&wall); } if (action || complete) { // reinitialise for next game... init_blocks(&everything); // static part of initialisation regardless of puzzle chosen if ((action == ABANDON) || complete) { thisgame = (thisgame + 1) % NUM_GAMES; #ifdef NEVER x_correction = 0; // probably no longer needed y_correction = 0; #endif } for (h = 0; h < 18; h++) { for (w = 0; w < 18; w++) add_element_yx(&everything, games[thisgame][h*18+w]/*line[w]*/, h, w); } add_walls((char *)walls[thisgame]); if (action == ABANDON) { fade_out(titles[thisgame]); } else if (action == RESTART) { fade_out("RESTARTING"); } else { fade_out("WELL DONE"); // would prefer to have left previous game on screen } // until this message was complete. complete = FALSE; } } return 0; }