Added elevator-controller.
This commit is contained in:
		
							parent
							
								
									cf10db1b7f
								
							
						
					
					
						commit
						80bb1986b2
					
				| 
						 | 
					@ -0,0 +1,399 @@
 | 
				
			||||||
 | 
					--[[
 | 
				
			||||||
 | 
					    Elevator Controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A script for an all-in-one elevator with floor selection, sounds, doors, and
 | 
				
			||||||
 | 
					more.
 | 
				
			||||||
 | 
					]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local SYSTEM_NAME = "Test Elevator"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Floors, in order from bottom to top.
 | 
				
			||||||
 | 
					local FLOORS = {
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label = "-1",
 | 
				
			||||||
 | 
					        name = "Basement",
 | 
				
			||||||
 | 
					        speaker = "speaker_2",
 | 
				
			||||||
 | 
					        doorRedstone = "redstoneIntegrator_17",
 | 
				
			||||||
 | 
					        contactRedstone = "redstoneIntegrator_18",
 | 
				
			||||||
 | 
					        monitor = "monitor_4",
 | 
				
			||||||
 | 
					        callMonitor = "monitor_5",
 | 
				
			||||||
 | 
					        height = 25
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label = "0",
 | 
				
			||||||
 | 
					        name = "Ground Floor",
 | 
				
			||||||
 | 
					        speaker = "speaker_1",
 | 
				
			||||||
 | 
					        doorRedstone = "redstoneIntegrator_7",
 | 
				
			||||||
 | 
					        contactRedstone = "redstoneIntegrator_6",
 | 
				
			||||||
 | 
					        monitor = "monitor_1",
 | 
				
			||||||
 | 
					        callMonitor = "monitor_3",
 | 
				
			||||||
 | 
					        height = 58
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        label = "1",
 | 
				
			||||||
 | 
					        name = "Mechanical Room",
 | 
				
			||||||
 | 
					        speaker = "speaker_0",
 | 
				
			||||||
 | 
					        doorRedstone = "redstoneIntegrator_4",
 | 
				
			||||||
 | 
					        contactRedstone = "redstoneIntegrator_5",
 | 
				
			||||||
 | 
					        monitor = "monitor_0",
 | 
				
			||||||
 | 
					        callMonitor = "monitor_2",
 | 
				
			||||||
 | 
					        height = 68
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local FLOORS_BY_LABEL = {}
 | 
				
			||||||
 | 
					for _, floor in pairs(FLOORS) do
 | 
				
			||||||
 | 
					    FLOORS_BY_LABEL[floor.label] = floor
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local FLOOR_LABELS_ORDERED = {}
 | 
				
			||||||
 | 
					for _, floor in pairs(FLOORS) do
 | 
				
			||||||
 | 
					    table.insert(FLOOR_LABELS_ORDERED, floor.label)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					table.sort(FLOOR_LABELS_ORDERED, function(lblA, lblB) return FLOORS_BY_LABEL[lblA].height < FLOORS_BY_LABEL[lblB].height end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local CONTROL_BASE_RPM = 16
 | 
				
			||||||
 | 
					local CONTROL_MAX_RPM = 256
 | 
				
			||||||
 | 
					local CONTROL_ANALOG_LEVEL_PER_RPM = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local CONTROL_DIRECTION_UP = true
 | 
				
			||||||
 | 
					local CONTROL_DIRECTION_DOWN = false
 | 
				
			||||||
 | 
					local CONTROL_REDSTONE = "redstoneIntegrator_13"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local CURRENT_STATE = {
 | 
				
			||||||
 | 
					    rpm = nil,
 | 
				
			||||||
 | 
					    direction = nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function openDoor(floor)
 | 
				
			||||||
 | 
					    peripheral.call(floor.doorRedstone, "setOutput", "back", true)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function closeDoor(floor)
 | 
				
			||||||
 | 
					    peripheral.call(floor.doorRedstone, "setOutput", "back", false)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function playChime(floor)
 | 
				
			||||||
 | 
					    local speaker = peripheral.wrap(floor.speaker)
 | 
				
			||||||
 | 
					    speaker.playNote("chime", 1, 18)
 | 
				
			||||||
 | 
					    os.sleep(0.1)
 | 
				
			||||||
 | 
					    speaker.playNote("chime", 1, 12)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Converts an RPM speed to a blocks-per-second speed.
 | 
				
			||||||
 | 
					local function rpmToBps(rpm)
 | 
				
			||||||
 | 
					    return (10 / 256) * rpm
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Sets the RPM of the elevator winch, and returns the true rpm that the system operates at.
 | 
				
			||||||
 | 
					local function setRpm(rpm)
 | 
				
			||||||
 | 
					    if rpm == 0 then
 | 
				
			||||||
 | 
					        peripheral.call(CONTROL_REDSTONE, "setOutput", "left", true)
 | 
				
			||||||
 | 
					        return 0
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        local analogPower = 0
 | 
				
			||||||
 | 
					        local trueRpm = 16
 | 
				
			||||||
 | 
					        while trueRpm < rpm do
 | 
				
			||||||
 | 
					            analogPower = analogPower + CONTROL_ANALOG_LEVEL_PER_RPM
 | 
				
			||||||
 | 
					            trueRpm = trueRpm * 2
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        peripheral.call(CONTROL_REDSTONE, "setAnalogOutput", "right", analogPower)
 | 
				
			||||||
 | 
					        peripheral.call(CONTROL_REDSTONE, "setOutput", "left", false)
 | 
				
			||||||
 | 
					        return trueRpm
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Sets the speed of the elevator motion.
 | 
				
			||||||
 | 
					-- Positive numbers move the elevator up.
 | 
				
			||||||
 | 
					-- Zero sets the elevator as motionless.
 | 
				
			||||||
 | 
					-- Negative numbers move the elevator down.
 | 
				
			||||||
 | 
					-- The nearest possible RPM is used, via SPEEDS.
 | 
				
			||||||
 | 
					local function setSpeed(rpm)
 | 
				
			||||||
 | 
					    if rpm == 0 then
 | 
				
			||||||
 | 
					        if CURRENT_STATE.rpm ~= 0 then
 | 
				
			||||||
 | 
					            CURRENT_STATE.rpm = setRpm(0)
 | 
				
			||||||
 | 
					            -- print("Set RPM to " .. tostring(CURRENT_STATE.rpm))
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if rpm > 0 then
 | 
				
			||||||
 | 
					        peripheral.call(CONTROL_REDSTONE, "setOutput", "top", CONTROL_DIRECTION_UP)
 | 
				
			||||||
 | 
					        CURRENT_STATE.direction = CONTROL_DIRECTION_UP
 | 
				
			||||||
 | 
					        -- print("Set winch to UP")
 | 
				
			||||||
 | 
					    elseif rpm < 0 then
 | 
				
			||||||
 | 
					        peripheral.call(CONTROL_REDSTONE, "setOutput", "top", CONTROL_DIRECTION_DOWN)
 | 
				
			||||||
 | 
					        CURRENT_STATE.direction = CONTROL_DIRECTION_DOWN
 | 
				
			||||||
 | 
					        -- print("Set winch to DOWN")
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if math.abs(rpm) == CURRENT_STATE.rpm then return end
 | 
				
			||||||
 | 
					    CURRENT_STATE.rpm = setRpm(math.abs(rpm))
 | 
				
			||||||
 | 
					    -- print("Set RPM to " .. tostring(CURRENT_STATE.rpm))
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function isFloorContactActive(floor)
 | 
				
			||||||
 | 
					    return peripheral.call(floor.contactRedstone, "getInput", "back")
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Determines the label of the floor we're currently on.
 | 
				
			||||||
 | 
					-- We first check all known floors to see if the elevator is at one.
 | 
				
			||||||
 | 
					-- If that fails, the elevator is at an unknown position, so we move it as soon as possible to top.
 | 
				
			||||||
 | 
					local function determineCurrentFloorLabel()
 | 
				
			||||||
 | 
					    for _, floor in pairs(FLOORS) do
 | 
				
			||||||
 | 
					        local status = peripheral.call(floor.contactRedstone, "getInput", "back")
 | 
				
			||||||
 | 
					        if status then return floor.label end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    -- No floor found. Move the elevator to the top.
 | 
				
			||||||
 | 
					    print("Elevator at unknown position, moving to top.")
 | 
				
			||||||
 | 
					    local lastFloor = FLOORS[#FLOORS]
 | 
				
			||||||
 | 
					    setSpeed(256)
 | 
				
			||||||
 | 
					    local elapsedTime = 0
 | 
				
			||||||
 | 
					    while not isFloorContactActive(lastFloor) and elapsedTime < 10 do
 | 
				
			||||||
 | 
					        os.sleep(1)
 | 
				
			||||||
 | 
					        elapsedTime = elapsedTime + 1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    setSpeed(0)
 | 
				
			||||||
 | 
					    if not isFloorContactActive(lastFloor) then
 | 
				
			||||||
 | 
					        print("Timed out. Moving down until we hit the top floor.")
 | 
				
			||||||
 | 
					        setSpeed(-1)
 | 
				
			||||||
 | 
					        while not isFloorContactActive(lastFloor) do
 | 
				
			||||||
 | 
					            -- Busy-wait until we hit the contact.
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        setSpeed(0)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return lastFloor.label
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Computes a series of keyframes describing the linear motion of the elevator.
 | 
				
			||||||
 | 
					local function computeLinearMotion(distance)
 | 
				
			||||||
 | 
					    local preFrames = {}
 | 
				
			||||||
 | 
					    local postFrames = {}
 | 
				
			||||||
 | 
					    local intervalDuration = 0.25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local distanceToCover = distance
 | 
				
			||||||
 | 
					    local rpmFactor = 1
 | 
				
			||||||
 | 
					    while rpmFactor * CONTROL_BASE_RPM < CONTROL_MAX_RPM do
 | 
				
			||||||
 | 
					        --print("Need to cover " .. distanceToCover .. " more meters.")
 | 
				
			||||||
 | 
					        local rpm = CONTROL_BASE_RPM * rpmFactor
 | 
				
			||||||
 | 
					        local potentialDistanceCovered = 2 * intervalDuration * rpmToBps(rpm)
 | 
				
			||||||
 | 
					        local nextRpmFactorDuration = (distanceToCover - potentialDistanceCovered) / rpmToBps(CONTROL_BASE_RPM * (rpmFactor + 1))
 | 
				
			||||||
 | 
					        --print("We'd cover " .. potentialDistanceCovered .. " by moving at " .. rpm .. " rpm for " .. intervalDuration .. " seconds twice.")
 | 
				
			||||||
 | 
					        if potentialDistanceCovered <= distanceToCover and nextRpmFactorDuration >= 2 then
 | 
				
			||||||
 | 
					            local frame = {
 | 
				
			||||||
 | 
					                rpm = rpm,
 | 
				
			||||||
 | 
					                duration = intervalDuration
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            table.insert(preFrames, frame)
 | 
				
			||||||
 | 
					            table.insert(postFrames, 1, frame)
 | 
				
			||||||
 | 
					            distanceToCover = distanceToCover - potentialDistanceCovered
 | 
				
			||||||
 | 
					            rpmFactor = rpmFactor * 2
 | 
				
			||||||
 | 
					        elseif nextRpmFactorDuration < 2 then
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- Cover the remaining distance with the next rpmFactor.
 | 
				
			||||||
 | 
					    local finalRpm = CONTROL_BASE_RPM * rpmFactor
 | 
				
			||||||
 | 
					    local finalDuration = distanceToCover / rpmToBps(finalRpm)
 | 
				
			||||||
 | 
					    local finalFrame = {
 | 
				
			||||||
 | 
					        rpm = finalRpm,
 | 
				
			||||||
 | 
					        duration = finalDuration
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    local frames = {}
 | 
				
			||||||
 | 
					    for _, frame in pairs(preFrames) do table.insert(frames, frame) end
 | 
				
			||||||
 | 
					    table.insert(frames, finalFrame)
 | 
				
			||||||
 | 
					    for _, frame in pairs(postFrames) do table.insert(frames, frame) end
 | 
				
			||||||
 | 
					    return frames
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Moves the elevator from its current floor to the floor with the given label.
 | 
				
			||||||
 | 
					-- During this action, all user input is ignored.
 | 
				
			||||||
 | 
					local function goToFloor(floorLabel)
 | 
				
			||||||
 | 
					    print("Going to floor " .. floorLabel)
 | 
				
			||||||
 | 
					    local currentFloorLabel = determineCurrentFloorLabel()
 | 
				
			||||||
 | 
					    if currentFloorLabel == floorLabel then return end
 | 
				
			||||||
 | 
					    local currentFloor = FLOORS_BY_LABEL[currentFloorLabel]
 | 
				
			||||||
 | 
					    local targetFloor = FLOORS_BY_LABEL[floorLabel]
 | 
				
			||||||
 | 
					    local rpmDir = 1
 | 
				
			||||||
 | 
					    if targetFloor.height < currentFloor.height then
 | 
				
			||||||
 | 
					        rpmDir = -1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local distance = math.abs(targetFloor.height - currentFloor.height) - 1
 | 
				
			||||||
 | 
					    local motionKeyframes = computeLinearMotion(distance)
 | 
				
			||||||
 | 
					    playChime(currentFloor)
 | 
				
			||||||
 | 
					    closeDoor(currentFloor)
 | 
				
			||||||
 | 
					    for _, frame in pairs(motionKeyframes) do
 | 
				
			||||||
 | 
					        local sleepTime = math.floor((frame.duration - 0.05) * 20) / 20 -- Make sure we round down to safely arrive before the detector.
 | 
				
			||||||
 | 
					        if frame.rpm == CONTROL_MAX_RPM then
 | 
				
			||||||
 | 
					            sleepTime = sleepTime - 0.05 -- For some reason at max RPM this is needed.
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        print("Running frame: rpm = " .. tostring(frame.rpm) .. ", dur = " .. tostring(sleepTime))
 | 
				
			||||||
 | 
					        setSpeed(rpmDir * frame.rpm)
 | 
				
			||||||
 | 
					        os.sleep(sleepTime)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- On approach, slow down, wait for contact, then slowly align and stop.
 | 
				
			||||||
 | 
					    setSpeed(rpmDir * 1)
 | 
				
			||||||
 | 
					    print("Waiting for floor contact capture...")
 | 
				
			||||||
 | 
					    local waited = false
 | 
				
			||||||
 | 
					    while not isFloorContactActive(targetFloor) do
 | 
				
			||||||
 | 
					        waited = true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    print("Contact made.")
 | 
				
			||||||
 | 
					    if waited then
 | 
				
			||||||
 | 
					        print("Aligning...")
 | 
				
			||||||
 | 
					        local alignmentDuration = 0.4 / rpmToBps(CONTROL_BASE_RPM)
 | 
				
			||||||
 | 
					        os.sleep(alignmentDuration)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    setSpeed(0)
 | 
				
			||||||
 | 
					    print("Locked")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    playChime(targetFloor)
 | 
				
			||||||
 | 
					    openDoor(targetFloor)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function initControls()
 | 
				
			||||||
 | 
					    print("Initializing control system.")
 | 
				
			||||||
 | 
					    setSpeed(0)
 | 
				
			||||||
 | 
					    local currentFloorLabel = determineCurrentFloorLabel()
 | 
				
			||||||
 | 
					    local currentFloor = FLOORS_BY_LABEL[currentFloorLabel]
 | 
				
			||||||
 | 
					    for _, floor in pairs(FLOORS) do
 | 
				
			||||||
 | 
					        openDoor(floor)
 | 
				
			||||||
 | 
					        os.sleep(0.05)
 | 
				
			||||||
 | 
					        closeDoor(floor)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    openDoor(currentFloor)
 | 
				
			||||||
 | 
					    print("Control system initialized.")
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--[[
 | 
				
			||||||
 | 
					    User Interface Section
 | 
				
			||||||
 | 
					]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function drawText(monitor, x, y, text, fg, bg)
 | 
				
			||||||
 | 
					    if fg ~= nil then
 | 
				
			||||||
 | 
					        monitor.setTextColor(fg)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    if bg ~= nil then
 | 
				
			||||||
 | 
					        monitor.setBackgroundColor(bg)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    monitor.setCursorPos(x, y)
 | 
				
			||||||
 | 
					    monitor.write(text)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function drawTextCentered(monitor, x, y, text, fg, bg)
 | 
				
			||||||
 | 
					    local w, h = monitor.getSize()
 | 
				
			||||||
 | 
					    drawText(monitor, x - (string.len(text) / 2), y, text, fg, bg)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function clearLine(monitor, line, color)
 | 
				
			||||||
 | 
					    monitor.setBackgroundColor(color)
 | 
				
			||||||
 | 
					    monitor.setCursorPos(1, line)
 | 
				
			||||||
 | 
					    monitor.clearLine()
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function drawGui(floor, currentFloorLabel, destinationFloorLabel)
 | 
				
			||||||
 | 
					    local monitor = peripheral.wrap(floor.monitor)
 | 
				
			||||||
 | 
					    monitor.setTextScale(1)
 | 
				
			||||||
 | 
					    monitor.setBackgroundColor(colors.black)
 | 
				
			||||||
 | 
					    monitor.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local w, h = monitor.getSize()
 | 
				
			||||||
 | 
					    clearLine(monitor, 1, colors.blue)
 | 
				
			||||||
 | 
					    drawText(monitor, 1, 1, SYSTEM_NAME, colors.white, colors.blue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i=1, #FLOOR_LABELS_ORDERED do
 | 
				
			||||||
 | 
					        local label = FLOOR_LABELS_ORDERED[#FLOOR_LABELS_ORDERED - i + 1]
 | 
				
			||||||
 | 
					        local floor = FLOORS_BY_LABEL[label]
 | 
				
			||||||
 | 
					        local bg = colors.lightGray
 | 
				
			||||||
 | 
					        if i % 2 == 0 then bg = colors.gray end
 | 
				
			||||||
 | 
					        local line = i + 1
 | 
				
			||||||
 | 
					        clearLine(monitor, line, bg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        local labelBg = bg
 | 
				
			||||||
 | 
					        if label == currentFloorLabel and destinationFloorLabel == nil then
 | 
				
			||||||
 | 
					            labelBg = colors.green
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        if label == destinationFloorLabel then
 | 
				
			||||||
 | 
					            labelBg = colors.yellow
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        -- Format label with padding.
 | 
				
			||||||
 | 
					        label = " " .. label
 | 
				
			||||||
 | 
					        while string.len(label) < 3 do label = label .. " " end
 | 
				
			||||||
 | 
					        drawText(monitor, 1, line, label, colors.white, labelBg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        drawText(monitor, 4, line, floor.name, colors.white, bg)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function drawCallMonitorGui(floor, currentFloorLabel, destinationFloorLabel)
 | 
				
			||||||
 | 
					    local monitor = peripheral.wrap(floor.callMonitor)
 | 
				
			||||||
 | 
					    monitor.setTextScale(0.5)
 | 
				
			||||||
 | 
					    monitor.setBackgroundColor(colors.white)
 | 
				
			||||||
 | 
					    monitor.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local w, h = monitor.getSize()
 | 
				
			||||||
 | 
					    if destinationFloorLabel == floor.label then
 | 
				
			||||||
 | 
					        drawTextCentered(monitor, w/2, h/2, "Arriving", colors.green, colors.white)
 | 
				
			||||||
 | 
					    elseif destinationFloorLabel ~= nil then
 | 
				
			||||||
 | 
					        drawTextCentered(monitor, w/2, h/2, "In transit", colors.yellow, colors.white)
 | 
				
			||||||
 | 
					    elseif floor.label == currentFloorLabel then
 | 
				
			||||||
 | 
					        drawTextCentered(monitor, w/2, h/2, "Available", colors.green, colors.white)
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        drawTextCentered(monitor, w/2, h/2, "Call", colors.blue, colors.white)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function renderMonitors(currentFloorLabel, destinationFloorLabel)
 | 
				
			||||||
 | 
					    for _, floor in pairs(FLOORS) do
 | 
				
			||||||
 | 
					        drawGui(floor, currentFloorLabel, destinationFloorLabel)
 | 
				
			||||||
 | 
					        drawCallMonitorGui(floor, currentFloorLabel, destinationFloorLabel)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function initUserInterface()
 | 
				
			||||||
 | 
					    local currentFloorLabel = determineCurrentFloorLabel()
 | 
				
			||||||
 | 
					    renderMonitors(currentFloorLabel, nil)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function listenForInput()
 | 
				
			||||||
 | 
					    local event, peripheralId, x, y = os.pullEvent("monitor_touch")
 | 
				
			||||||
 | 
					    for _, floor in pairs(FLOORS) do
 | 
				
			||||||
 | 
					        if floor.monitor == peripheralId then
 | 
				
			||||||
 | 
					            if y > 1 and y <= #FLOORS + 1 then
 | 
				
			||||||
 | 
					                local floorIndex = #FLOOR_LABELS_ORDERED - (y - 1) + 1
 | 
				
			||||||
 | 
					                local label = FLOOR_LABELS_ORDERED[floorIndex]
 | 
				
			||||||
 | 
					                print("y = " .. tostring(y) .. ", floorIndex = " .. floorIndex .. ", label = " .. label)
 | 
				
			||||||
 | 
					                local currentFloorLabel = determineCurrentFloorLabel()
 | 
				
			||||||
 | 
					                if label ~= currentFloorLabel then
 | 
				
			||||||
 | 
					                    renderMonitors(currentFloorLabel, label)
 | 
				
			||||||
 | 
					                    goToFloor(label)
 | 
				
			||||||
 | 
					                    renderMonitors(label, nil)
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        elseif floor.callMonitor == peripheralId then
 | 
				
			||||||
 | 
					            local currentFloorLabel = determineCurrentFloorLabel()
 | 
				
			||||||
 | 
					            if floor.label ~= currentFloorLabel then
 | 
				
			||||||
 | 
					                renderMonitors(currentFloorLabel, floor.label)
 | 
				
			||||||
 | 
					                goToFloor(floor.label)
 | 
				
			||||||
 | 
					                renderMonitors(floor.label, nil)
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--[[
 | 
				
			||||||
 | 
					    Main Script Area.
 | 
				
			||||||
 | 
					]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					initControls()
 | 
				
			||||||
 | 
					initUserInterface()
 | 
				
			||||||
 | 
					while true do
 | 
				
			||||||
 | 
					    listenForInput()
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,71 @@
 | 
				
			||||||
 | 
					local CONTROL_BASE_RPM = 16
 | 
				
			||||||
 | 
					local CONTROL_MAX_RPM = 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Converts an RPM speed to a blocks-per-second speed.
 | 
				
			||||||
 | 
					local function rpmToBps(rpm)
 | 
				
			||||||
 | 
					    return (10 / 256) * rpm
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Computes a series of keyframes describing the linear motion of the elevator.
 | 
				
			||||||
 | 
					local function computeLinearMotion(distance)
 | 
				
			||||||
 | 
					    local preFrames = {}
 | 
				
			||||||
 | 
					    local postFrames = {}
 | 
				
			||||||
 | 
					    local intervalDuration = 0.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- Linear motion calculation
 | 
				
			||||||
 | 
					    local v1Dist = 2 * intervalDuration * rpmToBps(CONTROL_BASE_RPM)
 | 
				
			||||||
 | 
					    local v2Dist = 2 * intervalDuration * rpmToBps(CONTROL_BASE_RPM * 2)
 | 
				
			||||||
 | 
					    local v3Dist = 2 * intervalDuration * rpmToBps(CONTROL_BASE_RPM * 4)
 | 
				
			||||||
 | 
					    local v4Dist = 2 * intervalDuration * rpmToBps(CONTROL_BASE_RPM * 8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local distanceToCover = distance
 | 
				
			||||||
 | 
					    local rpmFactor = 1
 | 
				
			||||||
 | 
					    while rpmFactor * CONTROL_BASE_RPM < CONTROL_MAX_RPM do
 | 
				
			||||||
 | 
					        print("Need to cover " .. distanceToCover .. " more meters.")
 | 
				
			||||||
 | 
					        local rpm = CONTROL_BASE_RPM * rpmFactor
 | 
				
			||||||
 | 
					        local potentialDistanceCovered = 2 * intervalDuration * rpmToBps(rpm)
 | 
				
			||||||
 | 
					        local nextRpmFactorDuration = (distanceToCover - potentialDistanceCovered) / rpmToBps(CONTROL_BASE_RPM * (rpmFactor + 1))
 | 
				
			||||||
 | 
					        print("We'd cover " .. potentialDistanceCovered .. " by moving at " .. rpm .. " rpm for " .. intervalDuration .. " seconds twice.")
 | 
				
			||||||
 | 
					        if potentialDistanceCovered <= distanceToCover and nextRpmFactorDuration >= 2 then
 | 
				
			||||||
 | 
					            local frame = {
 | 
				
			||||||
 | 
					                rpm = rpm,
 | 
				
			||||||
 | 
					                duration = intervalDuration
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            table.insert(preFrames, frame)
 | 
				
			||||||
 | 
					            table.insert(postFrames, 1, frame)
 | 
				
			||||||
 | 
					            distanceToCover = distanceToCover - potentialDistanceCovered
 | 
				
			||||||
 | 
					            rpmFactor = rpmFactor * 2
 | 
				
			||||||
 | 
					        elseif nextRpmFactorDuration < 2 then
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- Cover the remaining distance with the next rpmFactor.
 | 
				
			||||||
 | 
					    local finalRpm = CONTROL_BASE_RPM * rpmFactor
 | 
				
			||||||
 | 
					    local finalDuration = distanceToCover / rpmToBps(finalRpm)
 | 
				
			||||||
 | 
					    local finalFrame = {
 | 
				
			||||||
 | 
					        rpm = finalRpm,
 | 
				
			||||||
 | 
					        duration = finalDuration
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    local frames = {}
 | 
				
			||||||
 | 
					    for _, frame in pairs(preFrames) do table.insert(frames, frame) end
 | 
				
			||||||
 | 
					    table.insert(frames, finalFrame)
 | 
				
			||||||
 | 
					    for _, frame in pairs(postFrames) do table.insert(frames, frame) end
 | 
				
			||||||
 | 
					    return frames
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function printFrames(frames)
 | 
				
			||||||
 | 
					    for _, frame in pairs(frames) do
 | 
				
			||||||
 | 
					        print("Frame: rpm = " .. tostring(frame.rpm) .. ", duration = " .. tostring(frame.duration))
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local frames = computeLinearMotion(5)
 | 
				
			||||||
 | 
					printFrames(frames)
 | 
				
			||||||
 | 
					local dist = 0
 | 
				
			||||||
 | 
					for _, frame in pairs(frames) do
 | 
				
			||||||
 | 
					    dist = dist + rpmToBps(frame.rpm) * frame.duration
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					print(dist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(0.15 % 0.05 == 0)
 | 
				
			||||||
		Loading…
	
		Reference in New Issue