Take a look at this buffer.lua file in the CCPaint repository:
```lua
-- LAST RESORT FIX.
-- THIS BUFFER WILL ENSURE TO FIX THE FLICKERING
-- WITH THE MONITOR BY FIRST DRAWING TO THIS BUFFER.

-- IF THIS DOESN'T HELP THEN YOU ARE DOING SOMETHING WRONG!

-- WARNING: THIS MODULE REDEFINES GLOBAL VARIABLES
-- SUCH AS:
-- *term

-- DO NOT FORGET TO RUN `term.notify()` FUNCTION IN PARALLEL

-- IF THE SCREEN IS EMPTY WITH THE BUFFER
-- THEN YOU FORGOT TO PUT `term.flush()`

-- IF IT DOESN'T WORK WITH `term.flush()`
-- THEN PUT THE `require` FUNCTION A THE TOP.

local aterm = term
term = {}
term.cursorPos = {x = 1, y = 1}
do
    local x, y = aterm.getCursorPos()
    term.cursorPos.x = x
    term.cursorPos.y = y
end
local buffer = {}

---@class blitArgs
---@field [1] string
---@field [2] string
---@field [3] string

---@type blitArgs[]
buffer.data = {}
buffer.width = 0
buffer.height = 0

--- Resizes the canvas centering on the top-left corner.
---@param width integer
---@param height integer
buffer.resize = function (width, height)
    if width == buffer.width and height == buffer.height then
        return
    end
    -- Remember: it's y first, then x
    if height < buffer.height then
        for _ = height+1, buffer.height do
            -- removes the blitArgs at the end of the array to the needed height.
            table.remove(buffer.data)
        end
    elseif height > buffer.height then
        -- appends the blitArgs at the end of the array to the needed height.
        table.insert(buffer.data, {(" "):rep(width), ("0"):rep(width), ("f"):rep(width)})
    end

    if width < buffer.width then
        for _, blitArg in ipairs(buffer.data) do
            blitArg[1] = blitArg[1]:sub(1, width)
            blitArg[2] = blitArg[2]:sub(1, width)
            blitArg[3] = blitArg[3]:sub(1, width)
        end
    elseif width > buffer.width then
        for _, blitArg in ipairs(buffer.data) do
            blitArg[1] = blitArg[1]..(" "):rep(width-buffer.width)
            blitArg[2] = blitArg[2]..("0"):rep(width-buffer.width)
            blitArg[3] = blitArg[3]..("f"):rep(width-buffer.width)
        end
    end
end

---@param str string
---@param fcol string
---@param bcol string
term.blit = function (str, fcol, bcol)
    -- default error check
    if #str ~= #fcol or #str ~= #bcol or #fcol ~= #bcol then
        error("buffer.term.blit: Arguments must be the same length")
    end
    -- little optimisation if the cursor is not even on the screen or too far away.
    if term.cursorPos.y < 1 or term.cursorPos.y > buffer.height or term.cursorPos.x+#str-1 < 1 or term.cursorPos.x > buffer.width then
        return
    end

    local blitArg = buffer.data[term.cursorPos.y]
    -- TODO: Finish this stupid function
    -- (can't believe I wasted hours on this module already)
    blitArg[1] = (blitArg[1]):sub(math.max(1, term.cursorPos.x), term.cursorPos.x)..str
end

---@param x number
---@param y number
term.setCursorPos = function (x, y)
    -- the original term.setCursorPos also
    -- didn't clamp the values, so we will
    -- have to fix it in other functions.
    term.cursorPos.x = math.floor(x)
    term.cursorPos.y = math.ceil(y)
end

---@return integer, integer
term.getCursorPos = function ()
    return term.cursorPos.x, term.cursorPos.y
end
term.getSize = aterm.getSize
term.getCursorBlink = aterm.getCursorBlink
term.setCursorBlink = aterm.setCursorBlink

--- DOES NOT ACTUALLY CLEAR THE TERMINAL.
--- ONLY CLEARS THE BUFFER.
term.clear = function ()
    for _, blitArg in ipairs(buffer.data) do
        blitArg[1] = (" "):rep(buffer.width)
        blitArg[2] = ("0"):rep(buffer.width)
        blitArg[3] = ("f"):rep(buffer.width)
    end
end

--- Flushes the buffer to the screen.
term.flush = function ()
    for y, blitArg in ipairs(buffer.data) do
        aterm.setCursorPos(1, y)
        aterm.blit(blitArg[1], blitArg[2], blitArg[3])
    end
end

--- Notifies the terminal about some events.
--- Make sure to run it within the eventloop and trigger ONLY when the actual terminal resizes.
term.notify = function ()
    local width, height = aterm.getSize()
    if width ~= buffer.width or height ~= buffer.height then
        buffer.resize(width, height)
    end
end

-- why do we return the `term` here? It already modifies the _G, doesn't it?
return term
```