This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Dejected's profile picture

Make Computercraft Save And Store The Screen?

Started by Dejected, 22 July 2013 - 12:42 PM
Dejected #1
Posted 22 July 2013 - 02:42 PM
I Was Wondering If There Is A Way To Make Computercraft Save The State Of The Screen And Store It To Show Again Later.
Im Currently Working On A Wireless Controlled Turtle And It Show Where There's Block And Where There's Not And To Be Able To Go Up And Down The Z Level I Would Need To Save The Screen So That When They Go Back To That Level Then You Can Still See Where Everything Is And Not Have To Re Explore The Level You Already Explored.
Grim Reaper #2
Posted 22 July 2013 - 02:47 PM
You'll have to do something called "buffering" the screen.

Basically, you'll have to redirect the terminal API to write to a "buffer". This buffer is most commonly a table of strings which act as the characters on the screen. The buffer also has tables for the text colors and background colors for each of the pixels on the screen.

Here is a buffer file which I've been using to handle windowing:

Pixel
Spoiler

--[[
    Pixel           Trystan Cannon
                    17 July 2013

    Contains the Pixel class which couples with
    the Screen Buffer class in order to simulate
    a buffer for concurrently running programs
    properly.
]]

-- Variables -------------------------------------------
local pixelMetatable = { __index = getfenv (1) }
-- Variables -------------------------------------------

function new (textColor, backColor, character)
    return setmetatable ({
            character = character or ' ',
            textColor = textColor or colors.white,
            backColor = backColor or colors.black
            }, pixelMetatable)
end


Buffer
Spoiler

--[[
	Screen Buffer		   Trystan Cannon
							17 July 2013

	Contains the Screen Buffer class which contains
	the accurate behavior for a buffer when working
	with Windows or multiple threads which require
	their own buffer to read and write from/to when
	running concurrently.
]]



-- Variables -------------------------------------------
local bufferMetatable = { __index = getfenv (1) }
-- Variables -------------------------------------------




-- Returns a new 1d table of pixels with the given width and colors.
-- The colors, by default, will be white and black.
local function newPixelLine (width, textColor, backColor)
	local pixelLine = {}

	for space = 1, width do
		pixelLine[space] = Pixel.new (textColor, backColor)
	end

	return pixelLine
end

-- Returns a new 2d table of pixels with the given dimensions and colors.
-- The colors, by default, will be white and black.
local function newPixelTable (width, height, textColor, backColor)
	local pixels = {}

	for lineNumber = 1, height do
		pixels[lineNumber] = newPixelLine (width, textColor, backColor)
	end

	return pixels
end

-- Returns a table which will allow us to redirect the terminal to use
-- the buffer as a redirection object.
function getRedirectTable (buffer)
	local redirect = {}

	redirect.getSize = function()
		return buffer.width, buffer.height
	end
	redirect.getCursorPos = function()
		return buffer.cursorX, buffer.cursorY
	end
	redirect.setCursorPos = function (x, y)
		buffer.cursorX = math.floor (tostring (x) ~= "nan" and tonumber (x) or buffer.cursorX)
		buffer.cursorY = math.floor (tostring (y) ~= "nan" and tonumber (y) or buffer.cursorY)

		-- Before setting the position of the cursor, make sure that the buffer is visible and the cursor
		-- is within bounds of the buffer's borders.
		if buffer.isVisible then
			term.native.setCursorPos (buffer.cursorX + buffer.xPos - 1, buffer.cursorY + buffer.yPos - 1)
		end
	end
	redirect.setCursorBlink = function (shouldCursorBlink)
		buffer.shouldCursorBlink = shouldCursorBlink

		if buffer.isVisible then
			term.native.setCursorBlink (shouldCursorBlink)
		end
	end

	redirect.isColor = function()
		return buffer.isColor
	end
	redirect.setTextColor = function (color)
		buffer.textColor = color

		if buffer.isVisible then
			return term.native.setTextColor (color)
		end
	end
	redirect.setBackgroundColor = function (color)
		buffer.backColor = color

		if buffer.isVisible then
			return term.native.setBackgroundColor (color)
		end
	end
	redirect.isColour			= redirect.isColor
	redirect.setTextColour   = redirect.setTextColor
	redirect.setBackgroundColour = redirect.setBackgroundColor

	redirect.clearLine = function (lineNumber)
		lineNumber					= lineNumber or buffer.cursorY
		buffer.pixels[buffer.cursorY] = newPixelLine (buffer.width, buffer.textColor, buffer.backColor)

		-- Set the background color to that of the buffer so the colors match when cleared.
		if buffer.isVisible then
			term.native.setBackgroundColor (buffer.backColor)
			for space = 1, buffer.width do
				-- Only clear the space that the buffer is specified to take up on the screen.
				term.native.setCursorPos (space + buffer.xPos - 1, lineNumber + buffer.yPos - 1)
				term.native.write (string.rep (' ', buffer.width))
			end
			-- Reset the native cursor after clearing the line.
			term.native.setCursorPos (buffer.cursorX + buffer.xPos - 1, buffer.cursorY + buffer.yPos - 1)
		end
	end
	redirect.clear = function()
		buffer.pixels = newPixelTable (buffer.width, buffer.height, buffer.textColor, buffer.backColor)

		local currentCursorY = buffer.cursorY
		for lineNumber = 1, buffer.height do
			redirect.clearLine (lineNumber)
		end
	end

	redirect.scroll = function (timesToScroll)
		if timesToScroll > 0 then
			for timesScrolled = 1, timesToScroll do
				for lineNumber = 1, buffer.height - 1 do
					buffer.pixels[lineNumber]	 = buffer.pixels[lineNumber + 1]
					buffer.pixels[lineNumber + 1] = newPixelLine (buffer.width, buffer.textColor, buffer.backColor)
				end
			end
		elseif timesToScroll < 0 then
			for timesScrolled = 1, math.abs (timesToScroll) do
				for lineNumber = buffer.height, 2, -1 do
					buffer.pixels[lineNumber]	 = buffer.pixels[lineNumber - 1]
					buffer.pixels[lineNumber - 1] = newPixelLine (buffer.width, buffer.textColor, buffer.backColor)
				end
			end
		end

		-- If the buffer has a height of one, then the previous code won't have affected the buffer.
		-- Therefore, we should just set the only line in the buffer to a blank line of pixels.
		if buffer.height == 1 then
			buffer.pixels[buffer.height] = newPixelLine (buffer.width, buffer.textColor, buffer.backColor)
		end

		-- Render the buffer now that the buffer has been scrolled.
		-- This will scroll the buffer on the screen without scrolling the entire terminal.
		if buffer.isVisible then
			buffer:render()
		end
	end

	redirect.write = function (text)
		-- Remove escape sequences (control characers) from the string
		-- just as term.write would.
		text = tostring (text):gsub ("\t", " ")
		text = text:gsub ("%c", "?")

		-- Make sure that the cursor is in bounds.
		-- Truncate the string as necessary.
		if buffer.cursorX > buffer.width or buffer.cursorY < 1 or buffer.cursorY > buffer.height then
			buffer.cursorX = buffer.cursorX + text:len()
			return
		end
		if buffer.cursorX < 1 then
			local textLength = text:len()
			text			 = text:sub (math.abs (buffer.cursorX) + 2)

			-- If the cursor still won't reach the inside of the buffer even after truncation,
			-- just update the buffer's cursor and don't attempt to write anything.
			if text:len() == 0 then
				buffer.cursorX = buffer.cursorX + textLength
				return
			end
			buffer.cursorX = 1
		end
		if buffer.cursorX + text:len() - 1 > buffer.width then
			text = text:sub (1, buffer.width - buffer.cursorX + 1)
		end

		-- Write the text to the native terminal.
		if buffer.isVisible then
			term.native.setTextColor (buffer.textColor)
			term.native.setBackgroundColor (buffer.backColor)
			term.native.setCursorPos (buffer.cursorX + buffer.xPos - 1, buffer.cursorY + buffer.yPos - 1)
			term.native.write (text)
		end

		-- Write the text to the buffer character for character into pixels.
		for character in text:gmatch ('.') do
			buffer.pixels[buffer.cursorY][buffer.cursorX] = Pixel.new (buffer.textColor, buffer.backColor, character)
			buffer.cursorX = buffer.cursorX + 1
		end
	end

	return redirect
end

-- Renders the given buffer and returns the terminal's colors and cursor
-- back to their previous states (before rendering).
function render (buffer)
	term.native.setCursorBlink (false)

	-- Define the current colors for drawing pixels without disturbing
	-- the buffers current colors. This way, we can check if we need to
	-- change the colors already set in order to draw the pixel accurately.
	local currentTextColor = buffer.textColor
	local currentBackColor = buffer.backColor

	-- Set the native terminal colors to the current colors that we defined above.
	term.native.setTextColor (currentTextColor)
	term.native.setBackgroundColor (currentBackColor)

	-- Iterate over the 2d pixel table of the buffer and draw all of the pixels
	-- in it.
	for lineNumber = 1, buffer.height do
		for space = 1, buffer.width do
			local pixel = buffer.pixels[lineNumber][space]

			-- Try to avoid setting the text and background colors if
			-- the current text and or background color is already that of
			-- the pixel.
			if currentTextColor ~= pixel.textColor then
				currentTextColor = pixel.textColor
				term.native.setTextColor (currentTextColor)
			end
			if currentBackColor ~= pixel.backColor  then
				currentBackColor = pixel.backColor
				term.native.setBackgroundColor (currentBackColor)
			end

			-- Position the cursor properly for the buffer and draw the pixel for the iteration.
			term.native.setCursorPos (space + buffer.xPos - 1, lineNumber + buffer.yPos - 1)
			term.native.write (pixel.character)
		end
	end

	-- Return the state of the terminal to what it was before we rendered
	-- the buffer.
	term.native.setTextColor (buffer.textColor)
	term.native.setBackgroundColor (buffer.backColor)

	-- Make sure that the cursor is in bounds of the buffer and that the buffer is visible before setting the position and blink state to that
	-- of the the cursor to that of the buffer.
	if buffer.isVisible and buffer.cursorX + buffer.xPos - 1 >= buffer.xPos and buffer.cursorX + buffer.xPos - 1 <= buffer.xPos + buffer.width and
	   buffer.cursorY + buffer.yPos - 1 >= buffer.yPos and buffer.cursorY + buffer.yPos - 1 <= buffer.yPos + buffer.height then

		term.native.setCursorPos (buffer.cursorX + buffer.xPos - 1, buffer.cursorY + buffer.yPos - 1)
		term.native.setCursorBlink (buffer.shouldCursorBlink)
	-- Since the cursor will end up at the lower right hand corner of the window on the screen, we should turn off the cursor
	-- blink so that it doesn't look like the cursor is actually in the lower right corner of the window.
	else
		term.native.setCursorBlink (false)
	end
end

-- Constructs and returns a new Buffer object.
function new (width, height, xPos, yPos, isVisible, isColor)
	if isVisible == nil then
		isVisible = true
	end
	if isColor == nil then
		isColor = true
	end

	local this = {
		width  = width,
		height = height,

		textColor = colors.white,
		backColor = colors.black,

		xPos = xPos or 1,
		yPos = yPos or 1,

		cursorX = 1,
		cursorY = 1,

		shouldCursorBlink = true,

		isVisible = isVisible,
		isColor   = isColor,

		pixels = newPixelTable (width, height)
	}

	setmetatable (this, bufferMetatable)
	return this
end

If you're a stickler for memory management (which you should be :P/>), you could use strings for all of the lines, text color, and background colors. However, you'll need to serialize the colors and write similar functions for the terminal API to write to your buffer and still behave properly.
MKlegoman357 #3
Posted 26 July 2013 - 06:02 PM
Or you can store values in a table.
Dejected #4
Posted 10 September 2013 - 05:25 PM
Or you can store values in a table.
Hey I know you… lol
PixelToast #5
Posted 10 September 2013 - 05:35 PM
the best option for something like that is to save the table containing the data using:

local function savePrefs(name,data) -- saves data to a file
local file=fs.open(name,"w")
file.write(textutils.serialize(data))
file.close()
end
local function readPrefs(name) -- reads data from a file
local file=fs.open(name,"r")
if not file then return false end
local dat
dat=textutils.unserialize(file.readAll())
file.close()
return dat
end
-- reading
local data=readPrefs("filename") or {defaultstuff}
-- writing
savePrefs("filename",data)
Lyqyd #6
Posted 10 September 2013 - 09:27 PM
This shouldn't be dependent on saving the screen, you should save the underlying data that you're using to write to the screen. Much easier to simply re-draw the screen from the data as needed than to buffer the screen unnecessarily.