table of contents - github€¦ · [gcc 4.2.1 (llvm, emscripten 1.5)] on linux2 this is just...

170
1.1 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 Table of Contents Introduction 1.0 - Programming basics 1.1 - Interactive coding 1.2 - Strings 1.3 - Nil and variables 1.4 - Using functions 1.5 - Comments in code 1.6 - Scripting and printing 1.7 - Making functions 1.8 - Booleans 1.9 - Flow control 1.10 - While 1.11 - Type checking 1.12 - First game 1.13 - Tables (part 1) 1.14 - Tables (part 2) 1.15 - For loops (part 1) 1.16 - For loops (part 2) 1.17 - Scopes 1.18 - Chapter review 2.0 - Introducing LÖVE 2.1 - Up and running 2.2 - LÖVE structure 2.3 - Geometry 2.4 - Game loop 2.5 - Delta time 2.6 - Mapping 2.7 - The world 2.8 - Reading documentation 2.9 - Modules and organization 2.10 - Collision callbacks 2.11 - Breakout (part 1) 2.12 - Breakout (part 2) 2.13 - Breakout (part 3) 2.14 - Breakout (part 4) 2.15 - Breakout (part 5) 2.16 - Binary and bitmasks 1

Upload: others

Post on 25-Apr-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

1.1

1.2

1.2.1

1.2.2

1.2.3

1.2.4

1.2.5

1.2.6

1.2.7

1.2.8

1.2.9

1.2.10

1.2.11

1.2.12

1.2.13

1.2.14

1.2.15

1.2.16

1.2.17

1.2.18

1.3

1.3.1

1.3.2

1.3.3

1.3.4

1.3.5

1.3.6

1.3.7

1.3.8

1.3.9

1.3.10

1.3.11

1.3.12

1.3.13

1.3.14

1.3.15

1.3.16

TableofContentsIntroduction

1.0-Programmingbasics

1.1-Interactivecoding

1.2-Strings

1.3-Nilandvariables

1.4-Usingfunctions

1.5-Commentsincode

1.6-Scriptingandprinting

1.7-Makingfunctions

1.8-Booleans

1.9-Flowcontrol

1.10-While

1.11-Typechecking

1.12-Firstgame

1.13-Tables(part1)

1.14-Tables(part2)

1.15-Forloops(part1)

1.16-Forloops(part2)

1.17-Scopes

1.18-Chapterreview

2.0-IntroducingLÖVE

2.1-Upandrunning

2.2-LÖVEstructure

2.3-Geometry

2.4-Gameloop

2.5-Deltatime

2.6-Mapping

2.7-Theworld

2.8-Readingdocumentation

2.9-Modulesandorganization

2.10-Collisioncallbacks

2.11-Breakout(part1)

2.12-Breakout(part2)

2.13-Breakout(part3)

2.14-Breakout(part4)

2.15-Breakout(part5)

2.16-Binaryandbitmasks

1

Page 2: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

1.3.181.3.17

1.4

1.4.1

1.4.2

1.4.3

1.4.4

1.4.5

2.17-Networking(part1)

2.18-Networking(part2)

3.0-Programmingin-depth

3.01-Primitivesandreferences

3.02-Higher-orderfunctions

3.03-Mapandfilter

3.04-Stackandrecursion

3.05-Reduce

2

Page 3: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

learn2loveCurrentprogress:

Chapter1-Programmingbasics✔Chapter2-IntroducingLÖVE✔Chapter3-Programmingindepth(inprogress)Chapter4-LÖVEindepth(todo)

Viewasawebpage:link

Downloadinebookformat:pdf-epub

Whatisthisbook?ThisbookteachesprogrammingfromthegroundupinthecontextofLuaandLÖVE.Itteachesbasiccomputerscienceandsoftwarebuildingskillsalongtheway,butmoreimportantly,teachesyouhowtoteachyourselfandfindouthowtogoaboutsolvingaproblemorbuildingasolution.Toolscomeandgo,sothegoalistoteachthingsofvaluewithlessfocusontheprogramminglanguageandothertoolsusedtobuildthesoftware.Ihavebeenprogrammingsince2007,focusingonteachingmyselfbestpractices.AlongthewayIhavefoundalotofgoodandbadtutorialsontherightandwrongwaytobuildthingsandIwanttohelpothersavoidgettingstucklikeIdid.

Whoisthisfor?Anyagegroup.Kidstoo,withabitofdemonstration,helpandencouragement!Anybodythatwantstolearnbasiccomputerscience.Thisbookwilltouchonseveralcomputersciencesubjectsinordertobuildprograms.Anybodythatwantstolearntoprogram.Nopriorskillsorknowledgerequired.Anybodythatwantstolearntomakeagame.Makinggamesarefunandrequirelearningmanythingsalongtheway.We'llbuildafewthroughthisbook.AnybodythatwantstolearnLua.Althoughwewon'tdiveintotheadvancedfeaturesofthelanguage,wewillgainalargeunderstandingonhowthelanguageworksinordertoactuallybuildsomethings.Therearealreadyonlineguidesandreferencescoveringsomeofthemoreadvancedtopics.ForexperiencedprogrammerswantingtolearnLua,theProgramminginLuabookmaybesufficient.

Authorandcontributorsjaythomas:OriginalauthorJimmyStevens:Editsandsuggestionsinchapter1&2rm-code:Chapter2gettingstartedValentinChCloud:Chapter3primitivesandreferences

ContributingIssues,comments,andsuggestionscanbemadeusingtheGitHubissuespage.Todownload,build,andrunthebookoranycodeexamplesusethe"Cloneordownload"buttononthemainrepositorypage.

Introduction

3

Page 4: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Fordevelopersandthecurious

Feelfreetosubmitapullrequest.ThedocumentationisbuiltusingNodeJS.Ifyouwishtorunthedocumentationforlocaldevelopmentpurposes,installnodejsthenrunthesecommandsfromwithinthe learn2lovedirectoryyoudownloaded:

npminstall#Downloadsbuildtoolstothea"node_modules"folderinsidethedirectory

npmstart#Createsalocalwebservertowhereyoucanvisitthelinkhttp://localhost:4000

Oncethelocalwebserverisrunning,anyeditsyoumaketothepageswillrebuildthebookandreloadthepageyou'reviewing.

Introduction

4

Page 5: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Chapter1:ProgrammingBasicsThegoalofthischapteristoteachthemostnecessarybuildingblocksofprogramming.Bytheendofthechapteryouwillbebeabletobuildbasicprogramswhichwewillapplywithexercisesinthefollowingchapters.

1.0-Programmingbasics

5

Page 6: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Interactivecoding

What'saREPL?Programmingdoesn'ttakemucheffortbeyondloadingupaREPLandjusttyping.WhatisaREPL?It'saninteractivewindowyoucantypecodeintoanditspitsouttheresultsonscreenwhenyouhitenter.ItstandsforRead-Evaluate-Print-Loop.Thesearethe4thingstheREPLdoes:

1. Readthecodethatwasjusttyped2. Evaluate,orprocessthecodedownintoaresult3. Print,orspitouttheresult4. Loop...doeverythingagainandagainuntiltheprogrammerisdone

It'sactuallysimplerthanitsounds.Let'sgotoawebsitewithaREPLandtryitout:https://repl.it/languages/Lua

Youwillseetwowindowpanesonthewebsite:alightsideontheleftanddarksideontheright.Theright-sideistheREPLandiswhatwe'reinterestedinfornow.Ithasalotofinformationthatisn'tnecessarilyusefultousatthemoment.Somethingsimilartothis:

Lua5.1Copyright(C)1994-2006Lua.org,PUC-Rio

[GCC4.2.1(LLVM,Emscripten1.5)]onlinux2

ThisisjusttellingyouwhatprogramminglanguagethisREPLisloading,inthiscase,Lua.Ifyouclickinsidethewindowpaneandstarttypingyouwillseeyourtextappear.

Let'strytypingsomecodefortheREPLtoRead.Youalreadyknowsomecodeifyouknowarithmetic.Type:

2+2

ThenhitENTERandimmediatelytheREPLwillPrintout:

=>4

Alothappenedveryquickly.AfterhittingENTER,theREPL,Readtheline 2+2,itEvaluatedthevalueofthatstatementtobe 4,itPrinted4onthescreenforyou,thenLoopedbacktoanewlinetoawaityournextcommand.Tryoutsomemorearithmetic.Multiplication:

2*3

Subtraction:

2+2-4

Division:

6/2

Youcanuseparenthesistotellitwhichordertodotheoperations:

(2+2)*(3+1)

1.1-Interactivecoding

6

Page 7: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Whichgivesdifferentresultsthan:

2+2*3+1

IfyougivetheREPLasinglenumber:

12

Itwillgiveyouback 12,becausethiscan'tbesimplifieddownanyfurther.

Youcanalsodoexponentsusingthe ̂ (caret)symbol:

2^4

Numbersareatypeofdata,and +, -, /, *, ̂ , %areoperators.Statementssuchas 2-2and 23*19arealloperations.

Onelastarithmeticoperationwe'llcoverismodulo,whichisdonewiththemodulusoperator.Themodulusoperatorisrepresentedinmostlanguagesasa %(percent)symbol:

8%3

Modulusoperationsaren'tseeningradeschoolclassroomsasoftenastherest,butarequitecommoninsoftwareandcomputersciences.Thewayitworksisyoutakethe2ndnumberandsubtractitfromthebiggernumberasmanytimesaspossibleuntilthe2ndnumberisbiggerthanthe1st.Theresultiswhat'sleftofthe1stnumber.With 8%3,ifyoukeepsubtracting 3from 8thenyouendupwith 2left.

Arealworldexampleistimeelapsingonananalogclock.Imaginethefaceofaclockwiththehourhandonnoon.If25hourspassthenthehourhandgoesallthewayaroundtwiceandendson1.Thatwouldbeequivalenttowriting:

25%12

=>1

Thehourhandresetseverytimeitpasses12,so 13%12, 25%12,and 37%12wouldallequal 1.Likewise, 10%4resultsin 2because4goesinto10twice,andleavesaremainderof2.

ExercisesTrytypingdifferentmodulooperationsinandguessingwhattheanswerwillbe.Tryusingnegativenumbers( -3+-2).Tryusingasetofparenthesisinsideanothersetofparenthesis.Doesitbehaveasyouexpect?Afterrunningthroughalltheexercisespressthe'up'keyintheREPL.Whathappensandhowcanthisspeedupyourwork?

1.1-Interactivecoding

7

Page 8: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

StringsNumbersareonetypeofdatathatcanbeoperatedon.Let'sexploreanotherdatatypewithintheREPL.TakeasetofquotesandputsometextinitandhitENTER:

"hello"

TheREPLwillprint hellobacktoyou.Thisisastring.Astringisasetofcharacters(lettersandsymbols)stringedtogetherasonesinglepieceofdata.Thisstringismadeof9characters:

"H-E-L-L-O"

Likenumbers,thereareoperatorstomakestringsplaywitheachother.Theconcatenateoperator( ..)combinesstringstogether:

"hello".."world"

What'stheresult?Noticethattheresultingstringhasnospacebetweenthetwowords.Ifyouwantedaspace,youwouldhavetoputoneinthequotestobepartoftheoperation:

"hello".."world"

Youcouldevenmakeaseparatestringwiththespaceinit:

"hello".."".."world"

Stringscanhaveanycharactersinthemthatyouwant.

"abc".."123"

"Япо́нский".."ロシア語!!"

ExercisesTryusinganarithmeticoperatoronstrings "hello"/"world".Whathappens?Tryusingtheconcatenateoperator( ..)onnumbers( 1..1).

1.2-Strings

8

Page 9: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Nilandvariables

Data,orthelackthereofHumanshavedifferentwaysofrepresentingalackofdata.Iftherearenosheeptocountthenwehavezerosheep.Iftherearenowordsonapagethenthepageisblank.Inacomputerwemayrepresentthenumberofsheepas 0orthemissingwordsonapageasanempty "".Thesearestilldatathough...anumberandastring.Insoftwarewhenyouwanttorepresentalackofdatawehave:

nil

Sometimescalled nullor undefineddatainotherlanguages.It'sseeminglyuseless.Youcan'tuseoperatorsonnil.

nil+nil

Thiswillprintanerrorlikeitdidwhenyoutrieddoingarithmeticonstrings.Let'stakealookatvariablesandwe'lldiscoverthepurposeof nil.

VariablesSometimesyouwanttowriteoutdata,butyouwantthatdatatobeeasytochange.Variablesletyougivedataanametoreference.Here'sanexampletotry:

name="Mandy"

"hellomynameis"..name

Sinceyoutolditwhat nameis,itknowswhatvaluetoaddtothestring "hellomynameis".Ifyoutype:

name

...andhitENTER,itwillprintoutthevaluethatbelongstothisvariabletoremindyou.The =(equal)signtellsLuathatyouwanttoassignavaluetothegivenname/variable.Youcanchangethevalueofavariableandgetdifferentresults:

name="Jeff"

"hellomynameis"..name

Assignmentisn'tthesameasitisinAlgebra.Youcanchangethevalueofavariablemultipletimes.Wecantell namethatitequalsitselfwithsomeadditionalinformationconcatenatedtoit:

name="abc"

name=name.."def"

name

Youcanassignanytypeofdatatoavariable,includingnumbers:

name="Jeff"

1.3-Nilandvariables

9

Page 10: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

age=16

"hellomynameis"..name.."andIam"..age.."."

Youcanchangenumbersafterassignmenttoo:

age=16

age=age*2

"myagedoubledis"..age

So,whatifyoutypeinamadeupvariablename?

noname

Youwillseeithas nil,ornodatayet.Ifyoutrytouse nilinyourstringoperationyouwillgetanerror:

"hellomynameis"..nil

[string"return"hellomynameis"..nil"]:1:attempttoconcatenateanilvalue

"hellomynameis"..noname

[string"return"hellomynameis"..noname"]:1:attempttoconcatenateglobal'noname'(anilvalue)

Tryassigningavaluetoavariablename:

best_color="purple"

thenassigningthatvariabledatatoanother:

worst_color=best_color

worst_color

You'llseethatbothvariablesnowhavethevalue "purple".

Variablescanhavenamesmadeupofletters,numbersandunderscores( _).Variablenamescannotbeginwithanumberthough,otherwiseitwillthinkyou'retryingtotypeinnumberdata.Here'ssomeexamplesofvalidvariables:

my_dog="Poe"

myDog="Zia"

DOG3="Ember"

ExercisesTryoutdifferentvariablenames.Tryafewinvalidvariablesnamestoojusttoseewhattheerrormessagelookslike.It'simportanttoseeerrormessagesandunderstandthem.Theyhelpyouunderstandhowaprogrambreakssoyoucanfixit.

1.3-Nilandvariables

10

Page 11: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

UsingfunctionsMostprogramminglanguagescomewithsomevariablesalreadydefinedforus.Luahasmany,solet'stypeoneinandhitENTERtoseewhatthevalueis:

string.reverse

=>function:0x2381b60

Ohmy.So"function"isanotherdatatype,butwhatis 0x2381b60?It'sjusttellingyouwhereinthecomputer'smemorythatfunctionexists,justincaseyouwantedtoknow.Functionsworkverydifferentlythannumbersinstrings.Essentiallyfunctionsarepre-definedinstructionsthattelltheprogramhowtododifferentthings.Theytakedataandreturnbackdifferentdata.Let'sseehowtogivethisfunctiondata:

string.reverse("hello")

=>olleh

Attheendofthefunction'svariablename, string.reverse,wetypeasetofparenthesis, string.reverse(),andputinsidetheparenthesissomedatawewantchanged( string.reverse("hello")).Makingthefunctionrunisoftencalledinvokingthefunction.Havingafunctionthatreversestextinastringforuscanbeuseful,andwecancapturethereturnvalue(theresults)ofthefunctionusingavariable.Tryitout:

greeting="hello,howareyou?"

backwards_greeting=string.reverse(greeting)

backwards_greeting

=>?uoyerawoh,olleh

Itshouldbeobviousfromthenamewhatthatfunction'spurposeis.Howaboutthisone?

string.upper("hello,howareyou?")

Nowtrycapturingthatvaluebyassigningittoavariable:

greeting="hello,howareyou?"

shouting_greeting=string.upper(greeting)

crazy_greeting=string.reverse(shouting_greeting)

Wecangetcrazier.Howaboutinvokingafunctionwheninvokinganotherfunction??

string.reverse(string.upper("hey"))

What'shappeninghereisthestringisbeinguppercasedby string.upperbutthenthevaluefrom string.upperisbeingreversedby string.reverseassoonasitisdone.It'sjustlikeinarithmeticwhenyouhavenestedparenthesis.Theinner-mostparenthesisareresolvedbeforedoingtheouter-mostparenthesis.

Let'stryonemorefunction.Thisfunctionhastwoparameters,meaningitacceptstwopiecesofdatawhichitrequirestoworkproperly.

1.4-Usingfunctions

11

Page 12: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Let'stryonemorefunction.Thisfunctionhastwoparameters,meaningitacceptstwopiecesofdatawhichitrequirestoworkproperly.

math.max(7,10)

Whengivingmorethanonepieceofdatatoafunction,youneedtoputacomma( ,)betweentheparameters

Thesearegreatfunctions,butwouldn'titbegreatifwecouldmakeourown?We'llgiveitashotinjustafewpages.

ExercisesSeeifyoucanfigureoutwhat math.maxdoes.Giveitdifferentnumbersandexaminetheresult.Thereisanotherfunctioncalled math.minthatalsotakestwonumbers.Whatdoesitreturn?

1.4-Usingfunctions

12

Page 13: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

CommentsincodeSometimeswemightwanttowriteacommentinourcode–anexplanationtoafriendorourfutureselvesonwhatthepurposeofsomecodeis.Perhapswewanttowriteanotetoourselvestochangesomethinglater.Commentsworkverysimilarlyindifferentlanguagessothey'reprettyeasytoreadevenifyoudon'tunderstandtheprogramminglanguageorthecodeitself.Luadenotesacommentas --andanytextthatfollowsit:

1+1

--Thisisacodecomment

1+2

--Thisisanotherlineofcomments

3+4

Thesecommentswillbecompletelyignoredbythecomputerandaremeantforthehumantoread.Commentscanalsobeonthesamelineascode.Thecomputerwilljustignoretherestofthelinewhenitseesacommentstarting.

1+1--Thisismycomment.Thiscodeaddssomenumberstogetherincaseyoudidn'tknow!

Youwillseecommentsappearinfutureexamplecode,sodon'tletitsurpriseyou!

1.5-Commentsincode

13

Page 14: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

ScriptingandprintingLookingbackatthewebsite,(youbookmarkedit,right?)wehavebeenusingtheREPLwindowpaneontheright,buthaven'ttalkedaboutthepaneontheleft.Thiswindowisjustatexteditor.Insteadofrunningtheprogramwitheachlineyoutype,itallowsyoutowritemultiplelinesofcodebeforeexecutingitall.Let'strytypingsomethinginit.Onceyouaredonetypingallthecodeyoucanclickthe"Run"button.

number=4

number=number+1

Butwhenyouclickrun,nothinghappens.Okwellnowthatyouarewritingaprogram,youneedtoreturndata.Solet'sprovideanotherstatementtoourprogram.

number=4

number=number+1

returnnumber

NowwhenyouclickRun,thetext =>5appearsintheconsole.Whenyoutoldittorun,itreadandevaluatedeachlineofthecode,thenwhenitgottothelinewith returnonit,theprogramstoppedandreturnedthevalueyouaskedittoreturn(inthiscase 5).Ifyouwriteanycodeafterthereturnstatement,itwillcauseanerrorwhenwetrytoruntheprogram,sothereturnstatementshouldbethelastthinginourfile.

number=4

number=number+1

returnnumber

--Thislinewillcauseanerror:

number=10

Youcanreturnanytypeofdata,notjustnumbers:

return"hello"

Rememberthoseotherfunctionsweusedbefore?Youcanwritethoseaspartofthereturnstatement.

returnstring.reverse("hello")

Sometimeswhenwritingprograms,wewanttopokearoundandseevalueswhiletheprogramisrunningandnotwaituntilitisdone.Thisissocommonthatthereisafunctionthatprovidesthisforus.

print("hello")

return"world"

The printfunctiontakesanydataandprintsthevalueinthewindowpaneontheright.

>

"hello"

=>"world"

So "hello"isbeingprintedand =>"world"isbeingreturned.Youcaninvokefunctionsonthesamelinewhenprintingifyoureallywanttogetcrazy:

1.6-Scriptingandprinting

14

Page 15: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

print(string.reverse("hello"))

return"world"

The printfunctionand returnstatementwillbothcomeinhandywhentestingthefunctionswe'regoingtowrite.

ExercisesWhenwepassdataintoafunction,itiscalledanargument.Wepassed1argumentinto printbutitcanpassintwo,orthree,ormore.Whatdoesitlooklikewhenyouprintmultiplearguments?Funtip,whenusingatexteditoralong-sidetheREPLyoucanrunthecodewithoutthemousebypressing'command+enter'onMacand'Alt+enter'onWindows.Doesthisspeedupyourlearning?

1.6-Scriptingandprinting

15

Page 16: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

MakingfunctionsFunctionsarethethirddatatypewe'veseen.We'veaccessedsomevariableswherefunctionsweredefinedforusandhadablastusingthem(IknowIdid).Functionsarethebuildingblocksofsoftware.YoucancomposethemthensnapthemtogetherlikeDanishplasticblocks.Ittakestimetounderstandhowtheyworkandmuchlongertomastertheirinnerpower.Sowithoutfurtherado,let'sseewhattheyactuallylooklike:

function()

return4+4

end

Typeitoutinthetexteditorwindowandletusbreakthisdownlinebylineandwordforword.Wheneverwetypefunction()wearebeginninganewfunction.The2ndlineisthebodyofourfunctionwherethingshappen.Thebodyofthefunctioncanbemanylineslong.Thebodyofthefunctioncouldalsobeempty(butthat'snotveryuseful).Onthelastlineofthefunctionbodywereturndata.Thisreturnstatementwon'tendourentireprogramthough!Itwillonlytellthecomputerwe'refinishingupwithourfunction.Thenonthethirdline,we'retellingthecomputerwe'redonewritingourfunction.Inordertousethisexamplefunction,weshouldprobablyuseavariabletogiveitaname:

add=function()

return4+4

end

Thefirstbitshouldbeunderstandable.Wedeclaredavariablecalled add,thenweassignedsomedatatoitontherightoftheequalsign.Inthiscase,ourfunction.Nowitisreadytouse.

add=function()

return4+4

end

result=add()

returnresult

=>8

We'vemadeourveryownfunctionwithourveryownnameforitandeveninvokeditandgotbackdata!Ifyouinsteadgotanerrormessage,doublecheckwhatyoutypedthatnothingismissing.Errormessagesgiveyoualinenumberofwheretofindtheerrorthatcrashedtheprogram.

Takealookforaminuteathowweinvokedourfunction:

add()

Wetypedoutthevariablenamethatourfunctionisassignedto,followedbysomeparenthesis.Inthoseparenthesisisthedatathatwepassedintoourfunction...waitaminutetheparenthesisareempty.Wedidn'tpassanydataintoourfunction.Wheneverwecalledthoseotherfunctionswepassedindata,likewhenwepassed "hello"intostring.reverse("hello").Whatifwemodifyourlinewhereweinvokeourfunctionandgiveitsomedata?

add=function()

return4+4

end

result=add(16)

1.7-Makingfunctions

16

Page 17: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

returnresult

Itseemsitalwaysreturns =>8nomatterwhatargumentswetrytopassin.Weneedtorewindtothefirstlineofourfunctionandtakeacloselookatthisbit:

add=function()

The ()attheendof function()iswherewetellourprogramhowmanyargumentsweareaccepting.Iftheparenthesisareempty,thenourfunctionisignoringallargumentsandwilllikelyalwaysreturnthesameresult.Let'stweakthefunctionslightlyandgiveitoneparameterwiththename a.Let'salsotweakthesecondlinewhilewe'reatit:

add=function(a)

returna+4

end

result=add(16)

returnresult

=>20

Nowwhenwepassindifferentnumbers,wegetdifferentresults:

add=function(a)

returna+4

end

print(add(16))

print(add(12))

Tocompletethisfunction,let'sgiveitasecondparameterof bandmodifythereturnstatementinthefunctionbody:

add=function(a,b)

returna+b

end

print(add(16))

print(add(12))

Ifwetryandrunthecodenow,we'llgetanothererror:

[string"add=function(a,b)..."]:2:attempttoperformarithmeticonlocal'b'(anilvalue)

Let'sreadthiserrorcarefully.Itissayinginsidethesquarebracketsthatanerroroccurredwhenusingthefunctionwedefined( add=function(a,b)...).Totherightofthesquarebracketsitissayingline2( :2)ofourtextistheparticularlocationofthecrash.Totherightofthelinenumberiswhathappenedthatmadeitcrash.Ittriedtoperformadditionwith a+bbutthevalueof bwasnil.Westatedthatourfunctionrequirestwoparametersnow, aandb,andourprogramwillcrashifwetryandinvokethefunctionwithonlyoneparameter.Let'smodifythelineswhereweinvokethefunctiontogiveittwoargumentseachtimeweinvokeit:

add=function(a,b)

returna+b

end

print(add(16,10))

1.7-Makingfunctions

17

Page 18: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

print(add(12,2))

Great,everythingisworkingagain!Withtheexperienceofourfirst,fully-functionalfunction,wecannowstarttreadingthewatersofthisgreatworld.

ExercisesTogetusedtowritingfunctions,trywritingsomecomplimentaryfunctionsnamed subtract, multiply, divide,or modulate(modulus).Makea concatenatefunctionthataccepts2stringsandreturns1combinedstring.Trymakingafunctionthattakes3ormoreparameters.

1.7-Makingfunctions

18

Page 19: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

BooleansDatatypesarelikeelementsontheperiodictable.Themoreelementsyouhavethemorechemicalscancreate.Luckilytherearen'tasmanydatatypesasthereareelements.Infactwe'velearnedalmostallofthem.Thereareonlytwopossiblebooleans:

true

and

false

That'sright.Andyoucanassignthemtovariablesjustlikenumbers,strings,nil,andfunctions:

myboolean=true

print(myboolean)

Thecoolthingwithnumbersandstringsisyoucanusethemtocreatestatementsthatcanbeevaluatedas trueorfalse.Letmegiveanexamplebyintroducingsomenewoperators.TrytheseoutintheREPL:

5>3

=>true

5<3

=>false

"5isgreaterthan3"isatruestatementsoitreturnsa trueboolean.Naturally,"5islessthan3"isafalsestatementandreturns false.Wecanchecktoseeiftwonumbersareequalinvalue:

number=5

number==5

=>true

Byusingadoubleequal( ==)wecancomparetheequalityoftwonumbers.Thisalsoworksforstrings:

"hello"=="hello"

=>true

"hello"=="HELLO"

=>false

1.8-Booleans

19

Page 20: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Forstrings,oftentimeyouwillseesinglequotes ''(apostrophe)usedinsteadofregularquotes(sometimescalleddoublequotes)wrapperaroundthetext.Luadoesn'tcareaslongasthetextinsidebothstringsareidentical.Wecanprovethatwithanequalitycheck:

'hello'=="hello"

=>true

Anyways,youcanalsodotheinverseofanequalitycheckandcheckforinequality(iftwothingsarenotequal):

5~=3

=>true

"HELLO"~=string.upper("hello")

=>false

Nowlet'sdiginalittledeeperwithtwomoreoperators.Firstisthe andoperator:

3<4and4<5

=>true

ThisreadsoutalmostasplainEnglish.3islessthan4and4islessthan5.Thisisalogicallysoundstatementsoitevaluatestotrue.Justtobeclearonwhat'sactuallygoingonherethough,let'sbreakitdown.Whatwesaidisbeinggroupedinto3separateoperations:

(3<4)and(4<5)

Thetwosetsofparenthesisareevaluatedfirstandinternallythecomputerbreaksthesetwooperationsdownto:

(true)and(true)

Trueandtruearebothtrue.Thissoundssilly,butitisindeedlogicallysound.Let'stryonemorejusttogetthehangofit:

"hello"=="hello"and6>10

Finally,let'stryonemoreoperatortoputabowonthings.Sometimeswedon'tcarethatbothoperationsarecorrect.Weonlycareifone ortheotheriscorrect.

4==10or4~=10

=>true

1.8-Booleans

20

Page 21: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

1>100or12==12or"hello"=="bananas"

=>true

Aslongasoneoftheoperationsiscorrect,theentirestatementislogicallytrue.Withtheintroductionof trueandfalsewe'vebroughtinalotofnewoperators:"greaterthan"( >),"lessthan"( <),"equal"( ==),"notequal"( ~=),"and"( and),and"or"( or).

TriviaBooleansgettheirnamefromGeorgeBoolewhoinventedbooleanalgebra,whichwe'vejustseenalittlebitof.

ExercisesTrywritingdifferentstatementswithallthenewoperators.Tryusingtwo andoperatorsinthesamestatementandseeifyoucanmakeitevaluateto true.Tryoutthesetwobonusoperatorswithsomenumbers:"greaterthanorequalto"( >=),and"lessthanorequalto"( <=).

1.8-Booleans

21

Page 22: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

FlowcontrolTypicallythecomputerstartsatthetopofourscriptandreadseachlinedowninasequence.WemaketheprogramsjumparoundwithfunctionsinthemixTrythisoutinthetexteditor:

print("I'mcalled1st")

add=function(a,b)

print("I'mcalled5th")

returna+b

end

subtract=function(a,b)

print("I'mcalled3rd")

returna-b

end

print("I'mcalled2nd")

subtract(16,10)

print("I'mcalled4th")

add(12,2)

Wehaveafunctionthatissavedtothevariable addbutitisn'tinvokeduntilfurtherdowninthecode.Soinasenseourprogramhasworkeditswaydownthepagethenjumpedbackuptothefunctionandworkeditswaythroughthebodyofthefunctionthenpickedbackupwhereitwasbefore.Inasimilarfashion,wecanmakeourprogramtakeonepathoranotherdependingifthedatais trueor false.

noise=function(animal)

if(animal=="dog")thenreturn"woof"end

return""

end

print(noise("dog"))

print(noise("rabbit"))

Let'sanalyzethisfunctionlinebyline.Thefunctioniscallednoiseandtakesananimalname(string)asaparameter.Onthenextlineitsaysif"animalisdog"istruethenreturnsomethingspecial.Weputan endattheendofourstatementtomakeitobvioustothecomputer.Ifthestatementwasfalse,then "woof"doesnotgetreturned.Insteadanemptystring( "")getsreturned.Whenweinvokethefunctionwiththeargument"dog"thenwegetback"woof!".With"rabbit"wegetbacksilence.Maybetherabbitdoesn'twantthedogtohearwheresheis.Let'smakeourfunctionmoreversatilebyaddingmoreanimals:

noise=function(animal)

ifanimal=="dog"oranimal=="wolf"thenreturn"woof"end

ifanimal=="cat"thenreturn"meow"end

return""

end

print(noise("dog"))

print(noise("cat"))

print(noise("rabbit"))

print(noise("wolf"))

1.9-Flowcontrol

22

Page 23: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Wehavebranchingpathshappeningwithinourfunction.Ifweweretomapoutthesebranchesitmaylooksomethinglike:

|

+-->"woof"

+-->"meow"

|

+-->""

There'snorequirementthatastatementhastobeallwrittenoutononeline.Sometimeswhendoingmultiplethingsinsideanifstatementwemaywanttoputitonmultiplelines:

ifmy_age>17then

print("You'reanadult!")

print("Getajob!")

end

Similartofunctionshavingbodies,everythingbetween thenand endisconsideredthebodyoftheifstatement.Sometimesitisnecessaryforourbranchestohaveforkswithinthem.Let'ssayourfunctiontakesalanguageasasecond,optionalparameter:

noise=function(animal,language)

ifanimal=="dog"oranimal=="wolf"thenreturn"woof"end

ifanimal=="cat"thenreturn"meow"end

ifanimal=="bird"then

iflanguage=="spanish"thenreturn"pío"end

return"tweet"

end

return""

end

print(noise("dog"))

print(noise("rabbit"))

print(noise("bird"))

print(noise("bird","spanish"))

Theifstatementforcheckingiftheanimalisabirdis4lineslong.Oncewefindoutthattheanimalisabird,whilestillinthebodyoftheifstatement,westoptocheckandseeifthelanguageissettoSpanish.Ifitis,weendupinsideanifstatementwithinanifstatement!Otherwisewe'llreturn "tweet"ifthelanguageisn'tSpanish.Maybemappingoutthepathswillclearthingsup:

|

+-->"woof"

+-->"meow"

+----->"pío"

||

|+-->"tweet"

|

+-->""

Ourcodecangetunreadableveryquicklyifwestartnestingifstatementsinsideeachother.Fortunatelydoingsoisn'tusuallynecessary.

Let'stalkaboutanotheraspectofifstatements.SupposeIhavetwobranchesofcodethatareoppositeofeachother:

ifdaytime==truethen

thermostat=71

end

ifdaytime==falsethen

1.9-Flowcontrol

23

Page 24: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

thermostat=68

end

Ratherthanwritingthisoutastwoifstatementsandcheckingthevalueofdaytimetwice,Icantakeadvantageofthekeyword else:

ifdaytime==truethen

thermostat=71

else

thermostat=68

end

Thatwayif daytimeisnot true,itwilldefaulttothesecondbranch.Youcouldreadthisoffalmostlikeasentence:"Ifdaytimeistruethensetthethermostatto71,otherwisesetthethermostatto68."Nothavingtocheckthingstwicewhendoingcomputationssavesustimeandmakesourprogramrunmoreefficiently.Since daytimeisabooleaninthiscase,wedon'tneedtocheckifitistrueorfalse.Wecanjustpassittotheifstatementtobecheckedfortrue/ falseandmakeouroperationevensimpler.

ifdaytimethen

thermostat=71

else

thermostat=68

end

Better."Ifdaytimethensetthermostatto71,otherwisesetthermostatto68."There'sonemorefeatureofifstatementsweshoulddiscuss.Ifthereisanotherconditionyouneedtocheck,maybeseveralmore,youcanusetheelseifkeyword.Itlookssomethinglikethis:

color="green"

ifcolor=="blue"then

print("That'smyfavoritecolor!")

elseifcolor=="green"then

print("Verysubtlechoice.Ilikeit.")

elseifcolor=="pink"then

print("Nice,boldchoice.")

else

print("Idon'tthinkthatcolorwouldmatchyourshoes.")

end

Tryitout!

Thebeginningoftheifstatement... ifcolor=="blue"then...isfalse.Thiscodegetsskippedover.Thenthenextpartoftheifstatement... elseifcolor=="green"then...istruesothatsectionofcodeunderneathit... print("Verysubtlechoice.Ilikeit.")isran.Therestoftheifstatementisskippedwithoutcheckingifitstrueornot.So elseifcolor=="pink"then/ elseareneverprocessed.

ExercisesWriteoutafunctionthattakes1parameternamed"sides".Makethefunctionreturnthenameoftheshapedependingonthenumberofsides(forinstance,"triangle").Trytomaketheifstatementincludean elseattheendtoaccountforeverythingelsethattheifdoesn't.

1.9-Flowcontrol

24

Page 25: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

WhileAnotherwaytocheckconditionsiswiththe whilekeyword.

while1+1==2do

print("Mymathiscorrect!")

end

Whileaconditionistrue,thebody(everythingbetweenthe doand end)willberunrepeatedlyandnotstop.Soifyoutriedtorunthatbitofcode,yourscreenprobablywentcrazyprintingoverandoverinanever-endingloop.Weneedtomakesuretheconditioncangetchangedsowe'renotstuckinanever-endingloop.Let'swritealoopwecanescapeoutof.

boolean=true

--Thisconditionwillgetcheckedtwice.Thefirsttimeit

--ischeckeditwillbetrueandthebodyofthewhile-loop

--willberun.Thesecondtimetheconditionischecked,

--ourbooleanwillbefalseandthewhile-loopwon'tberunagain!

whilebooleando

print("Switchingbooleantofalse.")

boolean=false

print("Booleanhasbeensettofalse.")

end

print("Wemadeitoutoftheloop!"

Understandingthatwecanchangethewhileconditionfrominsidethebodyoftheloop,wehavethepowertowriteprogramsthatendexactlywhenwewantthemto.Canyouguesswhatthiswilldowhenwerunit?

countdown=10

whilecountdown>1do

print(countdown.."...")

--Thislineiscriticaltomakeournumbershrink.

countdown=countdown-1

end

print("Blastoff!")

...Andremembertousea >andnota <,oryourloopmayneverrun.

ExerciseComeupwithyourownideaforawhileloop.

1.10-While

25

Page 26: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

TypecheckingLuadoesn'tcarewhattypeofdataavariablehas.

data=12

data="hello"

data=true

Tothisend,wecanusethe typefunctiontocheckwhatkindofdataavariableisholding.

type(data)

=>boolean

Wecancheckthetypeoffunction:

type(string.reverse)

type(type)

Wecanalsouseittocheckwhattypeofdataafunctionisreturningbacktous:

type(string.reverse("hello"))

=>string

type(type(12))

=>string

ConvertingdatatypesWe'vealreadyseendatatypeconversionpreviouslywhenwetooknumbersinandoperationandtransformedthatintoatrueorfalsestatement.

type(12>3)

=>boolean

Therearealsowaystoconvertbetweennumbersandstringsusing tonumberand tostring.

number=tonumber("24")

print(type(number))

string=tostring(number)

print(type(string))

number

1.11-Typechecking

26

Page 27: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

string

Interestingbutmaybelessuseful,youcanconvertotherdatatypestostring:

print(tostring("alreadyastring"))

print(tostring(true))

print(tostring(nil))

print(tostring(tostring))

ExercisesWhichofthesestringscanbeconvertedtoanumbersuccessfully? "001", "7.12000", "5", "1,943"

1.11-Typechecking

27

Page 28: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

FirstgameLet'slearnaboutafewnewfunctionsandthenwe'llbeabletowriteourfirstgame!

ReadinginputNotonlycanourprogramprintoutdata,butusingthefunction io.readitcantakedatatoo.Thisfunctiondoesn'tneedanyargumentsbecauseitwillpromptusinthewindowontherightforustotypeindata.

print("Enteryourname:")

name=io.read()

print("Yournameis"..name..".")

Afteryouclick"Run",theprogramwillpausewhenitruns io.read().TypeyournameandhitENTERandlook,theprogramprintsbackoutthenameyougaveit.Noticethelastprintstatement.Wecombinedthenamewithtwootherstringstoformasentence.Youcanprompttheusermultipletimesifyouneedtogetadditionalinformation:

print("Enteryourname:")

name=io.read()

print("What'syourfavoritefood?")

food=io.read()

print("Yournameis"..name.."andyourfavoritefoodis"..food..".")

Onelimitationwithdoingthisisthedatawillalwayscomeinasastring:

print("What'syourfavoritenumber?")

data=io.read()

print(type(data))

string

Inthelastsectionwetalkedaboutconvertingdatabetweendifferenttypes.Ifwewantedtofindoutwhetheryourfavoritenumberisoddoreven,wewouldneedtoconvertittoanactualnumbertoperformoperationsonit.Typethisinyourtexteditorandrunit:

print("What'syourfavoritenumber?")

data=io.read()

number=tonumber(data)

--Iftheusergaveusananswerthatisn'ta

--number,thenthevalueof"number"isnil.

ifnumber==nilthenreturn"Invalidnumber."end

ifnumber%2==0thenreturn"Yournumberiseven."end

return"Yournumberisodd."

Randomnumber

1.12-Firstgame

28

Page 29: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Manylanguagesgiveusaccesstoarandomnumbergenerator.Randomnessishowwegeneratesecurepasswordsandkeysintherealworld.TogeneratearandomnumberinLua,weuse math.random:

math.random(100)

=>63

Thisgeneratesarandomnumberbetween1and100.Except,ifyouruntheprogramrepeatedlyyoumaynoticethatitspitsoutthesamenumber.That'sbecausenothinginthecomputerworldisrandom.Ifwefedinrandomnoisesthroughaspeakerorwhitenoisefromanoldtelevisionsetthenourcomputercouldusethistogeneraterandomnumbers.Sincewedon'teasilyhaveaccesstothosethings,weneedtoseedLuawithsomeperceivedrandomness.

Ifwerun os.timewewillgetthecomputer'scurrenttimeinintegerform:

os.time()

=>1.529098167e+09

Thisnumberishardenoughtoguessthatitwillworkasaseedforourprogram.Let'stakethesystemtimeandfeeditinusing math.randomseedthenfromthere,Luawillbeabletogeneratea"random"numberintherangewewant(1-100).

seed_number=os.time()

math.randomseed(seed_number)

returnmath.random(100)

=>19

Success!Itisgenerateddifferentnumberseachtimewerunit,withnopattern.

PuttingitalltogetherIshouldprobablyexplainwhatthisgameis.It'squitesimple.Wewantthecomputertomakeupanumberandtheuserhastoguesswhatthenumberis.Ifthey'rewrong,thenweshouldgivethemahintandmakethemguessagain.Wecantakeadvantageofthewhilelooptomakethemcontinueguessingwhiletheirguessisincorrect.

--Thecomputer'ssecretnumber

math.randomseed(os.time())

number=math.random(100)

print("Guessmysecretnumber.Itisbetween1and100.")

guess=tonumber(io.read())

--Whiletheuser'sguessisnotequalto

--thenumber,repeatthebodyoftheloop.

whileguess~=numberdo

--Givethemsomehints

ifguess>numberthen

print("Yourguessistoohigh.")

end

ifguess<numberthen

print("Yourguessistoolow.")

end

1.12-Firstgame

29

Page 30: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

--Makethemguessagainandagainuntiltheygetit

print("Guessagain:")

guess=tonumber(io.read())

end

--Winningmessage

print("Youguessedcorrectly!Thenumberwas"..number..".")

Let'sre-factoronebitofthiscodetomakeiteasiertoread.Whenwetalkedaboutifstatements,rememberthekeyword else?

--Thecomputer'ssecretnumber

math.randomseed(os.time())

number=math.random(100)

print("Guessmysecretnumber.Itisbetween1and100.")

guess=tonumber(io.read())

--Whiletheuser'sguessisnotequalto

--thenumber,repeatthebodyoftheloop.

whileguess~=numberdo

--Givethemsomehints

ifguess>numberthen

print("Yourguessistoohigh.")

else

print("Yourguessistoolow.")

end

--Makethemguessagainandagainuntiltheygetit

print("Guessagain:")

guess=tonumber(io.read())

end

--Winningmessage

print("Youguessedcorrectly!Thenumberwas"..number..".")

Nowthatthingsarecleaner,let'saddonefeaturetoourprogram.Itwouldbemorefunifthegamekepttrackofhowmanyguesseswemadesowecouldgivethemaspecialmessage.Let'screateavariablecalled guess_countthatwillstartat 1andincrementeverytimetheusermakesanotherguess.We'llalsogoaheadandaddsomemessagestothebottomtopraisetheuseriftheydiditinareasonablenumberofguesses.

--Thecomputer'ssecretnumber

math.randomseed(os.time())

number=math.random(100)

--Ourstartingnumberofguesses

guess_counter=1

print("Guessmysecretnumber.Itisbetween1and100.")

guess=tonumber(io.read())

--Whiletheuser'sguessisnotequalto

--thenumber,repeatthebodyoftheloop.

whileguess~=numberdo

--Incrementtheguesscounterby1

guess_counter=guess_counter+1

--Givethemsomehints

ifguess>numberthen

print("Yourguessistoohigh.")

else

print("Yourguessistoolow.")

1.12-Firstgame

30

Page 31: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

end

--Makethemguessagainandagainuntiltheygetit

print("Guessagain:")

guess=tonumber(io.read())

end

--Winningmessages

print("Youguessedcorrectly!Thenumberwas"..number..".")

ifguess_counter<=5then

print("Amazing!Itonlytookyou"..guess_counter.."tries.")

else

print("Ittookyou"..guess_counter.."tries.Notbad.")

end

ExercisesTryaddingmoremessagestothe guess_counterfordifferentscores.Tryaddingamessagetotheifstatementwiththehintsforwhentheuserguessesaninvalidnumber.Howwouldyoudothat?Maketheconditionexitif guess_countergoesabove10andtelltheusertheylostthegamebutshouldtryagain.

1.12-Firstgame

31

Page 32: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Tables(part1)Tablesarethelastdatatypewe'lldiscussinthischapter.Otherlanguageshavedifferentnamesforthisdatatypelike"object","hash","map"and"dictionary",andthefeaturesmayvaryfromoneprogramminglanguagetoanother.Tablesareusedtobuildcompositedatatypeslikelists,trees,orabiggreenorcrunningacrossthescreen.Compositedatatypesarehigherorderdatastructurescreatedfrommoreprimitivedatatypeslikenumbersandstrings.Thenumberofdatastructuresyoucancreateareendless.Weneedtolearnaboutafewtonotonlyunderstandhowtableswork,buttobeabletobuildanymodernsoftware.

Thebasicsyntaxfortablesistomakeacurlybrace {(samekeyasthesquarebraceonmostkeyboards)tostartthetable,writesomedatainthetable,thenputaclosingcurlybrace }toendthetable.Soanemptytablewouldlooklikethis:

my_cool_table={}

ListsListsareusuallystartedbywritingthefirstitem,thenthesecond,andsoon.Ifwewantedtomakeagrocerylistinsoftware,itmaylooklikethis:

groceries={

[1]="beans",

[2]="bananas",

[3]="buns"

}

Okmaybeyourtypicalgrocerylistlooksdifferent.Whatdowedowiththisdatanowthatwegotit?Wecanaccessandmodifythedataasiftheywerestoredintheirownvariables.

returngroceries[1]

=>beans

Firstwespecifythevariablenameofthetable,theninsquarebracketsweputthenumberwewant.Youcanaccesstheminanyorderandmodifythemasneeded:

print(groceries[3])

groceries[1]="coffeebeans"

print(groceries[1])

buns

coffeebeans

Theorderyoudefinethemindoesn'tmatter:

groceries={

[3]="beans",

[1]="bananas",

[2]="buns"

}

1.13-Tables(part1)

32

Page 33: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Thenumberinsquarebracketsisthekey.Akeythatispartofanumericsequenceofkeyssuchasthislistisoftencalledanindex.So "bananas"hasanindexof 1.Thepluralofindexisindices.

Don'tforgetthecommasbetweeneachiteminyourlistoryouwillgetquitetheerrormessage:

[string"groceries={..."]:3:'}'expected(toclose'{'atline1)near'['

Whenyouaremissingacommabetweenitems,itthinksithasreachedtheendofthetablebutthenerrorsoutwhenitgoestoclosethetablebutseesanotheriteminsteadoftheclosecurlybracket }.

Anotherissueyoumayrunintoisifyoutrytoaccessakeythathasnodata.Thereisno4thiteminourtablesoifwetrytoaccessit:

print(groceries[4])

Wegetback nil,thesamewaywewouldifwetriedtoaccessavariablenamethathasnodataassignedtoit.

Writingoutlargelistscanbecomeaheadachewhenwehavetomanuallynumbereachiteminalist:

groceries={

[1]="beans",

[2]="bananas",

[3]="buns",

[4]="blueberries",

[5]="butter",

[6]="broccoli",

[7]="basil"

}

Whatifweremoveanitemorwewanttomovesomethingtoadifferentpositioninthelist?Whatapaintohavetore-indexeverything.Thankfullythereisshorthandwayofwritinglists:

groceries={

"beans",

"bananas",

"buns",

"blueberries",

"butter",

"broccoli",

"basil"

}

Thisisidenticaltothecodewrittenabove,exceptnowtheindicesareauto-generatedforme. "basil"hasanindexof 7sinceitisthe7thiteminthelist,butifIcutandpastedittothetopofmylist,it'sindexwouldbe 1andeverythingbelowitwouldberenumberedaccordingly.

LoopingoverlistsIfwewantedtoprintourgrocerylist,wecouldsaysomethinglike:

print(groceries[1])

print(groceries[2])

print(groceries[3])

print(groceries[4])

--andsoon...

1.13-Tables(part1)

33

Page 34: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Butthatisquiterepetitiousandrequiresupdatingifthesizeofourlistchanges.Luckilywealreadyknowaboutwhileloops.

index=1

whilegroceries[index]~=nildo

print(index,groceries[index])

--Gotothenextindexinthelist

index=index+1

end

Seehowinsteadofaccessingeachitemas groceries[1], groceries[2]...wecanjustuseavariableinthesquarebracketsinsteadofanumber.Theninsidetheloopwebumpthenumberupandaccessthenextiteminthelist.Theloopstopswhentheindexgoesbeyondthelastiteminthelistandthereisnothingthere.Sowhenindex8isread,groceries[8]isnilandthewhileconditionisnolongertrue.Whileconditionsdon'tevenneedabooleanexpression.Itcanknowwhetherornottocontinuesimplyifthegivenitemhasdataorisnil.Itcanbesimplifiedtoread:

index=1

whilegroceries[index]do

print(index,groceries[index])

--Gotothenextindexinthelist

index=index+1

end

Again,itknowstoexitwhenitsees falseor nil.Thecaveattothiswouldbeifyoumakeaspeciallistwith falseinit:

groceries={

"beans",

"bananas",

false,

"blueberries",

"butter",

"broccoli",

"basil"

}

Whenthewhileloopgetstothethirditeminthelistandsees false,itwouldstoploopingbeforeitreadstherest.It'snottypicallyagoodideatomixandmatchdifferentdatatypesinalistbecauseofissueslikethis,however,wecouldworkaroundthisifweneededto.Thereisaspecialoperatorfortablestogetthesizeofthelist.

print(#groceries)

7

Aneasywaytorememberthe #operatoristorememberthatitreturnsthe#ofitemsinalist.Usingthisoperatorwecouldwriteourwhileloopinadifferentway.

index=1

whileindex<=#groceriesdo

print(index,groceries[index])

--Gotothenextindexinthelist

index=index+1

end

1.13-Tables(part1)

34

Page 35: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Youcouldeventweakthisslightlytoreadthelistbackwardsifyouwantedto:

index=#groceries

whileindex>0do

print(index,groceries[index])

--Gotothenextindexinthelist

index=index-1

end

Noticewearesubtractingfromtheindexwitheachloopinordertoaccomplishthis.

ExercisesTrytomodifythewhilelooptoonlyprinteveryotheriteminthegrocerylist.(Hint:insteadofincrementingby1oneachread,youwanttoincrementmore.)Writeawhileloopthatcountsto10andpopulatesanemptytablewiththesameitem10times.(Hint:youassigntoindicesjustlikevariables, list[index]="hi".)

1.13-Tables(part1)

35

Page 36: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Tables(part2)Inthelastsectionwesawhowsimpleitwastomakealist.Workingwiththelistwasalittletrickyatfirstbuthopefullynottoobad.Ifwerewindback,wecanrememberthatwecreatedatablebyassigningsomekeysvalues.

boxes={

[1]="JohnDoe",

[2]="AmandaParker",

[3]="TylerReese"

}

Thinkofitlikepostofficeboxesandwelabeleachboxwithauniquenumber.Wheneverwereferenceapostalbox,wedosobyreferencingthenumberwithinthearray(list)ofboxes: boxes[2].Thelabel,orkey,isultimatelyarbitrarythough.Formakingalist,welabelthingsinanincrementalordertomakethemeasiertoloopoverandtogiveusasenseoflinearsequence.Keysdon'tneedtobenumbers.Theycouldjustaswellbestrings:

coins={

["half"]="50cents",

["quarter"]="25cents",

["dime"]="10cents",

["nickel"]="5cents",

["penny"]="1cent"

}

Whichwouldbeaccessedjustthesameway:

print(coins["nickel"])

5cents

Thiscanbereallyusefulfordoingalookupifweinsteaduseavariableforthekey.Trythisoneout:

coins={

["half"]="50cents",

["quarter"]="25cents",

["dime"]="10cents",

["nickel"]="5cents",

["penny"]="1cent"

}

print("Whichcoindoyouhave?")

response=io.read()

print("Yourcoinisworth"..coins[response]..".")

Thisisn'tfarofffromhowcertaindatabasesanddigitalserviceswork.Itemsarestoredinauniquekeythatcanbereferencedforgettingadefinitionoutoflater.That'swhythisdatastructureissometimescalledadictionary.Remember,wecanadditemstoatableafteritisdefined:

coins["silverdollar"]="1dollar"

AnothershortcutLuagivesusiswedon'tneedtousethesquarebracesorquoteswhenaddingkeysthatarestrings.

1.14-Tables(part2)

36

Page 37: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

coins.nickel="5cents"

Thelimitationwithdoingthisisthekeysdefinedthiswaycan'thavespacesorspecialcharacters.Theymustbevalidinthesamewayvariablenamesarevalid.

coins.silverdollar="1dollar"--INVALID

coins.silver_dollar="1dollar"--Valid

coins.100="1dollar"--INVALID

Youcanusevariablenamesforkeyswhencreatingthetabletoo:

color="purple"

description="thebestcolor"

colors={

[color]=description

}

print(colors.purple)

print(colors[color])--printsthesamething

Byconvention,stringsaretypicallyusedfordictionary-liketableswhilelistsarenumbers.Don'tmakethemistakeofthinkingthesearethesame:

list={

1="someitem",

["1"]="auniqueitem"

}

Youcoulduseotherdatatypesaskeys,butyoumightfindyourresultstobeveryunexpected:

crazy_list={

[true]="works",

[false]="works",

["true"]="notthesame",

["false"]="notthesame"

}

print(crazy_list[true])

print(crazy_list[false])

print(crazy_list["true"])

print(crazy_list["false"])

crazy_key={}

crazy_list={

[crazy_key]="works"

}

print(crazy_list[crazy_key])

crazy_list={

[nil]="doesn'twork!"

}

print(crazy_list[nil])

Throwsanerror:

[string"crazy_list={..."]:2:tableindexisnil

1.14-Tables(part2)

37

Page 38: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Valuesinatablecanbeanytypeofdata,includingfunctions:

cat={

color="gray",

smelly=true,

make_sound=function()

print("meyuow!")

end

}

cat.make_sound()

ExercisesRemembertheearlyfunctionwemadethatreturnedtheanimalsounds?Makeafunctionwithatableinit,whereeachkeyinthetableisananimalname.Giveeachkeyavalueequaltothesoundtheanimalmakesandreturntheanimalsound.Tryinvokingthefunctionandseeifyougetbackthecorrectsound.

1.14-Tables(part2)

38

Page 39: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Forloops(part1)Wesawpreviouslythatwecouldusewhileloopsformanythings,butwealsosawhoweasyitwastomakeawhileloopthatdidn'trunproperly.Theprogrammerhastomakeavariabletopasstothecondition,makesuretheconditioniswrittenoutcorrectly,andthenmakesuretheconditioncanbechangedsotheloopcaneventuallyend.Thismanystepseachtimewewanttowriteasimpleloopleavesuspronetoerrorsandwastingourtime.Withforloops,wecantellaloopexactlyhowmanytimeswewantittorunandskipallthesesteps.

Numericforloops

fornumber=1,10do

print(number)

end

Onthefirstlinewearesaying"For[startingnumber]through[endingnumber]dothefollowing".The number=isavariableyouareassigningthestartingnumberto.Thevariablenamecanbewhateveryouwant.Thesecondlineisthebodyoftheloopandthethirdlineendstheloop.Ifyourunthisprogram,itwillprintthenumbers 1, 2, 3...through 10as numberisbeingincrementedby1witheachloop.Thisvariableisabitpeculiarthough,notonlybecausewedefineditinthemiddleofastatementbutbecauseitdisappearsafterwearedonewiththeloop.

fornumber=1,10do

print(number)

end

returnnumber

=>nil

Thisiscalledalocalvariable,becauseitonlyexistslocallywithintheforloop.

Forloopsactuallyhave3parameters:

startnumber-weassignthevariabletoitandthevariablewillincrementwitheachloopstopnumber-thelastnumbertoincrementtobeforestoppingtheloopstep-howmuchtoincrementbywitheachloop.Ifwedon'tspecifyastepsizeitwilldefaultto1.

Let'ssaywewantedtoprintoutonlyevennumbers.Wecouldchangethestartingnumberto2andsetthesizeofthestep(3rdparameter)to2:

fornumber=2,10,2do

print(number)

end

Ifwewantedtoiterate,orloopoverandreadeachiteminalist,itwouldlooksimilartoawhileloop.Let'slookatthewhileloopexampleagainjustforcomparison:

items={'a','b','c','d'}

index=1

whileindex<=#itemsdo

print(items[index])

index=index+1

1.15-Forloops(part1)

39

Page 40: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

end

items={'a','b','c','d'}

forindex=1,#itemsdo

print(items[index])

end

Wecouldalsocountdownbychangingtheparametersaroundandsettingthesteptoanegative1.

items={'a','b','c','d'}

forindex=#items,1,-1do

print(items[index])

end

Inthiscasetheindexstartsatthepositionofthelastitemandstopswhenitgetstothestopnumber,1.

ExercisesModifythepreviousloopsothatitonlyprintseveryotheriteminthelist.

1.15-Forloops(part1)

40

Page 41: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Forloops(part2)Wecancreateadifferentstyleofforloopusingfunctions,butinordertodothat,weneedtounderstandanotheraspectoffunctionswehaven'tyetcovered.Functionscanreturnmultiplevalues.

sort_numbers=function(a,b)

ifa>bthen

returna,b

end

returnb,a

end

bigger,smaller=sort_numbers(12,18)

print(bigger)

print(smaller)

Thisfunctiontakestwonumbers,checkstoseewhichisbigger,thenreturnsboththebiggernumberfirstthenthesmallernumbersecond.Noticewedidthisbyputtingacommainthereturnstatementthenprovidingasecondvalueafterthecomma.Likewise,wewereabletocapturebothvaluesintovariablesbyputtingthefirstvariablename,acomma,thenthesecondvariable( bigger,smaller=).Wedon'tneedtocaptureeverythingreturnedfromafunction.Wecouldhavejustaseasilycalledthefunctionandonlycapturedthebiggernumberifthat'sallwewantedfromit.

bigger=sort_numbers(12,18)

GenericforloopsLet'stakealookatthesiblingtothenumericforloopcalledthegenericforloop.It'scalledgenericforloopbecauseittakesafunctionthatmakesitbehaveindifferentwaysfordifferentsituations.Itdoesn'tdoanythingonitsown.Itreliesonthefunctiontotellithowtobehave.

ipairsHere'swhatgenericforloopslooklike:

list={'dog','cat','mouse'}

forindexinipairs(list)do

print(index,list[index])

end

ipairstakesourforloopandmakesititerateovereachiteminthelistandgivesusan indexvariabletoworkwithinsidetheloop.Butwait,there'smore! ipairsprovidesuswithanothervariablethatholdsthevalueoftheitematthatindex.Tryitoutyourself:

list={'dog','cat','mouse'}

forindex,valueinipairs(list)do

print(index,value)

end

Ahyes,soconvenient!Thereisonegotchawithdoingthis.Ifyouwantedtoeditthetablefrominsidetheloop,youneedtoaccessthetabledirectly:

1.16-Forloops(part2)

41

Page 42: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

list={'dog','cat','mouse'}

forindex,valueinipairs(list)do

list[index]=string.upper(value)

end

print(list[1])

Ifyoutrytojusteditthevalue:

list={'dog','cat','mouse'}

forindex,valueinipairs(list)do

value=string.upper(value)

end

print(list[1])

thelistwon'tbemodified,because valueisjustacopyofthedatathat'sactuallyinthelist.You'reeditingatemporarycopy.

pairsAnotherfunctionforprogrammingforloopswithspecialfunctionalityis pairs.Thiswilliterateovereverykeyinatable:

table={

cat='meow',

dog='bark'

}

forkey,valueinpairs(table)do

print(key,value)

end

Evenindices:

table={

'a',

'b',

'c',

cat='meow',

dog='bark'

}

forkey,valueinpairs(table)do

print(key,value)

end

Nosneakingpast pairsforanyofthesekeyseither:

table={

[1]='a',

[2]='b',

[3]='c',

cat='meow',

dog='bark',

[true]=false,

[{}]='what?'

}

forkey,valueinpairs(table)do

1.16-Forloops(part2)

42

Page 43: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

print(key,value)

end

Aneasywaytorememberthedifferencebetween ipairsand pairsisthe"i"in ipairsstandsforindex.Surethere'sadifferencewhenworkingwithweirdtablesliketheoneabove,butwhycan'twejustuse pairsforregularlist-styletables?

table={

[2]='b',

[3]='c',

[1]='a'

}

forkey,valueinpairs(table)do

print(key,value)

end

3c

2b

1a

Asyoucansee,theorderoftheitemsisn'tguaranteedwith pairs. ipairsisalsooptimizedtohandlenumerickeysandwillgenerallyperformfaster,soit'sgoodtoknowthedifference.

Underthegeneric-for-loophood

ipairsand pairsarejustregularfunctionsthatweinvoke.Theyreturnafunction(yes,afunctionthatreturnsafunction!)andthisreturnedfunctionprogramsourlooptobehavehowwewant.

forkey,valueiniterator,list,start_numberdo

print(index)

end

Sothisiswhatagenericforloopreallylookslikewithoutthehelpof ipairsor pairs.Itrequires3parametersthatipairs/ pairsprovidesdatabacktothekeyandvaluevariablesthatwecanuseinsidetheloop. iterator, list,start_numberareallvariableswewouldotherwisehavetodefinewithouttheirhelp.

iteratorwouldbeafunctionweprovidetothelooplistwouldbewhatwewanttoiterateoverstart_numberwouldbethestartingindexinthelist

list={'a','b','c'}

iterator,list,start_number=ipairs(list)

forindex,valueiniterator,list,start_numberdo

print(index)

end

ipairsgivesusaniteratortopasstotheforloop,aswellasourlistwealreadyhad,andastartingnumber.Wecanprinttheresultsofipairsandseethe3thingsitgivesus:

print(ipairs(list))

function:0x156a3f0table:0x1572aa00

1.16-Forloops(part2)

43

Page 44: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Sotosayitagain,genericforloopsrequire3things:aniteratorfunction,ourlist,andanumber.Inordertonothavetowritethemourselves,wegeneratedthose3thingsbyinvoking ipairsthenpassingthemintotheforloopparameters.Don'tfrettoomuchifthisseemsconfusingrightnowbecausewe'renotgoingtoneedtowritecustomforloopsorcustomiterators.

Numericversusgeneric:whichtouse?

Numericforloopsaregoodforsimplecountingbutperformjustaswellormaybeevenbetterthangenericforloops.Genericforloopsaremoreadaptable.Ifyouhaveasituationwhereeitherwouldwork,justusewhicheveryouwant.Itreallywon'tmakeanydifference.

ExercisesMakealistandthenwritebothanumericforloopandgenericforloopthatiterateoverthelistandprinteachitem.Comparethetwoapproaches.Makeatablewithanimalsforkeysandthesoundstheymakeforthekeyvalues.Makeaforloopthatusespairstoiterateovereachandchangethenoisestoallcapitalletters.

1.16-Forloops(part2)

44

Page 45: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

ScopesWhendefiningfunctions,wedefineparametersforthosefunctionswhichworklikeregularvariables.Ifwetrytoaccessaparameteroutsideafunctionwewillseethatitis nil.

addition=function(a,b)

print(a,b)

returna+b

end

addition(1,2)

print(a,b)

Theparameters aand barelocalvariables.We'veseenlocalvariableswithforloops,wherethevariablecounting_numbercouldn'tbeaccessedoutsidetheforloop:

forcounting_number=1,4do

print(counting_number)

end

print(counting_number)

Functions,forloops,andwhileloopscreateascopeeachtimetheyareran.Thingscreatedinthescope,includinglocalvariables,aredestroyedwhenthatlooporfunctioninvokeisdone.Thisishowtheprogramtidiesupafteritselfandkeepsthecomputerfromrunningoutofmemory.Theprocessofremovingunuseddatafrommemoryandreleasingcontrolofthatmemoryiscalledgarbagecollection.Luadoesthisforussowedon'thavetothinkaboutit.Variableswecreatenormallydon'tfollowthesamerules.Theywillcontinuetoexistafterthescopetheywerecreatedinhasbeendestroyed.

addition=function(a,b)

text="I'mnotgoingaway."

returna+b

end

addition(1,2)

print(text)

Eventuallyallthesevariableswemakewillfillupmemoryunnecessarily.Thiscanalsobeproblematicifweaccidentallymaketwovariablesbutusethesamename.

x=2

addition=function(a,b)

--Thismodifiesthexatthetop!

x=9

returna+b

end

print(x)

result=addition(x,y)

print(x)

1.17-Scopes

45

Page 46: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Whenyouwritealargeprogram,you'llinevitablymaketwovariableswiththesamename,sothiscouldbeahugeissue.Thesolutionistomakeourvariableslocalvariablesbyputtingthekeyword localbeforeallourvariableswhenwecreatethem.

addition=function(a,b)

localtext="I'monlyaccessibleinsidethefunction."

returna+b

end

addition(1,2)

print(text)

Now textisonlyinthescopeofthefunctionandnotgettingintootherpeople'sbusiness.Ifyoudon'twrite localbeforeavariable,thenwhatyouarecreatingisaglobalvariable.It'sashamethatvariablesareglobalunlessweexplicitlytellthemnottobe.Thereisneverareasontocreateglobalvariablesifyouhaveenoughknowledgetoknownotto.Soasabestpractice,allcodeexamplesgoingforward,onlylocalvariableswillbecreated.Let'sseeafewmoreexamples:

localnumber=12

--Thisfunctionhasnoparameters

localprint_numbers=function()

--Thisworks.Youcanseevariablesoutsidethefunction

print('number:',number)

--Thisdoesn'twork.Thevariabledidn'texist

--atthetime"print_numbers"wascreated.

print('number2:',number2)

end

localnumber2=18

print_numbers()

--Wealready"declared"number.Wedon'twrite"local"again.

number=13

print_numbers()

Noticewhenitprintedthatitknew numberwasupdatedto13butcouldn'ttrack number2.Aslongasavariablewascreatedbeforethescope(function'sscopeinthiscase)wascreatedthenitwillalwaystrackthelatestvalue.

Asareminder,wealreadysawwiththeforloopandwhileloopthatyoucanmodifyvariablesintheouter,orparent,scope:

localnumber=1

whilenumber<10do

number=number+1

end

Thisalsoworkswithfunctions:

localnumber=1

localmutate_number=function()

number=7

end

print(number)

mutate_number()

1.17-Scopes

46

Page 47: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

print(number)

Whatifyoumaketwovariableswiththesamenameintwodifferentscopes?Tryrunningthisone:

localnumber=18

localshadowing=function()

localnumber=6

print(number)

end

print(number)

shadowing()

Theinner numberdoesnotaffecttheouter numberinanyway.Theouter numberisnotaccessibleinsidethefunctionaslongastheinner numberexists.Ifavariablehasthesamenameasanothervariableinaparentscopethentheparentscopevariablebecomesinaccessible:thisiscalledshadowing.Typicallyyouwouldwanttoavoidshadowingifatleastforthereasonthatusingthesamevariablenametwiceinthesamefilecanmakethecodehardertoreadandmorepronetoerrorsbeingintroduced.

Onemoreinterestingthingswithscopes.Normallyafunctioncannotseeitself:

localself_reference=function()

print(self_reference)--Thiswillbenil!

end

self_reference()

Itdoesn'tseeitselfbecausethevariableisstillbeingcreatedwhenthefunctionisbeingcreated.Butrememberthatifavariableexistsbeforethefunctiondoes,itcanseethelatestup-to-datecontentofthatvariable.Sohere'sthetricktomakethatwork:

localself_reference=nil

self_reference=function()

print(self_reference)

end

self_reference()

Thevariableisdeclared,eventhoughweassigned niltoit.Assigningniltogetavariabledeclaredisprettycommon,soLuaincludesashorthandwayofdeclaringemptyvariables:

localself_reference

self_reference=function()

print(self_reference)

end

self_reference()

Thismayseemsillythatafunctionwouldneedtoaccessitself,buttherearesomeverypowerfulapplicationsforthisthatwewillseelateron.

ExercisesDeclareaglobalvariableinsideafunction, x=5(nolocalkeyword)thentrytoprintthevariablefromoutsidethefunction.Canitbeprinted?How?

1.17-Scopes

47

Page 48: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

1.17-Scopes

48

Page 49: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Chapterreview

TerminologyOperatorandOperation-Operatorsaresymbolsthatcauseanoperation,orinteractiontohappenbetweentwopiecesofdata.Anexampleoperationwouldbe 5+5. (5+5)*2wouldbetwooperations.

ModuloandModulus-Moduloisaspecialtypeofarithmeticoperationbetweentwonumbersusingamodulusoperator.Themodulusisrepresentedbya %(percentsymbol).Example: 24%2==0

VariableandValue-Variablesarenamesthatreferenceacertainpieceofdata.Thevalueiswhatisstoredinsidethevariable: variable="value"

Statement-Thisiswhenyoudosomething,likeanoperation(orgroupofoperations),declareavariable,orinvokeafunction.Forinstance,thisisaprintstatement: print("hello")

Invoke-Run/callafunction.

ParameterandArgument-Functionstellyouwhatandhowmanyparameterstheyhave.Argumentsarethedatathatgetspassedintothoseparameters.

Boolean- trueor false

Equality-Whetherornottwothingsareequal.Thisisusuallydonewithanequal( ==)comparison.

Loop-Codethatrepeats.

KeyandIndex-Keyisthenamedreferenceinatablewheredatacanbefound.Itissimilartoavariable.Indexisakeythatcomesinanorderedsequence,suchasnumberedkeysinalist.Thepluralofindexisindices.

Iterate-Loopoveralistanddosomethingwithit.

Scope-Anareawherevariablescanbecreatedthataren'taccessiblefromtheoutside.Scopesarecreatedbyfunctionsandloops.

LocalandGlobal-Localdescribesthingsaccessibleonlyinthecurrentscope,suchaslocalvariables.Globalthingsareaccessiblefromanywhereintheprogram.

Shadowing-Whenalocalvariablehasthesamenameasavariableinaparentscopeandpreventsyoufromaccessingtheparentscopevariable.

1.18-Chapterreview

49

Page 50: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Chapter2:IntroducingLÖVEThegoalofthischapteristoapplyallthebuildingblockswelearnedinthefirstchapterandmakethemconcretethroughpractice.Bytheendofthechapteryouwilllearnreal-worldskillssuchashowtointeractwithotherpeople'ssoftwareandbasicprinciplesfordesigningandbuildingyourownprograms.

2.0-IntroducingLÖVE

50

Page 51: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

UpandrunningLearningbymakingisfunandeffective.Learningtointerfacewithotherpeople'ssoftwareispartofbeingaprogrammerandisanecessaryskilltohaveasone.LÖVEisaframeworkformakinggames.Aframeworkisjustasetoftoolsorfunctionalitycombinedtogethertoservealargerpurpose.InthecaseofLÖVEthisincludes,butisnotlimitedto:

Functionsforloadingimages,audio,andtextFunctionsforcreatingandmovingobjectsonscreenParametersformakingtheobjectsinteract

InstallingyourdevelopmentenvironmentTheLÖVEwebsitehaslinkstoinstallthesoftwareonyoursystem.IfyouhaveLÖVEinstalledalready,makesurethatyouatleastversion11(mysteriousmysteries)assomefunctionalitywe'llcoverheredoesn'texistinolderversions.Formobiledevicesyoucanfindacopyintheappstore.

AlongwithinstallingLÖVE,youwillneedatexteditorforcreatingLuafilesonyoursystem.I'mnotgoingtomakeanyrecommendationshere,becauseintheenditallcomesdowntopersonalpreference,butyoucancheckthislistbytheLÖVEcommunityifyouneedastartingpoint.Itfeaturesdifferenteditors(andrecommendedplugins)forLÖVEandLuadevelopment.Simplypickone.

TestthatLÖVErunsWhenyoulaunchLÖVE,(seeinstructionsbelowonhowtodothat)youwillbegreetedwithafriendlygraphicandthetext"NOGAME",meaningyouarerunningtheenginebutdidn'tgiveitagametoload.

macOSOnceyouhavedownloadedtheLÖVEbinaryformacOS(64-bitzipped),proceedtothe"Downloads"folderandunzipthearchive.Youshouldnowseeanapplicationcalled"love".

macOSmightshowyouawarningmodal,becauseyouaretryingtoopenanapplicationbyanunverifieddeveloper.Ifso,rightclickontheapplicationandchoose"open"and"open"againinthefollowingdialog.Youshouldnowbegreetedbytheno-gamescreen.

Addendum:Homebrew

IfyouarefamiliarwithdevelopmentonamacOSmachine,youmighthaveheardofHomebrew.Itisapackagemanagerwhichallowsyoutoinstallalotofprograms,librariesandsoondirectlythroughyourTerminal.

Ionlyrecommendthisapproachforadvanceddeveloperswhoknowwhattheyaredoing.ForcompletenesssakeherearethestepstoinstallLÖVEviaHomebrew.

/usr/bin/ruby-e"$(curl-fsSLhttps://raw.githubusercontent.com/Homebrew/install/master/install)"

brewtapcaskroom/cask

brewcaskinstalllove

2.1-Upandrunning

51

Page 52: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Oneofthebenefitsofthisapproachis,thatyoudon'thavetosetupyourownterminalalias,becauseHomebrewalsotakescareofthat.

Windows

IfashortcutforLÖVEdidn'tappearinthestartmenu,youshouldbeabletotype"love"inthesearchandseeit.

Ubuntu

Openthe"UbuntuSoftware"applicationandsearch"love2d".Clickonthetopresultandyoushouldseeafamiliarapplicationdescription:

Clickthe"Install"buttontoinstallit.Onceinstalled,youcansearchforthe"terminal"application.Oncethatisopen,type lovetolaunchtheapplication.

OtherGNU/LinuxoperatingsystemsMostdistroshaveLÖVEintheirrespectiverepositories:

Archlinux-basedsystems- sudopacman-SyloveFedora-basedsystems- yuminstalllove

Onceinstalledfromyourpackagemanager,openaterminalandtype lovetotestthatitruns.

Ifyourdistrodoesn'thaveLÖVEinthepackagemanageranalternativewaytogetitistodownloadedtheAppImageversionfromthehomepage(https://love2d.org/).AppImagefilesarelikeauniversalexecutablethatworksacrossLinuxsystemssimilartothewayan"exe"fileworksonWindows.Oncedownloaded,opentheterminal,changetothedirectorywhereyoudownloadedtheAppImageandtypethecommands:

chmoda+xlove-11.1-linux-x86_64.AppImage#Marksthefileasasafeexecutable

2.1-Upandrunning

52

Page 53: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

./love-11.1-linux-x86_64.AppImage

love-11.1-linux-x86_64.AppImageshouldbechangedtomatchthenameofthedownloadedAppImagefile.

CreateaprojectfolderFindasafeplacetocreateafolderandgiveitthename"hello".Withinthefolder,createanewtextfilenamed"main.lua".Thiswillbewhereourgame'scodegoes.

NoteforWindows:Inordertocreateafilewiththename"main.lua",youmayneedtofirstcreateanew"TextDocument",right-clickonit,click"Properties"thenfromthepropertiesmenuchangethefileextensionfromreading"main.lua.txt"tojust"main.lua".ToavoidhavingtodothisforeveryLuafileyoucreateinthefutureyoucantellWindowstoalwaysshowthefullnameoffiles,includingtheirextension.Toenablethis,type"ControlPanel"intheprogramsearchandopenthe"ControlPanel"result.Withinthecontrolpanel,select"FileExplorerOptions".Clickthe"View"tab.Removethecheckmarkfromthe"Hideextensionsforknownfiletypes"andpressApply/OK.

CreateatestgameWithin"main.lua",writeoutthefollowingfunction:

love.draw=function()

love.graphics.print('HelloWorld!',400,300)

end

Nowlet'sfigureouthowtorunitandseewhatitdoes.

RunthegameThiswillbedifferentfordifferentoperatingsystems.

macOSStartingyourgame

ThesimplestwaytostartaLÖVEgameistodragthewholefoldercontainingthegame'ssourcefiles(notjustthemain.luafile!)ontotheapplicationfile.

2.1-Upandrunning

53

Page 54: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Thisalsoworkswith.lovefiles.

Usingtheterminal

IfyouarefamiliarwiththeTerminal,youcanuseitasamoreconvenientmethodofstartinggames.

Assumingthedownloaded"love"applicationisstillinyour"Downloads"folder,openanewTerminalandtypethefollowinglines(youneedtopressreturnaftereachline):

#SwitchestotheDownloadsfolder

cd~/Downloads/

#StarttheLÖVEapp

openlove.app

ThisobviouslystartsLÖVEwithano-gamescreensincewedidn'tspecifywhichfoldertoload.Let'sfixthisbytypingthefollowingcommand:

#Inmycasethefullcommandtostartthegalaxydemowouldbe:

#open-alove.app~/Downloads/galaxy

open-alove.app<path-to-your-game>

Usingaterminalalias

Wecanstillimproveonthepreviousmethodbyusinganalias.Beforewedothis,wemovethe"love"applicationbundletotheApplicationfolder.

#MovetheappfromDownloadstoApplications.

~mv~/Downloads/love.app/~/Applications/love.app

2.1-Upandrunning

54

Page 55: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Nowtrythefollowingcommand:

#StartLÖVEbyusingthescriptinsideoftheapplicationbundle.

~/Applications/love.app/Contents/MacOS/love<path-to-your-game>

AsyoucanseewenowcanrunLÖVEwithoutusingthe opencommand,whichalsohastheaddedbenefitofshowingthegame'sconsoleoutputdirectlyinourterminal.

Ofcourseitwouldberatherinconvenientifwehadtospecifythefullpatheachtimewewanttorunourgame,sowe'llnowsetupanaliasinyour.bash_profile(whichbasicallyactsasaconfigurationfileforyourbashsessions).

Sinceitisahiddenfileyoumightnotbeabletospotitinyourfinder,butwecansimplyedititthroughourTerminal.

#Appendsthealiasdefinitiontoanexisting.bash_profile

#orcreatesanewone.

echo"aliaslove='~/Applications/love.app/Contents/MacOS/love'">>~/.bash_profile

#Usetheupdated.bash_profileforthecurrentsession.

source~/.bash_profile

#Startyourgamethroughthealias.

love<path-to-your-game>

Andthat'sit:Youcannowquicklyrunyourgameswiththe lovealias.Thisisespeciallyhandyifyouareinsideofthegame'sdirectory,becauseallittakesnowisaquick love.tostartthegame.

Windows

FindtheshortcuttoLÖVEanddraganddropitinthegamefolderlikeso:

Thenright-clickontheLÖVEshortcutandyouwillseea"Properties"dialogwindowsimilartothis:

2.1-Upandrunning

55

Page 56: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

The"Target"fieldmaybethesameorslightlydifferentdependingonyoursystemversion.Withoutdeletingthetextstringcurrentlyinthe"Target"field,appendthepathtoyourgamefolderinquotestotheend.Youcancopyandpastethispathfromthefolder'saddressbar.Forinstancethetargetpathinthepictureshouldgofromreading:

"C:\ProgramFiles\LOVE\love.exe"

to

"C:\ProgramFiles\LOVE\love.exe""C:\Users\IEUser\Desktop\hello"

Nowpress"OK"toclosethePropertiesdialogandclickingtheshortcutwilllaunchthegame.Ifthegameransuccessfully,youwillseeablackwindowwiththetext"Helloworld!"insmallprint.

GNU/LinuxIfyouknowthelocationofyourfolder,youcanopenaterminalandtypethecommand:

love<path-to-your-game>

Where <path-to-your-game>hasbeenchangedtotheactualfolderpathwhereyourgameresides.

Ifyouarealreadynavigatedintothegamefolder,youcanrunaterminalcommandwithinthatdirectory:

2.1-Upandrunning

56

Page 57: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

love.

The"."simplymeans"thisfolderthatIamcurrentlyin".

Congratulations!You'vesetupyourdevelopmentenvironmentforwritingagameinLua.Ifyouhadissuesgettingthroughthis,reachouttomeeitherthroughaGitHubissueormycontactinformationandIwillupdatethisguidetoincludinganyadditionaltroubleshootingstepsforfutureusers.

Nowthatourdevelopmentenvironmentissetupandourfirstgameisrunning,trymodifyingthecodesothestring"HelloWorld!"readssomethingdifferent.It'sprettyapparentthatrunningthisfunctionprintstothescreenwhateverstringwegiveit.Butwhatarethe2ndand3rdparametersfor?

love.graphics.print('HelloWorld!',400,300)

Trymodifyingthosenumbersandseewhatitdoestothetext.

2.1-Upandrunning

57

Page 58: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

LÖVEstructureOpenup"main.lua"andtakealookatourfirstline.Wedefinedafunctioncalled love.draw,whichimpliesthereisatablecalled loveandwecreatedakeyinitcalled draw.Indeedthisisthecase,butsomehowthefunctionwasinvokedwithoutusinghavingtowrite love.draw()andinvokeitourselves.Thisrequiresahigh-levelexplanationofwhattheengineisdoingwithourfile.

WhenLÖVEisrun,beforeourmain.luafileisran,atablecalled loveisdefinedasaglobalvariable.Wecanassignfunctionstothistable( love.draw)andaccessfunctionsalreadydefinedinit( love.graphics.print).love.graphics.printhastwodotsinit,sothatmeansthelovetableprobablylookssomethinglike:

love={

draw=nil,

graphics={

print=function()...end

}

}

The lovetablehasaplentyofothertablesnestedinit,anditputssimilarfunctionsintablestogether.Soallthefunctionsrelatingtographicsareinsidethe love.graphicstable.

Once"main.lua"isdonerunning,we'veaccessedandmodifiedthe lovetableandaddedsomenewfunctionalitytoit,tellingithowtodrawtothescreenbydefiningour love.drawfunction.Ifwedefineafunctionwiththisname,thegameenginewillseeitandinvokeit.Infact,itcontinuouslyinvokes love.drawmanytimesasecond.Toprovemypoint,let'smodifymain.luaandmakeitprintanumber.

localnumber=0

love.draw=function()

number=number+1

love.graphics.print(number,400,300)

end

Eachtimewegotoprintthenumber,weincreaseitby1.Runthisprogramandseehowquicklythenumberclimbs.

The lovetableisaseeminglycomplexstructureoftablesinsidetablesandfunctionsinsidethose,butwe'llgraduallylearnthestructureandpurposeofeachthingoverthecourseofthischapter.Inthenextsection,let'stakealookatthe2ndand3rdparametersin love.graphics.printandseehowtheywork.

2.2-LÖVEstructure

58

Page 59: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

GeometryIfyoumodifiedthenumbers 400and 300inmain.luayouwillhaveseenthattheymovethetext.Realizingthatthey'resomekindofcoordinates,let'stalkaboutgraphs.

Whenlearningaboutgraphsingeometryclass,welearnedaboutanx-axisandy-axisandlabeledplottedpointsalongthegraph.Ifyouwantedtomark(-2,-4)thenyouwouldfindwhere-2onthex-axisintersectswith-4onthey-axis.KnowingthatXishorizontalandYisvertical,ifwehad(-2,-4)and(1,2)wecoulddrawitoutlikethis:

Thesetwopointscouldevenbeconnectedtoforma2-dimensionalline:

Beforewegettoofarondrawingpointsandlines,let'slookbackatourfunction:

love.graphics.print("HelloWorld!",400,300)

The 400istheXpositionandincreasingitwillmovethetextfurthertotheright.DecreasingtheXpositionwillmovethetextfurthertotheleft.The 300istheYposition,onedifferencebetweencomputersandgeometryclassisdataiscalculatedfromtoptobottom,soincreasingtheYpositionmovesthetextdownanddecreasingitmovesthetextup.Let'stakealookatwhatourgame'sgraphlookslikewiththepoint(5,3)highlighted:

Noticethatthetop-leftcornerofourscreenis(0,0),soifyoutriedtodrawanypointswithnegativenumberstheywouldbedrawnoffscreenwherewecan'tseethem.Anotherthingtonoteisinthegame,thecoordinatesrepresenthowmanypixelsdownandtotherightwewanttodraw.Sincecomputerscreensaremadeofsomanypixels,youneedtouselargenumberstomakeanoticeabledifference.

Ifwewantedtodrawapolygon(shape)suchasatriangleonthisgraph,wewouldhavetogivethreepoints:

Inthesameway,wecanplotoutpointsinourcodeandtellittodrawalinetoconnectthedots.Let'suselargernumbersthough.Rewritemain.luatolooklikethis:

love.draw=function()

love.graphics.polygon('line',50,0,0,100,100,100)

end

Thenumbersinthiscodecanbereadoffinpairstoidentifythecoordinates:(50,0),(0,100),(100,100)LÖVE'sphysicsenginetakesthecoordinates,startingatthefirstpointandconnectsthemwithalinesequentially.Onceitreachesthelastpoint,itdrawsalinefromthelastpointbacktothefirsttoclosetheshape.

Let'stryarectangletogetsomemorepracticein:

love.draw=function()

localrectangle={100,100,100,200,200,200,200,100}

love.graphics.polygon('line',rectangle)

end

Noticethistimeinsteadofpassingthenumbersdirectlyinto love.graphics.polygon,weputthemintoalistandpassedthelistin.Passingincoordinatesbothwayshasthesameeffect.

Anotherimportantthingtothinkaboutisifyoudrawapolygonwith4ormoresides,youneedtomakesurethepointsarelistedinthecorrectorder.Considerthefollowingexample:

Ifwefedthepointsintothefunctioninaclockwiseorcounter-clockwise/anti-clockwiseorder,therectanglewouldbedrawnthesameeitherway.Ifwefedthepointsinfromcrossdirections,wemayaccidentallydrawabowtie:

Creatingshapeswithunclosedsidesdon'tplaywellwithLÖVE'sphysicsengineassuchshapesarenotphysicallypossible.Ifyoutrytodothis,youmaynotseetheshapeyouexpect,andperhapsnothingwillbedrawn.

2.3-Geometry

59

Page 60: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Creatingshapeswithunclosedsidesdon'tplaywellwithLÖVE'sphysicsengineassuchshapesarenotphysicallypossible.Ifyoutrytodothis,youmaynotseetheshapeyouexpect,andperhapsnothingwillbedrawn.

ExercisesTakealookatthedocumentationforthefunctionlove.graphics.polygon.Theexampleshowstheargument'line'isastringandrepresentsthe"drawMode".Trychangingthe"drawMode"from 'line'tooneoftheotheravailableoptions(seetheexamplesorclickthedrawModelinkonthewikipage).Whatotheroptionisthere?Howdoesitwork?Tryitoutandseehowitworks!Trymakingapolygonwith5sides/points.Hint:usethesquareexampleaboveasareference.

2.3-Geometry

60

Page 61: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

GameloopAnotheraspectcommonwithgameenginesisthatthereisaloop(likeawhileloop)thatcontinuouslyrunsandkeepsthegamegoing.Theorderthatthingshappeninvaries,butthecontentsmoreoflesslooklike:

1. Gameisstarted.Loadgamefiles.2. Beginloop.3. Checkforinputfromkeyboard,joystick,orotherperipherals.4. Ticktimeingame.5. Redrawthescreen.6. Gobacktostep2.

Duringstepsinthegameloop,LÖVEinvokescertainfunctionsinsidethe lovetable.Forinstance,everytimethescreenneedstobere-drawn,thegameloopinvokes love.draw()ifyoudefinedit.InthestepwhereLÖVEchecksforuserinput,ifthereisuserinput,itinvokes love.keypressed(PRESSED_KEY)ifwedefinedit.The PRESSED_KEYthatispassesinofcoursedependsonwhatkeytheuserpressed.Whendefining love.keypressed,itmaylooksomethinglikethis:

love.keypressed=function(pressed_key)

print('keywaspressed:',pressed_key)

end

Let'smodifymain.luatohaveacontrivedexample:

localcurrent_color={1,1,1,1}

love.draw=function()

localsquare={100,100,200,200,100,200,100,200}

--Initializethesquarewiththedefaultcolor(white)

love.graphics.setColor(current_color)

--Drawthesquare

love.graphics.polygon('fill',square)

end

love.keypressed=function(pressed_key)

ifpressed_key=='b'then

--Blue

current_color={0,0,1,1}

elseifpressed_key=='g'then

--Green

current_color={0,1,0,1}

elseifpressed_key=='r'then

--Red

current_color={1,0,0,1}

elseifpressed_key=='w'then

--White

current_color={1,1,1,1}

end

end

Whenyoupressanyofthekeys,"b","g","r",or"w",ourfunction love.keypressedwillbeinvokedandthevariablepressed_keywillbeastringmatchingoneofourletters.Thischanges current_color,whichischangingthecolorbeingdrawnin love.draw.

Inthenextsection,let'sseehowLÖVEhandlesthe"4.Ticktimeingame."stepofthegameloop.

2.4-Gameloop

61

Page 62: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

ExercisesTryaddingafewmorecolorstotheprogram.Tounderstandhow love.graphics.setColorworks,seethedocumentation.Makeissothatiftheescapekeyispressed,thefunction love.event.quitisinvokedandthegameexits.Thestringtousefortheescapekeycanbefoundonthewiki'sKeyConstantpage.Spoilers:thesolutioncanbeseeninthenextsection.

2.4-Gameloop

62

Page 63: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

DeltatimeHere'swhatwe'velearnedaboutthegameloopsofar:

1. Gameisstarted.Loadgamefiles.main.luaisloadedandthe lovetableisupdatedwithourmodifications.

2. Beginloop.3. Checkforinputfromkeyboard,joystick,orotherperipherals.

Iftherewaskeyboardinputandwedefined love.keypressed,invokeit,passingitinformationaboutthepressedkey.

4. Ticktimeingame.???

5. Redrawthescreen.Invoke love.drawifwedefinedit.

6. Gobacktostep2.

Let'stakealookatthefunction love.update:

love.update=function(dt)

print(dt)

end

NoteforWindows:UnlessyouarerunningLÖVEfromtheconsole,youwon'tseeanythingprintedout.Putthisfileinthegamefoldernexttomain.luaunderthename"conf.lua":

--LÖVEconfigurationfile

love.conf=function(t)

t.console=true

end

Thisconfigurationfilewillletussetspecialparametersforourgame.Don'tworrytoomuchaboutwhatalltheoptionsare,butifyou'recuriousthenyoucanfindthedocumentationhere.EssentiallythiswillopenaconsolewindowonWindowstoseeprintedvalues.

Nowrunthegameandifyouweren'tseeingthe print(dt)messagedisplayanythingyoushouldnowseeitbeinginvokedmanytimesasecond,printingoutadecimalnumber. dtstandsfordeltatimeanditrepresentstheamountofsecondsthathaspassedsincethelastgameloop.Ifthegameloops4timesasecond,thatmeans love.updateand love.drawgetinvoked4timeseachsecondaswell.Thedeltatimeinthiscasewouldberoughly 0.25asroughly1/4asecondhaspassedbetweeneachtime love.updatewascalled.Somecomputersarefasterthanotherssothenumberofgameloopspersecondwillbedifferent.Youarelikelyseeingnumbersaround 0.01orsmaller,meaningthegameisrunningroughly100framesasecond.Let'saddacountertothescreenlikebefore,butnowusingdeltatime.

localcurrent_color={1,1,1,1}

localseconds=0

love.draw=function()

localsquare={100,100,100,200,200,200,200,100}

--Printacounterclock

localclock_display='Seconds:'..seconds

love.graphics.print(clock_display,0,0,0,2,2)

2.5-Deltatime

63

Page 64: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

--Initializethesquarewiththedefaultcolor(white)

love.graphics.setColor(current_color)

love.graphics.polygon('fill',square)

end

love.keypressed=function(pressed_key)

ifpressed_key=='b'then

--Blue

current_color={0,0,1,1}

elseifpressed_key=='g'then

--Green

current_color={0,1,0,1}

elseifpressed_key=='r'then

--Red

current_color={1,0,0,1}

elseifpressed_key=='w'then

--White

current_color={1,1,1,1}

end

ifpressed_key=='escape'then

love.event.quit()

end

end

love.update=function(dt)

--Addupallthedeltatimeaswegetit

seconds=seconds+dt

end

Imagineifwewantedtomoveacharacteracrossthescreenbutwedidn'tusedeltatime.Thecharacterwouldrunfasteronsomecomputersandsloweronothers.Computerswouldkeepgettingfasterandthegamewouldrunsofastitwouldnolongerbeplayable.Deltatimesolvesthisissueandwe'llbetakingadvantageofitforeverythingtime-basedinourgame.

ExercisesChangetheline localclock_display='Seconds:'..secondssothat secondsisformattedtodisplaywholenumbers.Hint:youwillneedtouseLua'sbuilt-in math.floorfunctiontoformat seconds.Changethexpositionoftheleftsideofthesquarefrom 100to (seconds*10)andwatchwhatthesquaredoes.

2.5-Deltatime

64

Page 65: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

MappingLet'ssidetrackfromLÖVEforaminutetolearnaboutaconceptcalledmaps.Nottobeconfusedwithoverheadmapsaplayerwouldwalkaroundoninagame,butdatamaps.Weactuallydidmappingbackinchapter1whenwelearnedabouttables.

coins={

["half"]="50cents",

["quarter"]="25cents",

["dime"]="10cents",

["nickel"]="5cents",

["penny"]="1cent"

}

print("Whichcoindoyouhave?")

response=io.read()

print("Yourcoinisworth"..coins[response]..".")

Whenevertheusertypedinacoin,wemappedthecoinnametoavaluebylookingupthecoinnameinthetable,ordictionary.Sowhat'sthedifferencebetweentables,dictionaries,andmaps?

tablesarejustadatatypeinLuathatcanbeusedtobuilddatastructureslikelistsanddictionariesdictionariesarekey-valuestoragesusedtocentralizesimilar-purposedatainoneplaceandmakeiteasiertolookthedataupmapsaredatastructuresusedtotranslateonetypeofinformationtoanother,andadictionaryisonetypeofmap

Dictionariesaretheonlytypesofmapwe'llbeconcernedabouthere,butknowthatmapsgenerallyrefertoinstancesofdatastructuresthatdomapping.Thereareoftendiscrepanciesinterminologybetweenmathematicsandthevariousfieldsincomputerscience.Don'tbesurprisedifyouseedictionariesandmapsbeingusedsynonymouslyinothercontextslaterinlife.

Let'sdosomemappingonourcodewepreviouslywrotetogetabetterfeelforthem.Rememberallthoseif/elseifstatementsinmain.lua?

ifpressed_key=='b'then

--Blue

current_color={0,0,1,1}

elseifpressed_key=='g'then

--Green

current_color={0,1,0,1}

elseifpressed_key=='r'then

--Red

current_color={1,0,0,1}

elseifpressed_key=='w'then

--White

current_color={1,1,1,1}

end

ifpressed_key=='escape'then

love.event.quit()

end

Wecanputallthatfunctionalityinamaplikethis:

localkey_map={

b=function()

current_color={0,0,1,1}--Blue

end,

2.6-Mapping

65

Page 66: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

g=function()

current_color={0,1,0,1}--Green

end,

r=function()

current_color={1,0,0,1}--Red

end,

w=function()

current_color={1,1,1,1}--White

end,

--Closethegame

escape=function()

love.event.quit()

end

}

Thisdoesn'tlookanymoreconcisethanourpreviouscode,butourgoalistokeepthe love.keypressedfunctioncleaninthiscase.Whenakeyispresseditwillbemappedtoakeyfunctionwedefinein key_map.Anotherimportantthingisthesefunctionscouldbemodularandmovedanywhereweneedthemtobe,andevenre-used.Let'snotgotoocrazyrightnowthough.We'llkeepthekeymapsomewherenearthetop.

localcurrent_color={1,1,1,1}

localseconds=0

localkey_map={

b=function()

current_color={0,0,1,1}--Blue

end,

g=function()

current_color={0,1,0,1}--Green

end,

r=function()

current_color={1,0,0,1}--Red

end,

w=function()

current_color={1,1,1,1}--White

end,

escape=function()

love.event.quit()

end

}

love.draw=function()

localsquare={100,100,100,200,200,200,200,100}

--Printacounterclock

localclock_display='Seconds:'..math.floor(seconds)

love.graphics.print(clock_display,0,0,0,2,2)

--Initializethesquarewiththedefaultcolor(white)

love.graphics.setColor(current_color)

love.graphics.polygon('fill',square)

end

love.keypressed=function(pressed_key)

--Checkinthekeymapifthereisafunction

--thatmatchesthispressedkey'sname

ifkey_map[pressed_key]then

key_map[pressed_key]()

end

end

love.update=function(dt)

--Addupallthedeltatimeaswegetit

seconds=seconds+dt

end

2.6-Mapping

66

Page 67: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Ifyoupressakeythatisn'tpartofthemapthenthenewifstatement( ifkey_map[pressed_key]...)willseethatkeydoesn'texistinthemapandnotdoanything. key_map[pressed_key]()isthesameassaying key_map['b'](),key_map['escape']()orwhateverthevalueof pressed_keywasatthetime.

2.6-Mapping

67

Page 68: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

TheworldAworldisaphysicalspacewhereobjectscanbecreated(spawned)andinteract.Shapesandotherthingsdrawnonthescreenarenotimplicitlypartofaworldandwon'tinteractwitheachotherunlesstheyare.Multipleworldscanco-exist,buttheobjectsineachworldwon'tinteract.GoingforwardIwillrefertotheseobjectsasentities.

EntitiesEntitiesaremadeupofdifferentcomponentsthatallowthemtointeract.Thesearethe3fundamentalphysicalcomponents:

shape-somesortofpolygontogiveourentityaphysicalshapethatdeterminestheboundariesoftheentitybody-holdsphysicalpropertiessuchasmassfixture-attachesashapetoabody

Let'swriteanewmain.luafromscratchandseehowtheseareallwiredup.First,aworldneedstobedefined:

localworld=love.physics.newWorld(0,100)

love.physics.newWorldreturnsatable,aninstanceofaworld.Thetableholdsfunctionsthatallowustoapplyattributestotheworld.Italsoholdsalltheentitiesinourworld,whichiscurrentlynoneoninitialization.Accordingtothedocumentationon love.physics.newWorld,our1stand2ndparameterssettheXandYgravityonourworld.Wedon'twantanysidewaysgravity,butwe'llgoaheadandsetanarbitrarynumberfortheverticalgravity.

Whilefocusingontheworld,weshouldallowtheworldtoknowwheneverwegetanupdatetothedeltatime.Aworldwithouttimewouldbefrozen;Bylettingtheworldknowaboutthepassageoftime,itcanknowwhetheritneedstomakeanentityfallanothermeterortwometers…

love.update=function(dt)

world.update(world,dt)

end

Actually,let'sdoonetrickhere.WhencallingafunctioninLuaandthefirstparameterofthefunctionisthetablethefunctionisstoredin,youcanuseashortcutnotation:

love.update=function(dt)

world:update(dt)

end

Asidefrombeingeasiertowrite,you'llseethiswayofinvokingfunctionsusedallovertheplaceintheLÖVEdocumentation.

Finally,we'lladdanentitytothegamein4steps:

1. Createatabletostoreallthepiecesofourentitytogether.Notentirelynecessarybutwe'lllearnlaterwhythisstepmakesthingseasier.

2. Createabody.Thiswillbeaddedtotheentitytableandtheworld.3. Createtheshapewewanttheentitytohave.4. Createafixturetoattachthebodyandshapetogether.

--Triangleisthenameofourfirstentity

localtriangle={}

2.7-Theworld

68

Page 69: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

triangle.body=love.physics.newBody(world,200,200,'dynamic')

--Givethetrianglesomeweight

triangle.body:setMass(32)

triangle.shape=love.physics.newPolygonShape(100,100,200,100,200,200)

triangle.fixture=love.physics.newFixture(triangle.body,triangle.shape)

love.draw=function()

love.graphics.polygon('line',triangle.body:getWorldPoints(triangle.shape:getPoints()))

end

Aftercreatingthe bodytableinside triangle,wecalled triangle.body.setMasstosetaweightpropertyonourtrianglesoitcanfall.Noticewewrote triangle.body:setMass(32),whichisthesameassayingtriangle.body.setMass(triangle.body,32)butshorterandmoreconventionaltothewaytheLÖVEdocumentationwrites.

What'sgoingoninside love.drawlooksprettycrazysolet'sbreakthelonglineup.

love.graphics.polygon(

'line',

triangle.body:getWorldPoints(triangle.shape:getPoints())

)

We'veused love.graphics.polygonpreviouslysoitspurposeshouldalreadybefamiliar.Thefirstparameter 'line'istellingitthatwewantanoutlineofashapedrawn.Thesecondparameterisatablecontainingthepointsthatneedtobeoutlined.Togetthetriangle'spointswecall triangle.shape:getPoints(),butthisonlyreturnstheshapeofthetriangleandtherelativepositionofthepoints.Bythencallingtriangle.body:getWorldPoints(triangle.shape:getPoints())weconvertthoserelativepointstotheirabsolutepositionasthat'swhatthepolygondrawingfunctionneedstoknowsoitcandrawthetriangleexactlywhereitissupposedtobeonthescreen.

Let'sputitalltogetherandaddonemoreentityintothemixsothetwocaninteract:

localworld=love.physics.newWorld(0,100)

--Triangleisthenameofourfirstentity

localtriangle={}

triangle.body=love.physics.newBody(world,200,200,'dynamic')

--Givethetrianglesomeweight

triangle.body.setMass(triangle.body,32)

triangle.shape=love.physics.newPolygonShape(100,100,200,100,200,200)

triangle.fixture=love.physics.newFixture(triangle.body,triangle.shape)

--Anotherentity

localbar={}

bar.body=love.physics.newBody(world,200,450,'static')

bar.shape=love.physics.newPolygonShape(0,0,0,20,400,20,400,0)

bar.fixture=love.physics.newFixture(bar.body,bar.shape)

localkey_map={

escape=function()

love.event.quit()

end

}

love.draw=function()

love.graphics.polygon('line',triangle.body:getWorldPoints(triangle.shape:getPoints()))

love.graphics.polygon('line',bar.body:getWorldPoints(bar.shape:getPoints()))

end

love.keypressed=function(pressed_key)

--Checkinthekeymapifthereisafunction

2.7-Theworld

69

Page 70: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

--thatmatchesthispressedkey'sname

ifkey_map[pressed_key]then

key_map[pressed_key]()

end

end

love.update=function(dt)

world:update(dt)

end

Thisisalottodigestsodon'thesitatetore-readthroughthiscodeseveraltimesifnecessary.Therewerealotofnewfunctionsintroducedinthissection,sointhenextsectionwe'lltakeadeeperlookatthedocumentationandreadmoreaboutthemandtheircomponents.

ExercisesTrychangingthemass( triangle.body:setMass)andgravity( love.physics.newWorld)andseewhathappens.

2.7-Theworld

70

Page 71: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

ReadingdocumentationYoutypicallyrunintotwotypesofdocumentationforsoftware:guidesandAPIdocumentation.Guideswouldbeinformationongettingstarted,tutorials,andbooks.AnAPI(applicationprogramminginterface)isaportionofsoftwarethataprogrammerwritesforhis/herprogramtoallowfellowprogrammerstointeractwiththeirapplication.AsforLÖVE'sprogramminginterface,mostofyourinteractionswiththeframeworkaredonethroughthe loveglobalvariablethattheframeworkpurposelyexposes.APIdocumentationisthemostfundamentalformofsoftwaredocumentationbecausewithoutit,youwouldnotknowwhatallthefunctionsintheprogramdounlessyouwereresourcefulenoughtogoinandstudyallthesourcecodeandfigureeachfunctionoutonyourown.

ThedocumentationforLÖVE(bookmarkthis!)iswritteninawikistylewhereeachtableandfunctionhasanarticlethatdescribeshowtouseit.Fromhereyoushouldseemanymoduleslisted.Clickon love.physics.Again,love.physicsisjustatablewithfunctionsinit.Sowithinthearticleweseeeachofthefunctionsstoredinitincludingthefunctions love.physics.newBodywhichweusedtocreateourentities'bodies, love.physics.newPolygonShapewhichweusedtocreatetheirshapes,and love.physics.newFixturewhichweusedtocreatetheirfixtures.Wealsosee love.physics.newWorldwhichcreatedourworldtable.Let'slookatthefirstfunction'sarticle.

Clickingonthearticlefor love.physics.newBodywegetasynopsisshowinghowthefunctionmightbeused,alongwithadescriptionofitsparametersandwhatthefunctionreturns.Overthecourseofdifferentversionsoftheframework,functionsmaybemodifiedsointhecaseofthisfunctionyoucanseeexamplesofhowtouseitdifferentlyindifferentversionsofLÖVE.Sincethefunctionliststhatitreturnsabody,clickthelinktogoovertothearticleonthebodytable'sdocumentation.

There'salotoffunctionsherethatsetpropertiesonthebodyandgetpropertiesfromthebody.Oneofthemisthebody:setMassfunctionweusedtogiveourentityweight.Wecanseethatittakesoneparameterandthattheparameterismeanttosimulatehowmanykilogramsofmassthebodyhas.Weoriginallytolditthatourtriangleinthelastsectionweighed32kilograms,whichifyouthinktheobjectfelltoofastortooslowthenyoumayneedtoadjustyourworld'sgravitytomatchyourexpectation.

Nowlet'sgobackto love.physicsforamomentandtakealookatoneoftheothercomponentsweaddedtoourpreviouscode,thefixture.Wepreviouslycreatedafixturetablebycalling triangle.fixture=love.physics.newFixture(triangle.body,triangle.shape).However,wehaven'tseenanyofthesefunctionsinthefixturetablethatcouldcomeinhandy.Forinstance,wecouldgiveourtrianglebouncinessbyinvokingfixture:setRestitution.Ourtrianglefixtureisnamed triangle.fixturethough,not fixture.If0isnobounciness(default)and1is100%,trymodifyingthegamecodeandaddingarestitutionof75%:

triangle.fixture:setRestitution(0.75)

Tryrunningthegameandseehowthatworks.Ifyousettherestitutionto 1orhigherthenthetrianglewon'tstopbouncingandwillbounceitselfrightoffthescreen.

CallbacksLet'sbacktracknowtothemainarticleaboutthelovetable.Ifyouscrolldownalittleonthepage,you'llseeasectiontitled"Callbacks"thatcontainssomefunctionswe'vebecomefamiliarwithsuchas love.drawand love.update.Thisisalistofallthefunctionsinthegameloopthatwehaveandhaven'ttalkedaboutyet.A"callback"isafunctionyoucreateandgivetoanAPI(the lovetableinthiscase)thatwilllatergetinvokedasneeded.Creatingfunctionswiththesenamesallowyoutotapintospecificportionsofthegameloopandtriggeryourownevents.

2.8-Readingdocumentation

71

Page 72: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Let'stakealookatthe love.keypressedcallbackforinstance.Inthesynopsisyouseethatithas3parameters(thedocumentationmistakenlycallsparameters"arguments").Weusedthefirstparameter keytoseewhatkeywaspressed.Ifyoueverneedtoknowwhatkeysareavailable,youcanclickonthelinkprovidednexttotheparametername, KeyConstanttoseeawell-definedlistofalltheavailablekeystringspassedintothisparameter.Thesecondparameter scancodewedidn'ttalkabout,butithasawell-defined Scancodearticleexplainingwhatitis.Ifyouarenotfamiliarwithscancodes,takeaminutetoreaditandperhapsyou'lllearnaboutafeatureyoumaywanttouseinyourgame.

Onemorecallbackwe'lllookatwhilewe'rehereis love.focus.Takeamomenttostophereandreadwhatitdoesandwhatparametersittakesbeforecontinuing.Nowitwouldbereallycoolifweweremakingagameandwhentheuserswitchedtoanotherapplicationwindow,thegameautomaticallypausedfortheuser.Sofirstlet'sstartbyimplementingapausefeatureinourearliergamecode:

localworld=love.physics.newWorld(0,9.81*128)

--Triangleisthenameofourfirstentity

localtriangle={}

triangle.body=love.physics.newBody(world,200,200,'dynamic')

--Givethetrianglesomeweight

triangle.body.setMass(triangle.body,32)

triangle.shape=love.physics.newPolygonShape(100,100,200,100,200,200)

triangle.fixture=love.physics.newFixture(triangle.body,triangle.shape)

triangle.fixture:setRestitution(0.75)

--Anotherentity

localbar={}

bar.body=love.physics.newBody(world,200,450,'static')

bar.shape=love.physics.newPolygonShape(0,0,0,20,400,20,400,0)

bar.fixture=love.physics.newFixture(bar.body,bar.shape)

--Booleantokeeptrackofwhetherourgameispausedornot

localpaused=false

localkey_map={

escape=function()

love.event.quit()

end,

space=function()

paused=notpaused

end

}

love.draw=function()

love.graphics.polygon('line',triangle.body:getWorldPoints(triangle.shape:getPoints()))

love.graphics.polygon('line',bar.body:getWorldPoints(bar.shape:getPoints()))

end

love.keypressed=function(pressed_key)

--Checkinthekeymapifthereisafunction

--thatmatchesthispressedkey'sname

ifkey_map[pressed_key]then

key_map[pressed_key]()

end

end

love.update=function(dt)

ifnotpausedthen

world:update(dt)

end

end

Noticethe3changes:

Weaddedabooleancalled pausedandsetittofalse

2.8-Readingdocumentation

72

Page 73: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Weaddedanewfunctionto key_mapsothatwhen "space"ispressed,thevalueof pausedissetto notpaused. notisanoperatorforbooleanswepreviouslydidn'tdiscuss.Itsimplysays"theoppositeofthisboolean".Soif pausedis true,thensetting pausedto notpausedwillsetitto false.Lastly,inside love.updatetotoldtheworldtoupdateonlyifweare notpaused.Sothepassageoftimeinthegameworldwillceasewhenpressingthespacekey.

ExercisesNowwiththedocumentationinhand,define love.focusandmakeitsothegamepauseswhentheuserclicksawayfromthegamewindow.Bonus:makethegameprintatextsayingthatthegameispausedwhen pausedis true.Gofindthedocumentationfor love.graphics.printtoseeanexampleondisplayingtext.

2.8-Readingdocumentation

73

Page 74: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

ModulesandorganizationEventuallywhenyoustartwritingrealprograms,yourealizeifyoukeepallthecodeinonefilethatthingscangetabitmessy.Puttingyourcodeinseparatefileshelpsyounotonlykeepyourdifferentpiecesofcodeseparatedfromeachother,butithelpsyouvisualizethestructureofyourprogram.

Let'sstartwithasinglemain.luafileandwe'llthensplititintodifferentfiles:

localmy_cool_functon=function()

love.graphics.print('Thisfunctioncamefromfunction-module.lua',100,100,0,2)

end

localmy_cool_table={}

my_cool_table.print_stuff=function()

love.graphics.print('Thisfunctioncamefromtable-module.lua',100,200,0,2)

end

print('my_cool_functionis',my_cool_function)

print('my_cool_tableis',my_cool_table)

Whenwegotorunthecode,wegetablankwindowbecausewe'renotdrawinganything.Wedoseeourprintstatementsoutputourfunctionandtabletotheconsolethough:

my_cool_functionisfunction:0x41f2abc0

my_cool_tableistable:0x41f2aa08

Modulesand requireThinkofyourLuafilesasgiantfunctionsthatgetinvokedwheneveryouloadthefile.Justlikeafunction,youcanreturnvaluesfromyourfiles.IfyouloadoneLuafilefromanother,youwillgetwhatevervalueisreturned.Let'smodifyourpreviouscode.Firstdefinethesetwonewfilesinthegamefolder:

function-module.lua

returnfunction()

love.graphics.print('Thisfunctioncamefromfunction-module.lua',100,100,0,2)

end

table-module.lua

localmy_cool_table={}

my_cool_table.print_stuff=function()

love.graphics.print('Thisfunctioncamefromtable-module.lua',100,200,0,2)

end

returnmy_cool_table

Thenupdatemain.lua:

localfunction_module=require('function-module')

localtable_module=require('table-module')

print('function_moduleis',function_module)

print('table_moduleis',table_module)

2.9-Modulesandorganization

74

Page 75: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Let'sstartfromthetop.Infunction-module.luawewriteareturnstatementthatreturnsafunctionwithnoname.Wedon'tinvokethefunction,wejustreturnitasavaluethesamewayafunctionmayreturnanumberorstring.Likewiseintable-module.luawedefinedatable(withafunctioninit)andreturnedthetableonthelastlineofthefile.Thefunctionnameandlocalvariablename my_cool_tableisinconsequentialandcan'tbeseenoutsidethetable-module.luafileasmoduleshavetheirownscope.

Inmain.luawearerequiringfunction-moduleusingabuilt-inLuafunction, require. requiretakesoneargument,astringthatequalsthenameofyourfileandittheninvokesthatfileandreturnsbackafunctionwhichweassigntoanewvariable function_module.Wethendothesamethingfortable-module.lua.Werequireit,whichinvokesitandreturnsbackwhateverthatfilereturns.Inthiscaseisatable.Noticethatwhenwepassinthefilenamesasargumentswejustgivethefirstpartofthefilenamewithouttheextension".lua"attheend.ThisfunctionexpectsthatanyfileitisrequiringisaLuafile.

Afterwerequiredthefiles,weprintedthevaluesofthosevariables,soyoushouldseetheresultsoftheprintstatementsappearintheconsolelikebefore:

function_moduleisfunction:0x40479548

table_moduleistable:0x40479bc8

Wepulledinthereturnvaluesfromtheothertwofilesintoourmain.luafileandprintedthevalues,butsincewedidn'tinvokethefunctionsfromthosetwofilesthenwegotablankgamewindowwhenrunningtheprogram.Let'sdefinealove.drawinmain.lualikebeforeandinvokethefunctionswegotbackfrombothmodules:

localfunction_module=require('function-module')

localtable_module=require('table-module')

print('function_moduleis',function_module)

print('table_moduleis',table_module)

love.draw=function()

function_module()

table_module.print_stuff()

end

Wewereabletoinvokethefunctionsandusethereturneddataasifitwereallinthesamefile.

OrganizingmodulesItmayhelptoseearealexampleofusingmodulesinagame,solet'stakeourpreviousgamecodefrom2.8-Readingdocumentationandseehowwecanseparateoutfunctionality.Thefirstthingwedidinourgamewasdefineaworld,solet'sstartbyputtingourworld-relatedcodeinadedicatedfilenamedworld.lua:

--world.lua

localworld=love.physics.newWorld(0,9.81*128)

returnworld

Rememberthatyouneedthereturnstatementattheendofyourfilesorelsethecodewillreturn nilwhenyougotorequireitandthiscouldcauseallkindsofunexepctederrorswhenyourunit.Nextlet'screateafoldernamedentitiesthatwecankeepallourgameentitiesin.Weplanoncreatingmoreentitiessoitwillhelptokeepthemalltogether.Intheentitiesfolder,createafileandnameittriangle.lua.We'llcutallthecodefromtheoriginalmain.luathatrelatedtoourtriangleentityandputithere:

--entities/triangle.lua

2.9-Modulesandorganization

75

Page 76: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localworld=require('world')

localtriangle={}

triangle.body=love.physics.newBody(world,200,200,'dynamic')

triangle.body.setMass(triangle.body,32)

triangle.shape=love.physics.newPolygonShape(100,100,200,100,200,200)

triangle.fixture=love.physics.newFixture(triangle.body,triangle.shape)

triangle.fixture:setRestitution(0.75)

returntriangle

Noticehowwearerequiringthe worldtablefromworld.lua,becauseweneedtoaccessthattableinthisentity'sfilesowecanaddtheentitytotheworld.Wealsoneedtodothesamethingasabovewiththebarentity:

--entities/bar.lua

localworld=require('world')

localbar={}

bar.body=love.physics.newBody(world,200,450,'static')

bar.shape=love.physics.newPolygonShape(0,0,0,20,400,20,400,0)

bar.fixture=love.physics.newFixture(bar.body,bar.shape)

returnbar

Nowourmain.luashouldonlycontainourkeymapand lovefunctions:

--main.lua

localbar=require('entities/bar')

localtriangle=require('entities/triangle')

localworld=require('world')

--Booleantokeeptrackofwhetherourgameispausedornot

localpaused=false

localkey_map={

escape=function()

love.event.quit()

end,

space=function()

paused=notpaused

end

}

love.draw=function()

love.graphics.polygon('line',triangle.body:getWorldPoints(triangle.shape:getPoints()))

love.graphics.polygon('line',bar.body:getWorldPoints(bar.shape:getPoints()))

end

love.focus=function(focused)

ifnotfocusedthen

paused=true

end

end

love.keypressed=function(pressed_key)

--Checkinthekeymapifthereisafunction

--thatmatchesthispressedkey'sname

ifkey_map[pressed_key]then

key_map[pressed_key]()

end

end

2.9-Modulesandorganization

76

Page 77: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

love.update=function(dt)

world:update(dt)

end

Ourtwoentitiesandworldgetpulledintomain.luaandeverythingshouldrunexactlyasbefore.Onethingtonoteisthateventhoughwerequireworld.lua3timesinourcode,itisthesameworldandnot3copies.ThisisbecauseLuaknowstoonlyrunamodulethefirsttimeyourequireitandnotinvokeitagain.Onceitrunsthefirsttime,thereturnedresultsarestoredinmemoryforthenexttimeyoutrytorequireit.Wecanprovethisbyaddingaprintstatementtoworld.lua:

--world.lua

print("Thisistheworld")

localworld=love.physics.newWorld(0,9.81*128)

returnworld

Howmanytimesdoes "Thisistheworld"getprintedtotheconsole?

ExercisesTrycreatingtwonewmodules;Onethatreturnsastringandonethatreturnsanumber.

2.9-Modulesandorganization

77

Page 78: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

CollisioncallbacksWhenwritingagamesuchasaplatformeryoumaywantsomethingspecialtohappenwhentwoobjectscollide.Ifit'sapowerup,forinstance,youmaywantthepoweruptodespawn(beremovedfromtheworld)ifaplayertouchesitandthengivetheplayeraspecialability(thinkMarioandmushrooms).Ifaplayerandanenemybumpintoeachother,youmaywanttheplayer'shealthtodecrement.Theworldtablehasamethodthatallowsyoutoprograminfunctionalitylikethisforwhentwoentitiescollide.Itdoesthisbyallowingyoutocreatecallbacksaswelearnedbefore,butthesecallbacksaretriggeredbefore,during,oraftercollision.TakealookatWorld:setCallbacks.

Ifyoulookattheparametersfor World:setCallbacks,youseeitcantakefourfunctions.Thedescriptionoftheseparametershelpsexplainwhenthefunctionswillbecalled. beginContactand endContactshouldbeselfexplanatory;Theyhappenatthepointwherecontactbeginsandendsinacollision,but preSolveand postSolvemaynotbeasobvious.Nonetheless,let'seditthepreviously-createdworld.luafileandwritesomecollisioncallbackstotestthisfunctionality.

--world.lua

localbegin_contact_counter=0

localend_contact_counter=0

localpre_solve_counter=0

localpost_solve_counter=0

localbegin_contact_callback=function()

begin_contact_counter=begin_contact_counter+1

print('beginContactcalled'..begin_contact_counter..'times')

end

localend_contact_callback=function()

end_contact_counter=end_contact_counter+1

print('endContactcalled'..end_contact_counter..'times')

end

localpre_solve_callback=function()

pre_solve_counter=pre_solve_counter+1

print('preSolvecalled'..pre_solve_counter..'times')

end

localpost_solve_callback=function()

post_solve_counter=post_solve_counter+1

print('postSolvecalled'..post_solve_counter..'times')

end

localworld=love.physics.newWorld(0,9.81*128)

world:setCallbacks(

begin_contact_callback,

end_contact_callback,

pre_solve_callback,

post_solve_callback

)

returnworld

Tryitout.Everytimeoneofthecallbacksisinvoked,itwillincrementitsownnumberby1thenprintamessagetotheconsoletellingyouhowmanytimesithasbeeninvoked.It'sclearrightawaythat pre_solve_callbackandpost_solve_callbackgetinvokedmanymoretimesthan begin_contact_callbackand end_contact_callbackinthissituation.

2.10-Collisioncallbacks

78

Page 79: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Unlessyou'veeditedthebehaviorofthetriangleentity,itwillbounceabit(becauseofthetriangle'srestitution).Onceitbouncesandneithercornerorsideistouchingthefloorunderneath,thecontactends.Thisprocessisrepeatedeverytimeitbounces.Oncethetrianglesettlesdownitwillslideabit,maybeevenalot...likeanairhockeypuck.Thisisbecauseourtriangleandbarhavenofrictionbetweenthemtopreventthat.Anyways,thisisgoodbecauseitallowsustoseethatwhilethetriangleisslidingitisstillmakingcontact.Whilethetriangleisslidingandstillmakingcontact,the pre_solve_callbackand post_solve_callbackwillcontinuetogetcalledwitheveryframeofmovement.

Pretendourtrianglewasafuturisticracecarmovingacrossaneonstripofroadthatrechargedthevehicle.Youcouldstartincreasingtheracecar'spowermeterinside begin_contact_callbackasthecarmakescontactwiththatsectionofroadandthenstopincreasingpowerwhen end_contact_callbackisinvoked.Thiscouldworkprettywell,butthentheplayermaytryparkingforamomentonthepowerstripandcontinuetogainhealthaslongastheywant.Soanotherapproachcouldbetoonlyincreasethepowermeterastheplayercontinuestomoveandmakecontactwiththeroad,increasinghealthby1pointeverytimethe post_solve_callbackfunctionisinvoked.

Youdon'tnecessarilyneedtouseallofthesecallbacks,soyoucouldjustpassinanemptyfunctionor niltoWorld:setCallbacksfortheargumentsyoudon'tneed.

Withoutknowingwhatentitiesarecolliding,thecollisioncallbacksaren'tveryuseful.Luckily,ourcallbackshaveparametersoftheirownthatwecanaccess.Let'smodifythecodeagainandcheckoutthoseparameters:

--world.lua

--Calledatthebeginningofanycontactintheworld.Parameters:

--{fixture}fixture_a-firstfixtureobjectinthecollision.

--{fixture}fixture_b-secondfixtureobjectinthecollision.

--{contact}contact-worldobjectcreatedonandatthepointof

--contact.Whenslidingalonganobject,theremaybeseveral.

--Seefurther:https://love2d.org/wiki/Contact

localbegin_contact_callback=function(fixture_a,fixture_b,contact)

print(fixture_a,fixture_b,contact,'beginningcontact')

end

localend_contact_callback=function(fixture_a,fixture_b,contact)

print(fixture_a,fixture_b,contact,'endingcontact')

end

localpre_solve_callback=function(fixture_a,fixture_b,contact)

print(fixture_a,fixture_b,contact,'abouttoresolveacontact')

end

localpost_solve_callback=function(fixture_a,fixture_b,contact)

print(fixture_a,fixture_b,contact,'justresolvedacontact')

end

localworld=love.physics.newWorld(0,9.81*128)

world:setCallbacks(

begin_contact_callback,

end_contact_callback,

pre_solve_callback,

post_solve_callback

)

returnworld

Thisshouldprintoutsomeinformationintheconsolesimilarto:

Fixture:0x561020bf8570Fixture:0x561020bf7350Contact:0x561020bf7480beginningcollision

Fixture:0x561020bf8570Fixture:0x561020bf7350Contact:0x561020bf7480abouttoresolveacontact

Fixture:0x561020bf8570Fixture:0x561020bf7350Contact:0x561020bf7480justresolvedacontact

Fixture:0x561020bf8570Fixture:0x561020bf7350Contact:0x561020bf7480endingcollision

2.10-Collisioncallbacks

79

Page 80: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Fixture:0x561020bf8570isatextrepresentationofourfirstentity'sfixture.The 0x56...isthememoryaddressofthefixturetohelpidentifyit,althoughthisinformationstilldoesn'ttelluswhichentitythisfixturebelongsto.Wealsoprintedoutacontacttable,whichcontainsasetoffunctionsjustliketheentities.Thisinstanceofacontactprovidesinformationsuchaswherethecontacthappenedandhowmuchvelocitywasinvolved.

Let'sworkonmodifyingtheprintstatementssowecancollectmoreusefulinformationonthesecollisions.Thereisapairoffunctionsoneveryfixturethatlet'syousetanyarbitrarydatayouwantonthatfixtureandanotherfunctiontogetthatdatabackoutthefixture.Thesefunctionsarecalled Fixture:setUserDataand Fixture:getUserData.ThesefunctionscanbeusedtosetanameorIDonthefixturetohelpusidentifywhatentityitbelongsto.Wecanaccomplishthisbyfirstmodifyingourentityfilesandpassingsomestringsto Fixture:setUserData:

--entities/bar.lua

localworld=require('world')

localbar={}

bar.body=love.physics.newBody(world,200,450,'static')

bar.shape=love.physics.newPolygonShape(0,0,0,20,400,20,400,0)

bar.fixture=love.physics.newFixture(bar.body,bar.shape)

bar.fixture:setUserData('bar')

returnbar

--entities/triangle.lua

localworld=require('world')

localtriangle={}

triangle.body=love.physics.newBody(world,200,200,'dynamic')

triangle.body.setMass(triangle.body,32)

triangle.shape=love.physics.newPolygonShape(100,100,200,100,200,200)

triangle.fixture=love.physics.newFixture(triangle.body,triangle.shape)

triangle.fixture:setRestitution(0.75)

triangle.fixture:setUserData('triangle')

returntriangle

Nowgobacktotheworld'scollisioncallbacksandyoucaneasilyextractthisinformationbackoutofthefixtures:

--world.lua

--Calledatthebeginningofanycontactintheworld.Parameters:

--{fixture}fixture_a-firstfixtureobjectinthecollision.

--{fixture}fixture_b-secondfixtureobjectinthecollision.

--{contact}contact-worldobjectcreatedonandatthepointof

--contact.Whenslidingalonganobject,theremaybeseveral.

--Seefurther:https://love2d.org/wiki/Contact

localbegin_contact_callback=function(fixture_a,fixture_b,contact)

localname_a=fixture_a:getUserData()

localname_b=fixture_b:getUserData()

print(name_a,name_b,contact,'beginningcontact')

end

localend_contact_callback=function(fixture_a,fixture_b,contact)

localname_a=fixture_a:getUserData()

localname_b=fixture_b:getUserData()

print(name_a,name_b,contact,'endingcontact')

end

localpre_solve_callback=function(fixture_a,fixture_b,contact)

localname_a=fixture_a:getUserData()

2.10-Collisioncallbacks

80

Page 81: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localname_b=fixture_b:getUserData()

print(name_a,name_b,contact,'abouttoresolveacontact')

end

localpost_solve_callback=function(fixture_a,fixture_b,contact)

localname_a=fixture_a:getUserData()

localname_b=fixture_b:getUserData()

print(name_a,name_b,contact,'justresolvedacontact')

end

localworld=love.physics.newWorld(0,9.81*128)

world:setCallbacks(

begin_contact_callback,

end_contact_callback,

pre_solve_callback,

post_solve_callback

)

returnworld

Ah,nowwecanseewhichfixtureiscollidingwhich!

bartriangleContact:0x55bf29c07590beginningcontact

bartriangleContact:0x55bf29c07590abouttoresolveacontact

bartriangleContact:0x55bf29c07590justresolvedacontact

bartriangleContact:0x55bf29c07590endingcontact

ExercisesModifytheprintstatementsineachcollisioncallbacktoprintoutthecoordinateswheretheentities'fixturesaremakingcontact.Youcanfindtheinformationyouneedtodothisinthedocumentationforthecontacttablementionedabove.

2.10-Collisioncallbacks

81

Page 82: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Breakout(part1):moreentitypracticeLet'sbringalltheseconceptstogetherbymakinganothergame,abreakoutclone.Therequirementsareprettysimple:

TheobjectiveofthegameistodestroyallthebricksonthescreenTheplayercontrolsa"paddle"entitythathitsaballTheballdestroysthebricksTheballneedstostaywithintheboundariesofthescreenIftheballtouchesthebottomofthescreen(belowthepaddle),thegameends

Ifyoustillhavethecodefromtheprevioussections,feelfreetocopythefoldernamingthenewone"breakout"orwhateveryouwantyourbreakoutclonetobecalled.Attheendofthissectiontherewillbealinktoallthesourcecodetouseasareferenceincaseyougetstuck.Thismaybetimeconsuming,butIencourageyoutotypeouteachsectionandstoptounderstandwhatitisyouaretyping.Ifyoucopy,paste,anddon'treadthenitwillbeeasytogetlostinthischunkofthechapterasthingswillmovefast.

Thefirstmodificationwe'llmakeistosetaspecificwindowsizesonomatterwhichversionofLÖVEyou'reonwe'reworkingwiththesamewindowproportionsandentitydimensions.Todothis,openofconf.luaorcreateitifyoudon'thaveitandputinthefollowingcode:

--conf.lua

--LÖVEconfigurationfile

love.conf=function(t)

t.console=true--EnablethedebugconsoleforWindows.

t.window.width=800--Game'sscreenwidth(numberofpixels)

t.window.height=600--Game'sscreenheight(numberofpixels)

end

Theconf,orconfigurationfileletsyoudefineacallbackinthe lovetablethatmodifiesthegameengine'sconfigurationonload.Youcanreadmoreaboutalltheinterestingthingsyoucandowithitherebutmostofitsfeatureswon'tbenecessaryforoursimplegame.

Thenextmodificationwe'llmakeisdeletingtheentitiesfromthelastsection.Let'screatenewentitiestorepresenttheballandpaddle:

--entities/ball.lua

localworld=require('world')

localentity={}

entity.body=love.physics.newBody(world,200,200,'dynamic')

entity.body:setMass(32)

entity.body:setLinearVelocity(300,300)

entity.shape=love.physics.newCircleShape(0,0,10)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setRestitution(1)

entity.fixture:setUserData(entity)

returnentity

--entities/paddle.lua

localworld=require('world')

2.11-Breakout(part1)

82

Page 83: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localentity={}

entity.body=love.physics.newBody(world,200,560,'static')

entity.shape=love.physics.newRectangleShape(180,20)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

returnentity

Beforewetryandrunanything,takealookatafewthingswe'vedonedifferentlyindefiningtheseentitiesthanwe'vepreviouslydone.

Inball.luawearedefiningacircleshapeinsteadofapolygon.Thismeanswehavenosidesorcornerpointswecanreferencewhenspawningortrackingthepositionofthisobject.Circleshavetobetrackedfromtheircenterpointandtheirboundariesbytheirradius.Inthisfilewe'reusing Body:setLinearVelocitytoapplymovementontheballinaspecificdirectionwhentheentityspawns.Inpaddle.luawearedefiningapolygonshape,butinsteadofspecifyingeachpointweareusingthelove.physics.newRectangleShapefunctiontodefinetheshape.Thiswillstillgenerateapolygonasbefore,butinsteadofspecifyingeachpointintheshapewearegivingaheightandwidthandallowingittofigureouttheshapewewantbasedonthosetwoparameters.Thinkofitasashortcutversionofthelove.physics.newPolygonShapefunction.Thepaddlehasastaticbodywhiletheballisdynamic.Whatthisentailsistheballwillbeaffectedbythepaddlebutthepaddlewon'tbeaffectedbytheball.Eventhoughthepaddleisstatic,itcanbemanuallyrepositionedaswe'lldolaterwithbuttons.Inbothentityfiles,wearepassingthefullentitytableasthefixtureuserdatainsteadofjustastringnamelikebefore.Thiswillallowustoeasilyaccesstheentireentityinsidethecollisioncallbackaswe'llseelater.You'llwanttogobackandcomparethatcodefromtheCollisionCallbackssectiontotheseentities,butdon'tworryifitdoesn'tmakecompletesenseyet.

Nowweneedtomodifymain.luatoloadupournewentities:

--main.lua

localpaddle=require('entities/paddle')

localball=require('entities/ball')

localworld=require('world')

--Booleantokeeptrackofwhetherourgameispausedornot

localpaused=false

localkey_map={

escape=function()

love.event.quit()

end,

space=function()

paused=notpaused

end

}

love.draw=function()

localball_x,ball_y=ball.body:getWorldCenter()

love.graphics.circle('fill',ball_x,ball_y,ball.shape:getRadius())

love.graphics.polygon(

'line',

paddle.body:getWorldPoints(paddle.shape:getPoints())

)

end

love.focus=function(focused)

ifnotfocusedthen

paused=true

2.11-Breakout(part1)

83

Page 84: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

end

end

love.keypressed=function(pressed_key)

--Checkinthekeymapifthereisafunction

--thatmatchesthispressedkey'sname

ifkey_map[pressed_key]then

key_map[pressed_key]()

end

end

love.update=function(dt)

ifnotpausedthen

world:update(dt)

end

end

Takenoteofafewthingswe'redoinghere:

Fordrawingthecircle,weneedtoinvoke love.graphics.circle.Fordrawingthepaddle,westillinvoke love.graphics.polygonastherectangleisstillapolygonshape.

Nowlet'sremoveanyprintstatementsinworld.luajusttocleanthingsup.We'llleavethecallbackstheresincewemayusethemlaterbutwe'llleavethememptyfornow.We'llalsosetthegravityto 0becausewewanttheballtobouncefreelylikeintherealBreakoutgameandnotloseanymomentum.

--world.lua

--Calledatthebeginningofanycontactintheworld.Parameters:

--{fixture}fixture_a-firstfixtureobjectinthecollision.

--{fixture}fixture_b-secondfixtureobjectinthecollision.

--{contact}contact-worldobjectcreatedonandatthepointof

--contact.Whenslidingalonganobject,theremaybeseveral.

--Seefurther:https://love2d.org/wiki/Contact

localbegin_contact_callback=function(fixture_a,fixture_b,contact)

end

localend_contact_callback=function(fixture_a,fixture_b,contact)

end

localpre_solve_callback=function(fixture_a,fixture_b,contact)

end

localpost_solve_callback=function(fixture_a,fixture_b,contact)

end

localworld=love.physics.newWorld(0,0)

world:setCallbacks(

begin_contact_callback,

end_contact_callback,

pre_solve_callback,

post_solve_callback

)

returnworld

Whathappensifyourunthegamenow?Theballfliesrightoffthescreenwithoutconsequence.Thereareacoupledifferentwaysofpreventingtheballfrommovingoffscreen.Possiblythemostsimpleapproachistoputupsomewalls.Canyouguesswhatthecodetothosewallsmaylooklike?Yup,theywillbeentitiessimilartothepaddleexceptthattheyjustsitattheedgesofthescreen.Let'screatesomeentitiesforthatpurpose:

--entities/boundary-top.lua

2.11-Breakout(part1)

84

Page 85: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localworld=require('world')

localentity={}

entity.body=love.physics.newBody(world,400,5,'static')

entity.shape=love.physics.newRectangleShape(800,10)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

returnentity

Takealookatthesenumbersforaminute.Forthelocationofthebodywespecified400pixels.Sostartingfromthetopleftcornerandmovingrightalongthex-axiswe'vespecifiedtheverycenterofan800-pixel-widewindow.Thereasonwe'vedonethisisbecausewewantthetopandbottomwallboundariestostretch800pixelswide,theentirelengthofthewindow,andwhencalling newBodyandspawninganentity'sbodyitwillspawnthecenterpointoftheentityshapeatthatlocation.Notallentityshapesaresquare,orevenpolygonal,soitissimplestforthegameenginetocentertheshapeonthebody'sspawnpointratherthanusinganotherpointofreferenceontheshape,likethetopleftcorneroftheshape(notallshapeshavecorners).Infact,theballandpaddlespawnedcenteredonthelocationwegavefortheirbodies.

Sowemadethewalls800pixelswideandjusttogiveitalittlevisibilitywemadethem10pixelstall.Youwouldthinkwe'dspawnthewallattheverytopofthescreen(0pixelsonthey-axis,)butsinceourwallswillbecenteredtothespawnpointsweshouldmovedownhalftheheightofthewallifwewantitalltoappearonscreen.

Nowtheboundaryonthebottomwillhavethesamedimensions,butitwillbespawnedatthebottomofthescreen(600pixels)minushalftheheightofthewall(5pixels):

--entities/boundary-bottom.lua

localworld=require('world')

localentity={}

entity.body=love.physics.newBody(world,400,595,'static')

entity.shape=love.physics.newRectangleShape(800,10)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

returnentity

Theleftandrightboundarieswillfollowthesamepatternexcepttheywillbetheheightofthescreeninsteadofthewidthofthescreen:

--entities/boundary-left.lua

localworld=require('world')

localentity={}

entity.body=love.physics.newBody(world,5,300,'static')

entity.shape=love.physics.newRectangleShape(10,600)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

returnentity

--entities/boundary-right.lua

localworld=require('world')

localentity={}

entity.body=love.physics.newBody(world,795,300,'static')

entity.shape=love.physics.newRectangleShape(10,600)

2.11-Breakout(part1)

85

Page 86: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

returnentity

Wewon'tseetheseentitiesuntilwerequirethemanddrawthemonthescreen.Somodifymain.luatorequireanddrawthemthesamewaywedotheballandpaddle:

--main.lua

localboundary_bottom=require('entities/boundary-bottom')

localboundary_left=require('entities/boundary-left')

localboundary_right=require('entities/boundary-right')

localboundary_top=require('entities/boundary-top')

localpaddle=require('entities/paddle')

localball=require('entities/ball')

localworld=require('world')

--Booleantokeeptrackofwhetherourgameispausedornot

localpaused=false

localkey_map={

escape=function()

love.event.quit()

end,

space=function()

paused=notpaused

end

}

love.draw=function()

love.graphics.polygon('line',boundary_bottom.body:getWorldPoints(boundary_bottom.shape:getPoints()))

love.graphics.polygon('line',boundary_left.body:getWorldPoints(boundary_left.shape:getPoints()))

love.graphics.polygon('line',boundary_right.body:getWorldPoints(boundary_right.shape:getPoints()))

love.graphics.polygon('line',boundary_top.body:getWorldPoints(boundary_top.shape:getPoints()))

localball_x,ball_y=ball.body:getWorldCenter()

love.graphics.circle('fill',ball_x,ball_y,ball.shape:getRadius())

love.graphics.polygon('line',paddle.body:getWorldPoints(paddle.shape:getPoints()))

end

love.focus=function(focused)

ifnotfocusedthen

paused=true

end

end

love.keypressed=function(pressed_key)

--Checkinthekeymapifthereisafunction

--thatmatchesthispressedkey'sname

ifkey_map[pressed_key]then

key_map[pressed_key]()

end

end

love.update=function(dt)

ifnotpausedthen

world:update(dt)

end

end

2.11-Breakout(part1)

86

Page 87: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Whenyourunthegame,youshouldseeprettymuchthesamethingasthis:

Ifyoumissedanythingorarehavingissues,here'sacopyofthecompletedsourcecodeforthissection:https://github.com/RVAGameJams/learn2love/tree/master/code/breakout-1

Lookingbackatourlistofminimalrequirementswe'vealreadycompletedonethingonourlist:

Theballneedstostaywithintheboundariesofthescreen

There'sstillquiteabitmoreworktocompletethislistsolet'scontinueinthenextsection.

ExercisesMaybeitwouldbebetteriftheboundarylineswereevenwiththescreensowecouldn'tseethem.Modifytheboundarypositionssoitlooksliketheballisbouncingofftheedgeofthescreen.Whathappensifwe requiretheboundariesbutdon'tdrawthemin love.draw?Doesthegamestillwork?

2.11-Breakout(part1)

87

Page 88: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Breakout(part2):entitymanagement

ReviewIntheprevioussectionwemadeachecklistofrequirementsandaccomplishedoneofthem:

TheobjectiveofthegameistodestroyallthebricksonthescreenTheplayercontrolsa"paddle"entitythathitsaballTheballdestroysthebricks✔TheballneedstostaywithintheboundariesofthescreenIftheballtouchesthebottomofthescreen,thegameends

Inthepreviousexercise,thegoalwastomovetheboundariessotheywerejustoffscreen.Thisgivestheeffectthattheballisbouncingofftheedgesofthegamewindow.

--entities/boundary-bottom.lua

entity.body=love.physics.newBody(world,400,606,'static')

--entities/boundary-left.lua

entity.body=love.physics.newBody(world,-6,300,'static')

--entities/boundary-right.lua

entity.body=love.physics.newBody(world,806,300,'static')

--entities/boundary-top.lua

entity.body=love.physics.newBody(world,400,-6,'static')

Heretheyhavebeenmoved6pixelsoffscreenjusttouseevennumbersandmakecalculationeasier.Previouslywealsoraisedthequestionofwhetherornottheboundarieswouldworkifwestill require'dtheminmain.luabutdidn'tdrawthemin love.draw.Theansweristheystillworkbutwedon'tseethem.Sincetheyareoffscreen,thatdoesn'tmatteranywayandwecansaveourprogramfromdoingextrawork:

--main.lua

love.draw=function()

localball_x,ball_y=ball.body:getWorldCenter()

love.graphics.circle('fill',ball_x,ball_y,ball.shape:getRadius())

love.graphics.polygon('line',paddle.body:getWorldPoints(paddle.shape:getPoints()))

end

EntitylistLet'sthinkabouttheproblemofbrickentitiesforaminute.Wecouldcreateanentityfileforeachbrick,buttheyaremoreorlessthesameexceptthattheyspawnindifferentspots.Imaginemaking50differententityfilesandtheninside love.drawmaking50linestodraweachbrickandsoon.Whatwecaninsteaddoismakeanentityfilefor1brickthenmakealistwith50copiesofit(orhowevermanybrickcopiesweendupfittingonthescreen).Wecanthenloopoverthislisttodrawthebricks.

Let'sfirstcreatethebrickentityfile:

2.12-Breakout(part2)

88

Page 89: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

--entities/brick.lua

localworld=require('world')

returnfunction(x_pos,y_pos)

localentity={}

entity.body=love.physics.newBody(world,x_pos,y_pos,'static')

entity.shape=love.physics.newRectangleShape(50,20)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

returnentity

end

Insteadofreturninganentityinthisfile,wereturnedafunctionthattakesanx-positionandy-positionasparameters.Whenthefunctiongetsinvokedwhereveritisrequired,itwillgenerateanewentitywiththosecoordinatesforitsspawnpoint.Here'showwecanuseit:

--main.lua

localboundary_bottom=require('entities/boundary-bottom')

localboundary_left=require('entities/boundary-left')

localboundary_right=require('entities/boundary-right')

localboundary_top=require('entities/boundary-top')

localpaddle=require('entities/paddle')

localball=require('entities/ball')

localbrick=require('entities/brick')

localentities={

brick(100,100),

brick(200,100),

brick(300,100)

}

localworld=require('world')

--Booleantokeeptrackofwhetherourgameispausedornot

localpaused=false

localkey_map={

escape=function()

love.event.quit()

end,

space=function()

paused=notpaused

end

}

love.draw=function()

localball_x,ball_y=ball.body:getWorldCenter()

love.graphics.circle('fill',ball_x,ball_y,ball.shape:getRadius())

love.graphics.polygon('line',paddle.body:getWorldPoints(paddle.shape:getPoints()))

for_,entityinipairs(entities)do

love.graphics.polygon('fill',entity.body:getWorldPoints(entity.shape:getPoints()))

end

end

love.focus=function(focused)

ifnotfocusedthen

paused=true

end

end

love.keypressed=function(pressed_key)

2.12-Breakout(part2)

89

Page 90: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

--Checkinthekeymapifthereisafunction

--thatmatchesthispressedkey'sname

ifkey_map[pressed_key]then

key_map[pressed_key]()

end

end

love.update=function(dt)

ifnotpausedthen

world:update(dt)

end

end

Wemadeanentitytablewithalistofbrickentitiesinit,thenin love.drawwemadeaforlooptodraweachentityinthelist.Beforewechangeanythingelsetryrunningthegameandtakingalookthatthebricksappearandthateverythingworks.

RuleofsingleresponsibilityOurgoalfortherestofthissectionwillbetosimplifyentitymanagement.Onestrategywe'llhavefordoingthisistothinkofeachfileinourgameashavingasingleresponsibility.Agoodsignthatwe'redoingthisismain.luaisverysmallandeasytoscanoverwiththeeyesanddigest.

Sowhatistheresponsibilityofmain.lua?

Createthecallbackfunctionsnecessarytorunthegame.

Here'ssomethingsitisdoingthatdon'tfitthatresponsibility:

LoadandstorealltheentitiesFigureouthowtodraweachtypeofentityin love.drawStoreamapofkeypresses

Imagineourgameisanorganizationandeachfileisaroleinthecompany.Ourmainfileislikethesecretarythatknowshowtohandlerequestsfromoutsiders.Ifsomebodycalledaskingthesecretaryaboutbuilding-maintenanceissues,thesecretarywouldn'tgrabplumbingtoolsandtakecareoftheproblembutratherdispatchthepersonwhoseresponsibilityisthatexactkindofproblem.Astheownerofthisorganizationweshouldknoweveryone'srolessoit'seasytoknowwhereeachresponsibilitylies.Itwillmakeiteasierforustogrowthecompanytothesizewedesire.

Oneeasyimprovementistonotwriteoutalltheinstructionsfordrawingeachentitywithinthemainfile,butratherleteachentityfileberesponsibleforeveryfeatureofthatentity,includinghowtodrawthatentity.Wemaywanttogetfancylateranddrawbricksindifferentcolors,forinstance.Thatcouldgetcomplicatedandwedon'twantthemainfiletoretainabunchofcodeaboutbrickcolorsandsuch.

Modifyingtheentitiesisaseasyascreating drawfunctionsintheentitytables:

--entities/brick.lua

localworld=require('world')

returnfunction(x_pos,y_pos)

localentity={}

entity.body=love.physics.newBody(world,x_pos,y_pos,'static')

entity.shape=love.physics.newRectangleShape(50,20)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

entity.draw=function(self)

love.graphics.polygon('fill',self.body:getWorldPoints(self.shape:getPoints()))

end

2.12-Breakout(part2)

90

Page 91: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

returnentity

end

--entities/paddle.lua

localworld=require('world')

returnfunction(pos_x,pos_y)

localentity={}

entity.body=love.physics.newBody(world,pos_x,pos_y,'static')

entity.shape=love.physics.newRectangleShape(180,20)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

entity.draw=function(self)

love.graphics.polygon('line',self.body:getWorldPoints(self.shape:getPoints()))

end

returnentity

end

--entities/ball.lua

localworld=require('world')

returnfunction(x_pos,y_pos)

localentity={}

entity.body=love.physics.newBody(world,x_pos,y_pos,'dynamic')

entity.body:setMass(32)

entity.body:setLinearVelocity(300,300)

entity.shape=love.physics.newCircleShape(0,0,10)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setRestitution(1)

entity.fixture:setUserData(entity)

entity.draw=function(self)

localself_x,self_y=self.body:getWorldCenter()

love.graphics.circle('fill',self_x,self_y,self.shape:getRadius())

end

returnentity

end

Goaheadandmakealltheentitiesreturnafunctionwith x_posand y_posparametersandwe'lljustaddeverythingtotheentitylistlikethebricks.Don'tforgettochangeoutthenumbersinthe love.physics.newBody(world,200,200,'dynamic')withtheargumentsbeingpassedinbythefunction: love.physics.newBody(world,x_pos,y_pos,'dynamic').Fortheboundariesentityfilesthereisnoneedfor entity.drawfunctions,butstillmakethemreturnfunctionswiththetwoparameters.Nowupdatethe entitieslistinmain.luatoincludealltheentities:

--main.lua

localboundary_bottom=require('entities/boundary-bottom')

localboundary_left=require('entities/boundary-left')

localboundary_right=require('entities/boundary-right')

localboundary_top=require('entities/boundary-top')

localpaddle=require('entities/paddle')

localball=require('entities/ball')

localbrick=require('entities/brick')

localentities={

boundary_bottom(400,606),

boundary_left(-6,300),

2.12-Breakout(part2)

91

Page 92: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

boundary_right(806,300),

boundary_top(400,-6),

paddle(300,500),

ball(200,200),

brick(100,100),

brick(200,100),

brick(300,100)

}

localworld=require('world')

--Booleantokeeptrackofwhetherourgameispausedornot

localpaused=false

localkey_map={

escape=function()

love.event.quit()

end,

space=function()

paused=notpaused

end

}

love.draw=function()

for_,entityinipairs(entities)do

ifentity.drawthenentity:draw()end

end

end

love.focus=function(focused)

ifnotfocusedthen

paused=true

end

end

love.keypressed=function(pressed_key)

--Checkinthekeymapifthereisafunction

--thatmatchesthispressedkey'sname

ifkey_map[pressed_key]then

key_map[pressed_key]()

end

end

love.update=function(dt)

ifnotpausedthen

world:update(dt)

end

end

Takealookatour love.drawfunction.Itismuchsimplernowthatitnolongerneedstoknowhowtodraweachentity.Itjustaskstheentityifitknowshowtodrawitselfandifitdoesittellsittodoso.Rememberthatinvokingentity:draw()isjustshorthandforwriting entity.draw(entity)becauseofthe :.

Ok,butputtingtheentitiesinalistdidn'tcleanupthisfile.Nowthisfileisresponsibleforknowingwheretospawntheentitiesandhavingtheminalistjustmakesthisfilebigger.Wellyouseethereasonweputtheminalistisbecausewewanttomakeanewgamefilecalledentities.luathatwillberesponsibleforloading,spawning,andstoringalltheentitieswhenthegamestartsup.Createanewfilethencutalltheentity requirestatementsandtheentitylistandpasteitinthenewfile:

--entities.lua

localboundary_bottom=require('entities/boundary-bottom')

localboundary_left=require('entities/boundary-left')

localboundary_right=require('entities/boundary-right')

2.12-Breakout(part2)

92

Page 93: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localboundary_top=require('entities/boundary-top')

localpaddle=require('entities/paddle')

localball=require('entities/ball')

localbrick=require('entities/brick')

return{

boundary_bottom(400,606),

boundary_left(-6,300),

boundary_right(806,300),

boundary_top(400,-6),

paddle(300,500),

ball(200,200),

brick(100,100),

brick(200,100),

brick(300,100)

}

Andnowthetopofourmainfileonlyneedstoloadtheentitiesfileanditwillhavethelisttousein love.drawandelsewhereasneeded:

--main.lua

localentities=require('entities')

localworld=require('world')

Whenyourunthegame,youshouldbeseeingsomethingsimilartothis:

Ifyoumissedanythingorarehavingissues,here'sacopyofthecompletedsourcecodeforthissection:https://github.com/RVAGameJams/learn2love/tree/master/code/breakout-2

2.12-Breakout(part2)

93

Page 94: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Andthat'saboutitforentitymanagement.We'llfigureouthowtohandlekeypressesforthepaddleandeverythingelseinthenextsection.We'llfinishthecleanupinourmainfilewhilewe'reatit.

ExercisesNowthatourentitieshavepassedoffknowledgeonwheretheyspawnovertoentities.lua,ourleftandrightboundariesareidenticalfiles.Replaceboundary-left.luaandboundary-right.luawithasingleboundary-vertical.luafileandspawntwocopiesofthatinentities.lua.Ifyougetstuck,checkouttheentities.luafileinthenextsectionforhowthisisdone.

2.12-Breakout(part2)

94

Page 95: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Breakout(part3):inputs

ReviewIntheprevioussectionwereconstructedourentitiestomakeroomforbricksandadditionalfunctionality.Wehaven'tcompletedanynewitemsonourchecklist:

TheobjectiveofthegameistodestroyallthebricksonthescreenTheplayercontrolsa"paddle"entitythathitsaballTheballdestroysthebricks✔TheballneedstostaywithintheboundariesofthescreenIftheballtouchesthebottomofthescreen,thegameends

Solet'scomeupwithasystemtohandleuserinputandgetthepaddlemoving.

InputserviceInsidemain.luathereissomefunctionalityforthisthatwearegoingtoremoveandrewritestartingwithanewfilethatspecificallyhandlesalltheuserinput.Thiskindoffileistypicallycalledaservicebecauseitabstractsawaytediousfunctionalityintoaneasy-to-useservice.Iencourageyoutowriteouttheserviceinsteadofcopyingandpasting.Readthrougheachfunctionandtrytounderstandwhateachonedoes.

--input.lua

--Thistableistheserviceandwillcontainsomefunctions

--thatcanbeaccessedfromentitiesorthemain.lua.

localinput={}

--Mapspecificuserinputstogameactions

localpress_functions={}

localrelease_functions={}

--Formovingpaddleleft

input.left=false

--Formovingpaddleright

input.right=false

--Keeptrackofwhethergameispause

input.paused=false

--Lookupinthemapforactionsthatcorrespondtospecifickeypresses

input.press=function(pressed_key)

ifpress_functions[pressed_key]then

press_functions[pressed_key]()

end

end

--Lookupinthemapforactionsthatcorrespondtospecifickeyreleases

input.release=function(released_key)

ifrelease_functions[released_key]then

release_functions[released_key]()

end

end

--Handlewindowfocusing/unfocusing

input.toggle_focus=function(focused)

ifnotfocusedthen

input.paused=true

end

end

2.13-Breakout(part3)

95

Page 96: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

press_functions.left=function()

input.left=true

end

press_functions.right=function()

input.right=true

end

press_functions.escape=function()

love.event.quit()

end

press_functions.space=function()

input.paused=notinput.paused

end

release_functions.left=function()

input.left=false

end

release_functions.right=function()

input.right=false

end

returninput

Theinputtableiswhatgetsreturned,meaningwhenwe require('input')inanotherfile,wegetbackthattableanditscontents.Insidetheinputtherearethreebooleanpropertiesthatgettoggledbyuserinput: input.left,input.right,and input.paused.Alongwiththesethreeproperties,therearethreefunctionsexposedtoustomakeuseof: input.press, input.release,and input.toggle_focus,allofwhichwewillinvokefromourcallbacksinmain.lua:

--main.lua

localentities=require('entities')

localinput=require('input')

localworld=require('world')

love.draw=function()

for_,entityinipairs(entities)do

ifentity.drawthenentity:draw()end

end

end

love.focus=function(focused)

input.toggle_focus(focused)

end

love.keypressed=function(pressed_key)

input.press(pressed_key)

end

love.keyreleased=function(released_key)

input.release(released_key)

end

love.update=function(dt)

ifnotinput.pausedthen

for_,entityinipairs(entities)do

ifentity.updatethenentity:update(dt)end

end

world:update(dt)

end

end

2.13-Breakout(part3)

96

Page 97: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

In love.updateweskipupdatesif input.pausedis true.Howeverifthegameisnotpausedthenitwillloopthroughtheentitylist,calling entity.updateiftheentityhasanupdatefunction.Withthisaddedfunctionality,wecanappendan entity.updatefunctionintoourexistingpaddlecode:

--entities/paddle.lua

entity.update=function(self)

--Don'tmoveifbothkeysarepressed.Justreturn

--insteadofgoingthroughtherestofthefunction.

ifinput.leftandinput.rightthen

return

end

localself_x,self_y=self.body:getPosition()

ifinput.leftthen

self.body:setPosition(self_x-10,self_y)

elseifinput.rightthen

self.body:setPosition(self_x+10,self_y)

end

end

Theleftandrightarrowswillnowmovethepaddle!Thereisn'tmuchelsetosayhereinthewayofinput.Abitunrelatedtotheactualinput,butmoresothepaddlefunctionalityisitmovesoffscreenanddoesn'tadheretotheboundaries?Whyisthat?

Ifyourememberwhenwecreatedthepaddle,itisastaticentity.Itdoesn'thavetheabilitytomoveonitsownorbytheeffectofotherentities.Thiswillcauseussomeproblemslater(andwe'relearningthehardway)!Ratherthanforcingthepaddlewithaninvisiblepush,weforceanewpositionforthepaddlewhenwecall body:setPositioninsidethepaddle's entity.updatefunction.It'slikewe'reteleportingitontopofwhateverspacewewantwithakeystroke,ignoringallphysicsandcollision.Thisissimplertocodeandgetsaroundthefactthepaddle'sstaticbodywon'trespondtoforce.Tofixthis,wecanartificiallysettheboundaryonthepaddlebycheckingifitisoutofboundsbeforemovingit.

--entities/paddle.lua

entity.update=function(self)

--Don'tmoveifbothkeysarepressed.Justreturn

--insteadofgoingthroughtherestofthefunction.

ifinput.leftandinput.rightthen

return

end

localself_x,self_y=self.body:getPosition()

ifinput.leftthen

localnew_x=math.max(self_x-10,108)

self.body:setPosition(new_x,self_y)

elseifinput.rightthen

localnew_x=math.min(self_x+10,700)

self.body:setPosition(new_x,self_y)

end

end

Calling math.maxmeanswewillsetthenewx-positiontoeither self_x-10or 100,whichevernumberisbigger.Thispreventsusfromgettinganumbersosmallitrunsofftoofartotheleft. math.mindoestheoppositeandtakescareoftherightsideofthescreen.

Oneissueyoumayormaynotnoticeismovementisn'talwaysauniformspeed,anddependingonthespeedofyourcomputerthepaddlemayappeartogofasterorslower.Rememberthearticleondeltatime?Weneedtoscalethedistancetravelledtomatchtheamountoftimethathaspassed.Conveniently,wearegettingthedeltatimefromlove.updatealready.Takeacloserlookatit:

--main.lua

2.13-Breakout(part3)

97

Page 98: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

love.update=function(dt)

ifnotinput.pausedthen

for_,entityinipairs(entities)do

--Deltatimeisbeingpassed

--totheentity.updatefunctionhere

--|

--|

--V

ifentity.updatethenentity:update(dt)end

end

world:update(dt)

end

end

Whichmeanswecandothis:

--entities/paddle.lua

entity.update=function(self,dt)

--Don'tmoveifbothkeysarepressed.Justreturn

--insteadofgoingthroughtherestofthefunction.

ifinput.leftandinput.rightthen

return

end

localself_x,self_y=self.body:getPosition()

ifinput.leftthen

localnew_x=math.max(self_x-(400*dt),100)

self.body:setPosition(new_x,self_y)

elseifinput.rightthen

localnew_x=math.min(self_x+(400*dt),700)

self.body:setPosition(new_x,self_y)

end

end

Thenumber 400isarbitraryandcanbewhateverspeedyouwantthepaddletomoveat. dtisasmallnumbersoitneedstobemultipliedbyalargenumberlike400tomatchaspeedsimilartowhatwewereseeingbeforewhenwesimplywereaddingandsubtracting 10.

Ifyoumissedanythingorarehavingissues,here'sacopyofthecompletedsourcecodeforthissection:https://github.com/RVAGameJams/learn2love/tree/master/code/breakout-3

Inthenextsectionwewillworkonthephysicsmoretogivetheballmovementamorerealisticfeel.Wewillalsoimplementtheabilitytodestroybricksusingtheworldcollisioncallbacks.

ExercisesDespitehavingarestitutionof1,theballislosingmomentumasitcollideswithotherobjects.Thisisduetofriction.Howcanthatbefixed?Whenthegameispaused,makeitdisplaytextonthescreensotheplayerknowsthegameisn'tjustfrozen.Hint:you'llneedoneofthedrawfunctionsfromlove.graphicstoprintthetext.

Theanswerstotheseexerciseswillbeinthenextsection'ssourcecode.

2.13-Breakout(part3)

98

Page 99: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Breakout(part4):physics

ReviewInthepreviousexerciseswediscussedissueswiththeballslowingdownduetofriction.Withabitofbrowsingthroughthe love.physicsdocumentationyoumighthaveseenthatfrictionisapropertyofthefixtureandcanbesetto0in fixture:setFriction.

Howaboutcreatingthepausescreentext?Wereyouabletodoitwithouttouchingmain.lua?Takealookatthisentitythatwascreatedjustforthesingleresponsibilityofdisplayingpausetext:

--entities/pause-text.lua

localinput=require('input')

returnfunction()

localwindow_width,window_height=love.window.getMode()

localentity={}

entity.draw=function(self)

ifinput.pausedthen

love.graphics.print(

{{0.2,1,0.2,1},'PAUSED'},

math.floor(window_width/2)-54,

math.floor(window_height/2),

0,

2,

2

)

end

end

returnentity

end

That'sright.Eventhepausescreenisanentity.Thefirstnaturalplacetothinktoputitwouldbethemainfilebutentityfilesareperfectbecausewecancreateasmanyasweneedforeachtaskandaddittoentities.luawhereitwillbehandledbythegameloop.Forcenteringthetextthe love.window.getModefunctionisusedtogetthefullwindowdimensionsthenthosenumbersaredividedinhalf.Thissavesusfrommanuallycodinginnumbersthatwouldneedtobereadjustedifthewindowsizechanged.Additionally, math.floorwasusedforgoodmeasuretomakesurewearereturningawholenumber.Itisrecommendedtorounddecimalsofffromnumberswhenpassingcoordinatestothedrawingfunctions.Otherwiseitmayattempttodrawthatobjectbetweenpixelsonthescreenandcausesomeblurriness.

PhysicsupdatesAnissuewehadwiththegamephysicssincewegotthepaddlemovingisthattheballdoesn'talwaysricochetoffthepaddleasyouwouldexpect.Thisisbecausewemadethepaddlestaticsotheballdoesn'tpushitaround,butthishastheeffectofthepaddlenotinteractingwiththeballcorrectly.Thisiswhere kinematicbodiescomein.Kinematicbodies,likestaticbodiesaren'taffectedbydynamicbodies.Kinematicbodies,unlikestaticbodies,canaffectdynamicbodies.

We'regoingtomake3changestopaddle.lua:

2.14-Breakout(part4)

99

Page 100: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Movetheboundarydimensions,paddledimensions,andpaddlespeedtoeasily-referencedvariablesatthetopofthefile.ChangethebodytypetokinematicOverhaultheupdatecodetomovethebodywithlinearvelocityratherthanmanuallysettinganewlocationonthescreenwitheveryupdate

--entities/paddle.lua

localinput=require('input')

localworld=require('world')

returnfunction(pos_x,pos_y)

localwindow_width=love.window.getMode()

--Variablestomaketheseeasiertoadjust

localentity_width=120

localentity_height=20

localentity_speed=600

--Thelimitofhowfarleft/righttheentitycanmovetowards

--theedges(withalittlebitofpaddingthrownon).

localleft_boundary=(entity_width/2)+2

localright_boundary=window_width-(entity_width/2)-2

localentity={}

entity.body=love.physics.newBody(world,pos_x,pos_y,'kinematic')

entity.shape=love.physics.newRectangleShape(entity_width,entity_height)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

entity.draw=function(self)

love.graphics.polygon('line',self.body:getWorldPoints(self.shape:getPoints()))

end

entity.update=function(self)

--Don'tmoveifbothkeysarepressed.Justreturn

--insteadofgoingthroughtherestofthefunction.

ifinput.leftandinput.rightthen

return

end

localself_x=self.body:getX()

ifinput.leftandself_x>left_boundarythen

self.body:setLinearVelocity(-entity_speed,0)

elseifinput.rightandself_x<right_boundarythen

self.body:setLinearVelocity(entity_speed,0)

else

self.body:setLinearVelocity(0,0)

end

end

returnentity

end

Itookthelibertyofadjustingthepaddlesize,butwithourniceboundary-sizecalculationsinplacethepaddledimensionscaneasilybeadjustedandtheboundarysizewilltakethosechangesintoaccount.Let'sdrillintotheentity.updatefunction.

Oncetheinputsarecheckedtobetrueorfalsethecurrentx-positionofthepaddleischeckedtoseeifitgoesoutoftheboundaries(calculatednearthetop).Noticethatthecalculationsfortheboundarylocationsaredoneatthetopinsteadofin entity.update.Thismeansthosecalculationsaren'tdoneoneveryupdatesincetheydon'tneedtobe.

Abitmorecomplexthanthepaddlearethecalculationsfortheball:

--entities/ball.lua

localworld=require('world')

2.14-Breakout(part4)

100

Page 101: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

returnfunction(x_pos,y_pos)

localentity_max_speed=880

localentity={}

entity.body=love.physics.newBody(world,x_pos,y_pos,'dynamic')

entity.body:setLinearVelocity(300,300)

entity.shape=love.physics.newCircleShape(0,0,10)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setFriction(0)

entity.fixture:setRestitution(1)

entity.fixture:setUserData(entity)

entity.draw=function(self)

localself_x,self_y=self.body:getWorldCenter()

love.graphics.circle('fill',self_x,self_y,self.shape:getRadius())

end

entity.update=function(self)

localvel_x,vel_y=self.body:getLinearVelocity()

localspeed=math.abs(vel_x)+math.abs(vel_y)

localvel_x_is_critical=math.abs(vel_x)>entity_max_speed*2

localvel_y_is_critical=math.abs(vel_y)>entity_max_speed*2

--Ballisbouncingtoofasttoreasonablyhit.

--Cutdownitsspeedby75%ifso.

ifvel_x_is_criticalorvel_y_is_criticalthen

self.body:setLinearVelocity(vel_x*.75,vel_y*.75)

end

ifspeed>entity_max_speedthen

self.body:setLinearDamping(0.1)

else

self.body:setLinearDamping(0)

end

end

returnentity

end

Inthefirstchunkwegetthecurrentxandyvelocity,whichtellsusthexandydirectionoftheball:

localvel_x,vel_y=self.body:getLinearVelocity()

localspeed=math.abs(vel_x)+math.abs(vel_y)

Anexample vel_x/ vel_ymaybe 212/ -300,whichmeanstheballismovingupandtowardstheright.Thespeediscalculatedbyturningboththesenumbersintoabsolutenumbersandaddingthemtogether(so 512intheexample).

Inthenextchunkthereisasafetychecktomakesuretheballdidn'tricochetwithsomuchforcethatit'sgoingtoofasttopossiblyhit.Ifeitherbooleanvariableistruethenthelinearvelocityismultipliedbyafractionofitselftoquicklyslowitdown:

localvel_x_is_critical=math.abs(vel_x)>entity_max_speed*2

localvel_y_is_critical=math.abs(vel_y)>entity_max_speed*2

--Ballisbouncingtoofasttoreasonablyhit.

--Cutdownitsspeedby75%ifso.

ifvel_x_is_criticalorvel_y_is_criticalthen

self.body:setLinearVelocity(vel_x*.75,vel_y*.75)

end

Nowthereisjustachecktoeasetheballbackdowntoacomfortablemaximumspeed.Iftheball'sspeedisgreaterthan entity_max_speedadampingisappliedwhichwillreducetheballsspeedbelow880.Oncethetargetspeedisreachedthenthedampingswitchesbackto0:

2.14-Breakout(part4)

101

Page 102: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

ifspeed>entity_max_speedthen

self.body:setLinearDamping(0.1)

else

self.body:setLinearDamping(0)

end

Tryoutthechangestofeelitinactioncomparedtothepreviousphysicsandhopefullyyouwillfindthatit'sanimprovement.It'snotaperfectreplicaofthearcadegame,butplayingaroundwiththesetricksandfeaturesyoucangetitprettydarnclosetosomethingsatisfactory.Anotherthingtotryoutifwithintheball's entity.update,addalineunderthespeedvariablethatreads print(speed)andwatchthenumberincreaseanddecreaseagainasthedampingkicksin.Prettyneatthatmostoftheheavycalculationsarehandledbythephysicsengineforus.

CollisionThereare4changesinvolvedtomakethebricksdestructible:

Updateworld.luatocheckforcollisionfunctionalityfortheentitieswhentheycollideUpdatebrick.luatoincludeacollisioncallbackAddanewattributeonthebrickentitytoletusknowitscurrentconditionandifitneedstobedestroyed.We'lljustcallit entity.health.Updatemain.luatoremove/destroyanyentitiesthathavenomorehealth

Firsttheworld:

--world.lua

--Calledattheendofanycontactintheworld.Parameters:

--{fixture}fixture_a-firstfixtureobjectinthecollision.

--{fixture}fixture_b-secondfixtureobjectinthecollision.

--{contact}contact-worldobjectcreatedonandatthepointofcontact

--Seefurther:https://love2d.org/wiki/Contact

localend_contact_callback=function(fixture_a,fixture_b,contact)

localentity_a=fixture_a:getUserData()

localentity_b=fixture_b:getUserData()

ifentity_a.end_contactthenentity_a:end_contact()end

ifentity_b.end_contactthenentity_b:end_contact()end

end

localworld=love.physics.newWorld(0,0)

world:setCallbacks(nil,end_contact_callback,nil,nil)

returnworld

Theonlycallbackwe'llbeusingforthistutorialistheend-contactcallback,sofor world:setCallbackswearegoingtoreturning nilfortheresttokeepourcodefastandclean.Takealookatwhatishappeninginsideend_contact_callback.Rememberinsideeachentitywhenweinvoked entity.fixture:setUserData(entity)?Withtheentityattachedtoeachfixture,wecangetaccesstothoseentitiesbyinvoking fixture:getUserDatainthecallbackabove.Oncewehaveaccesstoeachentity,wechecktoseeiftheentityhasany end_contactfunctions,codespecifictothatentitythatneedstorunwhenendingthecollision.

Nowwecangotobrick.luaanddefinethatfunctionality:

--entities/brick.lua

localworld=require('world')

returnfunction(x_pos,y_pos)

2.14-Breakout(part4)

102

Page 103: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localentity={}

entity.body=love.physics.newBody(world,x_pos,y_pos,'static')

entity.shape=love.physics.newRectangleShape(50,20)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

--Howmanytimesthebrickcanbehitbeforeitisdestroyed

entity.health=2

entity.draw=function(self)

love.graphics.polygon('fill',self.body:getWorldPoints(self.shape:getPoints()))

end

entity.end_contact=function(self)

self.health=self.health-1

end

returnentity

end

Noticethetwonewvaluesinthetable, entity.healthand entity.end_contact.Inside end_contactwearesubtracting1healthwhenthecollisionends.Healthcouldstartatanynumberandthatmeanstheballwillneedtocollidewiththebrickthatmanytimesbeforethehealthreaches0.Lastly,weneedtogointomain.luaandadjustlove.updatesoitdoessomethingwhenitseesanentitywith0health:

--main.lua

love.update=function(dt)

ifnotinput.pausedthen

localindex=1

whileindex<=#entitiesdo

localentity=entities[index]

ifentity.updatethenentity:update(dt)end

--Whenanentityhasnohealth(brickhasbeenhitenoughtimes

--thenweremoveitfromthelistofentities.Don'tincrement

--theindexnumberifdoingthatthoughbecausewehaveshrunk

--thetableandmadealltheitemsshiftdownby1intheindex.

ifentity.health==0then

table.remove(entities,index)

entity.fixture:destroy()

else

index=index+1

end

end

world:update(dt)

end

end

Theentityisremovedfrom entitiesaswellashavingitsfixturedestroyedfromtheworld.Thiswillonlyhappentobrickswith0health.Itwon'thappentoentitieswherewedidn'tdefinehealthbecause nilisnotthesamethingas0.Noticethata whileloopwasusedhere.Thisisbecausewemayremoveentitiesfromthelistweareloopingoverandthiswouldthrowofftheindexcountforaregular forloop.

Ifyoumissedanythingorarehavingissues,here'sacopyofthecompletedsourcecodeforthissection:https://github.com/RVAGameJams/learn2love/tree/master/code/breakout-4

Inthenextsectionwe'llreviewthechecklistandseewhatislefttocover.

ExercisesItwouldbegreatifthecolorsofthebrickschangeddependinghowmuchhealththebrickhas.Updatethebrick'sentity.drawfunctionwithsomecolors.Hint:wecoveredcolorsin2.4-Gameloop.

2.14-Breakout(part4)

103

Page 104: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Addmorebrickstothescreen.What'stheeasiestwaytodothat?

2.14-Breakout(part4)

104

Page 105: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Breakout(part5):gamestate

ReviewWe'vegottenabitdonesolet'slookatthebasicrequirementsagain:

Theobjectiveofthegameistodestroyallthebricksonthescreen✔Theplayercontrolsa"paddle"entitythathitsaball✔Theballdestroysthebricks✔TheballneedstostaywithintheboundariesofthescreenIftheballtouchesthebottomofthescreen,thegameends

Inthepreviousexercisethequestionwasbroughtupwhatwouldbetheeasiestwaytodrawabunchofbricksacrossthescreen.Asimple,butverytediousanswertothatwouldbetopositionthebricksoneatatimeinentities.lualikeso:

brick(40,80),

brick(100,140)

--andsoon...

Ifyouwanttomakeyourbricksintoashapeorsculpturethenthatmightbethebestapproach.Ifyoujustwanttoarrangeyourbricksintoagrid,thentheeasiestwaywouldbetowriteanumericfor-loop.

--entities.lua

localboundary_bottom=require('entities/boundary-bottom')

localboundary_vertical=require('entities/boundary-vertical')

localboundary_top=require('entities/boundary-top')

localpaddle=require('entities/paddle')

localpause_text=require('entities/pause-text')

localball=require('entities/ball')

localbrick=require('entities/brick')

localentities={

boundary_bottom(400,606),

boundary_vertical(-6,300),

boundary_vertical(806,300),

boundary_top(400,-6),

paddle(300,500),

pause_text(),

ball(200,200)

}

localrow_width=love.window.getMode()-20

fornumber=0,38do

localbrick_x=((number*60)%row_width)+40

localbrick_y=(math.floor((number*60)/row_width)*40)+80

entities[#entities+1]=brick(brick_x,brick_y)

end

returnentities

Okthisadmittedlylooksmorecomplicatedatfirst,butifyourememberthearithmeticandordersofoperationcoveredin1.1-Interactivecodingstatementsareprocessedfromtheinnerparenthesisandworkedoutwards.Sowhythelongcalculation?Let'sstartoffwithasimplercalculation:

localbrick_x=number*60

2.15-Breakout(part5)

105

Page 106: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Startingwiththe number0upto38,therewillbe39loopsandtherefore39bricksdrawn.Onthefirstloop, numberis0.Sincethebricksare50pixelswidethiswoulddrawthebrickswitha10pixelspacebetweeneach.Firstbrickat60,then120,then180...Ok,butthenafteronlyadozenbrickswewouldstartrunningoffthescreen.Thisiswherethemoduluscomesinhandy:

localbrick_x=(number*60)%row_width

row_widthishowwidewewantarowofbrickstobebe.Inthiscase row_widthisthescreenwidth,800pixels,subtract20pixelsforpadding.Sodrawthebricksevery60pixels,butthenwhenyougetto780pixels,startbackat0pixelsandbegindrawinganewrow.Thanksmodulus!Nowjusttogivethebrickssomespacingontheleftsideawayfromthewall,wecangoaheadandadd40pixelstothefinalresultforthex-position:

localbrick_x=((number*60)%row_width)+40

Thebrick'sy-positioniscalculatedalittlebitdifferently.Whatweneedtofindoutiswhichrowwe'reonsoweknowwhereonthey-axistodraw.Ifwetakethe numberandmultiplyitby60thendoamodulusweknowthatgivesusthex-position.Solet'stakethatchunkofcodefromaboveandmakethatthebasisofoury-positioncalculation:

localbrick_y=(number*60)%row_width

Ratherthanusingmodulus,ifweuseregulardivisionwegetasmallremaindereverytime (number*60)exceedstherowwidth:

localbrick_y=(number*60)/row_width

Thiswillgiveusanumberwithdecimalssotokeepthingsroundedwecanuse math.floortosnapthey-positiondowntothenearestwholenumber:

localbrick_y=math.floor((number*60)/row_width)

Great!Noweverytimethex-positionexceedstherowwidth,wegetbackthenumberoftherowwe'reon...0forthefirst,1forthesecond,2andsoon.Withthisnumberwecannowspaceouteachrowby40pixels:

localbrick_y=math.floor((number*60)/row_width)*40

Thenfinallyjusttoshiftthebricksalittlefurtherdownthescreenwegiveitapaddingthatlooksright,say80:

localbrick_y=(math.floor((number*60)/row_width)*40)+80

Andthereyougo.Theentitycanjustbeaddedtotheendoftheentitieslistsoitdoesn'tgetlost:

entities[#entities+1]=brick(brick_x,brick_y)

Inthepreviousexerciseswealsotalkedaboutdrawingthebricksdifferentcolorstoindicatetheirintegrity/healthleftbeforetheywillbedestroyed.Ratherthanreviewthatnow,let'sdiveintostatemanagementandwe'llwrapcoloringupalongtheway.

Statemanagement

2.15-Breakout(part5)

106

Page 107: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Youraverage,every-dayprogramhasalotofinformationitneedstostoryinmemory.Forourgametofunctionwithjustthebasicfeatures,weneedtostoreinformationabouteachentity,whetherornotthegameiscurrentlypaused,orifthegameiswonorlost.Thisinformationiscalledthestate.Thestateisdatathatmaychangeduringthelifetimeoftheapplication.Thinkofthestateofyourlightsinyourroom.Aretheycurrentlyinan"on"or"off"state?Thestatecancausedifferenteffectsontheapplication,likeifthe"pause"stateofthegameis"true"thentheworldwillnolongerreceiveupdates.

Onethingwemustthinkofishowtoorganizethestateofourapplication.Thisissomethingwetakeforgrantedoftenintherealworld;Wedon'thavetofigureoutwheretostorethestateofourlights.It'sapieceofinformationintrinsictothelamp'sdesign.

Sowhydowehavetocaresomuchaboutourgame'sstate?Tobefair,ourgameissmallsoweprobablydon'tneedto.However,itiscrucialtoreconcilesuchthingswhileapplicationsaresmallbecauseitwillbeverydifficulttogobackandfixabunchofcodeoncetheapplicationisbig.Thewayyoushouldorganizethestateofyourapplicationshouldaccomplishafewthings:

Itshouldbeeasytofindandaccessthenecessarydatathatmakesupthestate.Forinstance,howeasyisitforourmainfiletoaccesstheentitiesandloopovertheminthe love.updatefunction?Thereshouldonlybeonecopyofthestate.Ifwewanttoaccessthe"paused"stateofourgameinmultipleplacesthatisfine,butweshouldn'thavemultiple"paused"variablesfloatingaroundourgame.Ifwehada"paused"variableinsideanentityfileandanotherinsidetheinputserviceupdatingindependentlythentheycouldgetoutofsyncandourgamewouldgetconfusedonwhenitshouldbepaused.Thestateshouldonlybeaccessedwhereitisneeded.Ifyouwereaccessingorstoringthe"paused"stateinsidetheballentity,thenifthatballwasdestroyedthensomethingbadwillhappenthenexttimethegamecheckstoseeifitispaused.

Whatfilescontainthestateofourgame?

entities.lua-Eachentitytableisresponsibleforitsownstate.Forinstance,eachbrickstoresthestateofitsownhealth.Alltheentitiestablesaregeneratedandstoredhere.Theentitiesarenotstoredintheentitiesfolder.Thosearejustfunctionsusedtogeneratetheentities.Theblueprints.input.lua-Thisfileisresponsibleforcapturinguserinput,butalsostoringthestateofwhatkeysarecurrentlybeingpressed.world.lua-Thisfileisnotonlytheblueprintsforthegameworld,butitalsostorestheworldinstancethatisgeneratedwhenthegamestarts.Wemadetheworldinstanceeasilyaccessibletotherestoftheapplicationbywriting returnworldattheend.Therewouldbenogameifthiswasn'teasilyaccessible.

Afewpiecesofgamestateweneedtoaddareabooleanofwhetherthegameisover,anotherforifthestageiscleared,andalsoalistofcolorstouseinourgamewhichwe'llrefertoasourpalette.Thisinformationwouldn'treallyfitinanyoftheplaceswelistedabove,andwedon'twanttoaddittomain.luabecauseofourfirstrulethatthegamestateshouldbeeasytoaccesswhereitisneeded.Besides,that'snotthemainfile'sresponsibility.We'llgoaheadandjustmakeanewfilecalledstate.luaandstoretheoverallgamestateinthisfile.Thisisalsoalittlematterofopinionbutthe"paused"andbuttonstateswe'llalsomoveinheresincetheyaffecttheoverallgame'sstate.Thiswillalsomakeitsothatinput.lua'sonlyresponsibilityistocaptureandtranslatetheuserinput,nottohandleanystatewhatsoever.

--state.lua

--Thestateofthegame.Thiswayourdataisseparatefromourfunctionality.

return{

button_left=false,

button_right=false,

game_over=false,

palette={

{1.0,0.0,0.0,1.0},--red

2.15-Breakout(part5)

107

Page 108: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

{0.0,1.0,0.0,1.0},--green

{0.4,0.4,1.0,1.0},--blue

{0.9,1.0,0.2,1.0},--yellow

{1.0,1.0,1.0,1.0}--white

},

paused=false,

stage_cleared=false

}

It'skindofanicefeelingtokeepallthestatetogether.Wecouldevenmovetheentitieslistintostate.luaandgetridofentities.lua,butthisdoesn'tseemnecessary.Nowwiththisshiftindataweneedtoupdateinput.luaandmain.luatoreferencethenewfile:

--input.lua

localstate=require('state')

--Mapspecificuserinputstogamestates

localpress_functions={

left=function()

state.button_left=true

end,

right=function()

state.button_right=true

end,

escape=function()

love.event.quit()

end,

space=function()

ifstate.game_overorstate.stage_clearedthen

return

end

state.paused=notstate.paused

end

}

localrelease_functions={

left=function()

state.button_left=false

end,

right=function()

state.button_right=false

end

}

--Thistableistheserviceandwillcontainsomefunctions

--thatcanbeaccessedfromentitiesorthemain.lua.

return{

--Lookupinthemapforactionsthatcorrespondtospecifickeypresses

press=function(pressed_key)

ifpress_functions[pressed_key]then

press_functions[pressed_key]()

end

end,

--Lookupinthemapforactionsthatcorrespondtospecifickeyreleases

release=function(released_key)

ifrelease_functions[released_key]then

release_functions[released_key]()

end

end,

--Handlewindowfocusing/unfocusing

toggle_focus=function(focused)

ifnotfocusedthen

state.paused=true

end

end

2.15-Breakout(part5)

108

Page 109: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

}

--main.lua

localentities=require('entities')

localinput=require('input')

localstate=require('state')

localworld=require('world')

love.draw=function()

for_,entityinipairs(entities)do

ifentity.drawthenentity:draw()end

end

end

love.focus=function(focused)

input.toggle_focus(focused)

end

love.keypressed=function(pressed_key)

input.press(pressed_key)

end

love.keyreleased=function(released_key)

input.release(released_key)

end

love.update=function(dt)

ifstate.game_overorstate.pausedorstate.stage_clearedthen

return

end

localindex=1

whileindex<=#entitiesdo

localentity=entities[index]

ifentity.updatethenentity:update(dt)end

--Whenanentityhasnohealth(brickhasbeenhitenoughtimes

--thenweremoveitfromthelistofentities.Don'tincrement

--theindexnumberifdoingthatthoughbecausewehaveshrunk

--thetableandmadealltheitemsshiftdownby1intheindex.

ifentity.healthandentity.health<1then

table.remove(entities,index)

entity.fixture:destroy()

else

index=index+1

end

end

world:update(dt)

end

Noticethechangeto love.update.Wecheckif state.game_over, state.pausedor state.stage_clearedistrueandifso,wereturnfrom love.updatewithoutdoinganyoftheupdatesasthesekindofgamestatesmeritfreezingthescreen.

Nextup,updatepaddle.luatorequire stateinsteadof input.The entity.updatefunctionnowneedstoreferencestate.button_leftand state.button_righttotelliftheplayerhaspressedanybuttons.Tryupdatingitonyourown.Ifyoudogetstuck,thesourcecodewillbeinthelinkatthebottomwaitingforyou.

Ok,nowthatwehaveastatewherewestoredthecolorsitisprobablyagoodtimetotryandupdatebrick.lua.Firstlet'slookatthosecolorsstoredinstate.lua:

palette={

{1.0,0.0,0.0,1.0},--red

2.15-Breakout(part5)

109

Page 110: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

{0.0,1.0,0.0,1.0},--green

{0.4,0.4,1.0,1.0},--blue

{0.9,1.0,0.2,1.0},--yellow

{1.0,1.0,1.0,1.0}--white

},

The palettetableisalistofmoretables.Eachtableinthelistrepresentscolorswherethefirstnumberistheamountofred,2ndtheamountofgreen,3rdtheamountofblue,and4thnumbertheamountofopacity.Settingthelastnumberto 0meansthecoloris100%transparentand 1meansitiscompletelyopaque.Allofthesevaluesmixtogethertoformasinglecolor.Inthecaseofthefirstcolor,wehavetheredvaluesettomaximumopaqueredwithnoothercolorsmixedin.Iwouldencourageyoutogobackandeditthecolorsinthispaletteaftereverythingisworking.Now,insidebrick.lualet'supdate entity.draw:

--entities/brick.lua

localstate=require('state')

localworld=require('world')

returnfunction(x_pos,y_pos)

localentity={}

entity.body=love.physics.newBody(world,x_pos,y_pos,'static')

entity.shape=love.physics.newRectangleShape(50,20)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

--Howmanytimesthebrickcanbehitbeforeitisdestroyed

entity.health=2

--Usedtocheckduringupdateifthisentityisabrick

--Ifnobricksarefoundthenthelevelwascleared

entity.type='brick'

entity.draw=function(self)

--Drawthebrickinadifferentcolordependingonhealth

love.graphics.setColor(state.palette[self.health]orstate.palette[5])

love.graphics.polygon('fill',self.body:getWorldPoints(self.shape:getPoints()))

--Resetgraphicsdrawerbacktothedefaultcolor(white)

love.graphics.setColor(state.palette[5])

end

entity.end_contact=function(self)

self.health=self.health-1

end

returnentity

end

Beforedrawingthebrick'spolygon,wesetthegraphicsrenderertouseoneofthecolorsfrom state.palette.Thecolortousedependsonwhatthebrick'shealthis.Soifthebrickhas2healththen state.palette[self.health]willbecome state.palette[2]whichwillgrabthe2ndcolorinthelist...green.Ifthebrick'shealthwas1,thenthefirstcolorfromthepalettewouldbeselected...red.Afterthecoloredpolygonisdrawn, entity.drawfinishesupbysettingtherenderercolorbacktowhite.Ifwedidn'tdothisstep,theballandpaddlewouldgetdrawnthesamecolorasthebricks.

Onelastthingweneedtodotogetthegameworkingisupdatepause-text.luaasitisincorrectlylookingforthe"pause"stateininput.luainsteadofthenewstate.lualocation:

--entities/pause-text.lua

localstate=require('state')

returnfunction()

localwindow_width,window_height=love.window.getMode()

2.15-Breakout(part5)

110

Page 111: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localentity={}

entity.draw=function(self)

ifstate.pausedthen

love.graphics.print(

{state.palette[3],'PAUSED'},

math.floor(window_width/2)-54,

math.floor(window_height/2),

0,

2,

2

)

end

end

returnentity

end

FinaltouchesWeneedthegametoendwhentheplayerdestroysallthebricksorlosestheball.Justlikethepause-textentity,displaysomemessagesbasedonthegamestate.

--entities/game-over-text.lua

localstate=require('state')

returnfunction()

localwindow_width,window_height=love.window.getMode()

localentity={}

entity.draw=function(self)

ifstate.game_overthen

love.graphics.print(

{state.palette[5],'GAMEOVER'},

math.floor(window_width/2)-100,

math.floor(window_height/2),

0,

2,

2

)

end

end

returnentity

end

--entities/stage-clear-text.lua

localstate=require('state')

returnfunction()

localwindow_width,window_height=love.window.getMode()

localentity={}

entity.draw=function(self)

ifstate.stage_clearedthen

love.graphics.print(

{state.palette[4],'STAGECLEARED'},

math.floor(window_width/2)-110,

math.floor(window_height/2),

2.15-Breakout(part5)

111

Page 112: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

0,

2,

2

)

end

end

returnentity

end

Totriggerthe"GAMEOVER"textiseasyenough.Weneedtoaddacollisioncallbacktoboundary-bottom.luatosetthegame's state.game_overtotrueonanycollision:

--entities/boundary-bottom.lua

localstate=require('state')

localworld=require('world')

returnfunction(x_pos,y_pos)

localentity={}

entity.body=love.physics.newBody(world,x_pos,y_pos,'static')

entity.shape=love.physics.newRectangleShape(800,10)

entity.fixture=love.physics.newFixture(entity.body,entity.shape)

entity.fixture:setUserData(entity)

entity.end_contact=function(self)

state.game_over=true

end

returnentity

end

Don'tforgetweneedtoupdateentities.luatoaddourtwonewentities:

--entities.lua

localboundary_bottom=require('entities/boundary-bottom')

localboundary_vertical=require('entities/boundary-vertical')

localboundary_top=require('entities/boundary-top')

localpaddle=require('entities/paddle')

localgame_over_text=require('entities/game-over-text')

localpause_text=require('entities/pause-text')

localstage_clear_text=require('entities/stage-clear-text')

localball=require('entities/ball')

localbrick=require('entities/brick')

localentities={

boundary_bottom(400,606),

boundary_vertical(-6,300),

boundary_vertical(806,300),

boundary_top(400,-6),

paddle(300,500),

game_over_text(),

pause_text(),

stage_clear_text(),

ball(200,200)

}

localrow_width=love.window.getMode()-20

fornumber=0,38do

localbrick_x=((number*60)%row_width)+40

localbrick_y=(math.floor((number*60)/row_width)*40)+80

entities[#entities+1]=brick(brick_x,brick_y)

end

2.15-Breakout(part5)

112

Page 113: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

returnentities

Ok,testthatoutandcheckthatthe"GAMEOVER"textworks.Ifitdoes,thenlet'scontinueonandaddtheconditionsforhowtowinthegame.Thisinvolvescheckingthroughalltheentitiesin love.updatetomakesurewestillhavebricks.Ifwedon'thaveanybricksleft,thentheplayerdestroyedthemallandthestageiscleared.

--main.lua

love.update=function(dt)

ifstate.game_overorstate.pausedorstate.stage_clearedthen

return

end

--Switchtotrueifwehavebricksleft

localhave_bricks=false

localindex=1

whileindex<=#entitiesdo

localentity=entities[index]

ifentity.type=='brick'thenhave_bricks=trueend

ifentity.updatethenentity:update(dt)end

--Whenanentityhasnohealth(brickhasbeenhitenoughtimes

--thenweremoveitfromthelistofentities.Don'tincrement

--theindexnumberifdoingthatthoughbecausewehaveshrunk

--thetableandmadealltheitemsshiftdownby1intheindex.

ifentity.healthandentity.health<1then

table.remove(entities,index)

entity.fixture:destroy()

else

index=index+1

end

end

--Flagthestageclearediftherearenomorebricks

state.stage_cleared=nothave_bricks

world:update(dt)

end

Everytime love.updateisran,wesetavariable have_brickstofalse.Ifthisbooleanstays falseallthewaytothebottomofthefunctionthen state.stage_clearedgetsswitchedtotrueandthegameiswon.Insidethe whileloop,however,wecheckeveryentitytoseeifwefindan entity.typeof 'bricks'andifso, have_bricksgetsflippedtotruetostopthegamefrombeingwonyet.

Sothataboutdoesitforcompletingourchecklist.Thegamemaynotbeasfeature-completeasatruebreakoutgame,butthatroomforimprovementleavesopportunityforyoutomodifythegametoworkhowyouwantitto.It'sreallyuptoyourimagination.Tryoutafewexercisesifyoucan'tthinkupanynewfeatures.Ifyouarehavingtroublerunningthegame,besuretocheckoutthesourcecode:

https://github.com/RVAGameJams/learn2love/tree/master/code/breakout-5

ExercisesInsteadofgettingagameoverassoonastheballtouchesthegroundonce,addanewpropertyinstate.luanamed livesandsetittoasmanylivesasyouwanttheplayertohave.Makeissothe state.livesdecreaseswhentheballhitsthegroundandmakethe game_overnottriggerunless state.lives<1.TrysettingthepaddletodifferentshapetomakethegameplaydifferentlyComeupwithnewfeaturestomakethegameplaybetterandfeelmorepolished

ChangetheballandpaddlecolorsAddabackgroundcolor

2.15-Breakout(part5)

113

Page 114: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

FigureouthowtoplayasoundeffectwhentheballcollideswiththingsCreatesomekindofpower-upentity

2.15-Breakout(part5)

114

Page 115: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

BinaryandbitmasksIn2.10-Collisioncallbackswesawhowtoreacttoentitiescolliding.Inthissectionwe'regoingtodiscusshowwecanbettercontrolwhencollisionshappen.Asthetitlesuggests,thiswillinvolveunderstandingsomebinary.

Let'ssaywehaveabeat-em-upgamewheretwoplayersarefightingbadguysandwedon'twantplayerstocollidewitheachotherandinsteadonlycollidewithenemies.Thecollisioncallbackcouldlooksomethinglikethis:

localbegin_contact_callback=function(fixture_a,fixture_b)

localentity_a_type=fixture_a:getUserData()

localentity_b_type=fixture_b:getUserData()

--Checkthesearen'tthesametypeofentity

ifentity_a_type~=entity_b_typethen

--Somecodetohandlethecollisiongoeshere...

end

end

Butwhatifyouhadpower-upsandyouwantplayerstocollidewiththepower-upsbutyoudon'twantenemiestouchingthepower-ups?Thingscangetcomplicatedprettyquickly:

localbegin_contact_callback=function(fixture_a,fixture_b)

locala=fixture_a:getUserData()

localb=fixture_b:getUserData()

if(a=='powerup'andb=='player')or(a=='player'andb=='powerup')then

--Somepower-upcode...

--Don'tletpower-upscollidewithotherentitytypeslikebadguys

elseifa~=banda~='powerup'andb~='powerup'then

--Codetohandletherestofthecollisions...

end

end

Let'sfindabetterway!

BinaryoperationsBackin1.0-Programmingbasicswediscussedoperations–howtooperateonstringswithequality( ==)checks,howtooperateonnumberswitharithmeticoperations,andevenhowtoperformbooleanoperationslike andandor.Binarynumbershavetheirownoperations,oftencalledbitwiseoperations.Toperformbinaryoperations,let'sfirstlookathowtorepresentbinarynumbers.Typinganumberlike 101,Luawillinterpretitasadecimalnumber(literallyone-hundredone)soweneedtorepresentitasastringandconvertittoanumber.Toconvertastringtoanumber,youpassinthenumberandthebase(base-2inthiscase)likeso:

print(tonumber('101',2))

Whichconvertsthebinarynumber 101todecimalwhenitprintsout:

5

Forcountinginbinaryandlearninghowtoreadandconvertbetweenbinaryanddecimal,therearemanyresourcesthatalreadyexplainitinmuchbetter.Learninghowtodotheconversionsisn'tnecessarytolearningthesebasicbinaryoperations,butisanessentialskilltohaveinthefieldofcomputerscience.

2.16-Binaryandbitmasks

115

Page 116: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Forcountinginbinaryandlearninghowtoreadandconvertbetweenbinaryanddecimal,therearemanyresourcesthatalreadyexplainitinmuchbetter.Learninghowtodotheconversionsisn'tnecessarytolearningthesebasicbinaryoperations,butisanessentialskilltohaveinthefieldofcomputerscience.

Movingon,let'stakealookatsomeofthebasicoperations.

AND

Binarynumbersaresimilartobooleansinthatbinaryonlyhas1'sand0's.TheANDoperatoralsoworkssimilarlytotheboolean and.Yougiveittwodigitsandbothmustbe 1( true)fortheoutputtobe 1.

UnfortunatelyatthetimeofwritingthistheonlineREPLhasanoutdatedversionofLuathatdoesn'tsupportbinaryoperations.Noworries,let'screateamain.luafileandtryitoutusingLÖVE.Toperformbinaryoperations,theincluded 'bit'librarymustbeloaded.Whenrequired,itwillreturnatablewithmanyfunctionsinitrelatedtobinaryoperations.Thefirstfunctionwe'lltry, bit.band()performsabinaryANDoperation.

localbit=require('bit')

print(bit.band(0,0))

print(bit.band(0,1))

print(bit.band(1,0))

print(bit.band(1,1))

Thiswillprinttothedebugconsole:

0

0

0

1

Youcanpassitthedecimals 1and 0asthosenumbersarethesameinbinaryanddecimal.

Theoperationisnotlimitedtotwoinputs:

print(bit.band(1,0,1))

Youcanalsopassitmulti-digitnumbers:

print(bit.band(

tonumber('111',2),

tonumber('101',2)

))

Notethatyouneedtoalwaysuse tonumber()toconvertyourbinarystringtoanumberasthefunctionalwaysexpectsadecimalnumber.Likewisetheoutputwillalwaysbeadecimalnumber:

5

Layitoutlikeanarithmetictableandyoucansolveitjustaseasily:

111

101

---

101-->5

11011010

2.16-Binaryandbitmasks

116

Page 117: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

10111100

--------

10011000-->152

ORLikewiththeboolean or,thebinaryORoutputwillbe 1ifeitherthefirstorthesecondnumberis1.Youcouldsayitistheleastpickyoperatorinthatitdoesn'tcareaslongasitgetsa1somewhereatleastonce.

--main.lua

localbit=require('bit')

print(bit.bor(1,1))

print(bit.bor(1,0))

print(bit.bor(0,0))

print(bit.bor(0,0,0,1,0))

1

1

0

1

XOR

Xor(exclusiveor),returns1onlywhenitgetsone1.Let'scompareXORinatabletotheothers:

AND

inputA inputB output

0 0 0

0 1 0

1 0 0

1 1 1

OR

inputA inputB output

0 0 0

0 1 1

1 0 1

1 1 1

XOR

inputA inputB output

0 0 0

0 1 1

1 0 1

1 1 0

2.16-Binaryandbitmasks

117

Page 118: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Binaryoperationsaresomeofthemostfundamentalcomputeroperationsandcanbephysicallybuiltwithafewtransistors.Giventhesimplicityoftheseoperations,italsomakesforafastmethodofcalculatingcollisions.

Bitmasks

Let'stakealookatthissceneforamomentandidentifyfromthecrudelydrawnshapessomepotentialentities:

2.16-Binaryandbitmasks

118

Page 119: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Alltheseentitiesfallintouniquecategoriesinthatwewanteachofthemtocollidewithcertainotherentities.Ifthiswereagame,we'ddefineeachcategorywithauniquebinarydigit,orbit,solet'sfirstdothat:

entity category

sun 0000

player 0001

powerup 0010

enemy 0100

ground 1000

Let'ssetsomerulesforeachoftheseentities.Forinstance,wewanttheplayertocollidewiththepowerup(0010),enemy(0100),andofcoursetheground(1000).Totellthegameenginethis,wecreateabitmaskforthefixture.Thisisabinarynumberwithallthebitsswitchedonthatwewanttheentitytocollidewith.Inotherwords,theplayer'sbitmaskwouldbe(1110).Weleftthefirstbitblanksothattheplayercan'tcollidewithotherpotentialplayers(player2).Let'supdatethetablewiththebitmaskwewanteachentitytohave:

entity category bitmask

sun 0000 0000

player 0001 1110

powerup 0010 1001

enemy 0100 1001

ground 1000 1111

2.16-Binaryandbitmasks

119

Page 120: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Sohowdoesitallcometogetherandwork?Whentwoentity'sfixturescontact,abinaryANDoperationisperformedagaintheentity'sbitmaskandtheotherentity'scategory.Iftheresultingnumberisn't0000thenwehaveacollision.Taketheplayerandenemyforinstance:

0001player'scategory

1001enemy'sbitmask

----

0001wehaveacollision

Andhowabouttheenemyandthepowerup:

0100enemy'scategory

1001powerup'sbitmask

----

0000wehaveNOcollision

Armedwiththisknowledge,wecanassertthefollowinginformationfromthetableabove:

Thesuncollideswithnothing(anddoesn'tevengetacategory).It'sjustinthebackgroundandnon-interactive.Theplayercollideswitheverythingexceptotherplayers(andofcoursethesun).Thepowerupcollidesonlywiththegroundandplayers.Theenemycollidesonlywiththegroundandplayers.Thegroundcollideswitheverything.

Copyordownloadthe"collision"gamefromtheexamplecodeandrunit:

https://github.com/RVAGameJams/learn2love/tree/master/code/collision

Dotheentitiesinteractasexpected?Takealookinsidetheentitiesfoldertoseetheparticularfunctionbeingcalledtoaccomplishapplythecategoriesandbitmaskstoeachentity–Fixture:setFilterData

--square.lua

...

square.category=tonumber('0001',2)

square.mask=tonumber('1110',2)

square.group=0

...

square.fixture:setFilterData(square.category,square.mask,square.group)

Theexamplesaboveonlyuse4bitsforthecategoryandmaskbceausethat'sallweneeded,howeverLÖVEsupportsupto16bitsforthecategoryandbitmask( 0000000000000000).Thegrouppropertyisn'tusedandshouldbesetto0whenitisn't.Wehaven'tmentionedgroupsbeforebecauseifyouknowhowtousecategoriesandbitmasksthenyoudon'tneedtousegroupsascategoriesandbitmasksofferamorepowerfulwayofdoingthesamething.Thatbeingsaid,collisiongroupsshouldberelativelystraight-forwardtolearnaboutsoitwillbeleftupasanexercisetoreadandstudy.

ExercisesPlaywiththebitmasks.Canyoumaketheenemycollidewiththepowerupinsteadoftheplayer?TakealookathowgroupsworkasdescribedinFixture:setGroupIndex.Thisisasimpler,butmorelimitedmethodofdetectingcollision.Canitbeusedtoimitatethecollisionrulesabove?

2.16-Binaryandbitmasks

120

Page 121: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Networking(part1)Whencreatingaprogramsuchasagame,oneofthefirstthingstoconsidershouldbewhetheritisanetworkedapplication.Suchachoicewillradicallychangethestructureandcomplexityoftheapplication.Tobuildanetworked("online")multiplayergame,wemustunderstandsomenetworkingbasics.Someofthisinformationisoversimplified,butlet'sestablishabaselineofknowledge.

Internetprotocol(IP)Networksarepossiblebecausecomputersagreeonawaytocommunicatewitheachother.Likeogres,messagessentacrosstheinternethavemanylayers.Eachlayerrepresentsadifferentprotocolthatinterpretshowthemessageshouldbehandled.Theinternetprotocol(IP)tellscomputershowtorelaymessagestotheirintendeddestination.Therearetwothingsweneedtoknowaboutthisprotocol:IPaddressesandports.

EverydeviceconnectedtotheinternethasanIPaddressassignedtoitwhenitconnects.MessagessentoutfromyourdevicearesentwithadestinationIPaddressattachedsoitknowswheretogo.Messagesarerelayedfromonemachinetoanotheruntilitreachesthedestinationmachine'saddress.

Ifyouopenyourterminalorcommandpromptandtype pinggoogle.comyouwillgetaresponsebackthattellsyouthedestinationIPaddress;TheIPaddressoftheserverrunningthegoogle.comhomepageyousee.YoumayevenbeabletotypethatIPaddressintoyourwebbrowseranditwilldirectyoutothewebsiteinthesamefashiontypinggoogle.comintheaddressbarwould(althoughthiswon'tworkforallwebsitesbecauseofunrelated,complicatedreasons).Let'ssayyouconnectedtogoogle.comthroughtheIPaddress172.217.7.14.You'reactuallyconnectingtothatIPthroughaspecificport.Portsarerepresentedasnumbers,sothatIPlikemosteveryotherwebsiteontheinternetisaccessedthroughport443.

IPportsarelikethemaritimeportsthatharborships.Asingledestinationcanhavemultipleportsfordifferentpurposes.IfIwerebringinginamilitaryvesselImaygotoadifferentportthanacommercialvessel.

DependingonyourintentionsforanetworkconnectionyouwillusedifferentIPports.Forinstance,ifyouaretryingtoviewawebsitelocatedat172.217.7.14youwilluseport443foranHTTPSconnection,port80foranHTTPconnection(ifallowed),andifIamanadministratorofthemachinerunningon172.217.7.14Iwilluseacompletelydifferentporttoestablishabackdoorconnectionsuchasport22.

ForourexampleprogramwewilltryconnectingtoaspecialreservedIPaddress,127.0.0.1.ThisIPaddressisyourmachine'sownIPaddressituseswhenitwantstoconnecttoitself.Sincewe'llbetestingourprogrambyrunningbothcopiesonthesamecomputerwewon'tneedtoworryaboutmultipleIPaddressesfornow.Fortheportyouhavearangefrom0to65535anditdoesn'treallymatterwhichoneyouusesolongasit'snotalreadyinuseorbeingreservedforotherpurposes.We'llpickarandomonethatisunlikelytobeinusebyotherprograms...6789.

TransportlayerThetransportlayerdecideshowyourdatawillbepackagedandstreamed.Youhaveachoiceonafewdifferentprotocolsforthetransportlayer.Understandingthedetailsofeachprotocolinthetransportlayerisn'ttooimportantforthissectionofthebookbutlet'sdiscusswhywemaywanttouseoneortheother.

TCP-Thisprotocolprovidesdifferentfeaturestomakesuredatadoesn'tgetcorrupt.Mostnotably,itwaitsforaconfirmationresponsefromtheotherendtomakesurethemessagewasreceived.Ifaresponseisn'treceivedbyacertaintimeoutthentheconnectionisconsideredafailure.WebsitesuseTCP99%ofthetimebecauseofitsreliabilityandensuringyou'vereceivedthesite'sfullcontent.

2.17-Networking(part1)

121

Page 122: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

UDP-Thisprotocolsendsdatatoaserverandexpectsnoresponseback.Sendingdatawithoutconfirmingitreachesthedestinationcouldleadtolessreliabledatatransportation.However,lessbackandforthcommunicationcouldmeanafasterconnection.Thisprotocolisusedbyapplicationsneedingtosendlotsofdataquickly,likeanaudiostreamoravideogame.Thisistheprotocolwe'lluse.

ImagineyouhavetwoplayersneedingtocommunicatetheirpositionwitheachothersowedecidetouseUDP.Youmaysendmessagesbackandforthseveraltimesasecondtocommunicateyourpositions.Sinceyouaresendingdatasorapidly,ifoneofthosemessagesislostthentheplayerpositioncanbere-synchronizednextmessage.Thisisfastandunlessoneoftheplayershasafaultyinternetconnectionyoutypicallywon'tnoticeasmalljitterorhiccupeverynowandthen.

Nowimagineanotherscenariowherewewanttosendamessagethataplayergainedanextralife.IfwewereusingUDPandthatmessagegotlost,wecouldhavetwoonlineplayerswithout-of-syncinformationthatwouldultimatelyjeopardizegameplay.OnesolutionaroundthiswouldbetouseTCPformission-criticalmessagesandUDPforeverythingelse.AnothersolutionistokeepallmessagesinUDP,buttowriteacallbackinLuaaroundourmission-criticalmessagestocheckthatwegetareply.Yup,youcanhaveyourapplicationsendUDPmessagesandexpectaresponsebuteventhoughUDPdoesn'thavethisfeatureaspartofitsprotocolyoucanstillprograminyourapplicationatimeoutthatexpectsaresponse.Thissoundslikealotofwork,butLuaandmanyotherlanguageshavelibrariesavailableyoucanrequireinyourprojectthatdothisforyou.We'llseehoweasythisislateron.

ApplicationlayerFinallywehavetheprotocolwecreateforeachrunningcopyofagametoknowhowtocommunicateonceaconnectionisestablished.Forinstanceifamessagewiththestring "ping"isbeingreceived,wemaywanttorespond "pong".Themorecomplicatedthegameis,themorecomplicatedtheprotocolwillbe.Let'scheckoutoneofthelibrariesLuaoffersfornetworkingandbuildatestprogramwithabasicapplicationprotocolwheretheserverrespondsto "meow"with "bark"andtheclientrespondsto "bark"with "meow".Asyoucanguessthiswillleadtoaninfiniteback-and-forthconversationbetweenthetwohostsifwearesuccessful.

ENetThereareseveralthird-partylibrariesforLuafornetworking.LÖVEincludestwoofthemostpopular,LuaSocketandlua-enet.LuaSocketisveryflexibleandallowsyoutocreateTCPandUDPconnections.Lua-enetisbuiltontopoftheENetlibrary,asimpleyethighperformancenetworkinglibrary.ItusesUDP,buthandleseverythingaroundthetransportlayerforussowecanfocusonourapplicationlayer.ItevendoesmessageconfirmationoverUDPforuswhenweneedittosowegetthebestofbothworlds.Let'screateaserverandclientprograminLÖVEandwe'llrunthemseparately,connectingthemtoeachother.

OurserverapplicationCreateafoldercalled serverandinitcreateafilenamed server.lua.We'llstartbyrequiring enet:

--server/server.lua

localenet=require('enet')

Thisfilewillreturnatableoffunctionsforstartingandstoppingtheserver.Tostarttheserver,weneedtocreateahostandtellitwhichIPaddressandportitisrunningon.Let'screatea server.startfunctionthatdoesjustthat:

--server/server.lua

localenet=require('enet')

2.17-Networking(part1)

122

Page 123: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localhost

localserver={}

server.start=function()

host=enet.host_create('127.0.0.1:6789')

end

returnserver

TheIPaddressis 127.0.0.1aswesaidwewoulduse.ThatistellingENetwewanttostarttheserveronourmachine'slocaladdress.TheIPaddressisfollowedbyacolon( :)thentheportnumber( 6789)whichisanarbitraryportthatshouldbefreetouse.Ifwecreateamain.luafilewecanrequireserver.luaandcreateaserverwhenLÖVEstarts.

--server/main.lua

--Ourserverapplication

localserver=require('server')

love.load=function()

server.start()

end

Ifwetryandrunthis,nothingwillhappen.Let'sdefine love.drawandprintsometexttotelluswhensomeoneconnectstoourserver:

--server/main.lua

--Ourserverapplication

localserver=require('server')

love.load=function()

--Keeptextpixelssharpandintactinsteadofblurring

--https://love2d.org/wiki/FilterMode

love.graphics.setDefaultFilter('nearest','nearest')

server.start()

end

love.draw=function()

--Scaleupthesizeofthetextbeingprinted

localtransform=love.math.newTransform(0,0,0,3)

ifserver.is_connected()then

love.graphics.print('clientconnectedtous(seeconsole)',transform)

else

love.graphics.print('serverstarted...awaitingclients',transform)

end

end

--It'sconvenienttobeabletopressescapetoclosetheprogram

love.keypressed=function(pressed_key)

ifpressed_key=='escape'then

love.event.quit()

end

end

Withthisdone,weneedtofigureouthowtheserverknowssomeoneisconnected.Wecall server.is_connected()inlove.draw,solet'sstartbydefiningthat:

--server/server.lua

localenet=require('enet')

localhost

2.17-Networking(part1)

123

Page 124: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localreceived_data=false

localserver={}

server.start=function()

host=enet.host_create('127.0.0.1:6789')

end

server.is_connected=function()

returnreceived_data

end

returnserver

Ok,so server.is_connected()willreturnthevalueof received_datawhichdefaultsto false.Nowthepartthatdoesalltheaction:

--server/server.lua

localenet=require('enet')

localhost

localpeer

localreceived_data=false

localserver={}

server.start=function()

host=enet.host_create('127.0.0.1:6789')

end

server.is_connected=function()

returnreceived_data

end

server.update=function()

ifnothostthenreturnend

localevent=host:service()

ifeventthen

received_data=true

peer=event.peer

print('----')

fork,vinpairs(event)do

print(k,v)

end

event.peer:send('bark')

end

end

returnserver

Let'stakeacloselookat server.updatepiecebypiece.Firstthingisan ifstatementtocheckthat hostisdefined.If server.updateiscalledbefore server.startthenitwon'tbesothereisnoserverupdatetobemade.Ifourserverhostwascreatedandwegetpasttheif-statementcheck,wecall host:service().Ifwereadthedocumentationfor host:servicewecanseethepurposeofcallingthisistocheckforanyincomingpackets(messages)andsendoutanywehavequeuedup.Ifwereceiveany,wewillgetbackan eventtable.Ifwedogetaneventtable,we'llchange received_datato true(whichinturnmeans server.is_connected()nowreturns true).Nextwewillcapturethepeer(theclient)thatsentusthisdatawhichwecanusetosendmessagestolater:

peer=event.peer

Whilewehavetheeventtable,let'sjustiterateoveritandprintitscontentstotheconsole:

fork,vinpairs(event)do

print(k,v)

2.17-Networking(part1)

124

Page 125: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

end

Thenfinallywe'llsendtheclientamessagethatsimplyreads"bark".

event.peer:send('bark')

Wecannowcall server.updateinsideourgameloop's love.updatefunction:

love.update=function()

server.update()

end

Weneedtotestourserver,buttotestourserver,weneedaclient.

OurclientapplicationCreatea"client"folderlikethe"server"foldercreatedabove.Mostofthecodewillbeidenticaltoourserver.Themaindifferenceisthatwhenwecreateahostwewon'tpassitanIPaddressandporttoserveon,butinsteadwilltellittoconnecttotheaddressandporttheserverisrunningon.

--client/main.lua

--Ourclientapplication

localclient=require('client')

love.load=function()

--Keeptextpixelssharpandintactinsteadofblurring

--https://love2d.org/wiki/FilterMode

love.graphics.setDefaultFilter('nearest','nearest')

client.start()

end

love.draw=function()

--Scaleupthesizeofthetextbeingprinted

localtransform=love.math.newTransform(0,0,0,3)

ifclient.is_connected()then

love.graphics.print('connectedtoserver(seeconsole)',transform)

else

love.graphics.print('establishingaconnection...',transform)

end

end

love.keypressed=function(pressed_key)

ifpressed_key=='escape'then

love.event.quit()

end

end

love.update=function()

client.update()

end

--client/client.lua

localenet=require('enet')

localclient={}

localhost

localpeer

localreceived_data=false

client.start=function()

2.17-Networking(part1)

125

Page 126: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

host=enet.host_create()

peer=host:connect('127.0.0.1:6789')

end

client.is_connected=function()

returnreceived_data

end

client.update=function()

ifhostthen

localevent=host:service()

ifeventthen

received_data=true

print('----')

fork,vinpairs(event)do

print(k,v)

end

event.peer:send('meow')

end

end

end

returnclient

Ifwereceiveamessagefromtheserverwe'll"meow"backatit.

TestingthingsoutIfyouruntheserveryouwillseeamessagesaying"serverstarted...awaitingclients".Sinceweareprintingtotheconsole,ifyouarerunningthiscodeonWindowsrememberthatyouwillneedtoenabletheconsole.Thiscanbedonebycreatingaconf.luafileinboththeclientandserverfolders.

--LÖVEconfigurationfile

love.conf=function(t)

t.console=true--EnablethedebugconsoleforWindows.

t.window.width=800--Game'sscreenwidth(numberofpixels)

t.window.height=600--Game'sscreenheight(numberofpixels)

end

Iftheserverisupandrunningwiththeconsoleenabled,goaheadandstarttheclientwithitsconsoleenabledtoo.Youshouldimmediatelyseeafloodofeventsprintingoutintheserverandclientconsoles.

Serverconsole:

----

peer127.0.0.1:58384

channel0

datameow

typereceive

Clientconsole:

----

peer127.0.0.1:6789

channel0

databark

typereceive

2.17-Networking(part1)

126

Page 127: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Thiswillgobackandforthuntilyoucloseeitheroneofthem.Ifyoucloseonethough,themessageswillstopanditwilljustsitthere.Ifyouclosetheserverfirst,forinstance,theclientwillsittherethenafterseveralsecondsamessagewillappear:

----

peer127.0.0.1:6789

data0

typedisconnect

Normallyadisconnectlikethiswouldn'tbedetectedwithUDP,buttheENetlibrarysends"heartbeat"messagesbackandforthtomakesurebothpeersarestillconnectedtoeachother.Thetimeoutisdefinedtobesomewherebetween5and30secondsbeforethepeerrealizesithasbeendisconnnectedfromtheotherone.Justtopolishthingsoffhere,let'smakeENetsendadisconnecteventtotheotherpeerimmediatelywhenweareclosingourapplication.Thelua-enetdocumentationlistsafunctionwecaninvoketodothat, peer:disconnect_now.LÖVEhasa love.quitcallbackthatiscalledwhenourapplicationisclosing.Wecanwritea server.disconnectfunctionandcallitfromlove.quit.

Server:

--server/main.lua

...

love.quit=function()

server.disconnect()

end

--server/server.lua

...

server.disconnect=function()

ifpeerthen

peer:disconnect_now()

peer=nil

end

host=nil

received_data=false

end

The client.disconnectcodewouldbeidentical.

ToseethisfullexampleorifyouhaveanyproblemsgettingyourcodetoruncheckoutthecodeonGitHub:https://github.com/RVAGameJams/learn2love/tree/master/code/networking-1

Inthenextpartwewilllookatnetworkarchitectureandaddentitiestothescreentoworkwith.

ExercisesWhathappensifyoutrytoconnectmultipleclientstotheserver?WhataboutrunningmultipleserversonthesameIPaddressandport?Whydoesitbehavelikeitdoes?

2.17-Networking(part1)

127

Page 128: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Networking(part2)Intheprevioussectionwemadetwoapplicationsthatcouldtalktoeachother.Oneapplicationwastheserverandthesecondoneconnectingtoitwastheclient.Ingamedesignthisstyleofnetworkingcanbedescribedasadirectconnection.

Directconnection

Inadirectconnectiononeoftheplayerstakesontheroleofserver,meaningtheirgameworldistheultimateauthorityifthereareanydiscrepanciesorout-of-synccommunicationbetweenthetwo.Thisalsomeanstheserverplayercanfindwaystocheatandexploitthegame.

Oneoftheadvantagestothissetupissinceyouaredirectlyconnectedtoeachother,yougetasminimallagaspossible.Thisadvantagedoesn'tholdtrueiftherearemorethan2players.Ifplayer1istheserverandplayer2and3areconnectedtoplayer1,thenplayer2and3havetorelayupdatestoeachotherthroughplayer1insteadofdirectlytoeachother.Outsideof2-playergames,thissetupisn'taspopularashavingadedicatedserver.

Dedicatedserver

2.18-Networking(part2)

128

Page 129: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Dedicatedserversareexactlywhattheysoundlike.Theyarehostsdedicatedtoservingplayers.Thedifferencehereisallplayersareclientsandtheserverisaneutralgroundwhereplayerscanconnectandcommunicateindirectlywitheachotherthroughit.Serverstypicallyrunamodifiedversionofthegamecodethathasnouserinterfaceandthereforecanrunonalessexpensivecomputer.Ifoneoftheplayersisdetectedcheatingtheservercandetectthatsomethingiswrongandkickthemfromthegame.Theserveristheultimateauthorityoverthestateofthegameworld.

Ournetworksetupwillsortofbeamixbetweenthetwostylesofnetworking.We'llhaveadedicatedserverthatdoesn'tparticipateinthegameplay,buttheserverwillhaveagraphicalinterfacesowecanviewwhatisgoingonduringourtesting.

ConsolidatingourcodeRatherthanmanagingtwofoldersofcodelikeintheprevioussection,we'llcombinethecodeanduseamenusystemtoselectbetweenbeingaserverandbeingaclient.Themenucodeisn'timportanttothistutorialsotrytofocusontheclientandservercodeasbefore.Therefactoredcodecanbefoundinthecoderepositoryhere.

Giventheamountoffilesitiseasiesttodownloadthezipofthewholeprojectwhereyouwillfindtherelevantfilesinside code/networking-2:https://github.com/RVAGameJams/learn2love/archive/master.zip

Oncedownloaded,whenyouruntheprogramyoushouldbegreetedwithamenuscreenlikeso:

2.18-Networking(part2)

129

Page 130: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Testitoutandconfirmyoucanconnectaserverandclientinstancewiththenewcode.

Ok.Themodificationsto main.luashouldbeeasyenoughtounderstand.Let'stakealookatthatandthenew"net"servicefirstbeforewebeginmakinganymodifications.Atthetopofthefilewe'reloadingthenetandmenuservicesthentellingthemenuservicewhichmenutoloadonstartup:

--main.lua

localmenu_service=require('services/menu')

localnet_service=require('services/net')

love.load=function()

--Keeptextpixelssharpandintactinsteadofblurring

--https://love2d.org/wiki/FilterMode

love.graphics.setDefaultFilter('nearest','nearest')

menu_service.load('main-menu')

end

Next,ifakeyispresseditwillpassthatpressed-keyeventtothemenuservice.Ifweareinthegameandnomenuisloaded,themenuservicewilldonothingwiththeevent.

love.keypressed=function(pressed_key)

menu_service.handle_keypress(pressed_key)

end

Inside love.drawwehaveasimilarstory.Ifwehaveanactivemenuthen menu_service.draw()willdrawitOtherwiseitwon'tdoanything.(Ifyouopen services/menu.luayouwillseethedrawfunctionwherethisallhappens.)

2.18-Networking(part2)

130

Page 131: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

love.draw=function()

menu_service.draw()

--Scaleupthesizeofthetextbeingprinted

localtransform=love.math.newTransform(0,0,0,3)

ifnet_service.is_connected()then

love.graphics.print('peerconnected(seeconsole)',transform)

end

end

Anotherthinginside love.drawisachecktoseeifwe'vemadeaconnectioneitherasserverorclient(usingnet_service.is_connected())thendrawthe"peerconnected"textonthescreenasbefore.Weusetheword"peer"asagenerictermtorefertoeithertheclientwe'reconnectedto(ifwe'retheserver)ortheserver(ifwe'retheclient).

Inside love.updateand love.quitwehavecombinedthecodewehadbeforeandaddeda menu.update()call.Ifthereisamenu,updateit.Ifeitheraserverhostorclienthostisrunning, net.update()willupdateit.

love.update=function()

menu_service.update()

net_service.update()

end

love.quit=function()

net_service.disconnect()

end

Soeverywherewewerecalling"server"or"client"wejustcall net_serviceanditwilldoit'sthingnomatterthetypeofconnection.Let'sopenup net.luaandwe'llseesomethingveryclosetotheoriginalcode:

--net.lua

localenet=require('enet')

--Populateoneortheotherdependingifwestartaserverorclienthost

localclient_host

localserver_host

--Asaserver,wewanttokeeptrackofalltheconnectedclients

localpeers={}

localreceived_data=false

--Theservicewewillbereturning

localnet={}

Atthetopofthefilewecreatesomeemptylocalvariables.The nettableisfulloffunctionsthatarebeingusedinmain.luaandelsewhere.

net.start_server=function()

server_host=enet.host_create('localhost:6789')

end

net.start_client=function()

client_host=enet.host_create()

server_host=client_host:connect('localhost:6789')

end

Onthemenuwhenyouselect"Host"or"Join",the net.start_serverand net.start_clientfunctionsarebeingcalledrespectively.

Belowthataresomefunctionstocheckwhatkindofconnectionwehave:

2.18-Networking(part2)

131

Page 132: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

net.is_connected=function()

returnreceived_data

end

net.is_client=function()

returnclient_hostandtrueorfalse

end

net.is_server=function()

returnserver_hostandnotclient_host

end

Thenfinallywehavethe updateand disconnectcodelikeweoriginallyhadintheserverandclientservices,butcombined.Oneadditiontothedisconnectfunctionisweareloopingoverthe peerslist.The peerslistexistsbecauseweareexpectingtohavemultipleplayersconnecttotheserverandiftheserverisrunning,itwillwanttodisconnectfromthemallwhenthegamequits.

CommunicationlayerBeforeweupdatethecode,let'sdiscussthefunctionalityanddrawoutthenetworkcommunicationforthatfunctionality.

WhenconnectingtotheserveryoushouldhaveacontrollableplayerspawnonscreenWhenyoumoveyourplayer,yourpeersshouldbeabletoseeyourplayermoveWhenotherpeersmovetheirplayers,youshouldbeabletoseethemmoveEachplayershouldlookdifferentsoyouknowwhichoneisyou

Client Server Description

(Createahost) (Createahost)Bothclientandserver'snetservicesarebooted.Nocommunicationhasbeenmadebetweenthetwoatthispoint.

Send"connect"messagetoserver

Clientsendsaconnectmessageautomatically.Thiswillbeinterpretedasarequesttojointhegame.

(Spawnentity)Servergeneratesandstoresalltheentitiesinthegame.Whenspawninganentity,associatetheclient'sconnectionIDwithit.

Respond"your-id|4177457821|100|500"

Serverrespondsbypassingtheclienta "your-id"message.Allmessagessentmustbestringssointhiscasewheremultiplevaluesneedtobeembeddedinthemessage,eachvalueisseparatedbyapipe("|")charactertohelpusre-separatethevalueswhenreceivingthemessageclientside.ThefirstvalueistheuniqueplayerIDthattheserverhasassignedtheclient,followedbytheXpositionthenYposition.

Send"peer-id|233142890|326|177"

Serversendsthenewclientotherpeersthatneedtobespawnedonscreen.IncludedaretheID,XandYpositionofthatplayer.

Send"move|233142890|327|177"

ServerislettingtheclientknowtheplayerwiththeID"233142890"haschangedposition.NoticetheupdatedXposition.

Send"move|233142890|328|177" Anothermoveupdate.

Send"move|100|502" Clientislettingtheserverknowitismovingitsplayertoo.

2.18-Networking(part2)

132

Page 133: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Send"peer-id|81850530|500|500" Anotherplayerhasjoinedtheserver.

AddingentitiesOnconnectionfromaclient,theserverneedstospawnentitiessolet'screateanentityserviceforhandlingallourentity-relatedneeds:

--entity.lua

localentity_service={}

--Allplayerentities

entity_service.entities={}

entity_service.spawn=function(player_id,x_pos,y_pos)

return{

--TODO:We'lladdacolorrandomizerlater

color={1,1,1,1},

id=player_id,

--TODO:We'lladdashaperandomizerlatertoo

shape=love.physics.newPolygonShape(0,0,50,0,50,50,0,50),

x_pos=x_pos,

y_pos=y_pos

}

end

entity_service.draw=function(entity)

love.graphics.setColor(entity.color)

localpoints={entity.shape:getPoints()}

foridx,pointinipairs(points)do

ifidx%2==1then

points[idx]=point+entity.x_pos

else

points[idx]=point+entity.y_pos

end

end

love.graphics.polygon('line',points)

end

returnentity_service

Theseentitieswilljustbebasicshapes.Noworld,body,orfixturestoworryaboutasdealingwithphysicsisabitoutofthescopeofthissection.

Nowwe'lldosomeheavyupgradestothenetservice.Somewherenearthetopofthefilewe'lldefinetwotableswithcallbacksthatwillgetinvokedwhenaneventcomesin.They'llbeemptyfunctionsfornowandwe'llfillthemoutaswego.

--Callbackstoinvokewhencertaineventsarereceivedfromapeer

--Defineacallbacktohandleeverytypeofmessageinourapplicationprotocol

localmessage_handlers={

['your-id']=function(message,event,is_server)

end,

['peer-id']=function(message,event,is_server)

end,

['move']=function(message,event,is_server)

end

}

--TheseeventtypesaredefinedbyLua-enet.A"receive"typeofevent

--isagenericeventthatcarriesanyofthemessagesabove.

2.18-Networking(part2)

133

Page 134: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localevent_handlers={

connect=function(event,is_server)

end,

disconnect=function(event,is_server)

end,

receive=function(event,is_server)

end

}

Thenwe'llmodifythe net.updatefunctionsoitcancalloneofthethreecallbacksinsidethe event_handlerstable:

net.update=function()

localhost=client_hostorserver_host

ifnothostthenreturnend

localevent=host:service()

ifeventthen

received_data=true

--event.typewillbeeither"connect","disconnect",or"receive"

event_handlers[event.type](event,net.is_server())

--Printouttheeventtablefordebugpurposes

print('----')

fork,vinpairs(event)do

print(k,v)

end

end

end

Soyousee, event_handlers.connectwillbecalledwhena"connect"typeeventcomesinandwe'llpassittheeventand net.is_server()booleanasitstwoparameters.Nowwecangobackandfilloutthe"connect"eventhandler.Readeachcodecommentasthereisalotgoingonhereinjustafewlinesofcode.

localevent_handlers={

connect=function(event,is_server)

--Onlytheserverneedstodostuffhereonconnect

ifis_serverthen

--event.peer:connect_id()providesuswithauniquenumber.

--We'llconvertthatnumbertoastringanduseitastheplayerID.

localplayer=entity_service.spawn(tostring(event.peer:connect_id()),100,100)

--StorethisplayerintheplayertablewiththeplayerIDasthekey.

entity_service.entities[player.id]=player

--Sendtheinitial"your-id"messagebacktotheconnectingclientsotheycanspawnthisentitytoo.

event.peer:send('your-id|'..player.id..'|'..player.x_pos..'|'..player.y_pos)

end

end,

disconnect=function(event,is_server)

--TODO:Addcodetoremoveentitieswhenaclientdisconnects

end,

receive=function(event,is_server)

--TODO:Addcodetoparse"receive"eventsandcalltheappropriatemessagehandlercallback

end

}

Sincewe'reusingtheentityserviceinnet.luawe'llneedtorequireitattheverytop:

localentity_service=require('services/entity')

Nowthattheplayerreceivesthemessagetospawnanentity,wecanfilloutthe"receive"eventhandler.

localevent_handlers={

connect=function(event,is_server)

--Onlytheserverneedstodostuffhereonconnect

ifis_serverthen

2.18-Networking(part2)

134

Page 135: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

--We'llconvertthatnumbertoastringanduseitastheplayerID.

localplayer=entity_service.spawn(tostring(event.peer:connect_id()),100,100)

--StorethisplayerintheplayertablewiththeplayerIDasthekey.

entity_service.entities[player.id]=player

--Sendtheinitial"your-id"messagebacktotheconnectingclientsotheycanspawnthisentitytoo.

event.peer:send('your-id|'..player.id..'|'..player.x_pos..'|'..player.y_pos)

end

end,

disconnect=function(event,is_server)

--TODO:Addcodetoremoveentitieswhenaclientdisconnects

end,

receive=function(event,is_server)

--Extractthemessageoutfromtheeventandcalltheappropriatecallbackabove

localmessage={}

formatchin(event.data..'|'):gmatch('(.-)|')do

table.insert(message,match)

end

message_handlers[message[1]](message,event,is_server)

end

}

Don'tletthiscodefeelintimidatingasit'sonlytakingthestringmessagefromtheevent( "your-id:87335:500:500")andsplittingitintoalisttable( {"your-id","87335","500","500"})sowecanworkwiththedata.Oncewehavethemessagefragments,wecall message_handlers[message[1]](),where message[1]willbeoneofthekeysinthemessage_handlerstable: "your-id", "peer-id",or "move".Let'sfilloutthe"your-id"handlerfirst:

--Callbackstoinvokewhencertaineventsarereceivedfromapeer

--Defineacallbacktohandleeverytypeofmessageinourapplicationprotocol

localmessage_handlers={

['your-id']=function(message,event,is_server)

localplayer_id=message[2]

localx_pos=message[3]

localy_pos=message[4]

entity_service.player_id=player_id

entity_service.entities[player_id]=entity_service.spawn(player_id,x_pos,y_pos)

end,

['peer-id']=function(message,event_is_server)

end,

['move']=function(message,event,is_server)

end

}

Nowwhentheclientgetsthe"your-id"message,itcanspawnanentitytoo.Let'saddtheappropriatecodetomain.luafordrawingentitiessowecanseeifourprotocolisworkingsofar:

--main.lua

localentity_service=require('services/entity')

...

love.draw=function()

menu_service.draw()

--Scaleupthesizeofthetextbeingprinted

localtransform=love.math.newTransform(0,0,0,3)

ifnet_service.is_connected()andnet_service.is_server()then

love.graphics.setColor({1,1,1,1})

love.graphics.print('Serverpreview.Seeconsolefordetails.',transform)

end

for_,entityinpairs(entity_service.entities)do

entity_service.draw(entity)

end

end

2.18-Networking(part2)

135

Page 136: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Ifyou'veupdatedeverythingcorrectly,you'llseethishappenwhenaserverandclientrunside-by-side:

Ifyougetanerror,remembertocheckthelinenumberandfilenamewheretheerroroccurredandmakesurethecodelookssimilartohowitisabove.Ifyougetstuck,therewillbeafullexampleavailableatthebottomofthissection.

Ifeverythingisworkingforyouthenfantastic.Thismeanstheserverandclientaresuccessfullysyncinganentitystatewitheachother.

Nextlet'saddmovementsowecanseeliveupdateshappeningbetweenthetwogamewindows.Insidetheservicesfolder,we'llcreateaninput.luafilethatreturnsanemptytable:

--input.lua

return{}

Whyarewereturninganemptytable?Well,it'sonlyemptyrightnow,butaskeysarepressedandreleasedourtablewillgetupdated.Let'supdate love.keypressedandadda love.keyreleasedfunctiontomain.luatoseehowthatworks:

--main.lua

localinput_service=require('services/input')

...

love.keypressed=function(pressed_key)

menu_service.handle_keypress(pressed_key)

input_service[pressed_key]=true

end

love.keyreleased=function(released_key)

input_service[released_key]=false

end

Nowwhenwepresssomearrowkeysforinstance,ourtableininput.luawilllookmorelikethisinmemory:

{

left=true,

right=false,

up=true,

down=false

2.18-Networking(part2)

136

Page 137: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

}

Nowinsideentity.luawe'lladdan entity_service.movefunctionthatchecksforinputchangesandmovestheentityifanyofthearrowkeysarepressed:

--entity.lua

localinput_service=require('services/input')

...

entity_service.move=function()

localplayer=entity_service.entities[entity_service.player_id]

--Don'tlettheplayerpressupanddownatthesametime

ifinput_service.upandnotinput_service.downthen

player.y_pos=player.y_pos-2

elseifinput_service.downandnotinput_service.upthen

player.y_pos=player.y_pos+2

end

--Don'tlettheplayerpressleftandrightatthesametime

ifinput_service.leftandnotinput_service.rightthen

player.x_pos=player.x_pos-2

elseifinput_service.rightandnotinput_service.leftthen

player.x_pos=player.x_pos+2

end

end

Thiswillcauseourentitytomoveacrosstheclient'sscreen,buttheserverwon'tgettheseupdatesunlesstheclientsendsthemover.Weneedtogobacktomain.luaandsenda"move"message.Inside love.updatechecktoseeiftheplayerpositionhaschangedandsendamovemessagetotheserverifso:

love.update=function()

menu_service.update()

--Checktoseeifaplayerhasspawnedandupdateitsmovementifanydirectionkeysarebeingpressed

ifentity_service.player_idthen

localplayer=entity_service.entities[entity_service.player_id]

localold_x=player.x_pos

localold_y=player.y_pos

entity_service.move()

ifplayer.x_pos~=old_xorplayer.y_pos~=old_ythen

net_service.send('move|'..player.id..'|'..player.x_pos..'|'..player.y_pos)

end

end

net_service.update()

end

The net_service.sendfunctionwehaven'tdefinedthatyet.Jumpbackovertonet.luaandwe'lldefinethatforsendingeitherclientorservermessagesifneeded:

net.send=function(message)

ifnet.is_client()then

server_host:send(message)

else

server_host:broadcast(message)

end

end

2.18-Networking(part2)

137

Page 138: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Nowwearesendingamessagetotheserverwhenwemove,butweneedtohavetheserverreadthemessageandupdatetheentitypositiononitsendtoo.Let'sfilloutthe"move"messagehandlerinnet.luatoaccomplishthis:

localmessage_handlers={

['your-id']=function(message,event,is_server)

localplayer_id=message[2]

localx_pos=message[3]

localy_pos=message[4]

entity_service.player_id=player_id

entity_service.entities[player_id]=entity_service.spawn(player_id,x_pos,y_pos)

end,

['peer-id']=function(message,event,is_server)

--TODO:handlepeer-idmessagesforwhenmoreplayersjointheserver

end,

['move']=function(message,event,is_server)

localplayer_id=message[2]

localx_pos=message[3]

localy_pos=message[4]

entity_service.entities[player_id].x_pos=x_pos

entity_service.entities[player_id].y_pos=y_pos

ifis_serverthen

--Relaythismessagetotheotherplayers

forid,peerinpairs(peers)do

ifid~=player_idthen

peer:send(event.data)

end

end

end

end

}

Noticetheextraservercheck.Iftheserverreceivedthe"move"commanditshouldrelayitovertoanyotherconnectedplayersotheycanseeyoumovingtoo.The peerstableneedstobedefinedinsidenet.luaabovethemessagehandlersoryoumaygetanerrorwhenittriestoloopoverthetableandseesanilvaluethathasn'tbeendefinedyet.

Tryitoutagainandweshouldseethesquaremovingonbothscreensnow.

Allthathardworkisstartingtopayoff!Wehaveafewmorechangestomakeforthistobefullyfunctionalthough.Ifyoutryandrunthegamenowwithmultipleclients,theplayerswon'tseeeachother.Inourapplicationprotocolwedefineda peer-idmessagesothatwhennewplayersconnectwereceiveinformationtospawnthem.

Insidenet.lua,goaheadandfilloutthe"peer-id"messagehandlersothatitspawnsanentitywheninvoked:

['peer-id']=function(message,event,is_server)

localplayer_id=message[2]

localx_pos=message[3]

localy_pos=message[4]

entity_service.entities[player_id]=entity_service.spawn(player_id,x_pos,y_pos)

end,

Nowtheserverneedstosendallthepeerstoanewplayerconnecting,butitalsoneedstosendnewplayerstopre-existingpeersduringaconnectevent.We'llupdatethe"connect"eventhandlertodothat:

connect=function(event,is_server)

--Onlytheserverneedstodostuffhereonconnect

ifis_serverthen

--event.peer:connect_id()providesuswithauniquenumber.

--We'llconvertthatnumbertoastringanduseitastheplayerID.

localplayer=entity_service.spawn(tostring(event.peer:connect_id()),100,100)

--StorethisplayerintheplayertablewiththeplayerIDasthekey.

entity_service.entities[player.id]=player

2.18-Networking(part2)

138

Page 139: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

--Sendtheinitial"your-id"messagebacktotheconnectingclientsotheycanspawnthisentitytoo.

event.peer:send('your-id|'..player.id..'|'..player.x_pos..'|'..player.y_pos)

--Letalltheotherpeersknowaboutthisplayer

for_,peerinpairs(peers)do

localpeer_player=entity_service.entities[tostring(peer:connect_id())]

peer:send('peer-id|'..player.id..'|'..player.x_pos..'|'..player.y_pos)

event.peer:send('peer-id|'..peer_player.id..'|'..peer_player.x_pos..'|'..peer_player.y_pos)

end

--Addthispeertothepeerlist

peers[tostring(event.peer:connect_id())]=event.peer

end

end,

Aftersendingthenewclienttheiridas"your-id",wesendthatidtoeveryotherclientas"peer-id"intheforloop.Noticeweaddthenewconnectingclienttothepeertableattheveryend.Wedothisafterloopingoverthepeerlistsowedon'tsendthatclienta"peer-id"messageofthemselves.Again,whenregisteringpeersandentitieswecalltostring()onthe connect_id()toensurewearestoringIDsasstringsratherthannumbers.Storingdataintablesitmatterswhetheryoustorethemaskeysornumbers.See1.14-Tables(part2)toseewhatImean.

Anyways,tryrunningthegamenowwithmultipleclientsconnectingtotheserverandyouwillseeeachentitycanmoveseparatelyandthechangeswillbesynchronizedacrossallclients.Onechangetomaketheentitieseasiertodistinguishwouldbetorandomizetheircolorandshape.Let'smodify entity_service.spawninsideentity.lua:

entity_service.spawn=function(player_id,x_pos,y_pos)

localcolors={

{1,0,0,1},

{0,1,0,1},

{0,0,1,1},

{0,1,1,1},

{1,0,1,1},

{1,1,0,1},

{1,1,1,1}

}

localshapes={

love.physics.newPolygonShape(25,0,50,50,0,50),

love.physics.newPolygonShape(0,0,50,0,50,50,0,50),

love.physics.newPolygonShape(12,0,36,0,49,15,49,33,36,49,12,49,0,33,0,15)

}

return{

--Cyclethroughthelistofcolorsbasedonwhatevertheplayeridis

--Callingtonumber()tomakeplayer_idanumberinsteadofastringsowecandomathonit

color=colors[(tonumber(player_id)%#colors)+1],

id=player_id,

shape=shapes[(tonumber(player_id)%#shapes)+1],

x_pos=x_pos,

y_pos=y_pos

}

end

Hereweuseamodulustocyclethroughthelistofcolorsandshapesandassignonebasedonthepseudo-randomplayerIDwereceived.Thisalsomeansaplayerwilllookthesameoneveryotherplayers'client.Tryrunningitagainandyoushouldseesomethingsimilar:

2.18-Networking(part2)

139

Page 140: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Thefinalnetworkingcodecanbefoundhere.Again,giventheamountoffilesifyouneedthewholefolderthenitiseasiesttodownloadthezipofthewholeproject.Youwillfindtherelevantfilesinside code/networking-3:https://github.com/RVAGameJams/learn2love/archive/master.zip

ConclusionThisisaboutasbasicinfunctionalityasamultiplayergamecanget.Therearemanyfeaturesmissingfromourcode.Justtonameafewmajorones:

Sanitychecksonmessages.Youcancrashtheserverbysendingitaninvalidmessage.The"move"messageexpectsaplayeridwhentheservershouldbeabletofigurethisoutonitsown.Theservercaneasilybefooledintoaccepting"move"messagesfromaclienttomoveanotherclient.Tomakethingssimplerthereisnoworldorphysicsnoranynetcodetohandlecollisionsoranythingofthelike.Theentitiesjuststickaroundwhenyouclose/disconnectaclient.Ifyouloseconnection,thereisnoattempttorestoretheconnection.

ThereisagreatsetofarticlesbyGabrielGambettaontheproblemsfacedbymakingaaction-basedmultiplayergamesandit'sworthareadtogetahigh-leveloverviewofthechallengesandhowtoreasonaboutthem.Linktohisarticles.

ExercisesHavingalltheplayersspawnattheexactsamepointisn'tideal.Insidenet.luainthe"connect"eventhandler,makealistofspawnpointsandmaketheplayersspawnatoneofthepointsbasedontheir peer:connect_id().Hint:thecodeshouldlooksimilartothecolorcyclecodeinside entity.spawninentity.lua.Insidenet.lua,completethe"disconnect"eventhandlerandmakeissowhenaclientquitstheirentityisremovedfromthegameforallpeers.

2.18-Networking(part2)

140

Page 141: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Chapter3:ProgrammingindepthThegoalofthischapteristotouchonavarietyoftopicsandproblemsfacedwhileprogrammingandtounderstandandsolvethemusingLua.Awidervarietyoftopicswillbecoveredhere.Onetopicwillleadintoanotherwhichwillthenbuildontopofconceptsintroducedintheprevioustopic.

3.0-Programmingin-depth

141

Page 142: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

PrimitivesandreferencesTakealookatthiscode:

localstring1="hello"

localstring2='hello'

print(string1==string2)

localnumber1=14

localnumber2=14

print(number1==number2)

localtable1={}

localtable2={}

print(table1==table2)

localfunction1=function()end

localfunction2=function()end

print(function1==function2)

Whatwouldhappenifyouweretorunthis?

Inchapter1welearnedaboutcomparingstringswiththe ==operatorwhenwetalkedaboutbooleans.RunthecodeaboveintheREPLandseewhatitreturns:

true

true

false

false

Thestringsequalandthenumbersequal,butwhyaren'tthetablesandfunctionsequalsincetheyarebothempty?Tryprintingthetablesandfunctionsandlookwhathappens:

localtable1={}

localtable2={}

print(table1)

print(table2)

localfunction1=function()end

localfunction2=function()end

print(function1)

print(function2)

table:0x16af270

table:0x16af220

function:0x16ae840

function:0x16aeff0

Attemptingtoprinteachvalueyouaregivenbackahexadecimalnumber,theplaceinmemorywherethosevaluesarelocated.Eachtableandfunctionresidesinadifferentplaceinmemory.Sohowisthisrelevant?

3.01-Primitivesandreferences

142

Page 143: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Whencheckingdatalikestringsandnumbers,the ==operatordoesindeedcheckthatthedatamatches.Thesedatatypesaresimpleandtakeverylittleeffortforacomputertocheckthattheyareequal.Booleans,strings,numbers,and nilareallprimitivetypesofdataandbehavethisway.

Whencheckingdatalikefunctionsandtables,however,the ==operatorchecksthememorylocationofthedataonbothsidesoftheoperatorandifthevariablesreferencethesamelocationthentheyareequal.Inotherwords,the==operatorchecksthesedatatypestoseeiftheyhavethesameidentity.Nomatterhowmanyemptytablesorfunctionsyouhave,eachoneiscreatedwithauniqueidentity.

localstring1='hello'

localstring2="hello"

--Anothercopyof"hello"iscreatedinmemory:

localstring3=string2

--Butthesetwocopiesareequal

print(string2==string3)

localtable1={}

localtable2=table1

print(table1==table2)

Whatistheresultof print(table1==table2)?Aha!Boththesevariablesreferencethesamedata.Quick–amagicianwavestwowandsinfrontofyourfaceandasksyoutocounthowmanywandsthereare.Howdoyouknowiftherearereallytwowandsorifthisisjustatrickwithmirrors?Whatdoyoudo?Youtakeoneofthewandsandbreakitofcourse.Iftheotherwandbreaksthentheywerethesamewandtheentiretime.Let'strythatwiththetwoobjects:

localtable1={}

localtable2=table1

table1.rabbit='white'

print(table2.rabbit)--Equals'white'too

Aslongasyourvariablesreferencethesametable,updatingthetablefromonevariableyouwillseetheresultwhencheckingtheothervariable.Thisdoesn'tworkwithprimitivedatabecauseyou'realwaysmakingacopywhenassigningittoanewvariablename:

localstring1='hello'

localstring2=string1

string1='world'

returnstring2

=>hello

Primitiveversusnon-primitivedatatypesWheneverweassignnon-primitivedatatoanewvariable,we'realwaysreferencingtheoriginaldata:

localgrocery_list={

'carrots',

'celery',

'pecans'

}

localsame_list=grocery_list

3.01-Primitivesandreferences

143

Page 144: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

grocery_list[1]='grapes'

returnsame_list[1]

Butassigningprimitivedatatoavariable,evenprimitivedatainsidetables,we'realwaysmakingauniquecopy:

localgrocery_list={

'carrots',

'celery',

'pecans'

}

localitem_copy=grocery_list[1]

print('item_copyis'..item_copy)

grocery_list[1]='grapes'

print('item_copystillis'..item_copy)

Ifyouneedtomakeeachiteminyourtablereference-able,youneedtomakeeachitemanon-primitivedatatype:

localgrocery_list={

{name='carrots',location='produce'},

{name='celery',location='produce'},

{name='pecans',location='baking'}

}

localitem_reference=grocery_list[1]

print('item_referenceis'..item_reference.name)

grocery_list[1].name='grapes'

print('item_referenceisnow'..item_reference.name)

Soratherthanreplacingthefirstiteminthelist,thefirstitemwasretainedandonlymodified:

item_referenceiscarrots

item_referenceisnowgrapes

Cloningnon-primitivedatatypesAswearefamiliarwithatthispoint,tablesareaspecialdatatypethatcancontainotherdatatypes.Youcanbuildstructurescontainingstrings,variables,andevenothertables.Thatmakesthetableacompositedatatype,inotherwords,adatatypewithdistinguishableparts.Notalllanguageshavecompositedatatypes,butforLuathetableisoneofitsprimaryfeatures.

Onethingaprogrammermaywanttodowithatableisonceconstructed,createacopyofit.Iftherewasatableforamonsterinavideogame,youmaywanttohavemorethanonetable.Ifyoudidthis:

localenemy1={health=10,strength=12,type='orc'}

localenemy2=enemy1

Youwouldstillonlyhaveonetable.Youcouldusealooptocopyallthevaluesoutofatableandintoabrandnewtable.Afunctiontodothatmaylooklikethis,moreorless:

3.01-Primitivesandreferences

144

Page 145: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localcopy=function(orig_table)

localnew_table={}

forkey,valueinpairs(orig_table)do

new_table[key]=value

end

returnnew_table

end

localenemy1={health=10,strength=12,type='orc'}

localenemy2=copy(enemy1)

Thereisnothingterriblywrongwiththismethod,butamoreefficientwaytodosuchathingwouldbetoconstructeachmonstertableinsideafunctioninsteadofcopyingonefromanother.Thismethodwillbefamiliaralreadyifyoureadandfollowedthroughthebreakoutgame.

localcreate_orc=function(strength)

return{

health=10,

strength=strength,

type='orc'

}

end

localenemy1=create_orc(12)

localenemy2=create_orc(12)

Everytimethefunction create_orcisran,itconstructsanewtablefromscratch.Youdefineanorc-styletableonlyonceanddon'tneedtoreadvaluesinfromonetabletoanother.Afunctionthatconstructstablesforyouisacommonparadigminprogrammingknownasafactoryfunction.Youmadeafactorythatbuildsorcs!Ofcoursethisfactoryfunctionparadigmworkswithothernon-primitivetypesofdataaswell:

localcreate_function=function()

returnfunction()return1+1end

end

localfn1=create_function()

localfn2=create_function()

print(fn1)

print(fn2)

Afunctionthatgeneratesotherfunctions?Thismayseemlikeanoddthingtowanttodo,butthismethodofprogrammingcanbequiteusefulaswe'llseein3.02-Higher-orderfunctionsandlaterfollow-upsections.Onethingthatshouldbementionedthoughisthatfunctionscanalsobeconsideredacompositedatatypeasitcanreturnotherdatatypes,andevenotherfunctions.Compositeinthatyoucancomposehigher-orderfunctionalityinthewaytablescanbeusedtocomposehigher-orderstructures.

ConclusionWhencomparingorreferencingdata,alwayskeepinmindwhetheryouhandlingprimitiveornon-primitivedata.Ifyouaremodifyingdatainoneplace,thinkifthismightbeaffectingyousomewhereelseinyourprogram.Evenwhenwritingouta localsome_module=require('some-module')inyourcode some_moduleisjustatableandlikeeveryothertable,everyreferencetoitcanaffecteachother.Somodifying some_moduleintwodifferentfilescanhaveeitherbeneficialordisastrousconsequencesdependinghowmuchcareandregardyougiveyourcode.

3.01-Primitivesandreferences

145

Page 146: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Higher-orderfunctionsIn1.07Makingfunctionswelearnedabout,well,makingfunctions.Sowhatabouthigher-orderfunctions?Whataretheyandhowdowemakethem?Simplyput,higher-orderfunctionsarefunctionsbuiltontopofotherfunctions.Here'sabasicexample:

localrun_twice=function(some_function,some_data)

some_function(some_data)

some_function(some_data)

end

run_twice(print,'HelloWorld!')

Itcantakeanyfunctionandrunittwiceforyou,inthiscasethe printfunction,butitcouldbeanyfunctionyoupassit.Typicallyhigher-orderfunctionsreturndata.Here'satrickierexamplethatdoesjustthat:

localtwice=function(fn,val)

returnfn(fn(val))

end

localadd_four=function(num)

returnnum+4

end

returntwice(add_four,12)

Takealookatthebottomlineforasecond.Wearecallingthefunction twicewithtwoarguments,the add_fourfunctionandthenumber 12.Thepurposeofthe twicefunctionistotakeavalue, 12inthiscase,andrunitthroughthegivenfunction( add_four)twice.Nowtakealookinsidethe twicefunction.Insideitreturnsfn(fn(val)).Givenwhatweknowisbeingpassedtothisfunction,thiscanbereadassayingadd_four(add_four(12)).Theorderofoperationsaystostartfromtheinner-mostparenthesisandworkyourwayout:

add_four(add_four(12))

becomes

add_four(16)

whichbecomes

20

andthatiswhatisreturnedwhenyourunthecode.Thepowerofthesehigher-orderfunctionsisthattheyarere-usable.Youcangivethe twicefunctionanythingthattakesandreturnsavalue:

localtwice=function(fn,val)

returnfn(fn(val))

end

localdouble=function(number)

returnnumber*2

end

returntwice(double,3)

3.02-Higher-orderfunctions

146

Page 147: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

...orsimilartoouroriginalexample:

localtwice=function(fn,val)

returnfn(fn(val))

end

localshout=function(message)

print(message..'!!')

returnmessage

end

returntwice(shout,'hello')

Thereareallexamplesofhigher-orderfunctionsthatacceptafunctionasanargument.Anotherkindofhigher-orderfunctionisonethatreturnsanotherfunction:

localwrapper=function()

returnfunction()

return'Youfoundthetreasure!'

end

end

localkinder_surprise=wrapper()

localsecret=kinder_surprise()

returnsecret

Whenweran wrapperitreturnedusanotherfunctionthatwehadtoinvoketogettotheinnermostvalue.Toavoidallthevariablenames,youcansavesometimeandinvokesuchkindsoffunctionslikeso:

localwrapper=function()

returnfunction()

return'Youfoundthetreasure!'

end

end

returnwrapper()()

ClosuresWhichnumberwillprintoutbyrunningthefollowingcode?

localnumber=3

localclosure=function()

localnumber=5

returnfunction()

print(number)

end

end

localprint_number=closure()

print_number()

Strange?

Ok,solet'stryathissamefunction-returning-a-functionthingbutpassinginsomedata:

3.02-Higher-orderfunctions

147

Page 148: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localadder=function(a)

returnfunction(b)

returna+b

end

end

localadd_three=adder(3)

returnadd_three(1)

The add_threevariableisassignedauniqueandspecialfunction.Itisassignedtheinnerfunctionwithintheadderfunction,butwiththedatawepassedinnowassignedtothe avariable.Eventhoughthefunctionwasreturnedoutsideofthescopeitwasdefinedin,thescope'sdatawasenclosedinsidethereturnedfunctionuntilthefunctionwasdiscardedandtheprogramexited.Thesetypesoffunctionsarecommoninsituationswhereafunctionneedstobegeneratedmultipletimesbutwithdifferentdatasets.

Thedataintheclosurecanalsocontinuetobeupdated,givingyoutheabilitytomakestoragecontainersforyourdata.Trythisout:

localmake_counter=function()

localnumber=0

returnfunction()

number=number+1

returnnumber

end

end

localcount=make_counter()

print(count())

print(count())

print(count())

print(count())

InprogramslikeLÖVEtherearecallbacksystemswhereasimilareffecthappens:

localentity=require('entity')

love.draw=function()

entity:draw()

end

Asseeninthepreviouschapter,the love.drawcallbackisdefinedinamain.luafileandlaterinvokedsomewherewithinthegameengine.Since love.drawwasdefinedinthescopewheretheentityvariableisdefined,theentityvariablelivesonandcanbeusedinside love.drawlongafterthemain.luafileisdonebeinginvoked.

ConclusionClosurestakesomepracticetounderstandandappreciate,butonceyouseepracticalexamplesofwhereandhowtousethemtheybecomeanindispensableitemonyourprogrammingtoolbelt.Intheprevioussectionweusedthetermcompositedatatocompareprimitiveandnon-primitivedatatypes.Inthissectionwesawhowtogoaboutcomposinghigher-orderfunctions.Inthefollowingpageswewillcoversomehigher-orderfunctionsthatarethebuildingblocksforoldandmodernsoftwarealike.

Exercises

3.02-Higher-orderfunctions

148

Page 149: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Inthe make_counterexampleabove,trygeneratingmultiplecounters:

--Dothenumbersineachcounterstayin

--syncoraretheytrackedindependently?

localcount_a=make_counter()

localcount_b=make_counter()

Usingthesame make_counterexample,modifyittoreturnatableinsteadofafunction.Withinthistable,definean incrementand decrementfunctionsothatyoucanmakethecounternumbergoupordown.Howwouldyouusesuchafunction?

3.02-Higher-orderfunctions

149

Page 150: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

MapandfilterIntheprevioussectionwepracticedcreatingsomehigherorderfunctions.Inthissectionswe'llcomposetwohigher-orderfunctionscommonlyusedininternetapplicationsfortransforminglists.

We'llstartbytakingalookatourgrocerylisttoseewhatitemsweneedtopickup:

localgrocery_list={

{

name='grapes',

price='7.20',

location='produce'

},

{

name='celery',

price='5.50',

location='produce'

},

{

name='walnuts',

price='6.20',

location='baking'

},

{

name='sugar',

price='8.00',

location='baking'

},

{

name='mayonnaise',

price='3.50',

location='dressings'

},

{

name='cream',

price='3.00',

location='dairy'

}

}

Thislisthasmoreinformationthanwewanttoseeataquickglance.Ifwewantedtoonlydisplayanumberedlistofitemnames,wecoulddosobywritingafor-loopthatgeneratesanewlistforus:

localnew_grocery_list={}

forkey,valueinipairs(grocery_list)do

new_grocery_list[key]=key..'.'..value.name

end

for_,valueinipairs(new_grocery_list)do

print(value)

end

Herewegeneratedalistwithaloopthenloopedoverthelistagaintoprintourresults:

1.grapes

2.celery

3.walnuts

4.sugar

5.mayonnaise

6.cream

3.03-Mapandfilter

150

Page 151: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Thisworksgreatforsimplecodelikethisexample,butitcangetmessyifyouareworkingwithmanylistsorifyouwanttotransformliststodifferentformats.

MapHere'sourhigherorderfunction, map.Ittakesalistandafunctionasargumentsthenreturnsanewlist.

localmap=function(list,transform_fn)

localnew_list={}

forkey,valueinipairs(list)do

new_list[key]=transform_fn(value,key)

end

returnnew_list

end

Anewlistiscreatedbyloopingovereachitemintheoriginallist,applyingyourfunctiontotheitem,thenassigningthetransformeddatatothenewlist.Ourcodecanbere-writtentousethemapfunction:

localmap=function(list,transform_fn)

localnew_list={}

forkey,valueinipairs(list)do

new_list[key]=transform_fn(value,key)

end

returnnew_list

end

localgrocery_list={

{

name='grapes',

price='7.20',

location='produce'

},

{

name='celery',

price='5.50',

location='produce'

},

{

name='walnuts',

price='6.20',

location='baking'

},

{

name='sugar',

price='8.00',

location='baking'

},

{

name='mayonnaise',

price='3.50',

location='dressings'

},

{

name='cream',

price='3.00',

location='dairy'

}

}

localnew_grocery_list=map(grocery_list,function(item,index)

returnindex..'.'..item.name

end)

3.03-Mapandfilter

151

Page 152: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

for_,valueinipairs(new_grocery_list)do

print(value)

end

Calling map(...)wegetbackthenewlistthenweloopoveritagainjusttoprintourresultsout.Noticehowthesecondargumentwepassedintomapisjustafunctionwithnoname.Functionswithnonamesaresometimescalledanonymousfunctions.Insomelanguagesthey'recalledlambdas,especiallywhenusedinsideahigher-orderfunctioninasituationlikethis.Thetransformfunctiontakesintheitemanditsindexandmustreturnbackanewresultformaptoputinsidethenewfunction.

Maybeafewmoreexampleswillhelpout,sowhatifwewanttoreturnanotherlistwithjustthepricessowecanadduphowmuchweneedtospend?

localprice_list=map(grocery_list,function(item)

print(item.price)

returnitem.price

end)

7.20

5.50

6.20

8.00

3.50

3.00

Herethemapfunctionispassedinatransformfunctionwithaprintstatementinsideit.Thatwayitwillprinttheitempricesasitbuildsthelistsoyoucanseewhateachvaluewillbe.

Ifyouhadotherlistsforwhichyouwantedtoprintprices,itcouldbedonequiteeasilywith map:

localtransform_fn=function(item)returnitem.priceend

map(grocery_list,transform_fn)

map(car_parts,transform_fn)

map(card_transactions,transform_fn)

FilterLet'ssaywewantedtoonlyseethethingsonourgrocerylistthatareinthebakingaisle.Wecouldwritealooptodothat:

localfiltered_list={}

for_,valueinipairs(grocery_list)do

ifvalue.location=='baking'then

filtered_list[#filtered_list+1]=value

end

end

for_,valueinipairs(filtered_list)do

print(value.name)

end

Tryrunningthatandonceitmakessense,let'sthinkabouthowtoturnthisintoare-usablehigher-orderfunctionlikemap.We'llmakeafunctioncalled filterthat,like map,takesalistandafunction.Thefunctionwillreturn trueifitwantstoputaniteminthenewlistor falseifitdoesn't.We'llcallitthepredicatefunction.

3.03-Mapandfilter

152

Page 153: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localfilter=function(list,predicate_fn)

localnew_list={}

forkey,valueinipairs(list)do

--Thepredicate_fnthatwaspassedinshouldreturn

--avaluethatevaluatestoeithertrueorfalse.

ifpredicate_fn(value,key)then

new_list[#new_list+1]=value

end

end

returnnew_list

end

Andwecanusethisfunctiontofilterdowntojustourbakingitemslikethis:

localfilter=function(list,predicate_fn)

localnew_list={}

forkey,valueinipairs(list)do

ifpredicate_fn(value,key)then

new_list[#new_list+1]=value

end

end

returnnew_list

end

localgrocery_list={

{

name='grapes',

price='7.20',

location='produce'

},

{

name='celery',

price='5.50',

location='produce'

},

{

name='walnuts',

price='6.20',

location='baking'

},

{

name='sugar',

price='8.00',

location='baking'

},

{

name='mayonnaise',

price='3.50',

location='dressings'

},

{

name='cream',

price='3.00',

location='dairy'

}

}

localfiltered_list=filter(grocery_list,function(item)

returnitem.location=='baking'

end)

for_,valueinipairs(filtered_list)do

print(value.name)

end

3.03-Mapandfilter

153

Page 154: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

walnuts

sugar

Noticeourpredicatefunctionwewrote:

function(item)

returnitem.location=='baking'

end

Theoperationafterthe returnalwaysreturnsaboolean trueor false,so filterknowsexactlywhattodowiththeitembasedonthoseresults.

Youcanimaginethe filterfunctioncouldbeusefulforprocessingasearchquery.Forinstance,ifwewantedtoseeonlymedium-sizedshirtsthatfitaspecificpricerange:

filter(products,function(item)

ifitem.type=='shirt'then

ifitem.size=='M'then

returnitem.price<40

end

end

returnfalse

end)

CaveatsThefilterfunctionreturnsanewlist,buttheitemsintheliststillreferencetheoldlistiftheyaren'tprimitives.Forinstanceifwemodifiedthegrocerylist,thefilteredcopywouldbeupdated.

localfiltered_list=filter(grocery_list,function(item)

returnitem.location=='baking'

end)

grocery_list[3].name='peanuts'

print(filtered_list[1].name)

peanuts

Thisbehaviorcanbeadvantageousifit'sexpected,butit'ssomethingthatshouldbeunderstoodabouthowLuaandsimilarprogramminglanguageswork.Thisisexplainedmorein3.1-Primitivesandreferences.

Anotherthingtoconsideriswhetherornottowritethefunctionsyourselfortouseapre-writtenlibraryyoucanrequireintoyourproject.Notallimplementationsarethesameandsomemayperformbetterthanothers,orbehavedifferently.Somelanguageshavebuilt-inversionsofthesefunctionstostandardizethings.UnfortunatelyLuadoesn'tprovidethesefunctionsbuiltinorasastandardlibrary.

Atleastyounowknowhowtowritethemyourselfiftheneedarises.

ExercisesTryfilteringthegrocerylisttoonly"produce"items,thenmappingthoseresultsdowntojustthenames.Using filter,nowcanyoureturnthenumberofitemsinthegrocerylistwithapriceofmorethan5?Hint:youwillneedtousetonumber()toconverttheitempricestonumbersforcomparing.

3.03-Mapandfilter

154

Page 155: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

StackandrecursionWhenrunningaprogram,theinterpreter(Luainthiscase)keepstrackofvariablesdefinedinascopeandwhichfunctionyouarecurrentlyin.Itorganizesthisinformationintoalistinmemorycalledthestack.Thefirstiteminthestackisthestartingpoint-therootofyourapplication.Takethefollowingexample:

localtwo=function()

print('two')

end

localone=function()

print('one')

two()

end

one()

Whenstartingtheprogram,thestartofthestackisthetoplevelofthemodule.TheLuastackcallsthisthe"mainchunk".Whenafunctionisinvoked,anotherlayerisaddedtothestack.Everytimeafunctioniscalledfromanotherfunction,thestackcontinuestobuild.Sowiththeexamplecodeabove,Thestackwillfollowtheprogression:

Stackis {"mainchunk"}.Nowstartexecuting one.Stackis {"mainchunk","one"}.Nowstartexecuting twowhilestillin one.Stackis {"mainchunk","one","two"}.twoisdoneexecuting.Stackisnow {"mainchunk","one"}.oneisdoneexecuting.Stackisnow {"mainchunk"}.Programexits.

Thiscanbevisualizedbythrowinganerroratanypointtheprogram.Theinterpreterwillgiveyoubackastacktracethatdetailswhereitwaswhentheproblemoccurred.Luaprovidesahelpful errorfunctionfordebuggingthatwecanusehere:

localthree=function()

error('Thisisanerror.')

end

localtwo=function()

print('two')

three()

end

localone=function()

print('one')

two()

end

one()

UnfortunatelytheREPLdoesn'tprovideuswithstacktraces,butifyouhaveaLuainterpreteronyourcomputer( luacommand, luajit,orLÖVE)youwillseetheerrormessageandastacktracelikethis:

lua:test.lua:2:Thisisanerror.

stacktraceback:

[C]:infunction'error'

test.lua:2:inupvalue'three'

test.lua:7:inupvalue'two'

test.lua:12:inlocal'one'

3.04-Stackandrecursion

155

Page 156: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

test.lua:15:inmainchunk

[C]:in?

Fromthe"stacktraceback"youcanseethenewestfromthetopofthestacktotheoldestonthebottom.Incomplexprogramsiscanbeverybeneficialtoseewhichfunctioninvokedanotherfunctiontohelptracedownhowanerrorcameabout.

Understandingthestackisbeneficialformorethanjustreadingerrors.Let'sswitchtheconversationovertosomethingseeminglyunrelatedforabit.

RecursionWhenthinkingofloops,manyprogrammersfirstthinkofthe forlooporthe whileloop.Anothercommonmethodistomakeafunctioncallitself.Similartothe whileloop,youcancreateinfiniteloopslikethisone

localloop

loop=function()

print('hello!')

loop()

end

Whenafunctioninvokesitself,whetherdirectlyorindirectly,thisiscalledrecursion.Thesamefunctionwillrecuragainandagainuntilaconditionchanges.Orinthecaseabove, loop()willbecalledunconditionally.Withoutacondition,anykindofloopwillruninfinitely(orcrashtrying).Here'saloopthatisalittlesafertorun:

localcount_to_5

count_to_5=function(current_number)

print(current_number)

ifcurrent_number<5then

count_to_5(current_number+1)

end

end

count_to_5(1)

Whichprints:

1

2

3

4

5

Onequicklittleaside;Noticehowthefunctionwasdefinedinboththesesituations:

localloop

loop=function()

...

Thevariablewasdefinedbeforethefunctionwascreated.Sincethefunctionneedstoaccessthevariableinsideitself,thevariableneedstoexistatthetimethefunction'sscopeiscreated.Variablescreatedafterthefunctionareunknowntothefunction.Thisisdiscussedin1.17-ScopesandisalimitationofLua'sdesign.Fortunatelythereisshorthandsyntaxforwritingrecursivefunctions:

localfunctioncount_to_5(current_number)

print(current_number)

3.04-Stackandrecursion

156

Page 157: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

ifcurrent_number<5then

count_to_5(current_number+1)

end

end

count_to_5(1)

isthesameaswriting:

localcount_to_5

count_to_5=function(current_number)

...

Let'stryanotherrecursiveloop:

localgrocery_list={

'pumpkin',

'pecans',

'butter',

'flour',

'sugar'

}

localfunctionprint_items(list,index)

index=indexor1

ifindex<=#listthen

print(list[index])

print_items(list,index+1)

end

end

print_items(grocery_list)

Whichprintsthegrocerylist.Don'tforgetthe localatthebeginningof localfunctionprint_items,otherwiseyouwillaccidentallygenerateglobalvariablesinyourcodewhentryingtodefinefunctions.

Wecanevenre-implementour mapfunctionfromearliertouserecursioninsteadofa forloop.

localgrocery_list={

'pumpkin',

'pecans',

'butter',

'flour',

'sugar'

}

localfunctionmap(orig_list,transform_fn,new_list)

new_list=new_listor{}

if#new_list<#orig_listthen

localindex=#new_list+1

new_list[index]=transform_fn(orig_list[index],index)

returnmap(orig_list,transform_fn,new_list)

end

returnnew_list

end

localnew_list=map(grocery_list,function(value,index)

returnindex..'.'..value

end)

map(new_list,function(value)

print(value)

returnvalue

end)

3.04-Stackandrecursion

157

Page 158: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Whichprints:

1.pumpkin

2.pecans

3.butter

4.flour

5.sugar

StackoverflowSowhatdoesthestacklooklikeduringrecursionwhenafunctionentersitself?Here'sascripttotest:

localfunctionrecur(n)

--assertislikeerror,buttakesanexpressiontotest.Ifthe

--expressionpassedbecomesfalsethenitthrowstheerrormessage.

assert(n<5,'Thisisaconditionalerror')

print(n)

recur(n+1)

end

recur(1)

lua:test2.lua:2:Thisisaconditionalerror

stacktraceback:

[C]:infunction'assert'

test2.lua:2:inupvalue'recur'

test2.lua:4:inupvalue'recur'

test2.lua:4:inupvalue'recur'

test2.lua:4:inupvalue'recur'

test2.lua:4:inlocal'recur'

test2.lua:7:inmainchunk

[C]:in?

Everytimethefunctionrecurswegetanotheradditiontothestack.Thiscanbeaproblemifyouareloopingoveralargesetofdatabecausethestackwillconsumemoreandmorememoryasitstacksup.Thiscanbeaccomplishedbycreatingarecursiveloopthatrunsinfinitely.Ifyouhaven'ttriedsoalready,here'saneasyexample:

localfunctionrecur()

recur()

end

recur()

Whenthestackreachesacriticalsize,yougetastackoverflowerror:

lua:test3.lua:2:stackoverflow

stacktraceback:

test3.lua:2:inupvalue'recur'

test3.lua:2:inupvalue'recur'

...

test3.lua:2:inupvalue'recur'

test3.lua:2:inupvalue'recur'

test3.lua:2:inlocal'recur'

test3.lua:5:inmainchunk

[C]:in?

Withaspecific returnstatementaddedtotheloop,however,wenolongergetastackoverflow:

3.04-Stackandrecursion

158

Page 159: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localfunctionrecur()

returnrecur()

end

recur()

Thiswillrununtilyoumanuallykilltheapplicationprocess.Killingitreturnsasomewhatmysteriousstacktrack:

lua:test4.lua:2:interrupted!

stacktraceback:

test4.lua:2:infunction<test4.lua:1>

(...tailcalls...)

test4.lua:2:infunction<test4.lua:1>

(...tailcalls...)

test4.lua:5:inmainchunk

[C]:in?

Sohowdidourmodificationsaveusfromoverflowingourstack?

TailcalloptimizationInsideafunctionwhenyoureturnanotherfunctioncall,theinterpreterhastheabilitytore-usethesamelayerofthestackinsteadofcreatinganotherlayer.Thisworkswithdirectrecursion(functioncallingitself)andindirect(mutual)recursionsuchastwofunctionscallingeachother:

localone

localtwo

one=function()

returntwo()

end

two=function()

returnone()

end

one()

ProgramminginLuagoesintogreaterdetailonwhenarecursionwillorwon'tbeoptimized,butthesimplethingtorememberisthatthefunction(s)mustreturnthevalueofinvokingafunctionforthistowork.Thefollowingwillbetail-calloptimized:

localone

localtwo

one=function(n)

print(n)

returntwo(n+1)

end

two=function(n)

print(n)

returnone(n+1)

end

--Countuntilwerunoutofnumbers

one(1)

Butthefollowingwon't,sinceitreturnsanoperationincludingthefunctioncallinsteadofjustthefunctioncallitself:

3.04-Stackandrecursion

159

Page 160: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localone

localtwo

one=function(n)

print(n)

return1+two(n)

end

two=function(n)

print(n)

return1+one(n)

end

--Thiswon'twork!

one(1)

ThecaseforrecursiveloopsSowhywouldwewanttodorecursion?Itseemstrickierthana forloopandperhapsjustaseasytomessupasawhileloop.

It'snotnecessarilyareplacementforthe forloop,butallowsyoutodocertainthingsyoucan'teasilydowithoutrecursion.TakethisexamplefromRosettaCodewhichwillflattenalistoflistsintoasingle,flatlist.Itusesa forloopandarecursiveloopinconjunctionwitheachother:

localfunctionflatten(list)

iftype(list)~="table"thenreturn{list}end

localflat_list={}

for_,eleminipairs(list)do

for_,valinipairs(flatten(elem))do

flat_list[#flat_list+1]=val

end

end

returnflat_list

end

localtest_list={

{1},

2,

{{3,4},5},

{{{}}},

{{{6}}},

7,

8,

{}

}

print(table.concat(flatten(test_list),","))

Whichprints:

1,2,3,4,5,6,7,8

Thisfunctionisn'ttail-calloptimized,butitprobablywon'tbepassedanestedlistdeepenoughtocauseastackoverflow.

Here'sjustafewofthemanysituationswhererecursionisusuallythebesttoolforthejob:

SortingdataSearchingtrees(nesteddata)inadatabaseornestedfoldersinafilesystem.

3.04-Stackandrecursion

160

Page 161: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

FindingtheshortestpathbetweentwopointsLoopsthatincrementordecrementinirregularpatternsEvaluatingafinitesetofmovesinagamelikechess

Thepointisn'ttoreplacethe forloop,althoughyoucan.Takethefollowingexample,whichreturnsthefactorialofthegivennumber(5):

localfact=function(n)

localacc=1

foriteration=n,1,-1do

acc=acc*iteration

end

returnacc

end

print(fact(5))

Thesamefunctionalitywrittenwitharecursiveloopwouldlookverydifferent:

localfunctionfact(n,acc)

acc=accor1

ifn==0then

returnacc

end

returnfact(n-1,n*acc)

end

...butonemethodwouldn'tofferanadvantageovertheotherhere.Dependingonthelanguageyouareworkingin,onemethodmaybeeasiertoreadthantheother.Maybethelanguagesupportsonetypeofloopandnottheother.Thesearethefactorsthatwilloftendothedecidingforyou.

3.04-Stackandrecursion

161

Page 162: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Reduce(fold)Inprevioussectionswediscussedmanymethodsforiteratingoverdataandtransformingit.Inthissectionwe'lldiscussanotherhigherorderfunctionthatisarguablyoneofthemostpowerful.Itisaconceptrecognizedacrossenoughprogramminglanguagestogetitsownwikipediaarticle.Mostpopularlanguagescallitreduce,althoughsomelanguageswillcallitfoldorinject.Here'stheparametersittakes,andalthoughtheorderoftheparametersmaybedifferentinotherlanguagesthefunctionalityandoutputwillbethesame.

reduce(list,fn,starting_value)

Likewith map()and filter(),ittakesalistyouwanttotransformandafunction( fn)todothetransformation.Thetransformationfunctionbehaveslikearecursivelooplikeseeninthelastsection.Here'safunctionthattakesalistofnumbersandgivesyouthetotalsumofthosenumbers.

locallist={23,63,12,48,3}

localsum_fn=function(accumulator,current_number)

returnaccumulator+current_number

end

localtotal_sum=reduce(list,sum_fn,0)

Wepassreduceastartingnumberof 0.Whathappensis sum_fnisinvokedwiththefirstparameter,theaccumulatorbeingthestartingnumber0and current_numberbeingthefirstnumberinthelist.Whatevervaluethefunctionreturnsbecomesthenewvaluefor accumulatornextlooparound.

Luadoesn'thaveareducefunctionbuiltinsowe'llimplementourownherewithadetaileddescriptionofalltheparameters.Trynottogettoohungupontheactualreducefunction'simplementationatthetop,butratherfocusbelowthatonhowitworks.Therewillbeseveralmoreexamples.Onceyouunderstandhowtouseit,gobacktothetopandlookattheactual reducefunction'simplementation.CopyallthiscodeintothetexteditorwindowontheREPLandrunit:

--Appliesfnontwoargumentscumulativetotheitemsofthearrayt,

--fromlefttoright,soastoreducethearraytoasinglevalue.If

--afirstvalueisspecifiedtheaccumulatorisinitializedtothis,

--otherwisethefirstvalueinthearrayisused.

--@param{table}t-atabletoreduce

--@param{function}fn-thereducerforcomparingthetwovalues

--@param{*}acc-Theaccumulatoraccumulatesthecallback'sreturn

--values;Itistheaccumulatedvaluepreviouslyreturnedinthe

--lastinvocationofthecallback,or`first_value`,ifsupplied.

--@param{*}current_value-Thecurrentelementbeingprocessedinthelist.

--@param{number}current_index-Theindexofthecurrentelement

--beingprocessedinthelist,startingat1.

--@param{*}first_value-Theinitialvalueoftheaccumulation.Ifthearrayis

--empty,thefirst_valuewillalsobethereturnedvalue.Ifthearrayisempty

--andnofirstvalueisspecifiedanerrorisraised.

--@example

----returns'zxy'

--reduce(

--{'x','y'},

--function(a,b)returna+bend,

--'z'

--)

localfunctionreduce(t,fn,first)

localacc=first

3.05-Reduce

162

Page 163: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localstarting_value=first~=nil

fori,vinipairs(t)do

--Nostartingvalue,starton

--thefirstelementinthelist

ifstarting_valuethen

acc=fn(acc,v,i,t)

else

acc=v

starting_value=true

end

end

assert(

starting_value,

'Attemptedtoreduceanemptytablewithnofirstvalue.'

)

returnacc

end

locallist={23,63,12,48,3}

localsum_fn=function(accumulator,current_number)

print(accumulator)

returnaccumulator+current_number

end

localtotal_sum=reduce(list,sum_fn,0)

print('Thetotalsumis:',total_sum)

Followingthe printstatementinsideof sum_fn,wecanseethatthe accumulatorstartsoutwiththe0wepassin.Weadd current_numberto accumulatoranditbeginstoaccumulateallthevaluesasitgoes.

0

23

86

98

146

Thetotalsumis:149

Ifwedon'tpassinastartingnumber,theaccumulatorwillbeginrightawaywiththefirstnumberinthelist:

localsum_fn=function(accumulator,current_number)

print(accumulator)

returnaccumulator+current_number

end

localtotal_sum=reduce(list,sum_fn)

23

86

98

146

Thetotalsumis:149

Ifyou'veusedjavascript,youmaybestartingtoseetheuncannyresemblanceitbearstojavascript'sreducefunction.Bothlanguagesareverysimilarsyntactically,andgiventheubiquityofjavascriptthisLuaimplementationfollowsmuchofthesamebehavior.

Let'slookatsomemoreexamplestobetterunderstandhowtoreduceandwhatsituationsdoingsocouldproveuseful.Thereducefunctionisomittedinthefollowingexamples,butyoucancopyandpastethefunctionintheREPLalongsidetheexamplestorunthecodeyourself.

--Concatenatealistofwords

3.05-Reduce

163

Page 164: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

locallist={'this','is','a','sentence'}

localsentence=reduce(list,function(acc,word,index,list)

--Addaperiodifthisisthelastword

ifindex==#listthen

word=word..'.'

end

--Otherwiseaddaspacebetweenthewords

returnacc..''..word

end)

print(sentence)

thisisasentence.

--Onlykeepoddnumbers

locallist={23,63,12,48,3}

localodd_numbers=reduce(list,function(acc,current_number)

ifcurrent_number%2==0then

returnacc

end

acc[#acc+1]=current_number

returnacc

end,{})

forkey,valueinipairs(odd_numbers)do

print(value)

end

23

63

3

Thislookssimilartowhatwemightdowiththe filterfunctionpreviouslycoveredin3.3-Mapandfilter.Infact,wecancompose filterand mapfrom reduce.Takealookatthesamecoderefactoredout:

localfilter=function(list,predicate_fn)

returnreduce(list,function(acc,val,i,t)

ifpredicate_fn(val,i,t)then

acc[#acc+1]=val

returnacc

end

returnacc

end,{})

end

--Onlykeepoddnumbers

locallist={23,63,12,48,3}

localodd_numbers=filter(list,function(current_number)

returncurrent_number%2~=0

end)

forkey,valueinipairs(odd_numbers)do

print(value)

end

Anexampleofwrapping reducewithanew mapfunctionwon'tbeexplainedhere,butratherleftuptothereaderasanexerciseattheendofthissection.

3.05-Reduce

164

Page 165: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

Here'sonemoreexamplethatisabitmorecomplex,afunctioncalled composethatcreatesapipelineforpassingdatathrough.Itaccomplishesthisbypassinganyfunctionsyougiveitthroughto reduceasalist:

--Functionthatallowsyoutocomposeotherfunctions

--togethertoformapipeline.Theresultingpipeline

--isafunctionthatyoucanpassyourintendeddatathrough.

localcompose=function(...)

--"..."and"arg"arespecialkeywordsinLua.

--See:https://www.lua.org/pil/5.2.html

localfns=arg

returnfunction(x)

returnreduce(fns,function(acc,v)

returnv(acc)

end,x)

end

end

--Someexamplecomposablefunctions

localadd=function(x)

returnfunction(y)

returny+x

end

end

localmultiply=function(x)

returnfunction(y)

returny*x

end

end

localsubtract=function(x)

returnfunction(y)

returny-x

end

end

localnumber_pipeline=compose(add(12),multiply(2),subtract(9))

print(number_pipeline(3))

print(number_pipeline(2))

Alternativereduceimplementations

IteratingtablesLet'sgobacktotheimplementationofreduceforamoment.Takealookattheimplementationofitgivenabove.Noticetheiterationinsideisusing ipairswhichexpectsanarray/list-typetable.Ifwewantedtoreduceanon-listtablewecouldmodify reducetofirstcheckifthetableisanarrayanddoappropriateiterationoverthetablewhetherornotitis.Let'stestthat:

localfunctionreduce(t,fn,first)

localget_iterator=function(t)

iftype(t)=='table'then

--Ifpropertyof1isemptythen

--iterateasaregularkeyedtable

ift[1]==nilthen

returnpairs(t)

end

returnipairs(t)

end

error('Expectedtable,got'..tostring(t))

end

localacc=first

localstarting_value=first~=nil

--Whetherwedoipairsorpairsisconditional

3.05-Reduce

165

Page 166: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

fori,vinget_iterator(t)do

--Nostartingvalue,starton

--thefirstelementinthelist

ifstarting_valuethen

acc=fn(acc,v,i,t)

else

acc=v

starting_value=true

end

end

assert(

starting_value,

'Attemptedtoreduceanemptytablewithnofirstvalue.'

)

returnacc

end

locallist={

monday=23,

tuesday=63,

wednesday=12,

thursday=48,

friday=3

}

localtotal_sum=reduce(list,function(acc,current_number,key)

print(key..':'..current_number)

returnacc+current_number

end)

print('totalsum:'..total_sum)

Thisshouldprintsomethinglikethis:

wednesday:12

friday:3

thursday:48

monday:23

totalsum:149

Notethattheorderthekeysareiteratedinarenotguaranteed.Also"tuesday"wasn'tprintedoutbecauseitwasthestartingnumber,butitwasstillincludedinthetotal.Passinganextraargumentof 0to reducewouldhavecausedallthedaystobepassedthroughourreducerfunctionandprintedout.

Breakearly

Ok,here'sanotherexamplethatseemstrickyatfirstglance;Let'ssayyouimplementedsomesearchfunctionalityontopofreducelikethis:

locallist={23,63,12,48,3}

localfind=function(list,predicate_fn)

returnreduce(list,function(acc,v,i,t)

ifpredicate_fn(v,a,t)then

returnv

end

returnacc

end)

end

print(find(list,function(val)

returnval>50

end))

print(find(list,function(val)

3.05-Reduce

166

Page 167: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

returnval%8==0

end))

Whichprintsouttheexpectedresults:

63

48

Butdoyouseewhat'sproblematicaboutthis?Ifwefindtheresultswewant,thereducefunctionwillkeeprunningthroughtheentirelistunnecessarily.Typicallywhendoingasearchyouonlywantthefirstitemyoufindanyway,buttheaboveimplementationwillreturnthelastitemfoundifmorethanonematchismade.Doyourememberhowthereducefunctionpassesinthetableasthelastargumenttothereducerfunction?Wecantakecontrolofiteratorviathetableandkilltheiterationprematurely.Thisinvolvedmutatingthetable:

locallist={23,63,12,48,3}

localfind=function(list,predicate_fn)

returnreduce(list,function(acc,v,i,t)

ifpredicate_fn(v,a,t)then

--Ifaresultwasfound,destroythenextiteminthelist

--topreventtheiterationfromgoinganyfurther.

t[i+1]=nil

returnv

end

returnacc

end)

end

print(find(list,function(val)

returnval>1

end))

Thisreturnsthecorrectresult:

23

Butifweloopoverthetableafterwardswecanseewe'vemessedwiththeoriginaldatawhichcanleadtounexpectedconsequencesinarealapplication.Ifyourdataiscomingfromanimmutablesource,meaningsomethingisgeneratinganewcopyeachtimeyouuseitthenthiswouldn'tbeaproblem:

localgenerate_list=function()

return{23,63,12,48,3}

end

reduce(generate_list(),function()

...

...

Howeverwecouldfixallofthisifwearewillingtoaddanotherparametertoourreduceimplementation.

localfunctionreduce(t,fn,first)

localget_iterator=function(t)

iftype(t)=='table'then

--Ifpropertyof1isemptythen

--iterateasaregularkeyedtable

ift[1]==nilthen

returnpairs(t)

end

returnipairs(t)

3.05-Reduce

167

Page 168: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

end

error('Expectedtable,got'..tostring(t))

end

localacc=first

localstarting_value=first~=nil

fori,vinget_iterator(t)do

--Exittheloopwhentrue

localshould_break=false

--Nostartingvalue,starton

--thefirstelementinthelist

ifstarting_valuethen

acc,should_break=fn(acc,v,i,t)

ifshould_breakthen

break

end

else

acc=v

starting_value=true

end

end

assert(

starting_value,

'Attemptedtoreduceanemptytablewithnofirstvalue.'

)

returnacc

end

Nowifwepass trueasasecondreturnparameterthenwewillgetthefirstnumberwearelookingforinsteadofthelast.Loopthroughandprintoutthelistafterwardtomakesurewehaven'tmutateditunexpectedly.

locallist={23,63,12,48,3}

localfind=function(list,predicate_fn)

returnreduce(list,function(acc,v,i,t)

ifpredicate_fn(v,a,t)then

returnv,true

end

returnacc

end,false)

end

print(find(list,function(val)

returnval>1

end))

foridx,valinipairs(list)do

print(idx,val)

end

reduce_right

Anotherpossiblechangeyouwouldwanttomakeistoreplacetheiteratorwithacustom-madeonetotransformdatainaspecificorderorpattern.Takenfromlua-users.org'sIterationTutorialisthisreverse-ipairs( ripairs)implementationthatallowsyoutoiterateoveratablefromrighttoleft.Thismodifiedversionof reduceistypicallycalled reduce_right.

localfunctionreduce_right(t,fn,first)

localripairs=function(t)

localmax=1

whilet[max]~=nildo

max=max+1

end

localfunctionripairs_it(t,i)

i=i-1

3.05-Reduce

168

Page 169: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

localv=t[i]

ifv~=nilthen

returni,v

else

returnnil

end

end

returnripairs_it,t,max

end

localacc=first

localstarting_value=first~=nil

fori,vinripairs(t)do

--Exittheloopwhentrue

localshould_break=false

--Nostartingvalue,starton

--thefirstelementinthelist

ifstarting_valuethen

acc,should_break=fn(acc,v,i,t)

ifshould_breakthen

break

end

else

acc=v

starting_value=true

end

end

assert(

starting_value,

'Attemptedtoreduceanemptytablewithnofirstvalue.'

)

returnacc

end

Thenswapout reducefor reduce_rightintheplacesyouwanttouseit:

locallist={23,63,12,48,3}

localfind=function(list,predicate_fn)

returnreduce_right(list,function(acc,v,i,t)

ifpredicate_fn(v,a,t)then

returnv,true

end

returnacc

end,false)

end

print(find(list,function(val)

returnval>1

end))

Recursive

Sincewetalkedaboutrecursioninthelastsection,let'stryarecursiveimplementationof reduce.AlthoughwithLuathere'snopracticalreasontochoosearecursiveimplementationoverafor-looporwhile-loopimplementation,doingrecursionisfun.

localfunctionreduce(t,fn,acc,key)

--Checkforstartingvalue

ifkey==nilandacc==nilthen

key=next(t,key)

acc=t[key]

end

--Beginnextiteration.NextisaLuabuilt-infunction

--thatfetchesthenextkeyinatableafterthegivenkey.

3.05-Reduce

169

Page 170: Table of Contents - GitHub€¦ · [GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2 This is just telling you what programming language this REPL is loading, in this case, Lua. If you

--See:https://www.lua.org/pil/7.3.html

key=next(t,key)

--Returnaccifwe'veiteratedallkeys

ifkey==nilthen

returnacc

end

localbreak_early=false

--Collectnewaccumulatorfrompredicatefunction

acc,break_early=fn(acc,t[key],key,t)

--Checktoseeifthepredicatewantstoendearly

ifbreak_earlythen

returnacc

end

--Recur

returnreduce(t,fn,acc,key,acc)

end

--Testitbygettingthetotalsumfromatablelikebefore

locallist={

monday=23,

tuesday=63,

wednesday=12,

thursday=48,

friday=3

}

localtotal_sum=reduce(list,function(acc,current_number,key)

print(key..':'..current_number)

returnacc+current_number

end,0)

print('totalsum:'..total_sum)

Thissupportsbreakingearlylikethetwopreviousimplementations.

ExercisesCreatea countfunctionthatcountsupthenumberofitemsinalistthatmatchthepredicateandreturnsthetotal.Itshouldworklikethis:

localcount=function(list,predicate_fn)

????

end

locallist={23,63,12,48,3}

--Printnumberofitemsevenlydivisibleby3(shouldreturn4)

print(count(list,function(v)

returnv%3==0

end))

Gobacktothemapsectionin3.3andseeifyoucanreimplementthe mapfunctionontopof reduce.

3.05-Reduce

170