· 4 years ago · May 15, 2021, 01:04 PM
1--[[
2 Project info:
3
4 Name: Maze 3D
5 Creator: Jesusthekiller
6 Language: Lua (CC)
7 Website: None
8 License: GNU GPL
9 License file can be fount at www.jesusthekiller.com/license-gpl.html
10
11 Version: 2.1
12]]--
13
14--[[
15 Big thanks to Gopher for 3D engine!
16 http://www.computercraft.info/forums2/index.php?/topic/10786-wolf3d-style-3d-engine-proof-of-concept/page__hl__wolf3d
17]]--
18
19--[[
20 Changelog:
21 1.0:
22 Initial Release
23 2.0:
24 No-HTTP version for Treasure disk
25 2.1:
26 No more temp files!
27]]--
28
29--[[
30 LICENSE:
31
32 Maze 3D
33 Copyright (c) 2013 Jesusthekiller
34
35 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
36
37 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
38
39 See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
40]]--
41
42-- The color check
43if (not term.isColor()) or turtle then
44 print("This program has to be run on advanced computer.")
45 error()
46end
47
48-- The cprint
49local function cwrite(msg)
50 msg = tostring(msg)
51 local x, y = term.getCursorPos()
52 term.setCursorPos((51-#msg)/2, y)
53 write(msg)
54end
55
56local function cprint(msg)
57 cwrite(msg.."\n")
58end
59
60-- The splash
61term.setBackgroundColor(colors.black)
62term.setTextColor(colors.white)
63term.clear()
64
65paintutils.drawImage({[1]={[1]=1,[2]=0,[3]=0,[4]=0,[5]=1,[6]=0,[7]=0,[8]=1,[9]=1,[10]=0,[11]=0,[12]=0,[13]=1,[14]=1,[15]=1,[16]=1,[17]=0,[18]=1,[19]=1,[20]=1,[21]=1,},[2]={[1]=1,[2]=1,[3]=0,[4]=1,[5]=1,[6]=0,[7]=1,[8]=0,[9]=0,[10]=1,[11]=0,[12]=0,[13]=0,[14]=0,[15]=0,[16]=1,[17]=0,[18]=1,[19]=0,[20]=0,[21]=0,},[3]={[1]=1,[2]=0,[3]=1,[4]=0,[5]=1,[6]=0,[7]=1,[8]=1,[9]=1,[10]=1,[11]=0,[12]=0,[13]=0,[14]=1,[15]=1,[16]=0,[17]=0,[18]=1,[19]=1,[20]=1,[21]=0,},[4]={[1]=1,[2]=0,[3]=0,[4]=0,[5]=1,[6]=0,[7]=1,[8]=0,[9]=0,[10]=1,[11]=0,[12]=0,[13]=1,[14]=0,[15]=0,[16]=0,[17]=0,[18]=1,[19]=0,[20]=0,[21]=0,},[5]={[1]=1,[2]=0,[3]=0,[4]=0,[5]=1,[6]=0,[7]=1,[8]=0,[9]=0,[10]=1,[11]=0,[12]=0,[13]=1,[14]=1,[15]=1,[16]=1,[17]=0,[18]=1,[19]=1,[20]=1,[21]=1,},[6]={[1]=0,[2]=0,[3]=0,[4]=0,[5]=0,[6]=0,[7]=0,[8]=0,},[7]={[1]=0,[2]=0,[3]=0,[4]=16384,[5]=16384,[6]=16384,[7]=16384,[8]=0,[9]=0,[10]=0,[11]=0,[12]=512,[13]=512,[14]=512,[15]=512,[16]=0,[17]=0,[18]=0,[19]=0,[20]=0,[21]=0,},[8]={[1]=0,[2]=0,[3]=0,[4]=0,[5]=128,[6]=128,[7]=128,[8]=16384,[9]=0,[10]=0,[11]=0,[12]=512,[13]=128,[14]=128,[15]=128,[16]=512,[17]=0,[18]=0,[19]=0,[20]=0,[21]=0,},[9]={[1]=0,[2]=0,[3]=0,[4]=16384,[5]=16384,[6]=16384,[7]=16384,[8]=0,[9]=128,[10]=0,[11]=0,[12]=512,[13]=128,[14]=0,[15]=0,[16]=512,[17]=128,[18]=0,[19]=0,[20]=0,[21]=0,},[10]={[1]=0,[2]=0,[3]=0,[4]=0,[5]=128,[6]=128,[7]=128,[8]=16384,[9]=0,[10]=0,[11]=0,[12]=512,[13]=128,[14]=0,[15]=0,[16]=512,[17]=128,[18]=0,[19]=0,[20]=0,[21]=0,},[11]={[1]=0,[2]=0,[3]=0,[4]=16384,[5]=16384,[6]=16384,[7]=16384,[8]=0,[9]=128,[10]=0,[11]=0,[12]=512,[13]=512,[14]=512,[15]=512,[16]=128,[17]=128,[18]=0,[19]=0,[20]=0,[21]=0,},[12]={[1]=0,[2]=0,[3]=0,[4]=0,[5]=128,[6]=128,[7]=128,[8]=128,[9]=0,[10]=0,[11]=0,[12]=0,[13]=128,[14]=128,[15]=128,[16]=128,},}, 15, 3)
66
67parallel.waitForAny(
68 function() coroutine.yield(); os.pullEvent("key"); coroutine.yield() end,
69 function() term.setBackgroundColor(colors.black); term.setTextColor(colors.white) while true do term.setCursorPos(18, 16); term.write("Press any key.."); sleep(0.5); term.clearLine(); sleep(0.5) end end
70)
71
72-- The size
73local size
74
75repeat
76 term.setCursorPos(1, 16)
77 term.clearLine()
78
79 cwrite("Enter maze size (5-99):")
80 size = read()
81
82 size = tonumber(size)
83 if not size then
84 size = 0
85 end
86until size > 4 and size < 100
87
88-- The generate
89local function mazeGen(mx, my)
90
91 --[[
92 Format:
93
94 maze.x.y.(1/2/3/4) = true/false
95
96 1 - top
97 2 - bottom
98 3 - right
99 4 - left
100 ]]--
101
102 local maze = {}
103 for i = 1, mx do
104 maze[i] = {}
105 for j = 1, my do
106 maze[i][j] = {}
107 for k = 1, 4 do
108 maze[i][j][k] = true
109 end
110 end
111 end
112
113 local vis = 1
114 local tot = mx * my
115 local curr = {}
116 curr.x = math.random(1, mx)
117 curr.y = math.random(1, my)
118 local stack = {}
119
120 while vis < tot do
121 local intact = {}
122 local x = curr.x
123 local y = curr.y
124
125 if x - 1 >= 1 and maze[x-1][y][1] and maze[x-1][y][2] and maze[x-1][y][3] and maze[x-1][y][4] then -- Check for full cells
126 intact[#intact+1] = {x-1, y, 1}
127 end
128
129 if x + 1 <= mx and maze[x+1][y][1] and maze[x+1][y][2] and maze[x+1][y][3] and maze[x+1][y][4] then
130 intact[#intact+1] = {x+1, y, 2}
131 end
132
133 if y + 1 <= my and maze[x][y+1][1] and maze[x][y+1][2] and maze[x][y+1][3] and maze[x][y+1][4] then
134 intact[#intact+1] = {x, y+1, 3}
135 end
136
137 if y - 1 >= 1 and maze[x][y-1][1] and maze[x][y-1][2] and maze[x][y-1][3] and maze[x][y-1][4] then
138 intact[#intact+1] = {x, y-1, 4}
139 end
140
141 if #intact > 0 then
142 local i = math.random(1, #intact) -- Choose random
143
144 if intact[i][3] == 1 then -- Set intact's attached wall to false
145 maze[intact[i][1]][intact[i][2]][2] = false
146 elseif intact[i][3] == 2 then
147 maze[intact[i][1]][intact[i][2]][1] = false
148 elseif intact[i][3] == 3 then
149 maze[intact[i][1]][intact[i][2]][4] = false
150 elseif intact[i][3] == 4 then
151 maze[intact[i][1]][intact[i][2]][3] = false
152 end
153
154 maze[x][y][intact[i][3]] = false -- Set attached wall to false
155
156 vis = vis + 1 -- Increase vis
157
158 stack[#stack+1] = intact[i] -- Add to stack
159 else
160 local tmp = table.remove(stack) -- Get last cell
161 curr.x = tmp[1]
162 curr.y = tmp[2]
163 end
164 end
165
166 return maze
167end
168
169local m = mazeGen(size, size)
170
171-- The game init
172local posx = 2
173local posy = 2
174
175local offsetx = 51/2-2
176local offsety = 19/2-2
177
178-- The maze-to-table
179local tab = {}
180
181for x = 1, size * 2 + 1 do
182 tab[x] = {}
183
184 for y = 1, size * 2 + 1 do
185 if x % 2 == 0 and y % 2 == 0 then -- Fill cells (empty)
186 tab[x][y] = " "
187 elseif x % 2 == 1 and y % 2 == 1 then -- Fill corners (full)
188 tab[x][y] = "1"
189 end
190 end
191end
192
193for x, tV in ipairs(m) do
194 for y, v in ipairs(tV) do
195 if x == size and y == size then
196 v[1] = v[1] and "2" or " "
197 v[2] = v[2] and "2" or " "
198 v[3] = v[3] and "2" or " "
199 v[4] = v[4] and "2" or " "
200 tab[x*2-1][y*2] = v[1] -- Up
201 tab[x*2+1][y*2] = v[2] -- Down
202 tab[x*2][y*2+1] = v[3] -- Right
203 tab[x*2][y*2-1] = v[4] -- Left
204 else
205 v[1] = v[1] and "1" or " "
206 v[2] = v[2] and "1" or " "
207 v[3] = v[3] and "1" or " "
208 v[4] = v[4] and "1" or " "
209 tab[x*2-1][y*2] = v[1] -- Up
210 tab[x*2+1][y*2] = v[2] -- Down
211 tab[x*2][y*2+1] = v[3] -- Right
212 tab[x*2][y*2-1] = v[4] -- Left
213 end
214 end
215end
216
217local gtab = {}
218
219for k, v in ipairs(tab) do
220 gtab[#gtab+1] = table.concat(v)
221end
222
223size = size * 2 + 1
224
225--[[
226local template = fs.open("maze3d_template", "r")
227local game = fs.open("maze3d_game", "w")
228
229game.writeLine("local mapH, mapW = "..size..","..size)
230game.writeLine("local dir = "..(gtab[2]:sub(3,3) == " " and '0' or '88'))
231game.writeLine("local map = {")
232
233for k, v in ipairs(gtab) do
234 game.writeLine('"'..v..'",')
235end
236
237game.writeLine("}")
238
239game.writeLine(template.readAll())
240game.close()
241template.close()
242
243shell.run("maze3d_game")
244
245fs.delete("maze3d_game")
246fs.delete("maze3d_template")]]
247
248local mapH, mapW = size, size
249local dir = gtab[2]:sub(3,3) == " " and '0' or '88'
250local map = gtab
251local startdir = dir
252
253------------------------------------------------------------------------------------------------------
254--GOPHER'S CODE HERE
255
256local buffer=term
257local loadedAPI=false
258
259local stime = os.clock()
260
261if redirect then
262 buffer=redirect.createRedirectBuffer()
263 print("redirect API found, using buffer")
264else
265 local pe=printError
266 rawset(_G,"printError",error)
267 local ok, err=pcall(os.loadAPI,"redirect")
268 if not ok then
269 print("trying "..shell.dir().."/redirect")
270 ok,err=pcall(os.loadAPI,shell.dir().."/redirect")
271 end
272 if ok then
273 print("Loaded redirect API, using buffer")
274 buffer=redirect.createRedirectBuffer()
275 loadedAPI=true
276 else
277 print("redirect API not found or could not be loaded, drawing directly; this may cause flickering.")
278 end
279 rawset(_G,"printError",pe)
280end
281
282local colorSchemes = {
283 {0,8}, --white+gray
284 {3,11}, --blue
285 {6,14}, --red
286 {5,13}, --green
287 {4,1}, --yellow/orange
288}
289
290
291local function cast(cx,cy,angle)
292 --direction vector
293 local vx,vy=math.cos(angle), math.sin(angle)
294 local slope=vy/vx
295 --next distance, x and y axis points
296 local ndx, ndy
297 --steps, distance and block
298 local dsx, dsy, bsx, bsy
299 if vx<0 then
300 local x=(cx%1)
301 bsx=-1
302 ndx=math.sqrt(x*x*(1+slope*slope))
303 dsx=math.sqrt((1+slope*slope))
304 else
305 local x=1-(cx%1)
306 bsx=1
307 ndx=math.sqrt(x*x*(1+slope*slope))
308 dsx=math.sqrt((1+slope*slope))
309 end
310
311 if vy<0 then
312 local y=(cy%1)
313 bsy=-1
314 ndy=math.sqrt(y*y*(1+1/(slope*slope)))
315 dsy=math.sqrt((1+1/(slope*slope)))
316 else
317 local y=1-(cy%1)
318 bsy=1
319 ndy=math.sqrt(y*y*(1+1/(slope*slope)))
320 dsy=math.sqrt((1+1/(slope*slope)))
321 end
322
323 local x,y=math.floor(cx),math.floor(cy)
324 while x>0 and x<=mapW and y>0 and y<=mapH do
325 local hitD
326 local isX
327 if ndx<ndy then
328 --x crossing is next
329 x=x+bsx
330 isX=true
331 hitD=ndx
332 ndx=ndx+dsx
333 else
334 y=y+bsy
335 isX=false
336 hitD=ndy
337 ndy=ndy+dsy
338 end
339 local wall=map[y]:sub(x,x)
340 if wall~=" " then
341
342 return colorSchemes[tonumber(wall)][isX and 1 or 2], hitD
343 end
344 end
345end
346
347local w,h=term.getSize()
348local centerX, centerY=math.floor((w+1)/2), math.floor((h+1)/2)
349
350local px, py=2.5,2.5
351--local dir=0
352local fx,fy
353local speed=.1
354local turnSpeed=4
355
356local function turn(amt)
357 dir=dir+amt
358 fx,fy=math.cos(math.rad(dir)), math.sin(math.rad(dir))
359end
360
361turn(0)
362
363--build table of angles and base distances per scanline
364local screenDist=.55*w
365local scan={}
366
367for x=1,w do
368 local t={}
369 scan[x]=t
370 t.angle=math.atan2(x-centerX,screenDist)
371 t.dist=((x-centerX)^2+screenDist^2)^.5/screenDist
372end
373
374local function redraw()
375 local oldTerm
376 if buffer.isBuffer then
377 oldTerm = term.redirect(buffer)
378 end
379 for x=1,w do
380 local wall,dist=cast(px,py,math.rad(dir)+scan[x].angle)
381 if wall then
382 --calc wall height based on distance
383 local height=scan[x].dist/dist
384 height=math.floor(math.min(height*centerY,(h+1)/2))
385 term.setBackgroundColor(colors.gray)
386 for y=1,(h+1)/2-height-1 do
387 term.setCursorPos(x,y)
388 term.write(" ")
389 end
390 for y=centerY+height+1,h do
391 term.setCursorPos(x,y)
392 term.write(" ")
393 end
394 term.setBackgroundColor(2^wall)
395 for y=centerY-height,centerY+height do
396 term.setCursorPos(x,y)
397 term.write(" ")
398 end
399 end
400 end
401 if buffer.isBuffer then
402 term.redirect(oldTerm)
403 buffer.blit()
404 end
405end
406
407local function clampCollision(x,y,radius)
408 --am I *in* a block?
409 local gx,gy=math.floor(x),math.floor(y)
410 if map[gy]:sub(gx,gx)~=" " then
411 --I am. Complete fail, do nothing.
412 return x,y
413 end
414
415 --ok, check the neighbors.
416 local right=math.floor(x+radius)>gx
417 local left=math.floor(x-radius)<gx
418 local front=math.floor(y-radius)<gy
419 local back=math.floor(y+radius)>gy
420
421 local pushed=false
422
423 if right and map[gy]:sub(gx+1,gx+1)~=" " then
424 --push left
425 pushed=true
426 x=gx+1-radius
427 elseif left and map[gy]:sub(gx-1,gx-1)~=" " then
428 --push right
429 pushed=true
430 x=gx+radius
431 end
432
433 if front and map[gy-1]:sub(gx,gx)~=" " then
434 --push back
435 pushed=true
436 y=gy+radius
437 elseif back and map[gy+1]:sub(gx,gx)~=" " then
438 --push forward
439 pushed=true
440
441
442
443 y=gy+1-radius
444 end
445
446 --if I wasn't pushed out on any side, I might be hitting a corner
447 if not pushed then
448 --square rad
449 local r2=radius^2
450 local pushx,pushy=0,0
451 if left then
452 if front and map[gy-1]:sub(gx-1,gx-1)~=" " then
453 --check front-left
454 local dist2=(gx-x)^2+(gy-y)^2
455 if dist2<r2 then
456 local pushd=(r2-dist2)/2^.5
457 pushx,pushy=pushd,pushd
458 end
459 elseif back and map[gy+1]:sub(gx-1,gx-1)~=" " then
460 local dist2=(gx-x)^2+(gy+1-y)^2
461 if dist2<r2 then
462 local pushd=(r2-dist2)/2^.5
463 pushx,pushy=pushd,-pushd
464 end
465 end
466 elseif right then
467 if front and map[gy-1]:sub(gx+1,gx+1)~=" " then
468 --check front-left
469 local dist2=(gx+1-x)^2+(gy-y)^2
470 if dist2<r2 then
471 local pushd=(r2-dist2)/2^.5
472 pushx,pushy=-pushd,pushd
473 end
474 elseif back and map[gy+1]:sub(gx+1,gx+1)~=" " then
475 local dist2=(gx+1-x)^2+(gy+1-y)^2
476 if dist2<r2 then
477 local pushd=(r2-dist2)/2^.5
478 pushx,pushy=-pushd,-pushd
479 end
480 end
481 end
482 x=x+pushx
483 y=y+pushy
484 end
485
486 return x,y
487end
488
489term.setBackgroundColor(colors.black)
490--term.setTextColor(colors.white)
491term.clear()
492term.setCursorPos(1, 1)
493
494term.setTextColor(colors.yellow)
495write("Move: ")
496term.setTextColor(colors.lime)
497print("WASD")
498
499term.setTextColor(colors.yellow)
500write("Turn: ")
501term.setTextColor(colors.lime)
502print("Left/Right arrow")
503
504term.setTextColor(colors.yellow)
505write("Teleport to start: ")
506term.setTextColor(colors.lime)
507print("R")
508
509term.setTextColor(colors.yellow)
510write("Quit: ")
511term.setTextColor(colors.lime)
512print("Q\n")
513
514term.setTextColor(colors.white)
515write("Goal: go to ")
516term.setTextColor(colors.lightBlue)
517write("blue")
518term.setTextColor(colors.white)
519print(" spot (opposite corner of the map)\n\n\n\n")
520
521term.setTextColor(colors.white)
522cprint("Press any key to start!")
523
524os.pullEvent("key")
525
526local frameTimer=os.startTimer(0.5)
527local prevTick=0
528local dirty=true
529local win = false
530while true do
531 px,py=clampCollision(px,py,.25)
532 if dirty then
533 redraw()
534 dirty=false
535 end
536
537 local e={os.pullEvent()}
538 if e[1]=="key" then
539 if e[2]==keys.left then
540 turn(-turnSpeed)
541 dirty=true
542 elseif e[2]==keys.right then
543 turn(turnSpeed)
544 dirty=true
545 elseif e[2]==keys.up or e[2]==keys.w then
546 px=px+fx*speed
547 py=py+fy*speed
548 dirty=true
549 elseif e[2]==keys.down or e[2]==keys.s then
550 px=px-fx*speed
551 py=py-fy*speed
552 dirty=true
553 elseif e[2]==keys.a then
554 px=px+fy*speed
555 py=py-fx*speed
556 dirty=true
557 elseif e[2]==keys.d then
558 px=px-fy*speed
559 py=py+fx*speed
560 dirty=true
561 elseif e[2]==keys.q then
562 break
563 elseif e[2]==keys.r then
564 px,py = 2.5,2.5
565 dir=startdir
566 dirty=true
567 end
568
569 if px >= mapW-1 and py >= mapH-1 then
570 win = true
571 break
572 end
573 end
574end
575
576
577if loadedAPI then
578 os.unloadAPI("redirect")
579end
580
581-- JESUS PART
582
583-- The win/loose message
584term.setBackgroundColor(colors.white)
585term.setTextColor(colors.black)
586term.clear()
587term.setCursorPos(1, 1)
588
589if win then
590 local ntime = os.clock()
591 write("\n")
592 cprint("Congratulations!")
593 cprint("You made it in")
594 cprint(tostring(math.floor((ntime-stime)/60)).." minutes and "..tostring(math.ceil((ntime-stime)%60)).." seconds")
595 cprint("Size of maze: "..(mapW-1)/2)
596sleep(1)
597else
598 write("\n")
599 cprint("Oh noes D:")
600end
601
602
603
604parallel.waitForAny(
605 function() coroutine.yield(); os.pullEvent("key"); coroutine.yield() end,
606 function() term.setBackgroundColor(colors.white); term.setTextColor(colors.black) while true do term.setCursorPos(18, 14); term.write("Press any key.."); sleep(0.5); term.clearLine(); sleep(0.5) end end
607)
608
609term.setBackgroundColor(colors.black)
610term.setTextColor(colors.white)
611term.clear()
612term.setCursorPos(1, 1)
613cprint(" Maze 3D by JTK. Thanks for playing!")
614cprint("3D engine by Gopher, He is A-W-E-S-O-M-E")
615