September Morn Update

This commit is contained in:
Ingy döt Net 2019-09-12 10:33:56 -07:00
parent 4e2d22a71d
commit aac6731f2c
6856 changed files with 141342 additions and 21127 deletions

View file

@ -0,0 +1,93 @@
struct BoardState
board::String
csol::String
position::Int
end
function move(s::BoardState, dpos)
buffer = Vector{UInt8}(deepcopy(s.board))
if s.board[s.position] == '@'
buffer[s.position] = ' '
else
buffer[s.position] = '.'
end
newpos = s.position + dpos
if s.board[newpos] == ' '
buffer[newpos] = '@'
else
buffer[newpos] = '+'
end
String(buffer)
end
function push(s::BoardState, dpos)
newpos = s.position + dpos
boxpos = newpos + dpos
if s.board[boxpos] != ' ' && s.board[boxpos] != '.'
return ""
end
buffer = Vector{UInt8}(deepcopy(s.board))
if s.board[s.position] == '@'
buffer[s.position] = ' '
else
buffer[s.position] = '.'
end
if s.board[newpos] == '$'
buffer[newpos] = '@'
else
buffer[newpos] = '+'
end
if s.board[boxpos] == ' '
buffer[boxpos] = '$'
else
buffer[boxpos] = '*'
end
String(buffer)
end
function solve(board)
width = findfirst("\n", board[2:end])[1] + 1
dopt = (u = -width, l = -1, d = width, r = 1)
visited = Dict(board => true)
open::Vector{BoardState} = [BoardState(board, "", findfirst("@", board)[1])]
while length(open) > 0
s1 = open[1]
open = open[2:end]
for dir in keys(dopt)
newpos = s1.position + dopt[dir]
x = s1.board[newpos]
if x == '$' || x == '*'
newboard = push(s1, dopt[dir])
if newboard == "" || haskey(visited, newboard)
continue
end
newsol = s1.csol * uppercase(string(dir))
if findfirst(r"[\.\+]", newboard) == nothing
return newsol
end
elseif x == ' ' || x == '.'
newboard = move(s1, dopt[dir])
if haskey(visited, newboard)
continue
end
newsol = s1.csol * string(dir)
else
continue
end
open = push!(open, BoardState(newboard, newsol, newpos))
visited[newboard] = true
end
end
"No solution" # we should only get here if no solution to the sokoban
end
const testlevel = strip(raw"""
#######
# #
# #
#. # #
#. $$ #
#.$$ #
#.# @#
#######""")
println("For sokoban level:\n$testlevel\n...solution is :\n$(solve(testlevel))")

View file

@ -0,0 +1,171 @@
-- demo\rosetta\Sokoban.exw
integer w, h -- (set from parsing the input grid)
sequence moves -- "", as +/-w and +/-1 (udlr)
string live -- "", Y if box can go there
function reachable(sequence pushes, string level)
integer p = find_any("@+",level)
string ok = repeat('N',length(level))
ok[p] = 'Y'
while true do
p = find('Y',ok)
if p=0 then exit end if
ok[p] = 'y'
for i=1 to length(moves) do
integer pn = p+moves[i]
if ok[pn]='N'
and find(level[pn]," .") then
ok[pn] = 'Y'
end if
end for
end while
for i=length(pushes)-1 to 1 by -2 do
if ok[pushes[i]-pushes[i+1]]!='y' then
pushes[i..i+1] = {}
end if
end for
return pushes
end function
function pushable(string level)
sequence res = {}
for i=1 to length(level) do
if find(level[i],"$*") then
if find(level[i-w]," .@+")
and find(level[i+w]," .@+") then
if live[i-w]='Y' then res &= {i,-w} end if
if live[i+w]='Y' then res &= {i,+w} end if
end if
if find(level[i-1]," .@+")
and find(level[i+1]," .@+") then
if live[i-1]='Y' then res &= {i,-1} end if
if live[i+1]='Y' then res &= {i,+1} end if
end if
end if
end for
return reachable(res,level)
end function
function solve(string level)
atom t2 = time()+2
integer seen = new_dict()
sequence solution = "No solution.", partial = {}
sequence todo = {{level,partial,pushable(level)}}, pushes
while length(todo) do
sequence t1 = todo[1]
todo = todo[2..$]
{level,partial,pushes} = t1
integer p = find_any("@+",level)
while length(pushes) do
integer {s,m} = pushes[1..2]
pushes = pushes[3..$]
level[p] = " ."[find(level[p],"@+")]
level[s] = "@+"[find(level[s],"$*")]
level[s+m] = "$*"[find(level[s+m]," .")]
if getd_index(level,seen)=0 then
sequence np = partial&{s,m}
if not find('$',level) then
solution = np
todo = {}
pushes = {}
exit
end if
setd(level,true,seen)
if time()>t2 then
printf(1,"working... (seen %d)\r",dict_size(seen))
t2 = time()+2
end if
todo = append(todo,{level,np,pushable(level)})
end if
level = t1[1] -- (reset)
end while
end while
destroy_dict(seen)
return solution
end function
procedure plays(string level, sequence solution)
-- This plays push-only solutions (see play() for lurd)
string res = level
integer p = find_any("@+",level)
for i=1 to length(solution) by 2 do
integer {s,m} = solution[i..i+1] m+=s
level[p] = " ."[find(level[p],"@+")]
level[s] = "@+"[find(level[s],"$*")]
level[m] = "$*"[find(level[m]," .")]
res &= level
p = s
end for
-- (replacing +0 with 1/2/3 may help in some cases)
puts(1,join_by(split(res,'\n'),h,floor(80/(w+2))+0))
end procedure
procedure mark_live(integer p, string level)
-- (idea cribbed from the C version)
if live[p]='N' then
live[p] = 'Y'
integer l = length(level)
if p-w*2>=1 and level[p-w]!='#' and level[p-w*2]!='#' then mark_live(p-w,level) end if
if p+w*2<=l and level[p+w]!='#' and level[p+w*2]!='#' then mark_live(p+w,level) end if
if p-2 >=1 and level[p-1]!='#' and level[p-2] !='#' then mark_live(p-1,level) end if
if p+2 <=l and level[p+1]!='#' and level[p+2] !='#' then mark_live(p+1,level) end if
end if
end procedure
function make_square(string level)
--
-- Sets {h, w, moves, live}, and returns an evened-out/rectangular level
--
if level[$]!='\n' then level &= '\n' end if -- (for the display)
sequence lines = split(level,'\n')
h = length(lines)-1 -- set height (ignore trailing \n)
sequence ln = repeat(0,h)
for i=1 to h do
ln[i] = {length(lines[i]),i}
for j=1 to length(lines[i]) do
-- validate each line, why not
if not find(lines[i,j]," #.$@*") then
crash("invalid input")
end if
end for
end for
ln = sort(ln)
w = ln[$][1]+1 -- set width (==longest, inc \n)
moves = {-w,+w,-1,+1} -- and make these (udlr) legal ...
for i=1 to h do
integer {l,n} = ln[i], pad = w-1-l
if pad=0 then exit end if
lines[n] &= repeat(' ',pad) -- ... by evening up the "grid"
end for
level = join(lines,'\n')
live = join(repeat(repeat('N',w-1),h),'\n')
for p=1 to length(level) do
if find(level[p],".+*") then
mark_live(p,level)
end if
end for
return level
end function
constant input = """
#######
# #
# #
#. # #
#. $$ #
#.$$ #
#.# @#
#######
"""
atom t0 = time()
string level = make_square(input)
sequence pushset = solve(level)
integer pop = length(pushset)/2
if string(pushset) then
puts(1,level)
printf(1,"%s\n",{pushset}) -- ("No Solution.")
else
printf(1,"solution of %d pushes (%s)\n",{pop,elapsed(time()-t0)})
plays(level,pushset)
end if

View file

@ -0,0 +1,7 @@
constant input = """
#############
# # #
# $$$$$$$ @#
#....... #
#############
"""

View file

@ -0,0 +1,10 @@
constant input = """
####
##. ##
##### . #
# # # #
# $ # # #
# $ @ #
###### ##
####
"""

View file

@ -0,0 +1,7 @@
constant input = """
#############
#... # #
#.$$$$$$$ @#
#... #
#############
"""

View file

@ -0,0 +1,13 @@
constant input = """
#####
# #
# #
### #$##
# #
### #$## # ######
# # ## ##### .#
# $ $ ..#
##### ### #@## .#
# #########
#######
"""

View file

@ -0,0 +1,382 @@
#--------------------------------------------------#
# Sokoban Game #
#--------------------------------------------------#
# Game Data
aPlayer = [ :Row = 3, :Col = 4 ]
aLevel1 = [
[1,1,1,2,2,2,2,2,1,1,1,1,1,1],
[1,2,2,2,1,1,1,2,1,1,1,1,1,1],
[1,2,4,3,5,1,1,2,1,1,1,1,1,1],
[1,2,2,2,1,5,4,2,1,1,1,1,1,1],
[1,2,4,2,2,5,1,2,1,1,1,1,1,1],
[1,2,1,2,1,4,1,2,2,1,1,1,1,1],
[1,2,5,1,6,5,5,4,2,1,1,1,1,1],
[1,2,1,1,1,4,1,1,2,1,1,1,1,1],
[1,2,2,2,2,2,2,2,2,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1]
]
aLevel2 = [
[1,1,1,2,2,2,2,2,2,2,2,2,1,1],
[1,2,2,2,1,5,1,4,1,1,1,2,1,1],
[1,2,4,3,5,1,1,1,5,1,1,2,1,1],
[1,2,2,2,1,1,4,1,1,1,1,2,1,1],
[1,2,4,2,2,1,5,4,1,5,1,2,1,1],
[1,2,1,2,1,4,1,5,1,1,2,2,1,1],
[1,2,5,1,6,5,1,4,1,1,1,2,1,1],
[1,2,1,1,1,4,1,4,1,5,1,2,1,1],
[1,2,2,2,2,2,2,2,2,2,2,2,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1]
]
aLevel = aLevel1
nActiveLevel = 1
# For Game Restart
aLevel1Copy = aLevel1
aLevel2Copy = aLevel2
aPlayerCopy = aPlayer
C_LEVEL_ROWSCOUNT = 10
C_LEVEL_COLSCOUNT = 14
C_EMPTY = 1
C_WALL = 2
C_PLAYER = 3
C_DOOR = 4
C_BOX = 5
C_BOXONDOOR = 6
C_PLAYERONDOOR = 7
nKeyClock = clock()
# Will be used when moving a Box
aCurrentBox = [ :Row = 0, :Col = 0 ]
nRowDiff = 0
nColDiff = 0
# When the player win
lPlayerWin = False
load "gameengine.ring"
func main
oGame = New Game
{
title = "Sokoban"
Map {
blockwidth = 60
blockheight = 60
aMap = aLevel
aImages = [
"images/empty.jpg",
"images/wall.jpg",
"images/player.jpg",
"images/door.jpg",
"images/box.jpg",
"images/boxondoor.jpg",
"images/player.jpg" # Player on Door
]
keypress = func oGame,oSelf,nkey {
# Avoid getting many keys in short time
if (clock() - nKeyClock) < clockspersecond()/4 return ok
nKeyClock = Clock()
Switch nkey
on Key_Esc
oGame.Shutdown()
on Key_Space
# Restart the Level
if nActiveLevel = 1
aLevel = aLevel1Copy
else
aLevel = aLevel2Copy
ok
aPlayer = aPlayerCopy
UpdateGameMap(oGame)
lPlayerWin = False
on Key_Right
if aPlayer[:col] < C_LEVEL_COLSCOUNT
nRowDiff = 0 nColDiff = 1
MoveObject(oGame,PlayerType(),aPlayer[:row],aPlayer[:col]+1)
ok
on Key_Left
if aPlayer[:col] > 1
nRowDiff = 0 nColDiff = -1
MoveObject(oGame,PlayerType(),aPlayer[:row],aPlayer[:col]-1)
ok
on Key_Up
if aPlayer[:row] > 1
nRowDiff = -1 nColDiff = 0
MoveObject(oGame,PlayerType(),aPlayer[:row]-1,aPlayer[:col])
ok
on Key_Down
if aPlayer[:row] < C_LEVEL_ROWSCOUNT
nRowDiff = 1 nColDiff = 0
MoveObject(oGame,PlayerType(),aPlayer[:row]+1,aPlayer[:col])
ok
off
if lPlayerWin = False
if CheckWin()
lPlayerWin = True
DisplayYouWin(oGame)
ok
ok
}
}
text {
x = 70 y=550
animate = false
size = 20
file = "fonts/pirulen.ttf"
text = "Level:"
color = rgb(0,0,0)
}
NewButton(oGame,180,550,150,30,"Level 1",:Click1)
NewButton(oGame,350,550,150,30,"Level 2",:Click2)
}
func MoveObject oGame,nObjectType,nNewRow,nNewCol
lMove = False
switch nObjectType
on C_PLAYER
switch aLevel[nNewRow][nNewCol]
on C_EMPTY
aLevel[aPlayer[:row]][aPlayer[:col]] = C_EMPTY
aLevel[nNewRow][nNewCol] = C_PLAYER
UpdateGameMap(oGame)
aPlayer[:row] = nNewRow
aPlayer[:col] = nNewCol
lMove = True
on C_DOOR
aLevel[aPlayer[:row]][aPlayer[:col]] = C_EMPTY
aLevel[nNewRow][nNewCol] = C_PLAYERONDOOR
UpdateGameMap(oGame)
aPlayer[:row] = nNewRow
aPlayer[:col] = nNewCol
lMove = True
on C_BOX
aCurrentBox[:row] = nNewRow
aCurrentBox[:col] = nNewCol
if MoveObject(oGame,C_BOX,nNewRow+nRowDiff,nNewCol+nColDiff)
aLevel[aPlayer[:row]][aPlayer[:col]] = C_EMPTY
aLevel[nNewRow][nNewCol] = C_PLAYER
UpdateGameMap(oGame)
aPlayer[:row] = nNewRow
aPlayer[:col] = nNewCol
lMove = True
ok
on C_BOXONDOOR
aCurrentBox[:row] = nNewRow
aCurrentBox[:col] = nNewCol
if MoveObject(oGame,C_BOXONDOOR,nNewRow+nRowDiff,nNewCol+nColDiff)
aLevel[aPlayer[:row]][aPlayer[:col]] = C_EMPTY
aLevel[nNewRow][nNewCol] = C_PLAYERONDOOR
UpdateGameMap(oGame)
aPlayer[:row] = nNewRow
aPlayer[:col] = nNewCol
lMove = True
ok
off
on C_PLAYERONDOOR
switch aLevel[nNewRow][nNewCol]
on C_EMPTY
aLevel[aPlayer[:row]][aPlayer[:col]] = C_DOOR
aLevel[nNewRow][nNewCol] = C_PLAYER
UpdateGameMap(oGame)
aPlayer[:row] = nNewRow
aPlayer[:col] = nNewCol
lMove = True
on C_DOOR
aLevel[aPlayer[:row]][aPlayer[:col]] = C_DOOR
aLevel[nNewRow][nNewCol] = C_PLAYERONDOOR
UpdateGameMap(oGame)
aPlayer[:row] = nNewRow
aPlayer[:col] = nNewCol
lMove = True
on C_BOX
aCurrentBox[:row] = nNewRow
aCurrentBox[:col] = nNewCol
if MoveObject(oGame,C_BOX,nNewRow+nRowDiff,nNewCol+nColDiff)
aLevel[aPlayer[:row]][aPlayer[:col]] = C_DOOR
aLevel[nNewRow][nNewCol] = C_PLAYER
UpdateGameMap(oGame)
aPlayer[:row] = nNewRow
aPlayer[:col] = nNewCol
lMove = True
ok
on C_BOXONDOOR
aCurrentBox[:row] = nNewRow
aCurrentBox[:col] = nNewCol
if MoveObject(oGame,C_BOXONDOOR,nNewRow+nRowDiff,nNewCol+nColDiff)
aLevel[aPlayer[:row]][aPlayer[:col]] = C_DOOR
aLevel[nNewRow][nNewCol] = C_PLAYER
UpdateGameMap(oGame)
aPlayer[:row] = nNewRow
aPlayer[:col] = nNewCol
lMove = True
ok
off
on C_BOX
switch aLevel[nNewRow][nNewCol]
on C_EMPTY
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_EMPTY
aLevel[nNewRow][nNewCol] = C_BOX
UpdateGameMap(oGame)
lMove = True
on C_DOOR
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_EMPTY
aLevel[nNewRow][nNewCol] = C_BOXONDOOR
UpdateGameMap(oGame)
lMove = True
on C_BOX
aOldBox = aCurrentBox
aCurrentBox[:row] = nNewRow
aCurrentBox[:col] = nNewCol
if MoveObject(oGame,C_BOX,nNewRow+nRowDiff,nNewCol+nColDiff)
aCurrentBox = aOldBox
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_EMPTY
aLevel[nNewRow][nNewCol] = C_BOX
UpdateGameMap(oGame)
lMove = True
ok
on C_BOXONDOOR
aOldBox = aCurrentBox
aCurrentBox[:row] = nNewRow
aCurrentBox[:col] = nNewCol
if MoveObject(oGame,C_BOXONDOOR,nNewRow+nRowDiff,nNewCol+nColDiff)
aCurrentBox = aOldBox
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_EMPTY
aLevel[nNewRow][nNewCol] = C_BOXONDOOR
UpdateGameMap(oGame)
lMove = True
ok
off
on C_BOXONDOOR
switch aLevel[nNewRow][nNewCol]
on C_EMPTY
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_DOOR
aLevel[nNewRow][nNewCol] = C_BOX
UpdateGameMap(oGame)
lMove = True
on C_DOOR
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_DOOR
aLevel[nNewRow][nNewCol] = C_BOXONDOOR
UpdateGameMap(oGame)
lMove = True
on C_BOX
aOldBox = aCurrentBox
aCurrentBox[:row] = nNewRow
aCurrentBox[:col] = nNewCol
if MoveObject(oGame,C_BOX,nNewRow+nRowDiff,nNewCol+nColDiff)
aCurrentBox = aOldBox
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_DOOR
aLevel[nNewRow][nNewCol] = C_BOX
UpdateGameMap(oGame)
lMove = True
ok
on C_BOXONDOOR
aOldBox = aCurrentBox
aCurrentBox[:row] = nNewRow
aCurrentBox[:col] = nNewCol
if MoveObject(oGame,C_BOXONDOOR,nNewRow+nRowDiff,nNewCol+nColDiff)
aCurrentBox = aOldBox
aLevel[aCurrentBox[:row]][aCurrentBox[:col]] = C_DOOR
aLevel[nNewRow][nNewCol] = C_BOXONDOOR
UpdateGameMap(oGame)
lMove = True
ok
off
off
return lMove
func UpdateGameMap oGame
# The Map is our first object in Game Objects
oGame.aObjects[1].aMap = aLevel
func PlayerType
# It could be (Player) or (Player on door)
return aLevel[aPlayer[:row]][aPlayer[:col]]
func CheckWin
for aRow in aLevel
if find(aRow,C_DOOR) or find(aRow,C_PLAYERONDOOR)
return False
ok
next
return True
func DisplayYouWin oGame
oGame {
text {
point = 400
size = 30
nStep = 9
file = "fonts/pirulen.ttf"
text = "You Win !!!"
x = 500 y=10
state = func ogame,oself {
if oself.y >= 400
ogame.remove(oSelf.nIndex)
ok
}
}
}
func NewButton oGame,nX,nY,nWidth,nHeight,cText,cFunc
oGame {
Object {
x = nX y=nY width = nWidth height=nHeight
AddAttribute(self,:Text)
AddAttribute(self,:EventCode)
Text = cText
EventCode = cFunc
draw = func oGame,oSelf {
oSelf {
gl_draw_filled_rectangle(x,y,x+width,y+height,gl_map_rgb(0,100,255))
gl_draw_rectangle(x,y,x+width,y+height,gl_map_rgb(0,0,0),2)
oFont = oResources.LoadFont("fonts/pirulen.ttf",20)
gl_draw_text(oFont,gl_map_rgb(0,0,0),x+width/2,y+5,1,Text)
}
}
mouse = func oGame,oSelf,nType,aMouseList {
if nType = GE_MOUSE_UP
MouseX = aMouseList[GE_MOUSE_X]
MouseY = aMouseList[GE_MOUSE_Y]
oSelf {
if MouseX >= x and MouseX <= X+270 and
MouseY >= y and MouseY <= Y+40
call EventCode(oGame,oSelf)
ok
}
ok
}
}
}
return len(oGame.aObjects)
func Click1 oGame,oSelf
aLevel = aLevel1
nActiveLevel = 1
aPlayer = aPlayerCopy
UpdateGameMap(oGame)
lPlayerWin = False
func Click2 oGame,oSelf
aLevel = aLevel2
nActiveLevel = 2
aPlayer = aPlayerCopy
UpdateGameMap(oGame)
lPlayerWin = False