rem sokoban principles - part 1.
rem by jsm.

rem libraries.
import "Keycodes.lib"
import "Speed.lib"

rem constants.
constant:
	rem fields of vLevel array.
	IMG		0
	OFFS_X	1
	OFFS_Y	2

	rem block types.
	NONE		0
	BRICK		1
	BOX		2

rem globals.
visible:
	rem level data.
	vLevel[20][15][3]

	rem player.
	vPlyX
	vPlyY
	vPlyOffsX
	vPlyOffsY

hidden:

rem set display to 320x240x2
set window 32, 32, 320, 240, false, 2	

rem turn off automatic redraw.
set redraw off

rem load images.
load image 1, "assets/brick.bmp"
load image 2, "assets/box.bmp"
load image 10, "assets/ply.bmp"
set image colorkey 10, 255, 0, 255

rem try load level.
if not loadLevel(1) then end

rem game loop.
do
	rem logic.
	proc UpdatePlayer
	proc UpdateLevel

	rem drawing.
	set color 32, 32, 128
	cls

	rem level.
	proc DrawLevel

	rem player.
	draw image 10, vPlyX*16 + vPlyOffsX, vPlyY*16 + vPlyOffsY

	rem instructions.
	set caret 160, 207
	center "Use arrow keys to move."
	center "The crates can be pushed."

	proc SPD_HoldFrame 60
	redraw
until keydown(27, true)

rem load level, return true on success.
function loadLevel(level)
	rem open file.
	open file 0, "assets/" + str$(level) + ".txt"
	rem return false on failure.
	if not file(0)
		return false
	endif
	
	rem load level.
	for y = 0 to 14
		for x = 0 to 19
			rem clear tile.
			vLevel[x][y][IMG] = 0
			vLevel[x][y][OFFS_X] = 0
			vLevel[x][y][OFFS_Y] = 0

			rem read.
			vLevel[x][y][IMG] = read(0)

			rem player?
			if vLevel[x][y][IMG] = 9
				vPlyX = x
				vPlyY = y
				vLevel[x][y][IMG] = 0
			endif
		next
	next

	rem close file.
	free file 0

	rem player.
	vPlyOffsX = 0
	vPlyOffsY = 0

	rem success.
	return true
endfunc

rem update player.
procedure UpdatePlayer()
	rem move right?
	if vPlyOffsX > 0
		vPlyOffsX = vPlyOffsX + 1
		if vPlyOffsX = 16
			vPlyOffsX = 0
			vPlyX = vPlyX + 1
		endif
	rem move left?
	elseif vPlyOffsX < 0
		vPlyOffsX = vPlyOffsX - 1
		if vPlyOffsX = -16
			vPlyOffsX = 0
			vPlyX = vPlyX - 1
		endif
	rem move down?
	elseif vPlyOffsY > 0
		vPlyOffsY = vPlyOffsY + 1
		if vPlyOffsY = 16
			vPlyOffsY = 0
			vPlyY = vPlyY + 1
		endif
	rem move up?
	elseif vPlyOffsY < 0
		vPlyOffsY = vPlyOffsY - 1
		if vPlyOffsY = -16
			vPlyOffsY = 0
			vPlyY = vPlyY - 1
		endif
	else
		rem want to move right?
		if keydown(VK_RIGHT)
			block = blockAt(vPlyX + 1, vPlyY)
			rem free move?
			if block = NONE
				vPlyOffsX = 1
			rem move box?
			elseif block = BOX
				rem ok to move box?
				if blockAt(vPlyX + 2, vPlyY) = NONE
					vPlyOffsX = 1
					vLevel[vPlyX + 1][vPlyY][OFFS_X] = 1
				endif
			endif
		rem want to move left?
		elseif keydown(VK_LEFT)
			block = blockAt(vPlyX - 1, vPlyY)
			rem free move?
			if block = NONE
				vPlyOffsX = -1
			rem move box?
			elseif block = BOX	
				rem ok to move box?
				if blockAt(vPlyX - 2, vPlyY) = NONE
					vPlyOffsX = -1
					vLevel[vPlyX - 1][vPlyY][OFFS_X] = -1
				endif
			endif
		rem want to move down?
		elseif keydown(VK_DOWN)
			block = blockAt(vPlyX, vPlyY + 1)
			rem free move?
			if block = NONE
				vPlyOffsY = 1
			rem move box?
			elseif block = BOX
				rem ok to move box?
				if blockAt(vPlyX, vPlyY + 2) = NONE
					vPlyOffsY = 1
					vLevel[vPlyX][vPlyY + 1][OFFS_Y] = 1
				endif
			endif
		rem want to move up?
		elseif keydown(VK_UP)
			block = blockAt(vPlyX, vPlyY - 1)
			rem free move?
			if block = NONE
				vPlyOffsY = -1
			rem move box?
			elseif block = BOX
				rem ok to move box?
				if blockAt(vPlyX, vPlyY - 2) = NONE
					vPlyOffsY = -1
					vLevel[vPlyX][vPlyY - 1][OFFS_Y] = -1
				endif
			endif
		endif
	endif
endproc

rem return tile ay (x, y)
function blockAt(x, y)
	rem outside of bounds is brick obstacle.
	if x < 0 or x > 19 then return BRICK
	if y < 0 or y > 14 then return BRICK
	return vLevel[x][y][IMG]
endfunc

rem update level.
procedure UpdateLevel()
	for y = 0 to 14
		for x = 0 to 19
			rem move right?
			if vLevel[x][y][OFFS_X] > 0
				vLevel[x][y][OFFS_X] = vLevel[x][y][OFFS_X] + 1
				rem moved a whole tile?
				if vLevel[x][y][OFFS_X] = 16
					vLevel[x][y][OFFS_X] = 0
					vLevel[x + 1][y][IMG] = vLevel[x][y][IMG]
					vLevel[x][y][IMG] = 0
				endif
			rem move left?
			elseif vLevel[x][y][OFFS_X] < 0
				vLevel[x][y][OFFS_X] = vLevel[x][y][OFFS_X] - 1
				rem moved a whole tile?
				if vLevel[x][y][OFFS_X] = -16
					vLevel[x][y][OFFS_X] = 0
					vLevel[x - 1][y][IMG] = vLevel[x][y][IMG]
					vLevel[x][y][IMG] = 0
				endif
			rem move down?
			elseif vLevel[x][y][OFFS_Y] > 0
				vLevel[x][y][OFFS_Y] = vLevel[x][y][OFFS_Y] + 1
				rem moved a whole tile?
				if vLevel[x][y][OFFS_Y] = 16
					vLevel[x][y][OFFS_Y] = 0
					vLevel[x][y + 1][IMG] = vLevel[x][y][IMG]
					vLevel[x][y][IMG] = 0
				endif
			rem move up?
			elseif vLevel[x][y][OFFS_Y] < 0
				vLevel[x][y][OFFS_Y] = vLevel[x][y][OFFS_Y] - 1
				rem moved a whole tile?
				if vLevel[x][y][OFFS_Y] = -16
					vLevel[x][y][OFFS_Y] = 0
					vLevel[x][y - 1][IMG] = vLevel[x][y][IMG]
					vLevel[x][y][IMG] = 0
				endif
			endif
		next
	next
endproc

rem draw level.
procedure DrawLevel()
	set color 255, 255, 255

	for y = 0 to 14
		for x = 0 to 19
			rem something to draw at this tile?
			if vLevel[x][y][IMG] > 0
				rem each tile is 16x16 pixels large, so multiply x and y with 32.
				rem also add the offset set for the tile in each direction. this
				rem is used for moving tiles.
				draw image vLevel[x][y][IMG], x*16 + vLevel[x][y][OFFS_X], y*16 + vLevel[x][y][OFFS_Y]
			endif
		next
	next
endproc
