Elite Games - Свобода среди звезд!
.
  » .Net скрываем конечный автомат или "бот v2.0" | страница 1
Конференция предназначена для общения пилотов. Для удобства она разделена на каналы, каждый из которых посвящен определенной игре. Пожалуйста, открывайте темы только в соответствующих каналах и после того, как убедитесь, что данный вопрос не обсуждался ранее.

Поиск | Правила конференции | Фотоальбом | Регистрация | Список пилотов | Профиль | Войти и проверить личные сообщения | Вход

   Страница 1 из 1
 
Поиск в этой теме:
Канал Игры Мечты: «.Net скрываем конечный автомат или "бот v2.0"»
Варсик
 545 EGP


Рейтинг канала: 4(81)
Репутация: 117
Сообщения: 4034
Откуда: Москва
Зарегистрирован: 22.12.2002
Предисловие

Не так давно в КСО проскакивал разговор по поводу того как взломать игрушку и написать бота для нее. Результатом стал удачный взлом и я получил возможность писать ботов используя обстракции клиента. Однако бот получался сложный, из-за особенностей написания... Сейчас объясню подробнее...

Итак... Что я получил на входе... Каждый фреим (перерисовка экрана) я получал управление и мог использовать обращаться к любому примитиву внутри игры. Скрипт выглядел как конечный автомат. Программирование автоматов - это отдельная наука и почитать про это можно в Вики... Скажите, сколько народу умеют программировать конечные автоматы или машины состояний?

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

Потоки, которые не потоки

Самое простое, что можно было сделать: Запускать на каждый скрипт свой поток, который был-бы в постоянном синхроне. То есть... Когда выполняется операция со временем "лететь туда-то", то поток ждет выполнения этой операции, а машина состояний проверяет - закончилась-ли эта операция. Как только закончилась, поток получает управление, а основная программа ждет, пока поток разберется что и где ему надо сделать. Фактически принудительный свитч потока 2 раза с синхронизацией. При 60 FPS это ОЧЕНЬ дорогая реализация.

Итераторы и сахар

А потом я нашел конструкцию yield return. Подробности по ее поводу тут: http://msdn.microsoft.com/ru-ru/library/9k7k7cf0.aspx Если коротко, то... Это замыкание в C# через итераторы. Теперь код бота выглядит так:
Код:
namespace StateScripting
{
   public sealed class Test:Script
   {
      public Test ():base("ssTest", "ssTest", "State Scripting test")
      {
      }

      protected override IEnumerable<ScriptOperation> Invoke ()
      {
         for (int i = 0; i<10; i++) {
            Object target = Util.FindNextTarget();
            yield return API.flyTo (target);
         }
      }
   }
}

Код выше просто последовательно летит к 10 выбранным целям.

Если разбираться в коде, то есть пару непоняток... Первое - это yield return - это просто перечислитель. Если вы сделаете
Код:
foreach(ScriptOperation so in Test.Invkoe()){
    Process(so);
}


То вы увидите что Process вызовется 10 раз. Однако сахар тут не в этом... Давайте рассмотрим код API.flyTo():
Код:
      public static FlyTo flyTo(Object target){

         return new FlyTo (target, DefaultDistance, UseBoosters);

      }

Фактически это "макрос"... "сахар" над созданием объекта ScriptOperation. ScriptOperation - эта та самая операция, которая растянута по времени. Прототип ScriptOperation довольно простой:
Код:
namespace StateScripting.Operations
{
   public class ScriptOperation
   {
      public enum FrameResult
      {
         Continue,
         Done,
         Error,
         ErrorButContinue,
         SimulateUI
      }
      public ScriptOperation ()
      {
      }

      public virtual FrameResult Frame () {
         return FrameResult.Done;
      }
      public virtual void Abort() {}
   }
}
У нас есть метод Frame, который и будет той самой машиной состояний, но выполняющий определенную команду, не более... В нашем случае - лететь к цели. Сам код flyTo я показывать не буду, ибо он не является целью этого поста/статьи... Однако надо показать как выполняется этот скрипт... Есть статический класс ScriptEngine, который содержит бибилиотеку скриптов, которые могут выполняться. У этого класса есть статический метод Frame:
Код:
      public static void Frame(){
         foreach(Script s in m_scripts.Values){
            if(s.Executing){
               if (s.ResumeFrom != DateTime.MinValue) {
                  if (s.ResumeFrom <= DateTime.Now) {
                     s.ResumeFrom = DateTime.MinValue;
                  }
               } else if (s.Operation != null) {
                  ScriptOperation.FrameResult result = ScriptOperation.FrameResult.Done;
                  try {
                     result = s.Operation.Frame ();
                  } catch (Exception e) {
                     Error (String.Format ("Error executing script {0}.\r\nStacktrace:\r\n{1}\r\nScript stopped.", s.Name, e));
                     s.Stop ();
                  }
                  switch (result) {
                  case ScriptOperation.FrameResult.Error:
                     Error (String.Format ("Script {0} returned error state and will be stopped"));
                     s.Stop ();
                     break;
                  case ScriptOperation.FrameResult.Done:
                     s.Operation = null;
                     break;
                  case ScriptOperation.FrameResult.ErrorButContinue:
                     break;
                  case ScriptOperation.FrameResult.SimulateUI:
                     s.ResumeFrom = DateTime.Now + TimeSpan.FromMilliseconds (100 + (uint)((new System.Random ()).NextDouble () * 100));
                     break;
                  }
               } else {
                  if (s.ScriptState.MoveNext ()) {
                     s.Operation = s.ScriptState.Current;
                  } else {
                     s.Stop ();
                  }
               }
            }
         }
      }
Нас тут интересует вот этот кусочек:
Код:
if (s.ScriptState.MoveNext ()) {
   s.Operation = s.ScriptState.Current;
} else {
   s.Stop ();
}
Пазл начинает складываться... Фактически мы итерируемся по итератору, который является нашим скриптом бота и, как только ловим операцию (а не поймать ее мы не можем. Иначе скрипт закончен) - выполняем ее до тех пор, пока она не закончится, не вызывая код итератора.
Чтобы пазл совсем сложился надо показать код запуска скрипта:
Код:
public virtual void Start(string[] args) {
   ClearState();
   Executing = true;
   m_scriptState = Invoke ().GetEnumerator ();
}
Вот теперь все понятно... Объект скрипта содержит в себе итератор, созданный из метода Invoke, по которому итерируется движок скриптов, получая следующую "временную" операцию, которую этот движок и выполняет. Временная итерация - это маленькая машина состояний. Таким образом полностью от машины состояний уйти не удалось, однако их стало больше и каждая из них стала гораздо проще.

Фактически, точно так-же работает система скриптинга в X'ах. Если вы все команды с @ замените на return yield, вы получите то-же самое.

Конечно, код не оптимален, его можно написать лучше и т.д. но основную идею, как можно использовать return yield в .Net я описал.
_________________
WARNING: By reading this post you accept that this post is genius.

Последний раз редактировалось: Варсик (17:33 08-07-2013), всего редактировалось 1 раз
    Добавлено: 11:45 08-07-2013   
Shirson
 1604 EGP


Модератор
Рейтинг канала: 7(626)
Репутация: 217
Сообщения: 16511
Откуда: 79°W 44°N
Зарегистрирован: 29.01.2002
Варсик :
Скажите, сколько народу умеют программировать конечные автоматы или машины состояний?
FSM это основа и фундамент программирования как такового. Не умея рабоать с и/или не понимая FSM, в профессиии делать нечего, вообще Улыбка
_________________
У меня бисера не доxеpа.
    Добавлено: 21:05 08-07-2013   
Варсик
 545 EGP


Рейтинг канала: 4(81)
Репутация: 117
Сообщения: 4034
Откуда: Москва
Зарегистрирован: 22.12.2002
Ну... Это ты кодерам расскажи. Вообще там интуитивно понятно, но есть-же дибилы...

добавлено спустя 19 минут:
И тут-же появился вопрос...
А если надо получить результат долгой операции?
Вообще хочется что-то типа:
Код:
uint res = yield return LongOp();
Но это невозможно. Можно сделать, конечно, так:
Код:
unit res;
yield return LongOp(ref res);
Но это как-то не красиво (( В то-же время сделать yield return внутри вызванной сабы - нельзя. Что печально.
_________________
WARNING: By reading this post you accept that this post is genius.

Последний раз редактировалось: Варсик (21:49 08-07-2013), всего редактировалось 1 раз
    Добавлено: 21:49 08-07-2013   
Diff
 707 EGP


Модератор
Рейтинг канала: 2(11)
Репутация: 44
Сообщения: 4179
Откуда: Сферическая Земля в вакууме.
Зарегистрирован: 04.07.2003
Варсик :
А если надо получить результат долгой операции?

Если б там действительно был FSM - то просто. FSM находится в состоянии Жду_Результата_Долгой_Операции и активируется по приходу сигнала Результат_Операции. Автомат, конечно, должен быть асинхронным.
Но у тебя тут описан не автомат, а какой-то странный мутант.
_________________
Конец света в конце тоннеля
    Добавлено: 22:44 08-07-2013   
Minx
 907 EGP


Модератор
Рейтинг канала: 6(320)
Репутация: 140
Сообщения: 10416
Откуда: Gomel, Belarus
Зарегистрирован: 19.11.2005
Варсик :
Самое простое, что можно было сделать: Запускать на каждый скрипт свой поток, который был-бы в постоянном синхроне. То есть... Когда выполняется операция со временем "лететь туда-то", то поток ждет выполнения этой операции, а машина состояний проверяет - закончилась-ли эта операция. Как только закончилась, поток получает управление, а основная программа ждет, пока поток разберется что и где ему надо сделать. Фактически принудительный свитч потока 2 раза с синхронизацией. При 60 FPS это ОЧЕНЬ дорогая реализация.


Reactor - берешь готовую реализацию и радуешься.

Реакторы бывают разные. Например Leader-Follower Model геморойна в реализации, но делает только один принудительный свитч.

---

По поводу мутанта согласен. Но я могу быть привередливым, т.к. в моем сознании автоматы больше ассоциируются с цветными сетями Петри, нежели с перебором списка действий.
_________________
μηδείς αγεωμέτρητος εισίτω
    Добавлено: 23:36 08-07-2013   
Варсик
 545 EGP


Рейтинг канала: 4(81)
Репутация: 117
Сообщения: 4034
Откуда: Москва
Зарегистрирован: 22.12.2002
Minx :
Reactor - берешь готовую реализацию и радуешься.
И не находишь C#.
А реализовывать что-то страшное... Когда есть простой и удобный механизм как для платформы, так и для программиста... Я, конечно, мазахист, но не настолько.
_________________
WARNING: By reading this post you accept that this post is genius.
    Добавлено: 23:46 08-07-2013   
Minx
 907 EGP


Модератор
Рейтинг канала: 6(320)
Репутация: 140
Сообщения: 10416
Откуда: Gomel, Belarus
Зарегистрирован: 19.11.2005
Википузией мир не ограничен.

Если все устраивает, то тут вообще вопросов нет (; Просто отписал, что то, что "можно было сделать", уже сделано до нас.
_________________
μηδείς αγεωμέτρητος εισίτω
    Добавлено: 00:16 09-07-2013   
Варсик
 545 EGP


Рейтинг канала: 4(81)
Репутация: 117
Сообщения: 4034
Откуда: Москва
Зарегистрирован: 22.12.2002
Почитал код... Мутно и не понятно... ИМХО они плохой пример взяли. В их варианте надо просто эвент луп крутить. А не этой фигней заниматься... epoll/kqueue с этим нормально справятся. Они для этого и сделаны. А в последнем epoll'е ты даже ветчить можешь в ядре и только потом отправлять сообщение в юзерспейс.

А так... Это смесь, причем не совсем удачная, моего метода и эвент лупа.

добавлено спустя 48 секунд:
Да и код там становится еще более непонятным, чем в случае с базовой стейт машиной. Я дрался за упрощение кода, а тут, походу, усложнение.
_________________
WARNING: By reading this post you accept that this post is genius.

Последний раз редактировалось: Варсик (01:20 09-07-2013), всего редактировалось 1 раз
    Добавлено: 01:20 09-07-2013   
Sh.Tac.
 150 EGP


Рейтинг канала: 5(108)
Репутация: 14
Сообщения: 1426

Зарегистрирован: 27.07.2005
Варсик :
А если надо получить результат долгой операции?

корутинам отказать, в шарпе 5.0 появился async await
_________________
This is what you get ...
(c) Radiohead
    Добавлено: 01:32 09-07-2013   
Варсик
 545 EGP


Рейтинг канала: 4(81)
Репутация: 117
Сообщения: 4034
Откуда: Москва
Зарегистрирован: 22.12.2002
Sh.Tac. :
корутинам отказать, в шарпе 5.0 появился async await
К сожалению это не 5-й шарп. Это моно.
_________________
WARNING: By reading this post you accept that this post is genius.
    Добавлено: 11:24 09-07-2013   
Olorin
 70 EGP


Рейтинг канала: 1(6)
Репутация: 12
Сообщения: 97
Откуда: Хьёрвард
Зарегистрирован: 27.02.2006
Варсик :
Sh.Tac. :
корутинам отказать, в шарпе 5.0 появился async await
К сожалению это не 5-й шарп. Это моно.

В 2.11 же поддержка появилась вроде.. о_О
Да и смысл не столько в синтаксисе, а в концепции, кою некоторые даже на 2.0 перекладывали.. ээ.. Вот.
_________________
Мы на многое не отваживаемся не потому что оно трудно; оно трудно именно потому, что мы на него не отваживаемся.
Сенека Старший
    Добавлено: 16:51 09-07-2013   
Канал Игры Мечты: «.Net скрываем конечный автомат или "бот v2.0"»
 
  
Показать: 
Предыдущая тема | Следующая тема |
К списку каналов | Наверх страницы
Цитата не в тему: Ксеноны - народ не злопамятный. Отомстили и забыли.

  » .Net скрываем конечный автомат или "бот v2.0" | страница 1
Каналы: Новости | Elite | Elite: Dangerous | Freelancer | Star Citizen | X-Tension/X-BTF | X2: The Threat | X3: Reunion | X3: Terran Conflict | X Rebirth | X4: Foundations | EVE Online | Orbiter | Kerbal Space Program | Evochron | VoidExpanse | Космические Миры | Онлайновые игры | Другие игры | Цифровая дистрибуция | play.elite-games.ru | ЗВ 2: Гражданская война | Творчество | Железо | Игра Мечты | Сайт
   Дизайн Elite Games V5 beta.18