(Disclosure: I'm very new to Rails)
I am trying to make a RISK-style board-game in Rails, though this question may apply for any MVC-style framework.
I have players and games. Players can join games until the game is full. Trying to join a full game (or the same game twice) signals an error, yada yada.
Below are three pieces of game logic that I am unsure where to place, or at least where they are typically placed.
If a game is full, it should start and do things related to that (ie, messaging all players that the game has begun, randomly disperse armies across the map).
When a player executes moves during his turn it seems reasonable to have that logic in the controller. What about when his turn ends and it is time to message the next player? Would that code go in the same controller as
Suppose that a player forfeits his turn if he does not finish it within 24 hours. I'd need to periodically look at all the games in my app and see if a player started a turn more than 24 hours ago. Where would this logic go?
My question is: Where does logic for items like the above go in a Rails/MVC app?
In one sense I could stuff all of it except 3. into the controllers for the last-done-action. For instance I could place the logic for 1. in the player-joins-game controller method (check if the game is full after every player join, if it is, commence 1. related logic). This seems like it might be the wrong place, but maybe that's how it is typically done.
Rails' convention is "fat model, thin controller". so I would suggest that the state of the game should be held by the Game model.
You webapp consists of zero or more games, and each game consists of 1 or more players.
The state of "full", or the state of "game begun" are properties of the game, and should be held by that model.
So for 1) When the final player joins (or perhaps, all current players vote to start the game), the game state (a property of Game) would be set to "begun", the property that holds the currently active player would be set, and a delayed job would be queued to message all the players.
For 2, the game has a "execute move" method in the Game controller that would check that the player executing the move is the current player, then it would execute the move against the Game model. The Game model internally would know if the move is valid, what the result is, and what the next step(s) would be. It would, again, use something like a delayed job to message the next player.
For #3, again, a delayed job could be set to execute the timeout. I'm not 100% on how to schedule delayed jobs, or if there's another gem/plugin that would work better. But, the job would call a method on the Game controller to check the status of the game at the required time. If the player has not moved, then execute your forfit logic, which would, again, be a method in the Game model.
The state of each player could be held in the Player model, or in the Game model, I suppose, depending on the game, and how much interaction between Player models there might be.
In the case of a risk game, I would think the player model would be rather thin, as the state of the board is more about which player owns a country, and how many armies they have there - that's more a state of the game, then a state of each individual player. I would expect the Player model in a risk game to be more oriented towards metadata about the actual player - username, wins/losses, skill level, etc.
In a game like Supremacy, where the player has resources, nukes, etc., then there's more data to store in the Player model.
Related
In this blog from Firebase: https://firebase.blog/posts/2020/01/google-loteria-realtime-database they are talking about matchmaking.
This blog includes:
"When a player selects “Random Match,” their player ID is written to the location /queue/. A Cloud Function is set up to trigger when a new entry is created, and it checks whether we have enough players to start a game. If so, it creates the game at /games/{gameId}, and writes the gameId to each player's entry, like so: /players/{playerId}/gameId = {gameId}
Players listen to gameId on their own database entries, so they know when to transition to the gameplay screen.
Since there are a lot of players connecting simultaneously, and the Cloud Function is asynchronous, we once again used a transaction to ensure that players are removed from the queue and placed into games atomically. Making one game at a time atomically could create a bottleneck, so instead we create as many games as possible in one transaction."
How do I check there are enough players to start a game and how to atomically create as many games as possible in one transaction?
Thank you
I use the same structure with queue, games and players.
For example, I would have a main lobby in my Roblox game. Then, a player gets sent into another game and collects coins. When they come back to the lobby after they get a certain amount of coins, I want it to save their all time coins.
I would share code, but I have absolutely no idea how to do this.
Roblox "Games" can hold multiple "Places". Up to 20, I believe, including the "Starting Place". The neat thing about this is that datastores are saved across the entire game. As a result, if you save someone's data in place1, then they move to place2, their data will carry over.
To learn more about Games vs. Places, you can visit the developer API documentation.
EDIT: Alternatively, if you want data to save across multiple games, you would have to look into HTTPService, and off-roblox datastores.
I'm totally lost with how to implement a turn-based game. I've been trying to use the GKLocalPlayerListener methods to handle turn-based in iOS9. The only method that ever fires is receivedTurnEventForMatch, which leaves me with no method that I know of that calls an end game routine for each player. I am trying to handle turn-based matches inside of my app using the Game Center match maker view controller and delegate methods. I read that GKLocalPlayerListener methods work for matches when going through the actual Game Center app (Apple docs don't mention this). So if that's true, then GKLocalPlayerListener is not an option for my app?
What can I do to detect when a match ends? I want to keep a win-lose record for each player, so it's important that a routine is called for each player when a match ends.
The lifecycle of a turn based match looks like this:
Create a match
Invite others
Others join
Players take turns and pass the match object around (your game logic decides the order)
Players send exchanges back and forth (optional)
Players start leaving
----because they have been eliminated
----because they quit
----because they timed out
Someone Wins
If you are not the active player, you are notified when steps 3, 4, 7, 8 and 9 happen by playerReceivedTurnEventForMatch firing; however, as you can see from my answer here https://stackoverflow.com/a/34458493/1641444 playerReceivedTurnEventForMatch fires for a lot of different conditions, and it doesn't tell you which one triggered it. You have to discern that based on the status of the players, which player is the active player, and whatever other information you track in the match object.
You are notified of #5 by playerReceivedExchangeRequest firing (and Replies and Cancellation functions).
Your game logic will decide when to trigger #7. My preference is that the match object comes to the eliminated player, they are recognized as defeated, and calls participantQuitInTurnWithOutcome
Players decide when to trigger #8 by quitting, and the code calls either participantQuitInTurnWithOutcome or participantQuitOutOfTurnWithOutcome depending on their state.
Condition #9 is a real pain in the ass. Between limitations in the game design and outright bugs, timeouts can create several unrecoverable edge cases. Handling timeouts warrants its own full answer.
Finally, #10 is triggered by calling endMatchInTurnWithMatchData. GKTurnBasedEventHandlerDelegate and handleMatchEnded were deprecated in IOS7. When using GKLocalPlayerListner, you'll be notified of #10 by yet another occurrence of playerReceivedTurnEventForMatch
Edit--Clarifications based on followup questions:
Yeah, exactly. In your sendTurn function, when the game is over, don't call endTurnWithNextParticipant. Instead, set the each participant's status to indicate who won and who lost, and then call endMatchInTurnWithMatchData.
The other players will see playerReceivedTurnEventForMatch fire. You can discern from the match status and the player status that the game is over and decide what actions to take for that recipient.
To end a match, you should have your game call endMatchInTurnWithMatchData:completionHandler:
If you've implemented GKTurnBasedEventHandlerDelegate protocol on an object, your handleMatchEnded will get a push event.
More details in Apple's programming guide
Let's say I have a turn based match with two players. At some point player 1 recognizes that he is about to lose the game. When it is Player 1's turn, he uses Game Center App to do a swipe to remove the match.
Issues:
A. Take turn timer never expires on Player 1. So the match's turn will not switch to Player 2 when the time expired.
B. The game also offers a view only mode so players can view the game progress while he is out of turn. But since no status was updated to indicate that Player 1 had removed the match manually. App can offers no resolution. Also, you can only end match while it is your turn.
Ideally, I want to declare Player 2 as a winner and end the match.
How do you handle in this situation?
I finally found a workaround for this.
If you delete a match, then call GKTurnBasedMatch:loadMatchesWithCompletionHandler, the deleted match doesn't appear (as expected). However, it turns out that you can still re-download the deleted match using GKTurnBasedMatch:LoadMatchWithID, if you happen to still have the deleted match's ID.
I think we can reasonably assume that The Cheater is going to play the game again; otherwise, why would they care about incurring a loss? Therefore, I implemented the following:
Maintain a table locally, on the device, of matches.
On startup, pull the list of local player's matches from Game Center and compare against my local list.
When The Cheater recognizes the situation and deletes the match using the Game Center interface, the match is removed from Game Center, but not from my local DB. When The Cheater starts my game again, I see that they have more matches locally than on Game Center.
I then call either participantQuitInTurnWithOutcome or participantQuitOutOfTurnWithOutcome, as appropriate, with an outcome of GKTurnBasedMatchOutcomeLost.
This passes the turn to the next player and records a loss for The Cheater. It won't work if the cheater never plays the game again, though. (But, if they're not playing, they're not wrecking any more matches, so the chaos is contained)
I'm really hoping I'm wrong about this, but from what I can tell, GameKit hosted matchmaking simply does not work if a player invites some friends into a match but leaves some slots open for automatches. I can get each case to work individually, but when mixed, no one can connect.
Consider three players: A, B, and C. A invites B into the match and leaves a third slot open. C chooses to be matched into a three player game.
Now, when B processes the invite from A (by virtue of having registered an inviteHandler on GKMatchmaker), he contacts the hosting server to connect and sets his player ready state. Player A will then get a delegate message to matchmakerViewController:didReceiveAcceptFromHostedPlayer: saying that B has connected. In a two player game, B could at this point start the game and proceed. Instead, clicking the "Play Now" button causes the match-maker to wait for a third player to arrive.
Meanwhile Player C has chosen to be added to a match automatically. If A and B had also asked to be added automatically, all three would then be notified of the match via the delegate matchmakerViewController:didFindPlayers: method. Instead, however, clicking the "Play Now" button causes the match-maker to wait... forever. Player A is also waiting... forever. Player B knows only about the inviter, player A, so he could in theory try to start the game, but it would be pointless since A only knows about some of the participants.
It's hard for me to believe the system could have such a catastrophic flaw, but I've run this scenario, and traced all the delegate methods exposed in the relevant GameKit classes, and when the players get into their infinite-waiting state there are no delegate messages sent. It's as if GameCenter does not fully know about the existence of A and B, which would help explain why A has to explicitly mark B as present in the view controller despite obviously having been visible "enough" to have received the invite in the first place. I've also tried having player C request only a two-player game, on the idea that perhaps this meant that Player A counted as only one "visible" player.
Does anyone have any direct experience with this API, and can claim explicitly that it works (or doesn't) for this use case? This is a serious issue for me; this pretty much means that I have to throw away my GameKit-based matchmaking code and write something completely from scratch.
Thanks in advance for any information.