Skip to main content

Rewarding players for playing every day in Corona SDK

Rewarding players for coming back and playing your game every day is nothing new, and is probably used in most successful games out there in one way or another. I don't want to miss an opportunity to keep my Ice Trap players for a little longer if possible, so I've implemented a simple daily rewards solution in Corona SDK to handle this.

My solution is the simplest possible really: Give the player a reward if he/she plays two days in a row. The reward will be the same every day regardless of the number of consecutive days played. I guess you could make this just as advanced as you like, with increasing rewards after a specific number of days, different rewards on different days and so on. But for the moment I don't see a need for that, and a flat reward will do just fine to start with.

Recently I implemented a concept in Ice Trap called level keys, allowing the player to unlock the next level if getting stuck. The player starts the game with a few keys, and can then earn new level keys while playing.

Allow the player to unlock the next level from level selection...
...and when getting tired of failing on some level.


Level keys make a perfect daily reward in my opinion. If a player tries to unlock a new level and is out of level keys, just pop up a dialog saying something like "Check back tomorrow to get 2 new level keys". This should hopefully entice the player to do just that, check back, instead of quitting and never play the game again.

Following is the full source code for my Corona SDK implementation of a dailyrewards module together with a small example of how to use it. Note that dailyrewards also requires the following modules, so make sure to get them too if you want to be able to use the code:


  • fileutil - A little helper class I've written myself to simplify working with local json files.
  • Lua Date - A date/time module for Lua which I got from here: https://github.com/wscherphof/lua-date. It's enough to download and require the date.lua file.


local fileutil = require("fileutil")
local date = require("date")
-- Configuration of the daily rewards, and an optional callback function. Will be injected in init() function
local _rewardsConfig
local _onReward
local _rewards = {
lastRewardDate = tostring(date()), -- Save an initial timestamp
pending = {
},
redeemed = {
}
}
local _filename = "daily-rewards.json"
local function _saveDailyRewards()
fileutil.saveUserFile(_filename, _rewards)
end
-- This function is normally only called when app is launched
-- Returns true if rewards were loaded from file, false if not
local function _loadDailyRewards()
local fileContents = fileutil.readUserFile(_filename)
if ( fileContents ) then
_rewards = fileContents
return true
else
return false
end
end
local function _validateRewardId(rewardId)
assert(_rewardsConfig[rewardId], "Invalid rewardId for daily reward: " .. rewardId)
end
--[[
Gets the value of a pending reward
--]]
local function getPendingReward(rewardId)
_validateRewardId(rewardId)
return _rewards.pending[rewardId] or 0
end
--[[
Checks if there's a pending reward. Convenience function to use instead of checking that getPendingReward(rewardId) > 0.
--]]
local function hasPendingReward(rewardId)
return (getPendingReward(rewardId) > 0)
end
--[[
Resets the value for given rewardId
Returns the pre-reset value of the pending reward
--]]
local function redeemPendingReward(rewardId)
_validateRewardId(rewardId)
-- Get and reset the pending reward
local rewardValue = _rewards.pending[rewardId] or 0
_rewards.pending[rewardId] = 0
-- Save a little information about the redeemed reward. Just for statistics and history purposes, not used anywhere right now...
if (not _rewards.redeemed[rewardId]) then
_rewards.redeemed[rewardId] = {}
end
_rewards.redeemed[rewardId][#_rewards.redeemed[rewardId] + 1] = {
timestamp = tostring(date(true)),
value = rewardValue
}
_saveDailyRewards()
return rewardValue
end
local function _addPendingReward(rewardId, value)
_validateRewardId(rewardId)
_rewards.pending[rewardId] = (_rewards.pending[rewardId] or 0) + value
end
local function _setDailyRewardsTimestamp()
_rewards.lastRewardDate = tostring(date())
end
--[[
This function does all the work of checking if there are any new rewards that should be given to the player, and saves them as pending.
--]]
local function checkForRewards()
print("Checking for daily rewards...")
local function getDaysSinceLastReward()
local lastCheck = date(_rewards.lastRewardDate)
local now = date()
-- Return the number of days passed since rewards were last given
return (math.floor(now:spandays()) - math.floor(lastCheck:spandays()))
end
local daysPassed = getDaysSinceLastReward()
-- Daily rewards are only given if a player plays two days in a row. If more time has passed, the timestamp will be reset.
if (daysPassed == 1) then
print("Will reward player for being loyal and playing two days in a row! :-)")
-- Create new pending rewards that can later be redeemed
for rewardId, rewardConfig in pairs(_rewardsConfig) do
if ((rewardConfig.isEligible == nil) or rewardConfig.isEligible()) then
_addPendingReward(rewardId, rewardConfig.value)
-- Make callback if a function was specified in init()
if (_onReward) then
_onReward(rewardId, rewardConfig.value)
end
else
print("Player not eligible for daily reward " .. rewardId .. "... :-(")
end
end
-- Set a new timestamp, and save the changes
_setDailyRewardsTimestamp()
_saveDailyRewards()
elseif(daysPassed > 1) then
print("No daily rewards to give... More than one day has passed since player was given daily rewards. Will reset timestamp.")
-- We only save a new timestamp so that the player can collect a daily reward tomorrow instead
_setDailyRewardsTimestamp()
_saveDailyRewards()
else
print("No daily rewards to give... Player has already been rewarded for this day.")
end
end
--[[
Initialize the module
rewardsConfig (required) - A table to configure the daily rewards. Note that a rewardId must exist as a key in this table to be able to call any of the {has/get/redeem}PendingReward() functions with that rewardId. Example config:
{
bombs = {
value = 5,
},
coins = {
value = 100,
-- Optional function to check additional conditions that must be fulfilled in order for player to be rewarded. Return true if reward should be given, false otherwise.
-- Not specifying isEligible is the same as an isEligible function that always returns true.
isEligible = function()
-- Player will not get 100 more coins if he has more than 1000.
return (player.coins <= 1000)
end,
}
}.
onReward (optional) - An optional callback function accepting two parameters rewardId and rewardValue that will be called when a new daily reward is created.
--]]
local function init(rewardsConfig, onReward)
assert((rewardsConfig ~= nil), "No rewardsConfig specified")
assert((next(rewardsConfig) ~= nil), "No rewards specified in rewardsConfig")
if ( not _loadDailyRewards() ) then
_saveDailyRewards()
end
_rewardsConfig = rewardsConfig
_onReward = onReward
end
return {
init = init,
checkForRewards = checkForRewards,
hasPendingReward = hasPendingReward,
getPendingReward = getPendingReward,
redeemPendingReward = redeemPendingReward,
}
local json = require("json")
local function readFile( filename, directory)
local filePath = system.pathForFile( filename, directory )
local theFile = io.open( filePath, "r" )
if theFile then
local contents = theFile:read( "*a" )
io.close( theFile )
local asJson = json.decode(contents)
return asJson
else
return nil
end
end
local function readResourceFile( filename )
return readFile( filename, system.ResourceDirectory)
end
local function readUserFile( filename )
return readFile( filename, system.DocumentsDirectory)
end
local function saveUserFile( filename, tableData )
local filePath = system.pathForFile( filename, system.DocumentsDirectory )
local userFile = io.open( filePath, "w" )
userFile:write( json.encode(tableData) )
io.close( userFile )
userFile = nil
end
local function fileExists(filename, baseDir)
assert(filename, "filename is missing")
assert(baseDir, "baseDir is missing")
local filePath = system.pathForFile( filename, baseDir )
local exists = false
if (filePath) then
-- File MAY exist. won't know for sure until it's actually opened...
local fileHandle = io.open( filePath, "r" )
if (fileHandle) then
exists = true
io.close(fileHandle)
end
end
return exists
end
local function resourceFileExists(filename)
return fileExists(filename, system.ResourceDirectory)
end
local function userFileExists(filename)
return fileExists(filename, system.DocumentsDirectory)
end
return {
resourceFileExists = resourceFileExists,
readResourceFile = readResourceFile,
userFileExists = userFileExists,
readUserFile = readUserFile,
saveUserFile = saveUserFile,
}
view raw fileutil.lua hosted with ❤ by GitHub
--[[
Example of using the dailyrewards module to rewards loyal players for playing two days in a row
When running this example for the first time, no rewards will be given.
To test that the rewards work, just open the daily-rewards.json file located in the {Project Sandbox}/Documents, update the lastRewardTimestamp to the day before and then run the example again.
Markus Ranner 2016
--]]
local dailyrewards = require("dailyrewards")
display.setStatusBar(display.HiddenStatusBar)
-- Setup a hard coded player object
local player = {
bombs = 10,
coins = 500,
}
-- The callback function when a reward is given
local onReward = function(rewardId, value)
print("There is a new pending '" .. rewardId .. "' reward waiting, value = " .. value)
-- If you want to reward the player immediately when a daily reward is created, do it here.
-- Otherwise, if you want to have more control over when to actually reward the player, make calls to dailyrewards.{has/get/redeem}PendingReward() instead. See the bottom of this example.
end
local rewardsConfig = {
bombs = {
value = 5,
},
coins = {
value = 100,
isEligible = function()
-- Player will not get 100 more coins if he already has more than 1000.
return (player.coins <= 1000)
end,
}
}
-- Init the daily rewards module with game specific settings
dailyrewards.init(rewardsConfig, onReward)
-- Setup a system event listener to check for daily rewards every time the app is launched or resumed from the background
local function onSystemEvent( event )
local eventType = event.type
if ( eventType == "applicationStart" or eventType == "applicationResume" ) then
dailyrewards.checkForRewards()
end
end
Runtime:addEventListener( "system", onSystemEvent )
-- After a second has passed, we check for any pending rewards.
timer.performWithDelay(1000, function()
if (dailyrewards.hasPendingReward("bombs")) then
-- Here we start by getting the reward value, using it to update the player, and then redeem the reward to that it can't be rewarded again.
local bombsReward = dailyrewards.getPendingReward("bombs")
player.bombs = player.bombs + bombsReward
dailyrewards.redeemPendingReward("bombs")
print("player.bombs = " .. player.bombs)
end
if (dailyrewards.hasPendingReward("coins")) then
-- But we can just as well redeem the reward directly and update the player afterwards, like this:
local coinsReward = dailyrewards.redeemPendingReward("coins")
player.coins = player.coins + coinsReward
print("player.coins = " .. player.coins)
end
end)
view raw main.lua hosted with ❤ by GitHub

Comments