Built-in programs

minecraft
Minecraft in Minecraft (2D)
Type this at the prompt of a computer to copy it onto the medium:
> examples minecraft > edit minecraft

The program
-- Minecraft in Minecraft: a world to explore, mine and build, drawn with gfx.
-- Left click breaks a block, right click places the chosen block.
-- On a monitor (one kind of click): the pickaxe breaks, a block places.
-- The sky follows the time of your world: night falls here too.
local size = gfx.size()
local PW = size.w
local PH = size.h
local T = 8 -- a block is 8 x 8 pixels
local TW = PW // T -- blocks across the screen
local WH = (PH - 14) // T -- rows of blocks; the hotbar is below
local WW = TW * 4 -- the world is four screens wide
local HUD = WH * T + 1 -- first row of pixels of the hotbar
local AIR = 0
local GRASS = 1
local DIRT = 2
local STONE = 3
local SAND = 4
local WOOD = 5
local LEAVES = 6
local PLANKS = 7
local WATER = 8
local COAL = 9
local IRON = 10
local DIAMOND = 11
local BEDROCK = 12
-- Textures: one hex digit per pixel, the colours of term.colors: 0 white,
-- 1 orange, 4 yellow, 5 lime, 6 pink, 7 gray, 8 light gray, 9 cyan,
-- b blue, c brown, d green, f black. "." lets the sky show through.
local TEX = {}
TEX[GRASS] = {"55d5555d", "5ddd5dd5", "dcdcddcd", "cc1ccccc", "ccccc1cc", "c1cccccc", "cccc1ccc", "ccccccc1"}
TEX[DIRT] = {"cc1ccccc", "ccccc1cc", "c1ccccc7", "ccc7cccc", "cccccc1c", "c1cccccc", "cccc1ccc", "7ccccc1c"}
TEX[STONE] = {"87778777", "77787787", "78777777", "77777877", "77877778", "87778777", "77777787", "78777777"}
TEX[SAND] = {"44404444", "44444414", "40444444", "44441444", "44444404", "41444444", "44440444", "44444441"}
TEX[WOOD] = {"cfcccfcc", "cfcccfcc", "ccfccfcc", "ccfcccfc", "cfccccfc", "cfcccfcc", "ccfccfcc", "ccfcccfc"}
TEX[LEAVES] = {"d5dd.dd5", "dd5ddd.d", "5dd.d5dd", "dd5dddd.", ".ddd5d5d", "d5d.ddd5", "dd5dd5dd", "5d.dd.dd"}
TEX[PLANKS] = {"11111111", "1111c111", "cccccccc", "11c11111", "11111111", "111111c1", "cccccccc", "1c111111"}
TEX[WATER] = {"bbbbbbbb", "b33bbb3b", "bbbbbbbb", "bbb33bbb", "bbbbbbbb", "33bbbb33", "bbbbbbbb", "bbb3bbbb"}
TEX[COAL] = {"87778777", "7ff77787", "7fff7777", "777778f7", "778777ff", "8777f777", "77fff787", "78777777"}
TEX[IRON] = {"87778777", "76677787", "77167777", "77777667", "77877716", "86777777", "71677787", "78777777"}
TEX[DIAMOND] = {"87778777", "79977787", "77097777", "77777997", "77877709", "89777777", "70977787", "78777777"}
TEX[BEDROCK] = {"f7ff7f8f", "7ff7fff7", "ff8f7fff", "7fff7f7f", "f7f8ff7f", "ff7fff7f", "7fff8fff", "ff7f7ff7"}
local STARS = {"ffffffff", "ffffff0f", "ffffffff", "ff8fffff", "ffffffff", "fffff8ff", "f0ffffff", "ffffffff"}
local PICKAXE = {".8888...", "8...88..", "....c8..", "...c..8.", "..c....8", ".c......", "c.......", "........"}
local STEVE = {
"cccccccc", "cccccccc", "c666666c", "66666666", "60f66f06", "66666666", "666cc666", "66666666",
"99999999", "69999996", "69999996", "69999996", ".bbbbbb.", ".bbbbbb.", ".bb..bb.", ".77..77.",
}
local SLOTS = {0, DIRT, STONE, SAND, WOOD, PLANKS, LEAVES} -- 0 is the pickaxe
local ORES = {COAL, IRON, DIAMOND}
local world = {}
local top = {} -- surface row of each column
local tree = {} -- columns with a tree
local made = {} -- columns whose blocks exist
local sea = 0
local inv = {}
local slot = 1
local px = 0
local py = 0 -- the feet; the head is the block above
local cx = 0 -- the screen shows columns cx + 1 to cx + TW
local jump = 0
local hang = 0
local night = false
-- The shape of the world is decided at once: the height of each column
-- (hills, and beaches below the sea level) and where trees grow.
local function plan()
sea = math.floor(WH * 0.6)
local s = math.floor(WH * 0.45)
local last = -10
for x = 1, WW do
if math.random(3) == 1 then
s = s + math.random(-1, 1)
end
s = math.max(math.floor(WH * 0.3), math.min(WH - 4, s))
top[x] = s
if x >= 3 and x <= WW - 2 and s < sea and s >= 6 and x - last >= 5 and math.random(4) == 1 then
tree[x] = true
last = x
end
end
end
-- The blocks of a column are only made the first time it is needed, like the
-- chunks of Minecraft: the game starts at once, whatever the size of the world.
local function make(x)
made[x] = true
local s = top[x]
local i = x
for y = 1, WH do
local id = AIR
if y == WH then
id = BEDROCK
elseif y < s then
if y >= sea then
id = WATER
end
elseif y <= s + 2 then
if s >= sea then
id = SAND
elseif y == s then
id = GRASS
else
id = DIRT
end
else
id = STONE
local r = math.random(100)
if r <= 6 then
id = COAL
elseif r <= 9 then
id = IRON
elseif r >= 98 and y >= WH - 3 then
id = DIAMOND
end
end
world[i] = id
i = i + WW -- the same column, one row lower
end
-- the trunk of its tree, and the leaves of the trees next to it
for c = x - 1, x + 1 do
if tree[c] then
local ts = top[c]
for k = 3, 4 do
if world[(ts - k - 1) * WW + x] == AIR then
world[(ts - k - 1) * WW + x] = LEAVES
end
end
if c == x then
for k = 1, 3 do
world[(ts - k - 1) * WW + x] = WOOD
end
world[(ts - 6) * WW + x] = LEAVES
end
end
end
end
local function get(x, y)
if x < 1 or x > WW or y > WH then
return BEDROCK
end
if y < 1 then
return AIR
end
if not made[x] then
make(x)
end
return world[(y - 1) * WW + x]
end
local function set(x, y, id)
world[(y - 1) * WW + x] = id
end
-- The player fits in air and water only.
local function free(x, y)
local id = get(x, y)
return y >= 1 and (id == AIR or id == WATER)
end
-- Draws one block of the world if it is on screen: the sky first, then its texture.
local function drawTile(x, y)
local sx = x - cx
if sx < 1 or sx > TW or y < 1 or y > WH then
return
end
local left = (sx - 1) * T + 1
local up = (y - 1) * T + 1
local id = get(x, y)
if id == AIR or id == LEAVES then
if night then
gfx.image(left, up, STARS)
else
gfx.rect(left, up, T, T, "light_blue", true)
end
end
if id ~= AIR then
gfx.image(left, up, TEX[id])
end
end
local function drawPlayer()
gfx.image((px - cx - 1) * T + 1, (py - 2) * T + 1, STEVE)
end
-- A whole column at once: one rectangle of sky, then only the blocks that are not air.
local function drawColumn(x)
local sx = x - cx
if sx < 1 or sx > TW then
return
end
if not made[x] then
make(x)
end
local left = (sx - 1) * T + 1
if night then
gfx.rect(left, 1, T, WH * T, "black", true)
else
gfx.rect(left, 1, T, WH * T, "light_blue", true)
end
local i = x
for y = 1, WH do
local id = world[i]
if id ~= AIR then
gfx.image(left, (y - 1) * T + 1, TEX[id])
elseif night then
gfx.image(left, (y - 1) * T + 1, STARS)
end
i = i + WW -- one row lower
end
end
local function drawWorld()
for sx = 1, TW do
drawColumn(cx + sx)
end
drawPlayer()
end
-- Moves the player. Near an edge the world slides by one block: gfx.scroll
-- moves the picture, then only the new column is drawn (fast).
local function moveTo(x, y)
local ox = px
local oy = py
px = x
py = y
local d = 0
if px - cx <= TW // 3 and cx > 0 then
d = -1
elseif px - cx > TW - TW // 3 and cx < WW - TW then
d = 1
end
if d ~= 0 then
cx = cx + d
gfx.scroll(-d * T, 0, 1, 1, TW * T, WH * T)
if d > 0 then
drawColumn(cx + TW)
else
drawColumn(cx + 1)
end
end
drawTile(ox, oy)
drawTile(ox, oy - 1)
drawPlayer()
end
local function onGround()
return not free(px, py + 1)
end
local function walk(dx)
local x = px + dx
if free(x, py) and free(x, py - 1) then
moveTo(x, py)
elseif onGround() and free(x, py - 1) and free(x, py - 2) and free(px, py - 2) then
moveTo(x, py - 1) -- steps up one block, like auto-jump
end
end
local function tryJump()
if onGround() or get(px, py) == WATER then
jump = 1
end
end
-- Every 0.15 s: jump, gravity, and day or night.
local function physics()
if jump > 0 then
jump = jump - 1
if free(px, py - 2) then
moveTo(px, py - 1)
end
hang = 1 -- a moment at the top, to land on a block
elseif hang > 0 then
hang = hang - 1
elseif free(px, py + 1) then
moveTo(px, py + 1)
if get(px, py) == WATER then
hang = 1 -- sinks slowly in water
end
end
local t = os.day_time()
local dark = t >= 13000 and t < 23000
if dark ~= night then
night = dark
drawWorld()
end
end
-- Sand falls, like in Minecraft.
local function settle(x, y)
while get(x, y) == SAND and y < WH and free(x, y + 1) and not (x == px and y + 1 >= py - 1 and y + 1 <= py) do
set(x, y + 1, SAND)
set(x, y, AIR)
drawTile(x, y)
drawTile(x, y + 1)
y = y + 1
end
end
local function reach(x, y)
return math.abs(x - px) <= 4 and y >= py - 5 and y <= py + 3
end
local function count(x, n)
gfx.rect(x, HUD + 6, 8, 5, "gray", true)
gfx.text(x, HUD + 6, tostring(math.min(n, 99)), "white")
end
local function drawSlot(i)
local x = 2 + (i - 1) * 22
local id = SLOTS[i]
gfx.rect(x, HUD + 2, 12, 12, "black", true)
if i == slot then
gfx.rect(x, HUD + 2, 12, 12, "white")
end
if id == 0 then
gfx.image(x + 2, HUD + 4, PICKAXE)
else
gfx.image(x + 2, HUD + 4, TEX[id])
count(x + 13, inv[id])
end
end
local function drawHud()
gfx.rect(1, HUD, PW, PH - HUD + 1, "gray", true)
for i = 1, #SLOTS do
drawSlot(i)
end
for i = 1, #ORES do
local x = 2 + #SLOTS * 22 + (i - 1) * 22
gfx.image(x, HUD + 4, TEX[ORES[i]])
count(x + 10, inv[ORES[i]])
end
end
local function mine(x, y)
local id = get(x, y)
if not reach(x, y) or id == AIR or id == WATER or id == BEDROCK then
return
end
local item = id
if id == GRASS then
item = DIRT -- grass gives dirt
end
inv[item] = inv[item] + 1
set(x, y, AIR)
drawTile(x, y)
local k = y - 1
while k >= 1 and get(x, k) == SAND do
settle(x, k)
k = k - 1
end
drawHud()
end
local function place(x, y)
local id = SLOTS[slot]
if id == 0 or inv[id] < 1 or not reach(x, y) or not free(x, y) then
return
end
if x == px and (y == py or y == py - 1) then
return
end
inv[id] = inv[id] - 1
set(x, y, id)
drawTile(x, y)
settle(x, y)
drawSlot(slot)
end
local function choose(i)
local old = slot
slot = i
drawSlot(old)
drawSlot(slot)
end
-- C turns one wood into four planks, like the crafting grid.
local function craft()
if inv[WOOD] > 0 then
inv[WOOD] = inv[WOOD] - 1
inv[PLANKS] = inv[PLANKS] + 4
drawHud()
end
end
local function onKey(e)
if e.name == "key" then
if e.key == "left" then
walk(-1)
elseif e.key == "right" then
walk(1)
elseif e.key == "up" then
tryJump()
end
return
end
local ch = string.lower(e.char)
if ch == "q" or ch == "a" then
walk(-1)
elseif ch == "d" then
walk(1)
elseif ch == " " or ch == "z" or ch == "w" then
tryJump()
elseif ch == "c" then
craft()
else
local n = tonumber(ch)
if n and n >= 1 and n <= #SLOTS then
choose(n)
end
end
end
-- e.px, e.py: the pixel clicked; e.button: 1 left, 2 right.
local function onClick(e)
if e.py >= HUD then
local i = (e.px - 2) // 22 + 1
if i >= 1 and i <= #SLOTS then
choose(i)
end
return
end
local x = cx + (e.px - 1) // T + 1
local y = (e.py - 1) // T + 1
if e.button == 2 or (e.source == "monitor" and SLOTS[slot] ~= 0) then
place(x, y)
else
mine(x, y)
end
end
-- Starts on the grass closest to the middle of the world.
local function spawn()
local mid = WW // 2
local d = 0
while d < mid - 1 and get(mid + d, top[mid + d]) ~= GRASS and get(mid - d, top[mid - d]) ~= GRASS do
d = d + 1
end
px = mid - d
if get(mid + d, top[mid + d]) == GRASS then
px = mid + d
end
py = top[px] - 1
while py > 2 and not (free(px, py) and free(px, py - 1)) do
py = py - 1 -- on top of a tree if there is one
end
cx = math.max(0, math.min(WW - TW, px - TW // 2))
end
-- The title screen: a dirt background and a logo made of pixels.
term.set_bg(term.colors.black)
term.clear()
for y = 1, PH, T do
for x = 1, PW, T do
gfx.image(x, y, TEX[DIRT])
end
end
local logo = (PW - 105) // 2
gfx.text(logo + 2, 8, "MINECRAFT", "black", 3)
gfx.text(logo, 6, "MINECRAFT", "light_gray", 3)
gfx.text(logo + 70, 25, "ALSO IN 2D!", "yellow")
gfx.rect(1, 37, PW, 63, "black", true)
term.set_fg(term.colors.white)
term.set_cursor(2, 5)
term.write("Arrows, Q or D: walk")
term.set_cursor(2, 6)
term.write("Space or Z: jump")
term.set_cursor(2, 7)
term.write("Left click: break a block")
term.set_cursor(2, 8)
term.write("Right click: place the chosen block")
term.set_cursor(2, 9)
term.write("1 to 7: choose, C: craft planks")
term.set_cursor(2, 10)
term.set_fg(term.colors.yellow)
term.write("A key or a click to play")
local started = false
while not started do
local e = os.pull_event()
started = e.name == "key" or e.name == "char" or e.name == "click"
end
term.clear()
term.set_cursor(2, 2)
term.write("Generating the world...")
plan()
term.clear()
for id = 0, BEDROCK do
inv[id] = 0
end
spawn()
gfx.clear("black")
drawWorld()
drawHud()
os.start_timer(0.15)
while true do
local e = os.pull_event()
if e.name == "timer" then
physics()
os.start_timer(0.15)
elseif e.name == "key" or e.name == "char" then
onKey(e)
elseif e.name == "click" then
onClick(e)
end
end