
Fool App for the Windows Store

Paul Cezanne, "Card Players"
Once upon a time, in Windows 95 there was a Microsoft Hearts game. Playing cards online, with opponents around the world. If memory serves me right, then in Windows for Workgroups 3.11 (yes, I found all these artifacts!) There was a version for playing on a local network using the so-called NetDDE.
I did not have to choose a card game for a long time. As they say, what is rich ... The high-flown bridge and preference fell away due to their complete ignorance. There was only one thing
The situation was complicated by the fact that so far I have never been involved in the development of a “backend”. Googling led me right to the right place - SignalR .
Here I want to say a few enthusiastic words to SignalR. A well-documented library that fits my needs perfectly. Bores will say that it is only for Windows, well, let them grit their teeth with envy. Although it seems there are collective farm clients for iOS, I did not study this issue in detail.
The next question is hosting. Then I did not think long, Azure was under my arms.
So what did I want?
- The way players connect should be similar to Microsoft Hearts. Players connect one by one, as soon as the right amount is gained - the game starts. For myself, I decided to limit myself to a one-on-one game - and no one's draws!
- There will be few players at the very beginning - how do they find out about each other? Then the idea came up to send everyone who wants to play push notifications at the moment when someone launches the application and connects to the game server. In order not to throttle users with pushes, he made a restriction “no more than once every N minutes”.
- I want detailed statistics, prizes, achievements, etc.
- I want cards of different designs
- I want to play with my acquaintance, and not with anyone "whom God sent."
What do I use with Azure?
- AppService is, in fact, an application to which all clients connect.
- SQL server + SQL database - for storing game statistics.
All.
Recently, I also used their push notification distribution service. But it seemed expensive (10 bucks a month), in addition, it turned out that due to the Microsoft billing glitch, I had paid for these two services for more than a year! Butting support led to the fact that they recognized the mistake and offered as much as a month of compensation. After some time, I completely abandoned this service, adding another table to my database for storing signers for push and sending them myself from the main application.
At this time, the cost of hosting monthly about 400 r. This is only the cost of the SQL server. I have small outgoing traffic and it fits into the free 5 GB per month.
Development
Development took place at Visual Studio 2015, for the client the MVVM framework MVVM light was used.
A little server "kitchen" (esthetes and the faint of heart is better not to watch)
user connection, pushing
public override Task OnConnected()
{
if (((DateTime.Now - LastPush).TotalSeconds) > 360)
{
LastPush = DateTime.Now;
Task.Run(() => SendNotifications());
}
return base.OnConnected();
}
creating an anonymous game
///
/// старт анонимной игры. При наличии уже подключенного соперника начинается игра
///
/// ID начатой игры
async public Task ConnectAnonymous(PlayerInformation pi)
{
MainSemaphore.WaitOne();
try
{
string res = String.Empty;
string p_ip = Context.Request.GetHttpContext().Request.UserHostAddress;
if (NextAnonymGame == null)
{
NextAnonymGame = new FoolGame(Context.ConnectionId, pi, p_ip, false);
res = NextAnonymGame.strGameID;
}
else
{
await NextAnonymGame.Start(Context.ConnectionId, pi, p_ip);
ActiveGames.Add(NextAnonymGame.strGameID, NextAnonymGame);
res = NextAnonymGame.strGameID;
NextAnonymGame = null;
}
return res;
}
finally
{
MainSemaphore.Release();
}
}
creation of a game room
///
/// создание игровой комнаты
///
/// кодовое слово игровой комнаты
public String CreatePrivateGame(PlayerInformation pi)
{
MainSemaphore.WaitOne();
try
{
string p_ip = Context.Request.GetHttpContext().Request.UserHostAddress;
FoolGame game = new FoolGame(Context.ConnectionId, pi, p_ip, true);
WaitingPrivateGames.Add(game.strGameID, game);
return game.PrivatePass;
}
finally
{
MainSemaphore.Release();
}
}
peep the top card in the deck
///
/// возвращает верхнюю карту из стека-колоды
///
///
///
async public Task PeekCard(string gameid)
{
FoolGame game = null;
game = ActiveGames.FirstOrDefault(games => games.Value.strGameID == gameid).Value;
if (game != null)
{
game.GameSemaphore.Wait();
await Task.Delay(35);
try
{
await Clients.Caller.PeekedCard(game.Deck.Peek());
}
finally
{
game.GameSemaphore.Release();
}
}
}
send a message to an opponent
///
/// чат
///
/// ID игры (и имя группы)
/// текст сообщения
///
async public Task ChatMessage(string gameid, string ChatMessage)
{
FoolGame game = null;
game = ActiveGames.FirstOrDefault(games => games.Value.strGameID == gameid).Value;
if (game != null)
{
game.GameSemaphore.Wait();
await Task.Delay(35);
try
{
await Clients.OthersInGroup(gameid).ChatMessage(ChatMessage);
}
finally
{
game.GameSemaphore.Release();
}
}
}
About customer
To identify the players, the LiveId functionality was first used, then the Graph API. At the first launch of the application, the player is invited to provide access to his account (from it I take only the name and the so-called anonymous id, which looks something like this: "ed4dd29dda5f982a"). However, the player can play anonymously, but then the statistics of his games are not kept.
For each non-anonymous player the following are stored:
1. date of the first game / date of the last game
2. name / nickname of the player
3. number of games played / how many of them are won
4. last IP address
5. prizes received
If two non-anonymous players play in the game, then before it starts I get the statistics of the games of these particular players (how many games they played with each other and who won how much). To do this, the received anonymous id is used in the SQL query.
In the screenshot at the top left you can see an example (clickable): Screenshot of the general statistics (clickable): In addition, there is a “competition” by country (here anonymous players also participate, information is taken from the IP address): Players can exchange short messages.



FoolHubProxy.On("ChatMessage", (chatmessage) => synchrocontext.Post(delegate
{
PlayChatSound();
ShowMessageToast(chatmessage);
}, null));
An example of a situation handler “I take cards, and the opponent adds me another 'after” ”:
FoolHubProxy.On("TakeOneMoreCard", (addedcard, lastcard) => synchrocontext.Post(delegate
{
CardModel card = new CardModel(addedcard, DeckIndex);
CardsOnTable_Low.Add(card);
OpponentsCards.Remove(OpponentsCards.Last());
if (lastcard)
{
AppMode = AppModeEnum.defeated;
}
}, null));
About prizes, cookies, etc.
Each month, the first five players who won the most victories receive a badge
About any additional functionality
The application is free, but it has various additional "buns" decorated as InApp Purchases:
- grouping your cards by suit and tips during the game (when you need to hit or toss, suitable cards are pushed up)
- the ability to not show the opponent how many cards you have in your hands. In a normal situation, he sees it
- the opportunity to always start the game first. Otherwise, the first move is randomly played.
- the ability to see the cards beaten off in the game
- the opportunity to peek into the deck once per game and find out the next card
- the ability to cheat. You can beat off 3 times per game or toss the wrong card, generally any. But the opponent has the opportunity, having noticed this, to return the wrong card to you.
Conclusion
As a result of developing this application, I:
- met SignalR
- refreshed SQL in memory (queries, stored procedures, functions)
- Learned how to host apps on Azure
- Dumbfounded from playing the "fool."
Question
And what about the situation with Habré with the
Update
YouTube: Record a couple of games