МСУИИ AmigaVirtual — универсальный ИИ для каждого

в 6:53, , рубрики: AmigaVirtual, Delphi, искусственный интеллект, машинное обучение, МСУИИ, открытые исходники, Программирование, метки:

Логотип Привет всем любителям и исследователям искусственного интеллекта! В данной статье я хотел бы рассказать об интересном проекте: модульной системе универсального искусственного интеллекта (МСУИИ) «Amiga Virtual» (AV, «Виртуальная Подружка»). Я расскажу об основных принципах её работы и опишу некоторые детали реализации, а самые любопытные смогут исследовать все исходные коды. Разработка ведётся на Delphi, но модули теоретически могут быть написаны на любом ЯП. Данная система будет интересна как конечным пользователям чат-ботов и связанных с ними систем, так и разработчикам ИИ — ведь на её основе можно разработать практически любой тип ИИ.

Для начала немного истории. Проект был начат в 2012-м году, когда я решил заняться разработкой чат-бота. А решил я так после знакомства с «Sayme 2» — простой флеш-игрой про общение с виртуальной девушкой (к сожалению, амбициозное продолжение этой игры — 3-я версия — по всей видимости умерло в зародыше. R.I.P., «Sayme 3»). Тогда AV представляла собой монолит, с вынесенными в одну DLL картинками с частями аватара — чтобы не качать лишние мегабайты, если аватар не нужен.

скриншот 1

скриншот 2

Так выглядела первая версия Amiga Virtual.

С тех пор концепция изменилась — теперь собственно AV — только платформа для организации работы модулей. Все «органы» ИИ вынесены в модули.

Несколько слов о языке программирования. В 2012 я выбрал Delphi, так как не знал практически ничего, кроме него (не считая Turbo Pascal). Позже я перепробовал множество языков… Больше всего мне понравилась Ada, но обучающих материалов по ней мало, да и программы получаются медленными. Ещё я пробовал Oxygen (Delphi Prism), но по нему материалов ещё меньше. C++ и другие си-подобные языки мне не нравятся вообще. Поэтому в итоге я вернулся к Delphi, который я хорошо знаю и по которому просто море обучающего материала в Интернете, да и готовых решений тоже много.

Теперь собственно о проекте. Amiga Virtual — это многоцелевая многоагентная система. В общих чертах она устроена так: есть основная программа (ОП) и набор модулей (представлены в виде DLL и являются программными агентами), у которых реализованы те или иные интерфейсы из определённого списка; во время старта ОП загружает и идентифицирует (определяет, что модуль может делать; назначение модуля программе не сообщается — это не имеет смысла) все модули, затем пользователь выбирает модули, с которыми он хочет работать, и запускает многопоточную обработку интерфейсов ввода-вывода выбранных модулей. Вот так происходит загрузка и идентифицирование модулей:

procedure LoadModules;
var
  i: Integer;
begin
  for i := 0 to Length(Module) - 1 do
  begin
    if Module[i].Handle > 0 then
      FreeModule(Module[i]);
    if LoadModule(Module[i]) then
      DetermineModuleType(Module[i]);
  end;
end;

function LoadModule(var M: TModule): boolean;
var
  B: String;
  C: Integer;
begin
  with M do
  begin
    Handle := LoadLibrary(PWideChar(FileName));
    if Handle > 0 then
    begin
      @SetLanguage := GetProcAddress(Handle, 'SetLanguage');
      IncAndUpdateProgress; // Для прогресс-бара
      @SendLanguageData := GetProcAddress(Handle, 'SendLanguageData');
      IncAndUpdateProgress;
      @GetName := GetProcAddress(Handle, 'GetName');
      IncAndUpdateProgress;

      // Тут много строк, подключающих интерфейсы

      // Вынимаем из модуля немного информации
      if @GetName <> nil then
      begin
        B := GetName;
        C := Pos(ControlCodeMarker, B);
        if C > 0 then
        begin
          Name := Copy(B, C + 1, Length(B));
          ControlCode := Copy(B, 1, C - 1);
          ControlCodeFormated := FormatControlCode(ControlCode);
        end
        else
        begin
          Name := B;
          ControlCode := MainForm.LanguageData[133];
        end;
      end
      else
      begin
        Name := MainForm.LanguageData[114];
        ControlCode := MainForm.LanguageData[133];
      end;

      if (@OpenWindow <> nil) and (@CloseWindow <> nil) then
        WindowState := closed
      else
        WindowState := window_does_not_exist;

      if (@Sleep <> nil) and (@WakeUp <> nil) then
        ModuleState := working
      else
        ModuleState := module_cant_sleep;

      LoadStatistics; // Это пока не реализовано
      Result := true;
    end
    else
      Result := false;
  end;
end;

function DetermineModuleType(var M: TModule): TModuleType;
begin
  with M do
  begin
    MType := undetermined;

    if (@SetLanguage = nil) and ... (* много сравнений интерфейсов с nil *) then
      MType := erroneous_or_empty
    else
    begin
      if (@SetSource <> nil) or (@NextData <> nil) or (@Progress <> nil) or
        (@RestartParsing <> nil) then
        MType := parser
      else
      begin
        if (@GetData <> nil) and (@SendData <> nil) then
          MType := input_and_output
        else if (@GetData <> nil) and (@SendData = nil) then
          MType := only_input
        else if (@GetData = nil) and (@SendData <> nil) then
          MType := only_output
        else if (@GetData = nil) and (@SendData = nil) then
          MType := no_input_and_no_output;
      end;
    end;

    Result := MType;
  end;
end;

ОП реализует только текстовый интерфейс взаимодействия пользователя с модулями, или, проще говоря, окно чата. Все модули общаются через этот чат, но обычно технические сообщения модулей не показываются пользователю, т.к. не представляют для него интереса (в целях отладки эти сообщения можно отобразить). Технические сообщения помечаются как несколько непечатных символов в начале строки. Этот код («контроль-код») задаётся разработчиком модуля. Загрузка двух модулей с одним контроль-кодом может привести к зацикливанию системы — эти два модуля будут бесконечно обмениваться сообщениями, как два поставленных друг против друга зеркала. Короче, контроль-код нужен для того, чтобы модули игнорировали сообщения, которые адресованы не им. Однако некоторые модули могут обрабатывать все сообщения — например, модули типа «интеллект-ядро».

Каждый модуль, имеющий интерфейсы ввода, вывода или ввода-вывода обрабатывается в отдельном потоке ОП. Это позволяет сохранить работоспособность ОП, если какой-то модуль зависнет или просто «задумается». Остальные модули используются пользователем в ручном режиме через их собственные окна. Следующий класс реализует поток ввода-вывода:

  TIOThread = class(TThread)
  public
  const
    SleepTime = 1000;
  var
    Module: TModule;
    SelfID: Integer;
    constructor Create(M: TModule; ID: Integer);
  protected
    procedure Execute; override;
  end;

А вот как происходит обработка ввода-вывода:

procedure TIOThread.Execute;
  procedure GetData;
  var
    M: String;
  begin
    M := String(Module.GetData);
    if M <> SCM_No_Message then
      Synchronize(
        procedure
        begin
          Pool.AddRecord(M, SelfID);
        end);
  end;

  procedure SendData;
  var
    i: Integer;
  begin
    with Pool do
      if not Empty then
      begin
        for i := 0 to Length(Records) - 1 do
          with Records[i] do
            if not ModuleGot[SelfID] and (AuthorID <> SelfID) then
            begin
              Module.SendData(PChar(Text));
              ModuleGot[SelfID] := true;
            end;
        Synchronize(
          procedure
          begin
            CheckAndDeleteOddRecords;
          end);
      end;
  end;

begin
  inherited;
  while not Terminated do
  begin
    case Module.MType of
      only_input:
        GetData;
      only_output:
        SendData;
      input_and_output:
        begin
          SendData;
          GetData;
        end;
    end;
    Sleep(SleepTime);
  end;
end;

Для обмена сообщениями ОП реализует пул сообщений. В него сохраняются все сообщения, пришедшие из окна чата и из модулей, и хранятся до тех пор, пока все модули не получат эти сообщения, затем они удаляются. Следующий класс реализует пул сообщений:

  TPoolRecord = record
    Text: String;
    AuthorID: Integer;
    ModuleGot: array of Boolean;
  end;

  TPool = class
    Records: array of TPoolRecord;
    Empty: Boolean;
    procedure AddRecord(RecordText: String; RecordAuthor: Integer);
    procedure CheckAndDeleteOddRecords;
    constructor Create;
    procedure Show;
  end;

procedure TPool.AddRecord(RecordText: String; RecordAuthor: Integer);
var
  i, RL: Integer;
begin
  RL := Length(Records);
  SetLength(Records, RL + 1);
  with Records[RL] do
  begin
    Text := RecordText;
    AuthorID := RecordAuthor;
    SetLength(ModuleGot, OutputModulesCount);
    for i := 0 to OutputModulesCount - 1 do
      if i = AuthorID then
        ModuleGot[i] := true
      else
        ModuleGot[i] := false;
  end;
  with MainForm, MainForm.ChatBox.Lines do
    case RecordAuthor of
      - 1:
        Add(User.Name + ': ' + RecordText);
    else
      if RecordText = SCM_Dont_Know_Answer then
      begin
        if DontKnowCheckBtn.Checked then
          Add(LanguageData[156]);
      end
      else
        Add(AVirtual.Name + ': ' + RecordText);
    end;
  Empty := false;
end;

procedure TPool.CheckAndDeleteOddRecords;
  function ItsOdd(ID: Integer): Boolean;
  var
    i: Integer;
  begin
    ItsOdd := true;
    with Records[ID] do
      for i := 0 to Length(ModuleGot) - 1 do
        if not ModuleGot[i] then
        begin
          ItsOdd := false;
          exit;
        end;
  end;

  procedure DeleteRecord(ID: Integer);
  var
    i: Integer;
  begin
    for i := ID to Length(Records) - 2 do
      Records[i] := Records[i + 1];
    SetLength(Records, Length(Records) - 1);
  end;

var
  i: Integer;
begin
  if not Empty then
  begin
    for i := Length(Records) - 1 downto 0 do
      if ItsOdd(i) then
        DeleteRecord(i);
    if Length(Records) = 0 then
      Empty := true;
  end;
  if MainForm.PoolShowBtn.Checked then
    Show;
end;

Теперь поговорим о возможных классах модулей и их возможностях.

Самый важный, ключевой класс модулей, ради которых была задумана и реализована Подружка — это «интеллект-ядра» (ИЯ, или просто ядра). Ядро обрабатывает все сообщения, поступающие в чат, в соответствии с заложенным в него алгоритмом. То есть он выполняет основную интеллектуальную работу. Конкретный алгоритм и его реализация зависит от разработчика ядра. Как сделать хорошее ядро — интересный вопрос, и я рассмотрю его в другой статье на примере моей серии ядер. Другой интересный вопрос — как разделить ядро на отдельные модули. У меня не получается подразделить ядра, над которыми я сейчас работаю. Но, в принципе, ничто не мешает вам сделать составное ядро.

Все остальные модули разделяются на несколько категорий: рецепторы (генерируют сообщения в чат — модуль зрения, модуль слуха и т.п.), эффекторы (совершают действия в ответ на сообщения из чата), инструменты (не относятся к ИИ напрямую, используются пользователем вручную), парсеры (используются обучающим модулем «Dream Fusion») и другие модули, которые могут использоваться какими-то особыми модулями.

Некоторые рецепторы и эффекторы соответственно кодируют и декодируют сообщения, записанные в стиле «контроль-код + команда». Такие сообщения обычно не показываются пользователю. Суть этих сообщений в следующем: рецептор распознаёт некое действие, совершённое пользователем, кодирует в сообщение и отправляет его ядру на хранение. Когда ядро выдаст это сообщение, соответствующий эффектор раскодирует сообщение, получив команду, соответствующую действию пользователя, и выполнит это действие. Поскольку ядру нет разницы, какие сообщения запоминать и выдавать, а подключить к системе мы можем любые рецепторы и эффекторы — интеллект получается универсальным, то есть его можно научить чему угодно. Здорово, правда?

Следует заменить, что для удобства родственные рецепторы и эффекторы могут быть реализованы в одном модуле. Пример — класс модулей под общим названием «эмоциональный аватар» (я называю это «эмотар»): пользователь собирает из частей лицо, выражающее определённую эмоцию, и модуль создаёт сообщение, кодирующее выбранную эмоцию, т.е. действует как рецептор; когда ядро выдает это сообщение в чат, эмотар декодирует его и выстраивает изображение лица с соответствующей эмоцией, т.е. действует как эффектор. Разделять эмотар на два отдельных модуля я не вижу смысла.

Вот пример шаблона для создания модуля на Delphi:

library ИмяМодуля;

uses
  System.SysUtils,
  System.Classes,
  SystemControlMessagesUnit
    in '....AmigaVirtualSystemControlMessagesUnit.pas',
  MainFormUnit in 'MainFormUnit.pas',

const
  ControlCode = три+непечатных+символа;
  Name = ControlCode + '>Имя Модуля';
  Help = 'Многострочная' + #13 + 'справка';

var
  FormState: (closed, opened);
  Buffer, VirtualName: String;
  NewMessageGot: Boolean;

function GetName: PChar; stdcall;
begin
  Result := PChar(Name);
end;

function GetHelp: PChar; stdcall;
begin
  Result := PChar(Help);
end;

procedure OpenWindow; stdcall;
begin
  if MainForm = nil then
    MainForm := TMainForm.Create(nil);
  MainForm.Show;
  FormState := opened;
end;

procedure CloseWindow; stdcall;
begin
  if FormState = opened then
  begin
    MainForm.Close;
    FormState := closed;
    MainForm.Release;
    MainForm := nil;
  end;
end;

procedure SendData(Data: PChar); stdcall;
begin
  Buffer := Data;
end;

function GetData: PChar; stdcall;
begin
  if NewMessageGot then
  begin
    Result := PChar(Buffer);
    NewMessageGot := false;
  end
  else
    Result := PChar(SCM_No_Message);
end;

procedure Start; stdcall;
begin
  NewMessageGot := false;
  if MainForm = nil then
    MainForm := TMainForm.Create(nil);
end;

procedure SetVirtual(Name: PChar); stdcall;
begin
  VirtualName := Name;
end;

procedure LoadData; stdcall;
begin
  // Загрузка базы данных
end;

procedure SaveData; stdcall;
begin
  // Сохранение базы данных
end;

exports GetName, GetHelp, OpenWindow, CloseWindow, SendData, GetData, Start,
  SetVirtual, LoadData, SaveData;

begin
end.

На данный момент есть 21 интерфейс:

    SetLanguage: function(Language: PChar): boolean; stdcall;
    SendLanguageData: function(Data: array of PChar): boolean; stdcall;
    GetName: function: PChar; stdcall;
    GetHelp: function: PChar; stdcall;
    Start: procedure; stdcall;
    Sleep: function: boolean; stdcall;
    WakeUp: procedure; stdcall;
    OpenWindow: procedure; stdcall;
    CloseWindow: procedure; stdcall;
    SetVirtual: procedure(Name: PChar); stdcall;
    SaveData: procedure; stdcall;
    LoadData: procedure; stdcall;
    Reset: procedure; stdcall;
    SetNewMainWindow: procedure(Position, Size: TPoint); stdcall;
    GetTimerInterval: function: Integer; stdcall;
    SendData: procedure(Data: PChar); stdcall;
    GetData: function: PChar; stdcall;
    SetSource: procedure(SourcePath: PChar); stdcall;
    NextData: function: PChar; stdcall;
    Progress: function: Real; stdcall;
    RestartParsing: procedure; stdcall;

Модуль может реализовывать любой набор из вышеперечисленных интерфейсов.

Является ли типичный модуль интеллектуальным агентом? Зависит от реализации. Я вижу два варианта реализации. Рассмотрим их на примере модуля зрения. Первый вариант — простой программный агент: он кодирует картинки как есть, то есть конвертирует битмапы в текстовую строку. Второй вариант — сложный интеллектуальный агент: например, искусственная нейронная сеть, которая распознаёт на картинках объекты и описывает их словами в сообщении. Если используется несколько модулей второго типа, то можно сказать, что универсальный ИИ построен из набора слабых ИИ. Сильный это интеллект или нет — зависит от реализации ядра.

А теперь о деталях основной программы. Их три: организация баз данных ИИ по именам, регистрация пользователей со сбором статистики и «Центр Обмена» для обмена контентом.

Знаете платформу чат-ботов iii? По сути, Amiga Virtual — это во много раз более продвинутая версия iii. Только я называю экземпляры ИИ (поименованные базы данных) не «Инфами», а «Виртуалами». Каждый Виртуал обучается пользователем с нуля и может быть легко передан на другой компьютер. А с помощью модулей-аватаров (или эмотаров) может быть создан уникальный визуальный образ Виртуала. Вкладка менеджера Виртуалов выглядит так:

скриншот

Выбор используемых модулей производится с проверкой на коллизии по контроль-коду:

type
  CResult = (no_collision, collision);

  function CheckModulesCollision: CResult;
  var
    i, j: Integer;
    CC: String;
  begin
    Result := no_collision;
    with ModulesList do
      for i := 0 to Items.Count - 1 do
        if Checked[i] then
        begin
          CC := FindModuleByFileName(ExtractDLLName(Items[i])).ControlCode;
          for j := 0 to Items.Count - 1 do
            if Checked[j] and (i <> j) and
              (CC = FindModuleByFileName(ExtractDLLName(Items[j])).ControlCode)
            then
              Result := collision;
        end;
  end;

Для того, чтобы знать, как используется AV большинством пользователей и в каком направлении развивать проект, я собираю анонимную статистику.
Пока сбор стастистики не реализован.

Центр Обмена (ЦО) — это сервис, доступный из основной программы, предназначенный для обмена модулями, Виртуалами и прочим контентом между пользователями:

скриншот

Вот так реализовано скачивание контента:

procedure TMainForm.DownloadFilesButtonClick(Sender: TObject);
var
  Dir, FileName: String;
begin
  SetCurrentDir(ProgramPath);
  Dir := Category[ContentCategoryBox.ItemIndex];
  Dir := UpCase(Dir[1]) + Copy(Dir, 2, Length(Dir));
  if not DirectoryExists(Dir) then
    CreateDir(Dir);
  SetCurrentDir(Dir);
  FileName := ContentList.Items[ContentList.ItemIndex];
  if FileExists(FileName) then
    MessageDlg(LanguageData[172], mtInformation, [mbOk], 0)
  else
  begin
    DownloadFile(SiteProtocol + OfficialWebsite + ExchangeCenterPage + '?c=' +
      Category[ContentCategoryBox.ItemIndex] + '&f=' + FileName + '&l=' +
      LanguageData[0], FileName);
    // В LanguageData[0] хранится название языка системы
    if Copy(FileName, Length(FileName) - 2, 3) = 'zip' then
      UnzipFiles(FileName, GetCurrentDir);
    case ContentCategoryBox.ItemIndex of
      0:
        UpdateModulesList;
      1:
        UpdateVirtualsList;
    end;
    SetStatusMessage(LanguageData[173] + ' ' + ProgramPath + Dir + '');
  end;
end;

procedure TMainForm.DownloadFile(From, SaveTo: String);
var
  LoadStream: TMemoryStream;
begin
  Downloading := true;
  LoadStream := TMemoryStream.Create;
  IdHTTP.Get(TIdURI.URLEncode(From), LoadStream);
  LoadStream.SaveToFile(SaveTo);
  LoadStream.Free;
  Downloading := false;
  SplashScreen.Close;
end;

Для загрузки контента в ЦО реализована система аккаунтов (пока не работает):

скриншот

Планирую соединить её с системой аккаунтов официального форума, но пока не знаю, как.

Базовый доступ (только скачивание) обеспечивается в гостевом аккаунте, а для загрузки своего контента требуется зарегистрироваться (пока алгоритм загрузки контента не реализован, загружаю вручную по FTP).

Кроме ЦО также есть форум, который обеспечивает техподдержку пользователей — он открывается во встроенном браузере (стандартный для Delphi — вроде бы используется ядро Trident, но мне многого и не нужно, только отрисовать простую страницу; сам форум на phpBB):

скриншот

Как видно из скриншота, можно ввести свой адрес и добавить страницу в закладки (закладки пока не реализованы) — чтобы не потерять обсуждение на форуме.

Ещё один момент. Заметили в коде выше LanguageData? Это массив, в котором хранятся текстовые строки для компонентов GUI, соответствующие выбранному языку. Русский и английский упакованы в .exe как ресурсы, и при старте распаковываются в папку Languages. Файлы с другими языками можно будет скачать через Центр Обмена. Т.к. Delphi поддерживает Юникод, язык можно установить какой угодно — японский или арабский, к примеру.

Вот так языковые файлы выгружаются из .exe:

procedure TMainForm.DeployDefaultLanguages;
  procedure DeployLanguage(LanguageName: String);
  var
    ResHandle, MemHandle: THandle;
    MemStream: TMemoryStream;
    ResPtr: PByte;
    ResSize: Longint;
    ResName: String;
    i: Integer;
  begin
    ResName := '';
    for i := 1 to Length(LanguageName) do
      ResName := ResName + UpCase(LanguageName[i]);
    ResName := ResName + '_LP';
    ResHandle := FindResource(HInstance, PWideChar(ResName), RT_RCDATA);
    if ResHandle = 0 then
    begin
      ShowMessage('Default language "' + LanguageName + '" not found. (' +
        ResName + ')');
      exit;
    end;
    MemHandle := LoadResource(HInstance, ResHandle);
    ResPtr := LockResource(MemHandle);
    MemStream := TMemoryStream.Create;
    ResSize := SizeOfResource(HInstance, ResHandle);
    MemStream.SetSize(ResSize);
    MemStream.Write(ResPtr^, ResSize);
    MemStream.Seek(0, 0);
    MemStream.SaveToFile(LangFilesDir + '/' + LanguageName +
      LanguageFileExtension);
    FreeResource(MemHandle);
    MemStream.Destroy;
  end;

begin
  if not DirectoryExists(LangFilesDir) then
    CreateDir(LangFilesDir);
  DeployLanguage('Russian');
  DeployLanguage('English');
end;

Как видно, чтобы встроить новый предустановленный язык, достаточно вставить файл языка в файл ресурсов, добавить строчку вида DeployLanguage('НазваниеЯзыка') и перекомпилировать проект — удобно.

Вот так загружается язык:

procedure TMainForm.ChangeLanguageTo;
  function LanguageDataLoaded: Boolean;
  var
    T: TextFile;
    B: RawByteString;
  begin
    SetCurrentDir(ProgramPath + LangFilesDir);
    if FileExists(Language + LanguageFileExtension) then
    begin
      AssignFile(T, Language + LanguageFileExtension);
      Reset(T);
      SetLength(LanguageData, 1);
      LanguageData[0] := Language;
      while not eof(T) do
      begin
        SetLength(LanguageData, Length(LanguageData) + 1);
        ReadLn(T, B);
        LanguageData[Length(LanguageData) - 1] := UTF8ToWideString(B);
      end;
      CloseFile(T);
      if Length(LanguageData) - 1 >= LangFileMinSize then
        Result := true
      else
        Result := false;
    end
    else
      Result := false;
  end;

  procedure SetCaptions;
  begin
    with HelpForm do
    begin
      LoadHelpTexts;
      if CurrentTopic < HelpLast then
        OpenTopic(CurrentTopic)
      else
        OpenTopic(0);
    end;
    AddModulesHelpToMainProgramHelp;
    ChangeModulesLanguageToProgramLanguage;

    AuthorizationTab.Caption := LanguageData[18];
    // много-много строк...
    CloudSaveNow.Caption := LanguageData[181];
    CloudLoadNow.Caption := LanguageData[182];
  end;

  procedure CheckMenuItem;
  var
    Item: TMenuItem;
  begin
    for Item in LanguageMenu.Items do
      if Item.Name = LanguageData[0] + 'Lang' then
        Item.Checked := true;
  end;

begin
  if LanguageDataLoaded then
  begin
    if not Silent then
      SetStatusMessage(LanguageData[2] + ' ' + LanguageData[0])
    else
      FormCaption.Caption := LanguageData[1];
    SetCaptions;
  end
  else
  begin
    if Language <> SavedLanguage then
      ChangeLanguageTo(SavedLanguage)
    else
      ChangeLanguageTo(DefaultLanguage);
    MessageDlg(LanguageData[3] + #13 + LanguageData[2] + ' ' + LanguageData[0] +
      '.', mtError, [mbOk], 0);
  end;
  CheckMenuItem;
end;

А ещё есть окно со справкой:

скриншот

В него же через главное меню можно вывести информацию о модуле:

скриншот

Ещё один интересный трюк — самообновление программы, сделано с помощью .bat-скрипта:

procedure TMainForm.UpdateProgram;
  procedure DeployBAT;
  var
    bat: TextFile;
  begin
    if not FileExists(ProgramPath + 'update.bat') then
    begin
      AssignFile(bat, ProgramPath + 'update.bat');
      Rewrite(bat);
      WriteLn(bat, 'taskkill /im av.exe');
      WriteLn(bat, 'sleep 1'); // Windows XP
      WriteLn(bat, 'timeout /t 1 /nobreak'); // Windows 7+
      WriteLn(bat, 'del av.exe');
      WriteLn(bat, 'move ' + ZipsDir + 'av.exe %1');
      WriteLn(bat, 'del /S /Q ' + ZipsDir);
      WriteLn(bat, 'start av.exe');
      // WriteLn(bat, 'pause');
      CloseFile(bat);
    end;
  end;

begin
  DownloadFile(SiteProtocol + OfficialWebsite + '/av.zip',
    ProgramPath + 'av.zip');
  UnzipFiles(ProgramPath + 'av.zip', ProgramPath + ZipsDir);
  DeployBAT;
  SetCurrentDir(ProgramPath);
  ShellExecute(Handle, nil, 'update.bat', PChar(ProgramPath), nil, SW_SHOW);
end;

Напоследок хочу заметить, что проект Amiga Virtual включает в себя не только Windows-программу. Кроме неё планируются варианты системы для Android (AV Mobile), роботизированных платформ (AV OS) и суперкомпьютеров (AV Super). А алгоритмы интеллект-ядер могут быть использованы для создания интеллектуальной поисковой системы (реорганизующей результаты поиска Яндекса и Google). Когда будет готова альфа версия по какому-то из этих направлений, я напишу статью с описанием её работы.

Исходные коды проекта и дизайн-документ можно скачать здесь: github.com/TimKruz/AV

Спасибо за внимание.

Автор: TimKruz

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js