ИгрыХабрахабр

Приложение Дурак для Windows Store


Поль Сезанн, «Игроки в карты»

Игра в карты по сети, с оппонентами по всему миру. Давным-давно, в Windows 95 была игра Microsoft Hearts. 11 (да, я застал все эти артефакты!) была версия для игры по локальной сети, с использованием так называемого NetDDE.
Карточную игру выбирать долго не пришлось. Если мне не изменяет память, то в Windows for Workgroups 3. Оставалось одно — пропадать подкидной дурак. Как говорится, чем богаты… Высоколобые бридж и преферанс отпали по причине полного их незнания.

Гуглинг привёл меня сразу в нужное место — SignalR. Ситуация осложнялась тем, что до сих пор я никогда не занимался разработкой «бэкенда».

Прекрасно документированная библиотека, подошедшая как нельзя лучше к моим нуждам. Здесь мне хочется сказать несколько восторженных слов в адрес SignalR. Хотя вроде есть её колхозные клиенты для iOS, детально этот вопрос я не изучал. Зануды скажут, что она только под винду, ну и пусть они скрипят зубами от зависти.

Тут я размышлял недолго, под руками был Azure. Дальше вопрос хостинга.

Итак, чего же мне хотелось?

  • Способ подключения игроков должен повторять способ Microsoft Hearts. Игроки подключаются один за одним, как только набралось нужное количество — игра стартует. Для себя я решил ограничиться игрой «один на один» — и никаких ничьих!
  • В самом начале игроков будет немного — как же они узнают друг о друге? Тут возникла идея рассылать всем желающим поиграть push-уведомления в тот момент, когда кто-то запускает приложение и подключается к игровому серверу. Чтобы не троттлить пользователей пушами, сделал ограничение «не чаще чем раз в N минут».
  • Хочется подробнейшей статистики, призов, достижений и пр.
  • Хочется карт разного дизайна
  • Хочется играть со своим знакомым, а не с кем «кого бог послал».

Что я использую у Azure?

  • AppService — собственно, приложение, к которому подключаются все клиенты.
  • SQL server + SQL database — для хранения игровой статистики.

Всё.

Но показалось дорого (10 баксов в месяц), кроме того, выяснилось, из-за глюка биллинга у Microsoft я больше года платил за две этих службы! Недавно я еще использовал и их службу рассылки push-уведомлений. Спустя какое-то время я полностью отказался от этой службы, добавив еще одну таблицу в свою базу данных для хранения подписантов на пуши и рассылаю их самостоятельно из основного приложения. Бодание с поддержкой привело к тому, что они признали ошибку и предложили аж месяц компенсации.

Это только стоимость SQL сервера. В данный момент времени стоимость хостинга ежемесячно около 400 р. Исходящий трафик у меня небольшой и он вполне укладывается в бесплатные 5 Гб в месяц.

Разработка

Разработка проходила на Visual Studio 2015, для клиента использовался MVVM фреймворк MVVM light.

Немного серверной «кухни» (эстетам и слабонервным лучше не смотреть)

подключение пользователей, рассылка пушей

public override Task OnConnected() 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»). Впрочем, игрок может играть и анонимно, но тогда статистика его игр не ведётся.

Для каждого неанонимного игрока хранятся:

дата первой игры/дата последней игры
2. 1. количество сыгранных партий/сколько из них выиграно
4. имя/ник игрока
3. полученные призы последний 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:

  • группировка своих карт по масти и подсказки во время игры (когда нужно бить или подбрасывать, подходящие карты выдвигаются вверх)
  • возможность не показывать сопернику, сколько у вас карт в руках. В обычной ситуации он это видит
  • возможность всегда первым начинать игру. Иначе первый ход разыгрывается случайно
  • возможность увидеть отбитые в игре карты
  • возможность один раз за игру подглядеть в колоду и узнать следующую карту
  • возможность «жульничать». Вы можете 3 раза за игру отбиться или подбросить неправильную карту, вообще любую. Но у соперника есть возможность, заметив это, вернуть вам неправильную карту.

Заключение

В результате разработки этого приложения я:

  • познакомился с SignalR
  • освежил в памяти SQL (запросы, хранимые процедуры, функции)
  • узнал, как размещать приложения в Azure
  • опупел от игры в «дурака».

Вопрос

А как на Хабре обстоит дело с разбрасыванием денег с вертолёта раздачей в личке промо-кодов желающим? Если за это не дают пинка, то обращайтесь.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть