Игра в 30 команд Ассемблера

в 17:59, , рубрики: 30 строк, крестики-нолики, ненормальное программирование, метки: , ,

В прошлом году были популярны темы, как написать программу за 30 строк кода. Все примеры были сделаны на JavaScript. Для запуска таких программ требуется не только веб страница, но и браузер, разные библиотеки, ядро ОС наконец. На самом деле работают не 30 строк кода, а десятки, сотни мегабайты программного кода, находящиеся в памяти компьютера.
А можно ли написать не полностью бесполезную программу за 30 строк ассемблера, без лишних библиотек и мегабайт ОС?
В этой статье я опишу, как можно сделать крестики-нолики за 30 строк ассемблера.

Задача: сделать рабочую игру крестики-нолики.
Правила «игры»:

  • до 30 строк чистых ассемблерных команд. Без жульничества в виде записи машинного кода в одну строку db ......
  • Нет ограничения на размер данных. Игра в 0 строк JS имела не мало данных в виде HTML и CSS
  • Не использовать сторонние библиотеки, лучше без прерываний ОС, только BIOS
  • при этом программа должна работать

Сложность реализации за 30 строк ассемблера заключается в том, что на ассемблере простой цикл занимает несколько строк (команд). Каждая операция — это отдельная команда, а значит новая строка.

Пример, показывающий сколько строк нужно на циклы, для тех кто плохо знает ассемблер
// На C++
for ( int i=0; i<100; i++)...;

_asm {
// На ассемблере
mov ecx,0 ; int i=0
labelForI:
  ...
inc ecx ;i++
cmp ecx,100 ;i<100
jna labelForI

// Если длина команд в байтах больше 127 между метками, то нужно использовать длинный jmp, если внутри цикла изменяется регистр ecx, то его нужно сохранять и восстанавливать...
mov ecx,0 ; int i=0
labelForI:
 push ecx
  ...
 pop ecx
 inc ecx ;i++
cmp ecx,99 ;i<100
ja labelEndFor
jmp labelForI
labelEndFor:

// Конечно можно оптимизировать и в лучшем случае будет компактнее.
mov ecx,100 ; int i=100
labelForI:
  ...
dec ecx ;i--
jnz labelForI

// ещё меньше
mov ecx,100 ; int i=100
labelForI: nop ; nop - команда, которая ничего не делает
loop labelForI

// пример, как можно жульничать, записав всё в одну строку.
DB B9h,00,01,00,00,90h,E2h,F8h ; выполнит 99 раз команду nop (90h), те. бездействие
}

Самая простая реализация крестиков-ноликов — это матрица со значениями клеток, которые нужно в циклах сравнивать для определения победы, в циклах выводить на экран. По этому сделать игру за 30 строк кода на первый взгляд кажется нереально.

Первая попытка — сократить массивы и оптимизировать

Так как если хранить игровое поле в виде массива, а уж тем более матрицы, то потребуется несколько циклов, чтобы определять победу. Можно вместить всё игровое поле, размером 3 на 3 (хватит и на 4*4) в 4 байта, или 2 машинных слова, по два байта на игрока. Так можно сократить проверку победы перебором восьми вариантам победы, представленным в виде восьми чисел (сравнивая совпадения бит с int map_x, int map_0).
Для проверки идеи сделал реализацию на Си.
Игра выводит игровое поле на экран в следующем виде:
Игра в 30 команд Ассемблера
Пользователь нажимает на цифровой клавиатуре цифры, по позиции соответствующие клетке. Например цифра 5 — это центр, 1 — левая-нижняя клетка.

Вариант 1, на Си

/* 
 * File:   main.c
 * Author: godAlex
 *
 * Первый вариант с побитовыми сдвигами и самый короткий по общему размеру.
 * На Си.
 *
 * Created on 29 Январь 2014 г., 11:51
 */

#include <stdio.h>
#include <stdlib.h>

#define win_x 1
#define win_0 2
#define win_tie 3
#define win_not 0

unsigned short KEYBOARD_BYTE_OFFSET[] = {0x40,0x80,0x100,0x8,0x10,0x20,0x1,0x2,0x4 }; //{0b001000000,0b010000000,0b100000000,0b000001000,0b000010000,0b000100000,0b000000001,0b000000010,0b000000100 };
unsigned short BOT_POSITION_PRIORITY[] =
{  0x10,        0x40,       0x4,        0x100,      0x1,        0x80,       0x2,        0x8,        0x20};
// center       bounds                                          other
//{0b000010000, 0b001000000,0b000000100,0b100000000,0b000000001,0b010000000,0b000000010,0b000001000,0b000100000 };

unsigned short WIN_MATRIX[] = { 0x1C0, 0x38, 0x7,  0x124, 0x92, 0x49,  0x111, 0x54 }; //{ 0b111000000, 0b000111000, 0b000000111,  0b100100100, 0b010010010, 0b001001001,  0b100010001, 0b001010100 }; // варианты для победы
unsigned short MAP_X = 0;
unsigned short MAP_0 = 0;
unsigned short WIN_MATRIX_NOT_WIN = 0x1FF; //0b111111111; // ничья, доделать с xor
unsigned short STRING_ENTER_POS = 0x124;//0b100100100; // Позиции переноса строк


void specialPrintChar(char *c) {
    printf(c); // TODO ASM
}
unsigned short specialReadNumber() {
    char outC;
    scanf("%c",&outC); // TODO ASM
    return outC-'0';
}

short testWon() {
    char i;
    for (i=0; i<8; i++) {
        if ( (MAP_X & WIN_MATRIX[i]) == WIN_MATRIX[i] ) return win_x;
        if ( (MAP_0 & WIN_MATRIX[i]) == WIN_MATRIX[i] ) return win_0;
    }
    if ( (MAP_X | MAP_0) == WIN_MATRIX_NOT_WIN ) return win_tie; // Ничья
    return win_not;
}
void printField() {
    unsigned short bOfs;
    for (bOfs=1; bOfs<WIN_MATRIX_NOT_WIN; bOfs=bOfs<<1) { // shl
        if ( MAP_X & bOfs ) specialPrintChar("X");
        else {
            if ( MAP_0 & bOfs ) specialPrintChar("0");
            else specialPrintChar(".");
        }
        if ( bOfs & STRING_ENTER_POS ) specialPrintChar("n"); // переносы строк
    }
}
// - - - - - - - - - - - - - - - -
int main(int argc, char** argv) {
    specialPrintChar("Test XO game!n");
    printField(); // Вывод
    // Игровой процесс...
    int whoIsWon=win_not;
    while (whoIsWon==win_not)
    {
        short cKey;
        unsigned short full_map;
        unsigned short p;
        // Ввод с клавиатуры
      do {
	do {
            specialPrintChar("Enter cell position (1-9):n");
            cKey = specialReadNumber();
        } while ( cKey<1 || cKey>9 );
        p=KEYBOARD_BYTE_OFFSET[cKey-1]; // позиция в поле
        full_map = MAP_X | MAP_0; // все поля
      } while ( (full_map & p) !=0); // или поле уже занято.
        MAP_X = MAP_X | p; // поставить крестик

	printField(); // Вывод
	// test Win
        whoIsWon=testWon();
        if (whoIsWon!=win_not) break;
        	
	// Бот
        full_map = MAP_X | MAP_0;
        for (p=0 ; (full_map & BOT_POSITION_PRIORITY[p]) != 0 ; p++);
        MAP_0 = MAP_0 | BOT_POSITION_PRIORITY[p]; // поставить нолик

	specialPrintChar(" the BOT:n");
	printField(); // Вывод
	
	whoIsWon=testWon(); // test Win
    };

   switch (whoIsWon) { // сокращается через GoTo label в testWon (asm)
	case win_x:
		specialPrintChar("Won X!n");
		return 1;
	case win_0:
		specialPrintChar("Won 0!n");
		return 2;
        case win_tie:
	    specialPrintChar("Win nothing!n");
	    return 3;
   }
   return (EXIT_SUCCESS);
}

Слишком много кода. Декомпилировав полученную программу, увидел, что команд больше сотни, не считая вызова printf и scanf. Может попробовать самому перевести на ассемблер?

Вариант 1, функция testWon(), переписанная на ассемблер

int testWon() {
	int winResult=-1; // todo to asm
	_asm {
		mov ecx,0 ;xor ecx,ecx компактнее
		mov edx, offset WIN_MATRIX
	lForI:
		//mov bx,WIN_MATRIX[ecx]
			mov bx, word ptr [edx] ; !!! криво
			add edx,2 ; [edx][ecx]
		mov ax,MAP_X
		and ax,bx
		cmp ax,bx ; TODO проверить замену на test, чтобы работал как нужно тут
		JE lblWonX
		mov ax,MAP_0
		and ax,bx
		cmp ax,bx
	  JE lblWon0
		inc ecx
		cmp ecx,8
		jne lForI
		mov ax,MAP_X ; проверка на ничью
		or ax,MAP_0
		cmp ax,WIN_MATRIX_NOT_WIN
		jne lblRet ; продолжение
		mov winResult,win_no ; или ничья
		jmp lblRet ;RET
lblWonX:
		mov winResult,win_x
		jmp lblRet ;RET
lblWon0:
		mov winResult,win_0
		jmp lblRet ;RET
lblRet:
		;RET
	}
	return winResult;
}

Только функция проверки победы занимает около 30 команд. А ещё остались функции вывода результата на экран, чтение позиции с клавиатуры.
Этот вариант не подходит. Зато такая реализация может поместиться в БНЗ, размер которого 512 байт первого сектора диска.

Второй способ — как сделать то же на HTML за 0 строк JS?

А если подумать, как это можно решить на HTML за 0 строк JavaScript?
Каждый вариант игры записать как готовую строку вывода на экран, и к каждому варианту добавить адреса следующих записей, которые нужно показывать при нажатии соответствующих кнопок.
На HTML это реализовывается с помощью анкоров и длинной «партянки». На ассемблере, или Си нужно сделать программу, которая просто выводит текст на экран, в зависимости от нажатия кнопок.
Сама программа при этом получится маленькой, но данные, нужные для её работы огромные.
Пример алгоритма, реализованного на Си:

#include <stdio.h>
#include <stdlib.h>
unsigned short data_addres[374][9] = {{1, 71, 113, 190, 214, 262, 300, 327, 353}, {1, 1, 2, 16, 24, 31, 45, 52, 65}, ...};
char text[374][13] = {"...n...n...n", "...n...nX0.n", "...n..0nX0Xn", ...};
int main(int argc, char** argv) {
    unsigned int data_pos = 0;
    while (true) // или data_pos != магическое число выхода
    {
        printf(text[data_pos]);
        int i;
        do scanf("%i",&i); while ( i<1 && i>9 );
        data_pos=data_addres[data_pos][i-1];
    }
    return (EXIT_SUCCESS);
}

А что будет на ассемблере?

Вариант 2, на ассемблере за 29 команд, работающий на прерываниях BIOS!

SECTION .text
org 0x100 ; для .com файлов. Это не команда, а указания на сдвиг адресов.
lblShowVariant: 
  mov ax,0x0001 ; clear screen, set graphic mode 40x25, color
  int 10h
  mov ax, [data_pos]
  mov bx, [TEXT_BLOCK_SIZE]
  mul bx
  mov bp, text_data
  add bp,ax  ; offset на текст
  mov cx,[TEXT_BLOCK_SIZE]
  mov ax,1300h
  mov bx,0eh ; color
  mov dx,0500h ; 5 строка 0 позиция для вывода
  int 10h
lReadKey:  rep nop ; авось поможет снизить нагрузку на ЦП для разогнанных компьютеров. А так это лишняя команда, можно убрать.
  xor ax,ax
  int 16h ; BIOS read key
  xor ah,ah
  sub al,'1' ; в al число запишем, а не символ
  cmp ax,8
  ja lReadKey
  shl ax,1 ; ax=ax*2
  mov bx, data_addres
  add bx,ax ; bx = data_addres[key]
  mov ax, [data_pos]
  mov cx, [CASE_BLOCK_SIZE]
  mul cx ; cx = [data_pos]
  add bx,ax ; bx = data_addres[data_pos][key]
  mov ax,[bx]
  mov [data_pos],ax ; переход на новый ключ.
  jmp lblShowVariant
SECTION .data
; Out data on assembler data format
data_pos DW 0 ; max=394
CASE_BLOCK_SIZE DW 18 ;bytes (2 byte per 1 case)
TEXT_BLOCK_SIZE DW 16
data_addres:
DW 1, 42, 72, 100, 139, 167, 198, 272, 341
DW 1, 2, 7, 9, 13, 14, 1, 17, 33
...
text_data: 
DB "...", 13,10, "...", 13,10, "...", 13,10, " "
DB "0..", 13,10, "...", 13,10, "X..", 13,10, " "
DB "00.", 13,10, "...", 13,10, "XX.", 13,10, " "
DB "You are won!", "    "
...

Программа работает, требуются функции только BIOS, может запуститься без операционной системы, только надо поправить регистры в начале программы, и прописать её в загрузчике. Получилось украсить вывод программы, изменив цвет текста. Возможно без добавления новых команд вставить звук, добавив символы с кодом 7 в текст победных строк (PC speaker).
Игра в 30 команд Ассемблера
Половина дела сделана. Теперь осталось написать массивы данных, состоящие из нескольких тысяч адресов и вариантов игры.
Приблизительные расчёты размера данных:

  • 9 клеток (кнопок), адресация word => 18 байт на каждый вариант игрового поля
  • 13-16 байт текстовое поле

Итого около 31-34 байт на каждый вариант. Всего вариантов игры может быть 9! (9*8*7*6*5*4*3*2*1 ). Это много, но нам нужно хранить только набор вариантов для игрока, так как варианты для компьютера определены на этапе генерации данных. По этому вариантов будет не больше 9*7*5*3*1 = 945. Если учесть, что некоторые варианты могут быть завершены до заполнения последней клетки (победы, поражения), то вариантов будет ещё меньше. Итого памяти, требуемой на хранения всех вариантов и адресов, потребуется не более 32130 байт (34*945), что уместится на одной странице памяти (65535 байт).
Чтобы получить эти данные, была написана программа на C++, генерирующая все варианты и выводящая их в виде массива для C/C++ и данных для ассемблера. Затем была добавлена возможность вывода полного исходника на C, ассемблере, затем и HTML с 0 строк JS.

Исходник генератора данных на C++ для игр в 30 строк кода assembler, 12 строк С, и 0 строк JS
/* 
 * Author: godAlex (C) 2014
 * License GNU GPL v 3
 * param: -h -a -c -sa -sc -sh
 */
#include <cstdlib>
#include "iostream"
#include "string.h"
using namespace std;
//-------------------------------------------

//#define show_debug 1

#define win_x 1
#define win_0 2
#define win_end 3
#define win_gameNext 0

unsigned short KEYBOARD_BYTE_OFFSET[] = {0x40,0x80,0x100,0x8,0x10,0x20,0x1,0x2,0x4 }; //{0b001000000,0b010000000,0b100000000,0b000001000,0b000010000,0b000100000,0b000000001,0b000000010,0b000000100 };
unsigned short BOT_POSITION_PRIORITY[] =
{  0x10,        0x40,       0x4,        0x100,      0x1,        0x80,       0x2,        0x8,        0x20};
// center       bounds                                          other
//{0b000010000, 0b001000000,0b000000100,0b100000000,0b000000001,0b010000000,0b000000010,0b000001000,0b000100000 };

#define CASE_BLOCK_SIZE 9
#define TEXT_BLOCK_SIZE 16
int Text_Block_Size=13; // 13, если завершение строки 13, если 13,10 то 16. Изменяется при выводе asm.
char End_Of_String=0;

unsigned short WIN_MATRIX[] = { 0x1C0, 0x38, 0x7,  0x124, 0x92, 0x49,  0x111, 0x54 }; //{ 0b111000000, 0b000111000, 0b000000111,  0b100100100, 0b010010010, 0b001001001,  0b100010001, 0b001010100 };
unsigned short MATRIX_FULL = 0x1FF; //0b111111111;
unsigned short STRING_ENTER_POS = 0x124;//0b100100100;

int testWon(unsigned short MAP_X,unsigned short MAP_0) {
    for (int i=0; i<8; i++) {
        if ( (MAP_X & WIN_MATRIX[i]) == WIN_MATRIX[i] ) return win_x;
        if ( (MAP_0 & WIN_MATRIX[i]) == WIN_MATRIX[i] ) return win_0;
    }
    if ( (MAP_X | MAP_0) == MATRIX_FULL ) return win_end;
    return win_gameNext;
}
void printField(unsigned short MAP_X,unsigned short MAP_0, char* result) {
    //char result[TEXT_BLOCK_SIZE]="...n...n...n";
    int p=0;
    for (unsigned int bOfs=1; bOfs<MATRIX_FULL; bOfs=bOfs<<1) { // shl TODO test owerflow!
            if ( MAP_X & bOfs ) result[p]='X';
		else {
                    if ( MAP_0 & bOfs ) result[p]='0';
                    else result[p]='.';
		}
            if ( bOfs & STRING_ENTER_POS ) {
                p++;
                result[p]='n';
            }
            p++;
    }
    result[p]=End_Of_String;
    return result;
}

#define MAX_DATA_SIZE 30000
unsigned int data_addres[MAX_DATA_SIZE][CASE_BLOCK_SIZE]; //= {{0,0,1,0,1,0,1,0},{0,0,1,0,1,0,1,0}};
char text[MAX_DATA_SIZE][TEXT_BLOCK_SIZE]; // = { "Hello","Hello 2" }; // варианты для победы
unsigned int data_pos = 0;

int setVariant(int varID,unsigned int variants[CASE_BLOCK_SIZE],char* txt) //(unsigned int MAP_X,unsigned int MAP_0)
{
    int i;
    for (i=0;i<CASE_BLOCK_SIZE;i++) data_addres[varID][i]=variants[i]; // TODO memcopy
    for (i=0; i<Text_Block_Size && ( txt[i]!=End_Of_String && txt[i]!=0 ) ; i++) text[varID][i]=txt[i];
    text[varID][Text_Block_Size-1]=End_Of_String; // если строка не обработана для ассемблера.
#ifdef show_debug
    cout<<" set №"<<varID<<" as "<<text[varID]<<endl;
#endif 
    return varID;
}
int getFreeVar() {
    int p=data_pos;
    data_pos++;
    if (p>MAX_DATA_SIZE) {
        cout<<"Owerflow data pos!"<<endl;
        p=-1;
    }
#ifdef show_debug
    else cout<<"New variant №"<<p<<endl;
#endif
    return p;
}

int itrGameHod_all(unsigned int MAP_X,unsigned int MAP_0, bool playOn_X, int *doRecord);

/**
 * Возвращает номер ячейки для победы или ничьей или хотя бы какую-либо.
 * Играет за нолик (0)
 * Тип возвращаемой позиции - int [0,8], без по битной адресации.
 */
int getBestBotHod(unsigned int MAP_X,unsigned int MAP_0) { // TODO непобедимый bot был чтобы.
    unsigned int lastMAP_X=MAP_X;
    unsigned int lastMAP_0=MAP_0;
    unsigned int full_map = MAP_X | MAP_0;
    
    int winLevel=-1; // уровень вероятности победы
    int winPos=-1;
    //*
    for (int i=0; i<9; i++) { // победить
        unsigned short p = 1<<i;
        if ( (full_map & p) == 0 ) {
            int w=testWon( MAP_X, MAP_0 | p );
            if (w==win_0) {
                winLevel=4;
                winPos=p;
            }
        }
    }
    // TODO ...
    //if (winLevel<1) 
    for (int i=0; i<9; i++) { // все клетки игрового поля
        unsigned short p = 1<<i; // TODO BOT_POSITION_PRIORITY[i];
        if ( (full_map & p) == 0 ) { // для всех пустых клеток
                MAP_0 |= p; // if (playOn_X) MAP_X |= p; else MAP_0 |= p;
                int tmpWLvl=0;
                int w=testWon(MAP_X,MAP_0);
                if ( w!=win_gameNext ) {
                    if (w==win_0) tmpWLvl=40;
                } else {
                    w=itrGameHod_all(MAP_X,MAP_0, true, 0x00);
                    if (w & win_0) tmpWLvl+=4;
                    if (w & win_end) tmpWLvl+=2;
                    if (w & win_x) tmpWLvl+=0;
                }
                if (tmpWLvl>winLevel) { //|| (tmpWLvl==winLevel && (rand() % 3 == 1))) {
                    winLevel=tmpWLvl;
                    winPos=i;
                }
                MAP_X=lastMAP_X;
                MAP_0=lastMAP_0;
        }
    }
    return winPos;
}

unsigned int winWariantCashe[4]={0,0,0,0}; // Вариантов с победой слишком много, их можно объединить, сократив место.
unsigned int winWariantVer[4]={0,0,0,0}; // Сколько исходов с победой, для статистики
unsigned int setEndGameVariant(unsigned int MAP_X,unsigned int MAP_0, char* txt)
{
    unsigned int currentRecordID;
    int wonIs = testWon(MAP_X,MAP_0);
    winWariantVer[wonIs]++;
    if (winWariantCashe[wonIs]==0) {
        unsigned int addres[CASE_BLOCK_SIZE]={0,0,0,0,0,0,0,0,0}; // адреса (на каждую кнопку)
        currentRecordID=getFreeVar(); // получение свободного адреса
        setVariant(currentRecordID,addres, txt);
        winWariantCashe[wonIs]=currentRecordID;
    }
    else currentRecordID=winWariantCashe[wonIs];
    /*  было
    unsigned int addres[CASE_BLOCK_SIZE]={0,0,0,0,0,0,0,0,0}; // адреса (на каждую кнопку)
    //currentText= printField (MAP_X,MAP_0); // можно выводить победное поле и текст о победе, заняв 2 и более соседних адреса.
    int currentRecordID=getFreeVar();
    setVariant(currentRecordID,addres, txt);
    //*/
    return currentRecordID;
}

/*
 * bot играет за 0
 * перебор всех вариантов свободных
 * playOn_X - 0=за нолик, иначе за крестик
 * doRecords - вызывать функцию addNewWariant или только тестировать исходы (==0) Чтобы включить - нужен адрес переменной, в которую запишется номер новой записи
 */
int itrGameHod_all(unsigned int MAP_X,unsigned int MAP_0, bool playOn_X, int *doRecord)
{
    unsigned int lastMAP_X=MAP_X;
    unsigned int lastMAP_0=MAP_0;
    unsigned int full_map = MAP_X | MAP_0;
    int winResult=0;

    if (playOn_X) { // user, все варианты
        for (int i=0; i<CASE_BLOCK_SIZE; i++) {
            unsigned int p = 1<<i;
            if ( (full_map & p) == 0 ) { // для всех пустых клеток
                MAP_X |= p; //if (playOn_X) MAP_X |= p; else MAP_0 |= p;
                int w=testWon(MAP_X,MAP_0);
                if (w!=win_gameNext) {
                    winResult |= w;
                } else {
                    w=itrGameHod_all(MAP_X,MAP_0, !playOn_X, 0x00); // iteration
                }
                MAP_X=lastMAP_X;
                MAP_0=lastMAP_0;
            }
        }
    } else { // компьютер, лучший для него вариант
        int i=getBestBotHod(MAP_X,MAP_0);
        unsigned short p = 1<<i;
        if ( (full_map & p) == 0 ) { // для всех пустых клеток
            MAP_0 |= p;
            int w=testWon(MAP_X,MAP_0);
            if (w!=win_gameNext) winResult |= w;
            else w=itrGameHod_all(MAP_X,MAP_0, !playOn_X, 0x00); // iteration
            MAP_0=lastMAP_0;
        }
    }
    
    if (doRecord==0) return winResult; // если просто проверка
    // TODO в отдельную функцию, или совместить
    
    unsigned int addres[CASE_BLOCK_SIZE]; // адреса (на каждую кнопку)
    char currentText[Text_Block_Size]; // текстовое сообщение на этот вариант
    printField (MAP_X,MAP_0,currentText);

    int currentRecordID=getFreeVar();

    if (playOn_X) { // за человека
        for (int i=0; i<CASE_BLOCK_SIZE; i++) {
            unsigned int p = KEYBOARD_BYTE_OFFSET[i]; //1<<i;
            if ( (full_map & p) == 0 ) { // для всех пустых клеток
                MAP_X |= p; //if (playOn_X) MAP_X |= p; else MAP_0 |= p;
                int w=testWon(MAP_X,MAP_0);
                if (w!=win_gameNext) {
                    // out wo is won
                    if (w==win_x) addres[i]=setEndGameVariant(MAP_X,MAP_0, "You are won!");
                    if (w==win_end) addres[i]=setEndGameVariant(MAP_X,MAP_0, "Not win. End");
                } else {
                    int p2Int=getBestBotHod(MAP_X,MAP_0); // за 0, то есть за !playOn_X
                    // TODO if p2Int != -1 
                    short p2Bit=1<<p2Int;
                    MAP_0 |= p2Bit;
                    int w=testWon(MAP_X,MAP_0);
                    if (w!=win_gameNext) {
                        // out wo is won
                        if (w==win_0) addres[i]=setEndGameVariant(MAP_X,MAP_0, "Bot won. 0");
                        if (w==win_end)  addres[i]=setEndGameVariant(MAP_X,MAP_0, "Not win. End");
                        //addres[i]=0;// TODO addres end game
                    } else {
                        // add new wariant
                        int nextDataAddr;
                        w=itrGameHod_all(MAP_X,MAP_0, playOn_X, &nextDataAddr); // iteration
                        addres[i] = nextDataAddr;
                    }
                }
                MAP_X=lastMAP_X;
                MAP_0=lastMAP_0;
            } else {
                addres[i]=currentRecordID;// TODO addres current data
            }
        }
        currentRecordID=setVariant(currentRecordID, addres,currentText);
        *doRecord=currentRecordID;
    } else {
        cout<<"Error! Этот вариант вызова не предусмотрен."<<endl;
    }
    return winResult;
}

void outDataFormatC() {
    int minimalCaseSize=1;
    if (data_pos>0xff) minimalCaseSize=2;
    if (data_pos>0xffff) minimalCaseSize=4;
    cout<< "// Out data on C array:"<<endl;
    
    cout<<"int data_pos = 0; // max="<<data_pos<<endl;
    cout<<"int CASE_BLOCK_SIZE = "<<CASE_BLOCK_SIZE<<";"<<endl;
    cout<<"int TEXT_BLOCK_SIZE = "<<Text_Block_Size<<";"<<endl;
   
    cout<<"unsigned "; //data_addres
    if (minimalCaseSize==1) cout<<"char";
    if (minimalCaseSize==2) cout<<"short";
    if (minimalCaseSize==4) cout<<"int";
    cout<<" data_addres["<<data_pos<<"]["<<CASE_BLOCK_SIZE<<"] = {";
    for ( int i=0; i<data_pos; i++) {
        if (i!=0) cout<<", ";
        cout<<"{";
        for ( int j=0; j<CASE_BLOCK_SIZE; j++) {
            if (j!=0) cout<<", ";
            cout<<data_addres[i][j];
        }
        cout<<"}";
    }
    cout<<"};"<<endl;
    
    //text
    cout<<"char text["<<data_pos<<"]["<<Text_Block_Size<<"] = {";
    for ( int i=0; i<data_pos; i++) {
        if (i!=0) cout<<", ";
        // числами
        //cout<<"{";
        //for ( int j=0; j<TEXT_BLOCK_SIZE; j++) {
        //    if (j!=0) cout<<", ";
        //    cout<< (int)text[i][j];
        //}
        //cout<<"}";

        //cout<< """<<text[i]<<"""<<endl; // вывод строкой без спец сиволов

        cout<<""";
        for ( int j=0; j<Text_Block_Size; j++) {
            if (text[i][j]>=30) cout<< text[i][j];
            else {
                if (text[i][j]=='n') cout<<"\n";
                 else if (text[i][j]==0) cout<<"";
                  else cout<< "\("<<(int)text[i][j]<<")";
            }
        }
        cout<<""";
    }
    cout<<endl;
    cout<<"// ---- end of data ----"<<endl;

}

void outDataFormatAsm()
{
    int minimalCaseSize=1;
    if (data_pos>0xff) minimalCaseSize=2;
    if (data_pos>0xffff) minimalCaseSize=4;

    cout<<"; Out data on assembler data format"<<endl;
    cout<<"data_pos DW 0 ; max="<<data_pos<<endl;
    cout<<"CASE_BLOCK_SIZE DW "<<CASE_BLOCK_SIZE*minimalCaseSize<<" ;bytes ("<<minimalCaseSize<<" byte per 1 case)"<<endl;
    cout<<"TEXT_BLOCK_SIZE DW "<<Text_Block_Size<<endl;
    cout<<"data_addres:"<<endl; //data_addres
    for ( int i=0; i<data_pos; i++) {
        if (minimalCaseSize==1) cout<<"DB ";
        if (minimalCaseSize==2) cout<<"DW ";
        if (minimalCaseSize==4) cout<<"QW "; // data_pos wrote as DW, see up
        for ( int j=0; j<CASE_BLOCK_SIZE; j++) {
            if (j!=0) cout<<", ";
            cout<<data_addres[i][j];
        }
        cout<<endl;
    }
    cout<<endl;
    //text
    cout<<"text_data: n";
    bool textMarker=false;
    for ( int i=0; i<data_pos; i++) {
        cout<<"DB ";
        int maxOutBytes=Text_Block_Size;
        for ( int j=0; j<maxOutBytes; j++) {
            if (text[i][j]>=30) {
                if (!textMarker) {
                    if (j!=0) cout<<", ";
                    cout<<""";
                    textMarker=true;
                }
                cout<< text[i][j];
            }
            else {
                if (textMarker) {
                    cout<<""";
                    textMarker=false;
                }
                if (text[i][j]=='n') {
                    cout<<", 13,10";
                    maxOutBytes--;
                }
                /*
                else if (text[i][j]==0) cout<<", ""<<End_Of_String<<"""; // FIXME откуда нули?
                  else // TODO это только под DOS int 21h.
                 */
                else if (text[i][j]==0) cout<<", " ""; // FIXME откуда нули?
                  else // TODO это только под DOS int 21h.
                cout<< ", "<<(int)text[i][j];
            }
        }
        if (textMarker) {
            cout<<""";
            textMarker=false;
        }
        cout<<endl;
    }
    cout<<"; ---- end of data ----"<<endl;
    return;
}

/**
 * 
 * @param showInfo выводить дополнительные сведения о данных
 */
void generator_game_data(bool showInfo) {
    if (showInfo) cout<<"Start generation."<<endl;
    for ( int i=0; i<data_pos; i++) for ( int j=0; j<Text_Block_Size; j++) text[i][j]=End_Of_String;
    int startP;
    itrGameHod_all(0,0, true, &startP);
    if (showInfo) {
        cout<< "Finish generation. Start game position="<<startP<<endl;
        cout<< "Data length = "<<data_pos<<endl;
        int minimalCaseSize=1;
        if (data_pos>0xff) minimalCaseSize=2;
        if (data_pos>0xffff) minimalCaseSize=4;
        cout<< " key array size is "<<(data_pos*CASE_BLOCK_SIZE*minimalCaseSize)<<" byte ("<<minimalCaseSize<<" byte per case)"<<endl;
        cout<< " text array size is "<<(data_pos*Text_Block_Size*sizeof(char))<<" byte"<<endl;
        cout<< " Вероятность исходов: ничья "<<winWariantVer[win_end]<<", побед 0 "<<winWariantVer[win_0]<<", X "<<winWariantVer[win_x]  <<endl;
    }
}
//-------------------------------------------
void outListingC()
{
    cout<<"/*"<<endl;
    cout<<"* example short command tic-tac-toe. By godAlex generator."<<endl;
    cout<<"*/"<<endl;
    cout<<"#include <stdio.h>"<<endl;
    cout<<"#include <stdlib.h>"<<endl;
    outDataFormatC();
    cout<<"int main(int argc, char** argv) {"<<endl;
    cout<<"    while (true) {"<<endl;
    cout<<"        printf(text[data_pos]);"<<endl;
    cout<<"        int i;"<<endl;
    cout<<"        do scanf("%i",&i); while ( i<1 && i>9 );"<<endl;
    cout<<"        data_pos=data_addres[data_pos][i-1];"<<endl;
    cout<<"    }"<<endl;
    cout<<"    return (EXIT_SUCCESS);"<<endl;
    cout<<"}"<<endl;
}
void outListingAsm()
{
    cout<<"SECTION .text"<<endl;
    cout<<"org 0x100 ; .com файл"<<endl;
//    cout<<"push cs"<<endl;
//    cout<<"pop ds ; без этого тоже работает"<<endl;
    cout<<"lblShowVariant: "; //<<endl;
    cout<<"  mov ax,0x0001 ; clear screen, set graphic mode 40x25, color"<<endl;
    cout<<"  int 10h"<<endl;
/*
	; DOS
;	mov ax, [data_pos]
;	mov bx, [TEXT_BLOCK_SIZE]
;	mul bx
;	mov dx, text_data
;	add dx,ax ; offset на текст
;	mov ah, 0x9 ; print [dx]
;	int 0x21 ; dos, dx-указывает на строку, завершающуюся $
*/
//; BIOS
    cout<<"  mov ax, [data_pos]"<<endl;
    cout<<"  mov bx, [TEXT_BLOCK_SIZE]"<<endl;
    cout<<"  mul bx"<<endl;
    cout<<"  mov bp, text_data"<<endl;
    cout<<"  add bp,ax  ; offset на текст"<<endl;
    cout<<"  mov cx,[TEXT_BLOCK_SIZE]"<<endl;
    cout<<"  mov ax,1300h"<<endl;
    cout<<"  mov bx,0eh ; color"<<endl;
    cout<<"  mov dx,0500h ; 5 строка 0 позиция для вывода"<<endl;
    cout<<"  int 10h"<<endl;
    cout<<"lReadKey: "; //<<endl;
    cout<<"  rep nop ; можно убрать, добавлена в надежде снизить нагрузку на ЦП"<<endl;
    cout<<"  xor ax,ax"<<endl;
    cout<<"  int 16h ; BIOS read key"<<endl;
    cout<<"  xor ah,ah"<<endl;
    cout<<"  sub al,'1' ; в al число запишем, а не символ"<<endl;
    cout<<"  cmp ax,8"<<endl;
    cout<<"  ja lReadKey"<<endl;
    cout<<"  shl ax,1 ; ax=ax*2"<<endl;
    cout<<"  mov bx, data_addres"<<endl;
    cout<<"  add bx,ax ; bx = data_addres[key]"<<endl;
    cout<<"  mov ax, [data_pos]"<<endl;
    cout<<"  mov cx, [CASE_BLOCK_SIZE]"<<endl;
    cout<<"  mul cx ; cx = [data_pos]"<<endl;
    cout<<"  add bx,ax ; bx = data_addres[data_pos][key]"<<endl;
    cout<<"  mov ax,[bx]"<<endl;
    cout<<"  mov [data_pos],ax ; переход на новый ключ."<<endl;
    cout<<"  jmp lblShowVariant"<<endl;
/*	
	;mov ax, 0x4c00; DOS EXIT, но у нас игра не закончится ))
	;int 0x21
*/
	
    cout<<"SECTION .data"<<endl;
    
    // TODO если вставить код 07 в сообщения о победе - будет звуковой сигнал (PC speaker))
    outDataFormatAsm();
}
void outListingHTML()
{
    cout<<"<html>"<<endl;
    cout<<"<head>"<<endl;
    cout<<"<!-- 0 строк JS -->"<<endl;
    cout<<"<script type="text-javascript">"<<endl;
    cout<<"</script>"<<endl;
    cout<<"<style>"<<endl;
    cout<<" div {"<<endl;
    cout<<"  height: 100%"<<endl;
    cout<<" }"<<endl;
    cout<<"</style>"<<endl;
    cout<<"</head>"<<endl;
    cout<<"<body>"<<endl;
    for ( int i=0; i<data_pos; i++) {
        cout<<"<div id="p"<<i<<"">"<<endl;
        if (text[i][0]=='X' || text[i][0]=='0' || text[i][0]=='.') {
            cout<<"<table border="border">"<<endl;
            cout<<"<tr>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][0]<<"">"<<text[i][6+2]<<"</a></td>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][1]<<"">"<<text[i][7+2]<<"</a></td>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][2]<<"">"<<text[i][8+2]<<"</a></td>"<<endl;
            cout<<"</tr><tr>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][3]<<"">"<<text[i][3+1]<<"</a></td>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][4]<<"">"<<text[i][4+1]<<"</a></td>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][5]<<"">"<<text[i][5+1]<<"</a></td>"<<endl;
            cout<<"</tr><tr>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][6]<<"">"<<text[i][0]<<"</a></td>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][7]<<"">"<<text[i][1]<<"</a></td>"<<endl;
            cout<<"  <td><a href="#p"<<data_addres[i][8]<<"">"<<text[i][2]<<"</a></td>"<<endl;
            cout<<"</tr>"<<endl;
            cout<<"</table>"<<endl;
        }
        else cout<<"<a href="#p"<<data_addres[i][0]<<"">"<<text[i]<<"</a>"<<endl;
        cout<<"</div>"<<endl;
    }
    cout<<"</body>"<<endl;
    cout<<"</html>"<<endl;
}

int main(int argc, char** argv) {
    int outFormat=-1; // 0 - assembler data, 1 - C array
    if (argc==2) {
        if ( strcmp(argv[1],"--help")==0 ) {
            cout<<"Неверное количество аргументов. Введите параметр --help для справки."<<endl;
            cout<<" --help вывод этой справки."<<endl;
            cout<<" -a вывод данных в формате для ассемблера (по умолчанию)"<<endl;
            cout<<" -с вывод данных в формате С массива"<<endl;
            cout<<" -sa вывод готовой программы на ассемблере"<<endl;
            cout<<" -sс вывод готовой программы на C"<<endl;
            cout<<" -sh вывод в HTML"<<endl;
            return 0;
        }
        if ( strcmp(argv[1],"-a")==0 ) outFormat=0;
        if ( strcmp(argv[1],"-c")==0 ) outFormat=1;
        if ( strcmp(argv[1],"-sa")==0 ) outFormat=2;
        if ( strcmp(argv[1],"-sc")==0 ) outFormat=3;
        if ( strcmp(argv[1],"-sh")==0 ) outFormat=4;
    } else outFormat=0; // Установка формата по умолчанию. default - assembler out (0-asm, 1-C array))
    if ( argc>2 || outFormat==-1 ) {
        cout<<"Неверное количество или значения аргументов. Введите параметр --help для справки."<<endl;
        return 1;
    }
    
    if ( outFormat==0 || outFormat==2 ) {
        //End_Of_String = '$'; // вариант конца строки для DOS
        Text_Block_Size=13+3;
    }
    generator_game_data( outFormat<2);
    
    if (outFormat==0) outDataFormatAsm();
    if (outFormat==1) outDataFormatC();
    
    if (outFormat==2) outListingAsm();
    if (outFormat==3) outListingC();
    if (outFormat==4) outListingHTML();
    
   return 0;
}

Компилируете, запускаете с параметром "--help" для вызова справки, или сразу получаете исходники:

  • Ассемблер: «generator.exe -sa > name.asm», затем компилируете. Если есть nasm, то компиляция производится командой «nasm -f bin name.asm -o asm30TTG.com», затем запускаете «asm30TTG.com» и играете. Выход из игры не предусмотрен, место для него не хватило.
  • Си: «generator.exe -sc > name.c» и получаете исходник.
  • HTML: «generator.exe -sh > name.html»
В заключение

Сделать игру за 30 строк кода можно не только на Java Script, но и на других языках, например ассемблере. Компилятор может оптимизировать программу и на минимальное время выполнения, и на размер. Но выбор алгоритм ускоряет или сокращает работу программы намного больше, чем может это сделать оптимизация.

Автор: godAlex

Источник

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


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