понедельник, 21 января 2013 г.

Action как замена копипаста

Даже начинающие программисты знают, что если один и тот-же код встречается в двух разных местах, то стоит задуматься над тем, чтобы вынести этот код в отдельную функцию дабы не плодить копи-паст.
  Но что делать, когда код почти полностью совпадает, но где-нибудь в середине отличается одной-двумя строчками от которых ну никак нельзя избавиться?

 Вот, например, есть у нас список игроков в MMO игре:

List<Player> players;

Теперь представим, где-нибудь в координатах x,y произошло некоторое событие, например, взорвалась граната и теперь надо пересчитать хиты игроков, находящихся в зоне поражения и послать сообщения об их полученом дамаге игрокам.

private void ExplodeGrenade(float x, float y, float range)
{
  //сохраним константу в локальную переменную, в реальности здесь может быть 
  //гораздо более сложный код
  float damage=Grenade.Damage;

  foreach(Player player in players)
  {
    //если игрок попадает в квадрат поражения, то пересчитаем ему
//уровень урона, чем дальше, тем меньше
if (player.X<x+range && player.X>x-range && player.Y<y+range && player.Y> y+ range) { //фунция Distance считает расстояние между двумя точками float playerDamage=damage/(Distance(player.X,player.Y,x,y)+1); //пошлем игроку сообщение о том, что он получил урон player.SendMessage(Message.TakeDamage,playerDamage); } } }
Теперь представим другую операцию, например игрок переместился на карте и нужно об этом сообщить другим игрокам, находящимся в зоне видимости:

private void MovePlayer(Player movingPlayer,float x, float y, float range)
{
  //Установим игроку новые координаты
  movingPlayer.X=x;
  movingPlayer.Y=y;

  foreach(Player player in players)
  {
    //сообщим всем игрокам в области видимости, что игрок меперестился
    if (player.X<x+range && player.X>x-range 
        && player.Y<y+range && player.Y> y+ range)
    {
      player.SendMessage(Message.Move,movingPlayer);
    }
  }
}

Как видно, две этих функции очень похожи, отличаются по сути только телом цикла внутри условия, а каждый раз писать длинные, совершенно одинаковые  условия проверки очень не хочется. И ведь это еще весьма упрощенный вариант, приведенный для примера, а в реальном многопоточном сервере должны быть блокировки, вместо линейного списка игроков употребляться более эффективные структуры хранения данных и множество других  дополнительных наворотов.
  Как решить данную задачу не копипастя постоянно один и тот же код? Делается это довольно просто используя класс дотнета Action<T>.

Общий код (цикл и условие) выносим в отдельную функцию

private void PlayerActionInRange(float x,float y,float range, 
                                 Action<Player> playerAction)
Как видно, в функции присутсвует параметр с типом Action<Player>. Класс Action является оберткой для делегата или просто говоря функции, которая будет вызываться из функции PlayerActionInRange. Параметром данной функции будет являться объект типа Player.



private void PlayerActionInRange(float x,float y,float range, 
              Action<Player> playerAction)
{
  foreach(Player player in players)
  {
    //для всех игроков в области видимости вызываем функцию 
    if (player.X<x+range && player.X>x-range 
        && player.Y<y+range && player.Y> y+ range)
    {
       //Параметром функции передаем текущего игрока в цикле
       playerAction(player); 
    }
  }
}
Теперь модифицируем функцию ExplodeGrenade

private void ExplodeGrenade(float x, float y, float range)
{
  //сохраним константу в локальную переменную, в реальности 
  //здесь может быть гораздо более сложный код
  float damage=Grenade.Damage;

  //В качестве параметра функции передаем ссылку на анонимный 
  //делегат, код которого будет находиться тут же
  PlayerActionInRange(x,y,range,player =>
  {
    //фунция Distance считает расстояние между двумя точками
    float playerDamage=damage/(Distance(player.X,player.Y,x,y)+1);
    //пошлем игроку сообщение о том, что он получил урон
    player.SendMessage(Message.TakeDamage,playerDamage);
  });
}
Теперь функция ExplodeGrenade стала гораздо более ясной для понимания. А представьте, если бы для организации выборки в реальном проекте пришлось бы писать не две, а с хотя бы десяток строчек кода, насколько упростился бы вид этой функции!

Функцию MovePlayer можно сделать еще проще, воспользовавшись лямбда-выражениями:
private void MovePlayer(Player movingPlayer, 
                        float x, float y, float range)
{
  //Установим игроку новые координаты:
  movingPlayer.X=x;
  movingPlayer.Y=y;

  //всего лишь одна строчка кода
  PlayerActionInRange(x,y,range,
                     p=>p.SendMessage(Message.Move,movingPlayer));
}
Вот таким нехитрым способом можно существенно упростить читаемость кода и избежать ошибок при копировании одинакового текста из одной фунции в другую

Реклама в приложении от креара медиа

Летом ко мне обращались многие рекламщики с предложением вставить в мою вконтактовскую игру рекламу. Я не собирался вставлять рекламу, которую не могу модерировать, т. к. велика вероятность того, что в приложении прорвется реклама, запрещенная правилами вконтакте и приложение будет забанено.
   Одной из обратившихся компаний была креара медиа. Т. к. компания является официальным партнером, то проблем с рекламным контентом не должно было быть, поэтому я у них запросил информацию о том, сколько я смогу зарабатывать на их рекламе. Они мне прислали усредненную статистику, я сделал расчеты и насчитал, что с каждых 10 тыс. уникальных пользователей я буду зарабатывать на рекламе аж целых 90 рублей в день.
  Возникло предположение, что я ошибся. Написал менеджеру креары вопрос, правильно ли я все посчитал, но ответа так и не получил, поэтому на креару забил.
  Через полгода решил проверить в реальности и подключить для теста прелоадеры рекламы. После трех дней тестов у меня получились очень интересные цифры:
  Прелоадер отображается примерно каждому десятому пользователю, т. е. на 1000 DAU имеем 100 показов.
  CTR около 3% (2.83)
  Оплата за каждый клик составляет примерно 1 руб. плюс за каждую 1000 показов - 6 рублей
 В итоге получаем, что на каждую 1000 DAU заработок на рекламе получается:
 1000*0.1*0.03*1+1000*0.1*6/1000=3 рубля 60 копеек. Соответсвенно со 100 тыс. DAU можно заработать 360 рублей в день. Просто фантастический доход!
  Для небольших приложений с DAU меньше 10 тыс. зайти самому один раз в день и прокликать ссылки оказывается гораздо выгоднее, чем получать деньги с рекламы, которая находится в приложении - это просто какой-то нонсенс.
  Я даже не говорю про то, что деньги от креары можно получить не ранее чем через 45 дней, после того, как они начислены и для получения этих копеек они требуют открыть публичную статистику приложения.

воскресенье, 20 января 2013 г.

MonoDevelop на Ubuntu 10.04

Что делать, если надо поставить на Ubuntu последнюю версию MonoDevelop, а в официальных репозиториях ее нет?

Для начала стоит глянуть в полуофициальный репозиторий badgerports - там, обычно, с небольшой задержкой после выпуска новых версий mono и monodevelop появляются пакеты для LTS версий Ubuntu, но, к сожалению, после выхода Ubuntu 12.04, автор прекратил обновлять пакеты для Lucid Lynx, а мне нужно было установить MonoDevelop именно на 10.04.

Поэтому, решил откомпилировать MonoDevelop из исходников. Если честно, то воспоминания о том сколько приходилось мучиться несколько лет назад, чтобы собрать MonoDevelop (а точнее, пререквизиты к нему), наводили на мысль, что данная затея может быть безуспешной. Но, как оказалось, все получилось довольно просто.

Сначала ставим Mono 2.10.8 из badgerports. Процесс подключения репозитория описан на сайте, поэтому тут повторять его не буду. Также установливаем MonoDevelop 2.8.

Устанавливаем пререквизиты:
sudo aptitude install intltool libmono-addins-cil-dev libmono-addins-gui-cil-dev gnome-sharp2
Хочу обратить внимание на пакеты libmono-addins-cil-dev libmono-addins-gui-cil-dev Если их не установить, то будут возникать ошибки на несоответствии версии mono-addins.
Дальше забираем исходники из последнего стабильного бранча:
git clone -b monodevelop-3.0-series https://github.com/mono/monodevelop.git
Собираем:
./configure 
make
Проверяем работоспособность:
make run
Если все нормально, можно проинсталлировать:
make install

Вот, собственно, и все.