We demonstrate how the card game from Tutorial #03: Card Game can be created solely with Lua.
For this tutorial it is assumed that the reader already has basic knowledge about Lua programming.
- Note
- You need to add the optional Lua add-on to your project in order to be able to use Lua scripts. The easiest way to configure add-ons is by using the Murl Dashboard and the command Project / Configure Project.
Resource Package
We use a copy of the resources game.murlres
from tutorial #03.
Additionally we add script resources to the package and store them in a separate lua
folder.
data/packages/game.murlres/lua/card_game_logic.lua
data/packages/game.murlres/lua/card_game_card_instance.lua
The script resources have to be added to the package like any other resource file.
<?xml version="1.0" ?> <!-- Copyright 2014 Spraylight GmbH --> <Package id="game"> <!-- Animation resources --> <Resource id="anim_game_screen" fileName="anim_game_screen.xml"/> <!-- Bitmap resources --> <Resource id="gfx_clubs" fileName="gfx_clubs.png"/> <Resource id="planes_clubs" fileName="planes_clubs.xml"/> <Resource id="gfx_diamonds" fileName="gfx_diamonds.png"/> <Resource id="planes_diamonds" fileName="planes_diamonds.xml"/> <Resource id="gfx_hearts" fileName="gfx_hearts.png"/> <Resource id="planes_hearts" fileName="planes_hearts.xml"/> <Resource id="gfx_misc" fileName="gfx_misc.png"/> <Resource id="planes_misc" fileName="planes_misc.xml"/> <Resource id="gfx_spades" fileName="gfx_spades.png"/> <Resource id="planes_spades" fileName="planes_spades.xml"/> <!-- Font resources --> <Resource id="arial_color_24_glyphs" fileName="fonts/arial_color_24_glyphs.murl"/> <Resource id="arial_color_24_map" fileName="fonts/arial_color_24_map.png"/> <Resource id="arial_color_48_glyphs" fileName="fonts/arial_color_48_glyphs.murl"/> <Resource id="arial_color_48_map" fileName="fonts/arial_color_48_map.png"/> <!-- Graph resources --> <Resource id="graph_camera" fileName="graph_camera.xml"/> <Resource id="graph_game_card" fileName="graph_game_card.xml"/> <Resource id="graph_game_card_suit" fileName="graph_game_card_suit.xml"/> <Resource id="graph_game_screen" fileName="graph_game_screen.xml"/> <Resource id="graph_materials" fileName="graph_materials.xml"/> <Resource id="graph_textures" fileName="graph_textures.xml"/> <!-- Lua resources --> <Resource id="lua_card_game_logic" fileName="lua/card_game_logic.lua" enableCompression="yes"/> <Resource id="lua_card_game_card_instance" fileName="lua/card_game_card_instance.lua" enableCompression="yes"/> <!-- Graph instances --> <Instance graphResourceId="graph_materials"/> <Instance graphResourceId="graph_textures"/> <Instance graphResourceId="graph_camera"/> <!-- Script instances --> <Instance scriptResourceId="lua_card_game_logic"/> </Package>
The resource lua_card_game_logic
is instantiated with <Instance
scriptResourceId="lua_card_game_logic"/>
. This means that the script automatically will be added as a logic processer after the package has been loaded and is processed like any other logic processor.
Scene Graph
The second script resource lua_card_game_card_instance
is used as a script controller of a graph node.
<?xml version="1.0" ?> <!-- Copyright 2014 Spraylight GmbH --> <Graph> <Namespace id="{cardName}" activeAndVisible="no" controller="ScriptLogicController" controller.scriptResourceId="game:lua_card_game_card_instance"> <Transform id="position"> <Reference targetId="/textures/{cardName}"/> <SubState> <TextureState textureId="/textures/tex_misc"/> <Transform axisX="0" axisY="1" axisZ="0" angle="180 deg"> <Switch id="back_switch"> <Reference targetId="/textures/black_back"/> <Reference targetId="/textures/red_back"/> </Switch> </Transform> </SubState> <Button id="button" sizeX="90" sizeY="130"/> </Transform> </Namespace> </Graph>
Thus for every card graph instance also a lua_card_game_card_instance
script instance is created. After a graph node has been initialized the corresponding controller script is processed on every logic tick.
Application
A pure Lua application uses a small portion of generic C++ code to initially load the first Lua script. This script has to implement the IApp
class in Lua and needs to be stored in the packages folder.
data/packages/lua_card_game_app.lua
- Note
- Hint: A pure Lua application with all necessary code parts can easily be created with the Murl Dashboard by using the Create Project command and the Lua template.
The program flow is almost identical to the C++ program from tutorial #03.
-- Copyright 2014 Spraylight GmbH -- Open Lua standard libraries local luaAddon = Murl.Addons.Lua.Factory.GetAddon() luaAddon:OpenLibrary("math") luaAddon:OpenLibrary("string") luaAddon:OpenLibrary("table") luaAddon:OpenLibrary("utf8") -- Parameter for Murl.IApp local name = ... -- print("\nLUA IApp new '" .. name .. "'") -- Murl IApp callbacks local mainIApp = { Configure = function (self, engineConfig, fileInterface) -- print("LUA IApp Configure") local appConfig = engineConfig:GetAppConfiguration() local platformConfig = engineConfig:GetPlatformConfiguration() engineConfig:SetProductName("LuaCardGame") appConfig:SetWindowTitle("LuaCardGame powered by Murl Engine") appConfig:SetSystemDebugInfoItems(Murl.IEnums.STATISTIC_ITEM_FRAMES_PER_SECOND) if (platformConfig:IsTargetClassMatching(Murl.IEnums.TARGET_CLASS_COMPUTER)) then if (platformConfig:IsOperatingSystemMatching(Murl.IEnums.OPERATING_SYSTEM_WINDOWS)) then engineConfig:SetVideoApi(Murl.IEnums.VIDEO_API_DX90) end appConfig:SetDisplaySurfaceSize(1024, 768) appConfig:SetLockWindowAspectEnabled(true) appConfig:SetFullScreenEnabled(false) elseif (platformConfig:IsTargetClassMatching(Murl.IEnums.TARGET_CLASS_HANDHELD)) then -- set landscape orientation appConfig:SetScreenOrientation(Murl.IEnums.SCREEN_ORIENTATION_LANDSCAPE_1) -- enable landscape orientations appConfig:SetAllowedScreenOrientations(Murl.IEnums.SCREEN_ORIENTATIONS_LANDSCAPE) -- enable auto rotation appConfig:SetAutoRotationActive(true) appConfig:SetOrientationActive(true) -- enable multi touch appConfig:SetMultiTouchActive(true) end engineConfig:SetDeactivatedAppRunState(Murl.IEnums.APP_RUN_STATE_PAUSED) return true end, IsUserConfigurationMatching = function (self, userConfigId) -- print("LUA IApp IsUserConfigurationMatching", userConfigId) return false end, RegisterCustomAddonClasses = function (self, addonRegistry) -- print("LUA IApp RegisterCustomAddonClasses") return true end, UnregisterCustomAddonClasses = function (self, addonRegistry) -- print("LUA IApp UnregisterCustomAddonClasses") return true end, Init = function (self, appState) -- print("LUA IApp Init") local loader = appState:GetLoader() loader:AddPackage("startup", Murl.ILoader.LOAD_MODE_STARTUP) loader:AddPackage("game", Murl.ILoader.LOAD_MODE_BACKGROUND) return true end, DeInit = function (self, appState) -- print("\nLUA IApp DeInit") return true end } return Murl.IApp.new(mainIApp)
This script is loaded and executed immediately when the program starts. The script has to return a IApp
object.
- In the first section (line 4) Lua standard libraries are loaded.
- In the second section (line 11) the parameters are evaluated. The Lua application script gets exactly one parameter containing the name of the script resource.
- In the third section (line 15) a Lua function table is created. These functions act as callback functions and have the same meaning as the equally named C++
IApp
methods. - In the last section (line 92) an instance of the
IApp
class with the callback function table is created and returned.
Afterwards the Lua callback functions in mainIApp
are executed like otherwise the equally named C++ methods.
The function Configure()
(line 17) contains the same program as the C++ method in card_game_app.cpp
from tutorial #03. The same applies for the function Init()
(line 75) which loads the resource packages.
Lua programming syntactically slightly differs from C++ programming but almost all Murl classes are mapped to equally named Lua tables. A complete documentation of all Lua tables can be found in the download section in the MurlEngineLuaAPI archive.
Card Instance Script
Our game has 54 card instances where each can be controlled separately. To keep it simple we implement a script which can control exactly one card. This script is used as a graph node controller as shown above in the sub graph graph_game_card.xml
.
-- Copyright 2014 Spraylight GmbH -- Parameters for Logic IAppGraph local resourceId, nodeId = ... -- print("\nLUA IAppGraph new", resourceId, nodeId) -- Logic IAppGraph callbacks local logicIAppGraph = { mNodeObserver = Murl.Logic.StaticFactory.CreateNodeObserver(), mNamespaceNode = Murl.Logic.NamespaceNode.new(), mTransformNode = Murl.Logic.TransformNode.new(), mButton = Murl.Logic.ButtonNode.new(), mBackSwitch = Murl.Logic.SwitchNode.new(), mStepableObserver = Murl.Logic.StaticFactory.CreateStepableObserver(), mPositionAnim = Murl.Logic.AnimationVector.new(), mRotationAnim = Murl.Logic.AnimationVector.new(), mIsShowingFront = false, OnPostInit = function (self, state) -- print("LUA IAppGraph OnInit", nodeId) self.mNodeObserver:Add(self.mNamespaceNode:GetReference(state:GetCurrentGraphNode())) if (not self.mNamespaceNode:IsValid()) then return false end self.mNodeObserver:Add(self.mTransformNode:GetReference(self.mNamespaceNode:FindNode("position"))) self.mNodeObserver:Add(self.mButton:GetReference(self.mNamespaceNode:FindNode("button"))) self.mNodeObserver:Add(self.mBackSwitch:GetReference(self.mNamespaceNode:FindNode("back_switch"))) if (not self.mNodeObserver:AreValid()) then return false end self.mPositionAnim:AddKey(0.0, Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION), Murl.IEnums.INTERPOLATION_EASE_OUT) self.mPositionAnim:AddKey(1.0, Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION)) self.mRotationAnim:AddKey(0.0, Murl.Math.Vector.new(), Murl.IEnums.INTERPOLATION_LINEAR) self.mRotationAnim:AddKey(0.5, Murl.Math.Vector.new(), Murl.IEnums.INTERPOLATION_EASE_OUT) self.mRotationAnim:AddKey(1.0, Murl.Math.Vector.new()); self.mStepableObserver:Add(self.mPositionAnim:GetStepable()) self.mStepableObserver:Add(self.mRotationAnim:GetStepable()) self:SetBack(CARDBACKSIDE_BLACK) self:EnableButton(false) self:SetCardPosition(self.mTransformNode:GetPosition(), false, 0) self.mIsShowingFront = true return true end, OnPreDeInit = function (self, state) -- print("LUA IAppGraph OnDeInit", nodeId) local ret = true if (not self.mNodeObserver:RemoveAll()) then ret = false end return ret end, OnPreProcessTick = function (self, state) self.mStepableObserver:ProcessTick(state) if (self.mPositionAnim:IsOrWasRunning()) then self.mTransformNode:SetPosition(self.mPositionAnim:GetCurrentValue()) end if (self.mRotationAnim:IsOrWasRunning()) then local currentRotation = self.mRotationAnim:GetCurrentValue() self.mTransformNode:SetRotation(currentRotation.x, currentRotation.y, currentRotation.z) end end, SetObtained = function (self, isObtained) self.mNamespaceNode:GetNodeInterface():SetActiveAndVisible(isObtained) end, SetSortDepth = function (self, sortDepth) self.mTransformNode:SetDepthOrder(sortDepth) end, SetBack = function (self, backSide) if (backSide == CARDBACKSIDE_RED) then self.mBackSwitch:SetIndex(1) else self.mBackSwitch:SetIndex(0) end end, SetCardPosition = function (self, position, showFront, duration) self.mTransformNode:SetPosition(position) if (showFront) then if (not self.mIsShowingFront) then if (duration > 0) then self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, -Murl.Math.PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, -Murl.Math.HALF_PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, 0, 0, 0)) self.mRotationAnim:ModifyKeyTime(1, duration / 2) self.mRotationAnim:ModifyKeyTime(2, duration) self.mRotationAnim:StartForward() else self.mTransformNode:SetRotation(0, 0, 0) end end else if (self.mIsShowingFront) then if (duration > 0) then self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, 0, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, Murl.Math.HALF_PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) self.mRotationAnim:ModifyKeyTime(1, duration / 2) self.mRotationAnim:ModifyKeyTime(2, duration) self.mRotationAnim:StartForward() else self.mTransformNode:SetRotation(0, Murl.Math.PI, 0) end end end self.mIsShowingFront = showFront end, MoveCardToPosition = function (self, position, showFront, duration) self.mPositionAnim:SetKey(0, 0.0, self.mTransformNode:GetPosition(), Murl.IEnums.INTERPOLATION_EASE_OUT) self.mPositionAnim:SetKey(1, duration, position) self.mPositionAnim:StartForward() if (showFront) then if (mIsShowingFront) then self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, 0, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, -1.0, 0.25, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, 0, 0, 0)) else self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, Murl.Math.HALF_PI, 0.5, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, 0, 0, 0)) end else if (mIsShowingFront) then self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, 0, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0, Murl.Math.HALF_PI, 0.5, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) else self.mRotationAnim:ModifyKeyValue(0, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) self.mRotationAnim:ModifyKeyValue(1, Murl.Math.Vector.new(0.5, Murl.Math.PI - 0.5, 0.5, 0)) self.mRotationAnim:ModifyKeyValue(2, Murl.Math.Vector.new(0, Murl.Math.PI, 0, 0)) end end self.mRotationAnim:ModifyKeyTime(1, duration / 2) self.mRotationAnim:ModifyKeyTime(2, duration) self.mRotationAnim:StartForward() self.mIsShowingFront = showFront end, IsMoving = function (self) return self.mPositionAnim:IsOrWasRunning() end, EnableButton = function (self, enable) self.mButton:SetEnabled(enable) end, WasPressed = function (self) return self.mButton:WasPressed() end, } -- add this instance to the global card instances table table.insert(mCards, logicIAppGraph) return Murl.Logic.IAppGraph.new(logicIAppGraph)
This script is automatically created and processed when the corresponding graph node is loaded. The script should return a Logic.IAppGraph
object.
- In the first section (line 4) the Lua parameters are evaluated. The Lua controller script gets exactly two parameters. The first parameter is the name (id) of the script resource and the second parameter is the name (id) of the controller node.
- In the second section (line 8) a lua function table is created. This functions act as callback functions and have the same meaning as the equally named C++
Logic::IAppGraph
methods. - In the third section (line 175) the function table is added to a global table
mCards
to allow the logic code to access the individual card instances. - Finally (line 177) an instance of the
Logic.IAppGraph
class is created and returned
The functions and variables of the logic.IAppGraph
table correspond to the methods and member variables of the card_game_card_instance.cpp
from Tutorial #03.
Script Processor
The logic processor instance is instantiated directly in the package.xml
as already shown above.
-- Copyright 2014 Spraylight GmbH -- Parameters for Logic IAppProcessor local resourceId, replication = ... -- print("\nLUA IAppProcessor new", resourceId, replication) -- the global table holding all card instances mCards = {} CARDBACKSIDE_BLACK = 0 CARDBACKSIDE_RED = 1 -- Logic IAppProcessor callbacks local logicIAppProcessor = { STATE_IDLE = 0, STATE_DEAL = 1, STATE_PLAY = 2, mStateMachine = 0, mCardsPerSuit = 13, mCardSizeX = 90, mCardSizeY = 130, mScreenTimeline = Murl.Logic.TimelineNode.new(), mGameInfoText = Murl.Logic.TextGeometryNode.new(), mStackButton = Murl.Logic.ButtonNode.new(), mRng = Murl.Util.TT800.new(), mCardDistribution = Murl.UInt32Array.new(), mCardsToDeal = Murl.SInt32Array.new(), mCardStack = Murl.SInt32Array.new(), mCardTray = Murl.SInt32Array.new(), mPlayfield = Murl.SInt32Array.new(), mGameStartTimeout = Murl.Logic.Timeframe.new(), mGameEndTimeout = Murl.Logic.Timeframe.new(), mDealCount = 0, mNumberOfGamesPlayed = 0, OnInit = function (self, state) -- print("\nLUA IAppProcessor OnInit", resourceId) state:GetLoader():UnloadPackage("startup") local root = state:GetGraphRoot() local processor = state:GetCurrentProcessor() processor:AddGraphNode(self.mScreenTimeline:GetReference(root, "/game_screen/screen_timeline")) processor:AddGraphNode(self.mGameInfoText:GetReference(root, "/game_screen/info_text")) processor:AddGraphNode(self.mStackButton:GetReference(root, "/game_screen/stack_button")) if (not processor:AreGraphNodesValid()) then return false end self.mScreenTimeline:Start(0.0, 0.5) processor:AddStepable(self.mGameStartTimeout:GetStepable()) processor:AddStepable(self.mGameEndTimeout:GetStepable()) return true end, OnDeInit = function (self, state) -- print("\nLUA IAppProcessor OnDeInit", resourceId) local ret = true return ret end, OnProcessTick = function (self, state) if (self.mScreenTimeline:WasRunning()) then self:EnterDeal(state) end local deviceHandler = state:GetDeviceHandler() if (deviceHandler:WasRawButtonPressed(Murl.RAWBUTTON_BACK)) then deviceHandler:TerminateApp() end --print("State", self.mStateMachine) if (self.mStateMachine == self.STATE_DEAL) then self:ProcessTickDeal(state) elseif (self.mStateMachine == self.STATE_PLAY) then self:ProcessTickPlay(state) end end, OnRunStateChanged = function (self, state, currentState, previousState) -- print("\nLUA IAppProcessor OnRunStateChanged", resourceId) if (self.mGameStartTimeout:IsOrWasRunning()) then return end if (currentState == Murl.IEnums.APP_RUN_STATE_PAUSED) then self.mGameInfoText:SetText("- Paused -") if (self.mStateMachine == self.STATE_PLAY) then self:UpdatePlayfield(0.0, true) end elseif (currentState == Murl.IEnums.APP_RUN_STATE_RUNNING) then self.mGameInfoText:SetText("- Play -") if (self.mStateMachine == self.STATE_PLAY) then self:UpdatePlayfield(0.3, false) end end end, EnterDeal = function (self, state) -- print("\nLUA IAppProcessor EnterDeal", resourceId) self.mStateMachine = self.STATE_DEAL self.mNumberOfGamesPlayed = self.mNumberOfGamesPlayed + 1 self:ShuffleCards(self.mNumberOfGamesPlayed % 3) self.mDealCount = 0 self.mGameStartTimeout:Start(1.0) self.mGameInfoText:SetText("") end, ProcessTickDeal = function (self, state) -- print("LUA IAppProcessor ProcessTickDeal", resourceId) if (self.mGameStartTimeout:IsRunning()) then return end if (self.mCardsToDeal:GetCount() > 0) then local index = self.mCardsToDeal:Pop() local n = self.mCardStack:GetCount() self.mCardStack:Add(index) mCards[index + 1]:SetObtained(true) mCards[index + 1]:SetSortDepth(n + 1) mCards[index + 1]:SetCardPosition(self:GetStackPosition(n), false, 0) mCards[index + 1]:EnableButton(false) if ((self.mNumberOfGamesPlayed % 2) == 0) then mCards[index + 1]:SetBack(CARDBACKSIDE_RED) else mCards[index + 1]:SetBack(CARDBACKSIDE_BLACK) end elseif (self.mDealCount < self.mPlayfield:GetCount()) then local index = self.mCardStack:Pop() local showFront = (self.mDealCount >= 18) mCards[index + 1]:SetSortDepth(self.mDealCount + 1000) mCards[index + 1]:MoveCardToPosition(self:GetPlayfieldPosition(self.mDealCount), showFront, 0.5) mCards[index + 1]:EnableButton(showFront) self.mPlayfield[self.mDealCount] = index self.mGameStartTimeout:Start(0.1) self.mDealCount = self.mDealCount + 1 else self:MoveToTray(self.mCardStack:Pop()) self:UpdateStackButton() self:EnterPlay(state) end end, EnterPlay = function (self, state) -- print("\nLUA IAppProcessor EnterPlay", resourceId) self.mStateMachine = self.STATE_PLAY self.mGameInfoText:SetText("- Play -") end, ProcessTickPlay = function (self, state) -- print("LUA IAppProcessor ProcessTickPlay", resourceId) if (self.mGameEndTimeout:IsRunning()) then return end if (self.mGameEndTimeout:WasRunning()) then self:EnterDeal(state) return end local canAnyMoveToTray = false local isPlayfieldEmpty = true for i, index in Murl.ipairs(self.mPlayfield) do if (index >= 0) then isPlayfieldEmpty = false if (mCards[index + 1].mIsShowingFront) then local canMoveToTray = self:CanMoveToTray(index) if (canMoveToTray) then canAnyMoveToTray = true end if (mCards[index + 1]:WasPressed()) then if (canMoveToTray) then self:MoveToTray(index) self.mPlayfield[i] = -1 self:UpdatePlayfield(0.3, false) end end end end end if (isPlayfieldEmpty) then -- Pyramid complete self.mGameInfoText:SetText("Congratulations!") self.mGameEndTimeout:Start(3.0) elseif (self.mCardStack:GetCount() > 0) then if (self.mStackButton:WasPressed()) then self:MoveToTray(self.mCardStack:Pop()) self:UpdateStackButton() end else if (not canAnyMoveToTray) then -- No more moves self.mGameInfoText:SetText("Game Over") self.mGameEndTimeout:Start(3.0) end end end, UpdateStackButton = function (self) local additionalSizeX = self.mCardStack:GetCount() * 4 if (additionalSizeX > 0) then self.mStackButton:SetScaleFactorX(self.mCardSizeX + additionalSizeX) self.mStackButton:SetScaleFactorY(self.mCardSizeY + 4) local position = self:GetStackPosition(0) position.x = position.x + (additionalSizeX / 2) self.mStackButton:GetTransformInterface():SetPosition(position) end end, GetStackPosition = function (self, index) local position = Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION) position.x = -400 + (index * 4) position.y = -260 return position end, GetTrayPosition = function (self, index) local position = Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION) position.x = 200 + (index * 2) position.y = -260 return position end, GetPlayfieldPosition = function (self, index) local position = Murl.Math.Vector.new(Murl.Math.Vector.ZERO_POSITION) local DISTANCE_X = self.mCardSizeX + 10 local DISTANCE_Y = self.mCardSizeY - 50 if (index < 3) then position.x = (index * 3) * DISTANCE_X - (6 * DISTANCE_X / 2) position.y = DISTANCE_Y + DISTANCE_Y / 2 elseif (index < 9) then position.x = (index - 3) * DISTANCE_X - (7 * DISTANCE_X / 2) position.x = position.x + (Murl.Math.Floor((index - 3) / 2) * DISTANCE_X) position.y = DISTANCE_Y / 2 elseif (index < 18) then position.x = (index - 9) * DISTANCE_X - (8 * DISTANCE_X / 2) position.y = -DISTANCE_Y / 2 else position.x = (index - 18) * DISTANCE_X - (9 * DISTANCE_X / 2) position.y = -DISTANCE_Y - DISTANCE_Y / 2 end position.y = position.y + 100 return position end, IsCardFree = function (self, index) -- Playfield index -- 0 1 2 -- 3 4 5 6 7 8 -- 9 10 11 12 13 14 15 16 17 -- 18 19 20 21 22 23 24 25 26 27 local left = -1 if (index < 3) then left = (index * 2) + 3 elseif (index < 9) then left = (Murl.Math.Floor((index - 3) / 2)) + index + 6 elseif (index < 18) then left = index + 9 end local right = -1 if (left >= 0) then right = left + 1 end local free = true if (left >= 0 and left < self.mPlayfield:GetCount()) then if (self.mPlayfield[left] >= 0) then free = false end end if (right >= 0 and right < self.mPlayfield:GetCount()) then if (self.mPlayfield[right] >= 0) then free = false end end return free end, CanMoveToTray = function (self, index) local isValid = false local trayCard = self.mCardTray:Top() if (trayCard >= 4 * self.mCardsPerSuit or index >= 4 * self.mCardsPerSuit) then -- Joker isValid = true else local playCard = index % self.mCardsPerSuit local trayCardPlus = (trayCard + 1) % self.mCardsPerSuit local trayCardMinus = (trayCard + self.mCardsPerSuit - 1) % self.mCardsPerSuit if (playCard == trayCardPlus or playCard == trayCardMinus) then isValid = true end end return isValid end, MoveToTray = function (self, index) local n = self.mCardTray:GetCount() self.mCardTray:Add(index) mCards[index + 1]:SetSortDepth(n + 2000) mCards[index + 1]:MoveCardToPosition(self:GetTrayPosition(n), true, 0.5) mCards[index + 1]:EnableButton(false) end, UpdatePlayfield = function (self, duration, isPause) for i, index in Murl.ipairs(self.mPlayfield) do if (index >= 0) then local isFree = false if (not isPause) then isFree = self:IsCardFree(i) end local card = mCards[index + 1] card:SetSortDepth(i + 1000) card:SetCardPosition(self:GetPlayfieldPosition(i), isFree, duration) if (isFree) then card:EnableButton(true) end end end end, ShuffleCards = function (self, numberOfJokers) for _, card in ipairs(mCards) do card:SetObtained(false) end if (numberOfJokers > 2) then numberOfJokers = 2 end self.mCardDistribution:Empty() self.mCardDistribution:SetCount(#mCards - 2 + numberOfJokers, 1) self.mCardsToDeal:Empty() for _ in Murl.ipairs(self.mCardDistribution) do local index index, self.mCardDistribution = self.mRng:DrawNoReplacement(self.mCardDistribution) self.mCardsToDeal:Add(index) end self.mCardStack:Empty() self.mCardTray:Empty() self.mPlayfield:Empty() self.mPlayfield:SetCount(28, -1) end } return Murl.Logic.IAppProcessor.new(logicIAppProcessor)
This script is automatically loaded and processed when the corresponding resource package is loaded. The script should return an instance of the Logic.IAppProcessor
class.
- In the first section (line 4) the parameters are evaluated. The Lua script gets exactly two parameters. The first parameter is the name of the script resource and the second parameter is the replication number.
- In the second section (line 14) a Lua function table is created. The functions act as callback functions and have the same meaning as the equally named C++ methods of the
Logic::IAppProcessor
class. - Finally (line 364) an instance of the
Logic.IAppProcessor
class is created and returned.
The functions and variables of the logicIAppProcessor
table correspond to the methods and member variables of the card_game_logic.cpp
from Tutorial #03.
The most obvious difference between the Lua implementation and the C++ implementation is the creation and initialization of the card instances, which in the Lua implementation occur automatically when the graph nodes are created.