/*
	Sokoban 1.1
	HP 95LX

	Copyright:
		Hans de Vreught
		hdev@dutiba.twi.tudelft.nl
		The Netherlands - 1992

	You are allowed to use and distribute this software for free only.
	Although the author has tried his best to make it bug free,
	he will not guarantee that this is indeed the case.
	You run the software entirely on your own risk only.

	You may customize this software, but in that case you are not
	allowed to distribute it without permission of the author.

	This software is written in Turbo C:
	* Tiny memory model
	  Use exe2bin to convert the EXE file into a COM file.
	* 8088/8086 instruction set
	* optimized for size

	Environment macro's (default is mentioned first):
	*	NO_DEBUG or DEBUG
	*	HP95LX or MesSDOS
	*	FULL_SOKOBAN or MINI_SOKOBAN
*/

#ifndef DEBUG
#define NO_DEBUG
#endif
#ifndef MesSDOS
#define HP95LX
#endif
#ifndef MINI_SOKOBAN
#define FULL_SOKOBAN
#endif

#include <sys\stat.h>
#include <dos.h>
#include <ctype.h>
#ifdef FULL_SOKOBAN
#include <fcntl.h>
#endif
#include <conio.h>

#include "sokoban.h"

#include "sok-code.c"

char	wall = CHAR_WALL;
char	dump = CHAR_DUMP;
char	pusher = CHAR_PUSHER;
char	object = CHAR_OBJECT;
char	p_on_d = CHAR_P_ON_D;
char	o_on_d = CHAR_O_ON_D;
#ifdef FULL_SOKOBAN
char	levels[MAX_LEVEL_BITS];
#endif

char	site[ROWS][COLUMNS];
int	level;

int	pos_x;
int	pos_y;

int	last_move;

clear_screen()
{
	union	REGS	regs;
#ifdef DEBUG
	int	r, c;
#endif

	regs.h.ah = 0x6;
	regs.h.bh = 0x7;
	regs.x.cx = 0x0;
	regs.h.dh = 24;
	regs.h.dl = 79;
	int86(VIDEO, &regs, &regs);
#ifdef DEBUG
	for (r = 16, c = 0; c <= 40; c ++) {
		put_cursor(r, c);
		write(1,"\333",1);
	}
	for (c=40, r = 0; r < 16; r ++) {
		put_cursor(r, c);
		write(1,"\333",1);
	}
#endif
	regs.h.ah = 0x2;
	regs.h.bh = 0x0;
	regs.x.dx = 0x0;
	int86(VIDEO, &regs, &regs);
}

put_cursor(row, colom)
int	row, colom;
{
	union	REGS	regs;

	regs.h.ah = 0x2;
	regs.h.bh = 0x0;
	regs.h.dh = (char) row;
	regs.h.dl = (char) colom;
	int86(VIDEO, &regs, &regs);
}

no_cursor()
{
	union	REGS	regs;

	regs.h.ah = 0x1;
	regs.h.ch = 0x20;
	int86(VIDEO, &regs, &regs);
}

dos_cursor()
{
	union	REGS	regs;

	regs.h.ah = 0x1;
	regs.h.ch = START_CURSOR;
	regs.h.cl = END_CURSOR;
	int86(VIDEO, &regs, &regs);
}

raw_put(c)
int	c;
{
	write(1, &c, 1);
}

raw_print(s)
char	*s;
{
	while (*s != '\0')
		write(1, s ++, 1);
}

input_number(row, column, string, min, max, def)
int	row, column;
char	*string;
int	min, max, def;
{
	int	c, d, t, max_state, state;

	dos_cursor();
	put_cursor(row, column);
	raw_print(string);
	raw_put(' ');
	column += strlen(string);
	t = max;
	for (max_state = 0; t > 0; max_state ++) {
		t /= 10;
		raw_put('_');
	}
	state = 1;
	put_cursor(row, column + state);
	d = 0;
	while (state > 0)
		if (isdigit(c = getch()) && state <= max_state) {
			d = 10 * d + c - '0';
			put_cursor(row, column + state ++);
			raw_put(c);
		}
		else if (c == '\b') {
			d /= 10;
			put_cursor(row, column + -- state);
			raw_print("_\b");
		}
		else if (c == '\r')
			if (state > 1)
				state = 0;
			else {
				state = -1;
				d = def;
			}
		else {
			if (c == '\0')
				getch();
			state = -1;
			d = def;
		}
	no_cursor();
	return min <= d && d <= max ? d : def;
}

#ifdef FULL_SOKOBAN
redraw_setup()
{
	int	l;

	clear_screen();
	raw_print("Sokoban: Setup\n\n1: Toggle level\n2: ");
	raw_put(wall);
	raw_print(" = wall\n3: ");
	raw_put(dump);
	raw_print(" = dump\n4: ");
	raw_put(pusher);
	raw_print(" = you\n5: ");
	raw_put(object);
	raw_print(" = object\n6: ");
	raw_put(p_on_d);
	raw_print(" = you on dump\n7: ");
	raw_put(o_on_d);
	raw_print(" = object on dump\n8: System default");
	put_cursor(0, 25);
	raw_print(" +\2630123456789");
	put_cursor(1, 25);
	raw_print("\304\304\305\304\304\304\304\304\304\304\304\304\304");
	put_cursor(2, 25);
	raw_print(" 0\263");
	put_cursor(3, 25);
	raw_print("10\263");
	put_cursor(4, 25);
	raw_print("20\263");
	put_cursor(5, 25);
	raw_print("30\263");
	put_cursor(6, 25);
	raw_print("40\263");
	put_cursor(7, 25);
	raw_print("50\263");
	put_cursor(8, 25);
	raw_print("60\263");
	put_cursor(9, 25);
	raw_print("70\263");
	put_cursor(10, 25);
	raw_print("80\263");
	for (l = 1; l <= MAX_LEVEL; l ++) {
		put_cursor(2 + l / 10, 28 + l % 10);
		if (! (levels[l / 8] & (1 << (l % 8))))
			raw_put('.');
		else
			raw_put('D');
	}
}
#endif

#ifdef FULL_SOKOBAN
setup()
{
	int	m, l;

	redraw_setup();
	while ((m = input_number(11, 0, MENU, 1, 8, 0)) > 0) {
		switch (m) {
		case 1:
			l = input_number(13, 0, LEVEL, 1, MAX_LEVEL, 0);
			if (l == 0)
				break;
			if (! (levels[l / 8] & (1 << (l % 8))))
				levels[l / 8] |= 1 << (l %8);
			else
				levels[l / 8] &= ~(1 << (l %8));
			break;
		case 2:
			wall = input_number(13, 0, ASCII, 0, 255, wall);
			break;
		case 3:
			dump = input_number(13, 0, ASCII, 0, 255, dump);
			break;
		case 4:
			pusher = input_number(13, 0, ASCII, 0, 255, pusher);
			break;
		case 5:
			object = input_number(13, 0, ASCII, 0, 255, object);
			break;
		case 6:
			p_on_d = input_number(13, 0, ASCII, 0, 255, p_on_d);
			break;
		case 7:
			o_on_d = input_number(13, 0, ASCII, 0, 255, o_on_d);
			break;
		default:
			wall = CHAR_WALL;
			dump = CHAR_DUMP;
			pusher = CHAR_PUSHER;
			object = CHAR_OBJECT;
			p_on_d = CHAR_P_ON_D;
			o_on_d = CHAR_O_ON_D;
			break;
		}
		redraw_setup();
	}
}
#endif

start()
{
#ifdef FULL_SOKOBAN
	int	handle;

	clear_screen();
	no_cursor();
	raw_print(MACHINE);
	raw_print("'s Sokoban Version ");
	raw_print(VERSION);
	raw_print("\n\n");
	raw_print(COPYRIGHT);
	raw_print(" Hans de Vreught, 1992\n\n");
	handle = open(SETUP_FILE, O_RDONLY | O_BINARY);
	if (handle < 0) {
		level = 1;
		raw_print(
			"Welcome beginner! It seems we haven't\n"
			"met before. You ("
		);
		raw_put(pusher);
		raw_print(") will have to put\nthe objects (");
		raw_put(object);
		raw_print(") on the dumps (");
		raw_put(dump);
		raw_print(") by\navoiding the walls (");
		raw_put(wall);
		raw_print(
			"). But you only\n"
			"have the ability to push one object at\n"
			"the time. When you are on the dump you\n"
			"look like "
		);
		raw_put(p_on_d);
		raw_print(
			", an object on the dump\n"
			"looks like "
		);
		raw_put(o_on_d);
		raw_print(".\n\n");
	}
	else {
		read(handle, &wall, 1);
		read(handle, &dump, 1);
		read(handle, &pusher, 1);
		read(handle, &object, 1);
		read(handle, &p_on_d, 1);
		read(handle, &o_on_d, 1);
		read(handle, levels, MAX_LEVEL_BITS);
		close(handle);
		for (level = 1; level <= MAX_LEVEL; level ++)
			if (! (levels[level / 8] & (1 << (level % 8))))
				break;
		if (level > MAX_LEVEL) {
			level = 1;
			raw_print("Welcome Master.\n\n");
		}
		else
			raw_print("Welcome pupil.\n\n");
	}
	raw_print("Hit a key to play.");
	put_cursor(HIDE_ROW, HIDE_COLUMN);
#else
	clear_screen();
	no_cursor();
	raw_print(MACHINE);
	raw_print("'s Sokoban Mini-Version ");
	raw_print(VERSION);
	raw_print("\n\n");
	raw_print(COPYRIGHT);
	raw_print(
		" Hans de Vreught, 1992\n"
		"\n"
		"Welcome.\n"
		"\n"
		"Hit a key to play."
	);
	put_cursor(HIDE_ROW, HIDE_COLUMN);
	level = 1;
#endif
}

setup_level()
{
	int	c, i, j, low_nibble;
	char	*s;

	for (i = 0 ;i < ROWS; i ++)
		for (j = 0; j < COLUMNS; j ++)
			site[i][j] = EMPTY;
	i = j = 0;
	last_move = 0;
	s = code[level - 1];
	low_nibble = TRUE;
	while (*s) {
		if (low_nibble) {
			c = *s & 0x0f;
			low_nibble = FALSE;
		}
		else {
			c = (*s >> 4) & 0x0f;
			low_nibble = TRUE;
			s ++;
		}
		if (c == CODE_NEWLINE) {
			i ++;
			j = 0;
		}
		else
			switch (c) {
			case CODE_WALL:
				site[i][j ++] = WALL;
				break;
			case CODE_DUMP:
				site[i][j ++ ] = DUMP;
				break;
			case CODE_PUSHER:
				site[i][j ++] = PUSHER;
				break;
			case CODE_OBJECT:
				site[i][j ++] = OBJECT;
				break;
			case CODE_P_ON_D:
				site[i][j ++] = PUSHER | DUMP;
				break;
			case CODE_O_ON_D:
				site[i][j ++] = OBJECT | DUMP;
				break;
			default:
				site[i][j ++] = EMPTY;
				break;
			}
	}
	for (i = 0 ;i < ROWS; i ++)
		for (j = 0; j < COLUMNS; j ++)
			if (site[i][j] & PUSHER) {
				pos_x = i;
				pos_y = j;
			}
}

redraw_position(positioning, row, column)
int	positioning, row, column;
{
	if (positioning)
		put_cursor(row, 16 + column);
	switch (site[row][column]) {
	case EMPTY:
		raw_put(' ');
		break;
	case DUMP:
		raw_put(dump);
		break;
	case PUSHER:
		raw_put(pusher);
		break;
	case PUSHER | DUMP:
		raw_put(p_on_d);
		break;
	case OBJECT:
		raw_put(object);
		break;
	case OBJECT | DUMP:
		raw_put(o_on_d);
		break;
	case WALL:
		raw_put(wall);
		break;
	}
}

redraw_level()
{
	int	i, j;

	clear_screen();
	raw_print("Sokoban: Game\n\nLevel ");
	if (level < 10)
		raw_put('0' + level);
	else {
		raw_put('0' + level / 10);
		raw_put('0' + level % 10);
	}
#ifdef FULL_SOKOBAN
	if (levels[level / 8] & (1 << (level % 8)))
		raw_print(" Done");
	raw_print("\n\nUndo\nRestart\nSetup\nQuit\nPrevious\nNext\nGoto");
#else
	raw_print("\n\nUndo\nRestart\nQuit\nPrevious\nNext\nGoto");
#endif
	for (i = 0;i < ROWS; i ++) {
		put_cursor(i,16);
		for (j = 0; j < COLUMNS; j ++)
			redraw_position(FALSE, i, j);
	}
}

congratulations()
{
#ifdef FULL_SOKOBAN
	int	old;
#endif

	put_cursor(12, 0);
	raw_print("Hit a key\nto continue.");
	put_cursor(HIDE_ROW, HIDE_COLUMN);
	if (getch() == '\0')
		getch();
#ifdef FULL_SOKOBAN
	old = level;
	if (! (levels[level / 8] & (1 << (level % 8)))) {
		levels[level / 8] |= 1 << (level % 8);
		for (level ++; level <= MAX_LEVEL; level ++)
			if (! (levels[level / 8] & (1 << (level % 8))))
				break;
		if (level > MAX_LEVEL)
			for (level = 1; level <= MAX_LEVEL; level ++)
				if (! (levels[level / 8] & (1 << (level % 8))))
					break;
		if (level > MAX_LEVEL) {
			clear_screen();
			raw_print(
				"Sokoban: Graduation\n"
				"\n"
				"You, pupil, have past all levels.\n"
				"I've decided to graduate you.\n"
				"You may now call yourself\n"
				"Master of the Sokoban game.\n"
				"\n"
				"Hit a key to continue."
			);
			put_cursor(HIDE_ROW, HIDE_COLUMN);
			if (getch() == '\0')
				getch();
			level = (old % MAX_LEVEL) + 1;
		}
	}
	else {
		for (level ++; level <= MAX_LEVEL; level ++)
			if (! (levels[level / 8] & (1 << (level % 8))))
				break;
		if (level > MAX_LEVEL)
			for (level = 1; level <= MAX_LEVEL; level ++)
				if (! (levels[level / 8] & (1 << (level % 8))))
					break;
		if (level > MAX_LEVEL)
			level = (old % MAX_LEVEL) + 1;
	}
#else
	level = (level % MAX_LEVEL) + 1;
#endif
	setup_level();
	redraw_level();
	return(level);
}

move(direction, dx, dy)
int	direction, dx, dy;
{
	int	i, j, end;

	switch (site[pos_x + dx][pos_y + dy]) {
	case EMPTY:
	case DUMP:
		last_move = direction;
		site[pos_x + dx][pos_y + dy] |= PUSHER;
		redraw_position(TRUE, pos_x + dx, pos_y + dy);
		site[pos_x][pos_y] &= ~ PUSHER;
		redraw_position(TRUE, pos_x, pos_y);
		pos_x += dx;
		pos_y += dy;
		break;
	case OBJECT:
	case OBJECT | DUMP:
		switch (site[pos_x + 2 * dx][pos_y + 2 * dy]) {
		case EMPTY:
		case DUMP:
			last_move = direction | WITH;
			site[pos_x + 2 * dx][pos_y + 2 * dy] |= OBJECT;
			redraw_position(TRUE, pos_x + 2 * dx, pos_y + 2 * dy);
			site[pos_x + dx][pos_y + dy] &= ~ OBJECT;
			site[pos_x + dx][pos_y + dy] |= PUSHER;
			redraw_position(TRUE, pos_x + dx, pos_y + dy);
			site[pos_x][pos_y] &= ~ PUSHER;
			redraw_position(TRUE, pos_x, pos_y);
			pos_x += dx;
			pos_y += dy;
			break;
		}
		break;
	}
	end = TRUE;
	for (i = 0;i < ROWS; i ++)
		for (j = 0; j < COLUMNS; j ++)
			if (site[i][j] & DUMP && ~ site[i][j] & OBJECT)
				end = FALSE;
	if (end == TRUE)
		level = congratulations();
}

undo_level()
{
	int	dx, dy;

	switch (last_move) {
	case UP:
	case UP | WITH:
		dx = -1;
		dy = 0;
		break;
	case RIGHT:
	case RIGHT | WITH:
		dx = 0;
		dy = 1;
		break;
	case DOWN:
	case DOWN | WITH:
		dx = 1;
		dy = 0;
		break;
	case LEFT:
	case LEFT | WITH:
		dx = 0;
		dy = -1;
		break;
	default:
		last_move = 0;
		return;
	}
	if (last_move & WITH) {
		site[pos_x + dx][pos_y + dy] &= ~ OBJECT;
		redraw_position(TRUE, pos_x + dx, pos_y + dy);
		site[pos_x][pos_y] |= OBJECT;
	}
	site[pos_x][pos_y] &= ~ PUSHER;
	redraw_position(TRUE, pos_x, pos_y);
	site[pos_x - dx][pos_y - dy] |= PUSHER;
	redraw_position(TRUE, pos_x - dx, pos_y - dy);
	pos_x -= dx;
	pos_y -= dy;
	last_move = 0;
}

play()
{
	setup_level();
	redraw_level();
	while (TRUE) {
		put_cursor(HIDE_ROW, HIDE_COLUMN);
		switch (getch()) {
		case '\0':
			switch (getch()) {
			case 'H': /* UP */
				move(UP, -1, 0);
				break;
			case 'M': /* RIGHT */
				move(RIGHT, 0, 1);
				break;
			case 'P': /* DOWN */
				move(DOWN, 1, 0);
				break;
			case 'K': /* LEFT */
				move(LEFT, 0, -1);
				break;
			default:
				redraw_level();
				break;
			}
			break;
		case 'u':
		case 'U':
			undo_level();
			break;
		case 'r':
		case 'R':
			setup_level();
			redraw_level();
			break;
#ifdef FULL_SOKOBAN
		case 's':
		case 'S':
			setup();
			redraw_level();
			break;
#endif
		case 'q':
		case 'Q':
			return;
		case 'p':
		case 'P':
			level --;
			if (level < 1)
				return;
			else {
				setup_level();
				redraw_level();
			}
			break;
		case 'n':
		case 'N':
			level ++;
			if (level>MAX_LEVEL)
				return;
			else {
				setup_level();
				redraw_level();
			}
			break;
		case 'g':
		case 'G':
                        clear_screen();
                        raw_print("Sokoban: Goto");
			level = input_number(2, 0, LEVEL, 1, MAX_LEVEL, 0);
			if (level < 1 || level > MAX_LEVEL)
				return;
			else {
				setup_level();
				redraw_level();
			}
			break;
		default:
			redraw_level();
			break;
		}
	}
}

end()
{
#ifdef FULL_SOKOBAN
	int	handle;

	clear_screen();
	handle = open(
		SETUP_FILE,
		O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
		S_IREAD | S_IWRITE
	);
	if (handle < 0) {
		raw_print(
			"Sorry, but I don't seem to be able\n"
			"to open the setup file.\n"
			"\n"
			"Better luck next time!\n"
			"\n"
			"Hit a key to continue."
		);
		put_cursor(HIDE_ROW, HIDE_COLUMN);
		if (getch() == '\0')
			getch();
	}
	else {
		write(handle, &wall, 1);
		write(handle, &dump, 1);
		write(handle, &pusher, 1);
		write(handle, &object, 1);
		write(handle, &p_on_d, 1);
		write(handle, &o_on_d, 1);
		write(handle, levels, MAX_LEVEL_BITS);
		close(handle);
	}
	dos_cursor();
#else
	clear_screen();
	dos_cursor();
#endif
}

main()
{
	start();
	if (getch() == '\0')
		getch();
	play();
	end();
}
