Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
The game "Double Choose" without custom images and sounds, so it can be run in anyone's Codea on any iPad. Of course without proper images it's nonsense; this version is only useful for code sharing.
--# Main
-- Main
saveProjectInfo( "Description", "Two-choice Adventure Games by Rosie and Charlotte." )
--[[
This project creates two simple adventure games that my daughters made up.
The only project-specific code is in the Main and ImageSet tabs.
All other code can be repurposed without needing direct modification.
The tabs explained in order:
1. Main sets everything up--instantiating and configuring all the other classes.
2. ImageSets contains tables with the names of all the images to be used.
3. GameMC is a menu screen for launching different TwoChoiceAdventures.
4. TwoChoiceAdventure is a class that can be made into a simple adventure game.
5. Screen is a basic interactive screen, the building block of the previous two classes.
6. Inventory is a class for tracking, modifying, and displaying inventory.
7. StyleTable is a utility class for storing and applying style settings at will.
8. Utilities contains miscellaneous handy functions with self-explanatory names.
A note on my code conventions: I have attempted wherever possible to stick to the "golden path"
convention, which means sticking to only two indentations maximum. I have also attempted to keep
every statement to one line of text (as measured in Codea's portrait mode). And as a reaction to
the very compelling points made in "Programming Sucks" (http://stilldrinking.org/programming-
sucks), I aspired to live by the slogan "the only good code is obsessively commented code."
]]
--set basic display settings
supportedOrientations(LANDSCAPE_ANY)
--use this setting inside Codea (so that Codea control buttons are active and visible on screen)
displayMode(FULLSCREEN)
--use following setting when exporting to XCode (so Codea control buttons don't show on screen)
--displayMode(FULLSCREEN_NO_BUTTONS)
--a flag for controlling whether or not custom sounds play (flip to false when sharing code)
function testDoubleChoose()
testGame = TwoChoiceAdventure("test adventure")
firstScreen = {name = "firstScreen", background = "ccFirst", narration = "hi there boobly bear", choices = {{choiceText = "go to test screen 2", resultScreen = "testScreenTable2"}}}
testScreenTable2 = {name = "testScreenTable2", background = "theAdventureIs", narration = "bagonna hamonna vagolla", choices = {{choiceText = "go to first screen", resultScreen = "firstScreen"}}}
testGame:addScreens(firstScreen, testScreenTable2)
print("added test screens")
testGame:startGame()
print("started test game")
end
--SODA STUFF
--user inputs:
function keyboard(key)
Soda.keyboard(key)
end
function orientationChanged(ori)
Soda.orientationChanged(ori)
end
--measure performance:
profiler={}
function profiler.init(quiet)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not quiet then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
--END SODA STUFF
function setup()
print(WIDTH, HEIGHT)
--SODA:
profiler.init()
Soda.setup()
parameter.watch("Soda.focus.title")
--overview{}
--/SODA
prepareConstants()
if testingDoubleChoose then
testDoubleChoose()
return
end
simpleSetup()
end
function simpleSetup ()
startBackgroundMusic()
createGames()
print("HERE")
createGameMC()
mainScreen = doubleMC
report("finished setup")
doneLoading = true
end
function report(...)
if printingReports then
local args = {...}
print(table.unpack(args))
end
end
function prepareConstants()
testingDoubleChoose = false
customSounds = false
printingReports = false
doneLoading = false
shouldUseOnlyCodeaImages = true
infoButton = {}
defaultFontSize = WIDTH / 720
local defaultSodaFillColor = color(97, 189, 195, 243)
local highlightTextColor = defaultSodaFillColor
local defaultStrokeColor = color(0, 241, 255, 255)
local highlightColor = color(50, 56, 57, 255)
local highlightColor = defaultStrokeColor
local defaultTextColor = color(255, 255, 255, 255)
defaultSodaStyle = {
shape = {fill = defaultSodaFillColor, stroke = defaultStrokeColor, strokeWidth = 4},
text = {fontSize = defaultFontSize, font = "GillSans-Light", fill=defaultTextColor},
highlight = {
text = {fontSize = defaultFontSize, font = "GillSans-Light", fill=highlightTextColor},
shape = {fill = highlightColor}
}
}
report("finished prepareConstants")
end
function startBackgroundMusic()
if customSounds == true then
music("Documents:Choices_rumble-2", true)
end
report("finished startBackgroundMusic")
end
function createGames()
rosiesGame = makeRosiesGame()
charlottesGame = makeCharlottesGame()
gamesCreated = true
report("finished createGames")
end
function createGameMC()
loadImages(gameMCImages)
doubleMC = GameMC(defaultSodaStyle)
addInfoScreenToGameMC()
doubleMC:addGame("Rosie's Game", rosiesGame)
doubleMC:addGame("Charlotte's Game", charlottesGame)
customizeGameSelectionScreen()
customizeGameExitButtons()
print("HERE2")
report("finished createGameMC")
print("HERE3")
end
function customizeGameSelectionScreen()
--set background image
doubleMC.gameSelectionScreen.background = gameMCImages.doubleMenuBackground
--define the placement of custom GameMC buttons
local roX, roY = WIDTH / 3.4, HEIGHT / 2.5
local chX, chY = WIDTH / 1.41, HEIGHT / 1.995
local roW, roH = WIDTH * 0.482, HEIGHT * 0.537
local chW, chH = WIDTH * 0.369, HEIGHT * 0.523
local resizedRosie = Utilities:makeResizedImage(gameMCImages.doubleMenuRosie, roW, roH)
local resizedChar = Utilities:makeResizedImage(gameMCImages.doubleMenuCharlotte, chW, chH)
--use those settings to customize the GameMC's game buttons
doubleMC:customGameButton("Rosie's Game", resizedRosie, roX, roY)
doubleMC:customGameButton("Charlotte's Game", resizedChar, chX, chY)
--cutomize the tint color that shows the game buttons have been pressed
doubleMC.gameSelectionScreen.touchedTint = color(213, 207, 207, 245)
--make a custom action to play a sound when Charlotte's game starts
local charlotteButtonAction = function ()
if customSounds == true then
Utilities:playSoundAndWaitUntilFinished("Documents:Charlotte_Game_intro-3")
end
infoButton:hide()
doubleMC:runGame("Charlotte's Game")
end
--make a custom action to play a sound when Charlotte's game starts
local rosieButtonAction = function ()
if customSounds == true then
Utilities:playSoundAndWaitUntilFinished("Documents:Rosie_game_intro-2")
end
infoButton:hide()
doubleMC:runGame("Rosie's Game")
end
--add those actions to the gameMC
doubleMC:customGameButtonAction("Charlotte's Game", charlotteButtonAction)
doubleMC:customGameButtonAction("Rosie's Game", rosieButtonAction)
end
function customizeGameExitButtons()
--[[ --old way:
--set a fill and stroke style for the rect of a custom exit button
fill(87, 90, 109, 129)
strokeWidth(0)
--make a StyleTable (which captures the current style upon initializing)
local exitBoxStyle = StyleTable()
--set a style for the text of thecustom exit button
fill(255, 255, 255, 255)
]]
--leftover from non-Soda, but seem to work for calculating button size:
font("GillSans-Light")
fontSize(HEIGHT / 30)
--local exitTextStyle = StyleTable()
--define the exit label text
local exitText = "back to menu"
--get the size of the exit text
local textW, textH = textSize(exitText)
--define a height and width for the exit label
local exitW, exitH = textW + (textW / 1.75), textH + (textH / 1.25)
--use those styles to create the image for the button
--exitLabel = Utilities:label("back to menu", 200, 50, nil, nil, exitTextStyle, exitBoxStyle)
--exitLabel = Utilities:label(exitText, exitW, exitH, nil, nil, exitTextStyle, exitBoxStyle)
--replace the GameMC's stored exit button image and location with custom values
--doubleMC.exitButton.image = exitLabel
--local exitX = math.floor((exitLabel.width / 2) - 1)
--local exitY = math.floor(HEIGHT / 12)
-- doubleMC.exitButton.x = (exitLabel.width / 2) - 1
-- doubleMC.exitButton.y = HEIGHT / 12
doubleMC.exitButton.x = 0.145
doubleMC.exitButton.y = 0.915
doubleMC.exitButton.w = exitW
doubleMC.exitButton.h = exitH
doubleMC.exitButton.title = exitText
--change the button background color
doubleMC.exitButton.style.shape.fill = color(218, 218, 218, 66)
doubleMC.exitButton.style.text.fill = color(106, 106, 106, 107)
doubleMC.exitButton.style.shape.stroke = doubleMC.exitButton.style.text.fill
--modify the exit function to also show the info button
local existingExit = doubleMC.exitButtonAction
local newExitAction = function()
existingExit()
infoButton:show()
end
doubleMC.exitButtonAction = newExitAction
end
function loadImages(tableWithImageNamesAsKeys)
--go through the keys in the table
for key, value in pairs(tableWithImageNamesAsKeys) do
--if shouldNotUseCustomImages is true, use a Codea default
if shouldUseOnlyCodeaImages then
tableWithImageNamesAsKeys[key] = readImage("Planet Cute:Dirt Block")
else
--attempt to get image locally--if image doesn't exist, this will return nil
local imageGot = readImage("Project:"..key)
--if the local attempt does not come up nil, assign the image to the name given
if imageGot == nil then
return
end
tableWithImageNamesAsKeys[key] = imageGot
end
end
end
function draw()
--SODA
pushMatrix()
Soda.camera()
drawing()
popMatrix()
profiler.draw()
--/SODA
end
function drawing(breakPoint)
--
if doneLoading then
mainScreen:draw()
--[[
elseif testingDoubleChoose then
testGame:draw()
else
--]]
else
sprite("Project:doubleMenuBackground", WIDTH /2, HEIGHT/2, WIDTH, HEIGHT)
--sprite("Project:Loading Text",WIDTH /2, HEIGHT/2, WIDTH, HEIGHT)
end
--SODA
--in order for gaussian blur to work, do all your drawing here
--background(40, 40, 50)
--sprite("Cargo Bot:Game Area", WIDTH*0.5, HEIGHT*0.5, WIDTH, HEIGHT)
Soda.draw(breakPoint)
--/SODA
end
function touched(touch)
--SODA
if Soda.touched(touch) then return end
--/SODA
--if the games have not been created, return without processing touches
if doneLoading then
--send the touch to the main screen, which redirects it from there
mainScreen:touched(touch)
--note: mainScreen gets defined in createGames()
return
elseif testingDoubleChoose then
testGame:touched(touch)
end
end
function addInfoScreenToGameMC()
local infoText = [['Double Choose' is two small adventure games written entirely by my daughters Rosie and Charlotte. Tap on Rosie to play her game, and tap on Charlotte to play hers.
Rosie did the art for her game, and I did most of Charlotte's game under her firm art-direction.
You'll enjoy the games most by trying to find all the possible endings. Rosie's game has awesome emotionally-excruciating endings. Charlotte actually included a little adventure-game-style inventory management--impressive for a five-year-old!
We made it with the excellent iPad app Codea. We hope you enjoy our games, and we especially hope they inspire other kids to make games too. Try Codea, it rocks!]]
--define sizes for the info window
local insetX, insetY = math.floor(WIDTH * 0.04), math.floor(HEIGHT * 0.04)
local windowW, windowH = WIDTH - (insetX * 2), HEIGHT - (insetY * 2)
local textW, textH = windowW - (insetX * 2), windowH - (insetY * 2)
--make a function that shows the info window
local iFontName = "GillSans-Light"
--use a Utilities method to find the right fontSize for the current device's screen
local iFontSize = Utilities:pointSizeToFitTextToHeight(infoText, iFontName, windowW, windowH)
--adjust iFontSize because, I think, it uses different point values for font sizes
iFontSize = iFontSize * 0.045
local infoColor = color(221, 120, 221, 255)
--determine the correct the size of the info circle
local iCircleSize = WIDTH * 0.065
--define the placement of the info circle font("ArialRoundedMTBold")
local iCircleOffset = iCircleSize * 0.48
local infoCircleFontSize = iFontSize * 1.9
local iStrokeWidth = infoCircleFontSize * 0.85
local iCircleColor = color(144, 125, 144, 255)
infoButton = Soda.Button{
title = "i",
style = {
shape = {fill = color(0, 0, 0, 0), stroke = iCircleColor, strokeWidth = iStrokeWidth},
text = {fontSize = infoCircleFontSize, font = "GillSans", fill=iCircleColor},
highlight = {
text = {fontSize = infoCircleFontSize, font = "GillSans", fill=color(255, 255, 255, 255)},
shape = {fill = iCircleColor}
}
},
shape = Soda.ellipse,
x = 20, y = 20, w = iCircleSize, h = iCircleSize
}
local window = 0 --placeholder value to initialize variable but keep it local
local infoScreenNeedsMaking = true
local showInfo = function()
if infoScreenNeedsMaking then
window = Soda.Window {
title = infoText,
label = {x = 0.5, y = 0.5},
blurred = true,
close = true,
doNotKill = true,
style = {
shape = {fill = color(37, 37, 37, 255), stroke = "midGrey", strokeWidth = iStrokeWidth}, text = {fontSize = iFontSize, font = iFontName, fill=infoColor, textAlign = CENTER}},
x = insetX, y = insetY, w = windowW, h = windowH
}
--[[ probably deletable:
local subWindow = {
--parent = window,
title = infoText,
close = true,
blurred = true,
label = {x = 0.5, y = 0.5},
style = {
shape = {fill = color(0, 0, 0, 0)},
text = {fontSize = iFontSize, font = iFontName, fill=infoColor, textAlign = CENTER},
},
-- x = 0, y = -1 * windowH / 3.65, w = windowW, h = windowH
x = 0, y = 0, w = windowW, h = windowH
}
]]--
infoScreenNeedsMaking = false
else
window:show()
end
end
infoButton.callback = showInfo
end
function makeRosiesGame()
--create the game object
local rosiesGame = TwoChoiceAdventure("Rosie's Game", defaultSodaStyle)
--flag game to only use Codea-supplied images, using setting from constant
rosiesGame:useOnlyCodeaImages(shouldUseOnlyCodeaImages)
--add an image set
rosiesGame:setImageSet(rcImages)
--create the individual screens (the format for these is explained in TwoChoiceAdventure)
rosiesGame:addScreens(
{name = "firstScreen",
background = "rcFirst",
images = {},
narration = "Do you want to have an adventure?",
choices = {
{choiceText = "adventure", resultScreen = "theAdventureIs"},
{choiceText = "stay in bed", resultScreen ="sleepForDays" } }
},
{name = "theAdventureIs",
background = "theAdventureIs",
images = {},
narration = "The adventure is: help everybody!",
choices = {
{choiceText = "first go see your boyfriend", resultScreen ="boyfriendAsks" } }
},
{name = "sleepForDays",
background = "rcSleepForDays",
images = {},
narration = "You sleep for days and days and\nend up hated by everybody.",
choices = {
{choiceText = "The End", resultScreen ="firstScreen"} }
},
{name = "boyfriendAsks",
background = "rcBoyfriendAsks",
images = {},
narration =
"Your boyfriend says,\n'Do you want to know my secret,\nor do you want to break up?'",
choices = {
{choiceText = "break up", resultScreen ="breakUp" },
{choiceText = "secret", resultScreen = "boyfriendSecret"} }
},
{name = "boyfriendSecret",
background = "rcBoyfriendWithNote",
images = {},
narration = "He says 'My secret is this note\nI need to get to my sister somehow.'",
choices = {
{choiceText = "send note for him", resultScreen ="helpEverybody" } }
},
{name = "breakUp",
background = "rcBreakup",
images = {},
narration = "You break up and he gets a girlfriend\nprettier than you and you cry.",
choices = {
{choiceText = "The End", resultScreen ="firstScreen"} }
},
{name = "helpEverybody",
background = "rcHelpEverybody",
images = {},
narration = "First, you send your boyfriend's note.\n\rThen you help everybody!",
choices = {
{choiceText = "good job", resultScreen ="queenAsks" } }
},
{name = "queenAsks",
background = "rcQueenAsksYouToDeliver",
images = {},
narration = "For doing such a good job, the Queen\nsays you should be Queen, but only if\nyou deliver some birthday invitations\nfor her.",
choices = {
{choiceText = "don't deliver", resultScreen ="everybodyDisappointed" },
{choiceText = "deliver", resultScreen = "deliverInvitations"} }
},
{name = "deliverInvitations",
background = "rcDeliverAllInvitations",
images = {},
narration = "You deliver all the invitations!",
choices = {
{choiceText = "go back to Queen", resultScreen ="doYouHaveASecret"} }
},
{name = "everybodyDisappointed",
background = "rcEverybodysDisappointed",
images = {},
narration = "You never get famous and everybody's\ndisappointed because they wanted you\nto be queen.",
choices = {
{choiceText = "The End", resultScreen ="firstScreen"} }
},
{name = "doYouHaveASecret",
background = "rcSecretOrQueen",
images = {},
narration = "The Queen asks:\n\r'Do you have a secret\nor do you want to be queen?'",
choices = {
{choiceText = "reveal your secret", resultScreen = "revealYourSecret"},
{choiceText = "become Queen", resultScreen ="becomeQueen" } }
},
{name = "revealYourSecret",
background = "rcTellYourSecret",
images = {},
narration = "You tell your secret:\n\r'I didn't really want to help anybody.'\n\rThe people kick you out and you lose.",
choices = {
{choiceText = "The End", resultScreen ="firstScreen"} }
},
{name = "becomeQueen",
background = "rcYoureQueen",
images = {},
narration = "You're Queen!",
choices = {
{choiceText = "The End", resultScreen ="firstScreen"} }
}
)
report("finished makeRosiesGame")
return rosiesGame
end
function makeCharlottesGame()
--create the game object
local charlottesGame = TwoChoiceAdventure("Charlotte's Game", defaultSodaStyle)
--flag game to only use Codea-supplied images, using setting from constant
charlottesGame:useOnlyCodeaImages(shouldUseOnlyCodeaImages)
--add an image set
charlottesGame:setImageSet(ccImages)
--create the individual screens (the format for these is explained in TwoChoiceAdventure)
charlottesGame:addScreens(
{name = "firstScreen",
background = "ccFirst",
images = {
{"heroine", "ccHeroine", WIDTH * 0.23, HEIGHT * 0.576, heightRatio = 0.61197917},
{"coffeeSmall", "ccCoffeeSmall", WIDTH * 0.457, HEIGHT * 0.57161, heightRatio = 0.0625} },
narration = "You woke up.\n\rWhat do you want to\ndo with your coffee?",
choices = {
{choiceText = "drink it", resultScreen = "drankCoffee"},
{choiceText = "save it for later", resultScreen ="savedCoffee",
inventoryAdd = {name = "coffee", icon = "ccCoffeeBig", heightRatio = 0.21875} } }
},
{name = "drankCoffee",
background = "ccFirst",
images = {
{"heroine", "ccHeroine", WIDTH * 0.23, HEIGHT * 0.576, heightRatio = 0.61197917} },
narration = "It tastes good.",
choices = {
{choiceText = "go outside", resultScreen = "boyfriendTellsAboutQueen"} },
},
{name = "savedCoffee",
background = "ccFirst",
images = {
{"heroine", "ccHeroine", WIDTH * 0.23, HEIGHT * 0.576, heightRatio = 0.61197917} },
narration = "You keep it with you for later.",
choices = {
{choiceText = "go outside", resultScreen = "boyfriendTellsAboutQueen"} },
},
{name = "boyfriendTellsAboutQueen",
background = "ccGenericOutside",
images = {
{"heroine", "ccHeroine", WIDTH * 0.3115, HEIGHT * 0.576, heightRatio = 0.61197917},
{"boyfriend", "ccBoyfriend", WIDTH * 0.54003906, HEIGHT * 0.5859375, heightRatio = 0.65885417} },
narration = "Your boyfriend tells you the queen is bored.",
choices = {
{choiceText = "go to see the queen", resultScreen = "knightScreen"} }
},
{name = "knightScreen",
background = "ccKnightScreen",
images = {
{"heroine", "ccHeroine", WIDTH * 0.2675, HEIGHT * 0.576, heightRatio = 0.61197917} },
narration = "You see a knight at the gate.\n\rWhat do you say to him?",
choices = {
{choiceText = "Just let me in to the castle.", resultScreen ="knightScolds" },
{choiceText = "Hi, having a nice day?", resultScreen = "knightGivesHeart",
inventoryAdd = {name = "heartBox", icon = "ccHeartBox", heightRatio = 0.22135417} } }
},
{name = "knightGivesHeart",
background = "ccKnightScreen",
images = {
{"heroine", "ccHeroine", WIDTH * 0.2675, HEIGHT * 0.576, heightRatio = 0.61197917} },
narration = "The knight likes you and gives you a heart box with chocolates in it.",
choices = {
{choiceText = "go in to castle", resultScreen ="boredQueen" } }
},
{name = "knightScolds",
background = "ccKnightScreen",
images = {
{"heroine", "ccHeroine", WIDTH * 0.23, HEIGHT * 0.576, heightRatio = 0.61197917} },
narration = "The knight tells you that you have a bad attitude.",
choices = {
{choiceText = "go home", resultScreen ="homeAfterKnight" } }
},
{name = "homeAfterKnight",
background = "ccFirst",
images = {
{"heroineFlipped", "ccHeroineFlipped", WIDTH * 0.390625, HEIGHT * 0.56380208, heightRatio = 0.61197917} },
narration = "You say to yourself, \"He doesn't like me anyway.\"",
choices = {
{choiceText = "start over", resultScreen ="firstScreen",
inventoryRemove = "allItems" } }
},
{name = "boredQueen",
background = "ccQueenBored",
images = {
{"heroine", "ccHeroine", WIDTH * 0.2197, HEIGHT * 0.576, heightRatio = 0.61197917} },
narration = "The queen tells you she's really bored.",
choices = {
{choiceText = "tell her you know a puppet show", resultScreen = "queenSaysShowMe" } }
},
{name = "queenSaysShowMe",
background = "ccQueenBored",
images = {
{"heroine", "ccHeroine", WIDTH * 0.2246, HEIGHT * 0.576, heightRatio = 0.61197917} },
narration = "You tell the queen you know how to put on a puppet show.\n\rShe says, \"Show me as soon as you can!\"",
choices = {
{choiceText = "leave", resultScreen = "grouchyPuppeteer" } }
},
{name = "grouchyPuppeteer",
background = "ccGenericOutside",
images = {
{"heroineFlipped", "ccHeroineFlipped", WIDTH * 0.68359375, HEIGHT * 0.59635417, heightRatio = 0.61197917},
{"puppeteerGrouchy", "ccPuppeteerGrumpy", WIDTH * 0.31738281, HEIGHT * 0.59635417, heightRatio = 0.67317708} },
narration = "On your way home you see a grouchy puppeteer.",
choices = {
{onlyIfInInventory = "coffee", choiceText = "give him your coffee",
resultScreen = "happyPuppeteer", inventoryRemove = "coffee" },
{choiceText = "go home and practice", resultScreen ="queenNotLike" } }
},
{name = "happyPuppeteer",
background = "ccGenericOutside",
images = {
{"heroineFlipped", "ccHeroineFlipped", WIDTH * 0.67675781, HEIGHT * 0.59635417, heightRatio = 0.61197917},
{"puppeteerHappy", "ccPuppeteerHappy", WIDTH * 0.31738281, HEIGHT * 0.59635417, heightRatio = 0.66145833} },
narration = "He teaches you a new puppet show.",
choices = {
{choiceText = "go home and give up", resultScreen ="homeAndSleep" },
{choiceText = "go show the queen", resultScreen = "queenLovesShow"} }
},
{name = "queenNotLike",
background = "ccQueenNotLike",
images = {
{"heroineWithSockPuppets", "ccHeroineWithSockPuppets", WIDTH * 0.26464844, HEIGHT * 0.55989583, heightRatio = 0.63932292} },
narration = "You practice, but the queen doesn't like your show, and you're embarrassed.",
choices = {
{choiceText = "start over", resultScreen ="firstScreen",
inventoryRemove = "allItems" } }
},
{name = "homeAndSleep",
background = "ccHomeAndSleep",
narration = "You go back home and go to sleep.",
choices = {
{choiceText = "start over", resultScreen ="firstScreen",
inventoryRemove = "allItems" } }
},
{name = "queenLovesShow",
background = "ccQueenLoves",
images = {
{"heroineWithMarionettes", "ccHeroineWithMarionettes", WIDTH * 0.26464844, HEIGHT * 0.57291667, heightRatio = 0.63932292} },
narration = "The queen loves the show!\n\rShe tells you a secret. If you give the princess a heart box with chocolates in it, you will become the new queen.",
choices = {
{choiceText = "choose who to give the heart to", resultScreen ="chooseHeart" } }
},
{name = "chooseHeart",
background = "ccGenericCastleInterior",
images = {
{"princess", "ccPrincess", WIDTH * 0.5, HEIGHT * 0.64453125, heightRatio = 0.72916667},
{"boyfriend", "ccBoyfriend", WIDTH * 0.23730469, HEIGHT * 0.58854167, heightRatio = 0.65885417} },
narration = "Do you give the heart to the princess or your boyfriend?",
choices = {
{choiceText = "princess", resultScreen = "youBecomeQueen",
inventoryRemove = "heartBox"},
{choiceText = "boyfriend", resultScreen ="happyInForest",
inventoryRemove = "heartBox" } }
},
{name = "youBecomeQueen",
background = "ccGenericCastleInterior",
images = {
{"heroineQueened", "ccHeroineQueened", WIDTH * 0.31152344, HEIGHT * 0.58854167, heightRatio = 0.64973958} },
narration = "You become the queen!",
choices = {
{choiceText = "The End", resultScreen ="firstScreen",
inventoryRemove = "allItems" } }
},
{name = "happyInForest",
background = "ccHappyInForest",
images = {
{"happyFamily", "ccHappyFamily", WIDTH * 0.31835938, HEIGHT * 0.42057292, heightRatio = 0.286} },
narration = "You and your boyfriend end up living in a shack in the forest with your kids.\n\rAnd you are happy.",
choices = {
{choiceText = "The End", resultScreen ="firstScreen",
inventoryRemove = "allItems" } }
}
)
report("finished makeCharlottesGame")
return charlottesGame
end
--# ImageSets
--[[
ImageSets is not a class, just a tab for holding tables that have image names as keys (and all values set to the placeholder value 0), as well as a function for setting the images directly. Main can either call that function, or set the images another way.
]]
--image keys for the game selection screen
gameMCImages = {}
gameMCImages.doubleMenuRosie = 0
gameMCImages.doubleMenuCharlotte = 0
gameMCImages.doubleMenuBackground = 0
--image keys for Charlotte's game
ccImages = {}
ccImages.ccFirst = 0
ccImages.ccHeroine = 0
ccImages.ccHeroineFlipped = 0
ccImages.ccCoffeeSmall = 0
ccImages.ccCoffeeBig = 0
ccImages.ccGenericOutside = 0
ccImages.ccBoyfriend = 0
ccImages.ccKnightScreen = 0
ccImages.ccHeartBox = 0
ccImages.ccQueenBored = 0
ccImages.ccPuppeteerGrumpy = 0
ccImages.ccPuppeteerHappy = 0
ccImages.ccQueenNotLike = 0
ccImages.ccHeroineWithSockPuppets = 0
ccImages.ccHomeAndSleep = 0
ccImages.ccQueenLoves = 0
ccImages.ccHeroineWithMarionettes = 0
ccImages.ccGenericCastleInterior = 0
ccImages.ccPrincess = 0
ccImages.ccHeroineQueened = 0
ccImages.ccHappyInForest = 0
ccImages.ccHappyFamily = 0
--image keys for Rosie's game
rcImages = {}
rcImages.rcFirst = 0
rcImages.theAdventureIs = 0
rcImages.rosiePlaceholder = 0
rcImages.rcSleepForDays = 0
rcImages.rcBoyfriendAsks = 0
rcImages.rcBoyfriendWithNote = 0
rcImages.rcBreakup = 0
rcImages.rcHelpEverybody = 0
rcImages.rcQueenAsksYouToDeliver = 0
rcImages.rcDeliverAllInvitations = 0
rcImages.rcEverybodysDisappointed = 0
rcImages.rcSecretOrQueen = 0
rcImages.rcTellYourSecret = 0
rcImages.rcYoureQueen = 0
function assignIemageSetsAllAtOnce()
--image keys for the game selection screen
gameMCImages = {}
gameMCImages.doubleMenuRosie = readImage("Project:doubleMenuRosie")
gameMCImages.doubleMenuCharlotte = readImage("Project:doubleMenuCharlotte")
gameMCImages.doubleMenuBackground = readImage("Project:doubleMenuBackground")
--image keys for Charlotte's game
ccImages = {}
ccImages.ccFirst = readImage("Project:ccFirst")
ccImages.ccHeroine = readImage("Project:ccHeroine")
ccImages.ccHeroineFlipped = readImage("Project:ccHeroineFlipped")
ccImages.ccCoffeeSmall = readImage("Project:ccCoffeeSmall")
ccImages.ccCoffeeBig = readImage("Project:ccCoffeeBig")
ccImages.ccGenericOutside = readImage("Project:ccGenericOutside")
ccImages.ccBoyfriend = readImage("Project:ccBoyfriend")
ccImages.ccKnightScreen = readImage("Project:ccKnightScreen")
ccImages.ccHeartBox = readImage("Project:ccHeartBox")
ccImages.ccQueenBored = readImage("Project:ccQueenBored")
ccImages.ccPuppeteerGrumpy = readImage("Project:ccPuppeteerGrumpy")
ccImages.ccPuppeteerHappy = readImage("Project:ccPuppeteerHappy")
ccImages.ccQueenNotLike = readImage("Project:ccQueenNotLike")
ccImages.ccHeroineWithSockPuppets = readImage("Project:ccHeroineWithSockPuppets")
ccImages.ccHomeAndSleep = readImage("Project:ccHomeAndSleep")
ccImages.ccQueenLoves = readImage("Project:ccQueenLoves")
ccImages.ccHeroineWithMarionettes = readImage("Project:ccHeroineWithMarionettes")
ccImages.ccGenericCastleInterior = readImage("Project:ccGenericCastleInterior")
ccImages.ccPrincess = readImage("Project:ccPrincess")
ccImages.ccHeroineQueened = readImage("Project:ccHeroineQueened")
ccImages.ccHappyInForest = readImage("Project:ccHappyInForest")
ccImages.ccHappyFamily = readImage("Project:ccHappyFamily")
--image keys for Rosie's game
rcImages = {}
rcImages.rcFirst = readImage("Project:rcFirst")
rcImages.theAdventureIs = readImage("Project:theAdventureIs")
rcImages.rosiePlaceholder = readImage("Project:rosiePlaceholder")
rcImages.rcSleepForDays = readImage("Project:rcSleepForDays")
rcImages.rcBoyfriendAsks = readImage("Project:rcBoyfriendAsks")
rcImages.rcBoyfriendWithNote = readImage("Project:rcBoyfriendWithNote")
rcImages.rcBreakup = readImage("Project:rcBreakup")
rcImages.rcHelpEverybody = readImage("Project:rcHelpEverybody")
rcImages.rcQueenAsksYouToDeliver = readImage("Project:rcQueenAsksYouToDeliver")
rcImages.rcDeliverAllInvitations = readImage("Project:rcDeliverAllInvitations")
rcImages.rcEverybodysDisappointed = readImage("Project:rcEverybodysDisappointed")
rcImages.rcSecretOrQueen = readImage("Project:rcSecretOrQueen")
rcImages.rcTellYourSecret = readImage("Project:rcTellYourSecret")
rcImages.rcYoureQueen = readImage("Project:rcYoureQueen")
end
--# GameMC
GameMC = class(Screen) --making this a subclass is a new experiment
--[[
GameMC creates a menu screen for launching TwoChoiceAdventure games. It will automatically generate menu buttons for every game added by the addGame method. The automatically-generated screen is hideous, though, so there are also a couple methods included for customizing it. This class also adds an "exit game" button to the first screen of every game it runs.
]]
function GameMC:init(sodaStyle)
--initialize the superclass
Screen.init(self, "GameMC")
--store the Soda style
self.style = Utilities:copyWholeTable(sodaStyle)
table.pack()
--make the games table
self.games = {}
--create an instance of the Screen class to become the selection screen
self.gameSelectionScreen = Screen()
--for setup purposes, instantiate a placeholder game
self.currentGame = TwoChoiceAdventure("placeholder game")
--add the placeholder to self, so an unconfigured selection menu can at least show one button
self:addGame("placeholder game", self.currentGame)
--make a boolean to track if a game is underway
self.gameUnderway = false
--create a table for the "exit game" button (overlaid on the first screen of any running game)
self.exitButton = {}
self.exitButton.image = Utilities:label("exit game", 200, 50)
--set the exit button location to be the middle of the screen (you'll want to customize that)
self.exitButton.x = WIDTH / 2
self.exitButton.y = HEIGHT / 10
--the action for the exit button to run when pressed
self.exitButtonAction = function () self:endGame() end
--instead:
--define the exit button as a Soda button table
self.exitButton = {
title = "exit game",
label = {x = 0.5, y = 0.5},
--blurred = true, -- causes Soda crash
style = self.style,
shape = Soda.RoundedRectangle,
x = 20, y = 20, w = 200, h = 75,
}
end
function GameMC:draw()
--if a game is not underway, use the draw() method of the selection screen
if self.gameUnderway == false then
self.gameSelectionScreen:draw()
else
--otherwise use the current game's draw() method
self.currentGame:draw()
end
end
function GameMC:touched(touch)
--if a game is not underway, use the touched(touch) method of the selection screen
if self.gameUnderway == false then
self.gameSelectionScreen:touched(touch)
else
--otherwise use the current game's touched(touch) method
self.currentGame:touched(touch)
end
end
function GameMC:addGame(gameName, gameObject)
--if the placeholder game exists, remove it
if Utilities:doesTableHaveStringKey(self.games, "placeholder game") then
self:removeGame("placeholder game")
end
--add the new game under the given name
self.games[gameName] = gameObject
--a count of the current games for measuring button placement
local gameCount = Utilities:numberOfKeyValueElements(self.games)
--the height to use for each button
local buttonHeight = (HEIGHT / gameCount)
--create new buttons for each game
for key, value in pairs(self.games) do
--make a name for the current game button
local buttonName = key.." Button"
--erase any old one by that name
Utilities:removeValueByStringKey(self.gameSelectionScreen.touchables, buttonName)
--make a new button image
local buttonImage = Utilities:label("press to start "..key, WIDTH, buttonHeight)
--calculate the y value to use
local buttonY = (gameCount * buttonHeight) - (buttonHeight / 2)
--add it to the game selection screen
self.gameSelectionScreen:addTouchableAt(buttonName, buttonImage, WIDTH / 2, buttonY)
--decrement the game counter so the next label will be placed under the current one
gameCount = gameCount - 1
--make an action for it
local buttonAction = function () self:runGame(key) end
--make a name for the action
local actionName = buttonName.." Action"
--assign the action to the button
self.gameSelectionScreen:assignActionToTouchable(actionName, buttonAction, buttonName)
end
end
function GameMC:customGameButton(gameName, newImage, x, y)
--exit if no such game
if self.games[gameName] == nil then
return false
end
--change the button of the given game to use the given image and given x, y coordinates
self.gameSelectionScreen.touchables[gameName.." Button"].image = newImage
self.gameSelectionScreen.touchables[gameName.." Button"].x = x
self.gameSelectionScreen.touchables[gameName.." Button"].y = y
return true
end
function GameMC:customGameButtonAction(gameName, action)
--a reference to the button name for the given game
local buttonName = gameName.." Button"
--a reference to that button's action's name
local actionName = buttonName.." Action"
--use that action name to assign the passed-in action to that button
self.gameSelectionScreen:assignActionToTouchable(actionName, action, buttonName)
--note: be sure to include runGame(gameName) in the new action!
end
function GameMC:removeGame(gameName)
--if the game to remove is the currentGame, set currentGame to nil to remove the reference
if self.currentGame == self.games[gameName] then
self.currentGame = nil
end
--use a utility function to remove the game from the games table
Utilities:removeValueByStringKey(self.games, gameName)
--lastly, remove the button and button action for the given game on the selection screen
self.gameSelectionScreen:removeTouchableNamed(gameName.." Button")
self.gameSelectionScreen:removeActionNamed(gameName.." Button Action")
end
function GameMC:runGame(gameName)
--set the internal flag that shows a game is underway
self.gameUnderway = true
--designate the given game as the current game
self.currentGame = self.games[gameName]
--make shorter references to the position where the exit button will be placed
--local buttonX = self.exitButton.x
--local buttonY = self.exitButton.y
--use them to create a table holding the arguments for adding the exit button
--local touchableArgs = {"exit button", self.exitButton.image, buttonX, buttonY}
--use that table to add the exit button to the first screen of the game about to be run
self.currentGame.firstScreen:addTouchableAt("exit button", self.exitButton.image, nil, nil, self.exitButton)
--add the exit button function to the given game as the action for the exit button
self.currentGame.firstScreen:assignActionToTouchable("exit", self.exitButtonAction, "exit button")
--start the game!
self.currentGame:startGame()
end
function GameMC:endGame()
--set the internal flag that shows that no game is underway
self.gameUnderway = false
--remove the exit button and its action from the first screen of the game
self.currentGame.firstScreen:removeTouchableNamed("exit button")
self.currentGame.firstScreen:removeActionNamed("exit")
--clear any Soda elements being displayed
self.currentGame.currentScreen:killSodaUIInstances()
end
--# Scratch
function addInfoScreen()
local infoText = [['Double Choose' is two small adventure games written entirely by my daughters Rosie and Charlotte. Tap on Rosie to play her game, and tap on Charlotte to play hers.
Rosie did the art for her game, and I did most of Charlotte's game under her firm art-direction.
You'll enjoy the games most by trying to find all the possible endings. Rosie's game has awesome emotionally-excruciating endings. Charlotte actually included a little adventure-game-style inventory management--impressive for a five-year-old!
We made it with the excellent iPad app Codea. We hope you enjoy our games, and we especially hope they inspire other kids to make games too. Try Codea, it rocks!]]
--define parameters for the info window
local insetX, insetY = math.floor(WIDTH * 0.04), math.floor(HEIGHT * 0.04)
local windowW, windowH = WIDTH - (insetX * 2), HEIGHT - (insetY * 2)
local textW, textH = windowW - (insetX * 2), windowH - (insetY * 2)
local iFontName = "GillSans-Light"
local iFontSize = 1.75
local infoColor = color(198, 110, 198, 255)
local showInfo = function()
local window = Soda.Window {
title = "",
blurred = true,
close = true,
style = {
shape = {fill = color(37, 37, 37, 255), stroke = "midGrey", strokeWidth = iStrokeWidth}, text = {}},
x = insetX, y = insetY, w = windowW, h = windowH
}
local subWindow = Soda.Window {
parent = window,
title = infoText,
style = {
shape = {fill = color(0, 0, 0, 0), stroke = "midGrey", strokeWidth = iStrokeWidth},
text = {fontSize = iFontSize, font = iFontName, fill=infoColor, textAlign = LEFT},
},
x = 0, y = -1 * windowH / 3.65, w = windowW, h = windowH
}
end
--determine the correct the size of the info circle
local iCircleSize = WIDTH * 0.065
--define the placement of the info circle
local iCircleOffset = iCircleSize * 0.48
local infoCircleFontSize = iFontSize * 1.9
local iStrokeWidth = infoCircleFontSize * 0.85
local iCircleColor = color(144, 125, 144, 255)
infoButtonTable = Soda.Button{
title = "i",
style = {
shape = {fill = color(0, 0, 0, 0), stroke = iCircleColor, strokeWidth = iStrokeWidth},
text = {fontSize = infoCircleFontSize, font = "GillSans", fill=iCircleColor},
highlight = {
text = {fontSize = infoCircleFontSize, font = "GillSans", fill=color(0, 0, 0, 0)},
shape = {fill = iCircleColor}
}
},
shape = Soda.ellipse,
x = 20, y = 20, w = iCircleSize, h = iCircleSize,
callback = showInfo
}
end
--# TwoChoiceAdventure
TwoChoiceAdventure = class()
--[[
TwoChoiceAdventure is a class that can be made into a simple adventure game. The game consists of a series of screens with a small amount of narration and up to two choices of response. The choices can either change the current screen to a new screen or modify the inventory. Choices can be displayed or hidden dynamically based on inventory state. Screens are defined using the addScreen(...) method. The very first screen must be named "firstScreen".
]]
function TwoChoiceAdventure:init(name, defaultStyle)
--check if a name was given
if name == nil then
--if no name was given, print an error message
print("TwoChoiceAdventure: game didn't init with name. Initialization failed.")
--and exit without initializing
return
end
self.name = name
--a default first screen, in case one is never set
local first = Screen()
--a label for it
local firstLabel = Utilities:label(name..": blank screen", WIDTH / 2, HEIGHT / 2)
--add the label to the screen
first:addImageAt("default art", firstLabel, WIDTH / 2, HEIGHT / 2)
--declare the set of tables for saving screen definitions
self.screenTables = {}
--store the first screen as a special variable
self.firstScreen = first
--change the current screen to be the firstScreen
self:changeScreen("firstScreen")
--make an Inventory object for tracking inventory
self.inventory = Inventory()
--make a style table to preserve the desired style for drawn text
self.textStyle = StyleTable()
--Soda style defaults
self.defaultStyle = Utilities:copyWholeTable(defaultStyle)
--set the desired style settings
font("SourceSansPro-Regular")
fontSize(HEIGHT / 28)
textWrapWidth(WIDTH / 2.4)
textAlign(LEFT)
fill(251, 251, 251, 255)
--capture the current style settings the style table
self.textStyle:captureCurrentStyle()
--specify the size and location of the narration box
local narrationHeightAsPercent = 0.2941176
self.narrationW = WIDTH / 2.15
self.narrationH = HEIGHT * narrationHeightAsPercent
self.narrationX = 0.73326
self.narrationY = 0.39483
--specify the size of the choice buttons
local choiceHeightAsPercent = 0.07194245
self.choiceW = self.narrationW
self.choiceH = HEIGHT * choiceHeightAsPercent
--align the horizontal position of the buttons with the narration box
self.choice1X = self.narrationX
self.choice2X = self.choice1X
--specify the gap between choice buttons as a screen percent
gapBetweenChoicesAsPercent = 0.0095
--find the height of the narration panel as a screen percent
local bottomOfNarration = self.narrationY - (narrationHeightAsPercent / 2)
--set the vertical positions of the choice buttons
self.choice1Y = bottomOfNarration - (choiceHeightAsPercent / 2) - gapBetweenChoicesAsPercent
self.choice2Y = self.choice1Y - choiceHeightAsPercent - gapBetweenChoicesAsPercent
print(self.choice2Y, fromNarrationYToChoice1Y, "iiiii")
end
function TwoChoiceAdventure:useOnlyCodeaImages(flagValue)
--used to toggle custom images off
self.shouldUseOnlyCodeaImages = flagValue
self.inventory:useOnlyCodeaImages(flagValue)
end
function TwoChoiceAdventure:addScreens(...)
--[[
This method can take any number of arguments, each of which must be a properly constructed screen table. The tables are stored in self.screenTables and used to assemble screens when needed. Screen tables are key-value tables, which use these keys:
name = screen name
background = background image name as defined in imageSet
images (optional) = a table of imageTables, which have the format: {name, image, x, y}
narration = narration text
choices = table of up to two choiceTables, key-value tables with these keys & values:
choiceText = choice text that appears on the choice button
resultScreen = name of screen shown after button is pressed
inventoryAdd (optional) = table with the keys & values needed for Inventory:addItem
inventoryRemove (optional) = name of inventory item to remove
onlyIfInInventory (optional) = name of item necessary for this choice to be displayed
]]
local arg = {...}
--iterate through the arguments
for index, screenTable in ipairs(arg) do
--add each argument to self.screenTables using its own "name" value
self.screenTables[screenTable.name] = screenTable
--if the screen is named "firstScreen", assemble it and keep it around
self:assembleIfNameIsFirstScreen(screenTable)
end
end
function TwoChoiceAdventure:assembleIfNameIsFirstScreen(screenTable)
--the screen named "firstScreen" has to be kept in memory, so check if this is it
if screenTable.name == "firstScreen" then
--if it is, make it, and assign it to the firstScreen instance variable
self.firstScreen = self:assembleScreen(screenTable)
report(self.name.." assembled first screen")
end
end
function TwoChoiceAdventure:assembleScreen(screenTable)
--create a Screen object to be configured
local newScreen = Screen()
--configure it using the values in the given screenTable
self:placeScreenImages(newScreen, screenTable)
self:placeScreenNarration(newScreen, screenTable)
self:createScreenChoices(newScreen, screenTable)
--return it
return newScreen
end
function TwoChoiceAdventure:changeScreen(newScreenName)
if self.currentScreen ~= nil then
--remove any Soda elements (which otherwise persist outside of the Screen's scope)
self.currentScreen:killSodaUIInstances()
--wipe out the current screen, hopefully preventing a crash from all screens being made
self.currentScreen = nil
end
--if returning to the firstScreen, which is kept in memory, nothing has to be assembled
if newScreenName == "firstScreen" then
--set the first screen to be the current screen
self.currentScreen = self.firstScreen
--exit
return
end
print("screen change done and good")
--if going to any other screen, first assemble it using the given name
local assembledScreen = self:assembleScreen(self.screenTables[newScreenName])
--then check if anything needs to happen to it based on inventory state
if assembledScreen.inventoryCheck ~= nil then
--if so, run that function
assembledScreen.inventoryCheck()
end
--put in the new screen
self.currentScreen = assembledScreen
collectgarbage()
end
function TwoChoiceAdventure:placeScreenImages(screen, screenTable)
report("screen:", screen, "screenTable:", table.unpack(screenTable))
--load the background image
if self.shouldUseOnlyCodeaImages then
screen:setBackground(readImage("Cargo Bot:Game Area"))
else
screen:setBackground(readImage("Project:"..screenTable.background))
end
--if no images are defined, bail
if screenTable.images == nil then
return
end
--otherwise iterate through each of the image tables in the screenTable
for index, imageTable in ipairs(screenTable.images) do
local thisImage
if self.shouldUseOnlyCodeaImages then
thisImage = readImage("Planet Cute:Grass Block")
else
thisImage = readImage("Project:"..imageTable[2])
end
--report it
report("placeScreenImages placing "..imageTable[1])
--after checking for heightRatio, add each image by unpacking its image table directly into addImageAt
if imageTable.heightRatio == nil then
screen:addImageAt(imageTable[1], thisImage, imageTable[3], imageTable[4])
else
report("placeScreenImages heightRatio detected")
screen:addImageAt(imageTable[1], thisImage, imageTable[3], imageTable[4], imageTable.heightRatio)
end
end
end
function TwoChoiceAdventure:placeScreenNarration(screen, screenTable)
--if no narration is present, bail
if screenTable.narration == nil then
return
end
--[[
--create a style table for the narration box's fill and stroke
local boxStyle = StyleTable()
--set the desired fill and stroke (which is no stroke at all)
fill(13, 132, 209, 239)
strokeWidth(0)
--capture those settings in the style table
boxStyle:captureCurrentStyle()
--a shorter variable name for covenience
local narration = screenTable.narration
--for readability, make shorter local variables for the needed stored values
local w, h, x, y = self.narrationW, self.narrationH, self.narrationX, self.narrationY
--create an image using the narration settings
local narrationBox = Utilities:label(narration, w, h, nil, nil, self.textStyle, boxStyle)
--place the image on the given screen
screen:addImageAt("narration", narrationBox, x, y)
]]
--a shorter variable name for covenience
local narration = screenTable.narration
--for readability, make shorter local variables for the needed stored values
local w, h, x, y = self.narrationW, self.narrationH, self.narrationX, self.narrationY
local narrationTable = {
--blurred = true, --causes crash
title = narration,
x = x, y = y, w = w, h = h,
style = Utilities:copyWholeTable(self.defaultStyle)
}
--Soda.Button(narrationTable)
screen:addTextBox("narration", narrationTable)
--designate the narration as the last image to be drawn
--screen:setLastImageToDraw("narration")
end
function TwoChoiceAdventure:createScreenChoices(screen, screenTable)
print("screenTable.choices", table.unpack(screenTable.choices))
--if no choices are present, bail
if screenTable.choices == nil then
report("no choices found")
return
end
--for readability, make shorter local variables for the needed stored values
local textStyle, height, width = self.textStyle, self.choiceH, self.choiceW
--make the style table for the choice button's colors
local buttonStyle = StyleTable()
--set the desired fill and stroke (which is no stroke at all)
fill(197, 17, 20, 239)
strokeWidth(0)
--capture those settings in the style table
buttonStyle:captureCurrentStyle()
--make buttons from the screenTable's stored choice settings
for index, choiceTable in ipairs(screenTable.choices) do
--shorter variable name for readability
local choiceText, width, height = choiceTable.choiceText, self.choiceW, self.choiceH
--a name for the current button based on which choice it is ("choice1", "choice2", etc)
local buttonName = "choice"..index
--capture the x and y at which to draw this button
local choiceX, choiceY = self[buttonName.."X"], self[buttonName.."Y"]
--make the button image for the current choice
local buttn = Utilities:label(choiceText, width, height, nil, nil, textStyle, buttonStyle)
local w, h, x, y = self.choiceW, self.choiceH, choiceX, choiceY
--make a SODA table
local sodaTable = {
title = choiceText,
x = x, y = y, w = w, h = h,
style = Utilities:copyWholeTable(self.defaultStyle)
}
--add that image as a touchable
screen:addTouchableAt(buttonName, nil, nil, nil, sodaTable)
--create the action function for this button
local actionFunction = function()
report("performing choice action")
self:performChoiceTableResults(choiceTable)
end
--make a name for the action by appending "action" to the button name
local actionName = buttonName.." action"
--assign the action to the button
screen:assignActionToTouchable(actionName, actionFunction, buttonName)
--if this button depends on an inventory state, make the action that checks for that
self:createInventoryCheckIfNeeded(screen, choiceTable, buttonName)
end
end
function TwoChoiceAdventure:performChoiceTableResults(choiceTable)
--play button-pushing sound
sound("Game Sounds One:Menu Back", 0.2)
--check if a result screen was specified
if choiceTable.resultScreen ~= nil then
--if so, make it the current screen
self:changeScreen(choiceTable.resultScreen)
end
--check if an inventory item should be added
if choiceTable.inventoryAdd ~= nil then
--if so, add it
local inventoryImage = readImage("Project:"..choiceTable.inventoryAdd.icon)
self.inventory:addItem(choiceTable.inventoryAdd.name, inventoryImage, choiceTable.inventoryAdd.heightRatio)
--play fanfare sound
sound("Game Sounds One:Bell 2", 0.2)
end
--check if an inventory item should be removed
if choiceTable.inventoryRemove ~= nil then
--if so, remove it (note: using the name "allItems" will remove all items)
self.inventory:removeItem(choiceTable.inventoryRemove)
--play fanfare sound
sound("Game Sounds One:Bell 2", 0.2)
end
end
function TwoChoiceAdventure:createInventoryCheckIfNeeded(screen, choiceTable, buttonName)
--check if the given choiceTable has a value for "onlyIfInInventory"
if choiceTable.onlyIfInInventory == nil then
--if not, return
return
end
--compose a function that checks for the item and hides or reveals the button appropriately
local inventoryCheck = function ()
--store a boolean for whether or not the necessary inventory item is present
local itemPresent = self.inventory:checkForItem(choiceTable.onlyIfInInventory)
--store a boolean for whether or not the screen currently has a hidden button
local visibleButton = screen.hiddenButton == nil
--store a boolean that tells if the item and button are both showing
local correctShownState = itemPresent and visibleButton
--store a boolean that tells if neither the item nor the button are showing
local correctHiddenState = (not itemPresent) and (not visibleButton)
--if either both-showing or both-not-showing is true, things are the way they should be
if correctShownState or correctHiddenState then
return
--otherwise, check if the state is button-hidden-but-item-present
elseif not visibleButton then
--in which case, assume the inventory is in the correct state, and show the button
self:restoreHiddenButtonUsingName(screen, buttonName)
else
--and conversely, if item is not present, also obey inventory state, and hide button
self:hideButton(screen, buttonName)
end
end
--assign the function thus created to the inventoryCheck value of the screen given
screen.inventoryCheck = inventoryCheck
end
function TwoChoiceAdventure:hideButton(screen, buttonName)
--store the touchable with the given buttonName under the hiddenButton value
screen.hiddenButton = screen.touchables[buttonName]
--erase that touchable from the touchables table
screen.touchables[buttonName] = nil
--if the button that was hidden is choice 1 (the bottom button) we're all done, so return
if buttonName == "choice2" then
return
--if the button that was hidden is choice 1, move choice 2 up to take its place
elseif buttonName == "choice1" then
screen.touchables.choice1 = screen.touchables.choice2
screen.touchables.choice1.x = self.choice1X
screen.touchables.choice1.y = self.choice1Y
end
end
function TwoChoiceAdventure:restoreHiddenButtonUsingName(screen, buttonName)
--if there is no hiddenButton key, return
if screen.hiddenButton == nil then
return
--if the hidden button is choice 2, then put it back on the screen as choice 2
elseif buttonName == "choice2" then
screen.choice2 = screen.hiddenButton
--if the hidden button is choice 1, move the existing choice 1 down to choice 2
elseif buttonName == "choice1" then
screen.touchables.choice2 = screen.touchables.choice1
screen.touchables.choice2.x = self.choice2X
screen.touchables.choice2.y = self.choice2Y
--then return the hidden button to choice 1
screen.touchables.choice1 = screen.hiddenButton
end
--erase anything that's still assigned to the hiddenButton key
screen.hiddenButton = nil
end
function TwoChoiceAdventure:setImageSet(imageSet)
self.imageSet = imageSet
end
function TwoChoiceAdventure:startGame()
self:changeScreen("firstScreen")
end
function TwoChoiceAdventure:draw()
--draw the current screen, being sure to draw the narration last
self.currentScreen:draw()
--draw the inventory (if any)
self.inventory:draw()
end
function TwoChoiceAdventure:touched(touch)
report("adventure touched")
--pass touches to the current screen
self.currentScreen:touched(touch)
end
--# Screen
Screen = class()
--[[
Screen is a basic interactive screen, with methods for adding and removing images, touchable images ("touchables"), actions, and text.
]]
function Screen:init(name)
self.name = name
--default font specs
--note: the text functions of this class haven't been tested
self.screenFont = "SourceSansPro-Regular"
self.screenFontSize = 34
self.screenFontColor = color(236, 211, 63, 255)
--blank background image & white background color:
self.background = image(WIDTH,HEIGHT)
self.backgroundColor = color(255, 255, 255, 255)
--empty table for images
self.images = {}
--empty table for things that can be touched
self.touchables = {}
--empty table for texts to print
self.texts = {}
--empty table for actions that can be triggered
self.actions = {}
--variable for designating a touchable that has been touched
self.touchedButton = "none"
--color to use for tinting a touched touchable
self.touchedTint = color(103, 108, 134, 255)
end
function Screen:setFontSpecs(font, size, color)
--define font specs to use for all text
--note: the text functions of this class haven't been tested
self.screenFont = font
self.screenFontSize = size
self.screenFontColor = color
end
function Screen:setBackground(thisImage)
--store background image
self.background = thisImage
end
function Screen:setBackgroundColor(thisColor)
--store background color
self.backgroundColor = thisColor
end
function Screen:addImageAt(imageName, imageToAdd, positionX, positionY, heightRatio)
--report image name
report("addImageAt adding "..imageName)
--if the name given is unique, use it as a key for a table holding the image and position
if Utilities:nameIsUniqueAndNotNil(imageName, self.images, "image") == true then
self.images[imageName] = {}
self.images[imageName].image = imageToAdd
self.images[imageName].x = positionX
self.images[imageName].y = positionY
if heightRatio ~= nil then
report("addImageAt detected heightRatio")
self.images[imageName].heightRatio = heightRatio
else
report("no ratio detected")
end
end
end
function Screen:setLastImageToDraw(imageName)
--define the last image to be drawn (not including touchables)
self.lastDrawnImage = self.images[imageName]
end
function Screen:addTouchableAt(name, touchableImage, positionX, positionY, sodaTable)
--if the name given is unique, use it as a key for a table holding the image and position
--sodaTable means handle this as a SODA button, using given table as init
--sodaTables override other settings
--returns a boolean indicating success or failure
--maybe add a soda table item thats a reference to the button itself, to use for verifying its existence?
--exit with error if name is used already
if Utilities:nameIsUniqueAndNotNil(name, self.touchables, "touchable") == false then
print("Touchable named '"..name.."' error: name already exists.")
return false
end
--if it's sodaTable, store by name, and add isShowing and any image
if sodaTable ~= nil then
self.touchables[name] = sodaTable
self.touchables[name].image = touchableImage
print("made soda: ", name, self.touchables[name], "x is:", self.touchables[name].x)
else
--if not, make table and set parameters for Screen's use
self.touchables[name] = {}
--flag the touchable as active, meaning it responds to touches
self.touchables[name].state = "active"
--store negative case so that when all non-sodae are removed there is no cruft left
self.touchables[name].isNotSoda = true
self.touchables[name].x = positionX
self.touchables[name].y = positionY
end
--set the values used whether Soda table or not
self.touchables[name].image = touchableImage
--indicate success
return true
end
function Screen:addTextBox(boxName, sodaTable)
--if the name given is unique, use it as a key for a table holding the text and position
--note: the text functions of this class haven't been tested
if Utilities:nameIsUniqueAndNotNil(boxName, self.texts, "text") then
self.texts[boxName] = sodaTable
--self.texts[boxName].inactive = true
self.texts[boxName].style.highlight = {
shape = sodaTable.style.shape,
text = sodaTable.style.text
}
end
end
function Screen:addAction(actionName, actionFunction)
--if the name given is unique, use it as a key for storing the action function
if Utilities:nameIsUniqueAndNotNil(actionName, self.images, "action") == true then
self.actions[actionName] = actionFunction
end
end
function Screen:assignActionToTouchable(actionName, actionFunction, touchableName)
--add function to the actions table under the given name
self:addAction(actionName, actionFunction)
--do the complicated thing as long as un-soda elements exist
if self.touchables[touchableName].isNotSoda then
--define the action of the given touchable using the given action name
self.touchables[touchableName].action = self.actions[actionName]
return
else
self.touchables[touchableName].callback = self.actions[actionName]
end
end
function Screen:draw()
--draw the preset background color and background image
background(self.backgroundColor)
sprite(self.background, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
--draw all the imageTables unless they're the designated lastDrawnImage
for key, imageTable in pairs(self.images) do
self:drawUnlessLastImage(imageTable)
end
--draw the lastDrawnImage, if there is one
if self.lastDrawnImage ~= nil then
self:drawImageWithScaleIfAny(self.lastDrawnImage)
end
--draw all the touchables
for key, touchable in pairs(self.touchables) do
if touchable.isNotSoda then
self:drawTouchable(touchable)
else
if touchable.instance == nil then
touchable.instance = Soda.Button(touchable)
end
end
end
--draw the text
for key, textTable in pairs(self.texts) do
self:drawTextBox(textTable)
end
end
function Screen:drawImageWithScaleIfAny(imageTable)
--set width and height, using heightRatio if it exists
local drawnWidth, drawnHeight
if imageTable.heightRatio ~= nil then
drawnHeight = HEIGHT * imageTable.heightRatio
drawnWidth = drawnHeight * imageTable.image.width / imageTable.image.height
else
drawnHeight = imageTable.image.height
drawnWidth = imageTable.image.width
end
--go ahead and draw this thing
sprite(imageTable.image, imageTable.x, imageTable.y, drawnWidth, drawnHeight)
end
function Screen:drawTextBox(textTable)
--note: the text functions of this class haven't been tested well
if textTable.instance == nil then
textTable.instance = Soda.Button(textTable)
end
end
function Screen:drawUnlessLastImage(imageTable)
--if there is a lastDrawnImage and this is it, return without drawing anything
if self.lastDrawnImage ~= nil and imageTable == self.lastDrawnImage then
return
end
self:drawImageWithScaleIfAny(imageTable)
end
function Screen:drawTouchable(touchable)
--if the touchable's state is "active", draw it normally
if touchable.state == "active" then
sprite(touchable.image, touchable.x, touchable.y)
--if the touchable's state is "pressed", apply a tint before drawing it
elseif touchable.state == "pressed" then
tint(self.touchedTint)
sprite(touchable.image, touchable.x, touchable.y)
noTint()
end
end
function Screen:touched(touch)
report("touched")
--iterate through the touchables
for index, touchable in pairs(self.touchables) do
report(touchable.name)
--react if they were touched
self:reactIfTouched(touchable, touch)
end
end
function Screen:reactIfTouched(touchable, touch)
--once all touchables are Soda elements this function wont be needed
if touchable.isNotSoda == nil then
return
end
--define the touchable's rect
local tWidth, tHeight = touchable.image.width, touchable.image.height
local tX = touchable.x - (touchable.image.width / 2)
local tY = touchable.y - (touchable.image.height / 2)
--check if the touch is outside the touchable's rect
if Utilities:isPointInRect(touch, tX, tY, tWidth, tHeight) == false then
--if so, set the touchable to the "active" state
touchable.state = "active"
--and quit
return
end
--otherwise, if the touch has just started, flip the touchable to its "pressed" state
if CurrentTouch.state == BEGAN then
touchable.state = "pressed"
end
--if the touch has ended, put the touchable back in the "active" state and fire its action
if CurrentTouch.state == ENDED then
touchable.state = "active"
self:doTouchableActionIfAny(touchable)
end
end
function Screen:doTouchableActionIfAny(touchable)
--if the touchable has no action, return
if touchable.action == nil then
return
end
--otherwise do it!
touchable.action()
end
function Screen:removeImageNamed(imageName)
--find the named value in self.images and remove it
Utilities:removeValueByStringKey(self.images, imageName)
end
function Screen:removeTouchableNamed(touchableName)
--kill any Soda button under that name
if self.touchables[touchableName].instance ~= nil then
self.touchables[touchableName].instance.kill = true
end
--find the named value in self.touchables and remove it
Utilities:removeValueByStringKey(self.touchables, touchableName)
end
function Screen:removeActionNamed(actionName)
--find the named value in self.actions and remove it
Utilities:removeValueByStringKey(self.actions, actionName)
end
function Screen:removeTextNamed(textName)
--find the named value in self.texts and remove it
--note: the text functions of this class haven't been tested
Utilities:removeValueByStringKey(self.texts, textName)
end
function Screen:clearAllImages()
--iterate through all images (not counting images in touchables) and remove them
for index, imageTable in ipairs(self.images) do
table.remove(self.images, index)
end
end
function Screen:killSodaUIInstances()
print("should be clearing soda elements")
function killInstanceIfAnyIn(possiblySodaTable)
if possiblySodaTable.instance ~= nil then
possiblySodaTable.instance.kill = true
possiblySodaTable.instance = nil
end
end
function clearAllInstancesIn(tableOfElements)
for key, value in pairs(tableOfElements) do
print("examining "..key)
killInstanceIfAnyIn(value)
end
end
clearAllInstancesIn(self.touchables)
clearAllInstancesIn(self.texts)
clearAllInstancesIn(self.images)
end
--# Inventory
Inventory = class()
--[[
Inventory maintains a set of items and can draw their icons to the screen. Currently it is only configured to display these icons in one specific area of the screen, namely the lower third.
]]
function Inventory:init()
--the set of items (must be an ordered table so icons display in consistent order)
self.items = {}
--the arbitrary setting for space between icons
self.spaceBetweenIcons = WIDTH / 9
end
function Inventory:useOnlyCodeaImages(flagValue)
--used to toggle custom images off
self.shouldUseOnlyCodeaImages = flagValue
end
function Inventory:addItem(name, icon, heightRatio)
--prevent an item from being named the reserved term "allItems"
if name == "allItems" then
print("Error: cannot name inventory item \"allItems\". Item not added.")
return
end
--make a new table for this item
local itemTable = {}
--store the name and the icon in it
itemTable.name = name
itemTable.icon = icon
itemTable.heightRatio = heightRatio
--add the table to self.items
table.insert(self.items, itemTable)
end
function Inventory:checkForItem(itemName)
--booleans for the current search result and the ultimate result
local currentResult, ultimateResult = false, false
--iterate through self.items
for index, itemTable in ipairs(self.items) do
--set the currentResult to true if the names match
currentResult = Utilities:thisIfThisIsThisElseThis(true, itemName, itemTable.name, false)
--if currentResult is true, keep ultimateResult true through all subsequent loops
ultimateResult = ultimateResult or currentResult
end
--send back the result
return ultimateResult
end
function Inventory:removeItem(nameOfItem)
--if sent the special term "allItems", remove all items
if nameOfItem == "allItems" then
self:clearInventory()
return
end
--otherwise set up a variable to hold the index of the item to remove
local removeAt = 0
--go through all the items
for index, item in ipairs(self.items) do
--set removeAt to the current index if this item has the right name
removeAt = Utilities:thisIfThisIsThisElseThis(index, item.name, nameOfItem, removeAt)
end
--if removeAt has not been set, the given nameOfItem isn't in the table, so return
if removeAt == 0 then
return
else
--if removeAt has been set, remove the item at that index
table.remove(self.items, removeAt)
end
end
function Inventory:clearInventory()
--clear the inventory by setting it to an empty table
self.items = {}
end
function Inventory:draw()
--quit if there are no items to draw
if #self.items == 0 then
return
end
--position for first item to draw
-- local itemY = 103
-- local itemX = 160
local itemY = HEIGHT * 0.13411458
local itemX = WIDTH * 0.15625
--go through all the items
for index, item in ipairs(self.items) do
local drawnWidth, drawnHeight
drawnHeight = HEIGHT * item.heightRatio
drawnWidth = drawnHeight * item.icon.width / item.icon.height
--draw item at the given position
if self.shouldUseOnlyCodeaImages then
sprite("Planet Cute:Grass Block", itemX, itemY, drawnWidth, drawnHeight)
else
sprite(item.icon, itemX, itemY, drawnWidth, drawnHeight)
end
--scoot the position to the right for the next item
itemX = itemX + self.spaceBetweenIcons + (drawnWidth / 2)
end
end
--# StyleTable
StyleTable = class()
--[[
A StyleTable can preserve the current style settings and restore them when requested.
]]
function StyleTable:init()
--create an empty style table
self.style = {}
--initialize with whatever the current style values are
self:captureCurrentStyle()
end
function StyleTable:apply()
--go through all the style table's keys and values
for key, value in pairs(self.style) do
--use a key as a command for setting its value
_G[key](unpack(value))
end
end
function StyleTable:captureCurrentStyle()
--store all the current style settings
self.style.stroke = {stroke()}
self.style.strokeWidth = {strokeWidth()}
self.style.fill = {fill()}
self.style.font = {font()}
self.style.fontSize = {fontSize()}
self.style.textWrapWidth = {textWrapWidth()}
self.style.textAlign = {textAlign()}
self.style.textMode = {textMode()}
--(manually set any variable to nil to prevent it from being changed when apply() is called)
end
--# Utilities
Utilities = class()
--[[
Utilities contains various handy tools. It is not meant to be instantiated. Its functions are meant to be accessed as class methods. The functions have very self-explanatory names.
]]
function Utilities:copyWholeTable(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[Utilities:copyWholeTable(orig_key)] = Utilities:copyWholeTable(orig_value)
end
setmetatable(copy, Utilities:copyWholeTable(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
function Utilities:doesTableHaveStringKey(tableToCheck, stringKey)
--make a boolean for the given string being in the given table
local tableHasKey = false
--iterate through the table
for thisKey, thisValue in pairs(tableToCheck) do
--set the boolean to true if the key is found
tableHasKey = Utilities:thisIfThisIsThisElseThis(true, thisKey, stringKey, tableHasKey)
end
--return the boolean
return tableHasKey
end
function Utilities:stringKeyInThisTableForThisValue(tableToCheck, valueToFind)
--make a variable to hold the string key once found, set to an error message to start with
local stringKey = "string key not found"
--iterate through the table
for thisKey, thisValue in pairs(tableToCheck) do
--grab the key if it is found
stringKey = Utilities:thisIfThisIsThisElseThis(thisKey, thisValue, valueToFind, stringKey)
end
--return the key/error message
return stringKey
end
function Utilities:removeValueByStringKey(tableToCheck, keyString)
--find out if the key exists (without this method, if key doesn't exist you'll get an error)
if Utilities:doesTableHaveStringKey(tableToCheck, keyString) then
--if it does, nil it out
tableToCheck[keyString] = nil
--send true back
return true
else
--send false back if the key was not found
return false
end
end
function Utilities:arbitraryValueFromKeyValueTable(keyValueTable)
--a variable to hold the arbitrary value
local returnMe = nil
--iterate through the table
for key, value in pairs(keyValueTable) do
--grab the first value (unpredictable: pairs(table) doesn't guarantee consistent sorting)
returnMe = value
--stop after first iteration
break
end
--send the value back
return returnMen
end
function Utilities:numberOfKeyValueElements(tableToInspect)
--a variable with which to count the number of key-value elements in a table
local count = 0
--iterate through the table and count the variables
for key, value in pairs(tableToInspect) do
count = count + 1
end
--return the total
return count
end
function Utilities:nameIsUniqueAndNotNil(mightBeAName, givenTable, elementType)
--a boolean to hold whether or not the name is unique and not nil
local uniqueAndNotNil = true
--verify that a name has been given
if mightBeAName == nil then
print("error: can't check for name because mightBeAName is nil")
uniqueAndNotNil = false
end
--make sure name isn't used yet
if uniqueAndNotNil and Utilities:doesTableHaveStringKey(givenTable, mightBeAName) then
print("error: "..mightBeAName.." is a name already in use")
uniqueAndNotNil = false
end
--return the result
return uniqueAndNotNil
end
--label-creation function: only the first three parameters are mandatory
function Utilities:label(lText, width, height, textX, textY, textStyleTable, labelStyleTable)
--save current style settings
pushStyle()
--if a StyleTable object has been passed in for the label, use it, otherwise use defaults
if labelStyleTable ~= nil then
labelStyleTable:apply()
else
stroke(126, 127, 185, 255)
fill(55, 70, 110, 255)
strokeWidth(4)
end
--make a blank image for this label
local thisLabel = image(width, height)
--make all drawing happen on the image
setContext(thisLabel)
--set an x value for the text if specified, otherwise center it
local actualX
if textX ~= nil then
actualX = textX
else
actualX = width / 2
end
--set a y value for the text if specified, otherwise center it
local actualY
if textY ~= nil then
actualY = textY
else
actualY = height / 2
end
--draw the label rect
rect(0, 0, width, height)
--if a StyleTable object has been passed in for the text, use it, otherwise use defaults
if textStyleTable ~= nil then
textStyleTable:apply()
else
font("SourceSansPro-Regular")
fontSize(25)
fill(192, 192, 218, 255)
textAlign(CENTER)
end
--draw the text to the image
text(lText, width / 2, height / 2)
--set drawing back to happening on the device screen
setContext()
--restore the current style settings
pushStyle()
--return the label
return thisLabel
end
--check if a given point is inside a rect
function Utilities:isPointInRect(point, rectX, rectY, rectWidth, rectHeight)
--a variable to return, initially stating the point to be outside the rect
local pointIsInRect = false
--if the point is inside the rect's values, make the variable true
if point.x > rectX and point.x < rectX + rectWidth and point.y > rectY
and point.y < rectY + rectHeight then
pointIsInRect = true
end
--send back the result
return pointIsInRect
end
function Utilities:playSoundAndWaitUntilFinished(soundName)
--start the sound playing and assign it to a variable
local soundMonitor = sound(soundName)
--run an empty loop until the sound is over
while soundMonitor.playing == true do
--nothing
end
end
function Utilities:thisIfThisIsThisElseThis(this, ifThis, isThis, elseThis)
--note: this is basically the equivalent of the C syntax "a == b ? c : d"
--if the two middle args are equal, return the first arg, otherwise return the last arg
if ifThis == isThis then
return this
else
return elseThis
end
end
function Utilities:makeResizedImage(imageToResize, desiredW, desiredH)
--make a blank image of the specified dimensions (the "+ 1"s should not be needed but are)
local newImage = image(desiredW + 1, desiredH + 1)
--set drawing operations to go to that image
setContext(newImage)
--draw the image to be resized at the size desired
sprite(imageToResize, desiredW / 2, desiredH / 2, desiredW, desiredH)
--set drawing operations to go to the screen, same as normal
setContext()
--send back the new image
return newImage
end
function Utilities:makeTintedImage(imageToTint, tintColor)
--make a blank image of the same size
local newImage = image(imageToTint.width, imageToTint.height)
--set drawing operations to go to that image
setContext(newImage)
--set tinting to the color specified
tint(tintColor)
print("imagetotint", imageToTint)
--draw the image to be tinted
sprite(imageToTint , imageToTint.width / 2, imageToTint.height / 2)
--remove the tint
noTint()
--set drawing operations to go to the screen, same as normal
setContext()
--send back the new image
return newImage
end
function Utilities:pointSizeToFitTextToHeight(textToFit, fontName, textWidth, textHeight)
pushStyle()
font(fontName)
textWrapWidth(textWidth)
--variables to track point size, track current height, and a dummy variable for width
local pointSize, currentHeight, unusedWidthVariable = 0, 0, 0
--make a loop that runs until the current height is one point higher than the desired height
while currentHeight <= textHeight do
--bump the current point setting by 1
pointSize = pointSize + 1
--set the fontSize to the new setting
fontSize(pointSize)
--get width (which isn't used for anything) and height of text at current point size
unusedWidthVariable, currentHeight = textSize(textToFit)
end
--take the point size back one point, since that was the last size that wasn't too big
pointSize = pointSize - 1
popStyle()
--send that point size back
return pointSize
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment