Walk through of Rummy Demo
Rummy Demo is a sample game developed for Android with backend built over AppWarpS2. Complete code is provided as part of the download.
Rummy is a traditional card game. Here are the rules of this sample game:
- Cards will be distributed by the server for a two or three user game
- In case of a two-user game, each user will get 9 cards
- In case of a three-user game, each user will get 6 cards
- A user can select a new card or can pick the top card only once in his turn
- After arranging the cards, a user has to do send move
- Users have to build combinations of 3 cards
- A valid combination is one in which either the cards are of the same Suit and in a Sequence or all the three cards have the same number
Example combinations
Same face value combination: {1,1,1} {K,K,K} {7,7,7}
Same color sequence combination: {1,2,3} {J,Q,K} {Q,K,1}
Note You first need to create an application zone through the admin dashboard. This is described on our Getting Started page.
Client Side
Android Client has three screens (Activity)
- MainActivity: User enters his name and connects to AppWarpS2 or he can also login with Facebook
- RoomSelectionActivity: User has option to join a two or a three user game. When user clicks any button, this first tries to join an existing room if not found it creates a new room and waits for other user/users to join the game
- GameActivity: This activity contains complete gameplay logic
- The rooms created from the client are turn-based rooms
How to play:
Please refer this video link
Server Work
To run this server side application, please first go through Running your first application. You need to follow the same steps for this sample as well. Following are details specific to this sample.
- Start the Game: The game is started when the number of joined users is equal to the desired number (2 or 3)
- Deal Cards: Server deals the cards and sends to every user only his cards
- Draw New Card: Server provides a new card when client requests a new card from the deck
- Validate Move: When client sends any move, the server will validate the cards in the move
- Finish Game: After receiving submit cards request, the server will validate the combinations and broadcast the result
Implementation
On server side we have defined extensions to override default server side functionality.
BaseServerAdapter: RummyServerExtension.java
BaseZoneAdapter: RummyZoneExtension.java
BaseTurnRoomAdaptor: RummyRoomExtension2User.java and RummyRoomExtension3User.java
Setting Adapter
Setting adapter is a necessary task to override default server side functionality. First we set BaseServerAdapter at the time of starting AppWarpS2.
String appconfigPath = System.getProperty("user.dir")+System.getProperty("file.separator")+"AppConfig.json"; boolean started = AppWarpServer.start(new RummyServerExtension(), appconfigPath); if(!started){ throw new Exception("AppWarpServer did not start. See logs for details."); }
we set zone adapter in RummyServerExtension.java as we receive notification onZoneCreated.
@Override public void onZoneCreated(IZone zone) { zone.setAdaptor(new RummyZoneExtension(zone)); }
and We set BaseTurnRoomAdaptor as we receive notification on creating room in RummyZoneExtension.
@Override public void handleCreateRoomRequest(IUser user, IRoom room, HandlingResult result) { if(room.isTurnBased() && room.getMaxUsers()==2){ room.setAdaptor(new RummyRoomExtension2User(izone, (ITurnBasedRoom)room)); }else if(room.isTurnBased() && room.getMaxUsers()==3){ room.setAdaptor(new RummyRoomExtension3User(izone, (ITurnBasedRoom)room)); } else{ result.code = WarpResponseResultCode.BAD_REQUEST; } }
Custom server side authentication
To check custom-auth we have logic in BaseZoneAdapter handleAddUserRequest i.e. if user selects Login with facebook, we check on server with Facebook Graph API, if user has the same user id from client facebook oauth token.
@Override public void handleAddUserRequest(final IUser user, final String authData, final HandlingResult result){ if(authData!=null && authData.length()>0){ result.code = WarpResponseResultCode.AUTH_PENDING;// indicates that response will be sent asynchronously new Thread(new Runnable() { @Override public void run() { checkForAuth(user, authData, result); } }).start(); } }
In checkForAuth we check using facebook Graph API whether user has the same fb_id as sent in auth_data.
if(response.get("id").equals(user.getName())){ // "Auth success on server" izone.sendAddUserResponse(user, WarpResponseResultCode.SUCCESS, "Auth success on server"); }else{ // "Auth Failed on server" izone.sendAddUserResponse(user, WarpResponseResultCode.AUTH_ERROR, "Auth failed on server"); }
Starting the Game
Game is started by server if joined users in room and max users in room are same. This is done using the startGame operation API of ITurnBasedRoom.
@Override public void handleTimerTick(long time){ if(GAME_STATUS==CardsConstants.STOPPED && gameRoom.getJoinedUsers().size()==gameRoom.getMaxUsers()){ GAME_STATUS=CardsConstants.RUNNING; dealNewCards(); gameRoom.startGame(CardsConstants.SERVER_NAME); } }
Handling move
When a client sends a move, the server validates it. To validate the move, server checks if the top card sent by the client is actually held by the sender or not.
@Override public void handleMoveRequest(IUser sender, String moveData, HandlingResult result){ int top_card =-1; JSONObject data = new JSONObject(moveData); top_card = data.getInt("top"); validateAndHandleMove(sender, top_card, result); .... } _validateAndHandleMove_ if(USER_1_HAND.indexOf(topCard)!=-1){ ... }
Check For Win
When a client submits his cards, then server checks if client has won the game or not on the basis of rules(defined above). If the client has won, server declares the client as winning user and broadcasts the result. If client cards are not the winning cards then server will send back a notification to the client.
boolean status = Utils.checkForWin(cardList); if(status){// for winning condition if(sender.getName().equals(user1_name)){ handleFinishGame(user1_name, cardList); }else if(sender.getName().equals(user2_name)){ handleFinishGame(user2_name, cardList); } }else{ String desc = CardsConstants.SUBMIT_CARD+"#"+"You don't have winning cards"; sender.SendChatNotification(CardsConstants.SERVER_NAME, desc, gameRoom); }
Miscellaneous
TurnBasedRoom
The RummyDemo uses AppWarpS2 TurnBasedRoom API. In this room, turn time is 180 seconds. User has to complete his turn in turn time otherwise his turn will expire. By default AppWarpS2 gives turn to next user. If you want to edit this functionality you can define logic in onTurnExpired of BaseTrunRoomAdapter
public void onTurnExpired(IUser turn, HandlingResult result) { }
Handling leaving user
If the game has started and a user leaves the game then in case of a two-user game, server declares the other user as winning user, and in case of a three user game, the server continues with the game and places leaving user’s cards in deck
@Override public void onUserLeavingTurnRoom(IUser user, HandlingResult result){ String leaveingUser = null; if(user.getName().equals(user1_name)){ leaveingUser = user1_name; }else if(user.getName().equals(user2_name)){ leaveingUser = user2_name; } String message = "You Win! Enemy "+leaveingUser+" left the room"; gameRoom.BroadcastChat(CardsConstants.SERVER_NAME, CardsConstants.RESULT_USER_LEFT+"#"+message); ... }
Connection Resiliency
Rummy Demo client has AppWarp Connection Resiliency feature. When the server receives onUserPause then it stops the game.
@Override public void onUserPaused(IUser user){ if(user.getLocation().getMaxUsers()==2){ RummyRoomExtension2User extension = (RummyRoomExtension2User)user.getLocation().getAdaptor(); extension.onUserPaused(user); } .... } _onUserPaused_ if(gameRoom.getJoinedUsers().contains(user)){ pausedUserList.add(user); GAME_STATUS = CardsConstants.PAUSED; gameRoom.stopGame(CardsConstants.SERVER_NAME); }
Server resume the game as it receives handleResumeUserRequest
@Override public void handleResumeUserRequest(IUser user, String authData, HandlingResult result){ if(user.getLocation().getMaxUsers()==2){ RummyRoomExtension2User extension = (RummyRoomExtension2User)user.getLocation().getAdaptor(); extension.onUserResume(user); } .... } _onUserResume_ if(pausedUserList.indexOf(user)!=-1){ pausedUserList.remove(user); } if(pausedUserList.isEmpty()){ GAME_STATUS = CardsConstants.RESUMED; }