Skip to content

Instantly share code, notes, and snippets.

@sorcerykid
Created January 12, 2025 16:42
Show Gist options
  • Save sorcerykid/c22f7463a83abe382c2ee13cdb20c702 to your computer and use it in GitHub Desktop.
Save sorcerykid/c22f7463a83abe382c2ee13cdb20c702 to your computer and use it in GitHub Desktop.
Super Mario demo for BearLibTerminal 2.0 + HexLib API
require "hexlib"
------------------------------------
local Mushroom, Fireflower, Superstar, Goomba, KoopaTroopa
local enemies = { }
local def = { }
local ref = { }
local core = { }
local game = { }
local timer_queue = { }
local slow_count = 0
local fast_count = 0
local origin = 0
local mario
local map
local hud
-- generic tiles
local A1 = 0xE000 -- ground
local A2 = 0xE001 -- brick w/ light
local A3 = 0xE002 -- brick w/ shade
local A4 = 0xE003 -- steel block
local A9 = 0xE008 -- solid block
local B10 = 0xE029
local B11 = 0xE02A
local B12 = 0xE02B
local B21 = 0xE034 -- hill top
local B28 = 0xE03B -- hill left
local B29 = 0xE03C -- hill tree 1
local B30 = 0xE03D -- hill center
local B31 = 0xE03E -- hill tree 2
local B32 = 0xE03F -- hill right
local C1 = 0xE044 -- bonus block [x3]
local D1 = 0xE050 -- cloud upper left
local D2 = 0xE051 -- cloud upper center
local D3 = 0xE052 -- cloud upper right
local D6 = 0xE055 -- cloud lower left
local D7 = 0xE056 -- cloud lower center
local D8 = 0xE057 -- cloud lower right
-- virtual tiles
local X1 = 0xF000 -- bonus block (coin)
local X2 = 0xF001 -- bonus block (mush)
local X3 = 0xF002 -- bonus block (fire)
local X4 = 0xF003 -- bonus block (star)
local X5 = 0xF004 -- bonus block (multicoin)
local Z1 = 0xF101 -- goomba
local Z2 = 0xF102 -- koopatroopa
def.cells = { {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, D1, D2, D3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, D6, D7, D8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, D1, D2, D2, D3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, A2, X1, A2, X2, A2, A2, 0, 0, 0, 0, 0, D6, D7, D7, D8, 0, 0, 0, 0, 0, 0, X4, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Z2, 0 },
{ 0, A2, A2, 0, 0, X5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, X3, 0, 0, 0, 0, A2, X1, A2, X1, A2, 0 },
{ 0, 0, B21, 0, 0, 0, 0, A2, 0, 0, 0, A4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, B28, B29, B32, 0, 0, 0, 0, 0, 0, 0, A9, A9, 0, 0, 0, B21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ A2, B29, B30, B31, B32, 0, Z2, 0, 0, 0, 0, B10, B11, B11, B12, B28, B29, B32, 0, Z1, 0, 0, 0, 0, 0, A1 },
{ A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, 0, 0, A1, A1, A1 },
{ A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, 0, 0, A1, A1, A1 },
}, {
{ A9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, A9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ A9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, A9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ A9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, Z2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1 },
{ A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1 },
}, {
{ A9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, A9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, A9, A9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, A9, A9, A9, 0, 0, 0, Z1, 0, Z1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, A1 },
{ A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1 },
{ A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1 },
} }
----------------
-- core.lua
----------------
core.get_free_sprite = function ()
for i, v in ipairs(hex.sprite) do
if not v.owner then
return v
end
end
end
core.timeout = function (wait, func)
table.insert(timer_queue, function ( )
wait = wait - 1
if wait == 0 then
func()
return true
end
return false
end)
end
core.hash_id = function (x, y)
return y * 500 + x
end
core.poke = function (x, y, dx, dy, code)
local z = math.floor((x - 1) / 26) + 1
local x2 = (x - 1) % 26 + 1
return map[z].poke_ext(x2 * 2 - 1, y * 2 - 1, dx, dy, code)
end
------------------
local function BlockBump(code, x, y, keep)
local bump = 4
local dy = 0
if not keep then
ref.cells[y][x] = code
end
return function ()
bump = bump - 1
dy = dy - bump
core.poke(x, y, 0, dy, code, keep)
return dy == 0
end
end
local brick = {
code = A2,
state = { },
on_punch = function (state, code, x, y)
table.insert(timer_queue, BlockBump(code, x, y))
return true
end,
}
local bonus_coin = {
code = C1,
state = { },
on_punch = function (state, code, x, y)
local jump = 12
local coin = core.get_free_sprite()
coin.poke(0xE209, true, false)
coin.moveto(x * 32 - 16, y * 32 - 16)
coin.bind(2)
coin.lock(true)
table.insert(timer_queue,
BlockBump(A4, x, y))
table.insert(timer_queue, function ()
jump = jump - 1
if jump > -6 then
coin.moveby(0, -jump)
return false
else
coin.poke(0)
return true
end
end)
game.score = game.score + 200
game.coins = game.coins + 1
game.print_score()
game.print_coins()
return true
end,
}
local bonus_multicoin = {
code = C1,
state = { },
on_punch = function (state, code, x, y)
local jump = 12
local coin = core.get_free_sprite()
local i = core.hash_id(x, y)
coin.poke(0xE209, true, false)
coin.moveto(x * 32 - 16, y * 32 - 16)
coin.bind(2)
coin.lock(true)
state[i] = not state[i] and 1 or state[i] + 1
if state[i] < 5 then
table.insert(timer_queue, BlockBump(code, x, y, true))
else
state[i] = nil
table.insert(timer_queue, BlockBump(A4, x, y, false))
end
table.insert(timer_queue, function ()
jump = jump - 1
if jump > -6 then
coin.bind(3)
coin.moveby(0, -jump)
return false
else
coin.poke(0)
return true
end
end)
game.score = game.score + 200
game.coins = game.coins + 1
game.print_score()
game.print_coins()
return true
end,
}
local bonus_mushroom = {
code = C1,
state = { },
on_punch = function (state, code, x, y)
local mush = Mushroom(x * 32 - 16, y * 32 - 16)
table.insert(timer_queue, BlockBump(A4, x, y))
table.insert(timer_queue, mush.on_update)
return true
end,
}
local bonus_fireflower = {
code = C1,
state = { },
on_punch = function (state, code, x, y)
local fire = Fireflower(x * 32 - 16, y * 32 - 16)
table.insert(timer_queue, BlockBump(A4, x, y))
table.insert(timer_queue, fire.on_update)
return true
end,
}
local bonus_superstar = {
code = C1,
state = { },
on_punch = function (state, code, x, y)
local star = Superstar(x * 32 - 16, y * 32 - 16)
table.insert(timer_queue, BlockBump(A4, x, y))
table.insert(timer_queue, star.on_update)
return true
end,
}
local enemy_goomba = {
type = "mob",
on_spawn = function (code, x, y)
local mob = Goomba(x * 32 - 16, y * 32)
table.insert(timer_queue, mob.on_update)
end,
}
local enemy_koopatroopa = {
type = "mob",
on_spawn = function (code, x, y)
local mob = KoopaTroopa(x * 32 - 16, y * 32)
table.insert(timer_queue, mob.on_update)
end,
}
local function GenericBlock( code )
return { code = code }
end
def.nodes = {
[A1] = GenericBlock(A1),
[A2] = brick,
[A4] = GenericBlock(A4),
[A9] = GenericBlock(A9),
[X1] = bonus_coin,
[X2] = bonus_mushroom,
[X3] = bonus_fireflower,
[X4] = bonus_superstar,
[X5] = bonus_multicoin,
}
def.mobs = {
[Z1] = enemy_goomba,
[Z2] = enemy_koopatroopa,
}
def.slow_anim = {
[C1] = { 1, 2, 3, 2 }, -- bonus block
[0xE300] = { 1, 2 }, -- goomba
[0xE308] = { 1, 2 }, -- koopatroopa
}
def.fast_anim = {
[0xE101] = { 1, 2, 3, 2 }, -- player
[0xE201] = { 1, 2, 3, 4 }, -- fireflower
[0xE205] = { 1, 2, 3, 4 }, -- superstar
[0xE209] = { 1, 2, 3, 4 }, -- coin toss
}
-------------------
-- prototypes
-------------------
function Fireflower(x, y)
local self = { }
local m = core.get_free_sprite()
local pos = m.state.origin
local yt = y - 16
local grow = true
self.on_update = function ()
if grow then
m.moveby(0, -1)
if pos.y > yt then
return false -- fully emerged yet?
end
m.bind(3)
grow = false
end
-- check for collisions with player
if mario and mario.intersect(pos.x, pos.y, 12, 28) then
mario.grow()
m.reset()
m.owner = nil
return true
end
return false
end
m.owner = "Fireflower"
m.poke(0xE201, true, false)
m.bind(2)
m.lock(true)
m.moveto(x, y)
return self
end
function Superstar(x, y)
local self = { }
local m = core.get_free_sprite()
local pos = m.state.origin
local walk = 0
local fall = 0
local yt = y - 16
local function collide(x, y)
local code = ref.cells[y] and ref.cells[y][x] or 0
if code == 0 then
return false
end
return def.nodes[code] ~= nil
end
self.on_update = function ()
if walk == 0 then
m.moveby(0, -1)
if pos.y > yt then
return false -- fully emerged yet?
end
m.bind(3)
walk = -3
fall = -6
elseif pos.x < 0 then
m.reset()
m.owner = nil
return true
end
-- check for collisions with player
if mario and mario.intersect(pos.x, pos.y, 12, 28) then
mario.grow()
m.reset()
m.owner = nil
return true
end
-- check for collisions near bottom
local near1 = fall < 0 and -32 or 0
local x1 = math.floor((pos.x - 10) / 32) + 1
local x2 = math.floor((pos.x + 10) / 32) + 1
local y1 = math.floor((pos.y + near1 + fall) / 32) + 1
if collide(x1, y1) or x1 ~= x2 and collide(x2, y1) then
if fall ~= 0 then
-- don't fall into block
-- so move nearby instead
local near2 = fall < 0 and -2 or 0
local yy = math.floor((pos.y - 16) / 32) * 32 + 32 -- at block bottom
local yd = yy + near2 - pos.y
m.moveby(0, yd)
if fall > 0 then
fall = -16
else
fall = 0
end
end
else
-- we're still in freefall
if fall < 10 then
fall = fall + 1 -- gravity
end
m.moveby(0, fall)
end
local near1 = walk > 0 and 16 or - 16
local y1 = math.floor((pos.y - 16 - 13) / 32) + 1
local y2 = math.floor((pos.y - 16 + 15) / 32) + 1
local x1 = math.floor((pos.x + near1 + walk) / 32) + 1
if collide(x1,y1) or y1 ~= y2 and collide(x1,y2) then
-- don't walk into block
-- so move nearby instead
local near2 = walk > 0 and -6 or 6
local xx = math.floor(pos.x / 32) * 32 + 16 -- at block middle
local xd = xx + near2 - pos.x
m.moveby(xd, 0)
walk = -walk -- reverse direction
else
m.moveby(walk, 0)
end
return false
end
m.owner = "Superstar"
m.poke(0xE205, true, false)
m.bind(2)
m.lock(true)
m.moveto(x, y)
return self
end
function Mushroom(x, y)
local self = { }
local m = core.get_free_sprite()
local pos = m.state.origin
local walk = 0
local fall = 0
local yt = y - 16
local function collide(x, y)
local code = ref.cells[y] and ref.cells[y][x] or 0
if code == 0 then
return false
end
return def.nodes[code] ~= nil
end
self.on_update = function ()
if walk == 0 then
m.moveby(0, -1)
if pos.y > yt then
return false -- fully emerged yet?
end
m.bind(3)
walk = 3
elseif pos.x < 0 then
m.reset()
m.owner = nil
return true
end
-- check for collisions with player
if mario and mario.intersect(pos.x, pos.y, 12, 28) then
mario.grow()
m.reset()
m.owner = nil
return true
end
-- check for collisions near bottom
local near1 = fall < 0 and -32 or 0
local x1 = math.floor((pos.x - 10) / 32) + 1
local x2 = math.floor((pos.x + 10) / 32) + 1
local y1 = math.floor((pos.y + near1 + fall) / 32) + 1
if collide(x1, y1) or x1 ~= x2 and collide(x2, y1) then
if fall ~= 0 then
-- don't fall into block
-- so move nearby instead
local near2 = fall < 0 and -2 or 0
local yy = math.floor((pos.y - 16) / 32) * 32 + 32 -- at block bottom
local yd = yy + near2 - pos.y
m.moveby(0, yd)
fall = 0
end
else
-- we're still in freefall
if fall < 10 then
fall = fall + 1 -- gravity
end
m.moveby(0, fall)
end
local near1 = walk > 0 and 16 or - 16
local y1 = math.floor((pos.y - 16 - 13) / 32) + 1
local y2 = math.floor((pos.y - 16 + 15) / 32) + 1
local x1 = math.floor((pos.x + near1 + walk) / 32) + 1
if collide(x1,y1) or y1 ~= y2 and collide(x1,y2) then
-- don't walk into block
-- so move nearby instead
local near2 = walk > 0 and -6 or 6
local xx = math.floor(pos.x / 32) * 32 + 16 -- at block middle
local xd = xx + near2 - pos.x
m.moveby(xd, 0)
walk = -walk -- reverse direction
else
m.moveby(walk, 0)
end
return false
end
m.owner = "Mushroom"
m.poke(0xE200, false, false)
m.bind(2)
m.lock(true)
m.moveto(x, y)
return self
end
function Goomba(x, y)
local self = { }
local m = core.get_free_sprite()
local pos = m.state.origin
local walk = -2
local fall = 0
local function collide(x, y)
local code = ref.cells[y] and ref.cells[y][x] or 0
if code == 0 then
return false
end
return def.nodes[code] ~= nil
end
self.on_update = function ()
if not enemies[self] then return true end
-- check for collisions with player
if mario then
if mario.intersect_top(pos.x, pos.y, 12, 28) then
m.poke(0xE302, false, false)
mario.jump(true)
core.timeout(8, function ()
m.reset()
m.owner = nil
end)
return true
elseif mario.intersect(pos.x, pos.y, 12, 28) then
game.kill_player()
end
end
-- check for collisions near bottom
local near1 = fall < 0 and -32 or 0
local x1 = math.floor((pos.x - 10) / 32) + 1
local x2 = math.floor((pos.x + 10) / 32) + 1
local y1 = math.floor((pos.y + near1 + fall) / 32) + 1
if collide(x1, y1) or x1 ~= x2 and collide(x2, y1) then
if fall ~= 0 then
-- don't fall into block
-- so move nearby instead
local near2 = fall < 0 and -2 or 0
local yy = math.floor((pos.y - 16) / 32) * 32 + 32 -- at block bottom
local yd = yy + near2 - pos.y
m.moveby(0, yd)
fall = 0
end
elseif y1 > 15 then
m.reset()
m.owner = nil
enemies[self] = nil
return true
else
-- we're still in freefall
if fall < 10 then
fall = fall + 1 -- gravity
end
m.moveby(0, fall)
end
local near1 = walk > 0 and 16 or - 16
local y1 = math.floor((pos.y - 16 - 13) / 32) + 1
local y2 = math.floor((pos.y - 16 + 15) / 32) + 1
local x1 = math.floor((pos.x + near1 + walk) / 32) + 1
if collide(x1, y1) or y1 ~= y2 and collide(x1, y2) then
-- don't walk into block
-- so move nearby instead
local near2 = walk > 0 and -6 or 6
local xx = math.floor(pos.x / 32) * 32 + 16 -- at block middle
local xd = xx + near2 - pos.x
m.moveby(xd, 0)
walk = -walk -- reverse direction
elseif x1 < 1 or x1 > 26 then
m.reset()
m.owner = nil
enemies[self] = nil
return true
else
m.moveby(walk, 0)
end
return false
end
self.intersect = function (x, y, radius, height)
local left = x - radius
local right = x + radius
local top = y - height
local bottom = y
if pos.x - 12 < right and pos.x + 12 > left and pos.y - 28 < bottom and pos.y > top then
return true
end
return false
end
self.kill = function ()
fall = -6
m.poke(0xE302, false, false)
m.flop(true)
table.insert(timer_queue, function ()
if fall < 16 then
fall = fall + 1 -- gravity
end
m.moveby(0, fall)
if pos.y > 512 then
m.reset()
m.owner = nil
return true
end
return false
end)
enemies[self] = nil
end
enemies[self] = "Goomba"
m.owner = "Goomba"
m.poke(0xE300, true, false)
m.bind(3)
m.lock(true)
m.moveto(x, y)
return self
end
function KoopaTroopa(x, y)
local self = { }
local m = core.get_free_sprite()
local pos = m.state.origin
local walk = -2
local kick = false
local fall = 0
local function collide(x, y)
local code = ref.cells[y] and ref.cells[y][x] or 0
if code == 0 then
return false
end
return def.nodes[code] ~= nil
end
self.on_update = function ()
if not enemies[self] then return true end
-- check for collisions with player
if mario then
if mario.intersect_top(pos.x, pos.y, 12, 44) then
mario.jump(true)
if not kick then
kick = true
walk = 0
m.poke(0xE30C, false, false)
elseif walk == 0 then
walk = mario.distance(pos.x) > 0 and 15 or -15
else
walk = 0
end
elseif mario.intersect(pos.x, pos.y, 12, 44) then
if walk ~= 0 then
game.kill_player()
else
walk = mario.distance(pos.x) > 0 and 10 or -10
end
end
end
-- check for collisions with enemies (when kicked)
if kick and walk ~= 0 then
for this in pairs(enemies) do
if this ~= self and this.intersect(pos.x, pos.y, 12, 32) then
this.kill()
end
end
end
-- check for collisions near bottom
local near1 = fall < 0 and -32 or 0
local x1 = math.floor((pos.x - 10) / 32) + 1
local x2 = math.floor((pos.x + 10) / 32) + 1
local y1 = math.floor((pos.y + near1 + fall) / 32) + 1
if collide(x1, y1) or x1 ~= x2 and collide(x2, y1) then
if fall ~= 0 then
-- don't fall into block
-- so move nearby instead
local near2 = fall < 0 and -2 or 0
local yy = math.floor((pos.y - 16) / 32) * 32 + 32 -- at block bottom
local yd = yy + near2 - pos.y
m.moveby(0, yd)
fall = 0
end
elseif y1 > 15 then
m.reset()
m.owner = nil
enemies[self] = nil
else
-- we're still in freefall
if fall < 10 then
fall = fall + 1 -- gravity
end
m.moveby(0, fall)
end
local near1 = walk > 0 and 16 or - 16
local y1 = math.floor((pos.y - 16 - 13) / 32) + 1
local y2 = math.floor((pos.y - 16 + 15) / 32) + 1
local x1 = math.floor((pos.x + near1 + walk) / 32) + 1
if collide(x1,y1) or y1 ~= y2 and collide(x1,y2) then
-- don't walk into block
-- so move nearby instead
local near2 = walk > 0 and -6 or 6
local xx = math.floor(pos.x / 32) * 32 + 16 -- at block middle
local xd = xx + near2 - pos.x
m.moveby(xd, 0)
walk = -walk -- reverse direction
m.flip(walk > 0)
elseif x1 < 1 or x1 > 26 then
m.reset()
m.owner = nil
enemies[self] = nil
return true
else
m.moveby(walk, 0)
end
return false
end
self.intersect = function (x, y, radius, height)
local left = x - radius
local right = x + radius
local top = y - height
local bottom = y
if pos.x - 12 < right and pos.x + 12 > left and pos.y - 28 < bottom and pos.y > top then
return true
end
return false
end
self.kill = function ()
fall = -6
m.poke(0xE308, false, false)
m.flop(true)
table.insert(timer_queue, function ()
if fall < 16 then
fall = fall + 1 -- gravity
end
m.moveby(0, fall)
if pos.y > 512 then
m.reset()
m.owner = nil
return true
end
return false
end)
enemies[self] = nil
end
enemies[self] = "KoopaTroopa"
m.owner = "KoopaTroopa"
m.poke(0xE308, true, false)
m.flip(false)
m.bind(3)
m.lock(true)
m.moveto(x, y)
return self
end
local function Mario(x, y)
local self = { }
local p = hex.sprite[1]
local pos = p.state.origin
local face = 1
local walk = 0
local fall = 0
local last_key
local standing = false
local function collide(x, y, action)
local code = ref.cells[y] and ref.cells[y][x] or 0
if code == 0 then
return false
end
local def = def.nodes[code]
if not def then
return false
elseif def[action] then
return def[action](def.state, def.code, x, y)
else
return true
end
end
self.on_update = function ()
-- check for collisions near top or bottom
local near3 = face == 1 and -10 or 10
local near1 = fall < 0 and -32 or 0
local x1 = math.floor((pos.x - near3) / 32) + 1
local x2 = math.floor((pos.x + near3) / 32) + 1
local y1 = math.floor((pos.y + near1 + fall) / 32) + 1
local action = fall < 0 and "on_punch" or "on_stand"
if collide(x1, y1, action) or x1 ~= x2 and collide(x2, y1, action) then
if fall ~= 0 then
-- don't fall into block
-- so move nearby instead
local near2 = fall < 0 and -2 or 0
local yy = math.floor((pos.y - 16) / 32) * 32 + 32 -- at block bottom
local yd = yy + near2 - pos.y
p.moveby(0, yd)
if fall > 0 or fall == -17 then
-- we've just hit the ground
-- or we attempted to jump
if walk ~= 0 then
self.slide()
else
self.stand()
end
end
fall = 0
end
else
-- we're still in freefall
if y1 > 17 then
game.kill_player()
elseif fall < -2 and not hex.key("SPACE") then
fall = fall + 2 -- low jump
elseif fall < 16 then
if fall == 0 then
self.fall( )
end
fall = fall + 1 -- gravity
end
p.moveby(0, fall)
end
if walk ~= 0 then
local near1 = 16 * face
local y1 = math.floor((pos.y - 16 - 13) / 32) + 1
local y2 = math.floor((pos.y - 16 + 15) / 32) + 1
local x1 = math.floor((pos.x + near1 + walk) / 32) + 1
local xp = pos.x + near1 + walk
if collide(x1, y1) or y1 ~= y2 and collide(x1,y2) then
if walk * face > 1 then
-- don't walk into block
-- so move nearby instead
local near2 = 6 * face
local xx = math.floor(pos.x / 32) * 32 + 16 -- at block middle
local xd = xx + near2 - pos.x
game.scroll(pos.x, xd)
p.moveby(xd, 0)
end
if not hex.key(face == 1 and "D" or "A") then
self.stand()
walk = 0
else
walk = face -- keep attempting walk
end
elseif xp < origin then
if walk * face > 1 then
local xd = origin - pos.x + 14
p.moveby(xd, 0)
end
if not hex.key(face == 1 and "D" or "A") then
self.stand()
walk = 0
else
walk = face -- keep attempting walk
end
elseif hex.key(face == 1 and "D" or "A") then
if walk * face < 8 then
walk = walk + face / 2 -- accelerate
end
if standing and walk * face == 4 then
p.poke(0xE101, true, false) -- overcome quick turnaround
end
game.scroll(pos.x, walk)
p.moveby(walk, 0)
else
walk = walk - face / 2 -- decelerate
if walk == 0 then
if fall == 0 then
self.stand()
end
else
game.scroll(pos.x, walk)
p.moveby(walk, 0)
end
end
end
hud.moveto(40,6)
hud.printf("%3d", pos.x)
end
self.distance = function (mx)
return mx - pos.x
end
self.intersect_top = function (mx, my, width2, height)
local left = mx - width2
local right = mx + width2
local top = my - height
if pos.x - 12 < right and pos.x + 12 > left and pos.y - 16 < top and pos.y > top and fall > 0 then
return true
end
return false
end
self.intersect = function (mx, my, width2, height)
local left = mx - width2
local right = mx + width2
local top = my - height
local bottom = my
if pos.x - 12 < right and pos.x + 12 > left and pos.y - 28 < bottom and pos.y > top then
return true
end
return false
end
self.grow = function ()
end
self.kill = function ()
p.poke(0xE106, false, false)
fall = -17
table.insert(timer_queue, function ()
if fall < 16 then
fall = fall + 1 -- gravity
end
p.moveby(0, fall)
return pos.y > 512
end)
end
self.slide = function ()
p.poke(0xE101, true, false)
standing = true
end
self.stand = function ()
p.poke(0xE100, false, false)
standing = true
end
self.fall = function ()
p.poke(0xE105, false, false)
end
self.jump = function (pouncing)
if standing or pouncing then
p.poke(0xE105, false, false)
standing = false
fall = pouncing and -11 or -17
end
end
self.walk_right = function ()
if standing then
if walk < 0 then -- quick turnaround?
p.poke(0xE104, false, false)
else
p.poke(0xE101, true, false)
end
end
p.flip(false)
walk = 1
face = 1
end
self.walk_left = function ()
if standing then
if walk > 0 then -- quick turnaround?
p.poke(0xE104, false, false)
else
p.poke(0xE101, true, false)
end
end
p.flip(true)
walk = -1
face = -1
end
p.owner = "Mario"
p.poke(0xE100, false, false)
p.bind(3)
p.lock(true)
p.moveto(x, y)
return self
end
-------------------
-- game.lua
-------------------
game.step_clock = function ()
if game.time > 0 then
game.time = game.time - 1
game.print_time()
if game.time == 0 and mario then
game.kill_player()
game.lives = game.lives - 1
end
end
end
game.scroll = function (x, xd)
if x + xd > origin + 300 then
origin = origin + xd
tmp.shift(-origin, 0)
map.shift(-origin, 0)
end
end
game.kill_player = function ()
mario.kill()
mario = nil
end
game.print_score = function ()
hud.moveto(3,3)
hud.print_ext(5, 1, game.score, "center")
end
game.print_coins = function ()
hud.moveto(14, 3)
hud.print_ext(5, 1, game.coins, "center")
end
game.print_world = function ()
hud.moveto(26, 3)
hud.printf("%d-%d", game.world[1], game.world[2])
end
game.print_time = function ()
hud.moveto(37, 3)
hud.clear_area(37, 3, 4, 1)
hud.print_ext(4, 1, game.time, "center")
end
game.print_lives = function ()
hud.moveto(46, 3)
hud.print_ext(5, 1, game.lives, "center")
end
game.reset = function (stage, level)
hud.moveto(3,2)
hud.print("SCORE")
hud.moveto(14,2)
hud.print("COINS")
hud.moveto(25,2)
hud.print("WORLD")
hud.moveto(37,2)
hud.print("TIME")
hud.moveto(46,2)
hud.print("LIVES")
game.score = 0
game.coins = 0
game.world = { stage, level }
game.time = 200
game.lives = 3
game.print_score()
game.print_coins()
game.print_world()
game.print_time()
game.print_lives()
end
-------------------
hex.signal.on_update = function (stats, cycle)
if cycle % 10 == 0 then
game.step_clock()
end
if mario then
mario.on_update() -- player update loop
end
if cycle % 6 == 0 then
for k, v in pairs(def.slow_anim) do
local code = k + v[slow_count % #v + 1] - 1
hex.filter_encode(k, code)
end
slow_count = slow_count + 1
end
if cycle % 2 == 0 then
for k, v in pairs(def.fast_anim) do
local code = k + v[fast_count % #v + 1] - 1
hex.filter_encode(k, code)
end
fast_count = fast_count + 1
end
for i = #timer_queue, 1, -1 do
-- work in reverse so we can
-- efficiently remove timers
-- by swapping last element
if timer_queue[i]( ) then
timer_queue[i] = timer_queue[#timer_queue]
timer_queue[#timer_queue] = nil
end
end
end
-------------------
-- init.lua
-------------------
hex.config = {
frame_delay = 40,
layer_limit = 8,
stack_limit = 2,
sprite_limit = 16,
}
hex.open( WindowDef { resizeable = false, size="52x29", cell_size="16x16", page_size = "3x2" } )
hex.pcall( function ()
hex.apply("font", "font8x8.png, size=8x8, resize=16x16, resize-filter=nearest, codepage=codepage.txt")
hex.apply("U+E000", "tiles1.png, size=16x16, resize=32x32, resize-filter=nearest, align=top-left") -- 32 tiles
hex.apply("U+E020", "tiles2.png, size=16x16, resize=32x32, resize-filter=nearest, align=top-left") -- 36 tiles
hex.apply("U+E044", "tiles3.png, size=16x16, resize=32x32, resize-filter=nearest, align=top-left") -- 12 tiles
hex.apply("U+E050", "tiles4.png, size=16x16, resize=32x32, resize-filter=nearest, align=top-left") -- 10 tiles
-- player
hex.apply("U+E100", "tiles6.png, size=16x32, resize=32x64, resize-filter=nearest, align=bottom")
-- friends
hex.apply("U+E200", "tiles7.png, size=16x16, resize=32x32, resize-filter=nearest, align=bottom")
-- enemies
hex.apply("U+E300", "tiles8.png, size=16x16, resize=32x32, resize-filter=nearest, align=bottom") -- 8 tiles
hex.apply("U+E308", "tiles9.png, size=16x24, resize=32x48, resize-filter=nearest, align=bottom") -- 8 tiles
mario = Mario(32, 40)
sky = BasicLayer(1)
tmp = BasicLayer(2)
map = BasicGroup(3, #def.cells)
hud = BasicLayer(8)
hex.bgcolor("#7777EE")
game.reset(1,1)
hud.moveto(40,4)
hud.print("POS1")
for k, v in pairs(def.slow_anim) do
hex.filter_encode(k, k)
end
for k, v in pairs(def.fast_anim) do
hex.filter_encode(k, k)
end
-- copy cells into ref table and add tiles to scene
-- this looks complicated, but it makes sense
ref.cells = { }
local off = 0
local idx = 3
for z = 1, #def.cells do
local page = def.cells[z]
for y = 1, #page do
local row = page[y]
if z == 1 then
ref.cells[y] = { }
end
for x = 1, #row do
local code = row[x]
ref.cells[y][off + x] = code
if code > 0 then
if def.mobs[code] then
def.mobs[code].on_spawn(code, off + x, y)
ref.cells[y][off + x] = 0
elseif def.nodes[code] then
local alias = def.nodes[code].code
local filter = def.slow_anim[code] ~= nil
map[z].poke(x * 2 - 1, y * 2 - 1, alias, filter, false)
else
map[z].poke(x * 2 - 1, y * 2 - 1, code, false, false)
end
end
end
end
map[z].slice(z, 1)
off = off + 26
idx = idx + 1
end
local last_key
hex.queue_event( {
on_keyup = function ( input )
if last_key == input then
last_key = nil
end
end,
on_keydown = function ( input )
if last_key == input then return end -- ignore key repeat
last_key = input
if input == "ESCAPE" then
return true
elseif mario then
if input == "D" then
mario.walk_right()
elseif input == "A" then
mario.walk_left()
elseif input == "SPACE" then
mario.jump(false)
elseif input == "SHIFT" then
mario.crouch()
end
end
end,
} )
end )
hex.close( )
@sorcerykid
Copy link
Author

The MIT License (MIT)

Copyright (c) 2020-2022, Leslie Krause ([email protected])

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

For more details:
https://opensource.org/licenses/MIT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment