git @ Cat's Eye Technologies Etcha / master impl / etcha.lua / src / playfield.lua
master

Tree @master (Download .tar.gz)

playfield.lua @masterraw · history · blame

--
-- Lua mutable playfield.  Extracted from Beturing sources
-- and converted to a more conventional Lua OO style.
--
-- SPDX-FileCopyrightText: In 2012, Chris Pressey, the original author of this work, placed it into the public domain.
-- For more information, please refer to <https://unlicense.org/>
-- SPDX-License-Identifier: Unlicense
--

--
-- to use: ensure LUA_PATH has "thisdir/?.lua", then
--
--    local Playfield = require "playfield.lua"
--    p = Playfield.new()
--
-- to run demo standalone:
--
--     lua -i playfield.lua
--     Playfield_demo()
--

local Playfield = {}

--
-- Private function: pick the appropriate quadrant & translate
--
local pick_quadrant = function(self, x, y)
    if x >  0 and y >  0 then return self.se, x, y end
    if x >  0 and y <= 0 then return self.ne, x, 1-y end
    if x <= 0 and y >  0 then return self.sw, 1-x, y end
    if x <= 0 and y <= 0 then return self.nw, 1-x, 1-y end
end

--
-- Read the symbol at a given position in the playfield
--
function Playfield:peek(x, y)
    local contents, nx, ny = pick_quadrant(self, x, y)
    contents[ny] = contents[ny] or {} -- make sure row exists
    local sym = contents[ny][nx] or " "
    return sym
end

--
-- Write a symbol at a given position in the playfield
--
function Playfield:poke(x, y, sym)
    local contents, nx, ny = pick_quadrant(self, x, y)
    contents[ny] = contents[ny] or {} -- make sure row exists
    contents[ny][nx] = sym
    if not self.min_x or x < self.min_x then self.min_x = x end
    if not self.max_x or x > self.max_x then self.max_x = x end
    if not self.min_y or y < self.min_y then self.min_y = y end
    if not self.max_y or y > self.max_y then self.max_y = y end
end

--
-- Return a string representing the playfield.
--
function Playfield:render(wrapper, start_x, start_y, end_x, end_y)
    wrapper = wrapper or function(v) return v end
    start_x = start_x or self.min_x
    start_y = start_y or self.min_y
    end_x = end_x or self.max_x
    end_y = end_y or self.max_y

    local y = start_y
    local s = ""
    while y <= end_y do
        local x = start_x
        while x <= end_x do
            s = s .. wrapper(self:peek(x, y), x, y)
            x = x + 1
        end
        s = s .. "\n"
        y = y + 1
    end

    return s
end

--
-- Load a multi-line string into a Playfield.
--
function Playfield:load(program_text, transformer, sx, sy)
    local i = 1
    local x = sx or 0
    local y = sy or 0
    local len = string.len(program_text)
    local c
    while i <= len do
        c = program_text:sub(i, i)
        if c == "\n" then
            x = 0
            y = y + 1
        else
            c = transformer(c, x, y)
            if c ~= nil then
                self:poke(x, y, c)
            end
            x = x + 1
        end
        i = i + 1
    end
end

Playfield.new = function()
    local self = {
        nw = {},
        ne = {},
        sw = {},
        se = {},
        min_x = nil,
        min_y = nil,
        max_x = nil,
        max_y = nil
    }
    setmetatable(self, {__index = Playfield})
    return self
end

Playfield_demo = function()
    p = Playfield.new()
    p:poke(0, 0, "*")
    p:poke(1, 0, "/")
    p:poke(1, 1, ">")
    p:poke(0, 1, "v")
    print(p:render())
end

return Playfield