2018-04-03 23:21:12 +01:00
-- SOKOBAN GAME, by rnd, robots port
if not sokoban then
sokoban = { } ;
local players = find_player ( 5 ) ;
if not players then error ( " sokoban: no player near " ) end
name = players [ 1 ] ;
self.show_form ( name ,
" size[2,1.25] " ..
" label[0,0;SELECT LEVEL 1-90] " ..
" field[0.25,1;1,1;LVL;LEVEL;1] " ..
" button_exit[1.25,0.75;1,1;OK;OK] "
)
state = 1 -- will wait for form receive otherwise game play
player_ = puzzle.get_player ( name ) ; -- get player entity - player must be present in area
player_ : set_eye_offset ( { x = 0 , y = 0 , z = 0 } , { x = 0 , y = 0 , z = 0 } ) ; player_ : set_physics_override ( { jump = 1 } ) -- reset player
self.spam ( 1 )
sokoban.push_time = 0
sokoban.blocks = 0 ; sokoban.level = 0 ; sokoban.moves = 0 ;
imax = 0 ; jmax = 0
sokoban.load = 0 ; sokoban.playername = " " ; sokoban.pos = { } ;
2018-06-30 12:53:45 +02:00
SOKOBAN_WALL = " moreblocks:cactus_brick "
SOKOBAN_FLOOR = " default:desert_sandstone "
SOKOBAN_GOAL = " moreblocks:wood_tile_center "
SOKOBAN_BOX = " basic_robot:buttonFFFFFF "
2018-04-03 23:21:12 +01:00
load_level = function ( lvl )
local pos = self.spawnpos ( ) ; pos.x = pos.x + 1 ; pos.y = pos.y + 1 ;
sokoban.pos = pos ;
sokoban.playername = name
if lvl == nil then return end
if lvl < 0 or lvl > 89 then return end
local file = _G.io . open ( minetest.get_modpath ( " basic_robot " ) .. " \\ scripts \\ sokoban.txt " , " r " )
if not file then return end
local str = " " ; local s ; local p = { x = pos.x , y = pos.y , z = pos.z } ; local i , j ; i = 0 ;
local lvl_found = false
while str ~= nil do
str = file : read ( " *line " ) ;
if str ~= nil and str == " ; " .. lvl then lvl_found = true break end
end
if not lvl_found then file : close ( ) ; return end
sokoban.blocks = 0 ; sokoban.level = lvl + 1 ; sokoban.moves = 0 ;
imax = 0 ; jmax = 0 ;
while str ~= nil do
str = file : read ( " *line " ) ;
if str ~= nil then
if string.sub ( str , 1 , 1 ) == " ; " then
imax = i ;
file : close ( ) ;
player_ : set_physics_override ( { jump = 0 } )
player_ : set_eye_offset ( { x = 0 , y = 20 , z = 0 } , { x = 0 , y = 0 , z = 0 } )
say ( " games: sokoban level " .. sokoban.level .. " loaded by " .. name .. " . It has " .. sokoban.blocks .. " boxes to push. " ) ; return
end
i = i + 1 ;
if string.len ( str ) > jmax then jmax = string.len ( str ) end -- determine max dimensions
for j = 1 , string.len ( str ) do
p.x = pos.x + i ; p.y = pos.y ; p.z = pos.z + j ; s = string.sub ( str , j , j ) ;
p.y = p.y - 1 ;
if puzzle.get_node ( p ) . name ~= SOKOBAN_FLOOR then puzzle.set_node ( p , { name = SOKOBAN_FLOOR } ) ; end -- clear floor
p.y = p.y + 1 ;
if s == " " and puzzle.get_node ( p ) . name ~= " air " then puzzle.set_node ( p , { name = " air " } ) end
if s == " # " and puzzle.get_node ( p ) . name ~= SOKOBAN_WALL then puzzle.set_node ( p , { name = SOKOBAN_WALL } ) end
if s == " $ " then puzzle.set_node ( p , { name = SOKOBAN_BOX } ) ; sokoban.blocks = sokoban.blocks + 1 end
if s == " . " then p.y = p.y - 1 ; puzzle.set_node ( p , { name = SOKOBAN_GOAL } ) ; p.y = p.y + 1 ; puzzle.set_node ( p , { name = " air " } ) end
--starting position
if s == " @ " then
player_ : setpos ( { x = p.x , y = p.y + 1 , z = p.z } ) ; -- move player to start position
--p.y=p.y-1;puzzle.set_node(p,{name="default:glass"});
puzzle.set_node ( p , { name = " air " } )
p.y = p.y + 1 ; puzzle.set_node ( p , { name = " air " } )
--p.y=p.y+2;puzzle.set_node(p,{name="default:ladder"})
end
if s ~= " @ " then p.y = pos.y + 2 ; puzzle.set_node ( p , { name = " air " } ) ; -- ceiling default:obsidian_glass
else --p.y=pos.y+2;puzzle.set_node(p,{name="default:ladder"})
end -- roof above to block jumps
end
end
end
file : close ( ) ;
end
2018-06-30 12:53:45 +02:00
clear_game = function ( )
local pos = self.spawnpos ( ) ; pos.x = pos.x + 1 ; pos.y = pos.y + 1 ;
for i = 1 , 20 do
for j = 1 , 20 do
local node = minetest.get_node ( { x = pos.x + i , y = pos.y - 1 , z = pos.z + j } ) . name
if node ~= " default:desert_sandstone " then minetest.set_node ( { x = pos.x + i , y = pos.y - 1 , z = pos.z + j } , { name = " default:desert_sandstone " } ) end
node = minetest.get_node ( { x = pos.x + i , y = pos.y , z = pos.z + j } ) . name
if node ~= " air " then minetest.set_node ( { x = pos.x + i , y = pos.y , z = pos.z + j } , { name = " air " } ) end
end
end
end
2018-04-03 23:21:12 +01:00
end
if state == 1 then
sender , fields = self.read_form ( ) ; -- get fields from form submittal
if sender then
--say(serialize(fields))
if fields.LVL then
2018-06-30 12:53:45 +02:00
clear_game ( )
2018-04-03 23:21:12 +01:00
load_level ( ( tonumber ( fields.LVL ) or 1 ) - 1 )
state = 0
self.label ( " stand close to blue box and punch it one time to push it. you can only push 1 box \n and cant pull. goal is to get all boxes pushed on diamond blocks " )
end
end
else
local ppos = player_ : getpos ( )
2018-06-30 12:53:45 +02:00
if math.abs ( ppos.y - sokoban.pos . y ) ~= 0.5 then minetest.chat_send_player ( name , colorize ( " red " , " SOKOBAN: " .. name .. " QUITS ! " ) ) ;
2018-04-03 23:21:12 +01:00
player_ : set_eye_offset ( { x = 0 , y = 0 , z = 0 } , { x = 0 , y = 0 , z = 0 } ) ; player_ : set_physics_override ( { jump = 1 } ) ; self.remove ( ) end
event = keyboard.get ( ) ;
if event then
local pname = event.puncher
if pname ~= name then goto quit end
local pos = { x = event.x , y = event.y , z = event.z } ;
local p = player.getpos ( pname ) ; local q = { x = pos.x , y = pos.y , z = pos.z }
p.x = p.x - q.x ; p.y = p.y - q.y ; p.z = p.z - q.z
if math.abs ( p.y + 0.5 ) > 0 then goto quit end
if math.abs ( p.x ) > math.abs ( p.z ) then -- determine push direction
if p.z <- 0.5 or p.z > 0.5 or math.abs ( p.x ) > 1.5 then goto quit end
if p.x + q.x > q.x then q.x = q.x - 1
else q.x = q.x + 1
end
else
if p.x <- 0.5 or p.x > 0.5 or math.abs ( p.z ) > 1.5 then goto quit end
if p.z + q.z > q.z then q.z = q.z - 1
else q.z = q.z + 1
end
end
if minetest.get_node ( q ) . name == " air " then -- push crate
sokoban.moves = sokoban.moves + 1
local old_infotext = minetest.get_meta ( pos ) : get_string ( " infotext " ) ;
minetest.set_node ( pos , { name = " air " } )
minetest.set_node ( q , { name = SOKOBAN_BOX } )
minetest.sound_play ( " default_dig_dig_immediate " , { pos = q , gain = 1.0 , max_hear_distance = 24 , } ) -- sound of pushing
local meta = minetest.get_meta ( q ) ;
q.y = q.y - 1 ;
if minetest.get_node ( q ) . name == SOKOBAN_GOAL then
if old_infotext ~= " GOAL REACHED " then
sokoban.blocks = sokoban.blocks - 1 ;
end
meta : set_string ( " infotext " , " GOAL REACHED " )
else
if old_infotext == " GOAL REACHED " then
sokoban.blocks = sokoban.blocks + 1
end
--meta:set_string("infotext", "push crate on top of goal block")
end
end
if sokoban.blocks ~= 0 then -- how many blocks left
--say("move " .. sokoban.moves .. " : " ..sokoban.blocks .. " crates left ");
else
say ( " games: " .. name .. " just solved sokoban level " .. sokoban.level .. " in " .. sokoban.moves .. " moves. " ) ;
player_ : set_physics_override ( { jump = 1 } )
player_ : set_eye_offset ( { x = 0 , y = 0 , z = 0 } , { x = 0 , y = 0 , z = 0 } )
local i , j ;
for i = 1 , imax do
for j = 1 , jmax do
minetest.set_node ( { x = sokoban.pos . x + i , y = sokoban.pos . y , z = sokoban.pos . z + j } , { name = " air " } ) ; -- clear level
end
end
sokoban.playername = " " ; sokoban.level = 1
end
:: quit ::
end
end