diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index f9170c9..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,74 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -This is a Jeopardy-style quiz application built with LÖVE (Love2D) game framework in Lua. The project uses two custom libraries: -- **multi** (v16.3.0) - A multitasking library for Lua with coroutine-based threading, system threads, and priority management -- **gui** (GuiManager) - A UI library built on top of multi for LÖVE-based interfaces - -## Running the Application - -```bash -# Run with LÖVE -love . - -# Run tests -lua main-test.lua -``` - -## Architecture - -### Core Modules - -| Module | Path | Purpose | -|--------|------|---------| -| `main.lua` | Root | LÖVE entry point with `love.load()`, `love.update()`, `love.draw()` | -| `board.lua` | Root | Builds the Jeopardy game board UI | -| `task_manager.lua` | Root | Manages game flow and question handling | -| `loader.lua` | Root | Loads question data from YAML files | -| `yaml.lua` | Root | YAML parser | -| `multi/` | Library | Multitasking library with threading support | -| `gui/` | Library | UI framework with frames, buttons, textboxes, images | - -### Key Patterns - -**multi library usage:** -```lua -multi, thread = require("multi"):init({print = true, priority=true}) -GLOBAL, THREAD = require("multi.integration.loveManager"):init() - --- Create threads -multi:newThread("Name", function() - while true do - thread.sleep(1) - -- do work - end -end) - --- Run in love.update() -multi:uManager(dt) -``` - -**gui library usage:** -```lua -local gui = require("gui") -local color = require("gui.core.color") - -gui:setAspectSize(1920, 1080) -local frame = gui:newFrame(x, y, w, h, sx, sy, sw, sh) -frame.color = color.new("#hex") -``` - -### Data Format - -Questions are loaded via `loader.lua` from YAML files with structure: -- `index.yaml` - Contains categories and settings -- `.yaml` - Contains questions for each category - -### Integration Points - -- `multi.integration.loveManager` - Bridges multi threading with LÖVE's event loop -- `gui.update(dt)` and `gui.draw()` - Called from `love.update()` and `love.draw()` -- UI uses aspect ratio scaling (1920x1080 base) diff --git a/ai-anime/index.yml b/ai-anime/index.yml new file mode 100644 index 0000000..2bf52ee --- /dev/null +++ b/ai-anime/index.yml @@ -0,0 +1,20 @@ +settings: + increment: 100 + start: 100 + tiers: 5 + dailyDouble: + enabled: true + minQuestions: 7 + count: 2 + +categories: + - name: shounen + displayName: "Shounen" + - name: villains + displayName: "Villains" + - name: openings + displayName: "Openings" + - name: studios + displayName: "Studios & Staff" + - name: plot-twists + displayName: "Plot Twists" diff --git a/ai-anime/openings.yml b/ai-anime/openings.yml new file mode 100644 index 0000000..70eb532 --- /dev/null +++ b/ai-anime/openings.yml @@ -0,0 +1,33 @@ +questions: + - title: "The iconic opening 'Guren no Yumiya' by Linked Horizon belongs to this anime." + time-limit: 15 + answer: "Attack on Titan" + template: "whatis" + + - title: "Which anime features the opening 'Unravel' by TK from Ling Tosite Sigure?" + answer: 2 + choices: + - "Parasyte" + - "Tokyo Ghoul" + - "Deadman Wonderland" + - "Elfen Lied" + template: "multiplechoice" + + - title: "'Cruel Angel's Thesis' is the legendary opening theme of this 1995 mecha anime." + time-limit: 20 + answer: "Neon Genesis Evangelion" + template: "whatis" + + - title: "Which of these bands performed 'Again', the first opening of Fullmetal Alchemist: Brotherhood?" + answer: 4 + choices: + - "Asian Kung-Fu Generation" + - "Flow" + - "Scandal" + - "YUI" + template: "multiplechoice" + + - title: "This artist performed 'Gurenge', the first opening of Demon Slayer: Kimetsu no Yaiba, which became a massive hit in Japan." + time-limit: 25 + answer: "LiSA" + template: "whatis" diff --git a/ai-anime/plot-twists.yml b/ai-anime/plot-twists.yml new file mode 100644 index 0000000..cc9acc1 --- /dev/null +++ b/ai-anime/plot-twists.yml @@ -0,0 +1,33 @@ +questions: + - title: "In Code Geass, Lelouch's Geass ability compels anyone who makes direct eye contact with him to do this." + time-limit: 20 + answer: "Obey any one order he gives them" + template: "whatis" + + - title: "At the end of Madoka Magica, Madoka's wish rewrites the laws of the universe to do what?" + answer: 2 + choices: + - "Destroy all witches that have ever existed and will ever exist" + - "Erase all witches before they are born, taking herself out of existence" + - "Turn every magical girl back into a normal human" + - "Destroy Kyubey and the incubator race" + template: "multiplechoice" + + - title: "In Fullmetal Alchemist: Brotherhood, Edward and Alphonse discover their mother was not actually revived — what they created during their human transmutation was this." + time-limit: 30 + answer: "A soulless body / failed homunculus (not their mother)" + template: "whatis" + + - title: "Which of these correctly describes the twist at the end of Assassination Classroom?" + answer: 3 + choices: + - "Koro-sensei was always a government weapon and willingly lets himself be killed" + - "The students refuse to kill Koro-sensei and he escapes to space" + - "Koro-sensei asks his students to be the ones to kill him as his final wish" + - "Nagisa kills the minister of defense and Koro-sensei is freed" + template: "multiplechoice" + + - title: "In Gurren Lagann, Simon discovers that the key to piloting Lagann is not a special device but actually this." + time-limit: 25 + answer: "His own drill / fighting spirit (spiral power)" + template: "whatis" diff --git a/ai-anime/shounen.yml b/ai-anime/shounen.yml new file mode 100644 index 0000000..f61e541 --- /dev/null +++ b/ai-anime/shounen.yml @@ -0,0 +1,33 @@ +questions: + - title: "This is the name of the signature technique Naruto Uzumaki uses to create physical copies of himself." + time-limit: 15 + answer: "Shadow Clone Jutsu" + template: "whatis" + + - title: "In Dragon Ball Z, Goku achieved this transformation for the first time during his battle against Frieza on Namek." + time-limit: 20 + answer: "Super Saiyan" + template: "whatis" + + - title: "Which of these is the name of Ichigo Kurosaki's Zanpakuto in Bleach?" + answer: 3 + choices: + - "Zangetsu" + - "Senbonzakura" + - "Tensa Zangetsu" + - "Ryujin Jakka" + template: "multiplechoice" + + - title: "In Hunter x Hunter, this is the name of the system of power that allows users to manipulate their life energy." + time-limit: 25 + answer: "Nen" + template: "whatis" + + - title: "Which of these Devil Fruits did Monkey D. Luffy eat, giving him his rubber powers?" + answer: 2 + choices: + - "Mera Mera no Mi" + - "Gomu Gomu no Mi" + - "Hana Hana no Mi" + - "Bara Bara no Mi" + template: "multiplechoice" diff --git a/ai-anime/studios.yml b/ai-anime/studios.yml new file mode 100644 index 0000000..d3e3c85 --- /dev/null +++ b/ai-anime/studios.yml @@ -0,0 +1,33 @@ +questions: + - title: "This animation studio, co-founded by Hayao Miyazaki, produced Spirited Away, My Neighbor Totoro, and Princess Mononoke." + time-limit: 15 + answer: "Studio Ghibli" + template: "whatis" + + - title: "Which studio produced Fullmetal Alchemist: Brotherhood, Sword of the Stranger, and Mob Psycho 100?" + answer: 1 + choices: + - "Bones" + - "Madhouse" + - "White Fox" + - "A-1 Pictures" + template: "multiplechoice" + + - title: "This director is known for helming Cowboy Bebop, Samurai Champloo, and Space Dandy." + time-limit: 25 + answer: "Shinichiro Watanabe" + template: "whatis" + + - title: "Which studio is responsible for the long-running adaptations of One Piece and Dragon Ball Super?" + answer: 3 + choices: + - "Pierrot" + - "Sunrise" + - "Toei Animation" + - "TMS Entertainment" + template: "multiplechoice" + + - title: "This legendary animator, known as 'the god of manga', created Astro Boy and is considered the father of modern anime and manga." + time-limit: 20 + answer: "Osamu Tezuka" + template: "whatis" diff --git a/ai-anime/villains.yml b/ai-anime/villains.yml new file mode 100644 index 0000000..6e1e652 --- /dev/null +++ b/ai-anime/villains.yml @@ -0,0 +1,33 @@ +questions: + - title: "This Death Note villain is a world-famous detective who refuses to sit in a normal chair." + time-limit: 15 + answer: "L" + template: "whatis" + + - title: "In Fullmetal Alchemist: Brotherhood, Father created seven Homunculi. Which of these is NOT one of them?" + answer: 4 + choices: + - "Gluttony" + - "Envy" + - "Wrath" + - "Malice" + template: "multiplechoice" + + - title: "In Attack on Titan, this character orchestrated the Rumbling to trample the world outside Paradis Island." + time-limit: 20 + answer: "Eren Yeager" + template: "whatis" + + - title: "Which of these best describes Griffith's betrayal in Berserk, the event known as the Eclipse?" + answer: 1 + choices: + - "He sacrificed the Band of the Hawk to be reborn as the demon king Femto" + - "He defected to the Midland king and had Guts executed" + - "He destroyed Guts' sword to prevent him from following him" + - "He erased everyone's memories of the Band of the Hawk" + template: "multiplechoice" + + - title: "Dio Brando's Stand in JoJo's Bizarre Adventure Part 3 has the ability to stop time. This is its name." + time-limit: 20 + answer: "The World" + template: "whatis" diff --git a/anime/assets/beer.jpg b/anime/assets/beer.jpg new file mode 100644 index 0000000..bc24dc0 Binary files /dev/null and b/anime/assets/beer.jpg differ diff --git a/anime/assets/gate.jpg b/anime/assets/gate.jpg new file mode 100644 index 0000000..3a0edba Binary files /dev/null and b/anime/assets/gate.jpg differ diff --git a/anime/index.yaml b/anime/index.yml similarity index 83% rename from anime/index.yaml rename to anime/index.yml index b68a36d..7a917a0 100644 --- a/anime/index.yaml +++ b/anime/index.yml @@ -1,25 +1,24 @@ -settings: - increment: 100 # how much each tier increases by - start: 100 # starting amount see tier's comment - tiers: 5 # 5 tiers 100, 200, 300, 400, 500 based on above settings - dailyDouble: - enabled: true # should daily double be enabled? - minQuestions: 7 # how many questions need to be answered before a daily double can appear? - count: 2 # how many daily doubles should there be? -categories: # the name of each category should correspond to a yaml file with the same name - - name: shounen # name must be alphanumeric, cannot start with a number or contain special characters "-" and "." are ok if they are not at the beginning - displayName: "Shounen" # if blank will use name - image: "assets/14018-3193093789.gif" # if set will display image instead of name, categories are referenced by name which is required - - name: openings - displayName: "Openings" # if blank will use name - - name: sadness - displayName: "Sadness" # if blank will use name - - name: random - displayName: Random # if blank will use name - - name: you-would-have-though - displayName: "You would have\nthought" # if blank will use name - - name: wtf-is-that - displayName: "Wtf is that!?" # if blank will use name - - name: owo - displayName: "owo" # if blank will use name - # image: "assets/anime-gif-boobs-funny-2710211137.gif" +settings: + increment: 100 # how much each tier increases by + start: 100 # starting amount see tier's comment + tiers: 5 # 5 tiers 100, 200, 300, 400, 500 based on above settings + dailyDouble: + enabled: true # should daily double be enabled? + minQuestions: 7 # how many questions need to be answered before a daily double can appear? + count: 2 # how many daily doubles should there be? +categories: # the name of each category should correspond to a yaml file with the same name + - name: shounen # name must be alphanumeric, cannot start with a number or contain special characters "-" and "." are ok if they are not at the beginning + displayName: "Shounen" # if blank will use name + #image: "assets/14018-3193093789.gif" # if set will display image instead of name, categories are referenced by name which is required + - name: openings + displayName: "Openings" # if blank will use name + - name: sadness + displayName: "Sadness" # if blank will use name + - name: random + displayName: Random # if blank will use name + - name: you-would-have-though + displayName: "You would have\nthought" # if blank will use name + - name: wtf-is-that + displayName: "Wtf is that!?" # if blank will use name + - name: owo + displayName: "owo" # if blank will use name diff --git a/anime/shounen.yaml b/anime/shounen.yaml deleted file mode 100644 index 21f6617..0000000 --- a/anime/shounen.yaml +++ /dev/null @@ -1,38 +0,0 @@ -questions: # expects a list equal to the tier - - title: "Name at least 10 of the characters here" - time-limit: 60 # If omitted there is no time limit - answer: 10 - image: "anime/assets/shounen_name_characters.png" - # Types: builtin - # - WhatIs (default), - # - MultipleChoice, - # - GuessSound (.wav, .mp3, .ogg, .oga, .ogv), - # - GuessImage (.png, jpg), - # - GuessVideo (.ogv) - template: "one-image" # template to use - - title: "2+2=" - time-limit: 10 # If omitted there is no time limit - answer: 4 - imageA: "" - imageB: "" - template: "imageword" # template to use - - title: "4+4=" - time-limit: 10 # If omitted there is no time limit - answer: 8 - # Types: builtin - # - WhatIs (default), - # - MultipleChoice, - # - GuessSound (.wav, .mp3, .ogg, .oga, .ogv), - # - GuessImage (.png, jpg), - # - GuessVideo (.ogv) - template: "whatis" # template to use - - title: "8+8=" - time-limit: 10 # If omitted there is no time limit - answer: 16 - # Types: builtin - # - WhatIs (default), - # - MultipleChoice, - # - GuessSound (.wav, .mp3, .ogg, .oga, .ogv), - # - GuessImage (.png, jpg), - # - GuessVideo (.ogv) - template: "whatis" # template to use \ No newline at end of file diff --git a/anime/shounen.yml b/anime/shounen.yml new file mode 100644 index 0000000..3af0c64 --- /dev/null +++ b/anime/shounen.yml @@ -0,0 +1,28 @@ +questions: # expects a list equal to the tier + - title: "Name at least 10 of the characters here" + time-limit: 60 # If omitted there is no time limit + answer: 10 + image: "anime/assets/shounen_name_characters.png" + template: "one-image" # template to use + display-answer: true # displays the correct answer before returning to the main screen + - title: "What anime is this?" + time-limit: 30 # If omitted there is no time limit + answer: "steins;gate" + imageA: "anime/assets/beer.jpg" + imageB: "anime/assets/gate.jpg" + display-answer: true # displays the correct answer before returning to the main screen + template: "imageword" # template to use + - title: "What is 2+2" + answer: 2 # positon of correct answer + choices: + - "22" + - "4" # this is 2 + - "9000" + - "-4" + - "wack" + template: "multiplechoice" # template to use + - title: "Who directed the groundbreaking film 'Akira',\nwhich helped introduce anime to Western audiences?" + time-limit: 10 # If omitted there is no time limit + answer: "Katsuhiro Otomo" + template: "whatis" # template to use + display-answer: true # displays the correct answer before returning to the main screen \ No newline at end of file diff --git a/assets/anime-gif-boobs-funny-2710211137.gif b/assets/anime-gif-boobs-funny-2710211137.gif deleted file mode 100644 index 2207195..0000000 Binary files a/assets/anime-gif-boobs-funny-2710211137.gif and /dev/null differ diff --git a/assets/anime-girls-boobs-minimalism-1455325-1259150827.jpg b/assets/anime-girls-boobs-minimalism-1455325-1259150827.jpg deleted file mode 100644 index 841d395..0000000 Binary files a/assets/anime-girls-boobs-minimalism-1455325-1259150827.jpg and /dev/null differ diff --git a/assets/checked.png b/assets/checked.png new file mode 100644 index 0000000..d435be2 Binary files /dev/null and b/assets/checked.png differ diff --git a/assets/convert.lua b/assets/convert.lua deleted file mode 100644 index 09b03bd..0000000 --- a/assets/convert.lua +++ /dev/null @@ -1,169 +0,0 @@ -local json = require("json") -function tprint (tbl, indent) - if not indent then indent = 0 end - for k, v in pairs(tbl) do - formatting = string.rep(" ", indent) .. k .. ": " - if type(v) == "table" then - print(formatting) - tprint(v, indent+1) - else - print(formatting .. tostring(v)) - end - end -end - -function ListItems(dir) - if Dir_Exist(dir) then - temp=List_Files(dir) -- current directory if blank - if GetDirectory(dir)=="C:\\\n" then - a,b=string.find(temp,"C:\\",1,true) - a=a+2 - else - a,b=string.find(temp,"..",1,true) - end - temp=string.sub(temp,a+2) - list=StringLineToTable(temp) - temp=string.sub(temp,1,list[#list-2]) - slist=lines(temp) - table.remove(slist,1) - table.remove(slist,#slist) - temp={} - temp2={} - for i=1,#slist do - table.insert(temp,string.sub(slist[i],40,-1)) - end - return temp - else - print("Directory does not exist") - return nil - end -end - -function lines(str) - local t = {} - local function helper(line) table.insert(t, line) return "" end - helper((str:gsub("(.-)\r?\n", helper))) - return t -end - -function StringLineToTable(s) - local t = {} -- table to store the indices - local i = 0 - while true do - i = string.find(s, "\n", i+1) -- find 'next' newline - if i == nil then return t end - table.insert(t, i) - end -end - -function GetDirectory(dir,flop) - s=List_Files(dir) - drive=string.sub(string.match(s,"drive.."),-1) - local t = {} -- table to store the indices - local i = 0 - while true do - i = string.find(s, "\n", i+1) -- find 'next' newline - if i == nil then - a,b=string.find(s,drive..":\\",1,true) - main = string.gsub(string.sub(s,a,t[4]), "\n", "") - if flop then - main=main:gsub("%\\", "/") - end - return main - end - table.insert(t, i) - end -end - -function List_Files(dir) - if not(dir) then dir="" end - local f = io.popen("dir \""..dir.."\"") - if f then - return f:read("*a") - else - print("failed to read") - end -end - -function GetFiles(dir) - local temp2={} - local dirs=ListItems(dir) - for i=1,#dirs do - if Dir_Exist(string.gsub(GetDirectory(dir).."\\"..dirs[i], "\n", "")) then - else - table.insert(temp2,dirs[i]) - end - end - return temp2 -end - -function Dir_Exist(strFolderName) - local fileHandle, strError = io.open(strFolderName.."\\*.*","r") - if fileHandle ~= nil then - io.close(fileHandle) - return true - else - if string.match(strError,"No such file or directory") then - return false - else - return true - end - end -end - -function GetDirectories(dir) - temp2={} - dirs=ListItems(dir) - for i=1,#dirs do - if Dir_Exist(string.gsub(GetDirectory(dir).."\\"..dirs[i], "\n", "")) then - table.insert(temp2,dirs[i]) - end - end - return temp2 -end - -local id = 0 - -local cards = {} -for _, dir in pairs(GetDirectories("cards")) do - for _, card in pairs(GetFiles("cards/"..dir)) do - id = id + 1 - print("Processing card: cards/"..dir.."/"..card) - table.insert(cards,{ - ID = id, - UUID = "", -- Generated during game - Name = (card:gsub("_"," "):gsub(".png","")), - Source = "assets/cards/" .. dir .. "/" .. card, - Count = 0, - Default_Pack = dir, - Rune_Cost = 0, - Attack_Cost = 0, - Insight_Cost = 0, - Night = false, - Day = false, - Honor = 0, - Type = { - Base = "Hero/Construct/Monster/Dreamscape/Temple", - Faction = "Mechana,Void,Enlightened,Lifebound", - Token = false, - Ongoing = false, - Transformation_Source = "", - }, - Unbanishable = false, - Effects = { - "Gain(#,'Rune,Attack,Insight,Honor',target)", - "Draw(#)", - "Shuffle(#)", - "Discard(#, target)", - "AdjustCost(#,'Rune,Attack,Insight',target)", - "Aquire(#,'Rune,Attack,Insight')", - "XasX('Rune,Attack,Insight', 'Rune,Attack,Insight')", - "Temple(type)", - "Banish(#, target)", - "TopDeck(#,target)" - } - }) - end -end - -json.encode_file("card_data/cards.json", cards) \ No newline at end of file diff --git a/assets/double.jpg b/assets/double.jpg new file mode 100644 index 0000000..60befa5 Binary files /dev/null and b/assets/double.jpg differ diff --git a/double.mp3 b/assets/double.mp3 similarity index 100% rename from double.mp3 rename to assets/double.mp3 diff --git a/assets/json.lua b/assets/json.lua deleted file mode 100644 index c90273f..0000000 --- a/assets/json.lua +++ /dev/null @@ -1,400 +0,0 @@ --- --- json.lua --- --- Copyright (c) 2020 rxi --- --- 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 json = { _version = "0.1.2" } - -------------------------------------------------------------------------------- --- Encode -------------------------------------------------------------------------------- - -local encode - -local escape_char_map = { - [ "\\" ] = "\\", - [ "\"" ] = "\"", - [ "\b" ] = "b", - [ "\f" ] = "f", - [ "\n" ] = "n", - [ "\r" ] = "r", - [ "\t" ] = "t", -} - -local escape_char_map_inv = { [ "/" ] = "/" } -for k, v in pairs(escape_char_map) do - escape_char_map_inv[v] = k -end - - -local function escape_char(c) - return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) -end - - -local function encode_nil(val) - return "null" -end - - -local function encode_table(val, stack) - local res = {} - stack = stack or {} - - -- Circular reference? - if stack[val] then error("circular reference") end - - stack[val] = true - - if rawget(val, 1) ~= nil or next(val) == nil then - -- Treat as array -- check keys are valid and it is not sparse - local n = 0 - for k in pairs(val) do - if type(k) ~= "number" then - error("invalid table: mixed or invalid key types") - end - n = n + 1 - end - if n ~= #val then - error("invalid table: sparse array") - end - -- Encode - for i, v in ipairs(val) do - table.insert(res, encode(v, stack)) - end - stack[val] = nil - return "[" .. table.concat(res, ",") .. "]" - - else - -- Treat as an object - for k, v in pairs(val) do - if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") - end - table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) - end - stack[val] = nil - return "{" .. table.concat(res, ",") .. "}" - end -end - - -local function encode_string(val) - return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' -end - - -local function encode_number(val) - -- Check for NaN, -inf and inf - if val ~= val or val <= -math.huge or val >= math.huge then - error("unexpected number value '" .. tostring(val) .. "'") - end - return string.format("%.14g", val) -end - - -local type_func_map = { - [ "nil" ] = encode_nil, - [ "table" ] = encode_table, - [ "string" ] = encode_string, - [ "number" ] = encode_number, - [ "boolean" ] = tostring, -} - - -encode = function(val, stack) - local t = type(val) - local f = type_func_map[t] - if f then - return f(val, stack) - end - error("unexpected type '" .. t .. "'") -end - - -function json.encode(val) - return ( encode(val) ) -end - -function json.encode_file(file, val) - local f = io.open(file, "w") - f:write(json.encode(val)) - f:close() -end - -function json.decode_file(file) - local f = io.open(file, "r") - local str = f:read("*a") - f:close() - return json.decode(str) -end - -------------------------------------------------------------------------------- --- Decode -------------------------------------------------------------------------------- - -local parse - -local function create_set(...) - local res = {} - for i = 1, select("#", ...) do - res[ select(i, ...) ] = true - end - return res -end - -local space_chars = create_set(" ", "\t", "\r", "\n") -local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") -local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") -local literals = create_set("true", "false", "null") - -local literal_map = { - [ "true" ] = true, - [ "false" ] = false, - [ "null" ] = nil, -} - - -local function next_char(str, idx, set, negate) - for i = idx, #str do - if set[str:sub(i, i)] ~= negate then - return i - end - end - return #str + 1 -end - - -local function decode_error(str, idx, msg) - local line_count = 1 - local col_count = 1 - for i = 1, idx - 1 do - col_count = col_count + 1 - if str:sub(i, i) == "\n" then - line_count = line_count + 1 - col_count = 1 - end - end - error( string.format("%s at line %d col %d", msg, line_count, col_count) ) -end - - -local function codepoint_to_utf8(n) - -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa - local f = math.floor - if n <= 0x7f then - return string.char(n) - elseif n <= 0x7ff then - return string.char(f(n / 64) + 192, n % 64 + 128) - elseif n <= 0xffff then - return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) - elseif n <= 0x10ffff then - return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, - f(n % 4096 / 64) + 128, n % 64 + 128) - end - error( string.format("invalid unicode codepoint '%x'", n) ) -end - - -local function parse_unicode_escape(s) - local n1 = tonumber( s:sub(1, 4), 16 ) - local n2 = tonumber( s:sub(7, 10), 16 ) - -- Surrogate pair? - if n2 then - return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) - else - return codepoint_to_utf8(n1) - end -end - - -local function parse_string(str, i) - local res = "" - local j = i + 1 - local k = j - - while j <= #str do - local x = str:byte(j) - - if x < 32 then - decode_error(str, j, "control character in string") - - elseif x == 92 then -- `\`: Escape - res = res .. str:sub(k, j - 1) - j = j + 1 - local c = str:sub(j, j) - if c == "u" then - local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) - or str:match("^%x%x%x%x", j + 1) - or decode_error(str, j - 1, "invalid unicode escape in string") - res = res .. parse_unicode_escape(hex) - j = j + #hex - else - if not escape_chars[c] then - decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") - end - res = res .. escape_char_map_inv[c] - end - k = j + 1 - - elseif x == 34 then -- `"`: End of string - res = res .. str:sub(k, j - 1) - return res, j + 1 - end - - j = j + 1 - end - - decode_error(str, i, "expected closing quote for string") -end - - -local function parse_number(str, i) - local x = next_char(str, i, delim_chars) - local s = str:sub(i, x - 1) - local n = tonumber(s) - if not n then - decode_error(str, i, "invalid number '" .. s .. "'") - end - return n, x -end - - -local function parse_literal(str, i) - local x = next_char(str, i, delim_chars) - local word = str:sub(i, x - 1) - if not literals[word] then - decode_error(str, i, "invalid literal '" .. word .. "'") - end - return literal_map[word], x -end - - -local function parse_array(str, i) - local res = {} - local n = 1 - i = i + 1 - while 1 do - local x - i = next_char(str, i, space_chars, true) - -- Empty / end of array? - if str:sub(i, i) == "]" then - i = i + 1 - break - end - -- Read token - x, i = parse(str, i) - res[n] = x - n = n + 1 - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "]" then break end - if chr ~= "," then decode_error(str, i, "expected ']' or ','") end - end - return res, i -end - - -local function parse_object(str, i) - local res = {} - i = i + 1 - while 1 do - local key, val - i = next_char(str, i, space_chars, true) - -- Empty / end of object? - if str:sub(i, i) == "}" then - i = i + 1 - break - end - -- Read key - if str:sub(i, i) ~= '"' then - decode_error(str, i, "expected string for key") - end - key, i = parse(str, i) - -- Read ':' delimiter - i = next_char(str, i, space_chars, true) - if str:sub(i, i) ~= ":" then - decode_error(str, i, "expected ':' after key") - end - i = next_char(str, i + 1, space_chars, true) - -- Read value - val, i = parse(str, i) - -- Set - res[key] = val - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "}" then break end - if chr ~= "," then decode_error(str, i, "expected '}' or ','") end - end - return res, i -end - - -local char_func_map = { - [ '"' ] = parse_string, - [ "0" ] = parse_number, - [ "1" ] = parse_number, - [ "2" ] = parse_number, - [ "3" ] = parse_number, - [ "4" ] = parse_number, - [ "5" ] = parse_number, - [ "6" ] = parse_number, - [ "7" ] = parse_number, - [ "8" ] = parse_number, - [ "9" ] = parse_number, - [ "-" ] = parse_number, - [ "t" ] = parse_literal, - [ "f" ] = parse_literal, - [ "n" ] = parse_literal, - [ "[" ] = parse_array, - [ "{" ] = parse_object, -} - - -parse = function(str, idx) - local chr = str:sub(idx, idx) - local f = char_func_map[chr] - if f then - return f(str, idx) - end - decode_error(str, idx, "unexpected character '" .. chr .. "'") -end - - -function json.decode(str) - if type(str) ~= "string" then - error("expected argument of type string, got " .. type(str)) - end - local res, idx = parse(str, next_char(str, 1, space_chars, true)) - idx = next_char(str, idx, space_chars, true) - if idx <= #str then - decode_error(str, idx, "trailing garbage") - end - return res -end - - -return json \ No newline at end of file diff --git a/assets/plus.png b/assets/plus.png new file mode 100644 index 0000000..51723b5 Binary files /dev/null and b/assets/plus.png differ diff --git a/timesup.mp3 b/assets/timesup.mp3 similarity index 100% rename from timesup.mp3 rename to assets/timesup.mp3 diff --git a/assets/unchecked.png b/assets/unchecked.png new file mode 100644 index 0000000..79216c5 Binary files /dev/null and b/assets/unchecked.png differ diff --git a/board.lua b/board.lua index 4b69e17..187291c 100644 --- a/board.lua +++ b/board.lua @@ -6,8 +6,8 @@ local fmt = require("fmt") local timer = require("utils") local multi, thread = require("multi"):init() -local timesup = love.audio.newSource("timesup.mp3", "static") -local double = love.audio.newSource("double.mp3", "static") +local timesup = love.audio.newSource("assets/timesup.mp3", "static") +local double = love.audio.newSource("assets/double.mp3", "static") local boardUpdater = gui:getProcessor():newProcessor("board-updater") boardUpdater.Start() @@ -37,17 +37,88 @@ end gui.Events.OnResized(resizeFonts) +local completed_questions = 0 +local min_questions = 0 +local dd_count = 0 +local dd_enabled = false +local applied_dd = false +local board, question + +function gui:cleanup() + for i = #self.children, 1, -1 do + self.children[i]:destroy() + end + self.children = {} + completed_questions = completed_questions + 1 +end + +local function pickUniqueIndices(t, count) + assert(#t >= count, "Not enough elements to pick " .. count .. " unique items") + + local indices = {} + for i = 1, #t do indices[i] = i end + + -- partial Fisher-Yates shuffle + for i = 1, count do + local j = math.random(i, #indices) + indices[i], indices[j] = indices[j], indices[i] + end + + return unpack(indices, 1, count) +end + +local function pickFiltered(items, count) + -- Filter to only elements whose text contains "$" + local filtered = {} + for _, item in ipairs(items) do + if item.text:find("%$") then + filtered[#filtered + 1] = item + end + end + + -- Use pickUniqueIndices to select from filtered results + local picks = { pickUniqueIndices(filtered, count) } + local results = {} + for _, idx in ipairs(picks) do + results[#results + 1] = filtered[idx] + end + + return unpack(results) +end + +function applyDD() + if not applied_dd and completed_questions > min_questions then + local dd = 0 + local dd_list = {} + local dds = {pickFiltered(board.children, dd_count)} + for i,v in pairs(dds) do + print(i,v) + v.isDouble = true + end + applied_dd = true + end +end + local function buildBoard(frame, path) local data = loader:new(path) + board, question, dailydouble = frame:newFrame(), frame:newFrame(), frame:newImageLabel("assets/double.jpg") index = data.index - local board, question = frame:newFrame(), frame:newFrame() board:fullFrame() question:fullFrame() + dailydouble:fullFrame() + dailydouble.visible = false question.visible = false question.color = color.new("#060ce9") local tiers = index.settings.tiers or 5 local start = index.settings.start or 100 local inc = index.settings.increment or 100 + + if index.settings.dailyDouble and index.settings.dailyDouble.enabled then + local dd = index.settings.dailyDouble + dd_enabled = dd.enabled + min_questions = dd.minQuestions + dd_count = dd.count + end for cat,v in pairs(index.categories) do local c if v.image then @@ -59,6 +130,8 @@ local function buildBoard(frame, path) c.color = color.new("#060ce9") end + c.UUID = multi.generate_uuid7() -- Each otpion gets a unique UUID + img = c:newImageButton("assets/placeholder.jpg") img.visibility = 0 @@ -78,6 +151,9 @@ local function buildBoard(frame, path) t.price = start + inc*(tier-1) t:OnReleased(boardUpdater:newFunction(function(self) if self.text == "" then return end + if dd_enabled then + applyDD() -- check and run DD if conditions meet + end if index.categories[cat].questions == nil then fmt.Printf("Question not defined: File: %v Category: %v - %v\n",path,index.categories[cat].name,start + inc*(tier-1)) return end local q = index.categories[cat].questions[self.index] if q == nil then fmt.Printf("Question contains no data: File: %v Category: %v Tier: %v\n",path,index.categories[cat].name,start + inc*(tier-1)) return end @@ -87,35 +163,56 @@ local function buildBoard(frame, path) local player = GetActivePlayer() local tm local stop - fmt.Printf("Question: %v \nAnswer: %v\n",q["title"],q["answer"]) - if q["time-limit"] then - tm = timer.startTimer({duration = q["time-limit"]}) - tm.OnStop(function() - -- Make sound? Subtract if daily double - if stop then return end - timesup:play() - end) - end - template.index(question, q, function(ans) - player = GetActivePlayer() - tm:Cleanup() - stop = true - if ans then - player:Add(self.price) - question.visible = false - elseif ans == false then - player:Add(-self.price) - else - question.visible = false - end -- nil is a valid option where you weren't right or wrong, you just skipped - self.text = "" - end) - boardUpdater:newThread("QuestionUpdater",function() - while true do - template.update(dt) - thread.yield() - if self.text == "" then return end + fmt.Printf("--------------------\nQuestion: %v \nAnswer: %v\n--------------------\n",q["title"],q["answer"]) + local mul = 1 + boardUpdater:newThread(function() + if self.isDouble then + mul = 2 + double:play() + dailydouble.visible = true + thread.hold(function() + return not double:isPlaying() + end) + dailydouble.visible = false end + if q["time-limit"] then + tm = timer.startTimer({duration = q["time-limit"]}) + tm.OnStop(function() + -- Make sound? Subtract if daily double + if stop then return end + timesup:play() + end) + end + local finished = false + template.index(question, q, function(ans) + if finished then return end + player = GetActivePlayer() + if tm then + tm:Cleanup() + end + stop = true + if ans then + player:Add(self.price*mul) + finished = true + question.visible = false + question:cleanup() + elseif ans == false then + player:Add(-self.price*mul) + player = GetNextPlayer() + else + finished = true + question.visible = false + question:cleanup() + end -- nil is a valid option where you weren't right or wrong, you just skipped + self.text = "" + end) + boardUpdater:newThread("QuestionUpdater",function() + while true do + template.update(dt) + thread.yield() + if self.text == "" then return end + end + end) end) end)) table.insert(manage,t) @@ -142,6 +239,10 @@ function LoadTemplate(name, path) error(err) end + local timer = function(wait, callback) + multi:newAlarm(wait):OnRing(callback) + end + local env = { -- lua built-ins except io/os code execution pairs=pairs, print=print, @@ -158,11 +259,17 @@ function LoadTemplate(name, path) select=select, xpcall=xpcall, utf8=utf8, + clock = require("socket").gettime, + ALIGN_CENTER = gui.ALIGN_CENTER, + ALIGN_LEFT = gui.ALIGN_LEFT, + ALIGN_RIGHT = gui.ALIGN_RIGHT, + ALIGN_JUSTIFY = gui.ALIGN_JUSTIFY, -- Global vars color = color, error = multi.error, theme = theme, - gui = gui + gui = gui, + timer = timer } env._G = env diff --git a/gui/init.lua b/gui/init.lua index c5c7959..44c834a 100644 --- a/gui/init.lua +++ b/gui/init.lua @@ -1185,6 +1185,7 @@ function gui:newTextBox(txt, x, y, w, h, sx, sy, sw, sh) c.cur_pos = 0 c.selection = {0, 0} + c.blink = true function c:getUniques() return gui.getUniques(c, { @@ -1254,7 +1255,7 @@ end local function textBoxThread() updater:newThread("Textbox Handler", function() - local check = function() return object_focus:hasType(box) end + local check = function() return object_focus:hasType(box) and object_focus.blink end while true do -- Do nothing if we aren't dealing with a textbox thread.hold(check) @@ -1648,21 +1649,21 @@ local drawtypes = { love.graphics.setColor(child.textColor[1], child.textColor[2], child.textColor[3], child.textVisibility) love.graphics.setFont(child.font) - if child.align == gui.ALIGN_LEFT then - child.adjust = 0 - elseif child.align == gui.ALIGN_CENTER then - local fw = child.font:getWidth(child.text) - child.adjust = (w - fw) / 2 - elseif child.align == gui.ALIGN_RIGHT then - local fw = child.font:getWidth(child.text) - child.adjust = w - fw - 4 - end + -- if child.align == gui.ALIGN_LEFT then + -- child.adjust = 0 + -- elseif child.align == gui.ALIGN_CENTER then + -- local fw = child.font:getWidth(child.text) + -- child.adjust = (w - fw) / 2 + -- elseif child.align == gui.ALIGN_RIGHT then + -- local fw = child.font:getWidth(child.text) + -- child.adjust = w - fw - 4 + -- end local mul = 1 if (child.formFactor == gui.FORM_ARC) or (child.formFactor == gui.FORM_CIRCLE) then mul = 2 end love.graphics.printf(child.text, child.adjust + x + child.textOffsetX, - y + child.textOffsetY, w*mul, "left", child.rotation, + y + child.textOffsetY, w*mul, ({[0]="center","left", "right", "justify"})[child.align], child.rotation, child.textScaleX, child.textScaleY, 0, 0, child.textShearingFactorX, child.textShearingFactorY) diff --git a/loader.lua b/loader.lua index 0233b26..7f347dd 100644 --- a/loader.lua +++ b/loader.lua @@ -6,17 +6,17 @@ function loader:new(path_or_file) local c = {} setmetatable(c, loader) c.source = path_or_file - local file = io.open(path_or_file .. "/index.yaml","r") + local file = love.filesystem.read(path_or_file .. "/index.yml") if not file then - error("Unable to load file: ".. path_or_file .."/index.yaml") + error("Unable to load file: ".. path_or_file .."/index.yml") end - c.index = yaml.parse(file:read("*a")) + c.index = yaml.parse(file) for i,v in pairs(c.index.categories) do - local link = io.open(path_or_file .. "/" .. v.name .. ".yaml") + local link = love.filesystem.read(path_or_file .. "/" .. v.name .. ".yml") if not link then - print("Error! Cannot find file: " .. path_or_file .."/" .. v.name .. ".yaml") + print("Error! Cannot find file: " .. path_or_file .."/" .. v.name .. ".yml") else - local category = yaml.parse(link:read("*a")) + local category = yaml.parse(link) c.index.categories[i].questions = category.questions end end diff --git a/main.lua b/main.lua index 570bf39..17036dc 100644 --- a/main.lua +++ b/main.lua @@ -1,11 +1,35 @@ local gui, color, theme, utils, board, yaml, loader, system, elements, scoreUpdater local activePlayer +local playerList = {} +local playerStaticList = {} +local scoreboard = {} function GetActivePlayer() + if not activePlayer then return end return activePlayer.link end +local function GetPlayerPos() + for i,v in pairs(playerStaticList) do + if v == GetActivePlayer() then + return i + end + end +end + +function GetNextPlayer() + local pos = GetPlayerPos() + + if pos >= #playerStaticList then + activePlayer = playerStaticList[1].Ref.Frame + else + activePlayer = playerStaticList[pos + 1].Ref.Frame + end + scoreboard:RenderPlayer(playerList) + return GetActivePlayer() +end + function love.filedropped(file) file:open("r") local data = file:read() @@ -33,8 +57,6 @@ function init() end function ScoreBoard(frame, x, y, w, h, sx, sy, sw, sh) - local scoreboard = {} - -- Colors local C_BG_PANEL = color.new("#1a1a2e") local C_BG_HEADER = color.new("#16213e") @@ -85,7 +107,7 @@ function ScoreBoard(frame, x, y, w, h, sx, sy, sw, sh) }, headernum, headerplayer, headerscore) local updateList = {header, headernum, headerplayer, headerscore} - local playerList = {} + local function ScoreResize() scoreUpdater:newThread(function() thread.skip(2) @@ -113,6 +135,7 @@ function ScoreBoard(frame, x, y, w, h, sx, sy, sw, sh) Name = name, Score = score, Icon = icon, + UUID = multi.generate_uuid7(), Add = function(self, amt) self.Score = tostring(tonumber(self.Score) + amt) table.sort(playerList, function(a, b) @@ -126,6 +149,7 @@ function ScoreBoard(frame, x, y, w, h, sx, sy, sw, sh) end, } table.insert(playerList, player) + table.insert(playerStaticList, player) scoreboard:RenderPlayer(playerList) ScoreResize() return player @@ -140,6 +164,66 @@ function ScoreBoard(frame, x, y, w, h, sx, sy, sw, sh) end end + local add_player = leaderboard:newFrame(5,-5,-10,0,0,1-PLAYER_HEIGHT,1,PLAYER_HEIGHT) + local remove_player = leaderboard:newTextButton("Remove Selected",5,-10,-10,0,0,1-2*PLAYER_HEIGHT,1,PLAYER_HEIGHT) + remove_player.color = color.new("#a13a3a") + remove_player:OnReleased(function() + local player = GetActivePlayer() + uuid = player.UUID + for i = 1, #playerList do + if playerList[i].UUID == uuid then + table.remove(playerList,i) + break + end + end + for i = 1, #playerStaticList do + if playerStaticList[i].UUID == uuid then + table.remove(playerStaticList,i) + break + end + end + scoreboard:RenderPlayer(playerList) + player.Ref.Frame:destroy() + end) + add_player.color = C_BORDER_NRM + local textbox = add_player:newTextBox("Player name",0,0,0,0,.015,.1,.8,.8) + textbox.textColor = C_GOLD + textbox.blink = false + textbox.color = C_BORDER_TOP + textbox.textColor = C_WHITE + textbox:OnPressed(function() + textbox.text = "" + end) + + -- A bit glitchy + -- gui:setHotKey({"return"})(function() + -- local object_focus = gui:getObjectFocus() + -- if object_focus:hasType(gui.TYPE_BOX) then + -- scoreboard:AddPlayer(textbox.text, "0") + -- end + -- end) + + local addbutton = add_player:newTextButton("Add",5,0,-10,0,.815,.1,.185,.8) + addbutton.color = color.new("#7eae5b") + + addbutton:OnReleased(function() + scoreboard:AddPlayer(textbox.text, "0") + end) + + gui.apply({ + setFont = {20}, + align = gui.ALIGN_CENTER + },textbox,addbutton,remove_player) + + thread:newThread(function() + while true do + thread.sleep(.01) + textbox:centerFont() + addbutton:centerFont() + remove_player:centerFont() + end + end) + function scoreboard:RenderPlayer(list) for index, player in ipairs(list) do if player.Ref then @@ -216,7 +300,7 @@ function ScoreBoard(frame, x, y, w, h, sx, sy, sw, sh) end gui.Events.OnResized(ScoreResize) - + ScoreResize() return scoreboard end @@ -225,6 +309,7 @@ require("gui.addons.players") -- local webp = require("webp") function love.load() init() + gui:cacheImage({"assets/checked.png","assets/unchecked.png"}) gui:setAspectSize(1920, 1080) gui.aspect_ratio = true local bg = gui:newFrame() @@ -235,12 +320,7 @@ function love.load() qframe.color = color.new("#060ee9") local scoreboard = ScoreBoard(bg, 0, 0, 0, 0, .015, .05, .170, .9) - p1 = scoreboard:AddPlayer("Epicknex", "0") - p2 = scoreboard:AddPlayer("Bob", "0") - p3 = scoreboard:AddPlayer("John", "0") - p4 = scoreboard:AddPlayer("Billy", "0") - - board.buildBoard(qframe, "anime") + board.buildBoard(qframe, "ai-anime") -- gui:newVideoPlayer("test.ogv",0,0,428,240) -- local img = webp.load("test.webp") diff --git a/skills-lock.json b/skills-lock.json deleted file mode 100644 index 3f59996..0000000 --- a/skills-lock.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": 1, - "skills": { - "filesystem": { - "source": "oimiragieo/agent-studio", - "sourceType": "github", - "computedHash": "70b2b09227ca549eac68abeefa5ca2bfba0d158da5faec7be5de875282fc7d28" - } - } -} diff --git a/anime/templates/imageword.lua b/templates/imageword.lua similarity index 63% rename from anime/templates/imageword.lua rename to templates/imageword.lua index 2d16672..fcc04a6 100644 --- a/anime/templates/imageword.lua +++ b/templates/imageword.lua @@ -1,14 +1,7 @@ ---[[ Constants - * (all lua builtins that don't allow io/executing code) - color (interface) - gui (interface) - multi (interface bound to a processor) no thread module - - callback true/false correct/wrong -]] local label local imageHolder local imageHolder2 +local plusLabel local function index(window, q, callback) frame = window:newFrame(0,0,0,-200,0,.2,1,.8) @@ -17,6 +10,7 @@ local function index(window, q, callback) label.align = ALIGN_CENTER label.textColor = color.white label.color = color.new("#060ce9") + label.borderColor = color.new("#060ce9") if not q.imageA or q.imageA == "" then error("Missing 'imageA' field for question!") @@ -26,13 +20,22 @@ local function index(window, q, callback) error("Missing 'imageB' field for question!") end + -- Left image: takes up 0.0 to 0.42 of width imageHolder = frame:newImageLabel(q.imageA) - imageHolder:setAspectSize(imageHolder.imageWidth,imageHolder.imageHeight) - imageHolder:setDualDim(0,0,imageHolder.imageWidth,imageHolder.imageHeight) + imageHolder:setAspectSize(imageHolder.imageWidth, imageHolder.imageHeight) + imageHolder:setDualDim(0, 0, 0, 0, 0, 0, .4, 1) + -- Plus sign: centered between images at 0.42 to 0.58 + plusLabel = frame:newImageLabel("assets/plus.png", 0, 0, 0, 0, .4125, .4, .175) + plusLabel.square = "w" + plusLabel.align = ALIGN_CENTER + plusLabel.textColor = color.white + -- plusLabel.visibility=0 + + -- Right image: takes up 0.58 to 1.0 of width imageHolder2 = frame:newImageLabel(q.imageB) - imageHolder2:setAspectSize(imageHolder2.imageWidth,imageHolder2.imageHeight) - imageHolder2:setDualDim(0,0,imageHolder2.imageWidth,imageHolder2.imageHeight) + imageHolder2:setAspectSize(imageHolder2.imageWidth, imageHolder2.imageHeight) + imageHolder2:setDualDim(0, 0, 0, 0, .6, 0, .4, 1) local correct = window:newTextButton("Correct",0,-200,0,100,0,1,.5) correct.color = color.new("#52b11b") @@ -40,10 +43,11 @@ local function index(window, q, callback) wrong.color = color.new("#bd2626") local skip = window:newTextButton("Skip",0,-100,0,100,.25,1,.5) skip.color = color.new("#5d5d5d") + window.apply({ - centerX = {true}, centerY = {true}, - },imageHolder, imageHolder2) + }, imageHolder, plusLabel, imageHolder2) + window.apply({ fitFont={}, align=window.ALIGN_CENTER, @@ -54,17 +58,14 @@ local function index(window, q, callback) end callback(self.text == "Correct") end, - },correct,wrong,skip) + }, correct, wrong, skip) end -local function update(dt) -- time in seconds that has passed since - _,_,w,h = imageHolder.parent:getAbsolutes() - local x,y,w,h = imageHolder:GetSizeAdjustedToAspectRatio(w,h) - imageHolder:setDualDim(w,h,x,y) +local function update(dt) label:fitFont() end return { index = index, update = update -} +} \ No newline at end of file diff --git a/templates/multiplechoice.lua b/templates/multiplechoice.lua new file mode 100644 index 0000000..1b85cb8 --- /dev/null +++ b/templates/multiplechoice.lua @@ -0,0 +1,102 @@ +--[[ Constants + * (all lua builtins that don't allow io/executing code) + color (interface) + gui (interface) + multi (interface bound to a processor) no thread module + + callback true/false confirm/wrong +]] +local label + +local function noOf(sx,sy,sw,sh) + return 0,0,0,0,sx,sy,sw,sh +end + +local choiceList = {} +local allBoxes = {} +local confirm +local selected + +local function index(window, q, callback) + label = window:newTextLabel(" " ..q.title.. " ",noOf(0,0,1,.3)) + label.align = ALIGN_CENTER + label.textColor = color.white + label.color = color.new("#060ce9") + label:setFont(50) + choices = window:newFrame(noOf(0,.3,1,.7)) + choices.color = color.new("#060ce9") + + function choices:newChoice(choice, i) + local c = choices:newFrame(noOf(.25,(i-1)/#q.choices,.5,1/#q.choices)) + c.visibility = 0 + local box = c:newImageButton("assets/unchecked.png",noOf(.024*(#q.choices/4),.1,0,.8)) + box.checked = false + box:OnReleased(function() + if box.checked then + box:setImage("assets/unchecked.png") + else + for _, other in pairs(allBoxes) do + other.checked = false + other:setImage("assets/unchecked.png") + end + box:setImage("assets/checked.png") + selected = i + end + box.checked = not checked + end) + + box.square = "h" + c.drawBorder = false + local choiceText = c:newTextLabel(choice,noOf(.3,0,1,1)) + choiceText.visibility = 0 + choiceText.textColor = color.white + -- choiceText.align = window.ALIGN_CENTER + choiceText:setFont(40) + table.insert(choiceList,choiceText) + table.insert(allBoxes,box) + + end + + for i,choice in pairs(q.choices) do + choices:newChoice(choice, i) + end + + local correct, wrong, netural = color.new("#52b11b"), color.new("#bd2626"), color.new("#5d5d5d") + confirm = choices:newTextButton("Confirm",0,0,0,0,.01,1-(1/#q.choices),.24,.9/#q.choices) + confirm.color = netural + gui.apply({ + align=gui.ALIGN_CENTER, + OnReleased=function(self) + if selected == q.answer then + confirm.color = correct + timer(1,function() + callback(true) + end) + else + confirm.color = wrong + timer(1,function() + confirm.color = netural + callback(false) + end) + end + end, + },confirm) +end + +local function update(dt) -- time in seconds that has passed since + -- label:fitFont() + label:centerFont() + for _, obj in pairs(choiceList) do + obj:centerFont() + -- obj:fitFont(nil, nil, {scale = 2/3}) + end + confirm:fitFont(nil, nil, {scale = 2/3}) + confirm:centerFont() + -- print(box.parent:getAbsolutes()) + -- print(box:getAbsolutes()) +end + +return { + index = index, + update = update +} diff --git a/templates/whatis.lua b/templates/whatis.lua index b4a3202..837e32e 100644 --- a/templates/whatis.lua +++ b/templates/whatis.lua @@ -9,7 +9,7 @@ local label local function index(window, q, callback) - label = window:newTextLabel(" " ..q.title.. " ") + label = window:newTextLabel(q.title) label.align = ALIGN_CENTER label:fullFrame() label.textColor = color.white @@ -31,11 +31,11 @@ local function index(window, q, callback) callback(self.text == "Correct") end, },correct,wrong,skip) + label:setFont(60) end local function update(dt) -- time in seconds that has passed since - -- label:centerFont() - label:fitFont() + label:centerFont() end return {