rem sokoban principles - part 2.
rem
rem put all crates together to finish level.
rem 
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
	CHECKED	3

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

	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
	levelCompleted = 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 and push crates."
	center "Assemble all crates to finish level."

	proc SPD_HoldFrame 60
	redraw
until keydown(27, true) or levelCompleted

rem level completed?
if levelCompleted
	set color 32, 32, 128
	cls
	set color 255, 255, 255
	set caret 160, 100
	center "Level completed"
	center "Press any key to quit ..."
	redraw
	wait keydown
endif

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
	
	vBoxCount = 0

	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
			vLevel[x][y][CHECKED] = false

			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
			rem box?
			elseif vLevel[x][y][IMG] = BOX
				rem inc box count.
				vBoxCount = vBoxCount + 1
			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. return true if level has been completed.
function updateLevel()
	rem level completed flag.
	completed = false

	rem update all tiles.
	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
					rem check for level complete.
					if levelComplete() then completed = true
				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
					rem check for level complete.
					if levelComplete() then completed = true
				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
					rem check for level complete.
					if levelComplete() then completed = true
				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
					rem check for level complete.
					if levelComplete() then completed = true
				endif
			endif
		next
	next
	
	return completed
endfunc

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

rem return true if level has been completed (all boxes are side by side)
function levelComplete()
	rem find any box.
	for y = 0 to 14
		for x = 0 to 19
			if vLevel[x][y][IMG] = BOX
				boxX = x
				boxY = y
			endif
		next
	next

	rem call recursive function to check if all boxes are side by side.
	for y = 0 to 14
		for x = 0 to 19
			vLevel[x][y][CHECKED] = false
		next
	next
	if boxConnections(boxX, boxY) = vBoxCount then return true
	return false
endfunc

rem return number of box connections from (x, y)
function boxConnections(x, y)
	rem outside?
	if x < 0 or x > 19 then return 0
	if y < 0 or y > 14 then return 0

	rem already checked?
	if vLevel[x][y][CHECKED] then return 0

	rem flag as checked.
	vLevel[x][y][CHECKED] = true
	
	rem box?
	if vLevel[x][y][IMG] = BOX
		rem inc count.
		count = 1

		rem check and add nearby.
		count = count + boxConnections(x + 1, y)
		count = count + boxConnections(x - 1, y)
		count = count + boxConnections(x, y + 1)
		count = count + boxConnections(x, y - 1)

		rem return connection count.
		return count
	else
		return 0
	endif
endproc
