2021-01-07 02:30:01 -08:00
-- SOKOBAN GAME, by rnd, robots port
if not sokoban then
load_player_progress = true -- true will disable level loading and load saved player progress
sokoban = { } ;
local players = find_player ( 8 ) ;
if not players then error ( " sokoban: no player near " ) end
name = players [ 1 ] ;
_ , text = book.read ( 1 )
gamedata = minetest.deserialize ( text ) ;
if gamedata and gamedata [ name ] then
say ( " #sokoban: welcome back " .. name .. " , loading level " .. gamedata [ name ] . lvl + 1 )
else
say ( " sokoban: welcome new player " .. name )
end
if not load_player_progress then
self.show_form ( name ,
" size[3,1.25] " ..
" label[0,0;SELECT LEVEL 1-90] " ..
" field[0.25,1;1.25,1;LVL;LEVEL;1] " ..
" button_exit[1.25,0.75;1,1;OK;OK] "
)
self.read_form ( ) -- clear inputs
end
state = 1 -- will wait for form receive otherwise game play
self.label ( " stand close to white box and punch it one time to push it. you can only push 1 box \n and cant pull. goal is to get all white boxes pushed on aspen blocks " )
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 )
2022-08-28 00:52:37 -07:00
self.listen_punch ( self.pos ( ) ) ; -- attach punch listener
2021-01-07 02:30:01 -08:00
sokoban.push_time = 0
sokoban.blocks = 0 ; sokoban.level = 0 ; sokoban.moves = 0 ;
imax = 0 ; jmax = 0
sokoban.load = 0 ; sokoban.playername = " " ; sokoban.pos = { } ;
SOKOBAN_WALL = " moreblocks:cactus_brick "
SOKOBAN_FLOOR = " default:silver_sandstone "
SOKOBAN_GOAL = " default:aspen_tree "
2022-08-28 00:52:37 -07:00
SOKOBAN_BOX = " basic_robot:buttonwhite "
2021-01-07 02:30:01 -08: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/games/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 } ) ;
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_ : set_pos ( { x = p.x , y = p.y - 0.5 , 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
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:silver_sandstone " then minetest.set_node ( { x = pos.x + i , y = pos.y - 1 , z = pos.z + j } , { name = " default:silver_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
force_load = function ( )
local lvl = 0 -- 1st level at idx 0 - sokoban level 1
clear_game ( )
if gamedata and gamedata [ name ] then lvl = gamedata [ name ] . lvl end -- load next level to play
load_level ( lvl )
state = 0
self.label ( " stand close to white box and punch it one time to push it. you can only push 1 box \n and can't pull. goal is to get all white boxes pushed on aspen blocks " )
end
if load_player_progress then
force_load ( ) -- this prevents selecting custom level and loads players progress if any
end
end
if state == 1 then -- wait to load game
sender , fields = self.read_form ( )
if fields then
if fields.OK then
local lvl = tonumber ( fields.LVL or 1 ) - 1 ;
clear_game ( )
load_level ( lvl )
state = 0
self.label ( " stand close to white box and punch it one time to push it. you can only push 1 box \n and cant pull. goal is to get all white boxes pushed on aspen blocks " )
else
self.remove ( )
end
end
else -- game
local ppos = player_ : get_pos ( )
if math.abs ( ppos.y - sokoban.pos . y ) ~= 0.5 then minetest.chat_send_player ( name , colorize ( " red " , " SOKOBAN: " .. name .. " QUITS ! " ) ) ;
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 } ;
if minetest.get_node ( pos ) . name == " air " then return end -- ignore move, block wasnt punched
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. " ) ;
if not gamedata then gamedata = { } end
gamedata [ name ] = { lvl = sokoban.level } ; -- increased level
book.write ( 1 , " sokoban players " , minetest.serialize ( gamedata ) )
player_ : set_physics_override ( { jump = 1 } )
player_ : set_eye_offset ( { x = 0 , y = 0 , z = 0 } , { x = 0 , y = 0 , z = 0 } )
local player = _G.minetest . get_player_by_name ( event.puncher ) ;
if player then
local inv = player : get_inventory ( ) ;
inv : add_item ( " main " , _G.ItemStack ( " default:gold_ingot " .. 2 * sokoban.level ) )
end
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
2018-04-03 15:21:12 -07:00
end