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

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.

Related

How to make items belong to someone in roblox?

I was thinking about making a game and couldn't think of a way to to this:
Let's say, for example, you want to make rideable horses in your game, but only the player that owns a certain horse can ride it.
I thought about giving the horses different names and then assigning them to players. Obviously, It would be many horses, so the amount of names... don't even want to think about that. Also then I'm facing a problem: How to automatize the process, so every new player can get thier horse with no problems?
Several ways of going about this:
Use datastores and have an array of horses for each user. Horses will want to be named by a unique name/id. See https://create.roblox.com/docs/scripting/data/data-stores for datastore docs.
Every time you wish to check if a player owns a horse you can query the datastore. The advantages of doing it this way is that it will be saved for each time the player rejoins.
You could also save the array of players horses inside a value instance inside the player object, again assigning each horse a unique name/id. Then reference this instance each time you wish to check if a player owns a horse. The drawback of this approach is that the data won't save between sessions.
Finally and the method I would recommend is a mixture of the two approaches. When a player joins save their horses inside a datastore but also in a value instance as in solution 2. Then during the gameplay you can reference the value instance. When the player leaves and/or every couple of minutes you could save the content of the value instance to the datastore.The reason I would recommend this approach is because you could end up being limited by the number of requests you can make to Roblox's datastores and datastores can get pretty complicated at time especially when it comes to pcalls. In general you should avoid making too many datastore requests in a short space of time.

Modeling Multiple Depot Vehicle Scheduling Problem in OR-Tools

Is it possible to solve the Multiple Depot Vehicle Scheduling Problem (MDVSP) in OR-tools?
The problem is detailed in this paper, but here is a brief summary.
We are given a set of depots and the number of available vehicles at each. We are given a set of timetabled trips, and we know the origin, destination, start time, end time, and a set of depots that can serve a given trip. While connecting two trips, that is assigning a vehicle to serve for two trips sequentially, there may be an unoccupied travel, so called a dead-head trip. There are also dead-heads while going from a depot to the first trip and returning from the last trip to a depot. The objective is to minimize the sum of all dead-head trip costs while ensuring each trip is served by exactly one vehicle and the number of vehicles used does not exceed the availability. (Other trips/links, i.e., occupied trips, must in any case need to be served/traversed; so, there is no need to include them in the objective).
Seems you want to take into account the arc cost only if vehicle is empty. (note: fixed typo)
AFAIK, there is no easy way to do it using OR-Tools. In C++ you may use the DimensionDependentDimension and returning the arc cost if a capacity dimension is zero, and zero otherwise...
Also I'm curious why you would like to only count dead-trip e.g. if the overall vehicle route is several time longer with very few dead-trip instead of a shorter route with few dead-trip why would you want to incentive the first one ?
e.g. a route of 100km with 1km dead-trip is two time better than a 50km route with 2km dead-trip...
For multiple depots please take a look at
vrp_starts_ends.py
For TimeWindows: vrp_time_windows.py
Did you take a look at the documentation ?
e.g. https://developers.google.com/optimization/routing/cvrp
using routing.NextVar(A).SetValues([A, B]) you can force the chain A->B
ref: https://github.com/google/or-tools/blob/49b6301e1e1e231d654d79b6032e79809868a70e/ortools/constraint_solver/routing.h#L1364-L1366
note: Solver won't have the possibility to use A->C->B even if is shorter than A->B->C or C->A->B (supposing TW allow both of them...)

Search for common objects in a two circle intersection area

I'm solving a task that can be described in a series of points:
Two user types: Fighter, Arena.
Each Fighter is free to set their geo-location and their preferable search radius in the process of registration (later they can change these values in their settings). The search radius, as it follows from the name, is the radius within which the user can search for a potential opponent.
Each Arena object has its geo-location and time slots of availability.
Two fighters match if: their search circles intersect and the intersection area contains at least one Arena.
Arena is available to a Fighter if it lays within their search radius.
I'm in the process of implementing the matching logic. So there's a need in implementing the match algorithm. And I'm struggling to work out the right approach.
To the best of my understanding, everything can be reduced to looking for common Arenas. In other words, there could be a number of tables that would provide information on which Arenas are available to each Fighter. That's to say, for example a Fighter with id 98 has Arenas with ids 34, 57, 22, another user with id 17 has Arenas with ids 156, 57.
userId = 98; available_arenas = [34, 57, 22];
userId = 17; available_arenas = [156, 57];
By comparing the ids of available Arenas we can see that they have got one Arena in common, its id is 57. So I assume that based on this fact, it would be safe to say that their search circles intersect and there's one Arena where a fighting event may take place.
I thought that this approach could be used to determine whether Fighters match or not. I would like to but I can't say whether the search based on this idea will be efficient or not and I see the following difficulty with it: all those Arena lists should be supported for each Fighter/Arena. So, if, let's say, some new Arena gets added to the DB, figuratively speaking, all those Fighter's available_arenas lists must be immediately updated...which may also be a very time consuming operation. If a new Fighter is registered, their personal list of available Arenas must be calculated. If an already existing Fighter changes its geo-location, their Arenas must be recalculated as well etc.
Another approach would probably be to do about the same but without storing those Arenas ids in the DB. Doing all the calculations on the fly, given the (long, lat, r) params for a Fighter and (long, lat) for an Arena.
I'm sure there are some better approaches out there. I'd like to hear what you think about mine and if you know a better approach I'd like to know it too.
I'm using PostgreSQL 13 for a DBMS.
I think there aren't many solutions, provided this fighter-arena structure. You may try to group your arena positions (by regions, anchor positions or similar) to see if you could reduce the weight of the search.
Another idea could be to store also an available-fighters list for each arena. Every time a fighter travels, it checks for new arenas and for those not available anymore, it can send them a message to be erased from the list. Then all possible fights in this arena are all possible combinations in its available-fighters

Firebase database Retrieve high score rank of a user [duplicate]

I have project that I need to display a leaderboard of the top 20, and if the user not in the leaderboard they will appear in the 21st place with their current ranking.
Is there efficient way to this?
I am using Cloud Firestore as a database. I believe it was mistake to choose it instead of MongoDB but I am in the middle of the project so I must do it with Cloud Firestore.
The app will be use by 30K users. Is there any way to do it without getting all the 30k users?
this.authProvider.afs.collection('profiles', ref => ref.where('status', '==', 1)
.where('point', '>', 0)
.orderBy('point', 'desc').limit(20))
This is code I did to get the top 20 but what will be the best practice for getting current logged in user rank if they are not in the top 20?
Finding an arbitrary player's rank in leaderboard, in a manner that scales is a common hard problem with databases.
There are a few factors that will drive the solution you'll need to pick, such as:
Total Number players
Rate that individual players add scores
Rate that new scores are added (concurrent players * above)
Score range: Bounded or Unbounded
Score distribution (uniform, or are their 'hot scores')
Simplistic approach
The typical simplistic approach is to count all players with a higher score, eg SELECT count(id) FROM players WHERE score > {playerScore}.
This method works at low scale, but as your player base grows, it quickly becomes both slow and resource expensive (both in MongoDB and Cloud Firestore).
Cloud Firestore doesn't natively support count as it's a non-scalable operation. You'll need to implement it on the client-side by simply counting the returned documents. Alternatively, you could use Cloud Functions for Firebase to do the aggregation on the server-side to avoid the extra bandwidth of returning documents.
Periodic Update
Rather than giving them a live ranking, change it to only updating every so often, such as every hour. For example, if you look at Stack Overflow's rankings, they are only updated daily.
For this approach, you could schedule a function, or schedule App Engine if it takes longer than 540 seconds to run. The function would write out the player list as in a ladder collection with a new rank field populated with the players rank. When a player views the ladder now, you can easily get the top X + the players own rank in O(X) time.
Better yet, you could further optimize and explicitly write out the top X as a single document as well, so to retrieve the ladder you only need to read 2 documents, top-X & player, saving on money and making it faster.
This approach would really work for any number of players and any write rate since it's done out of band. You might need to adjust the frequency though as you grow depending on your willingness to pay. 30K players each hour would be $0.072 per hour($1.73 per day) unless you did optimizations (e.g, ignore all 0 score players since you know they are tied last).
Inverted Index
In this method, we'll create somewhat of an inverted index. This method works if there is a bounded score range that is significantly smaller want the number of players (e.g, 0-999 scores vs 30K players). It could also work for an unbounded score range where the number of unique scores was still significantly smaller than the number of players.
Using a separate collection called 'scores', you have a document for each individual score (non-existent if no-one has that score) with a field called player_count.
When a player gets a new total score, you'll do 1-2 writes in the scores collection. One write is to +1 to player_count for their new score and if it isn't their first time -1 to their old score. This approach works for both "Your latest score is your current score" and "Your highest score is your current score" style ladders.
Finding out a player's exact rank is as easy as something like SELECT sum(player_count)+1 FROM scores WHERE score > {playerScore}.
Since Cloud Firestore doesn't support sum(), you'd do the above but sum on the client side. The +1 is because the sum is the number of players above you, so adding 1 gives you that player's rank.
Using this approach, you'll need to read a maximum of 999 documents, averaging 500ish to get a players rank, although in practice this will be less if you delete scores that have zero players.
Write rate of new scores is important to understand as you'll only be able to update an individual score once every 2 seconds* on average, which for a perfectly distributed score range from 0-999 would mean 500 new scores/second**. You can increase this by using distributed counters for each score.
* Only 1 new score per 2 seconds since each score generates 2 writes
** Assuming average game time of 2 minute, 500 new scores/second could support 60000 concurrent players without distributed counters. If you're using a "Highest score is your current score" this will be much higher in practice.
Sharded N-ary Tree
This is by far the hardest approach, but could allow you to have both faster and real-time ranking positions for all players. It can be thought of as a read-optimized version of of the Inverted Index approach above, whereas the Inverted Index approach above is a write optimized version of this.
You can follow this related article for 'Fast and Reliable Ranking in Datastore' on a general approach that is applicable. For this approach, you'll want to have a bounded score (it's possible with unbounded, but will require changes from the below).
I wouldn't recommend this approach as you'll need to do distributed counters for the top level nodes for any ladder with semi-frequent updates, which would likely negate the read-time benefits.
Final thoughts
Depending on how often you display the leaderboard for players, you could combine approaches to optimize this a lot more.
Combining 'Inverted Index' with 'Periodic Update' at a shorter time frame can give you O(1) ranking access for all players.
As long as over all players the leaderboard is viewed > 4 times over the duration of the 'Periodic Update' you'll save money and have a faster leaderboard.
Essentially each period, say 5-15 minutes you read all documents from scores in descending order. Using this, keep a running total of players_count. Re-write each score into a new collection called scores_ranking with a new field players_above. This new field contains the running total excluding the current scores player_count.
To get a player's rank, all you need to do now is read the document of the player's score from score_ranking -> Their rank is players_above + 1.
One solution not mentioned here which I'm about to implement in my online game and may be usable in your use case, is to estimate the user's rank if they're not in any visible leaderboard because frankly the user isn't going to know (or care?) whether they're ranked 22,882nd or 22,838th.
If 20th place has a score of 250 points and there are 32,000 players total, then each point below 250 is worth on average 127 places, though you may want to use some sort of curve so as they move up a point toward bottom of the visible leaderboard they don't jump exactly 127 places each time - most of the jumps in rank should be closer to zero points.
It's up to you whether you want to identify this estimated ranking as an estimation or not, and you could add some a random salt to the number so it looks authentic:
// Real rank: 22,838
// Display to user:
player rank: ~22.8k // rounded
player rank: 22,882nd // rounded with random salt of 44
I'll be doing the latter.
Alternative perspective - NoSQL and document stores make this type of task overly complex. If you used Postgres this is pretty simple using a count function. Firebase is tempting because it's easy to get going with but use cases like this are when relational databases shine. Supabase is worth a look https://supabase.io/ similar to firebase so you can get going quickly with a backend but its opensource and built on Postgres so you get a relational database.
A solution that hasn't been mentioned by Dan is the use of security rules combined with Google Cloud Functions.
Create the highscore's map. Example:
highScores (top20)
Then:
Give the users write/read access to highScores.
Give the document/map highScores the smallest score in a property.
Let the users only write to highScores if his score > smallest score.
Create a write trigger in Google Cloud Functions that will activate when a new highScore is written. In that function, delete the smallest score.
This looks to me the easiest option. It is realtime as well.
You could do something with cloud storage. So manually have a file that has all the users' scores (in order), and then you just read that file and find the position of the score in that file.
Then to write to the file, you could set up a CRON job to periodically add all documents with a flag isWrittenToFile false, add them all to the file (and mark them as true). That way you won't eat up your writes. And reading a file every time the user wants to view their position is probably not that intensive. It could be done from a cloud function.
2022 Updated and Working Answer
To solve the problem of having a leaderboards with user and points, and to know your position in this leaderboards in an less problematic way, I have this solution:
1) You should create your Firestorage Document like this
In my case, I have a document perMission that has for each user a field, with the userId as property and the respective leaderboard points as value.
It will be easier to update the values inside my Javascript code.
For example, whenever an user completed a mission (update it's points):
import { doc, setDoc, increment } from "firebase/firestore";
const docRef = doc(db, 'leaderboards', 'perMission');
setDoc(docRef, { [userId]: increment(1) }, { merge: true });
The increment value can be as you want. In my case I run this code every time the user completes a mission, increasing the value.
2) To get the position inside the leaderboards
So here in your client side, to get your position, you have to order the values and then loop through them to get your position inside the leaderboards.
Here you can also use the object to get all the users and its respective points, ordered. But here I am not doing this, I am only interested in my position.
The code is commented explaining each block.
// Values coming from the database.
const leaderboards = {
userId1: 1,
userId2: 20,
userId3: 30,
userId4: 12,
userId5: 40,
userId6: 2
};
// Values coming from your user.
const myUser = "userId4";
const myPoints = leaderboards[myUser];
// Sort the values in decrescent mode.
const sortedLeaderboardsPoints = Object.values(leaderboards).sort(
(a, b) => b - a
);
// To get your specific position
const myPosition = sortedLeaderboardsPoints.reduce(
(previous, current, index) => {
if (myPoints === current) {
return index + 1 + previous;
}
return previous;
},
0
);
// Will print: [40, 30, 20, 12, 2, 1]
console.log(sortedLeaderboardsPoints);
// Will print: 4
console.log(myPosition);
You can now use your position, even if the array is super big, the logic is running in the client side. So be careful with that. You can also improve the client side code, to reduce the array, limit it, etc.
But be aware that you should do the rest of the code in your client side, and not Firebase side.
This answer is mainly to show you how to store and use the database in a "good way".

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

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/.