Create: Computing AgesDoc Brass
Programmes intégrés

minecraft

Minecraft 2D dans Minecraft

Tourne sur Micro+ Micro-ordinateur, microcontrôleur, ordinateur moderne · 510 lignes
Tapez ceci à l'invite d'un ordinateur pour le copier sur le support :
Terminal
> examples minecraft
> edit minecraft
Écran
minecraft

Le programme

minecraft
-- Minecraft dans Minecraft : un monde à explorer, miner et bâtir, dessiné avec gfx.
-- Clic gauche : casser un bloc. Clic droit : poser le bloc choisi.
-- Sur un moniteur (un seul clic) : la pioche casse, un bloc se pose.
-- Le ciel suit l'heure de votre monde : la nuit tombe ici aussi.
local size = gfx.size()
local PW = size.w
local PH = size.h
local T = 8  -- un bloc fait 8 x 8 pixels
local TW = PW // T  -- blocs sur la largeur de l'écran
local WH = (PH - 14) // T  -- lignes de blocs ; la barre d'objets est dessous
local WW = TW * 4  -- le monde fait quatre écrans de large
local HUD = WH * T + 1  -- première ligne de pixels de la barre

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 : un chiffre hexa par pixel, les couleurs de term.colors : 0 blanc,
-- 1 orange, 4 jaune, 5 vert clair, 6 rose, 7 gris, 8 gris clair, 9 cyan,
-- b bleu, c marron, d vert, f noir. "." laisse voir le ciel.
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 est la pioche
local ORES = {COAL, IRON, DIAMOND}
local world = {}
local top = {}  -- ligne de la surface de chaque colonne
local tree = {}  -- colonnes avec un arbre
local made = {}  -- colonnes dont les blocs existent
local sea = 0
local inv = {}
local slot = 1
local px = 0
local py = 0  -- les pieds ; la tête est le bloc au-dessus
local cx = 0  -- l'écran montre les colonnes cx + 1 à cx + TW
local jump = 0
local hang = 0
local night = false

-- La forme du monde est décidée d'un coup : la hauteur de chaque colonne
-- (des collines, et des plages sous le niveau de la mer) et où poussent les arbres.
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

-- Les blocs d'une colonne ne sont faits que la première fois qu'on en a besoin,
-- comme les chunks de Minecraft : le jeu démarre tout de suite, quelle que soit la taille du monde.
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  -- la même colonne, une ligne plus bas
  end
  -- le tronc de son arbre, et les feuilles des arbres voisins
  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

-- Le joueur ne passe que dans l'air et l'eau.
local function free(x, y)
  local id = get(x, y)
  return y >= 1 and (id == AIR or id == WATER)
end

-- Dessine un bloc du monde s'il est à l'écran : le ciel d'abord, puis sa 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

-- Une colonne entière d'un coup : un rectangle de ciel, puis seulement les blocs qui ne sont pas de l'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  -- une ligne plus bas
  end
end

local function drawWorld()
  for sx = 1, TW do
    drawColumn(cx + sx)
  end
  drawPlayer()
end

-- Déplace le joueur. Près d'un bord le monde glisse d'un bloc : gfx.scroll
-- décale l'image, puis seule la nouvelle colonne est dessinée (rapide).
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)  -- monte d'un bloc, comme le saut automatique
  end
end

local function tryJump()
  if onGround() or get(px, py) == WATER then
    jump = 1
  end
end

-- Toutes les 0,15 s : saut, gravité, et jour ou nuit.
local function physics()
  if jump > 0 then
    jump = jump - 1
    if free(px, py - 2) then
      moveTo(px, py - 1)
    end
    hang = 1  -- un instant en haut, pour atterrir sur un bloc
  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  -- coule lentement dans l'eau
    end
  end
  local t = os.day_time()
  local dark = t >= 13000 and t < 23000
  if dark ~= night then
    night = dark
    drawWorld()
  end
end

-- Le sable tombe, comme dans 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  -- l'herbe donne de la terre
  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 change un bois en quatre planches, comme la grille d'artisanat.
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 : le pixel cliqué ; e.button : 1 gauche, 2 droit.
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

-- Démarre sur l'herbe la plus proche du milieu du monde.
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  -- sur l'arbre s'il y en a un
  end
  cx = math.max(0, math.min(WW - TW, px - TW // 2))
end

-- L'écran titre : un fond de terre et un logo en 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("Flèches, Q ou D : marcher")
term.set_cursor(2, 6)
term.write("Espace ou Z : sauter")
term.set_cursor(2, 7)
term.write("Clic gauche : casser un bloc")
term.set_cursor(2, 8)
term.write("Clic droit : poser le bloc choisi")
term.set_cursor(2, 9)
term.write("1 à 7 : choisir, C : planches")
term.set_cursor(2, 10)
term.set_fg(term.colors.yellow)
term.write("Une touche ou un clic pour jouer")
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("Création du monde...")
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