How can I capture metrics from an emulated (SNES) game? - android-emulator

My goal is to emulate a game (e.g., Super Bombliss) using an Android emulator (e.g., Snes9x EX+) and to capture game metrics (e.g., score and level) as the game is played.
I assume I would need to modify the open-source emulator and/or to modify the ROM to do this, but I need some guidance on the best approach. Thanks!

To answer "How can I capture metrics from an emulated (SNES) game?" you need to first answer "Where can I find metrics in a SNES game?". The answer to the latter is "It depends on the game."
Think about GameShark codes, they write a value to a specific position of the memory.
It can write a value constantly to a memory position. For keeping you life bar always full on Mortal Kombat, for example.
It can write a value to a memory position just once. If you want to jump to last level of Sunset Riders.
But how do they know which memory position they should write to? That is the hard question. Usually it comes down to reverse engineering the ROM. You create a map of which memory position corresponds to each metric you are looking for.
Let's assume you want to find your current score and your current level on Super Mario World. A possible solution would be to constantly scan the RAM memory, looking for a known value and create a map of matches.
Knowing your score is 321, you can dump the RAM and look for where memory positions that contain 321. If only one matches, there is a very likely chance that it is the memory position that indicates your score. If you have more matches you should keep a list of all matches and keep playing until your score changes. If it changes to 567 now you check from your previous list if any of the memory positions now holds the value 567.
It is not always straight forward, for example, for the stage level 6-3. Should you look in the memory for 6 and for a 3 separately? What if the game stores the levels in just one variable represented as BCD 63. Then you would have to look for something completely different.
The only way to be sure 100% is by reverse engineering the ROM, in that way you know where the game goes fetch whatever is displayed on the screen.
Now to answer your original question "How can I capture metrics from an emulated (SNES) game?" you have to first figure out where those metrics are located in every game ROM and once you know you just need to build an emulator that listens to modifications in those memory positions so you can capture those metrics.
You can find many websites that are dedicated to reverse engineering ROMs. I suggest you take a look at the work done on http://www.romhacking.net/.

Related

Firebase analytics - Unity - time spent on a level

is there any possibility to get exact time spent on a certain level in a game via firebase analytics? Thank you so much 🙏
I tried to use logEvents.
The best way to do so would be measuring the time on the level within your codebase, then have a very dedicated event for level completion, in which you would pass the time spent on the level.
Let's get to details. I will use Kotlin as an example, but it should be obvious what I'm doing here and you can see more language examples here.
firebaseAnalytics.setUserProperty("user_id", userId)
firebaseAnalytics.logEvent("level_completed") {
param("name", levelName)
param("difficulty", difficulty)
param("subscription_status", subscriptionStatus)
param("minutes", minutesSpentOnLevel)
param("score", score)
}
Now see how I have a bunch of parameters with the event? These parameters are important since they will allow you to conduct a more thorough and robust analysis later on, answer more questions. Like, Hey, what is the most difficult level? Do people still have troubles on it when the game difficulty is lower? How many times has this level been rage-quit or lost (for that you'd likely need a level_started event). What about our paid players, are they having similar troubles on this level as well? How many people have ragequit the game on this level and never played again? That would likely be easier answer with sql at this point, taking the latest value of the level name for the level_started, grouped by the user_id. Or, you could also have levelName as a UserProperty as well as the EventProperty, then it would be somewhat trivial to answer in the default analytics interface.
Note that you're limited in the number of event parameters you can send per event. The total number of unique parameter names is limited too. As well as the number of unique event names you're allowed to have. In our case, the event name would be level_completed. See the limits here.
Because of those limitations, it's important to name your event properties in somewhat generic way so that you would be able to efficiently reuse them elsewhere. For this reason, I named minutes and not something like minutes_spent_on_the_level. You could then reuse this property to send the minutes the player spent actively playing, minutes the player spent idling, minutes the player spent on any info page, minutes they spent choosing their upgrades, etc. Same idea about having name property rather than level_name. Could as well be id.
You need to carefully and thoughtfully stuff your event with event properties. I normally have a wrapper around the firebase sdk, in which I would enrich events with dimensions that I always want to be there, like the user_id or subscription_status to not have to add them manually every time I send an event. I also usually have some more adequate logging there Firebase Analytics default logging is completely awful. I also have some sanitizing there, lowercasing all values unless I'm passing something case-sensitive like base64 values, making sure I don't have double spaces (so replacing \s+ with " " (space)), maybe also adding the user's local timestamp as another parameter. The latter is very helpful to indicate time-cheating users, especially if your game is an idler.
Good. We're halfway there :) Bear with me.
Now You need to go to firebase and register your eps (event parameters) into cds (custom dimensions and metrics). If you don't register your eps, they won't be counted towards the global cd limit count (it's about 50 custom dimensions and 50 custom metrics). You register the cds in the Custom Definitions section of FB.
Now you need to know whether this is a dimension or a metric, as well as the scope of your dimension. It's much easier than it sounds. The rule of thumb is: if you want to be able to run mathematical aggregation functions on your dimension, then it's a metric. Otherwise - it's a dimension. So:
firebaseAnalytics.setUserProperty("user_id", userId) <-- dimension
param("name", levelName) <-- dimension
param("difficulty", difficulty) <-- dimension (or can be a metric, depends)
param("subscription_status", subscriptionStatus) <-- dimension (can be a metric too, but even less likely)
param("minutes", minutesSpentOnLevel) <-- metric
param("score", score) <-- metric
Now another important thing to understand is the scope. Because Firebase and GA4 are still, essentially just in Beta being actively worked on, you only have user or hit scope for the dimensions and only hit for the metrics. The scope basically just indicates how the value persists. In my example, we only need the user_id as a user-scoped cd. Because user_id is the user-level dimension, it is set separately form the logEvent function. Although I suspect you can do it there too. Haven't tried tho.
Now, we're almost there.
Finally, you don't want to use Firebase to look at your data. It's horrible at data presentation. It's good at debugging though. Cuz that's what it was intended for initially. Because of how horrible it is, it's always advised to link it to GA4. Now GA4 will allow you to look at the Firebase values much more efficiently. Note that you will likely need to re-register your custom dimensions from Firebase in GA4. Because GA4 is capable of getting multiple data streams, of which firebase would be just one data source. But GA4's CDs limits are very close to Firebase's. Ok, let's be frank. GA4's data model is almost exactly copied from that of Firebase's. But GA4 has a much better analytics capabilities.
Good, you've moved to GA4. Now, GA4 is a very raw not-officially-beta product as well as Firebase Analytics. Because of that, it's advised to first change your data retention to 12 months and only use the explorer for analysis, pretty much ignoring the pre-generated reports. They are just not very reliable at this point.
Finally, you may find it easier to just use SQL to get your analysis done. For that, you can easily copy your data from GA4 to a sandbox instance of BQ. It's very easy to do.This is the best, most reliable known method of using GA4 at this moment. I mean, advanced analysts do the export into BQ, then ETL the data from BQ into a proper storage like Snowflake or even s3, or Aurora, or whatever you prefer and then on top of that, use a proper BI tool like Looker, PowerBI, Tableau, etc. A lot of people just stay in BQ though, it's fine. Lots of BI tools have BQ connectors, it's just BQ gets expensive quickly if you do a lot of analysis.
Whew, I hope you'll enjoy analyzing your game's data. Data-driven decisions rock in games. Well... They rock everywhere, to be honest.

Player matching in Google Play Services based on player ranking/skill

Is there a way I can match players using Google Play Game Service based on individual players' skill level in the game?
I have locally stored the player level of each player, and want a player to be matched to his/her closest ranked player.
For example: a player ranked 10 (beginner) should be paired with the closest ranked player available (e.g. 5 to 15) instead of an expert level 100 player, so that we can have a balanced competition.
There are two variables that can be set to influence the match making:
First, you can set a variant of the game using RoomConfig.Builder.setVariant(). This method takes an non-negative value indicating the type of match. The variant specified needs to match exactly with other participants in order for auto-matching to take place. I suppose you could be strict in your match making and use the variant as the player's level. In this way, players would only be matched with players of the same level. An alternative would be to group levels together in a range, for example levels 1-5 could play each other, likewise group 6-8, etc.
The second variable is the exclusiveBitMask. This is passed in when calling RoomConfig.CreateAutoMatchCritera(). This method takes the min and max number of players to match, and the exclusiveBitMask. This mask when logically AND'ed with the other players will equal 0. This is used for things like role based games (need to have 1 offense and 1 defense). One possible use of this would be to mask out high level vs. low level capabilities so there is no outrageous mismatch.
I think Clayton Wilkinson's answer is all correct and I voted it up.
But, I imagine the OP is hoping for some way to do skill-based matching without splitting the player-base into segments.
Sadly, the answer is no you can't. The choices are to use some other matchmaking system or split up your player base. If you choose to split your player base then you need a lot of concurrent users to avoid making your players wait a long time.
On a recent title we rolled our own matchmaking service based on Raknet because we wanted more nuanced matchmaking. It's a lot of hassle though, and GPGS is pretty great otherwise, so skill based matching would have to be a very high priority before you consider abandoning GPGS.

Puzzle Solver - TreeNode Help

I'm trying to code a puzzle solver app.
I need to find out how many moves it takes, and how many solutions there are.
I would rather not give too many details on the puzzle.
but the player moves around a grid ( say 5 x 7 )
as they move, obstacles could be captured so the state of the board needs to be tracked.
( this could be done as a string or an array )
I understand I need to create a TreeNode, starting with a root ( the players start position )
and give each node children of the possible moves until all the possible moves are calculated.
The puzzle stats could then be collected.
Number of Possible solutions, minimum number of moves to solve, average number of moves to solve, etc.
I have the puzzle logic created that will return if moves are possible and such.
I'm having problems creating the TreeNode structure and making sure moves are not duplicated.
The puzzle app itself is on the iPhone, but I'm writing this solver/editor on the Mac.
Any help would be VERY much appreciated.
Perhaps you could do a variant of a tree recursion? Traverse the tree recursively, having each end node return a value of how hard it was to get there (if there are costs associated with different moves) and a description of how it got there. This of course requires the player to only move in one direction, otherwise the tree-structure doesn't describe the problem. A bit more info on what your exact problem looks like would be helpful.
It might be a heavy algorithm, but it gets the job done.
For detecting repeated states, you would put the states in a set as you went along, and then check every time you found new states to see if they already existed. Though if space is an issue, you will have to resort to only checking if the children are not the same as the parent, or some kind of limited version of this approach.
A node class is very simple. It just contains a pointer back to a parent (if it has one) and the variable it holds (such as a state). You will also probably want other variables depending on your application.
When you get to a node, you use a successor function to get all the child nodes from there (the states that can be reached in one move) and add them to a list. You pluck from the list to traverse the tree.

How to get nearby locations from latitude/longitude?

I have store various latitude and longitude in database(Sqlite3) for various location. Now I have current latitude and longitude. How can I know nearest locations from current position.
Please suggest.
I assume you're talking about Reverse Geocoding. There's an Apple class provided for that, MKReverseGeocoder. There are also plenty of how-to discussions about that, such as here, here, etc.
The syntax will likely need to change, but take a look at this question that uses MySQL. Essentially you want to create a rectangle with the current point at the center (most likely). Using the bounds of this new box, you can run your SQL query.
I dare say #Kongress answer is the best here - but I'm just going to chuck this one into the ring as the concept itself is one that I've dealt with before.
I can't tell you how to build one in objective-c, but for our lat/long reverse lookup I built a K-DTree from the lat longs in our database ( > 250,000) and it gives us sub-100-nanosecond lookups on desktop hardware. On an iphone it's still likely to be pretty fast - memory might be a concern though as you really need to be able to cache the instance in memory, to be really useful; it takes a little while to build it (ours builds in about 1.5 seconds).
Just an idea.

Line Level Profiling for iPhone

I'm looking for a way to find out how much time is spent in each of my program's source line when running on the iPhone.Similar to what Shark can provide on the method/function level. Is this possible with the standard tools? Are there 3rd party tools that can provide this sort of granularity?
It wouldn't be necessary for profiling data for every line of source code in the project to be collected. Ideally one would be able to select specific methods or functions whose performance would be analyzed.
This link talks about how to gather trace data on an iPhone app, and that includes sampling the stack. Unfortunately, I could not tell from the doc if you can have samples drawn at random wall-clock times, or manually when you hit a key combination.
When you have traces, you can get a call tree, and that should get you line-level information. In fact the percent of time a line is responsible for is a simple number, the fraction of stack traces containing the line. The problem is, the UI may not show you that. The fact that that is a useful statistic is not well known.