How do you make a game to replenish its lives over time? - android

I'm using Action Script3 Flash pro cc. I have tried to write codes to my game so that it replenishes the player's lives over time. But I have had no success. For example, in Candy Crush, it's called 'lives'. You can have maximum of 5 lives. And once you start a level, it takes off 1 life from the 5 lives. And then the timer next to the lives label starts counting down from 20 minutes to 0. once it reaches 0, it gives you 1 life back so that you can start a level again. I tried to do this like this: Frame EventListener to count down from 20 minutes to 0 whenever the number of lives is under 5, and once it reaches 5, stop counting down numbers. It sounds simple and it is simple. But what if the player quits the game while the timer is counting down?? Let's say the player left the game at 3:10pm with 3 lives and 10 min left for a life replenish. And the player starts the game again at 3:30pm. Then the player would have 4 lives and 10 min left for a life replenish. I tried to accomplish this by using date class. But the remaining time gets messed up whenever I quit the game and restart the game.

Basically you need to keep track of the last time you gave a free life, then just compare the current time with the last time to see how many lives to give.
You'll need to store the lastFreeLifeTS timestamp somewhere, so you have 2 options:
Locally - Either a SharedObject or an XML file, depending on if you're on, say, the web, or using AIR for a mobile app. As #DodgerThud pointed out, this is easy enough to cheat, either by finding the file and modifying the value, or changing the system time. Candy Crush does this though, so you have to decide if this is an important enough issue for you
Online - Ideally, you would store this timestamp on a server, in which case, it's best to move this logic there as well, so it's the server that decides when you get a free live (either by telling the client, or the client can poll the server when it thinks it should have one)
Getting your timestamp is as simple as:
this.lastFreeLifeTS = ( new Date() ).time;
Call this when you either a) give a new free life, or b) use a life when you were at maximum (as then you want your next free life to come at the maximum time from that point).
Then you need to deal with 2 issues:
When the player should get a life in the game
When the player comes back after X amount of time
The first one is easy - just have a timer counting down (NOTE: you don't need to go once every frame - that's higher fidelity than's needed. Once every second or so is all you need). When your time is up, give the life:
var currTime:Number = ( new Date() ).time;
var diff:Number = ( currTime - this.lastFreeLifeTime );
var lifeTime:Number = 1000 * 60 * 20; // give a life every 20 minutes
if( diff > lifeTime )
{
giveFreeLife();
this.lastFreeLifeTime += lifeTime;
}
Seeing as you know when you're going to give a new life, you could almost replace it with a getTimer(), which would mean you don't need to keep creating new Date objects all the time.
// starting off
var lifeTime:Number = 1000 * 60 * 20; // 20 minutes
var msUntilNextLife:Number = ( ( new Date() ).time - this.lastFreeLifeTime ) + lifeTime;
this.nextFreeLifeTime = getTimer() + msUntilNextLife;
...
// check if we need to give a new life
if( getTimer() > this.nextFreeLifeTime )
{
giveFreeLife();
this.lastFreeLifeTime += lifeTime;
this.nextFreeLifeTime += lifeTime;
}
For the second one, you just need to check the timestamp when you start your game, and give any free lives necessary, then you're back to the first type of logic, above.
// load our SharedObject/XML and set our lastFreeLifeTime
...
// check how much time has passed and give any lives necessary
var currTime:Number = ( new Date() ).time;
var diff:Number = currTime - this.lastFreeLifeTime;
var lifeTime:Number = 1000 * 60 * 20; // 20 minutes
var numLives:int = int( diff / lifeTime );
if( numLives > 5 )
numLives = 5; // only give a maximum number of lives
if( numLives > 0 )
{
giveFreeLives( numLives );
// update our lastFreeLifeTime only if we didn't give the max number of lives
// (as otherwise we don't need it)
// NOTE: because we're adding (lifeTime * numLives), it handles the time between
// lives nicely. E.g. If we get a life every 20m and we come back after 30m, then
// our lastFreeLifeTime will be (20 * i), meaning a 10m difference between the
// current time, meaning we only have to wait 10m to get our next one
if( numLives < 5 )
this.lastFreeLifeTime += lifeTime * numLives;
}
Update - getting time from the internet
The easiest way to get timestamp from the net it to control the source yourself; i.e. you own the server/webpage that you're asking the timestamp from.
A simple solution is to host the following php code in a file somewhere:
<?php
echo round( microtime( true ) * 1000 );
?>
All this does is print the current timestamp to the page. You should be able then read the time using a URLLoader:
// NOTE: add all the other listeners, like IOErrorEvent, and SecurityErrorEvent
var urlLoader:URLLoader = new URLLoader( new URLRequest( "http://myserver.com/timestamp.php" ) );
urlLoader.addEventListener( Event.COMPLETE, onGetTime );
function onGetTime( e:Event ):void
{
// remove our event listener so we can clean up
var urlLoader:URLLoader = ( e.target as URLLoader );
urlLoader.removeEventListener( Event.COMPLETE, onGetTime );
// the data is the timestamp returned by php, as a string, so convert it
var timestamp:Number = Number( urlLoader.data );
if ( isNaN( timestamp ) )
{
trace( "Couldn't get the timestamp from the server! Returned details: " + urlLoader.data );
timestamp = ( new Date() ).time; // fallback to client
}
// do something with the time
}
Problems with this approach
It's slow; you're making a web request every time you need to know the current time. The time you get won't be the actual current time, due to latency. For most cases, this isn't a problem, though
You need internet connection; if the user's turned theirs off, this won't work
You may run into security problems and need to host a crossdomain.xml (maybe)
It's a lot of hassle; it's a lot more awkward than a simple (new Date()).time; you now need to host this file somewhere; if your game is successful, your server can get slammed pretty quickly depending on how many players you have and how many times you're calling this
It's up to you to decide whether or not it's worth it. If your game already has a server component, then this generally isn't a problem. I'd advise you to get it working just on the client first, and come back to it if necessary (i.e. you have a ton of players cheating)

Related

ISipRegistrationListener.OnRegistrationDone( ) giving too high values for expiration time

I'm successfully registering a SipProfile with 1 hour expiration. I see the REGISTER message in Wireshark on the SIP server machine, re-sent with authorization.
Brekeke replies immediately with a STATUS: 200 OK, passing back 'Expires:3600'.
Both Xamarin and Android docs tell me that lExpire should be duration in seconds before the registration expires, so it represents an interval. I want to expose received values, so multiply by TimeSpan.TicksPerSecond (to convert to TimeSpan), 3600 would result in 0.01:00:00:
void ISipRegistrationListener.OnRegistrationDone( string lclPrfUri, long lExpire )
{
long l= DateTime.Now.Ticks;
double d= (double)l / lExpire;
string s= string.Format( "RegSucced( '{0}', {1} ), {2}, {3}", lclPrfUri, lExpire,
new TimeSpan( lExpire * TimeSpan.TicksPerSecond )
.ToString( "d\\.hh\\:mm\\:ss" ), d );
..
}
But i'm getting lExpire values in the range of 1.5 trillions (1'541'195'345'242)!
As i saw it constantly growing with time, i thought it might be related to Ticks, so i exposed both and gathered the following statistics (H == T + 1 hour -- expected expiration):
# DT.Now.Ticks (T) lExpire (E) T/E ratio T+36000000000 (H) H/E ratio
1 636767624266839520 1541183611836 413167.918 636767660266839520 413167.941
2 636767669188704010 1541188122398 413166.738 636767705188704010 413166.761
3 636767670260843180 1541188229643 413166.710 636767706260843180 413166.733
4 636767670974718350 1541188301027 413166.691 636767706974718350 413166.715
5 636767693193745790 1541190522922 413166.110 636767729193745790 413166.133
And the ratios look surprisingly consistent, though the magic of 413166 escapes me.. And from that lExpire looks more like a reference to a point in time, than an interval, no?
But according to docs i should get 3600 without any scaling factors, right? What is going on??!
UPDATE (2019-Jan-25)
Finally got closer to an answer. Digging through Android source files (e.g. https://android.googlesource.com/platform/frameworks/base/+/431bb2269532f2514861b908d5fafda8fa64da79/voip/java/com/android/server/sip/SipService.java) i found the following fragment:
#Override
public void onRegistrationDone(ISipSession session, int duration) {
if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
synchronized (SipService.this) {
if (notCurrentSession(session)) return;
mProxy.onRegistrationDone(session, duration);
if (duration > 0) {
mSession.clearReRegisterRequired();
mExpiryTime = SystemClock.elapsedRealtime() + (duration * 1000);
..
SystemClock.elapsedRealtime() returns milliseconds since boot, including time spent in sleep.
Time in UNIX/Linux/Java is kept as number of seconds since epoch (1970-01-01T00:00:00Z).
Wikipedia's page shows Current Unix time as 1548450313 (2019-01-25T21:05:13+00:00), which is only a 1000 times (s-to-ms multiplier!) different in range from lExpire values i observe. The formula for mExpiryTime in the last line kinda gives hope.. "Eureka!"?
Let's check if they conform to "# of ms since epoch" theory:
DateTime dtEpoch = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc );
void ISipRegistrationListener.OnRegistrationDone( string lclPrfUri, long lExpire )
{
DateTime dt = dtEpoch.AddMilliseconds( lExpire ).ToLocalTime( );
string s= string.Format( "RegSucced( '{0}', {1}, {2} )", lclPrfUri, lExpire,
dt.ToString( "yyyy-MM-dd HH:mm:ss.fff" ) );
..
}
Yesterday's screenshots - with that addition (magenta arrows mark exposed values):
As you can see, the difference between lExpire converted into time and DateTime.Now (captured in the log) is negligible - in ms range!
I actually like the idea of being given an expiration moment instead of duration (which need to be added to an undefined starting point). Was about to answer my question..
But still, there are follow-up unresolved mysteries:
Why documentation says the argument is duration in seconds?
Xamarin may be just copying Android docs, but the source is way wrong!
Is this really how lame Android devs are? Reckon that should be rethorical ((..
Does anybody care that requested expiration period may be trumped [down] by PBX /SIP server, and thus they have to re-register more often?
All calls to .onRegistrationDone(session, duration) in the source files specify duration, not mExpiryTime.
There are constant definitions (EXPIRY_TIME = 3600, SHORT_EXPIRY_TIME = 10, MIN_EXPIRY_TIME = 60) and comparisons of duration to these..
Range of values is drastically different between the two (3600 vs 15 trillion).
How/where does mExpiryTime (expiration moment) make it into the argument of my method?
ms since boot and s (or ms) since epoch are still very different, what makes that adjustment?
and finally, asking a 1 hr expiration and receiving it in the OK reply from SIP server, i expect that to be reflected here too, so lExpire should be 1 hr in the future, not now!!
Any ideas?

Mana recovery issue

We're making a game in Android Studio and we got stuck. The resource (mana) used for specific spells should recover on time, e.g. 1 mana point per 5 minutes. We don't really get how to make it recover while the game is off. Is there a method to check current date/time and count the amount of mana replenished? Converting date and time to String and comparing it with the new date/time seems to be an "exciting" work to do, but we would bypass these mechanics if there is a way.
Thank you in advance.
The best way to do this in the background is to register a receiver in your manifest. This means the receiver will keep listening for broadcasts even if the app is off.
What you need is this particular action when registering your receiver Intent.ACTION_TIME_TICK
There is a more detailed answer about this matter here Time change listener
Another solution is to use the Calendar class in java. With it you can get the exact minutes passed from a point in the past to this moment. This way you don't have to worry about parsing dates and similar. I can't provide you specific examples because me myself have not used the Calendar class very much, but I'm sure you can find lots of stuff in the official documentation and on stackoverflow about it.
No need to work with Date objects, the simple usage of System.currentTimeMillis() should work. Here's a basic outline:
long mLastManaRefreshTime = System.currentTimeMillis();
void refreshMana()
{
long timeDelta = System.currentTimeMillis() - mLastManaRefreshTime;
mLastManaRefreshTime = System.currentTimeMillis();
float totalManaToRefresh = (float)AMOUNT_TO_REFRESH_IN_ONE_MINUTE * ((float)timeDelta / 60000f);
mMana += totalManaToRefresh;
if (mMana > MAX_MANA)
mMana = MAX_MANA;
}
This method is of course just an outline. You will need to call this once every update cycle. It will calculate how much time passed since the last time refreshMana was called, and replenish the required amount.
If you need this to work while the game is off, you can save the mLastManaRefreshTime to a SharedPreferences object and reload it when the game loads up again.
With System.currentTimeMillis() you can a current time-stamp in milliseconds.
You could save the latest time-stamp in your Preferences with every 5 min tick of the running game. For the other case, when your App comes back from a state where it does not do this (i.e. called the first time, woken up etc.).
Something like this:
int manacycles = ((int) (((System.currentTimeMillis() - oldtimestamp) / 1000) / 60) ) % 5;
would give you the number of Mana points you would have to add.
Alternately you could do the same thing with the Calendar class.
Also keep in mind players could cheat this way by simply changing their time. If your game is online you could get the time from the internet, with something like this:
try {
TimeTCPClient client = new TimeTCPClient();
try {
// Set timeout of 60 seconds
client.setDefaultTimeout(60000);
// Connecting to time server
// Other time servers can be found at : http://tf.nist.gov/tf-cgi/servers.cgi#
// Make sure that your program NEVER queries a server more frequently than once every 4 seconds
client.connect("nist.time.nosc.us");
System.out.println(client.getDate());
} finally {
client.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}

SensorEvent.timestamp inconsistency

my application performs in background step counting using the step detector sensor API's introduced in android 4.4.X.
It's essential to my app to know the exact time (at least accuracy of a second) each step event has accrued.
because I perform sensor batching , the time onSensorChanged(SensorEvent event) been called is not the same time when the step event took place - I must use the event.timestampfield to get the event time.
the documentation about this field is:
The time in nanosecond at which the event happened
The problem:
In some devices (such Moto X 2013) seems like this timestamp is time in nano seconds since boot, while in some devices (such Nexus 5) it's actually returns universal system time in nano seconds same as System.currentTimeMills() / 1000.
I understand, there's already an old open issue about that, but since sensor batching is introduced - it becomes important to use this field to know the event time, and it's not possible to rely anymore on the System.currentTimeMills()
My question:
What should I do to get always the event time in system milliseconds across all devices?
Instead of your "2-day" comparison, you could just check if event.timestamp is less than e.g. 1262304000000000000 - that way you'd only have a problem if the user's clock is set in the past, or their phone has been running for 40 years...
Except that a comment on this issue indicates that sometimes it's even milliseconds instead of nanoseconds. And other comments indicate that there's an offset applied, in which case it won't be either system time or uptime-based.
If you really have to be accurate, the only way I can see is to initially capture an event (or two, for comparison) with max_report_latency_ns set to 0 (i.e. non-batched) and compare the timestamp to the system time and/or elapsedRealtime. Then use that comparison to calculate an offset (and potentially decide whether you need to compensate for milliseconds vs nanoseconds) and use that offset for your batched events.
E.g. grab a couple of events, preferably a couple of seconds apart, recording the System.currentTimeMillis() each time and then do something like this:
long timestampDelta = event2.timestamp - event1.timestamp;
long sysTimeDelta = sysTimeMillis2 - sysTimeMillis1;
long divisor; // to get from timestamp to milliseconds
long offset; // to get from event milliseconds to system milliseconds
if (timestampDelta/sysTimeDelta > 1000) { // in reality ~1 vs ~1,000,000
// timestamps are in nanoseconds
divisor = 1000000;
} else {
// timestamps are in milliseconds
divisor = 1;
}
offset = sysTimeMillis1 - (event1.timestamp / divisor);
And then for your batched events
long eventTimeMillis = (event.timestamp / divisor) + offset;
One final caveat - even if you do all that, if the system time changes during your capture, it may affect your timestamps. Good luck!
I found a work-around solution that solving the problem. the solution assumes that the timestamp can be only one of the two: system timestamp, or boot time:
protected long getEventTimestampInMills(SensorEvent event) {
long timestamp = event.timestamp / 1000 / 1000;
/**
* work around the problem that in some devices event.timestamp is
* actually returns nano seconds since last boot.
*/
if (System.currentTimeMillis() - timestamp > Consts.ONE_DAY * 2) {
/**
* if we getting from the original event timestamp a value that does
* not make sense(it is very very not unlikely that will be batched
* events of two days..) then assume that the event time is actually
* nano seconds since boot
*/
timestamp = System.currentTimeMillis()
+ (event.timestamp - System.nanoTime()) / 1000000L;
}
return timestamp;
}
According to the link in your question:
This is, in fact, "working as intended". The timestamps are not
defined as being the Unix time; they're just "a time" that's only
valid for a given sensor. This means that timestamps can only be
compared if they come from the same sensor.
So, the timestamp-field could be completely unrelated to the current system time.
However; if at startup you were to take two sensor samples, without batching, you could calculate the difference between the System.currentTimeMillis() and the timestamp, as well as the quotient to the differences between the different times you should be able to convert between the different times:
//receive event1:
long t1Sys = System.currentTimeMillis();
long t1Evt = event.timestamp;
//receive event2:
long t2Sys = System.currentTimeMillis();
long t2Evt = event.timestamp;
//Unregister sensor
long startoffset = t1Sys - t1Evt; //not exact, but should definitely be less than a second, possibly use an averaged value.
long rateoffset = (t2Sys - t1Sys) / (t2Evt - t1Evt);
Now any timestamp from that sensor can be converted
long sensorTimeMillis = event.timestamp * rateoffset + startoffset;

Corona, creating an item in a loop using a timer, that needs an internal timer

so im currently giving app development a shot. Ive been programming in corona for nearly 2 weeks and have hit my first proper road block. My problem is that ive created an item ( called a pointball ( its a little circle ball that you need to tap )). the pointball is called every 2 seconds and is given random coords and an event loop for handling a tap event. My problem, is that i would like to make it so that after 4 seconds each pointball disappears, which i currently cannot do because if i do the timer inside the item it gets called once and the timer isn't called for long enough. If its outside it throws up an error as it cannot see the contents inside the class. Here is the code ( i know its messy, and i know its inefficient, please focus on the problem and not my horrible code style )
function spawnball()
local pointball = display.newImage("pointball.png")
pointball.x, pointball.y = math.random(30,spawnrange2),math.random(30,spawnrange)
pointball.type = "standard"
pointball.id = ("point")
physics.addBody(pointball, "dynamic", {friction=0, bounce = 0})
pointball.myName = ("destructible")
pointball.num = 0
pointball.plus = pointball.num + 1
pointball.touch = function( self,event )
if event.phase == "ended" then
self:removeSelf()
score1 = score1 + 1
typenum = typenum + 1
if typenum == 25 then
level = level + 1
typenum = 0
end
end
return true
end
pointball:addEventListener("touch", pointball)
end
end
function starter()
tutorial2 = false
timer.performWithDelay( 2000, spawnball, -1)
end
after the intro starter gets called and it spawns a ball every 2 seconds ( as seen with the ( 2000, spawnball, -1 ). Now i need a way to add a timer inside the actual class!! if you can help, i will be so grateful.
You can create a timer inside your spawning function:
timer.performWithDelay(4000, function() pointball:removeSelf() end, 1)
I don't know Corna, but assuming the 3rd arg for the timer is the number of repeats, this should execute 4000ms after it's created, and destroy the ball.

Corona SDK: black screen on Composer transition

-- hide device status bar
display.setStatusBar( display.HiddenStatusBar )
-- require controller module
local composer = require( "composer" )
-- load first scene
local scrOptions =
{
effect = "fromRight",
time = 2000
}
composer.gotoScene( "game", scrOptions )
--
-- Display objects added below will not respond to storyboard transitions
local MemUsageDisplay = display.newText( "0", 400, 25, native.systemFont, 20 )
MemUsageDisplay:setFillColor( gray )
local monitorMem = function()
local textMem = system.getInfo( "textureMemoryUsed" ) / 1000000
collectgarbage()
local date = os.date( "*t" )
MemUsageDisplay.text = date.hour .. ":" .. date.min .. ":" .. date.sec .. " / Lua: " .. math.round(collectgarbage("count")) .. "K " .. "Tex: " .. math.round(textMem*10) * 0.1 .. "MB"
end
timer.performWithDelay( 500, monitorMem, 0 )
In the simulator everything is fine.
On the device however the splashscreen flashes for less than a second, then the screen goes black for about 5 seconds, and then the game starts.
There is no transition.
I have to add that my game.lua contains a lot of code, but if I understand the docs correctly, all of that should be processed while the splashscreen is visible? I also ran the app while watching it in debugging mode (catlog...) and put some markers in it to see how fast the code executes. The whole game.lua is processed in less than a second.
Is this normal behavior?
What is the parameter required for composer.gotoScene( "game", scrOptions )?
You need to figure it out in composer library.
have you decrease the time and change the effect in your scrOptions array?
Just try this and let me know what are you getting.
So i can do the further investigation.
It sounds like to me that you are not creating your scene in the scene:create() event function but in the scene:show() event function. Your transition is set for 2 seconds and if you are not creating anything in scene:create() then there won't be anything to transition, but the transition will still take place, ergo going black for a couple of seconds.
Rob
Add this code
local scene = composer.newScene()
If everything is working fine in simulator, that means please check in your code whether your using the proper file name (i.e image name and scene names are correct) since simulator will take Image.png and image.png as same, but in device it will show error.

Categories

Resources