Skip to content

Instantly share code, notes, and snippets.

@GlueBalloon
Last active May 10, 2021 15:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GlueBalloon/594f2a8ab825d4ec232d to your computer and use it in GitHub Desktop.
Save GlueBalloon/594f2a8ab825d4ec232d to your computer and use it in GitHub Desktop.
Double choose kid's adventure game
--# Main
-- Main
saveProjectInfo( "Description", "Two-choice Adventure Games by Rosie and Charlotte." )
--**NOTE** Assets can be found at:
--https://www.dropbox.com/sh/hg5cplngi7v7268/AAA8ezcK_ZJk2gZNF52sAvL_a?dl=0
--**project will not run without these in the project folder**
--[[
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(OVERLAY)
--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 setup()
loadingCoroutine=coroutine.create(setupBetweenDraws)
coroutine.resume(loadingCoroutine)
end
function setupBetweenDraws ()
coroutine.yield()
prepareGlobalVariables()
coroutine.yield()
startBackgroundMusic()
coroutine.yield()
createGames()
coroutine.yield()
createGameMC()
coroutine.yield()
mainScreen = doubleMC
report("finished setup")
doneLoading = true
end
function report(...)
if printingReports then
local args = {...}
print(table.unpack(args))
end
end
function prepareGlobalVariables()
customSounds = false
printingReports = true
doneLoading = false
report("finished prepareGlobalVariables")
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()
doubleMC:addGame("Rosie's Game", rosiesGame)
doubleMC:addGame("Charlotte's Game", charlottesGame)
customizeGameSelectionScreen()
customizeGameExitButtons()
addInfoScreenToGameMC()
report("finished createGameMC")
end
function customizeGameSelectionScreen()
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
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
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()
--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)
font("SourceSansPro-Regular")
fontSize(HEIGHT / 28)
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 / 2), textH + (textH / 2)
--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
doubleMC.exitButton.x = (exitLabel.width / 2) - 1
doubleMC.exitButton.y = HEIGHT / 6
end
function loadImages(tableWithImageNamesAsKeys)
--go through the keys in the table
for key, value in pairs(tableWithImageNamesAsKeys) do
--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
function draw()
backingMode(RETAINED)
if doneLoading then
mainScreen:draw()
return
else
sprite("Project:doubleMenuBackground", WIDTH /2, HEIGHT/2, WIDTH, HEIGHT)
sprite("Project:Loading Text",WIDTH /2, HEIGHT/2, WIDTH, HEIGHT)
coroutine.resume(loadingCoroutine)
end
end
function touched(touch)
--if the games have not been created, return without processing touches
if gamesCreated == false then
return
end
--send the touch to the main screen, which redirects it from there
mainScreen:touched(touch)
--note: mainScreen gets defined in createGames()
end
function addInfoScreenToGameMC()
local screenForButton = doubleMC.gameSelectionScreen
local infoText = " 'Double Choose' is two small adventure games written entirely by my daughters Rosie and Charlotte. I programmed it with the iPad app Codea. Click on Rosie to play Rosie's game, and click on Charlotte to play Charlotte's game.\n\n Rosie did the art for her game, though I helped a tiny bit. Rosie also kindly contributed one screen for Charlotte's game. Charlotte did some coloring for her game art, and firmly art-directed me on everything else.\n\n We were inspired by the Behold Studios adventure game 'The Story of Choices', in which every screen gives you a choice between two actions. I realized that making a simple two-choice system would be easy with Codea. So I asked my daughters if they wanted to make their own 'Story of Choices'. They did! Though they made some big changes, they both used the same overall story structure as 'The Story of Choices': the hero wakes up and gets given a royal mission.\n\n I recommend playing through each game several times to see all the possible endings. The games are the most fun when approached less as puzzles to solve and more as glimpses into how differently kids think. Rosie's game is mostly about feelings, which isn't clear until you see all of her awesome emotionally-excruciating endings. And Charlotte is more interested in making cool puzzles. Her game actually includes a little adventure-game-style inventory management--an impressive feat for a five-year-old.\n\n We hope you enjoy our games, and we especially hope they inspire other kids to make games too. Try Codea, it rocks!"
--create a style table for the info screen text
local infoStyle = StyleTable()
--define most text properties except fontSize
fill(85, 85, 85, 255)
font("HelveticaNeue")
textAlign(LEFT)
textWrapWidth(WIDTH - (WIDTH / 8))
--use a Utilities method to find the right fontSize for the current device's screen
local infoFontSize = Utilities:pointSizeToFitTextToHeight(infoText, HEIGHT - (HEIGHT / 15))
--set the fontSize to the determined value
fontSize(infoFontSize)
--capture the current style settings in the style table
infoStyle:captureCurrentStyle()
--create the info screen as a variable of the given screen
screenForButton.infoScreen = Screen()
--convenience reference to gameMCImages.infoCircle
local iCircle = gameMCImages.infoCircle
--determine the correct the size of the info circle image
local iCircleSize = WIDTH * 0.08
--adjust the info circle to the defined size
iCircle = Utilities:makeResizedImage(iCircle, iCircleSize, iCircleSize)
--define the placement of the info circle
local iCircleOffset = iCircleSize * 0.7
--tint the info circle image
iCircle = Utilities:makeTintedImage(iCircle, color(255, 255, 255, 120))
--add the info circle image as a button on the given screen
screenForButton:addTouchableAt("infoCircle", iCircle, iCircleOffset, iCircleOffset)
--make a function that changes the main screen to the info screen
local showInfo = function()
mainScreen = screenForButton.infoScreen
end
--use that function as the action for the info circle button
screenForButton:assignActionToTouchable("showInfo", showInfo, "infoCircle")
--add the info text to the info screen
screenForButton.infoScreen:addTextAt("infoText", infoText, WIDTH / 2, HEIGHT / 2, infoStyle)
--define the info screen's touch method to return to the game selection screen after any touch
screenForButton.infoScreen.touched = function (screenInstance, touch)
if CurrentTouch.state == ENDED then
mainScreen = doubleMC
end
end
end
function makeRosiesGame()
--create the game object
local rosiesGame = TwoChoiceAdventure("Rosie's Game")
--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 end up hated by everybody.",
choices = {
{choiceText = "The End", resultScreen ="firstScreen"} }
},
{name = "boyfriendAsks",
background = "rcBoyfriendAsks",
images = {},
narration = "Your boyfriend says 'Do you want to know my secret or 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 I 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 prettier 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 says you should be Queen, but only if you deliver some birthday invitations for 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 disappointed because they wanted you to be queen.",
choices = {
{choiceText = "The End", resultScreen ="firstScreen"} }
},
{name = "doYouHaveASecret",
background = "rcSecretOrQueen",
images = {},
narration = "The Queen asks:\n\rdo you have a secret or 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: \"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")
--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", 235, 442},
{"coffeeSmall", "ccCoffeeSmall", 468, 439} },
narration = "You woke up.\n\rWhat do you want to do with your coffee?",
choices = {
{choiceText = "drink it", resultScreen = "drankCoffee"},
{choiceText = "save it for later", resultScreen ="savedCoffee",
inventoryAdd = {name = "coffee", icon = "ccCoffeeBig"} } }
},
{name = "drankCoffee",
background = "ccFirst",
images = {
{"heroine", "ccHeroine", 235, 442} },
narration = "It tastes good.",
choices = {
{choiceText = "go outside", resultScreen = "boyfriendTellsAboutQueen"} },
},
{name = "savedCoffee",
background = "ccFirst",
images = {
{"heroine", "ccHeroine", 235, 442} },
narration = "You keep it with you for later.",
choices = {
{choiceText = "go outside", resultScreen = "boyfriendTellsAboutQueen"} },
},
{name = "boyfriendTellsAboutQueen",
background = "ccGenericOutside",
images = {
{"heroine", "ccHeroine", 319, 442},
{"boyfriend", "ccBoyfriend", 553, 450} },
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", 274, 442} },
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"} } }
},
{name = "knightGivesHeart",
background = "ccKnightScreen",
images = {
{"heroine", "ccHeroine", 274, 442} },
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", 274, 442} },
narration = "The knight tells you that you have a bad attitude.",
choices = {
{choiceText = "go home", resultScreen ="homeAfterKnight" } }
},
{name = "homeAfterKnight",
background = "ccFirst",
images = {
{"heroineFlipped", "ccHeroineFlipped", 400, 433} },
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", 225, 442} },
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", 230, 442} },
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", 700, 458},
{"puppeteerGrouchy", "ccPuppeteerGrumpy", 325, 458} },
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", 693, 458},
{"puppeteerHappy", "ccPuppeteerHappy", 325, 458} },
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", 271, 430} },
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", 271, 440} },
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", 512, 495},
{"boyfriend", "ccBoyfriend", 243, 452} },
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", 319, 452} },
narration = "You become the queen!",
choices = {
{choiceText = "The End", resultScreen ="firstScreen",
inventoryRemove = "allItems" } }
},
{name = "happyInForest",
background = "ccHappyInForest",
images = {
{"happyFamily", "ccHappyFamily", 326, 323} },
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
gameMCImages.infoCircle = 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 assignImageSetsAllAtOnce()
--image keys for the game selection screen
gameMCImages = {}
gameMCImages.doubleMenuRosie = readImage("Project:doubleMenuRosie")
gameMCImages.doubleMenuCharlotte = readImage("Project:doubleMenuCharlotte")
gameMCImages.doubleMenuBackground = readImage("Project:doubleMenuBackground")
gameMCImages.infoCircle = readImage("Project:infoCircle")
--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()
--initialize the superclass
Screen.init(self, "GameMC")
--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
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)
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(unpack(touchableArgs))
--make a function that runs this class's endGame() function
local exitGame = function () self:endGame() end
--add that function to the given game as the action for the exit button to run
self.currentGame.firstScreen:assignActionToTouchable("exit", exitGame, "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")
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)
--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()
--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
self.narrationW = WIDTH / 2.15
self.narrationH = HEIGHT / 3.4
self.narrationX = WIDTH - (self.narrationW / 2) - 35
self.narrationY = HEIGHT / 2.9
--specify the size of the choice buttons
self.choiceW = self.narrationW
self.choiceH = HEIGHT / 13.9
--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
self.gapBetweenChoices = HEIGHT / 100
--find the bottom of the narration box
local bottomOfNarration = self.narrationY - (self.narrationH / 2)
--set the vertical positions of the choice buttons
self.choice1Y = bottomOfNarration - (self.choiceH / 2) - self.gapBetweenChoices
self.choice2Y = self.choice1Y - self.gapBetweenChoices - self.choiceH
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:
choice = 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 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
--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
--wipe out the current screen, hoepfully preventing a crash from all these screens being made
self.currentScreen = nil
--put in the new screen
self.currentScreen = assembledScreen
end
function TwoChoiceAdventure:placeScreenImages(screen, screenTable)
report("screen:", table.unpack(screen), "screenTable:", table.unpack(screenTable))
--load the background image
screen:setBackground(readImage("Project:"..screenTable.background))
--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 = readImage("Project:"..imageTable[2])
report(imageTable[2])
--and add each image by unpacking its image table directly into addImageAt
screen:addImageAt(imageTable[1], thisImage, imageTable[3], imageTable[4])
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)
--designate the narration as the last image to be drawn
screen:setLastImageToDraw("narration")
end
function TwoChoiceAdventure:createScreenChoices(screen, screenTable)
--if no choices are present, bail
if screenTable.choices == nil then
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)
--add that image as a touchable
screen:addTouchableAt(buttonName, buttn, choiceX, choiceY)
--create the action function for this button
local actionFunction = function() 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
self.inventory:addItem(choiceTable.inventoryAdd.name, readImage("Project:"..choiceTable.inventoryAdd.icon))
--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)
--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)
--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
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)
--if the name given is unique, use it as a key for a table holding the image and position
if Utilities:nameIsUniqueAndNotNil(name, self.touchables, "touchable") == true then
self.touchables[name] = {}
self.touchables[name].image = touchableImage
self.touchables[name].x = positionX
self.touchables[name].y = positionY
--flag the touchable as active, meaning it responds to touches
self.touchables[name].state = "active"
--indicate success
return true
end
--indicate failure
return false
end
function Screen:addTextAt(textName, textString, positionX, positionY, styleTable)
--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(textName, self.texts, "text") == true then
self.texts[textName] = {}
self.texts[textName].text = textString
self.texts[textName].x = positionX
self.texts[textName].y = positionY
self.texts[textName].styleTable = styleTable
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 the given action function under the given action name
self:addAction(actionName, actionFunction)
--define the action of the given touchable using the given action name
self.touchables[touchableName].action = self.actions[actionName]
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
sprite(self.lastDrawnImage.image, self.lastDrawnImage.x, self.lastDrawnImage.y)
end
--draw all the touchables
for key, touchable in pairs(self.touchables) do
self:drawTouchable(touchable)
end
--draw the text
for key, textTable in pairs(self.texts) do
self:drawTextTable(textTable)
end
end
function Screen:drawTextTable(textTable)
--note: the text functions of this class haven't been tested well
--set aside the current style for use later
pushStyle()
--if no StyleTable has been defined, use the defaults from this screen
if textTable.styleTable == nil then
font(self.screenFont)
fontSize(self.screenFontSize)
fill(self.screenFontColor)
textMode(CENTER)
else
--otherwise use the settings from the StyleTable
textTable.styleTable:apply()
end
--draw the text at its stored location
text(textTable.text, textTable.x, textTable.y)
--restore the style that was set aside
popStyle()
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
--otherwise go ahead and draw this thing
sprite(imageTable.image, imageTable.x, imageTable.y)
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)
--iterate through the touchables
for index, touchable in pairs(self.touchables) do
--react if they were touched
self:reactIfTouched(touchable, touch)
end
end
function Screen:reactIfTouched(touchable, touch)
--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)
--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
--# 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:addItem(name, icon)
--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
--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
--go through all the items
for index, item in ipairs(self.items) do
--draw item at the given position
sprite(item.icon, itemX, itemY)
--scoot the position to the right for the next item
itemX = itemX + self.spaceBetweenIcons + (item.icon.width / 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: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)
--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, desiredHeight)
--note: this function assumes that textWrapWidth has been set to define the desired width
--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 <= desiredHeight 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
--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