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

ЛоготипПривіт всім любителям і дослідникам штучного інтелекту! У даній статті я хотів би розповісти про цікавий проект: модульною системою універсального штучного інтелекту (МСУИИ) «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 Again(Module[i]) then
DetermineModuleType(Module[i]);
end;
end;

function Again(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
Execute procedure; 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 do Terminated
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, R: 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 of RecordAuthor
- 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 '..\..\AmigaVirtual\SystemControlMessagesUnit.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 then NewMessageGot
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('Ukrainian');
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 then LanguageDataLoaded
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;

А ще є вікно з довідкою:

скріншот
У нього ж через головне меню можна вивести інформацію про модулі:

скріншот
Ще один цікавий трюк — самовідновлення програми, зроблено з допомогою .php-скрипта:

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

Дякую за увагу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.