#include <ctype.h> #include <curses.h> #include "sokoban.h" ``ctype.h'' is included for the use of ``iscntrl''. ``curses.h'' is included for screen movements. ``stdio.h'' is not needed, as there is no I/O /* defining the types of move */ #define MOVE 1 #define PUSH 2 #define SAVE 3 #define UNSAVE 4 #define STOREMOVE 5 #define STOREPUSH 6 /* defines for control characters */ #define CNTL_L '\014' #define CNTL_K '\013' #define CNTL_H '\010' #define CNTL_J '\012' #define CNTL_R '\022' #define CNTL_U '\025' These local define's are all to make it easier to handle the code that follows. extern char map[MAXROW+1][MAXCOL+1]; extern short rows, cols, level, moves, pushes, savepack, packets; extern short scorelevel, scoremoves, scorepushes; extern POS ppos; The extern defs allow the compiler to generate the correct code, even though it doesn't yet have addresses of these variables static POS tpos1, /* testpos1: 1 pos. over/under/left/right */ tpos2, /* testpos2: 2 pos. " */ lastppos, /* the last player position (for undo) */ lasttpos1, lasttpos2; /* last test positions (for undo) */ static char lppc, ltp1c, ltp2c; /* the char for the above pos. (for undo) */ static short action, lastaction; /** For the temporary save **/ static char tmp_map[MAXROW+1][MAXCOL+1]; static short tmp_pushes, tmp_moves, tmp_savepack; static POS tmp_ppos; The above defines variables with file scope only short play() { short c; short ret; short undolock = 1; /* locked for undo */ showscreen(); tmpsave(); ret = 0; while( ret == 0) { The following gets a character and branches on it. All the ``E_'' defines were non-zero, so we break from the loop on zero. It is not obvious where ``ret'' is set to zero - it is under the ``U'' branch. A define for the zero case might be nice.

Since there is no user manual, this is where you really find out what you are allowed to do.
Double sigh :-((

switch( (c = getch())) { case 'q': /* quit the game */ ret = E_ENDGAME; break; case 's': /* save the games */ if( (ret = savegame()) == 0) ret = E_SAVED; break; case '?': /* show the help file */ showhelp(); showscreen(); break; case CNTL_R: /* refresh the screen */ clear(); showscreen(); break; case 'c': /* temporary save */ tmpsave(); break; case CNTL_U: /* reset to temporary save */ tmpreset(); undolock = 1; showscreen(); break; case 'U': /* undo this level */ moves = pushes = 0; if( (ret = readscreen()) == 0) { showscreen(); undolock = 1; } break; case 'u': /* undo last move */ if( ! undolock) { undomove(); undolock = 1; } break; case 'k': /* up */ case 'K': /* run up */ case CNTL_K: /* run up, stop before object */ case 'j': /* down */ case 'J': /* run down */ case CNTL_J: /* run down, stop before object */ case 'l': /* right */ case 'L': /* run right */ case CNTL_L: /* run right, stop before object */ case 'h': /* left */ case 'H': /* run left */ case CNTL_H: /* run left, stop before object */ Here is the meat of the interface. This checks if you can do the move by ``testmove()''. If so, it saves the current state in ``last...', so that ``undo'' can work. Then it calls ``domove()'' to perform the action. Note how all the ``move'' cases fall into this code. do { if( (action = testmove( c)) != 0) { lastaction = action; lastppos.x = ppos.x; lastppos.y = ppos.y; lppc = map[ppos.x][ppos.y]; lasttpos1.x = tpos1.x; lasttpos1.y = tpos1.y; ltp1c = map[tpos1.x][tpos1.y]; lasttpos2.x = tpos2.x; lasttpos2.y = tpos2.y; ltp2c = map[tpos2.x][tpos2.y]; domove( lastaction); undolock = 0; } } while( (action != 0) && (! islower( c)) && (packets != savepack)); break; default: helpmessage(); break; } if( (ret == 0) && (packets == savepack)) { scorelevel = level; scoremoves = moves; scorepushes = pushes; break; } } return( ret); } testmove( action) register short action; { register short ret; register char tc; register short stop_at_object; if( (stop_at_object = iscntrl( action))) action = action + 'A' - 1; That was a C shortcut. ``iscntrl'' returns 0 or 1, which is assigned to ``stop_at_object'' and the result of the assignment is Boolean tested.

The assignment bumps ``action'' up from a control char to an alphabetic value.

action = (isupper( action)) ? tolower( action) : action; This person likes the ``?'' operator. What it does here is to map an upper case char down to lower case. In ANSI C, the conditional is redundant, and you could simply do action = tolower(action); Some non-ANSI C systems will break on this, though if( (action == 'k') || (action == 'j')) { tpos1.x = (action == 'k') ? ppos.x-1 : ppos.x+1; tpos2.x = (action == 'k') ? ppos.x-2 : ppos.x+2; tpos1.y = tpos2.y = ppos.y; Sets `tpos1'' and ``tpos2'' to one and two positions left or right. We need to be able to check if we have bumped into an obstacle, and if there is clear space beyond or not.

Note that the keys for these actions are hard-coded. This is not so good, and should have been in ``sokoban.h'' as configurable parameters. Allowing complete configuration would break code like the ``stop_at_action'' asignment above.

} else { tpos1.y = (action == 'h') ? ppos.y-1 : ppos.y+1; tpos2.y = (action == 'h') ? ppos.y-2 : ppos.y+2; tpos1.x = tpos2.x = ppos.x; } tc = map[tpos1.x][tpos1.y]; if( (tc == packet.obj_intern) || (tc == save.obj_intern)) { if( ! stop_at_object) { if( map[tpos2.x][tpos2.y] == ground.obj_intern) ret = (tc == save.obj_intern) ? UNSAVE : PUSH; else if( map[tpos2.x][tpos2.y] == store.obj_intern) ret = (tc == save.obj_intern) ? STOREPUSH : SAVE; else ret = 0; } else ret = 0; } The ``map'' array is a 2-D array of the board. The above checks if we are up against a ``packet'' or a ``saved'' object, and if there is ``ground'' beyond that. Note the complex set of values assigned to ``set'' due to the number of cases.

All of the variables ``packet'', ``save'', etc are of type ``OBJECT'', which is a structure. The code ``tc == save.obj_intern'' - and the reason for the structure - are due to the need to be able to figure out what the object is. It has a field

char obj_intern; which was intialised in ``sokoban.h'' to ``@'', ``+'', etc. else if( tc == ground.obj_intern) ret = MOVE; else if( tc == store.obj_intern) ret = STOREMOVE; else ret = 0; return( ret); <xmp> The function was defined with no type. In K&R, this means ``int'' type. So the function value is one of ``MOVE'', ``STOREMOVE'', etc. This value is passed as a parameter to ``domove'' below. <xmp> } domove( moveaction) register short moveaction; { map[ppos.x][ppos.y] = (map[ppos.x][ppos.y] == player.obj_intern) ? ground.obj_intern : store.obj_intern; switch( moveaction) { case MOVE: map[tpos1.x][tpos1.y] = player.obj_intern; break; case STOREMOVE: map[tpos1.x][tpos1.y] = playerstore.obj_intern; break; case PUSH: map[tpos2.x][tpos2.y] = map[tpos1.x][tpos1.y]; map[tpos1.x][tpos1.y] = player.obj_intern; pushes++; break; case UNSAVE: map[tpos2.x][tpos2.y] = packet.obj_intern; map[tpos1.x][tpos1.y] = playerstore.obj_intern; pushes++; savepack--; break; case SAVE: map[tpos2.x][tpos2.y] = save.obj_intern; map[tpos1.x][tpos1.y] = player.obj_intern; savepack++; pushes++; break; case STOREPUSH: map[tpos2.x][tpos2.y] = save.obj_intern; map[tpos1.x][tpos1.y] = playerstore.obj_intern; pushes++; break; } moves++; The next lines are all screen display stuff, upto the ``refresh()'' dispmoves(); disppushes(); dispsave(); mapchar( map[ppos.x][ppos.y], ppos.x, ppos.y); mapchar( map[tpos1.x][tpos1.y], tpos1.x, tpos1.y); mapchar( map[tpos2.x][tpos2.y], tpos2.x, tpos2.y); move( MAXROW+1, 0); refresh(); ppos.x = tpos1.x; ppos.y = tpos1.y; } undomove() { map[lastppos.x][lastppos.y] = lppc; map[lasttpos1.x][lasttpos1.y] = ltp1c; map[lasttpos2.x][lasttpos2.y] = ltp2c; ppos.x = lastppos.x; ppos.y = lastppos.y; switch( lastaction) { case MOVE: moves--; break; case STOREMOVE: moves--; break; case PUSH: moves--; pushes--; break; case UNSAVE: moves--; pushes--; savepack++; break; case SAVE: moves--; pushes--; savepack--; break; case STOREPUSH: moves--; pushes--; break; } dispmoves(); disppushes(); dispsave(); mapchar( map[ppos.x][ppos.y], ppos.x, ppos.y); mapchar( map[lasttpos1.x][lasttpos1.y], lasttpos1.x, lasttpos1.y); mapchar( map[lasttpos2.x][lasttpos2.y], lasttpos2.x, lasttpos2.y); move( MAXROW+1, 0); refresh(); } tmpsave() { register short i, j; for( i = 0; i < rows; i++) for( j = 0; j < cols; j++) tmp_map[i][j] = map[i][j]; tmp_pushes = pushes; tmp_moves = moves; tmp_savepack = savepack; tmp_ppos.x = ppos.x; tmp_ppos.y = ppos.y; } tmpreset() { register short i, j; for( i = 0; i < rows; i++) for( j = 0; j < cols; j++) map[i][j] = tmp_map[i][j]; pushes = tmp_pushes; moves = tmp_moves; savepack = tmp_savepack; ppos.x = tmp_ppos.x; ppos.y = tmp_ppos.y; }