А можно ли написать не полностью бесполезную программу за 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).
Для проверки идеи сделал реализацию на Си.
Игра выводит игровое поле на экран в следующем виде:

Пользователь нажимает на цифровой клавиатуре цифры, по позиции соответствующие клетке. Например цифра 5 — это центр, 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. Может попробовать самому перевести на ассемблер?
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\0", "...\n...\nX0.\n\0", "...\n..0\nX0X\n\0", ...}; 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); }
А что будет на ассемблере?
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).

Половина дела сделана. Теперь осталось написать массивы данных, состоящие из нескольких тысяч адресов и вариантов игры.
Приблизительные расчёты размера данных:
- 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.
/* * 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<<"\\0"; 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, но и на других языках, например ассемблере. Компилятор может оптимизировать программу и на минимальное время выполнения, и на размер. Но выбор алгоритм ускоряет или сокращает работу программы намного больше, чем может это сделать оптимизация.
ссылка на оригинал статьи http://habrahabr.ru/post/207964/
Добавить комментарий