knucklebones/game.lua
2023-08-12 11:36:14 +02:00

2524 lines
100 KiB
Lua

--
-- Bundle file
-- Code changes will be overwritten
--
-- title: Knucklebones
-- author: Simon Cambier
-- desc: A game of risk and reward, from "The Cult of the Lamb"
-- script: lua
-- Some sprites from https://piiixl.itch.io/frames-1-bit
-- [TQ-Bundler: utils.utils]
-- [TQ-Bundler: globals]
-- [TQ-Bundler: events]
EVENT_CHANGE_TURN = "change_turn"
EVENT_SET_STEP = "set_step"
EVENT_REMOVE_DIE = "remove_die"
EVENT_RESET_DIE = "reset_die"
-- [/TQ-Bundler: events]
trace = function(msg)
end
ALIGN = {
Left = 0,
Center = 1,
Right = 2
}
PALETTE_MAP = 0x3ff0
COLOR_PLAYER = 8
COLOR_ENEMY = 1
dt = 1 / 60
pt = 0
die_spr = {
1, 3, 5, 7, 9, 11
}
--- @class PLACED_DICE
PLACED_DICE = {
player = {
-- Each line represents a column
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
-- { 1, 2, 3 },
-- { 4, 5, 6 },
-- { 1, 2, 0 },
},
enemy = {
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
-- { 4, 5, 6 },
-- { 1, 2, 3 },
-- { 4, 5, 0 },
},
---@param self PLACED_DICE
---@param player 'player'|'enemy'
---@param x number
---@param y number
---@param score number
set = function(self, player, x, y, score)
self[player][x][y] = score
end,
---@param self PLACED_DICE
---@param player 'player'|'enemy'
---@param x number
---@param y number
---@return number
get = function(self, player, x, y)
return self[player][x][y]
end,
---returns true if a column has a free slot
---@param self PLACED_DICE
---@param player 'player'|'enemy'
---@param col number
has_free_slot = function(self, player, col)
return table.count(self[player][col], 0) >= 1
end,
---Returns if one of the boards is full
---@param self PLACED_DICE
is_game_over = function(self)
for _, player in ipairs({ "player", "enemy" }) do
local game_over = true
for col = 1, 3 do
for row = 1, 3 do
if self[player][col][row] == 0 then
game_over = false
end
end
end
if game_over then return true end
end
return false
end
}
--- Score values for each column
--- Must be manually updated
SCORES = {
player = { 0, 0, 0 },
enemy = { 0, 0, 0 },
update = function(self)
local players = { "player", "enemy" }
for _, player in ipairs(players) do
for col = 1, 3 do
local dice_col = PLACED_DICE[player][col]
local score = 0
for row = 1, 3 do
local value = dice_col[row]
local combo = table.count(dice_col, value)
local mul = combo == 2 and 1.5 or combo == 3 and 2 or 1
-- A 4-4-4 combo == 4 + 4*2 + 4*3
-- score = score + value * mul
--- A 4-4-4 combo == 4*3 + 4*3 + 4*3
score = score + combo * value
end
self[player][col] = math.floor(score)
end
end
end
}
-- [/TQ-Bundler: globals]
-- [TQ-Bundler: utils.tables]
--- Finds the first value in a table that satisfies a condition
---@param table any
---@param cb any @function(v, i) return boolean end
---@return unknown @value or nil
---@return unknown @index or nil
function table.find(table, cb)
for i, v in pairs(table) do
if cb(v) then
return v, i
end
end
return nil, nil
end
function table.filter(table, cb)
local new = {}
for _, v in pairs(table) do
if cb(v) then
table.insert(new, v)
end
end
return new
end
function table.remove_item(list, item)
for i, v in pairs(list) do
if v == item then
table.remove(list, i)
return
end
end
end
---needle can be a value or a function
---@param list table
---@param needle function|number
---@return integer
function table.count(list, needle)
local count = 0
for _, v in pairs(list) do
if type(needle) == "function" and needle(v) or v == needle then
count = count + 1
end
end
return count
end
function table.max(list, cb)
local max = -math.huge
for _, v in pairs(list) do
local value = cb(v)
if value > max then
max = value
end
end
return max
end
function math.randomitem(tbl)
return tbl[math.random(#tbl)]
end
function table.random(tbl)
return tbl[math.random(#tbl)]
end
-- [/TQ-Bundler: utils.tables]
-- [TQ-Bundler: utils.rendering]
function draw9box(corner, edge, x, y, width, height)
-- edges
for i = 8, width - 9, 8 do
spr(edge, x + i, y, 0) -- top
spr(edge, x + i, y + height - 8, 0, 1, 0, 2) -- bottom
end
for i = 8, height - 8,8 do
spr(edge, x, y + i, 0, 1, 0, 3) -- left
spr(edge, x + width - 8, y + i, 0, 1, 0, 1) -- right
end
-- corners
spr(corner, x, y, 0) -- top left
spr(corner, x + width - 8, y, 0, 1, 0, 1) -- top right
spr(corner, x, y + height - 8, 0, 1, 0, 3) -- bottom left
spr(corner, x + width - 8, y + height - 8, 0, 1, 0, 2) -- bottom right
end
local function rot(x, y, rad)
local sa = math.sin(rad)
local ca = math.cos(rad)
return x * ca - y * sa, x * sa + y * ca
end
--- Draw a sprite using two textured triangles.
--- Apply affine transformations: scale, shear, rotate, flip
--- https://cxong.github.io/tic-80-examples/affine-sprites
---comment
---@param id number
---@param x number
---@param y number
---@param colorkey? number
---@param sx? number scale x
---@param sy? number scale y
---@param flip? number
---@param rotate? number
---@param w? number
---@param h? number
---@param ox? number
---@param oy? number
---@param shx1? number
---@param shy1? number
---@param shx2? number
---@param shy2? number
function aspr(
id, x, y, colorkey, sx, sy, flip, rotate, w, h,
ox, oy, shx1, shy1, shx2, shy2
)
colorkey = colorkey or -1
sx = sx or 1
sy = sy or 1
flip = flip or 0
rotate = math.rad(rotate or 0)
w = w or 1
h = h or 1
ox = ox or w * 8 // 2
oy = oy or h * 8 // 2
shx1 = shx1 or 0
shy1 = shy1 or 0
shx2 = shx2 or 0
shy2 = shy2 or 0
-- scale / flip
if flip % 2 == 1 then
sx = -sx
end
if flip >= 2 then
sy = -sy
end
ox = ox * -sx
oy = oy * -sy
-- shear / rotate
shx1 = shx1 * -sx
shy1 = shy1 * -sy
shx2 = shx2 * -sx
shy2 = shy2 * -sy
local rx1, ry1 = rot(ox + shx1, oy + shy1, rotate)
local rx2, ry2 = rot(((w * 8) * sx) + ox + shx1, oy + shy2, rotate)
local rx3, ry3 = rot(ox + shx2, ((h * 8) * sy) + oy + shy1, rotate)
local rx4, ry4 = rot(((w * 8) * sx) + ox + shx2, ((h * 8) * sy) + oy + shy2, rotate)
local x1 = x + rx1
local y1 = y + ry1
local x2 = x + rx2
local y2 = y + ry2
local x3 = x + rx3
local y3 = y + ry3
local x4 = x + rx4
local y4 = y + ry4
-- UV coords
local u1 = (id % 16) * 8
local v1 = id // 16 * 8
local u2 = u1 + w * 8
local v2 = v1 + h * 8
ttri(x1, y1, x2, y2, x3, y3, u1, v1, u2, v1, u1, v2, 0, colorkey)
ttri(x3, y3, x4, y4, x2, y2, u1, v2, u2, v2, u2, v1, 0, colorkey)
end
-- [/TQ-Bundler: utils.rendering]
--- Print with outline
function print_border(text, x, y, color, outline, fixed, scale, smallfont)
local outline = outline == nil
and ((color == 0 or color == nil) and 12 or 0)
or outline
-- diagonals
print(text, x + 1, y + 1, outline, fixed, scale, smallfont)
print(text, x + 1, y - 1, outline, fixed, scale, smallfont)
print(text, x - 1, y + 1, outline, fixed, scale, smallfont)
print(text, x - 1, y - 1, outline, fixed, scale, smallfont)
-- cardinals
print(text, x + 1, y, outline, fixed, scale, smallfont)
print(text, x - 1, y, outline, fixed, scale, smallfont)
print(text, x, y + 1, outline, fixed, scale, smallfont)
print(text, x, y - 1, outline, fixed, scale, smallfont)
print(text, x, y, color, fixed, scale, smallfont)
end
--- Print debug
-- function printd(text, x, y)
-- print_border(text, x, y, 12, 2)
-- end
--- Like print(), but with font() and alignment
---@param txt string
---@param x number
---@param y number
---@param color number
---@param align? number
---@param scale? number
---@return number
function prn(txt, x, y, color, align, scale)
align = align or ALIGN.Left
scale = scale or 1
set1bpp()
if align == ALIGN.Right then
x = x - prn_len(txt, scale)
elseif align == ALIGN.Center then
x = x - prn_len(txt, scale) / 2
end
if color ~= nil then
swap_color(1, color)
end
local len = font(txt, x, y, 0, 4, 0, false, scale)
swap_color(1, 1)
set4bpp()
return len
end
---Like prn() but with an outline
---@param any string
---@param x number
---@param y number
---@param color number
---@param align? number
---@param border_color? number
---@param scale? number
---@return number
function prn_border(txt, x, y, color, align, border_color, scale)
if not border_color then
return prn(txt, x, y, color, align, scale)
end
align = align or ALIGN.Left
border_color = border_color or 0
scale = scale or 1
-- diagonals
prn(txt, x + 1, y + 1, border_color, align, scale)
prn(txt, x + 1, y - 1, border_color, align, scale)
prn(txt, x - 1, y + 1, border_color, align, scale)
prn(txt, x - 1, y - 1, border_color, align, scale)
-- cardinals
prn(txt, x + 1, y, border_color, align, scale)
prn(txt, x - 1, y, border_color, align, scale)
prn(txt, x, y + 1, border_color, align, scale)
prn(txt, x, y - 1, border_color, align, scale)
return prn(txt, x, y, color, align, scale)
end
---like prn_border() but with floaty text
---@param txt any
---@param x any
---@param y any
---@param color any
---@param align any
---@param border_color? any
---@param scale? any
function prn_border_floaty(txt, x, y, color, align, border_color, scale, delay)
delay = delay or 0
align = align or ALIGN.Left
border_color = border_color or 0
local len = prn_len(txt, scale)
prn_len(txt, scale)
if align == ALIGN.Right then
x = x - len
elseif align == ALIGN.Center then
x = x - len / 2
end
for i = 1, #txt do
local c = txt:sub(i, i)
local y = y + math.sin(((i+delay) * 100 + time()) / 200) * 3
x = x + prn_border(c, x, y, color, ALIGN.Left, border_color, scale)
end
return len
end
function swap_color(index, color)
poke4(PALETTE_MAP * 2 + index, color)
end
function reset_colors(...)
args = { ... }
if #args > 0 then
for _, c in ipairs(args) do
swap_color(c, c)
end
else
for i = 0, 16 do
poke4(PALETTE_MAP * 2 + i, i)
end
end
end
local _cachedPrintLen = {}
function print_len(txt)
if _cachedPrintLen[txt] then return _cachedPrintLen[txt] end
local len = print(txt, -1000, -1000)
_cachedPrintLen[txt] = len
return len
end
local _cachedFontLen = {}
function prn_len(txt, scale)
scale = scale or 1
if scale == 0 then scale = 1 end
local bpp = peek4(2 * 0x3ffc) -- get the current bpp value to reset it after
set1bpp()
if _cachedFontLen[txt] then return _cachedFontLen[txt] end
local len = font(txt, -1000, -1000, 0, 4, 0, false, scale)
_cachedFontLen[txt] = len
poke4(2 * 0x3ffc, bpp)
return len
end
function set1bpp()
poke4(2 * 0x3ffc, 8) -- 0b1000
end
function set4bpp()
poke4(2 * 0x3ffc, 2) -- 0b0010
end
function point_in_rect(x, y, rx, ry, rw, rh)
return x >= rx and x <= rx + rw and y >= ry and y <= ry + rh
end
function mouse_in_rect(rect)
local mx, my = mouse()
return point_in_rect(mx, my, rect.x, rect.y, rect.width, rect.height)
end
function clamp(min, val, max)
return math.max(math.min(max, val), min)
end
function normalize(val, min, max)
min = min or 0
max = max or val
return (val - min) / (max - min)
end
-- [/TQ-Bundler: utils.utils]
-- [TQ-Bundler: utils.input]
-- manage clicks, since tic-80 only returns a "pressed" state
Input = {
--- @type boolean
lclick_prev_state = false,
--- @type 'none'|'pressed'|'released'
lclick_state = "none"
}
function Input.update_mouse(self)
local x, y, left = mouse()
-- left btn down
if left then
-- was not pressed before
if not self.lclick_prev_state then
self.lclick_state = "pressed"
self.lclick_prev_state = true
-- was already pressed
elseif self.lclick_prev_state then
self.lclick_state = "none"
end
-- left btn up
elseif not left then
if self.lclick_prev_state then
self.lclick_state = "released"
self.lclick_prev_state = false
elseif not self.lclick_prev_state then
self.lclick_state = "none"
end
end
end
function Input.mouse_pressed(self)
return self.lclick_state == "pressed"
end
function Input.mouse_released(self)
return self.lclick_state == "released"
end
-- [/TQ-Bundler: utils.input]
-- [TQ-Bundler: utils.tween]
-- https://github.com/kikito/tween.lua
--[[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
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.
]]
-- Modified by Simon Cambier to better fit TIC-80 restrictions
---@return Tween
function require_tween()
-- easing
-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.
-- For all easing functions:
-- t = time == how much time has to pass for the tweening to complete
-- b = begin == starting property value
-- c = change == ending - beginning
-- d = duration == running time. How much time has passed *right now*
---@class Tween
local Tween = {}
local Tween_mt = { __index = Tween }
local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
-- linear
local function linear(t, b, c, d) return c * t / d + b end
-- quad
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
local function outQuad(t, b, c, d)
t = t / d
return -c * t * (t - 2) + b
end
local function inOutQuad(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
local function outInQuad(t, b, c, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end
-- cubic
local function inCubic(t, b, c, d) return c * pow(t / d, 3) + b end
local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
local function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
local function outInCubic(t, b, c, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end
-- quart
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end
local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
local function inOutQuart(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b
end
local function outInQuart(t, b, c, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end
-- quint
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end
local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
local function inOutQuint(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b
end
local function outInQuint(t, b, c, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end
-- sine
local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end
local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end
local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
local function outInSine(t, b, c, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
return inSine((t * 2) - d, b + c / 2, c / 2, d)
end
-- expo
local function inExpo(t, b, c, d)
if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end
local function outExpo(t, b, c, d)
if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end
local function inOutExpo(t, b, c, d)
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
end
local function outInExpo(t, b, c, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end
-- circ
local function inCirc(t, b, c, d) return (-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end
local function outCirc(t, b, c, d) return (c * sqrt(1 - pow(t / d - 1, 2)) + b) end
local function inOutCirc(t, b, c, d)
t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
end
local function outInCirc(t, b, c, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end
-- elastic
local function calculatePAS(p, a, c, d)
p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s
return p, a, p / (2 * pi) * asin(c / a) -- p,a,s
end
local function inElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p, a, s = calculatePAS(p, a, c, d)
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end
local function outElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p, a, s = calculatePAS(p, a, c, d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end
local function inOutElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
p, a, s = calculatePAS(p, a, c, d)
t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) * 0.5 + c + b
end
local function outInElastic(t, b, c, d, a, p)
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end
-- back
local function inBack(t, b, c, d, s)
s = s or 1.70158
t = t / d
return c * t * t * ((s + 1) * t - s) + b
end
local function outBack(t, b, c, d, s)
s = s or 1.70158
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
end
local function inOutBack(t, b, c, d, s)
s = (s or 1.70158) * 1.525
t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end
local function outInBack(t, b, c, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end
-- bounce
local function outBounce(t, b, c, d)
t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
end
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
end
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end
local function inOutBounce(t, b, c, d)
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end
local function outInBounce(t, b, c, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end
Tween.easing = {
linear = linear,
inQuad = inQuad,
outQuad = outQuad,
inOutQuad = inOutQuad,
outInQuad = outInQuad,
inCubic = inCubic,
outCubic = outCubic,
inOutCubic = inOutCubic,
outInCubic = outInCubic,
inQuart = inQuart,
outQuart = outQuart,
inOutQuart = inOutQuart,
outInQuart = outInQuart,
inQuint = inQuint,
outQuint = outQuint,
inOutQuint = inOutQuint,
outInQuint = outInQuint,
inSine = inSine,
outSine = outSine,
inOutSine = inOutSine,
outInSine = outInSine,
inExpo = inExpo,
outExpo = outExpo,
inOutExpo = inOutExpo,
outInExpo = outInExpo,
inCirc = inCirc,
outCirc = outCirc,
inOutCirc = inOutCirc,
outInCirc = outInCirc,
inElastic = inElastic,
outElastic = outElastic,
inOutElastic = inOutElastic,
outInElastic = outInElastic,
inBack = inBack,
outBack = outBack,
inOutBack = inOutBack,
outInBack = outInBack,
inBounce = inBounce,
outBounce = outBounce,
inOutBounce = inOutBounce,
outInBounce = outInBounce
}
-- private stuff
local function copyTables(destination, keysTable, valuesTable)
valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then
setmetatable(destination, mt)
end
for k, v in pairs(keysTable) do
if type(v) == "table" then
destination[k] = copyTables({}, v, valuesTable[k])
else
destination[k] = valuesTable[k]
end
end
return destination
end
local function checkSubjectAndTargetRecursively(subject, target, path)
path = path or {}
local targetType, newPath
for k, targetValue in pairs(target) do
targetType, newPath = type(targetValue), copyTables({}, path)
table.insert(newPath, tostring(k))
if targetType == "number" then
assert(type(subject[k]) == "number",
"Parameter '" .. table.concat(newPath, "/") .. "' is missing from subject or isn't a number")
elseif targetType == "table" then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else
assert(targetType == "number",
"Parameter '" .. table.concat(newPath, "/") .. "' must be a number or table of numbers")
end
end
end
local function checkNewParams(duration, subject, target, easing)
assert(type(duration) == "number" and duration > 0,
"duration must be a positive number. Was " .. tostring(duration))
local tsubject = type(subject)
assert(tsubject == "table" or tsubject == "userdata",
"subject must be a table or userdata. Was " .. tostring(subject))
assert(type(target) == "table", "target must be a table. Was " .. tostring(target))
assert(type(easing) == "function", "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target)
end
local function getEasingFunction(easing)
easing = easing or "linear"
if type(easing) == "string" then
local name = easing
easing = Tween.easing[name]
if type(easing) ~= "function" then
error("The easing function name '" .. name .. "' is invalid")
end
end
return easing
end
local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
local t, b, c, d
for k, v in pairs(target) do
if type(v) == "table" then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else
t, b, c, d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t, b, c, d)
end
end
end
-- Tween methods
---@param clock number
---@return boolean # true if the tween has expired
function Tween:set(clock)
assert(type(clock) == "number", "clock must be a positive number or 0")
self.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock
if self.clock <= 0 then
self.clock = 0
copyTables(self.subject, self.initial)
elseif self.clock >= self.duration then -- the tween has expired
self.clock = self.duration
copyTables(self.subject, self.target)
else
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
end
return self.clock >= self.duration
end
function Tween:reset()
return self:set(0)
end
---@param dt number
---@return boolean # true if the tween has expired
function Tween:update(dt)
assert(type(dt) == "number", "dt must be a number")
return self:set(self.clock + dt)
end
-- Public interface
function Tween.new(duration, subject, target, easing)
easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing)
return setmetatable({
duration = duration,
subject = subject,
target = target,
easing = easing,
clock = 0
}, Tween_mt)
end
return Tween
end
-- [/TQ-Bundler: utils.tween]
-- [TQ-Bundler: coroutines]
--
-- coroutines manager
--
local coroutines = {}
function addcoroutine(fn)
local c = coroutine.create(fn)
table.insert(coroutines, c)
return c
end
function startcoroutine(c)
coroutine.resume(c)
end
function stopcoroutine(c)
for k, v in ipairs(coroutines) do
if c == v then
table.remove(coroutines, k)
break
end
end
end
function _coresolve()
for k, co in ipairs(coroutines) do
if coroutine.status(co) ~= "dead" then
local _, msg = coroutine.resume(co)
assert(msg == nil, msg)
else
stopcoroutine(co)
end
end
end
--- @param frames number
function waitframes(frames)
local frame = 0
while frame < frames do
frame = frame + 1
coroutine.yield()
end
end
--- @param seconds number
function waitsecs(seconds)
return waitframes(seconds * 60)
end
-- [/TQ-Bundler: coroutines]
-- [TQ-Bundler: states.state_manager]
--- States stack
--- @class StateManager
StateManager = {
states = {},
---
---@param self StateManager
new = function(self, o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
---
---@param self StateManager
---@return unknown
_get_current_state = function(self)
return self.states[#self.states]
end,
---
---@param self StateManager
---@param state any
_set_current_state = function(self, state)
if #self.states == 0 then
self.states = { state }
else
local last = self.states[#self.states]
if last._leave then last:_leave() end
self.states[#self.states] = state
end
end,
---
---@param self StateManager
---@param state any
change_state = function(self, state, ...)
local current = self:_get_current_state()
if current and current._leave then current:_leave() end
self:_set_current_state(state)
if state._enter then state:_enter(...) end
end,
---
---@param self StateManager
push_state = function(self, state, data)
table.insert(self.states, state)
if state._enter then state:_enter(data) end
end,
---
---@param self StateManager
pop_state = function(self)
local state = table.remove(self.states)
if #self.states == 0 then trace("No state left", 2) end
if state._leave then state:_leave() end
end,
---
---@param self StateManager
update = function(self)
local current = self:_get_current_state()
if not current then return end
if current.update then current:update() end
end,
}
-- [/TQ-Bundler: states.state_manager]
-- [TQ-Bundler: states.state_main_menu]
function state_main_menu()
local covering = {}
local falling = {}
local states
local buttons = {
{
hover = false,
label = "Play",
box = { x = 120 - prn_len("Play") / 2, y = 100, w = prn_len("Play"), h = 10 },
cb = function()
trace("play")
for i = 0, 240, 16 do
for j = 0, 135, 16 do
table.insert(covering, Die:new({
value = math.random(1, 6),
angle = 360,
x = i,
y = j,
scale = 0
}))
end
end
end
}
}
local function show_transition()
-- slightly scale up the transition dice
for i, die in ipairs(covering) do
die.scale = die.scale + 0.03 / i * 80
die.angle = die.angle * 0.90
if die.scale > 1 then
die.scale = 1
end
swap_color(12, 15)
swap_color(0, 15)
die:draw(-1)
end
if table.count(covering, function(d) return d.scale == 1 end) == #covering then
state_manager:change_state(states.game, { covering = covering })
end
end
local function falling_dice()
local remove = {}
for i, die in ipairs(falling) do
die.y = die.y + 1
die.angle = die.angle + die.rot_speed
if die.y > 160 then
table.insert(remove, i)
end
end
for _, i in ipairs(remove) do
table.remove(falling, i)
end
end
local function draw_dice()
for _, die in ipairs(falling) do
swap_color(12, 15)
swap_color(13, 15)
swap_color(14, 15)
die:draw()
reset_colors(12)
end
end
local function _enter(self, data)
assert(data.states, "needs a states table")
states = data.states
covering = {}
end
local function update()
-- random sign
if math.random() < 0.05 then
local sign = math.random() < 0.5 and -1 or 1
-- Spawn a random die
local die = Die:new({
value = math.random(1, 6),
x = math.random(240),
y = -20,
angle = math.random(10, 40),
rot_speed = sign * math.random(6, 10) / 10,
})
table.insert(falling, die)
end
falling_dice()
cls()
draw_dice()
-- prn_border("KNUCKLEBONES", 118, 10, 1, ALIGN.Center, 0, 2, 0)
-- prn_border("KNUCKLEBONES", 122, 10, 2, ALIGN.Center, 0, 2, 1)
prn_border("KNUCKLEBONES", 120, 10, 12, ALIGN.Center, 0, 2)
local l = prn_len("A game of risk and reward")
local x = 120 - l / 2
local y = 50
x = x + prn_border("A game of ", x, y, 13, ALIGN.Left, 0)
x = x + prn_border_floaty("risk ", x, y + 5, 12, ALIGN.Left, 1, 1, 20)
x = x + prn_border("and ", x, y, 13, ALIGN.Left, 0)
x = x + prn_border_floaty("reward", x, y - 5, 12, ALIGN.Left, 8)
print_border("Originally from \"The Cult of the Lamb\"", 104, 130, 14, 0, false, 1, true)
local mx, my = mouse()
for i, button in ipairs(buttons) do
-- Print button
local x, y, w, h = button.box.x, button.box.y, button.box.w, button.box.h
local hover = mx >= x and mx <= x + w and my >= y and my <= y + h
button.hover = hover
local color = hover and 12 or 13
-- rect(x, y, w, h, color)
prn_border(button.label, x, y, color, ALIGN.Left, 0)
-- Click button
if hover and Input:mouse_pressed()
then
button.cb()
end
end
if #covering > 0 then
show_transition()
end
end
return { update = update, _enter = _enter }
end
-- [/TQ-Bundler: states.state_main_menu]
-- [TQ-Bundler: states.state_game]
-- [TQ-Bundler: board]
---@diagnostic disable: lowercase-global
-- [TQ-Bundler: scoring]
function get_total_score(scores)
local sum = 0
for col = 1, 3 do
local value = scores[col]
sum = sum + value
end
return sum
end
-- [/TQ-Bundler: scoring]
local cell_width = 22
local cell_height = 18
function draw_board()
local players = { "player", "enemy" }
for _, player in ipairs(players) do
-- columns
for col = 1, 3 do
local dice_col = PLACED_DICE[player][col]
for row = 1, 3 do
local pos = get_cell_coords(player, col, row)
-- cell background
rectb(pos.x, pos.y, cell_width, cell_height, 15)
-- dice
local value = dice_col[row]
local combo = table.count(dice_col, value)
if value > 0 then
swap_color(8, 0)
if combo == 2 then
swap_color(12, 4)
swap_color(13, 3)
elseif combo == 3 then
swap_color(12, 3)
swap_color(13, 2)
end
-- dice sprite
spr(die_spr[value], pos.x + 3, pos.y + 1, 0, 1, 0, 0, 2, 2)
reset_colors(8, 12, 13)
end
end
end
end
end
function draw_dice_boxes()
-- small trick to make the corners look better
swap_color(3, 0)
-- player
swap_color(12, COLOR_PLAYER) -- bones color
local r = get_dice_box("player")
draw9box(51, 52, r.x - 4, r.y - 4, r.width + 8, r.height + 8)
-- enemy
swap_color(12, COLOR_ENEMY) -- bones color
r = get_dice_box("enemy")
draw9box(51, 52, r.x - 4, r.y - 4, r.width + 8, r.height + 8)
-- reset colors
swap_color(3, 3)
swap_color(12, 12)
end
function print_names()
prn_border("You", 1, 50, 12, ALIGN.Left, COLOR_PLAYER)
prn_border("Opponent", 239, 135-50-8, 12, ALIGN.Right, COLOR_ENEMY)
end
function print_scores()
local players = { "player", "enemy" }
local sums = {}
for _, player in ipairs(players) do
local scores = SCORES[player]
local y
for col = 1, 3 do
local value = scores[col]
local pos = get_cell_coords(player, col, 1)
pos.y = pos.y + (player == "player" and -9 or 20)
y = pos.y
pos.x = pos.x + 11
prn_border(value, pos.x, pos.y, 12, ALIGN.Center)
end
-- sums
local sum = get_total_score(scores)
local x = player == "player" and 12 or 240 - 12
local align = player == "player" and ALIGN.Left or ALIGN.Right
table.insert(sums, {
sum = sum,
x = x,
y = y,
align = align,
bg = player == "player" and COLOR_PLAYER or COLOR_ENEMY
})
end
for _, sum in ipairs(sums) do
prn_border(sum.sum, sum.x, sum.y, 12, sum.align, sum.bg)
end
end
---comment
---@param player 'player'|'enemy'
---@param col number 1-3
---@param row number 1-3
---@return table
function get_cell_coords(player, col, row)
row = row - 1 -- fix 1-based index for y, makes it easier to calculate coords
local p = 14 -- padding
local cols = { 120 - cell_width - p, 120 - (cell_width / 2), 120 + p }
if player == "enemy" then
row = row == 0 and 2 or row == 2 and 0 or 1 -- "invert" the board for the enemy
return { x = cols[col], y = (cell_height * row + (row * 1)) + 1 }
else
return { x = cols[col], y = (cell_height * row + (row * 1)) + 79 }
end
end
--- @param player 'player'|'enemy'
--- @param column 1|2|3
function get_column_rect(player, column)
local c = get_cell_coords(player, column, player == "player" and 1 or 3)
return { x = c.x - 1, y = c.y - 1, width = cell_width + 2, height = cell_height * 3 + 4 }
end
--- @param player 'player'|'enemy'
function get_dice_box(player)
if player == "player" then
return { x = 10, y = 89, width = 60, height = 36 }
else
return { x = 170, y = 9, width = 60, height = 36 }
end
end
-- [/TQ-Bundler: board]
-- [TQ-Bundler: classes.die]
--- @class Die
Die = {
x = 0,
y = 0,
size = 16,
dx = math.random(0, 1) == 0 and -1 or 1,
dy = math.random(0, 1) == 0 and -1 or 1,
value = nil,
--
box = nil,
bouncing = false,
angle = 0,
scale = 1,
--
bg = nil,
--
---
---@param self Die
set_random_value = function(self)
-- Don't change the value if the die is barely moving
if (math.abs(self.dx)<0.25 or math.abs(self.dy)<0.25) then
return
end
local n
repeat
n = math.random(1, 6)
until n ~= self.value
self.value = n
end,
---
--- @param self Die
update = function(self)
-- bounce off walls
if math.abs(self.dx) < 0.05 then self.dx = 0 end
if math.abs(self.dy) < 0.05 then self.dy = 0 end
if self.box then
if self.x < self.box.x then
self.x = self.box.x
self.dx = -self.dx
self:set_random_value()
end
if self.x > self.box.x + self.box.width - self.size then
self.x = self.box.x + self.box.width - self.size
self.dx = -self.dx
self:set_random_value()
end
if self.y < self.box.y then
self.y = self.box.y
self.dy = -self.dy
self:set_random_value()
end
if self.y > self.box.y + self.box.height - self.size then
self.y = self.box.y + self.box.height - self.size
self.dy = -self.dy
self:set_random_value()
end
end
if not self.bouncing then
-- reduce speed
if self.dx ~= 0 then
self.dx = self.dx * 0.85
end
if self.dy ~= 0 then
self.dy = self.dy * 0.85
end
end
self.x = self.x + self.dx
self.y = self.y + self.dy
end,
---
---@param self Die
draw = function(self, colorkey)
colorkey = colorkey or 0
if self.bg then
swap_color(12, self.bg)
end
swap_color(8, 0)
-- aspr() uses the center as anchor point, so we offset the drawing position
-- while keeping the scaling centered
local offset = 8
aspr(die_spr[self.value], self.x + offset, self.y + offset, colorkey, self.scale, self.scale, 0, self.angle, 2, 2)
-- rectb(self.x, self.y, 16, 16, 2)
reset_colors(12, 8)
end
}
---comment
---@param o Die
---@return Die
function Die:new(o)
o = o or {} -- create object if user does not provide one
setmetatable(o, self)
self.__index = self
if o.box then
o.angle = math.random() < .5 and 180 or -180
o.x = o.box.x + math.random(0, o.box.width - o.size)
o.y = o.box.y + math.random(0, o.box.height - o.size)
end
return o
end
-- [/TQ-Bundler: classes.die]
-- [TQ-Bundler: states.game.loading]
function state_game_loading()
local covering
local event_bus
local turn
local banner
local function show_transition()
-- slightly scale up the transition dice
for i, die in ipairs(covering) do
die.scale = die.scale - 0.03 / i * 80
die.angle = die.angle - i / 50
if die.scale < 0 then
die.scale = 0
end
swap_color(12, 15)
swap_color(13, 15)
swap_color(14, 15)
swap_color(0, 15)
die:draw(-1)
end
-- clean
if table.count(covering, function(d) return d.scale == 0 end) == #covering then
covering = {}
end
end
local function _enter(self, data)
assert(data.covering, "needs data.covering")
assert(data.event_bus, "needs data.event_bus")
covering = data.covering
event_bus = data.event_bus
turn = data.turn
banner = {
h = 0,
w = 1,
}
addcoroutine(function()
while banner.w < 240 do
banner.w = banner.w *1.1
waitframes(1)
end
if banner.w > 240 then
banner.w = 240
end
while banner.h < 20 do
banner.h = banner.h + 1
waitframes(1)
end
waitsecs(1.5)
while banner.h > 0.5 do
banner.h = banner.h - 1
waitframes(1)
end
while banner.w > 1 do
banner.w = banner.w * 0.9
waitframes(1)
end
waitsecs(0.5)
event_bus:emit(EVENT_RESET_DIE)
event_bus:emit(EVENT_SET_STEP, "rolling")
end)
end
local function update(self)
-- black banner in the middle
rect(120 - banner.w / 2, 67 - banner.h / 2, banner.w, banner.h, 0)
rectb(120 - banner.w / 2, 67 - banner.h / 2, banner.w, banner.h, 15)
clip(120 - banner.w / 2, 67 - banner.h / 2, banner.w, banner.h)
-- show who's going first
local text
local color
if turn == "player" then
color = COLOR_PLAYER
text = "You begin this round"
else
color = COLOR_ENEMY
text = "Your opponent begins this round"
end
prn_border(text, 120, 67 - 4, 12, ALIGN.Center, color)
clip()
if #covering > 0 then
show_transition()
end
end
return { _enter = _enter, update = update }
end
-- [/TQ-Bundler: states.game.loading]
-- [TQ-Bundler: states.game.rolling]
function state_game_rolling()
--- @type Die
local die
--- @type EventBus
local event_bus
local function roll_die()
die.bouncing = true
local rot = Tween.new(0.7, die, {
angle = (die.angle + math.random(180,360))-- die.angle + (math.random()<.5 and 1 or -1)* math.random(270, 360)
}, "outSine")
local done = false
repeat
done = rot:update(dt)
coroutine.yield()
until done
die.bouncing = false
event_bus:emit(EVENT_SET_STEP, "placing")
end
---@param self any
---@param _die Die
---@param _event_bus EventBus
local _enter = function(self, _die, _event_bus)
assert(_die, "states.game.rolling needs a die")
assert(_event_bus, "states.game.rolling needs an event_bus")
die = _die
event_bus = _event_bus
addcoroutine(roll_die)
end
local update = function(self)
end
return {
_enter = _enter,
update = update
}
end
-- [/TQ-Bundler: states.game.rolling]
-- [TQ-Bundler: states.game.placing]
-- [TQ-Bundler: ai]
--[[
List of actions, by order of priority:
- Complete a 3 dice combo 4-6
- Cancel a 3 dice combo 4-6
- Complete a 3 dice combo 1-3
- Complete a 2 dice combo 4-6
- Cancel a 3 dice combo 1-3
- Complete a 2 dice combo 1-3
- Cancel a 2 dice combo 4-6
- Cancel a 2 dice combo 1-3
- Look at the column's sum before removing a die.
It if is <=15, try to keep it as is, unless you're removing a 6 (or a 5)
]]
---Returns the column indexes (1-based) with completable combos
---@param dice table The 2d table with dice values
---@param value integer The dice value to place
---@param len 2|3 The possible combo length
---@return table The columns list (1-based)
function get_combos_to_complete(dice, value, len)
local ids = {}
for col = 1, 3 do
if table.count(dice[col], 0) >= 1
and table.count(dice[col], value) == len - 1 then
table.insert(ids, col)
end
end
return ids
end
---@param mine table "My" 2d table with dice values
---@param other table The "other" (opponent) 2d table with dice values
---@param value integer The dice value to place
---@param len 2|3 The possible combo length
---@return table The columns list (1-based)
function get_combos_to_cancel(mine, other, value, len)
local ids = {}
for col = 1, 3 do
if table.count(mine[col], 0) >= 1
and table.count(other[col], value) == len - 1 then
table.insert(ids, col)
end
end
return ids
end
function get_most_empty_cols(dice)
local ids = {}
for z = 3, 1, -1 do
for col = 1, 3 do
if table.count(dice[col], 0) == z then
table.insert(ids, col)
end
end
if #ids > 0 then return ids end
end
end
---Place the die in the best possible column
---@param mine table The 2d table of "my" dice
---@param other any The 2d table of the "other" dice
---@param die integer The die to place
---@return integer # The column where to place the die
function ai_pick_column(mine, other, die)
trace("--")
trace("AI")
trace("--")
if die >= 4 then
--#region Complete a 3 combo 4-5-6
local combos = get_combos_to_complete(mine, die, 3)
if #combos > 0 then
trace("Making a 3 combo with " .. die)
return math.randomitem(combos)
end
--#endregion Complete a 3 combo 4-5-6
-- #region Cancel a 3-dice combo 4-5-6
combos = get_combos_to_cancel(mine, other, die, 3)
if #combos > 0 then
trace("Cancelling a 3 combo with " .. die)
return math.randomitem(combos)
end
--#endregion Cancel a 3-dice combo 4-5-6
-- #region Complete a 2 combo 4-5-6
combos = get_combos_to_complete(mine, die, 2)
if #combos > 0 then
trace("Making a 2 combo with " .. die)
return math.randomitem(combos)
end
-- #endregion Complete a 2 combo 4-5-6
end
-- #region Complete a 3 combo 1-2-3
combos = get_combos_to_complete(mine, die, 3)
if #combos > 0 then
trace("Making a 3 combo with " .. die)
return math.randomitem(combos)
end
-- #endregion Complete a 3 combo 1-2-3
-- #region Complete a 2 combo 1-2-3
combos = get_combos_to_complete(mine, die, 2)
if #combos > 0 then
trace("Making a 2 combo with " .. die)
return math.randomitem(combos)
end
-- #endregion Complete a 2 combo 1-2-3
-- #region Random column
-- TODO: if 5-6, attack the opponent.
-- TODO: else, chose the column with the lowest sum
-- TODO: it's best to AVOID attacking the opponent when his sum is <= 15
-- TODO: it's best to avoid canceling 1-2 values
trace("Placing " .. die .. " at random")
return math.randomitem(get_most_empty_cols(mine))
-- #endregion Random column
end
-- [/TQ-Bundler: ai]
function state_game_placing()
--- @type Die
local die
--- @type EventBus
local event_bus
--- @type 'player'|'enemy'
local turn
local placing = false
local waiting = 30
--- Click on a column to place the die
local function place_die(col_id)
if placing then return end
placing = true
local cell_id = 0
for i = 1, 3 do
if PLACED_DICE[turn][col_id][i] == 0 then
cell_id = i
break
end
end
step = "waiting"
addcoroutine(function()
-- move the die to its cell
local cell = get_cell_coords(turn, col_id, cell_id)
die.box = nil
die.angle = die.angle % 360
-- get value closest to 0. e.g. 270 -> -90
if die.angle > 180 then
die.angle = die.angle - 360
end
local tween = Tween.new(0.5, die, { x = cell.x + 3, y = cell.y + 1, angle = 0 }, "outQuad")
repeat
coroutine.yield()
until tween:update(dt)
PLACED_DICE:set(turn, col_id, cell_id, die.value)
-- Animate the dice and update the scores
event_bus:emit(EVENT_REMOVE_DIE)
event_bus:emit(EVENT_SET_STEP, "scoring")
end)
end
---@param self any
---@param _die Die
---@param _event_bus EventBus
---@param _turn 'player'|'enemy'
local _enter = function(self, _die, _event_bus, _turn)
assert(_die, "missing die")
assert(_event_bus, "missing event_bus")
assert(_turn, "missing turn")
die = _die
event_bus = _event_bus
turn = _turn
waiting = 30
placing = false
end
local update = function(self)
waiting = waiting - 1
if turn == "enemy" then
if waiting <= 0 and not placing then
local col = ai_pick_column(PLACED_DICE.enemy, PLACED_DICE.player, die.value)
place_die(col)
end
else -- turn == "player"
-- Highlight the hovered column
local columns = {
get_column_rect(turn, 1),
get_column_rect(turn, 2),
get_column_rect(turn, 3),
}
local col, col_id = table.find(columns, function(c)
return mouse_in_rect(c)
end)
if col then
if PLACED_DICE:has_free_slot(turn, col_id) then
-- draw hovered column
rect(col.x, col.y, col.width, col.height, 14)
-- place the die if clicked
if Input:mouse_released() then
place_die(col_id)
end
end
end
end
-- Redraw the board because the hovered column is drawn on top of it
-- and i'm too lazy to fix this properly.
draw_board()
end
return {
_enter = _enter,
update = update,
}
end
-- [/TQ-Bundler: states.game.placing]
-- [TQ-Bundler: states.game.scoring]
function state_game_scoring()
local dice_to_destroy = {}
--- @type EventBus
local event_bus
local destroyed = 0
local function update_board_and_score(player, col, row)
assert(player)
assert(col)
assert(row)
-- Create a Die and place it on the cell
local cell = get_cell_coords(player, col, row)
local die = Die:new({ value = PLACED_DICE:get(player, col, row), x = cell.x + 3, y = cell.y + 1 })
-- Set the score to 0
PLACED_DICE:set(player, col, row, 0)
table.insert(dice_to_destroy, die)
addcoroutine(function()
waitframes(10)
local tween = Tween.new(0.5, die, { scale = 0.1 }, "inOutQuad")
repeat
coroutine.yield()
until tween:update(dt)
table.remove_item(dice_to_destroy, die)
end)
end
---comment
---@param self any
---@param _die Die|nil
---@param _event_bus EventBus
---@param player 'player'|'enemy'
local function _enter(self, _die, _event_bus, player)
assert(_event_bus)
assert(player == "player" or player == "enemy")
event_bus = _event_bus
destroyed = 0
local other_player = player == "player" and "enemy" or "player"
-- Check if the newly placed die must remove other dice
local current_dice = PLACED_DICE[player]
local other_dice = PLACED_DICE[other_player]
-- Check the dice for each column, and remove corresponding dice in the opposing column
for col = 1, 3 do
for row = 1, 3 do
local value = current_dice[col][row]
for row2 = 1, 3 do
local value2 = other_dice[col][row2]
if value > 0 and value2 > 0 and value2 == value then
trace("destroy " .. other_player .. " " .. col .. " " .. row2 .. " = " .. value2)
update_board_and_score(other_player, col, row2)
destroyed = destroyed + 1
end
end
end
end
-- Update the scores and move on to next state
addcoroutine(function()
waitsecs((destroyed > 0) and 0.6 or 0.3)
SCORES:update()
-- Check if it's game over
if PLACED_DICE:is_game_over() then
SCORES:update()
event_bus:emit(EVENT_SET_STEP, "game_over")
else
event_bus:emit(EVENT_CHANGE_TURN)
end
end)
end
local function update(self)
for _, die in ipairs(dice_to_destroy) do
die:draw()
end
end
local function _leave(self)
end
return {
_enter = _enter,
update = update,
_leave = _leave
}
end
-- [/TQ-Bundler: states.game.scoring]
-- [TQ-Bundler: states.game.gameover]
function state_game_gameover()
local box
local event_bus
local y = 90
local buttons = {
-- {
-- hover = false,
-- label = "Play Again",
-- box = { x = 120 - prn_len("Play Again") / 2, y = y, width = prn_len("Play Again"), height = 10 },
-- cb = function()
-- trace("again")
-- end
-- },
{
hover = false,
label = "Main Menu",
box = { x = 120 - prn_len("Main Menu") / 2, y = y + 15, width = prn_len("Main Menu"), height = 10 },
cb = function()
reset()
end
}
}
local function open_box()
while box.w < 180 do
box.w = box.w * 1.1
waitframes(1)
end
if box.w > 180 then
box.w = 180
end
while box.h < 20 do
box.h = box.h + 1
waitframes(1)
end
end
local function show_buttons()
-- show a button "again", and another "main menu"
-- right under the box
for _, button in ipairs(buttons) do
button.hover = false
-- rect(button.box.x - 1, button.box.y - 1, button.box.width + 2, button.box.height + 2, 2)
-- Hover button
if mouse_in_rect(button.box) then
button.hover = true
-- Click button
if Input:mouse_released()
then
button.cb()
end
end
prn_border(button.label,
button.box.x, button.box.y,
button.hover and 12 or 13,
ALIGN.Left,
0)
end
end
local function update(self)
-- draw box
rect(120 - box.w / 2, 67 - box.h / 2, box.w, box.h, 0)
rectb(120 - box.w / 2, 67 - box.h / 2, box.w, box.h, 15)
clip(120 - box.w / 2, 67 - box.h / 2, box.w, box.h)
-- print who won according to score
local text
local color
local score_player = get_total_score(SCORES.player)
local score_enemy = get_total_score(SCORES.enemy)
if score_player > score_enemy then
color = COLOR_PLAYER
text = "YOU WON " .. score_player .. " - " .. score_enemy
elseif score_player < score_enemy then
color = COLOR_ENEMY
text = "YOU LOST " .. score_player .. " - " .. score_enemy
else
color = 14
text = "DRAW " .. score_player .. " - " .. score_enemy
end
prn_border(text, 120, 67 - 3, 12, ALIGN.Center, color)
clip()
show_buttons()
end
---comment
---@param self any
---@param die Die
---@param _event_bus EventBus
---@param turn "player"|"enemy"
local function _enter(self, die, _event_bus, turn)
assert(_event_bus, "event_bus is nil")
assert(turn, "turn is nil")
event_bus = _event_bus
event_bus:emit(EVENT_REMOVE_DIE)
box = {
h = 0, w = 1
}
addcoroutine(open_box)
end
return { update = update, _enter = _enter }
end
-- [/TQ-Bundler: states.game.gameover]
-- [TQ-Bundler: utils.event_bus]
--- @class EventBus
EventBus = {
_handlers = {},
new = function(self, o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
---
---@param self EventBus
---@param event any
---@param handler any
---@param index any
on = function(self, event, handler, index)
if not self._handlers[event] then self._handlers[event] = {} end
self:off(event, handler)
if not index then
table.insert(self._handlers[event], handler)
else
-- insertIntoTable(this.handlers[event], handler, index)
end
end,
---
---@param self EventBus
---@param event any
---@param handler any
off = function(self, event, handler)
if not self._handlers[event] then return end
self._handlers[event] = table.filter(self._handlers[event], function(o) return o ~= handler end)
end,
---
---@param self EventBus
---@param event any
---@param ... unknown
emit = function(self, event, ...)
trace("[EventBus] Emitted " .. event)
if not self._handlers[event] then return end
for k, handler in pairs(self._handlers[event]) do
handler(...)
end
end
}
-- [/TQ-Bundler: utils.event_bus]
function state_game()
local game = {}
local event_bus = EventBus:new()
--- @type "player"|"enemy"
local turn = "player"
--- @type "waiting"|"rolling"|"placing"
local step = "rolling"
--- @type Die|nil
local die
local function reset_die()
die = Die:new({ box = get_dice_box(turn), bouncing = false })
end
--
-- #region State management
--
--- Sub-states
--- @type StateManager
local state_manager = StateManager:new()
local states = {
loading = state_game_loading(),
rolling = state_game_rolling(),
placing = state_game_placing(),
scoring = state_game_scoring(),
game_over = state_game_gameover(),
}
event_bus:on(EVENT_SET_STEP, function(new_step)
trace("[Event] set_step: " .. step .. " -> " .. new_step)
assert(states[new_step], "state " .. new_step .. " does not exist")
state_manager:change_state(states[new_step], die, event_bus, turn)
step = new_step
end)
event_bus:on(EVENT_CHANGE_TURN, function()
turn = turn == "player" and "enemy" or "player"
reset_die()
state_manager:change_state(states.rolling, die, event_bus)
step = "rolling"
end)
event_bus:on(EVENT_REMOVE_DIE, function()
die = nil
end)
event_bus:on(EVENT_RESET_DIE, function()
reset_die()
end)
--
-- #endregion State management
--
game._enter = function(self, data)
assert(data.covering, "needs data.covering")
trace("entering game state")
turn = math.random(1, 2) == 1 and "player" or "enemy"
state_manager:change_state(states.loading, {
turn = turn,
event_bus = event_bus,
covering = data.covering
})
end
game.update = function()
cls()
if die then die:update() end
draw_board()
print_scores()
print_names()
draw_dice_boxes()
state_manager:update()
if die and die.value then
die:draw()
end
-- prn_border("Turn: " .. turn, 1, 1, 2)
-- prn_border("Step: " .. step, 1, 10, 2)
reset_colors()
end
game._leave = function()
end
return game
end
-- [/TQ-Bundler: states.state_game]
Tween = require_tween()
--- @type StateManager
state_manager = StateManager:new()
local states = {
main_menu = state_main_menu(),
game = state_game()
}
frames = 0
function BOOT()
-- Save original palette
memcpy(0x14e04, 0x3fc0, 48)
state_manager:change_state(states.main_menu, { states = states })
end
function TIC()
dt = (time() - pt) / 1000
pt = time()
frames = frames + 1
cls(13)
Input:update_mouse()
_coresolve()
state_manager:update()
-- mouse position
-- local mx, my = mouse()
-- print_border(mx .. "," .. my, mx + 1, my - 5)
end
-- function BDR(scn)
-- local cl = (math.floor(frames / 1.2) % (135 + 40)) - 20
-- greyGlitch(scn, cl)
-- end
function vhsGlitch(scn, cl)
local height = 10
local ampl = 10
local n = clamp(0, normalize(cl, scn - height, scn + height), 1) * 2 * math.pi
local s = math.cos(n)
local r = math.random() * (s * ampl - ampl) * s * s
-- 0x3ff9: screen offset
poke(0x3ff9, r)
poke(0x3ffa, r / 2)
end
function greyGlitch(scn, cl)
-- Reset rgb values
memcpy(0x3fc0, 0x14e04, 48)
local h = 10
-- If current scanline is in range
if (scn >= cl - h and scn <= cl + h) then
-- Loop through colors in ram
if (math.random() < 0.8) then
if (math.random() > 0.01) then
for i = 0, 16 do
local c = i * 3
local grey =
peek(0x3fc0 + c + 0) * 0.2126 +
peek(0x3fc0 + c + 1) * 0.7152 +
peek(0x3fc0 + c + 2) * 0.0722
-- Alter the color
poke(0x3fc0 + c + 0, grey)
poke(0x3fc0 + c + 1, grey)
poke(0x3fc0 + c + 2, grey)
end
else
-- line(0, scn, SCREEN_WIDTH, scn, RNG.random() * 15)
end
end
end
end
-- <TILES>
-- 001:0cccccccccccccccccdcccccccccccccccccdccccccccdccccccccc8cccccc88
-- 002:cccccce0cccccccecccccdcecccccccecccdccceccdcccce8cccccce88ccccce
-- 003:0ccccccccccccccccccccccccccddcccccdccdccccccccdcccc88ccccc8888cc
-- 004:cccccce0cccccccecccccccecccddcceccdccdcecdccccceccc88ccecc8888ce
-- 005:0cccccccccccccddccc88ccccc8888cccc8888cdccc88ccccdccccc8cdccdc88
-- 006:cccccce0ccccdccedccccdcecccdccceddccccceccdcccce8ccdccce88cdcdce
-- 007:0cccccccccccccdcccc88ccdcc8888cccc8888ccccc88ccccccccccdcdcccddc
-- 008:cccccce0cdcccccedcc88ccecc8888cecc8888ceccc88ccedccccccecddcccde
-- 009:0ccdddccccccccdcccc88ccdcc8888cccc8888ccccc88cdccccccdc8cccccc88
-- 010:cccccce0ccccccceccc88ccecc8888cecc8888cecdc88cce8cdcccde88cccdce
-- 011:0cccccccccc88ccccc8888cccc8888ccccc88cccccccccdcccc88ccdcc8888cd
-- 012:cccccce0ccc88ccedc8888cedc8888cedcc88ccecdccccceccc88ccecc8888ce
-- 017:cdcddc88ccccccc8cccccdccccccdcccccccccccccdccccceccccccc0eeeeeee
-- 018:88cddcde8cccccceccdccccecccdcccecccccccecccccdcecccccceeeeeeeee0
-- 019:cc8888ccccc88cccccccccdcccdccdcccccddccccccccccceccccccc0eeeeeee
-- 020:cc8888ceccc88ccecdccccceccdccdcecccddccecccccccecccccceeeeeeeee0
-- 021:ccdcdc88ccccdcc8cccccdcccccdccddcdccccccccdccccdeccccccc0eeeeeee
-- 022:88cdccde8cccccdeccc88ccedc8888cecc8888ceccc88cceddcccceeeeeeeee0
-- 023:cdccccccccccccccccc88ccdcc8888cccc8888ccccc88ccdeccccccc0eeeeeee
-- 024:ccccccdecccccccedcc88ccecc8888cecc8888cedcc88ccecccccceeeeeeeee0
-- 025:ccdccc88cdcccdc8dcc88cdcdc8888ccdc8888ccccc88ccceccccccc0eeeeeee
-- 026:88ccccce8cdccccecdc88ccecc8888cecc8888cedcc88ccecdcccceeeeeeeee0
-- 027:cc8888ccccc88cccccccccdcccc88ccdcc8888cdcc8888cdecc88ccc0eeeeeee
-- 028:dc8888cedcc88ccecdccccceccc88ccecc8888cecc8888ceccc88ceeeeeeeee0
-- 032:ccccccccc0000000c0000000c0000000c0000000c0000000c0000000c0000000
-- 033:cccccccc00000000000000000000000000000000000000000000000000000000
-- 034:ccccc0000000c0000000c0000000c0000000c0000000c0000000c0000000c000
-- 035:000cc0c0000cccc00000cc00cc00cc00ccc3cc3c0cc3cc3ccc00cc000000cc00
-- 036:00000000000000000000000000000000cccccccccccccccc0000000000000000
-- 037:0cccccd0ccc22ccdcc2cc2cdccccc2cdcccc2ccdcccccccddccc2cdd0dddddd0
-- 048:c0000000c0000000c0000000c0000000c0000000c0000000c0000000c0000000
-- 050:0000c0000000c0000000c0000000c0000000c0000000c0000000c0000000c000
-- 051:000ccc00000ccc00000ccc00cc3ccc3ccc3ccc3ccc3ccc3c000ccc00000ccc00
-- 052:000000000000000000000000cccccccccccccccccccccccc0000000000000000
-- 064:c0000000cccccccc000000000000000000000000000000000000000000000000
-- 065:00000000cccccccc000000000000000000000000000000000000000000000000
-- 066:0000c000ccccc000000000000000000000000000000000000000000000000000
-- </TILES>
-- <SPRITES>
-- 000:00c3c3000024e763005abdf70018fff7005affe30099bdc10024668000c3c300
-- 001:80808000c1c1c100e3a2e300f7f7f7c0e3a2f7c0c180800080c1c10000000000
-- 002:ffc13e0fff22dd0cff14eb0a7e14ebe97e14eb11ff22dd11ffc13e11ff00ffe0
-- 003:c1c0c380224142a22242c3c122424277c14142c1804072a2e370b38080308100
-- 016:100880f3300c8021700e8021f00fe321700e8071300c8001100880a000008040
-- 017:e3c100405120004051c000e06121c3404121c3e041c000404101004000e00000
-- 018:ff000c30ff000c3000000c3000000c3000000c3000000c3000ff0c3000ff0c30
-- 019:000000f7804000e380a000c1ff11008080a080008040c1000000e3000000f700
-- 032:0080414100804141008041e300800041000000e3000000410080004100000000
-- 033:80604001c362a080a0014040c180a20082402100e12323008003c00000000000
-- 034:014080008080a2804001c180400180e34001c1808080a2800140800000000000
-- 035:00000000000000800000008000c30040000000408000c0208000c02040000000
-- 048:c180c1c122c0222223a00202a28001816280c00222802022c1e3e3c100000000
-- 049:01e381e38120402241e020012101e180e30222800101228001e0c18000000000
-- 050:c1c100002222000022228080c1c300002202000022018080c1c0008000000040
-- 051:030060c18100c022c0e3810260000301c0e381808100c0000300608000000000
-- 064:c180e1812241424202224220c222c120a2e34220a2224242c122e18100000000
-- 065:e0e3e3c1412020224220202042e1e1a34220202241202022e0e320c100000000
-- 066:22c1832222800121228001a0e3800160228021a02280212122c1c02200000000
-- 067:202222c12063622220a2622220a2a2222022232220222322e32222c100000000
-- 080:e1c1e1c12222222222222220e122e1c120a2a0022021212220c222c100000000
-- 081:e32222228022222280222222802222a2802241a28022416380c1802200000000
-- 082:2222e3c1222202404122014080c1804041804040228020402280e3c100000000
-- 083:00c18000000141002001220040010000800100000101000002c100e300000000
-- 096:400020008000200001c1a1c10002622200c322200022622200c3a1c100000000
-- 097:0200010002008200c2c180c22322e32322e38023232080c2c2c18002000000c1
-- 098:008001404000004040c08142c1800141428001c04280014142c121420000c000
-- 099:c0000000800000008061a1c180a2622280a2222280a22222c1a222c100000000
-- 112:0000000000000000a1c2a1c362236220622320e1a1c22002200220e120020000
-- 113:4000000040000000e1212222402122a2402122a2422141a281c2804100000000
-- 114:00000003000000802222e380412201408023808041c240802202e30300c10000
-- 115:806040008080a2008080018000010041808000e3808000008060000000000000
-- 128:c1210180220080412000c1c1202122022221e3c3c121202280c2c1c3c0000000
-- 129:42808000000100c1c1c1c12002020220c3c3c320222222c1c3c3c301000000c0
-- 130:8041804141000100c1c1c100222222c0e3e3e38020202080c1c1c1c100000000
-- 131:804041804180000000008080c0c04141808022228080e3e3c1c1222200000000
-- 144:01008fc080004121e3c62100200927c0e1cfe12120212121e3ce2fc000000000
-- 145:214040400080a08000000000c0c0a0a02121a0a02121a0a0c0c0414100000000
-- 146:214141800000008021c122c321222220a1222220412222c301c1c180e0000080
-- 147:032260038441a0808080a080e3e362e380802780e8e322804780228000002640
-- 160:0101808080804040c100000002c0c021c380212122802121c3c1c0c200000000
-- 161:41820000a041c1c000000221a062c32161a222212123c3c0212200000000e3e1
-- 162:80000024000000228000002140f1f1a22010014522100124c100000200000007
-- 163:248000002200000021008421a280424243802184a28042428780842102000000
-- 176:88ccee8044ccdd802233bb801133778088ccee8044ccdd802233bb8011337780
-- 177:808041008080410080f04100f08071f180f04141808041418080414180804141
-- 178:0041410000414100f07141f180014101f0714171804141418041414180414141
-- 179:41418000414180007141f00001f180f0f100f080000000800000008000000080
-- 192:8080008080800080808000808fffff8f00008080000080800000808000008080
-- 193:008080410080804100808f41ffff804f00808f41008080410080804100808041
-- 194:82004100820041008ecf7fff804000008f4fff7f004100410041004100410041
-- 195:41004180410041804fff7fff400000004fff7fff410041004100410041004100
-- 208:410000434100004141ff0041ff00ffcf00ff4100008041000080410000804100
-- 209:80000041800000418f8f00418080cfff8f8f4141008041410080414100804141
-- 210:808000ff808000ffff8000ff80f08fffff0080ff800080ff800080ff800080ff
-- 211:00f00fff00f00fff00f00fff00f00ffffff00f00fff00f00fff00f00fff00f00
-- 224:0081e3e300422241c242204121c120412142204121422041c2c1202300600000
-- 225:e30000002200414140c341a080214180402141802221c280e3c0208000002000
-- 226:e380c18180412240c1222240a2e32280a2224141c141414180806341e3000080
-- 227:000283c100c1402200a2202241a2e322a2a22022a2c140224120832200000000
-- 240:00806003e38081c000e30220e38081c000806003e300000000e3e3e300000000
-- 241:0180000082808041808000a08080e30080800041808080a080a0000080400000
-- 242:0000008f0381008084c3008084c30080038181a0000000c00000008000000000
-- 243:a040000041a08300418083004140830000e08300000083000000830000000000
-- </SPRITES>
-- <WAVES>
-- 000:00000000ffffffff00000000ffffffff
-- 001:0123456789abcdeffedcba9876543210
-- 002:0123456789abcdef0123456789abcdef
-- </WAVES>
-- <SFX>
-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000304000000000
-- </SFX>
-- <SCREEN>
-- 000:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 001:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff00fff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 002:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 003:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 004:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff00fff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 005:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 006:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff00fff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 007:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 008:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 010:000000000000000000000000000000000000000000000000cc000000cc00cc000000cc00cc000000cc000000cccc0000cc000000cc00cc0000000000cccccccccc00cccccccc000000cccccc0000cc000000cc00cccccccccc0000cccccc0000000000000000000000000000000000000000000000000000
-- 011:000000000000000000000000000000000000000000000000cc000000cc00cc000000cc00cc000000cc000000cccc0000cc000000cc00cc0000000000cccccccccc00cccccccc000000cccccc0000cc000000cc00cccccccccc0000cccccc0000000000000000000000000000000000000000000000000000
-- 012:000000000000000000000000000000000000000000000000cc0000cc0000cccc0000cc00cc000000cc0000cc0000cc00cc0000cc0000cc0000000000cc000000000000cc0000cc00cc000000cc00cccc0000cc00cc0000000000cc000000cc00000000000000000000000000000000000000000000000000
-- 013:000000000000000000000000000000000000000000000000cc0000cc0000cccc0000cc00cc000000cc0000cc0000cc00cc0000cc0000cc0000000000cc000000000000cc0000cc00cc000000cc00cccc0000cc00cc0000000000cc000000cc00000000000000000000000000000000000000000000000000
-- 014:000000000000000000000000000000000000000000000000cc00cc000000cccc0000cc00cc000000cc00cc0000000000cc00cc000000cc0000000000cc000000000000cc0000cc00cc000000cc00cccc0000cc00cc0000000000cc0000000000000000000000000000000000000000000000000000000000
-- 015:000000000000000000000000000000000000000000000000cc00cc000000cccc0000cc00cc000000cc00cc0000000000cc00cc000000cc0000000000cc000000000000cc0000cc00cc000000cc00cccc0000cc00cc0000000000cc0000000000000000000000000000000000000000000000000000000000
-- 016:000000000000000000000000000000000000000000000000cccc00000000cc00cc00cc00cc000000cc00cc0000000000cccc00000000cc0000000000cccccccc000000cccccc0000cc000000cc00cc00cc00cc00cccccccc000000cccccc0000000000000000000000000000000000000000000000000000
-- 017:000000000000000000000000000000000000000000000000cccc00000000cc00cc00cc00cc000000cc00cc0000000000cccc00000000cc0000000000cccccccc000000cccccc0000cc000000cc00cc00cc00cc00cccccccc000000cccccc0000000000000000000000000000000000000000000000000000
-- 018:000000000000000000000000000000000000000000000000cc00cc000000cc0000cccc00cc000000cc00cc0000000000cc00cc000000cc0000000000cc000000000000cc0000cc00cc000000cc00cc0000cccc00cc000000000000000000cc00000000000000000000000000000000000000000000000000
-- 019:000000000000000000000000000000000000000000000000cc00cc000000cc0000cccc00cc000000cc00cc0000000000cc00cc000000cc0000000000cc000000000000cc0000cc00cc000000cc00cc0000cccc00cc000000000000000000cc00000000000000000000000000000000000000000000000000
-- 020:000000000000000000000000000000000000000000000000cc0000cc0000cc0000cccc00cc000000cc0000cc0000cc00cc0000cc0000cc0000000000cc000000000000cc0000cc00cc000000cc00cc0000cccc00cc0000000000cc000000cc00000000000000000000000000000000000000000000000000
-- 021:000000000000000000000000000000000000000000000000cc0000cc0000cc0000cccc00cc000000cc0000cc0000cc00cc0000cc0000cc0000000000cc000000000000cc0000cc00cc000000cc00cc0000cccc00cc0000000000cc000000cc00000000000000000000000000000000000000000000000000
-- 022:000000000000000000000000000000000000000000000000cc000000cc00cc000000cc0000cccccc00000000cccc0000cc000000cc00cccccccccc00cccccccccc00cccccccc000000cccccc0000cc00f000cc00cccccccccc0000cccccc0000000000000000000000000000000000000000000000000000
-- 023:000000000000000000000000000000000000000000000000cc000000cc00cc000000cc0000cccccc00000000cccc0000cc000000cc00cccccccccc00cccccccccc00cccccccc000000cccccc0000cc0ffff0cc00cccccccccc0000cccccc0000000000000000000000000000000000000000000000000000
-- 024:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 025:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff0000fffffff00000000000000000000000000000000000000000000000000000000000000000000
-- 026:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff0000ffffffff0000000000000000000000000000000000000000000000000000000000000000000
-- 027:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0fffffffff0000000000000000000000000000000000000000000000000000000000000000000
-- 028:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000
-- 029:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000ffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000
-- 030:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff00000000000000000000000000000000000000000000000fffffffff0ffffffff00000000000000000000000000000000000000000000000000000000000000000
-- 031:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffff0000000000000000000000000000000000000000000000ffffffff0000ffffff00000000000000000000000000000000000000000000000000000000000000000
-- 032:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0ffff0000000000000000000000000000000000000000000000fffffff0000ffffff00000000000000000000000000000000000000000000000000000000000000000
-- 033:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff000ffff00000000000000000000000000000000000000000000fffffff0000ffff0000000000000000000000000000000000000000000000000000000000000000000
-- 034:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff000fffff00000000000000000000000000000000000000000000fffffffffffff00000000000000000000000000000000000000000000000000000000000000000000
-- 035:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffff000ffffff0000000000000000000000000000000000000000000fffffffffff0000000000000000000000000000000000000000000000000000000000000000000000
-- 036:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffff0000000000000000000000000000000000000000000ffffffff000000000000000000000000000000000000000000000000000000000000000000000000
-- 037:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffff000000000000000000000000000000000000000000fffff00000000000000000000000000000000000000000000000000000000000000000000000000
-- 038:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00fff0000ff000000000000000000000000000000000000000000fff0000000000000000000000000000000000000000000000000000000000000000000000000000
-- 039:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff000ff0000ff000ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 040:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff000ff0000ff000fff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 041:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000fff00ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 042:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 043:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffff0000000000000000000000000000000000088888800000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 044:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff000fffff000000000000000000000000000000000008d8dd880000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 045:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffff000ffff0000000000000000000000000000000000008dd88d88888800000000000000000000000000000000000000000000000000000000000000000000000000000
-- 046:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff000fff00000000000000000000000000000000000008d888888ddd888808880000000000000008880000000000000000000000000000000000000000000000000000
-- 047:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff0ff000000000000000000000000000000000000008d80008d888d8d888d88888800000000008d80000000000000000000000000000000000000000000000000000
-- 048:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffff0000000000000000000000000000000000000008d80008ddddd8d8d8d88ddd888888808888d80000000000000000000000000000000000000000000000000000
-- 049:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff0000000000000000000000000000000000000008880008d88888d8d8d88888d8d8dd888dd8d80000000000000000000000000000ffffffffffffff0000000000
-- 050:00000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000d0000000000f000000000000000000000000000000000000d000000000088ddd88d8d8d88dddd8dd88d8d88dd8000000000000000000000000000ffffffffffffffff000000000
-- 051:0000000000000000000000000000000000000000000000000000d0d000000000000000000000000000000000000000000d0d0000000000000000000011100000000000000000000000d000000000008888888d8d88d888d8d88888d888d8000000000000000000000000000fff00fffffffffff000000000
-- 052:000000000000000000000000000000000000000000000000000d000d000000dd0d00ddd00dd0d000ddd0000000ddd0000d00000000000000000000001d100000000ddd00d0dd000dd0d000000000000000008888888dddd8d80008d88dd8000000000000000000000000000ff0000ffffffffff000000000
-- 053:000000000000000000000000000000000000000000000000000d000d00000d00dd00000d0d0d0d0d000d00000d000d0ddddd000000000001110111111d111100000000d0dd00d0d00dd00000000000000000000000888888d800088dd8d8000000000000000000000000000ff0000ffffffffff000000000
-- 054:000000000000000000000000000000000000000000000000000ddddd00000d00dd00dddd0d0d0d0ddddd00000d000d000d00000000000001d111dddd1d11d100000dddd0d000d0d000d00000000000000000000000000008880000888888000000000000000000000000000fff00fffffffffff000000000
-- 055:000000000000000000000000000000000000000000000000000d000d000000dd0d0d000d0d0d0d0d000000000d000d000d00000000000011111d11111d1d110000d000d0d000d0d00dd00000000000000000000000000000000000000000000000000000000000000000000fffffff00fffffff000000000
-- 056:000000000000000000000000000000000000000000000000000d000d000000000d00dddd0d0d0d00ddd0000000ddd0000d0000001111111dd11dddd11dd11000000dddd0d000d00dd0d00000000000000000000000000000000000000000000000000000000000000000000ffffff0000ffffff000000000
-- 057:00000000000000000000000000000000000000000000000000000000000000ddd0000000000000000000000000000000000000001d1dd111d111111d1d1d1100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff0000ffffff000000000
-- 058:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001dd11d11d11dddd11d11d100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffff00fffffff000000000
-- 059:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d111111d111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffff00fff000000000
-- 060:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d10001ddd10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffff0000ff000000000
-- 061:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d1000111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffff0000ff000000000
-- 062:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffff00fff000000000
-- 063:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff000000000
-- 064:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff0000000000
-- 073:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 074:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 075:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 076:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff0000ffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 077:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff0000ffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 078:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffff0fffffff0ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 079:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff000fff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 080:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff000ffffff0000ff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 081:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff0000fffff0000ff000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 082:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff000fffffffffff000000000000000000000000000000000000ffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 083:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffff000ffff000000000000000000000000000000000000fffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 084:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000fffff0000fff0000000000000000000000000000000000000fff00ffffff0ffff00000000000000000000000000000000000000000000000000000000000000000000000000
-- 085:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffffff000fff000000000000000000000000000000000000ff0000ffff000fff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 086:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff000ffffff00fff0000000000000000000000000000000000000fff000ffff0000ff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 087:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0fffffff0fffff0000000000000000000000000000000000000fff00ffffff000ff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 088:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff0000fff00000000000000000000000000000000000000fffffff00fffffff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 089:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff0000fff00000000000000000000000000000000000000ffffff0000ffffff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 090:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ff000000000000000000000000000000000000000ffffff0000ffffff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 091:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff000000000000000000000000000000000000000fffffff00fffffff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 092:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff0000000000000000000000000000000000000000ff000ffffff00fff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 093:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff000fff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 094:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff000ffff0000ff000000000000000000000000000000000000000000000000000000000000000000000000000
-- 095:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff0ffffff00fff0000000000000000000000000000000000000000000000000000000000000000000000000000
-- 096:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000
-- 097:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000
-- 098:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000000000000000000
-- 100:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dddd00dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 101:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000d00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 102:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000d00d000ddd00d000d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 103:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dddd000d000000d0d000d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 104:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000d000dddd0d00dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffff
-- 105:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000d00d000d00dd0d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 106:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d00000ddd00dddd00000d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 107:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ddd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 108:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 109:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 110:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffff00fffff
-- 111:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff0000ffff
-- 112:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffff0000ffff
-- 113:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffff00fffff
-- 114:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 115:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 116:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 117:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 118:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff
-- 119:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffff
-- 123:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 124:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff00fff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 125:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 126:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 127:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff00fff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 128:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 129:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff00fff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 130:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff0000e000000e00000e000000000ee00ee0000000000e000000000000000e0e0eee0e0000000000ee00000ee000e0000000000e0000e00e000000000e00000000000e000e0e0
-- 131:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff000e0e0e0e0000ee000ee00ee000e000e00e0e0000e00e0e00e00eee000e0e00e00ee000ee000e000e0e00e00eee0000e000e0000eee0ee000ee000e000ee00eee0ee00e0e0
-- 132:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff00fff000e0e0ee00e0e0e0e0e0e00ee00e000e00e0e000eee0ee00e0e0eee00000000e00e0e0e0e000e000e0e00e000e0000e0e0eee0000e00e0e0e0e000e0000ee0eee0e0e00000
-- 133:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff000e0e0e000e0eee0e0e0e0e0e00e000e000ee0000e00e000e0e0e0e00000000e00e0e0ee0000e000e0e00e000e0000e0e00e00000e00e0e0ee0000e000e0e0e0e0e0e00000
-- 134:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000fff00ffffff00fff0000e00e000e000e0e0e0e0eee0eee0eee000e0000e00e0000e00e0e00000000e00e0e00ee0000ee00ee0eee000e0000e000e000000e0e0e00ee000eee0eee0e0e0ee000000
-- 135:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000ffff0000ff00000000000000e000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </SCREEN>
-- <PALETTE>
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE>