- PVSM.RU - https://www.pvsm.ru -

Поль Сезанн, «Игроки в карты»
Давным-давно, в Windows 95 была игра Microsoft Hearts. Игра в карты по сети, с оппонентами по всему миру. Если мне не изменяет память, то в Windows for Workgroups 3.11 (да, я застал все эти артефакты!) была версия для игры по локальной сети, с использованием так называемого NetDDE.
Карточную игру выбирать долго не пришлось. Как говорится, чем богаты… Высоколобые бридж и преферанс отпали по причине полного их незнания. Оставалось одно — пропадать подкидной дурак.
Ситуация осложнялась тем, что до сих пор я никогда не занимался разработкой «бэкенда». Гуглинг привёл меня сразу в нужное место — SignalR [1].
Здесь мне хочется сказать несколько восторженных слов в адрес SignalR. Прекрасно документированная библиотека, подошедшая как нельзя лучше к моим нуждам. Зануды скажут, что она только под винду, ну и пусть они скрипят зубами от зависти. Хотя вроде есть её колхозные клиенты для iOS, детально этот вопрос я не изучал.
Дальше вопрос . Тут я размышлял недолго, под руками был Azure.
Всё.
Недавно я еще использовал и их службу рассылки push-уведомлений. Но показалось дорого (10 баксов в месяц), кроме того, выяснилось, из-за глюка биллинга у Microsoft я больше года платил за две этих службы! Бодание с поддержкой привело к тому, что они признали ошибку и предложили аж месяц компенсации. Спустя какое-то время я полностью отказался от этой службы, добавив еще одну таблицу в свою базу данных для хранения подписантов на пуши и рассылаю их самостоятельно из основного приложения.
В данный момент времени стоимость
Разработка проходила на Visual Studio 2015, для клиента использовался MVVM фреймворк MVVM light.
public override Task OnConnected()
{
if (((DateTime.Now - LastPush).TotalSeconds) > 360)
{
LastPush = DateTime.Now;
Task.Run(() => SendNotifications());
}
return base.OnConnected();
}
/// <summary>
/// старт анонимной игры. При наличии уже подключенного соперника начинается игра
/// </summary>
/// <returns>ID начатой игры</returns>
async public Task<String> 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();
}
}
/// <summary>
/// создание игровой комнаты
/// </summary>
/// <returns>кодовое слово игровой комнаты</returns>
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();
}
}
/// <summary>
/// возвращает верхнюю карту из стека-колоды
/// </summary>
/// <param name="gameid"></param>
/// <returns></returns>
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();
}
}
}
/// <summary>
/// чат
/// </summary>
/// <param name="gameid">ID игры (и имя группы)</param>
/// <param name="ChatMessage">текст сообщения</param>
/// <returns></returns>
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();
}
}
}
Для идентификации игроков первоначально использовалcя функционал LiveId, затем Graph API. При первом запуске приложения игроку предлагается предоставить доступ к его аккаунту (из него я беру только имя и так называемый анонимный id, выглядящий примерно так: «ed4dd29dda5f982a»). Впрочем, игрок может играть и анонимно, но тогда статистика его игр не ведётся.
Для каждого неанонимного игрока хранятся:
1. дата первой игры/дата последней игры
2. имя/ник игрока
3. количество сыгранных партий/сколько из них выиграно
4. последний IP адрес
5. полученные призы
Если в игре играют два неанонимных игрока, то перед её началом я получаю статистику игр именно этих игроков (сколько игр они сыграли друг с другом и кто сколько выиграл). Для этого в SQL-запросе и используются полученные анонимные id.
На скриншоте слева вверху можно увидеть пример (кликабельно):
Скриншот общей статистики (кликабельно):
Кроме того, ведётся «соревнование» по странам (тут участвуют и анонимные игроки, информация берётся из IP-адреса):

Игроки могут обмениваться короткими сообщениями.
FoolHubProxy.On<string>("ChatMessage", (chatmessage) => synchrocontext.Post(delegate
{
PlayChatSound();
ShowMessageToast(chatmessage);
}, null));
Пример обработчика ситуации «я беру карты, а соперник добавляет мне еще 'вдогонку'»:
FoolHubProxy.On<byte, bool>("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));
Каждый месяц первая пятерка игроков, одержавших больше всех побед, получает по бейджику «За взятие Берлина». Участникам топ-50 вручаются бейджики за лучший процент побед (тоже пятерым). Кроме того, есть бейджики за выигрыш «экспрессом» (ситуация, когда на последнем ходу у вас остаётся 2, 3 или 4, скажем, шестёрки или валетов). Тогда они выкладываются на стол все махом и вы — молодец. Есть печеньки за победу, когда соперник дает вам «в лист». Ему тоже достаётся утешительная, с черепом и костями.
Приложение бесплатное, но у него есть разные дополнительные «плюшки», оформленные как InApp Purchases:
В результате разработки этого приложения я:
А как на Хабре обстоит дело с разбрасыванием денег с вертолёта раздачей в личке промо-кодов желающим? Если за это не дают пинка, то обращайтесь.
Автор: infund
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/windows/310929
Ссылки в тексте:
[1] SignalR: https://docs.microsoft.com/ru-ru/aspnet/signalr/overview/getting-started/introduction-to-signalr
[2] хостинга: https://www.reg.ru/?rlink=reflink-717
[3] Image: https://habrastorage.org/webt/kf/f6/zy/kff6zyaelk9orpazyvgnvosfxg4.png
[4] Image: https://habrastorage.org/webt/rn/hi/fy/rnhifyxjr_frp_txbsjzeks4wsq.png
[5] Источник: https://habr.com/ru/post/442746/?utm_campaign=442746
Нажмите здесь для печати.