Last active
September 3, 2022 13:45
-
-
Save jan-martinek/a81605d35a585b265e2e0a42bb32966a to your computer and use it in GitHub Desktop.
Maze
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local function vectorFromAngle(magnitude, angle) | |
angle = (angle + 90) % 360 | |
local rad = math.rad(angle) | |
local x = magnitude * math.cos(rad) | |
local y = magnitude * math.sin(rad) | |
return v2D.new(x, y) | |
end | |
Maze = {} | |
class("Maze").extends(NobleScene) | |
local at = playdate.geometry.affineTransform | |
local fastInt = playdate.geometry.lineSegment.fast_intersection | |
local sqDist = playdate.geometry.squaredDistanceToPoint | |
local distance = nil | |
local distance2 = nil | |
local rayCount = nil | |
local forwardRayNumber = nil | |
local rays = nil | |
local directions = nil | |
local directionsNormal = nil | |
local angles = nil | |
local radAngle = nil | |
local forwardDir = nil | |
local forwardNormalDir = nil | |
local panelWidth = nil | |
local panelsOffset = nil | |
local move = nil | |
local rotation = nil | |
local walls = nil | |
local path = nil | |
local pathPositions = nil | |
local pos = nil | |
local automove = nil | |
local auto = nil | |
local frontRay | |
local leftNormalDir | |
local leftRay | |
local rightNormalDir | |
local rightRay | |
local function rotateCw(v) | |
return { x = v.y, y = -v.x } | |
end | |
local function rotateCcw(v) | |
return { x = -v.y, y = v.x } | |
end | |
local function rotateLevel(amount) | |
local t = at.new() | |
t:rotate(amount, pos.x, pos.y) | |
for i=1,#walls do | |
local x1, y1 = t:transformXY(walls[i][1], walls[i][2]) | |
local x2, y2 = t:transformXY(walls[i][3], walls[i][4]) | |
walls[i] = {x1, y1, x2, y2} | |
end | |
end | |
local function moveLevel(amount) | |
local t = at.new() | |
t:translate(- forwardNormalDir.x * amount, - forwardNormalDir.y * amount) | |
for i=1,#walls do | |
local x1, y1 = t:transformXY(walls[i][1], walls[i][2]) | |
local x2, y2 = t:transformXY(walls[i][3], walls[i][4]) | |
walls[i] = {x1, y1, x2, y2} | |
end | |
end | |
local function getMove(dir) | |
local countdown = 40 | |
return function() | |
if countdown == 0 then | |
move = nil | |
automove() | |
else | |
moveLevel(2.5 / 100) | |
countdown -= 1 | |
end | |
end | |
end | |
local function getRotation(right) | |
local countdown = 20 | |
return function() | |
if countdown == 0 then | |
rotation = nil | |
--walls = rotated | |
automove() | |
else | |
if right then | |
rotateLevel(-180 / 40) | |
else | |
rotateLevel(180 / 40) | |
end | |
countdown -= 1 | |
end | |
end | |
end | |
local function wallCheck(ray) | |
for wall=1,#walls do | |
local w = walls[wall] | |
if ((w[1] >= 0 and w[2] >= 0) or (w[3] >= 0 and w[4] >= 0)) and fastInt(w[1], w[2], w[3], w[4], ray[1], ray[2], ray[3], ray[4]) then | |
return true | |
end | |
end | |
end | |
local function frontCheck() | |
return wallCheck(frontRay) | |
end | |
local function leftCheck() | |
return wallCheck(leftRay) | |
end | |
local function rightCheck() | |
return wallCheck(rightRay) | |
end | |
automove = function() | |
if not frontCheck() then | |
move = getMove({ x = forwardNormalDir.x, y = forwardNormalDir.y }) | |
elseif not rightCheck() then | |
rotation = getRotation(false) | |
else | |
rotation = getRotation(true) | |
end | |
end | |
local Move = {} | |
Move.left = 1 | |
Move.fw = 2 | |
Move.right = 3 | |
Maze.backgroundColor = Graphics.kColorWhite -- This is the background color of this scene. | |
-- This runs when your scene's object is created, which is the first thing that happens when transitining away from another scene. | |
function Maze:init() | |
Maze.super.init(self) | |
distance = 4 | |
distance2 = distance^2 | |
rayCount = 41 | |
pos = playdate.geometry.point.new(0.5, 0.5) | |
forwardRayNumber = math.ceil(rayCount / 2) | |
radAngle = (0.5 * math.pi) / rayCount | |
rays = {} | |
for i=1,rayCount do | |
local angle = -90 + 90 / rayCount * i | |
local dir = vectorFromAngle(distance, angle) | |
rays[i] = {pos.x, pos.y, pos.x + dir.x, pos.y + dir.y} | |
if i == forwardRayNumber then | |
forwardDir = vectorFromAngle(distance, angle) | |
forwardNormalDir = vectorFromAngle(1, angle) | |
end | |
end | |
panelWidth = math.ceil(400 / rayCount) | |
panelsOffset = (rayCount * math.ceil(400 / rayCount) - 400) / 2 | |
frontRay = {pos.x, pos.y, pos.x + forwardNormalDir.x, pos.y + forwardNormalDir.y} | |
leftNormalDir = rotateCcw(forwardNormalDir) | |
leftRay = {pos.x, pos.y, pos.x + leftNormalDir.x, pos.y + leftNormalDir.y} | |
rightNormalDir = rotateCw(forwardNormalDir) | |
rightRay = {pos.x, pos.y, pos.x + rightNormalDir.x, pos.y + rightNormalDir.y} | |
auto = true | |
walls = { | |
{0, 0, 1, 0}, {1, 0, 2, 0}, {2, 0, 3, 0}, {3, 0, 4, 0}, | |
{1, 1, 2, 1}, {4, 1, 5, 1}, | |
{0, 2, 1, 2}, {2, 2, 3, 2}, | |
{1, 3, 2, 3}, {2, 3, 3, 3}, {4, 3, 5, 3}, | |
{3, 4, 4, 4}, | |
{1, 5, 2, 5}, {2, 5, 3, 5}, {3, 5, 4, 5}, {4, 5, 5, 5}, | |
{0, 0, 0, 1}, {0, 1, 0, 2}, | |
{1, 2, 1, 3}, {1, 3, 1, 4}, {1, 4, 1, 5}, | |
{2, 0, 2, 1}, {2, 1, 2, 2}, {2, 4, 2, 5}, | |
{3, 1, 3, 2}, {3, 3, 3, 4}, | |
{4, 0, 4, 1}, {4, 1, 4, 2}, | |
{5, 1, 5, 2}, {5, 2, 5, 3}, {5, 3, 5, 4}, {5, 4, 5, 5} | |
-- { 0, 0, 1, 0 }, { 1, 0, 2, 0 }, | |
-- { 0, 0, 0, 1 }, | |
-- { 0, 1, 1, 1 }, { 1, 1, 2, 1 }, | |
-- { 2, 0, 2, 1 }, | |
--{ 0, 0, 1, 0 }, { 1, 0, 2, 0 }, { 2, 0, 3, 0 }, { 3, 0, 4, 0 }, | |
--{ 4, 0, 4, 1 }, { 4, 1, 4, 2 }, { 4, 2, 4, 3 }, { 4, 3, 4, 4 }, | |
--{ 0, 4, 1, 4 }, { 1, 4, 2, 4 }, { 2, 4, 3, 4 }, { 3, 4, 4, 4 }, | |
--{ 0, 0, 0, 1 }, { 0, 1, 0, 2 }, { 0, 2, 0, 3 }, { 0, 3, 0, 4 }, | |
--{ 0, 1, 1, 1 }, { 1, 1, 2, 1 }, | |
--{ 3, 0, 3, 1 }, { 3, 1, 3, 2 }, | |
--{ 1, 2, 2, 2 }, { 2, 2, 3, 2 }, | |
--{ 1, 3, 2, 3 }, | |
--{ 3, 3, 4, 3 }, | |
} | |
rotateLevel(180 * 0.25) | |
automove() | |
end | |
-- When transitioning from another scene, this runs as soon as this scene needs to be visible (this moment depends on which transition type is used). | |
function Maze:enter() | |
Maze.super.enter(self) | |
end | |
-- This runs once a transition from another scene is complete. | |
function Maze:start() | |
Maze.super.start(self) | |
end | |
-- local function collisionDist(x1, y1, x2, y2, x3, y3, x4, y4) | |
-- local uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)) | |
-- local uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)) | |
-- if uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1 then | |
-- local x = x1 + (uA * (x2-x1)) | |
-- local y = y1 + (uA * (y2-y1)) | |
-- return (x - x1)^2 + (y - y1)^2 | |
-- end | |
-- return distance2 | |
-- end | |
local counter = 0 | |
-- This runs once per frame. | |
function Maze:update() | |
Maze.super.update(self) | |
-- Graphics.setColor(Graphics.kColorBlack) | |
-- Graphics.fillRect(0, 0, 400, 120) | |
if move then move() end | |
if rotation then rotation() end | |
local collisions = {} | |
for i=1,rayCount do | |
for wall=1,#walls do | |
local w = walls[wall] | |
if (w[1] > -0.4 and w[2] > -0.4 and w[3] > -0.4 and w[4] > -0.4 and w[1] < distance and w[2] < distance and w[3] < distance and w[4] < distance) then | |
local intersects, x, y = fastInt(w[1], w[2], w[3], w[4], rays[i][1], rays[i][2], rays[i][3], rays[i][4]) | |
if intersects then | |
local dist2 = sqDist(pos.x, pos.y, x, y) | |
if not collisions[i] or collisions[i] > dist2 then | |
collisions[i] = dist2 | |
end | |
end | |
end | |
end | |
end | |
for i=1,rayCount do | |
local collideDist = collisions[i] and math.sqrt(collisions[i]) or distance | |
local panel = 1 / (math.cos(radAngle * (i - forwardRayNumber)) * collideDist) * 100 | |
-- Graphics.setColor(Graphics.kColorWhite) | |
-- Graphics.fillRect( | |
-- (i - 1) * panelWidth - panelsOffset, | |
-- 120 - panel / 2, | |
-- panelWidth, | |
-- panel) | |
-- Graphics.setColor(Graphics.kColorBlack) | |
Graphics.setDitherPattern(math.clamp(panel / 400, 0.001, 0.999)) | |
Graphics.fillRect( | |
(i - 1) * panelWidth - panelsOffset, | |
120 - panel / 2, | |
panelWidth, | |
panel) | |
end | |
end | |
function playdate.debugDraw() | |
local s = 20 | |
local collisions = {} | |
for i=1,rayCount do | |
for wall=1,#walls do | |
local w = walls[wall] | |
if (w[1] > -0.4 and w[2] > -0.4 and w[3] > -0.4 and w[4] > -0.4 and w[1] < distance and w[2] < distance and w[3] < distance and w[4] < distance) then | |
local intersects, x, y = fastInt(w[1], w[2], w[3], w[4], rays[i][1], rays[i][2], rays[i][3], rays[i][4]) | |
if intersects then | |
local dist2 = sqDist(pos.x, pos.y, x, y) | |
if not collisions[i] or collisions[i] > dist2 then | |
collisions[i] = dist2 | |
end | |
end | |
end | |
if not collisions[i] then | |
collisions[i] = distance2 | |
end | |
end | |
end | |
Graphics.setDrawOffset(200 - pos.x * s, 120 - pos.y * s) | |
for i=1,rayCount do | |
local scale = math.sqrt(collisions[i]) / distance | |
local x, y = (rays[i][3] - rays[i][1]) * scale, (rays[i][4] - rays[i][2]) * scale | |
Graphics.drawLine(pos.x * s, pos.y * s, pos.x * s + x * s, pos.y * s + y * s) | |
end | |
counter += 1 | |
for i=1,#walls do | |
if counter > 100 then | |
local w = walls[i] | |
if (w[1] > -0.4 and w[2] > -0.4 and w[3] > -0.4 and w[4] > -0.4 and w[1] < distance and w[2] < distance and w[3] < distance and w[4] < distance) then | |
Graphics.drawLine(walls[i][1] * s, walls[i][2] * s, walls[i][3] * s, walls[i][4] * s) | |
end | |
else | |
Graphics.drawLine(walls[i][1] * s, walls[i][2] * s, walls[i][3] * s, walls[i][4] * s) | |
end | |
end | |
end | |
-- This runs once per frame, and is meant for drawing code. | |
function Maze:drawBackground() | |
Maze.super.drawBackground(self) | |
end | |
-- This runs as as soon as a transition to another scene begins. | |
function Maze:exit() | |
Maze.super.exit(self) | |
end | |
-- This runs once a transition to another scene completes. | |
function Maze:finish() | |
Maze.super.finish(self) | |
end | |
function Maze:pause() | |
Maze.super.pause(self) | |
end | |
function Maze:resume() | |
Maze.super.resume(self) | |
end | |
-- You can define this here, or within your scene's init() function. | |
Maze.inputHandler = { | |
-- A button | |
-- | |
AButtonDown = function() -- Runs once when button is pressed. | |
end, | |
AButtonHold = function() -- Runs every frame while the player is holding button down. | |
end, | |
AButtonHeld = function() -- Runs after button is held for 1 second. | |
end, | |
AButtonUp = function() -- Runs once when button is released. | |
end, | |
-- B button | |
-- | |
BButtonDown = function() | |
end, | |
BButtonHeld = function() | |
end, | |
BButtonHold = function() | |
end, | |
BButtonUp = function() | |
end, | |
-- D-pad left | |
-- | |
leftButtonDown = function() | |
--if not rotation then | |
-- rotation = getRotation(false) | |
--end | |
end, | |
leftButtonHold = function() | |
end, | |
leftButtonUp = function() | |
end, | |
-- D-pad right | |
-- | |
rightButtonDown = function() | |
--if not rotation then | |
-- rotation = getRotation(true) | |
--end | |
end, | |
rightButtonHold = function() | |
end, | |
rightButtonUp = function() | |
end, | |
-- D-pad up | |
-- | |
upButtonDown = function() | |
--if not move then | |
-- move = getMove({ x = forwardNormalDir.x, y = forwardNormalDir.y }) | |
--end | |
end, | |
upButtonHold = function() | |
end, | |
upButtonUp = function() | |
end, | |
-- D-pad down | |
-- | |
downButtonDown = function() | |
-- if not move then | |
-- move = getMove({ x = -forwardNormalDir.x, y = -forwardNormalDir.y }) | |
-- end | |
end, | |
downButtonHold = function() | |
end, | |
downButtonUp = function() | |
end, | |
-- Crank | |
-- | |
cranked = function(change, acceleratedChange) -- Runs when the crank is rotated. See Playdate SDK documentation for details. | |
end, | |
crankDocked = function() -- Runs once when when crank is docked. | |
end, | |
crankUndocked = function() -- Runs once when when crank is undocked. | |
end | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment