Finish up menus (for now)

- Add keyboard settings
 - Save settings (automatic)
 - Menu scroll position (for longer menus)
 - Menu key override function (for redefining keys)
 - Fix bug in "Restart" entry on the "end" menu
 - Add some more comments to improve code readability
This commit is contained in:
Lovetocode999 2021-03-24 16:54:26 -06:00
parent 614a532e28
commit baa4c739e5
3 changed files with 1048 additions and 58 deletions

750
libraries/binser.lua Normal file
View File

@ -0,0 +1,750 @@
-- binser.lua
--[[
Copyright (c) 2016-2019 Calvin Rose
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.
]]
local assert = assert
local error = error
local select = select
local pairs = pairs
local getmetatable = getmetatable
local setmetatable = setmetatable
local type = type
local loadstring = loadstring or load
local concat = table.concat
local char = string.char
local byte = string.byte
local format = string.format
local sub = string.sub
local dump = string.dump
local floor = math.floor
local frexp = math.frexp
local unpack = unpack or table.unpack
-- Lua 5.3 frexp polyfill
-- From https://github.com/excessive/cpml/blob/master/modules/utils.lua
if not frexp then
local log, abs, floor = math.log, math.abs, math.floor
local log2 = log(2)
frexp = function(x)
if x == 0 then return 0, 0 end
local e = floor(log(abs(x)) / log2 + 1)
return x / 2 ^ e, e
end
end
local function pack(...)
return {...}, select("#", ...)
end
local function not_array_index(x, len)
return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x)
end
local function type_check(x, tp, name)
assert(type(x) == tp,
format("Expected parameter %q to be of type %q.", name, tp))
end
local bigIntSupport = false
local isInteger
if math.type then -- Detect Lua 5.3
local mtype = math.type
bigIntSupport = loadstring[[
local char = string.char
return function(n)
local nn = n < 0 and -(n + 1) or n
local b1 = nn // 0x100000000000000
local b2 = nn // 0x1000000000000 % 0x100
local b3 = nn // 0x10000000000 % 0x100
local b4 = nn // 0x100000000 % 0x100
local b5 = nn // 0x1000000 % 0x100
local b6 = nn // 0x10000 % 0x100
local b7 = nn // 0x100 % 0x100
local b8 = nn % 0x100
if n < 0 then
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
end
return char(212, b1, b2, b3, b4, b5, b6, b7, b8)
end]]()
isInteger = function(x)
return mtype(x) == 'integer'
end
else
isInteger = function(x)
return floor(x) == x
end
end
-- Copyright (C) 2012-2015 Francois Perrad.
-- number serialization code modified from https://github.com/fperrad/lua-MessagePack
-- Encode a number as a big-endian ieee-754 double, big-endian signed 64 bit integer, or a small integer
local function number_to_str(n)
if isInteger(n) then -- int
if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data
return char(n + 27)
elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data
n = n + 8192
return char(128 + (floor(n / 0x100) % 0x100), n % 0x100)
elseif bigIntSupport then
return bigIntSupport(n)
end
end
local sign = 0
if n < 0.0 then
sign = 0x80
n = -n
end
local m, e = frexp(n) -- mantissa, exponent
if m ~= m then
return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
elseif m == 1/0 then
if sign == 0 then
return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
else
return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
end
end
e = e + 0x3FE
if e < 1 then -- denormalized numbers
m = m * 2 ^ (52 + e)
e = 0
else
m = (m * 2 - 1) * 2 ^ 52
end
return char(203,
sign + floor(e / 0x10),
(e % 0x10) * 0x10 + floor(m / 0x1000000000000),
floor(m / 0x10000000000) % 0x100,
floor(m / 0x100000000) % 0x100,
floor(m / 0x1000000) % 0x100,
floor(m / 0x10000) % 0x100,
floor(m / 0x100) % 0x100,
m % 0x100)
end
-- Copyright (C) 2012-2015 Francois Perrad.
-- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack
local function number_from_str(str, index)
local b = byte(str, index)
if not b then error("Expected more bytes of input.") end
if b < 128 then
return b - 27, index + 1
elseif b < 192 then
local b2 = byte(str, index + 1)
if not b2 then error("Expected more bytes of input.") end
return b2 + 0x100 * (b - 128) - 8192, index + 2
end
local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8)
if (not b1) or (not b2) or (not b3) or (not b4) or
(not b5) or (not b6) or (not b7) or (not b8) then
error("Expected more bytes of input.")
end
if b == 212 then
local flip = b1 >= 128
if flip then -- negative
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
end
local n = ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) *
0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
if flip then
return (-n) - 1, index + 9
else
return n, index + 9
end
end
if b ~= 203 then
error("Expected number")
end
local sign = b1 > 0x7F and -1 or 1
local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
local n
if e == 0 then
if m == 0 then
n = sign * 0.0
else
n = sign * (m / 2 ^ 52) * 2 ^ -1022
end
elseif e == 0x7FF then
if m == 0 then
n = sign * (1/0)
else
n = 0.0/0.0
end
else
n = sign * (1.0 + m / 2 ^ 52) * 2 ^ (e - 0x3FF)
end
return n, index + 9
end
local function newbinser()
-- unique table key for getting next value
local NEXT = {}
local CTORSTACK = {}
-- NIL = 202
-- FLOAT = 203
-- TRUE = 204
-- FALSE = 205
-- STRING = 206
-- TABLE = 207
-- REFERENCE = 208
-- CONSTRUCTOR = 209
-- FUNCTION = 210
-- RESOURCE = 211
-- INT64 = 212
-- TABLE WITH META = 213
local mts = {}
local ids = {}
local serializers = {}
local deserializers = {}
local resources = {}
local resources_by_name = {}
local types = {}
types["nil"] = function(x, visited, accum)
accum[#accum + 1] = "\202"
end
function types.number(x, visited, accum)
accum[#accum + 1] = number_to_str(x)
end
function types.boolean(x, visited, accum)
accum[#accum + 1] = x and "\204" or "\205"
end
function types.string(x, visited, accum)
local alen = #accum
if visited[x] then
accum[alen + 1] = "\208"
accum[alen + 2] = number_to_str(visited[x])
else
visited[x] = visited[NEXT]
visited[NEXT] = visited[NEXT] + 1
accum[alen + 1] = "\206"
accum[alen + 2] = number_to_str(#x)
accum[alen + 3] = x
end
end
local function check_custom_type(x, visited, accum)
local res = resources[x]
if res then
accum[#accum + 1] = "\211"
types[type(res)](res, visited, accum)
return true
end
local mt = getmetatable(x)
local id = mt and ids[mt]
if id then
local constructing = visited[CTORSTACK]
if constructing[x] then
error("Infinite loop in constructor.")
end
constructing[x] = true
accum[#accum + 1] = "\209"
types[type(id)](id, visited, accum)
local args, len = pack(serializers[id](x))
accum[#accum + 1] = number_to_str(len)
for i = 1, len do
local arg = args[i]
types[type(arg)](arg, visited, accum)
end
visited[x] = visited[NEXT]
visited[NEXT] = visited[NEXT] + 1
-- We finished constructing
constructing[x] = nil
return true
end
end
function types.userdata(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
error("Cannot serialize this userdata.")
end
end
function types.table(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
visited[x] = visited[NEXT]
visited[NEXT] = visited[NEXT] + 1
local xlen = #x
local mt = getmetatable(x)
if mt then
accum[#accum + 1] = "\213"
types.table(mt, visited, accum)
else
accum[#accum + 1] = "\207"
end
accum[#accum + 1] = number_to_str(xlen)
for i = 1, xlen do
local v = x[i]
types[type(v)](v, visited, accum)
end
local key_count = 0
for k in pairs(x) do
if not_array_index(k, xlen) then
key_count = key_count + 1
end
end
accum[#accum + 1] = number_to_str(key_count)
for k, v in pairs(x) do
if not_array_index(k, xlen) then
types[type(k)](k, visited, accum)
types[type(v)](v, visited, accum)
end
end
end
end
types["function"] = function(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
visited[x] = visited[NEXT]
visited[NEXT] = visited[NEXT] + 1
local str = dump(x)
accum[#accum + 1] = "\210"
accum[#accum + 1] = number_to_str(#str)
accum[#accum + 1] = str
end
end
types.cdata = function(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, #accum) then return end
error("Cannot serialize this cdata.")
end
end
types.thread = function() error("Cannot serialize threads.") end
local function deserialize_value(str, index, visited)
local t = byte(str, index)
if not t then return nil, index end
if t < 128 then
return t - 27, index + 1
elseif t < 192 then
local b2 = byte(str, index + 1)
if not b2 then error("Expected more bytes of input.") end
return b2 + 0x100 * (t - 128) - 8192, index + 2
elseif t == 202 then
return nil, index + 1
elseif t == 203 or t == 212 then
return number_from_str(str, index)
elseif t == 204 then
return true, index + 1
elseif t == 205 then
return false, index + 1
elseif t == 206 then
local length, dataindex = number_from_str(str, index + 1)
local nextindex = dataindex + length
if not (length >= 0) then error("Bad string length") end
if #str < nextindex - 1 then error("Expected more bytes of string") end
local substr = sub(str, dataindex, nextindex - 1)
visited[#visited + 1] = substr
return substr, nextindex
elseif t == 207 or t == 213 then
local mt, count, nextindex
local ret = {}
visited[#visited + 1] = ret
nextindex = index + 1
if t == 213 then
mt, nextindex = deserialize_value(str, nextindex, visited)
if type(mt) ~= "table" then error("Expected table metatable") end
end
count, nextindex = number_from_str(str, nextindex)
for i = 1, count do
local oldindex = nextindex
ret[i], nextindex = deserialize_value(str, nextindex, visited)
if nextindex == oldindex then error("Expected more bytes of input.") end
end
count, nextindex = number_from_str(str, nextindex)
for i = 1, count do
local k, v
local oldindex = nextindex
k, nextindex = deserialize_value(str, nextindex, visited)
if nextindex == oldindex then error("Expected more bytes of input.") end
oldindex = nextindex
v, nextindex = deserialize_value(str, nextindex, visited)
if nextindex == oldindex then error("Expected more bytes of input.") end
if k == nil then error("Can't have nil table keys") end
ret[k] = v
end
if mt then setmetatable(ret, mt) end
return ret, nextindex
elseif t == 208 then
local ref, nextindex = number_from_str(str, index + 1)
return visited[ref], nextindex
elseif t == 209 then
local count
local name, nextindex = deserialize_value(str, index + 1, visited)
count, nextindex = number_from_str(str, nextindex)
local args = {}
for i = 1, count do
local oldindex = nextindex
args[i], nextindex = deserialize_value(str, nextindex, visited)
if nextindex == oldindex then error("Expected more bytes of input.") end
end
if not name or not deserializers[name] then
error(("Cannot deserialize class '%s'"):format(tostring(name)))
end
local ret = deserializers[name](unpack(args))
visited[#visited + 1] = ret
return ret, nextindex
elseif t == 210 then
local length, dataindex = number_from_str(str, index + 1)
local nextindex = dataindex + length
if not (length >= 0) then error("Bad string length") end
if #str < nextindex - 1 then error("Expected more bytes of string") end
local ret = loadstring(sub(str, dataindex, nextindex - 1))
visited[#visited + 1] = ret
return ret, nextindex
elseif t == 211 then
local resname, nextindex = deserialize_value(str, index + 1, visited)
if resname == nil then error("Got nil resource name") end
local res = resources_by_name[resname]
if res == nil then
error(("No resources found for name '%s'"):format(tostring(resname)))
end
return res, nextindex
else
error("Could not deserialize type byte " .. t .. ".")
end
end
local function serialize(...)
local visited = {[NEXT] = 1, [CTORSTACK] = {}}
local accum = {}
for i = 1, select("#", ...) do
local x = select(i, ...)
types[type(x)](x, visited, accum)
end
return concat(accum)
end
local function make_file_writer(file)
return setmetatable({}, {
__newindex = function(_, _, v)
file:write(v)
end
})
end
local function serialize_to_file(path, mode, ...)
local file, err = io.open(path, mode)
assert(file, err)
local visited = {[NEXT] = 1, [CTORSTACK] = {}}
local accum = make_file_writer(file)
for i = 1, select("#", ...) do
local x = select(i, ...)
types[type(x)](x, visited, accum)
end
-- flush the writer
file:flush()
file:close()
end
local function writeFile(path, ...)
return serialize_to_file(path, "wb", ...)
end
local function appendFile(path, ...)
return serialize_to_file(path, "ab", ...)
end
local function deserialize(str, index)
assert(type(str) == "string", "Expected string to deserialize.")
local vals = {}
index = index or 1
local visited = {}
local len = 0
local val
while true do
local nextindex
val, nextindex = deserialize_value(str, index, visited)
if nextindex > index then
len = len + 1
vals[len] = val
index = nextindex
else
break
end
end
return vals, len
end
local function deserializeN(str, n, index)
assert(type(str) == "string", "Expected string to deserialize.")
n = n or 1
assert(type(n) == "number", "Expected a number for parameter n.")
assert(n > 0 and floor(n) == n, "N must be a poitive integer.")
local vals = {}
index = index or 1
local visited = {}
local len = 0
local val
while len < n do
local nextindex
val, nextindex = deserialize_value(str, index, visited)
if nextindex > index then
len = len + 1
vals[len] = val
index = nextindex
else
break
end
end
vals[len + 1] = index
return unpack(vals, 1, n + 1)
end
local function readFile(path)
local file, err = io.open(path, "rb")
assert(file, err)
local str = file:read("*all")
file:close()
return deserialize(str)
end
-- Resources
local function registerResource(resource, name)
type_check(name, "string", "name")
assert(not resources[resource],
"Resource already registered.")
assert(not resources_by_name[name],
format("Resource %q already exists.", name))
resources_by_name[name] = resource
resources[resource] = name
return resource
end
local function unregisterResource(name)
type_check(name, "string", "name")
assert(resources_by_name[name], format("Resource %q does not exist.", name))
local resource = resources_by_name[name]
resources_by_name[name] = nil
resources[resource] = nil
return resource
end
-- Templating
local function normalize_template(template)
local ret = {}
for i = 1, #template do
ret[i] = template[i]
end
local non_array_part = {}
-- The non-array part of the template (nested templates) have to be deterministic, so they are sorted.
-- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used
-- in templates. Looking for way around this.
for k in pairs(template) do
if not_array_index(k, #template) then
non_array_part[#non_array_part + 1] = k
end
end
table.sort(non_array_part)
for i = 1, #non_array_part do
local name = non_array_part[i]
ret[#ret + 1] = {name, normalize_template(template[name])}
end
return ret
end
local function templatepart_serialize(part, argaccum, x, len)
local extras = {}
local extracount = 0
for k, v in pairs(x) do
extras[k] = v
extracount = extracount + 1
end
for i = 1, #part do
local name
if type(part[i]) == "table" then
name = part[i][1]
len = templatepart_serialize(part[i][2], argaccum, x[name], len)
else
name = part[i]
len = len + 1
argaccum[len] = x[part[i]]
end
if extras[name] ~= nil then
extracount = extracount - 1
extras[name] = nil
end
end
if extracount > 0 then
argaccum[len + 1] = extras
else
argaccum[len + 1] = nil
end
return len + 1
end
local function templatepart_deserialize(ret, part, values, vindex)
for i = 1, #part do
local name = part[i]
if type(name) == "table" then
local newret = {}
ret[name[1]] = newret
vindex = templatepart_deserialize(newret, name[2], values, vindex)
else
ret[name] = values[vindex]
vindex = vindex + 1
end
end
local extras = values[vindex]
if extras then
for k, v in pairs(extras) do
ret[k] = v
end
end
return vindex + 1
end
local function template_serializer_and_deserializer(metatable, template)
return function(x)
local argaccum = {}
local len = templatepart_serialize(template, argaccum, x, 0)
return unpack(argaccum, 1, len)
end, function(...)
local ret = {}
local args = {...}
templatepart_deserialize(ret, template, args, 1)
return setmetatable(ret, metatable)
end
end
-- Used to serialize classes withh custom serializers and deserializers.
-- If no _serialize or _deserialize (or no _template) value is found in the
-- metatable, then the metatable is registered as a resources.
local function register(metatable, name, serialize, deserialize)
if type(metatable) == "table" then
name = name or metatable.name
serialize = serialize or metatable._serialize
deserialize = deserialize or metatable._deserialize
if (not serialize) or (not deserialize) then
if metatable._template then
-- Register as template
local t = normalize_template(metatable._template)
serialize, deserialize = template_serializer_and_deserializer(metatable, t)
else
-- Register the metatable as a resource. This is semantically
-- similar and more flexible (handles cycles).
registerResource(metatable, name)
return
end
end
elseif type(metatable) == "string" then
name = name or metatable
end
type_check(name, "string", "name")
type_check(serialize, "function", "serialize")
type_check(deserialize, "function", "deserialize")
assert((not ids[metatable]) and (not resources[metatable]),
"Metatable already registered.")
assert((not mts[name]) and (not resources_by_name[name]),
("Name %q already registered."):format(name))
mts[name] = metatable
ids[metatable] = name
serializers[name] = serialize
deserializers[name] = deserialize
return metatable
end
local function unregister(item)
local name, metatable
if type(item) == "string" then -- assume name
name, metatable = item, mts[item]
else -- assume metatable
name, metatable = ids[item], item
end
type_check(name, "string", "name")
mts[name] = nil
if (metatable) then
resources[metatable] = nil
ids[metatable] = nil
end
serializers[name] = nil
deserializers[name] = nil
resources_by_name[name] = nil;
return metatable
end
local function registerClass(class, name)
name = name or class.name
if class.__instanceDict then -- middleclass
register(class.__instanceDict, name)
else -- assume 30log or similar library
register(class, name)
end
return class
end
return {
VERSION = "0.0-8",
-- aliases
s = serialize,
d = deserialize,
dn = deserializeN,
r = readFile,
w = writeFile,
a = appendFile,
serialize = serialize,
deserialize = deserialize,
deserializeN = deserializeN,
readFile = readFile,
writeFile = writeFile,
appendFile = appendFile,
register = register,
unregister = unregister,
registerResource = registerResource,
unregisterResource = unregisterResource,
registerClass = registerClass,
newbinser = newbinser
}
end
return newbinser()

313
main.lua
View File

@ -1,6 +1,7 @@
-- Import libraries
local screen = require( "libraries/shack" )
screen:setDimensions( love.graphics:getWidth(), love.graphics:getHeight() )
local binser = require( "libraries/binser" )
-- Ensure that every play is different by changing the random number seed
math.randomseed( os.time() )
@ -12,6 +13,7 @@ keyCounter = 0
menuList = {}
currentMenu = "main"
menuSelect = 1
menuScroll = 0
-- Player coordinates and scores
player1 = {
@ -22,7 +24,9 @@ player1 = {
yImg = nil,
xVelocity = nil,
yVelocity = nil,
counter = 0,
counter = 0
}
player1Settings = {
ai = false,
aiDifficulty = 2
}
@ -34,7 +38,9 @@ player2 = {
yImg = nil,
xVelocity = nil,
yVelocity = nil,
counter = 0,
counter = 0
}
player2Settings = {
ai = true,
aiDifficulty = 2
}
@ -90,6 +96,56 @@ ball = {
color = { 1, 1, 1 }
}
-- Function to redefine keys w/ user input
function overrideKeys( player, key )
if player == "player1" then
menuList.redKeyboard.keyboardOverride = getCurrentKey
else
menuList.blueKeyboard.keyboardOverride = getCurrentKey
end
if keys[ player ][ key ] ~= nil then
keys[ player ][ key ][1] = "--"
else
keys[ player ][ key ] = { "--", "keyboard" }
end
currentKey = nil
settingKey = key
settingPlayer = player
end
-- Function to test for a key
function getCurrentKey()
if currentKey ~= nil then
keys[settingPlayer][settingKey] = currentKey
if settingPlayer == "player1" then
menuList.redKeyboard.keyboardOverride = nil
else
menuList.blueKeyboard.keyboardOverride = nil
end
end
end
-- Get current key and save it in a variable
function love.textinput(t)
if t == " " then
t = "space"
end
currentKey = { t, "keyboard" }
end
-- Arrow keys (supplement to love.textinput)
function arrowKeys()
if love.keyboard.isDown( "up" ) then
currentKey = { "up", "keyboard" }
elseif love.keyboard.isDown( "down" ) then
currentKey = { "down", "keyboard" }
elseif love.keyboard.isDown( "left" ) then
currentKey = { "left", "keyboard" }
elseif love.keyboard.isDown( "right" ) then
currentKey = { "right", "keyboard" }
end
end
-- Function to create the menus
-- Usage:
-- function another function
@ -101,10 +157,18 @@ function newMenu( objectParameters )
name = objectParameters.name or "menu" .. tostring( menuListLength )
title = objectParameters.title or "menu" .. tostring( menuListLength + 1 )
drawCode = objectParameters.drawCode or drawMenuBackground
keyboardOverride = objectParameters.keyboardOverride or nil
items = objectParameters.items or {}
menuList[name] = items
menuList[name].title = title
menuList[name].drawCode = drawCode
menuList[name].keyboardOverride = keyboardOverride
end
-- Function to save the game settings
function saveSettings()
settings = binser.serialize( keys, player1Settings, player2Settings )
love.filesystem.write( "settings.lua", settings )
end
-- Function to reset the game after every point
@ -115,6 +179,9 @@ function reset()
-- Reset player 1
player1.x = playerDistance + 20
player1.y = playAreaHeight - ( 120 + playerDistance )
player1.counter = 0
player1.xVelocity = nil
player1.yVelocity = nil
-- Reset player 2
player2.x = love.graphics:getWidth() - ( 120 + playerDistance )
@ -142,9 +209,9 @@ function restart()
playerDistance = 40
playAreaHeight = love.graphics:getHeight() - 50
keyCounter = 0
menuList = {}
currentMenu = "main"
currentMenu = nil
menuSelect = 1
menuScroll = 0
-- Player coordinates and scores
player1 = {
@ -155,9 +222,7 @@ function restart()
yImg = nil,
xVelocity = nil,
yVelocity = nil,
counter = 0,
ai = true,
aiDifficulty = 2
counter = 0
}
player2 = {
x = love.graphics:getWidth() - ( 120 + playerDistance ),
@ -167,9 +232,7 @@ function restart()
yImg = nil,
xVelocity = nil,
yVelocity = nil,
counter = 0,
ai = true,
aiDifficulty = 2
counter = 0
}
-- Blocks - 10 for each side, positive if the block is there, zero if it is not
@ -210,7 +273,7 @@ end
-- Test if key is down
function getKeyDown( player, key )
-- If the key is not set for the player, use the default settings
if keys[player][key] == nil then
if keys[player][key] == nil or keys[player][key][1] == "--" then
-- Keyboard test for key input, joystick for gamepad input
if keys["default"][key][2] == "keyboard" then
return( love.keyboard.isDown( keys["default"][key][1] ) )
@ -235,25 +298,36 @@ function love.load()
-- Ball
ball.img = love.graphics.newImage( "assets/ball.png" )
-- Load settings
if love.filesystem.getInfo( "settings.lua" ) ~= nil then
local settings = love.filesystem.read( "settings.lua" )
keys = binser.deserialize( settings )[1]
player1Settings = binser.deserialize( settings )[2]
player2Settings = binser.deserialize( settings )[3]
end
-- Create menus
-- Main menu
newMenu( { name = "main", title = "Breakout Pong", items = {
{ "Play", function() currentMenu = "start" end },
{ "Settings (WIP)", function() currentMenu = "settings" menuSelect = 1 end },
{ "Settings", function()
currentMenu = "settings"
menuSelect = 1
end },
{ "Quit", function() love.event.push( "quit" ) end }
} } )
-- Start menu
newMenu( { name = "start", title = "Breakout Pong", items = {
{
"Red: ${1}",
function() player1.ai = not player1.ai end,
function() return player1.ai and "AI" or "Human" end
function() player1Settings.ai = not player1Settings.ai end,
function() return player1Settings.ai and "AI" or "Human" end
},
{
"Blue: ${1}",
function() player2.ai = not player2.ai end,
function() return player2.ai and "AI" or "Human" end
function() player2Settings.ai = not player2Settings.ai end,
function() return player2Settings.ai and "AI" or "Human" end
},
{ "Start", function() currentMenu = nil end }
} } )
@ -262,42 +336,110 @@ function love.load()
{
"Red AI Difficulty: ${1}",
function()
player1.aiDifficulty = player1.aiDifficulty + 1
if player1.aiDifficulty > 3 then
player1.aiDifficulty = 1
player1Settings.aiDifficulty = player1Settings.aiDifficulty + 1
if player1Settings.aiDifficulty > 3 then
player1Settings.aiDifficulty = 1
end
end,
function()
return ( player1.aiDifficulty == 1 ) and "Easy" or
( player1.aiDifficulty == 2 ) and "Medium" or
( player1.aiDifficulty == 3 ) and "Hard"
return ( player1Settings.aiDifficulty == 1 ) and "Easy" or
( player1Settings.aiDifficulty == 2 ) and "Medium" or
( player1Settings.aiDifficulty == 3 ) and "Hard"
end
},
{
"Blue AI Difficulty: ${1}",
function()
player2.aiDifficulty = player2.aiDifficulty + 1
if player2.aiDifficulty > 3 then
player2.aiDifficulty = 1
player2Settings.aiDifficulty = player2Settings.aiDifficulty + 1
if player2Settings.aiDifficulty > 3 then
player2Settings.aiDifficulty = 1
end
end,
function()
return ( player2.aiDifficulty == 1 ) and "Easy" or
( player2.aiDifficulty == 2 ) and "Medium" or
( player2.aiDifficulty == 3 ) and "Hard"
return ( player2Settings.aiDifficulty == 1 ) and "Easy" or
( player2Settings.aiDifficulty == 2 ) and "Medium" or
( player2Settings.aiDifficulty == 3 ) and "Hard"
end
},
{ "Red Keyboard Settings", function() currentMenu = "redKeyboard" end },
{ "Blue Keyboard Settings", function() currentMenu = "blueKeyboard" end },
{ "Back", function() currentMenu = "main" menuSelect = 2 end }
{ "Red Keyboard Settings", function() currentMenu = "redKeyboard" menuSelect = 1 menuScroll = 0 end },
{ "Blue Keyboard Settings", function() currentMenu = "blueKeyboard" menuSelect = 1 menuScroll = 0 end },
{ "Back", function() currentMenu = "main" menuSelect = 2 menuScroll = 0 end }
} } )
-- Red keyboard menu
newMenu( { name = "redKeyboard", title = "Red Keyboard Settings", items = {
{ "Left", function() return end }
{ "Left: ${1}", function() overrideKeys( "player1", "left" ) end,
function()
return keys.player1.left[1]
end
},
{ "Right: ${1}", function() overrideKeys( "player1", "right" ) end,
function()
return keys.player1.right[1]
end
},
{ "Up: ${1}", function() overrideKeys( "player1", "up" ) end,
function()
return keys.player1.up[1]
end
},
{ "Down: ${1}", function() overrideKeys( "player1", "down" ) end,
function()
return keys.player1.down[1]
end
},
{ "Pause: ${1}", function() overrideKeys( "player1", "pause" ) end,
function()
return keys.player1.pause[1]
end
},
{ "Select: ${1}", function() overrideKeys( "player1", "menuSelect" ) end,
function()
if keys.player1.menuSelect ~= nil then
return keys.player1.menuSelect[1]
else
return keys.default.menuSelect[1]
end
end
},
{ "Back", function() currentMenu = "settings" menuSelect = 3 menuScroll = 0 end }
} } )
-- Blue keyboard menu
newMenu( { name = "blueKeyboard", title = "Blue Keyboard Settings", items = {
{ "Left", function() return end }
{ "Left: ${1}", function() overrideKeys( "player2", "left" ) end,
function()
return keys.player2.left[1]
end
},
{ "Right: ${1}", function() overrideKeys( "player2", "right" ) end,
function()
return keys.player2.right[1]
end
},
{ "Up: ${1}", function() overrideKeys( "player2", "up" ) end,
function()
return keys.player2.up[1]
end
},
{ "Down: ${1}", function() overrideKeys( "player2", "down" ) end,
function()
return keys.player2.down[1]
end
},
{ "Pause: ${1}", function() overrideKeys( "player2", "pause" ) end,
function()
return keys.player2.pause[1]
end
},
{ "Select: ${1}", function() overrideKeys( "player2", "menuSelect" ) end,
function()
if keys.player2.menuSelect ~= nil then
return keys.player2.menuSelect[1]
else
return keys.default.menuSelect[1]
end
end
},
{ "Back", function() currentMenu = "settings" menuSelect = 3 menuScroll = 0 end }
} } )
-- Pause menu
newMenu( { name = "pause", title = "Paused", items = {
@ -338,31 +480,57 @@ function love.load()
end
function menuUpdate()
-- Default to item 1 if a non-valid item is selected
menuLength = 0
for i in ipairs( menuList[ currentMenu ] ) do menuLength = menuLength + 1 end
if menuSelect > menuLength then
menuSelect = 1
elseif menuSelect < 1 then
menuSelect = 1
end
-- Redirect to an override function if specified
if menuList.redKeyboard.keyboardOverride ~= nil and keyCounter > 10 then
menuList.redKeyboard.keyboardOverride()
return
end
if menuList.blueKeyboard.keyboardOverride ~= nil and keyCounter > 10 then
menuList.blueKeyboard.keyboardOverride()
return
end
-- Down/up keys
if ( getKeyDown( "player1", "down" ) or getKeyDown( "player2", "down" ) or getKeyDown( "default", "down" ) )
and keyCounter > 10 then
menuSelect = menuSelect + 1
menuLength = 0
for i in ipairs( menuList[ currentMenu ] ) do menuLength = menuLength + 1 end
if menuSelect > menuLength then
menuSelect = 1
menuScroll = 0
end
-- Scroll down if not visible
if menuSelect > 4 + menuScroll then
menuScroll = menuScroll + 1
end
keyCounter = 0
elseif ( getKeyDown( "player1", "up" ) or getKeyDown( "player2", "up" ) or getKeyDown( "default", "up" ) )
and keyCounter > 10 then
menuSelect = menuSelect - 1
if menuSelect < 1 then
menuLength = 0
for i in pairs( menuList[ currentMenu ] ) do menuLength = menuLength + 1 end
menuSelect = menuLength
menuScroll = ( menuLength - 4 > -1 ) and menuLength - 4 or 0
end
-- Scroll up if not visible
if menuSelect < menuScroll + 1 then
menuScroll = menuScroll - 1
end
keyCounter = 0
end
-- Select the current item
if ( getKeyDown( "player1", "menuSelect" ) or getKeyDown( "player2", "menuSelect" ) )
and keyCounter > 10 then
menuList[ currentMenu ][ menuSelect ][2]()
keyCounter = 0
return
end
-- Increment the key counter
keyCounter = keyCounter + 1
end
@ -409,7 +577,7 @@ function love.update( dt )
return
end
if not player1.ai then
if not player1Settings.ai then
-- Left and right movement for player 1
if getKeyDown( "player1", "left" ) then
player1.x = player1.x - ( ( ( 3 * ball.velocity ) / 2 ) * dt )
@ -425,7 +593,7 @@ function love.update( dt )
else
-- Left and right movement for player 1
if player1.xVelocity == nil then
-- if player1.aiDifficulty
-- if player1Settings.aiDifficulty
if player1.x - 20 > ball.x then
player1.x = player1.x - ( ( ( 3 * ball.velocity ) / 2 ) * dt )
player1.xVelocity = "left"
@ -454,7 +622,7 @@ function love.update( dt )
end
end
if not player1.ai then
if not player1Settings.ai then
-- Up and down movement for player 1
if getKeyDown( "player1", "up" ) then
player1.y = player1.y - ( ( ( 3 * ball.velocity ) / 2 ) * dt )
@ -498,7 +666,7 @@ function love.update( dt )
end
end
if not player2.ai then
if not player2Settings.ai then
-- Left and right movement for player 2
if getKeyDown( "player2", "left" ) then
player2.x = player2.x - ( ( ( 3 * ball.velocity ) / 2 ) * dt )
@ -544,7 +712,7 @@ function love.update( dt )
end
end
if not player2.ai then
if not player2Settings.ai then
-- Up and down movement for player 2
if getKeyDown( "player2", "up" ) then
player2.y = player2.y - ( ( ( 3 * ball.velocity ) / 2 ) * dt )
@ -589,12 +757,12 @@ function love.update( dt )
end
-- Reset the counter(s) and allow the ai(s) to move twice a second
if player1.counter > love.timer.getFPS() / ( player1.aiDifficulty - 0.03 ) then
if player1.counter > love.timer.getFPS() / ( player1Settings.aiDifficulty - 0.03 ) then
player1.counter = 0
player1.xVelocity = nil
player1.yVelocity = nil
end
if player2.counter > love.timer.getFPS() / ( player2.aiDifficulty + 0.03 ) then
if player2.counter > love.timer.getFPS() / ( player2Settings.aiDifficulty + 0.03 ) then
player2.counter = 0
player2.xVelocity = nil
player2.yVelocity = nil
@ -874,23 +1042,49 @@ function love.draw( dt )
love.graphics.setFont( menuFont )
love.graphics.printf( menuList[currentMenu].title, 10, 160, love.graphics:getWidth() - 20, "center" )
for i, menuItem in ipairs(menuList[currentMenu]) do
if menuSelect == i then
menuFont = love.graphics.newFont( 35 )
love.graphics.setFont( menuFont )
love.graphics.setColor( 1, 1, 1 )
else
menuFont = love.graphics.newFont( 30 )
if i > menuScroll and i < 5 + menuScroll then
if menuSelect == i then
menuFont = love.graphics.newFont( 35 )
love.graphics.setFont( menuFont )
love.graphics.setColor( 1, 1, 1 )
else
menuFont = love.graphics.newFont( 30 )
love.graphics.setFont( menuFont )
love.graphics.setColor( 0.5, 0.5, 0.5 )
end
local itemText = menuItem[1]
for j, f in ipairs( menuItem ) do
if j > 2 then
local functionResult = tostring(f())
itemText = itemText:gsub( "${" .. tostring( j - 2 ) .. "}", functionResult )
end
end
love.graphics.printf( "> " .. itemText, 10, 160 + ( i * 80 ) - ( menuScroll * 80 ), love.graphics:getWidth() - 20, "center" )
elseif i == menuScroll then
menuFont = love.graphics.newFont( 25 )
love.graphics.setFont( menuFont )
love.graphics.setColor( 0.5, 0.5, 0.5 )
end
local itemText = menuItem[1]
for j, f in ipairs( menuItem ) do
if j > 2 then
local functionResult = tostring(f())
itemText = itemText:gsub( "${" .. tostring( j - 2 ) .. "}", functionResult )
local itemText = menuItem[1]
for j, f in ipairs( menuItem ) do
if j > 2 then
local functionResult = tostring(f())
itemText = itemText:gsub( "${" .. tostring( j - 2 ) .. "}", functionResult )
end
end
love.graphics.printf( ". . .", 10, 200, love.graphics:getWidth() - 20, "center" )
elseif i == 5 + menuScroll then
menuFont = love.graphics.newFont( 25 )
love.graphics.setFont( menuFont )
love.graphics.setColor( 0.5, 0.5, 0.5 )
local itemText = menuItem[1]
for j, f in ipairs( menuItem ) do
if j > 2 then
local functionResult = tostring(f())
itemText = itemText:gsub( "${" .. tostring( j - 2 ) .. "}", functionResult )
end
end
love.graphics.printf( ". . .", 10, 520, love.graphics:getWidth() - 20, "center" )
end
love.graphics.printf( "> " .. itemText, 10, 160 + ( i * 80 ), love.graphics:getWidth() - 20, "center" )
end
return
end
@ -1129,3 +1323,8 @@ function drawMenuBackground()
love.graphics.polygon( "fill", rectanglePoints )
love.graphics.setColor( 1, 1, 1 )
end
-- Save game settings when exiting
function love.quit()
saveSettings()
end

43
tags
View File

@ -9,22 +9,48 @@
!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/
!_TAG_PROGRAM_URL https://ctags.io/ /official site/
!_TAG_PROGRAM_VERSION 5.9.0 /p5.9.20210110.0/
__newindex libraries/binser.lua /^ __newindex = function(_, _, v)$/;" f
appendFile libraries/binser.lua /^ local function appendFile(path, ...)$/;" f
check_custom_type libraries/binser.lua /^ local function check_custom_type(x, visited, accum)$/;" f
deserialize libraries/binser.lua /^ local function deserialize(str, index)$/;" f
deserializeN libraries/binser.lua /^ local function deserializeN(str, n, index)$/;" f
deserialize_value libraries/binser.lua /^ local function deserialize_value(str, index, visited)$/;" f
drawBackground main.lua /^function drawBackground()$/;" f
drawCode main.lua /^ }, drawCode = function()$/;" f
drawCorners main.lua /^function drawCorners()$/;" f
drawMenuBackground main.lua /^function drawMenuBackground()$/;" f
drawScore main.lua /^function drawScore()$/;" f
frexp libraries/binser.lua /^ frexp = function(x)$/;" f
getCurrentKey main.lua /^function getCurrentKey()$/;" f
getKeyDown main.lua /^function getKeyDown( player, key )$/;" f
itemText main.lua /^ itemText = itemText:gsub( "${" .. tostring( j - 2 ) .. "}", functionResult )$/;" f
isInteger libraries/binser.lua /^ isInteger = function(x)$/;" f
itemText main.lua /^ itemText = itemText:gsub( "${" .. tostring( j - 2 ) .. "}", functionResu/;" f
lerp libraries/shack.lua /^local function lerp(a, b, k) --smooth transitions$/;" f
love.conf conf.lua /^function love.conf(t)$/;" f
love.draw main.lua /^function love.draw( dt )$/;" f
love.load main.lua /^function love.load()$/;" f
love.quit main.lua /^function love.quit()$/;" f
love.textinput main.lua /^function love.textinput(t)$/;" f
love.update main.lua /^function love.update( dt )$/;" f
make_file_writer libraries/binser.lua /^ local function make_file_writer(file)$/;" f
menuUpdate main.lua /^function menuUpdate()$/;" f
newMenu main.lua /^function newMenu( objectParameters )$/;" f
newbinser libraries/binser.lua /^local function newbinser()$/;" f
normalize_template libraries/binser.lua /^ local function normalize_template(template)$/;" f
not_array_index libraries/binser.lua /^local function not_array_index(x, len)$/;" f
number_from_str libraries/binser.lua /^local function number_from_str(str, index)$/;" f
number_to_str libraries/binser.lua /^local function number_to_str(n)$/;" f
overrideKeys main.lua /^function overrideKeys( player, key )$/;" f
pack libraries/binser.lua /^local function pack(...)$/;" f
readFile libraries/binser.lua /^ local function readFile(path)$/;" f
register libraries/binser.lua /^ local function register(metatable, name, serialize, deserialize)$/;" f
registerClass libraries/binser.lua /^ local function registerClass(class, name)$/;" f
registerResource libraries/binser.lua /^ local function registerResource(resource, name)$/;" f
reset main.lua /^function reset()$/;" f
restart main.lua /^function restart()$/;" f
saveSettings main.lua /^function saveSettings()$/;" f
serialize libraries/binser.lua /^ local function serialize(...)$/;" f
serialize_to_file libraries/binser.lua /^ local function serialize_to_file(path, mode, ...)$/;" f
shack:apply libraries/shack.lua /^function shack:apply()$/;" f
shack:getRotation libraries/shack.lua /^function shack:getRotation() return self.rotation end$/;" f
shack:getRotationTarget libraries/shack.lua /^function shack:getRotationTarget() return self.rotationTarget end$/;" f
@ -48,3 +74,18 @@ shack:shake libraries/shack.lua /^function shack:shake(...) return self:setShake
shack:tilt libraries/shack.lua /^function shack:tilt(...) return self:setShear(...) end$/;" f
shack:update libraries/shack.lua /^function shack:update(dt)$/;" f
shack:zoom libraries/shack.lua /^function shack:zoom(...) return self:setScale(...) end$/;" f
template_serializer_and_deserializer libraries/binser.lua /^ local function template_serializer_and_deserializer(metatable, template)$/;" f
templatepart_deserialize libraries/binser.lua /^ local function templatepart_deserialize(ret, part, values, vindex)$/;" f
templatepart_serialize libraries/binser.lua /^ local function templatepart_serialize(part, argaccum, x, len)$/;" f
type_check libraries/binser.lua /^local function type_check(x, tp, name)$/;" f
types.boolean libraries/binser.lua /^ function types.boolean(x, visited, accum)$/;" f
types.cdata libraries/binser.lua /^ types.cdata = function(x, visited, accum)$/;" f
types.number libraries/binser.lua /^ function types.number(x, visited, accum)$/;" f
types.string libraries/binser.lua /^ function types.string(x, visited, accum)$/;" f
types.table libraries/binser.lua /^ function types.table(x, visited, accum)$/;" f
types.thread libraries/binser.lua /^ types.thread = function() error("Cannot serialize threads.") end$/;" f
types.userdata libraries/binser.lua /^ function types.userdata(x, visited, accum)$/;" f
types["nil"] libraries/binser.lua /^ types["nil"] = function(x, visited, accum)$/;" f
unregister libraries/binser.lua /^ local function unregister(item)$/;" f
unregisterResource libraries/binser.lua /^ local function unregisterResource(name)$/;" f
writeFile libraries/binser.lua /^ local function writeFile(path, ...)$/;" f