В прошлом году были популярны темы, как написать программу за 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).
Для проверки идеи сделал реализацию на Си.
Игра выводит игровое поле на экран в следующем виде:
Пользователь нажимает на цифровой клавиатуре цифры, по позиции соответствующие клетке. Например цифра 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", "...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);
}
А что будет на ассемблере?
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<<"";
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