Created
January 12, 2025 16:42
-
-
Save sorcerykid/c22f7463a83abe382c2ee13cdb20c702 to your computer and use it in GitHub Desktop.
Super Mario demo for BearLibTerminal 2.0 + HexLib API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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( ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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