Простая игра с казино и бодалкой



В этой главе покажем 2 простые игровые модели: казино и бодалка. Начнём с казино.

Мы делаем ставку например 1000 монет. Выбираем, на что ставим: больше, меньше или равно. Ты и противник кидаете по 3 кубика. Если у тебя в сумме очков выпало больше и ты ставил на больше, то ты выиграл и тебе возвращается ставка и даётся ещё столько же. Если выпало меньше или равно- ты теряешь ставку. Аналогично со ставкой на меньше. Если выигрыш при ставке на равно, то помимо своей ставки, тебе даётся ещё четырёхкратная сумма. Эта игра называется "так иль так" и взята с некогда известной браузерной игры "ботва онлайн"

...

Вот функция работы этого казино. Его можно вставлять и в более крупную игру даже, что тут и происходит. На вход подаётся тип ставки (_stav), количество денег (mon- оно меняется, поэтому знаком амперсанда) и значение ставки.

bool kubwin (int _stav, int &mon, int stav) {

           cout << "Бросаем кубики..\n";

           cout << "Твои кубики: ";

           int kub[3]; int Kub=0;

           for(int ii=0; ii<3; ii++) {

                           kub[ii]= rand()%6+1;

                           cout << kub[ii] << " ";

                           Kub+=kub[ii];

           }

           cout << "(всего " << Kub << ")";

           _();

           cout << "Кубики бармена: ";

           int kub_en[3]; int Kub_en=0;

           for(int ii=0; ii<3; ii++) {

                           kub_en[ii]= rand()%6+1;

                           cout << kub_en[ii] << " ";

                           Kub_en+=kub_en[ii];

           }

           cout << "(всего " << Kub_en << ")";

           _();

           if (((_stav==2)&&(Kub>Kub_en))||((_stav==1)&&(Kub<Kub_en))) {

                           mon+=stav;

                           cout << "Ты выиграл, награда +" << stav << endl;

                           return true;

           }

           if ( ((_stav==2)&&(Kub<=Kub_en))||((_stav==1)&&(Kub>=Kub_en))||((_stav==0)&&(Kub!=Kub_en)) ) {

                           mon-=stav;

                           cout << "Ты проиграл, убыль -" << stav << endl;

                           return false;

           }

           if ((_stav==0)&&(Kub==Kub_en)) {

                           mon+=stav*9;

                           cout << "Ты выиграл, награда +" << stav*4 << endl;

                           return true;

           }

}

А вот запуск этой функции:

cout << "+ КАЗИНО -> делай ставку от 10\n";

cin >> u;

 

// казино (доступно с 6-го уровня только)

if ((lvl>=6)&&(u>=10)&&(u>money)) cout << "Ставка больше, чем денег у тебя!\n";

if ((lvl>=6)&&(u>=10)&&(u<=money)){

           _();

           cout << "Если ставим на больше, жми 2\n";

           cout << "Если ставим на меньше, жми 1\n";

           cout << "Если ставим на равно, жми 0\n";

           cout << "Ставка: "; int stavka_type; cin >> stavka_type;

           _();

           tapkyr[0]++; // для учёта статистики- сколько игр сыграно

           int Fix= money; // для сохранения начального количества денег- оно в функции казино потому что меняется

           // если выиграл

           if (kubwin(stavka_type,money,u)) {

                           stopmoney(money); // проверка на превышение максимального количества денег

                      cashwin+= (money-Fix); // опять же для статистики

                      __();

                      info (name,lvl,e,s,b,l,money,hp); // игровые значения вывод сверху

           }

           // если проиграл

           else {

                      cashlose+= (Fix-money);

                      hp/=1.2;

                      __();

                      info (name,lvl,e,s,b,l,money,hp);

           }

}

Теперь бодалка- это по сути просто сражение 2 игроков. Исход битвы определяется такими характеристиками как сила, защита, ловкость + есть элемент случайности чтобы было интереснее.

// бодалка- на вход сила, броня, ловкость, здоровье, уровень и потом соответствующие характеристики врага + diff это уровень сложности соперника

char win(int S, int B, int L, int &HP, int LVL, int S_en, int B_en, int L_en, int &HP_en, int LVL_en, int diff) {

           // 0- проигрыш, 1- выигрыш, 2- ничья (возвращаемые значения)

 

           int strike_num=0; // количество ударов

           int Dam=0, Dam_en=0, dam, dam_en; // уроны

           int p,q; // случайные факторы

 

           // само сражение

           while ((strike_num<LVL)&&(HP>0)&&(HP_en>0)) {

                           strike_num++;

                               

                           // твой удар

                           p = rand()%100+1;

                           if ((p<=L)&&(LVL<5)) dam= S*2;

                           if ((p<=L)&&(LVL>=5)) dam= S*3;

                           if (p>L) dam= S;

 

                           // защита врага

                           q = rand()%100+1;

                           if ((q<=B_en+L_en)&&((diff==0)||(diff==1))) {

                                           HP_en-=dam/2;

                                           Dam+=dam/2;

                                           cout << "[enemy armour] " << (int)(dam - dam/2) << endl;

                           }

                           if ((q<=B_en+L_en)&&(diff==2)) {

                                           HP_en-=dam/2.5;

                                           Dam+=dam/2.5;

                                           cout << "[enemy armour] " << (int)(dam - dam/2.5) << endl;

                           }

                           if (q>B_en+L_en) {

                                           HP_en-=dam;

                                           Dam+=dam;

                           }

 

                           // удар врага

                           p = rand()%100+1;

                           if ((p<=L_en)&&((diff==0)||(diff==1))) dam_en= S_en*2;

                           if ((p<=L_en)&&(diff==2)) dam_en= S_en*2.5;

                           if (p>L_en) dam_en= S_en;

 

                           // твоя защита

                           q = rand()%100+1;

                           if ((q<=B+L)&&(LVL<5)) {

                                           HP-=dam_en/2;

                                           Dam_en+=dam_en/2;

                                           cout << "! [my armour] " << (int)(dam_en - dam_en/2) << endl;

                           }

                           if ((q<=B+L)&&(LVL>=5)) {

                                           HP-=dam_en/3;

                                           Dam_en+=dam_en/3;

                                           cout << "! [my armour] " << (int)(dam_en - dam_en/3) << endl;

                           }

                           if (q>B+L) {

                                           HP-=dam_en;

                                           Dam_en+=dam_en;

                           }

                               

                           // информация о том как происходит битва

                           HP/=((100+LVL_en)/100); // типа усталость- здоровье тоже уменьшается

                           cout << "Удар " << strike_num; _();

                           cout << "! my hp " << HP << "/" << maxhp(LVL); _();

                           cout << "enemy hp " << HP_en << "/" << maxhp(LVL_en); _();

                           cout << "! my damage " << dam; _();

                           cout << "enemy damage " << dam_en; _(2);

           }

           cout << "My Total Damage " << Dam; _();

           cout << "Enemy Total Damage " << Dam_en; _(2);

 

           // итоговый исход битвы

           if ( ((Dam>Dam_en)||(HP_en<=0)) && (HP>0) ) return '1';

           else if ( (Dam==Dam_en) && (HP_en>0) && (HP>0) ) return '2';

           else return '0';

}

Вот пример запуска этой функции в той же программе:

                                                           int diff= Select();

                                                           tapkyr[1]++;

                                                           int en_s, en_b, en_l, en_hp, en_lvl;

                                                           int nran= rand()%50 + 51;

                                                           if((dozorlimit)&&(diff!=3)) dozors-=(diff+1);

 

                                                           if(diff==0) {

                                                                           // характеристики врага

                                                                           if ((lvl==1)||(lvl==2)) en_lvl= 1;

                                                                           if (lvl>2) {

                                                                                           int L_= rand()%3;

                                                                                           en_lvl= lvl - L_; 

                                                                           }

                                                                           en_hp= maxhp(en_lvl)*nran/100;

                                                                           int har[3];

                                                                           for(int i=0; i<=2; i++) har[i]= rand()%7;

                                                                           en_s = s - har[0];

                                                                           en_b = b - har[1];

                                                                           en_l = l - har[2];

                                                                           if(en_s<1) en_s=1;

                                                                           if(en_b<1) en_b=1;

                                                                           if(en_l<1) en_l=1;

                                                                           if(en_s>25) en_s=25;

                                                                           if(en_b>25) en_b=25;

                                                                           if(en_l>25) en_l=25;

                                                                           int M= rand()%(60*en_lvl*en_lvl);

 

                                                                           _();

                                                                           cout << "Характеристики врага:\n";

                                                                           cout << "--сила " << en_s << endl;

                                                                           cout << "--защита " << en_b << endl;

                                                                           cout << "--ловкость " << en_l << endl;

                                                                           cout << "--уровень " << en_lvl << endl;

                                                                           cout << "--здоровье " << en_hp << endl;

                                                                           __();

 

                                                                           char fight= win(s,b,l,hp,lvl,en_s,en_b,en_l,en_hp,en_lvl,diff);

                                                                           if (fight=='1') {

                                                                                           cout << "Ты победил!" << endl;

                                                                                           cout << "Награда:\n$ +" << (int)(M*0.05*lvl) << "\n[] +1" << endl;

                                                                                           __();

                                                                                           money+= M*0.05*lvl; cash[1]+= M*0.05*lvl;

                                                                                           stopmoney(money);

                                                                                           e++; tecribe[1]++;

                                                                                           levelup(e,lvl);

                                                                           }

                                                                           if (fight=='2') {

                                                                                           cout << "Ничья!" << endl;

                                                                                           cout << "Награда:\n[] +1" << endl;

                                                                                           __();

                                                                                           e++; tecribe[1]++;

                                                                                           levelup(e,lvl);

                                                                           }

                                                                           if (fight=='0') {

                                                                                           cout << "Ты проиграл :((" << endl;

                                                                                           cout << "Потеря: -" << money << endl;

                                                                                           __();

                                                                                           money=0;

                                                                           }

                                                           }

 

Новелла с выбором сюжета

Ещё один тип программы- программа в виде книги. Ты нажимаешь на ENTER и читаешь новую строку. Как в визуальных новеллах, только тут нет картинок. И иногда можно выбрать на какую ветку сюжета пойти. Посмотрим пример такой реализации.

Сами рассказы могут выглядеть так:

// НАЧАЛО ПРОГУЛКИ

string opening[15]= {

           "Как-то прохладным весенним днём сидел парень и грустил..",

           "~ Почему у меня нет девочки? Почему я не могу с ней познакомиться? Хнык",

           "~ Нужно сходить на улицу, в центр города- попробую с кем-нибудь познакомиться",

           "~ Ну или хотя бы просто заговорить.. ((",

           "~ В нашем городе много красивых девочек (( эхх..",

           "Парень спокойно вышел из дома, хоть и с неким социофобным волнением.",

           "И отправился в сторону автобусной остановки.",

           "Через несколько минут он уже ожидал автобус в центр города.",

           "Время было 4 часа дня, довольно светло- хоть солнце и скрылось за тучами",

           "На остановке сидела какая-то пожилая тётка, мужик..",

           "Но где-то в углу сидела неприметная девочка лет 16-ти и уткнулась в телефон",

           "Можно было разглядеть, что она весьма симпатична и наверное дружелюбна",

           "Парень на минутку задумался, сердце заволновалось, по телу прошлась депрессивная тоска и скованность",

           "~ Может подойти, хотя бы просто сказать привет и потрогать за ручку?..",

           "~ Автобус скоро приедет и я могу упустить свой шанс. Жалко пропускать такую девочку.."

};

 

string opening_select[3]= {

           "Подойти к девочке",

           "Оставаться на месте",

           "У меня плохое настроение"

};

 

string open_vbros[9]= {

           "~ Всё-таки я решил к ней подойти, не знаю что будет, но мне захотелось",

           "Найдя в себе какие-то силы, я направился к ней",

           "Моё сердце дрожало, но для меня это было маленькое приключение",

           "Девочка сидела всё так же невозмутимо, словно не заметив моего приближения",

           "Я стоял перед ней и аккуратно дотронулся до её нежной мягкой ручки",

           "Игрок: Привет :)",

           "Девочка обратила на меня внимания, неохотно переключая внимание с телефона на меня",

           "Девочка: привет..",

           "Зависло неловкое молчание- я не знал что дальше делать"

};

 

string open_vbros_select[5]= {

           "Попытаться познакомиться",

           "Задать ситуационный вопрос",

           "Сделать комплимент и попробовать обнять",

           "Ничего не говорить, сесть рядом",

           "Уйти"

};

 

string open_ryadom[3]= {

           "Я просто сел рядом",

           "Девочка продолжила сидеть, не обращая на меня внимания",

           "Вскоре мы спокойно разъехались в разные стороны"

};

 

string open_escape[2]= {

           "Не найдя что делать дальше, я просто ушёл",

           "Состояние чуточку поднялось, я стал чувствовать себя лучше"

};

 

string open_situat[5]= {

           "Я: ты тоже в центр едешь?",

           "Девочка: нет",

           "Я: погода сегодня хорошая, да?",

           "Девочка: наверное",

           "В общем, разговор не ладился и я решил просто уйти, уже будучи довольным"

};

 

string open_obn[3]= {

           "Я: ты очень симпатичная",

           "Девочка: спасибо",

           "Я: иди ко мне, дай я тебя обниму",

};

 

string open_obn_luck[2]= {

           "Девочка обнялась со мной",

           "Мне было очень приятно и я почувствовал себя лучше"

};

 

string open_obn_fail[2]= {

           "Девочка: нет, я не хочу",

           "Я оставил девочку в покое и отошёл, было немного грустно и обидно"

};

 

string open_stop[3]= {

"Вон уже автобус подъезжает, пойду прыгну на него",

"Всё-таки в центре города будет ещё много симпатичных девушек, ничего страшного",

"Зато я чувствую себя более менее нормально, будем постепенно разогреваться"

};

Все эти рассказы можно хранить в отдельном заголовочном файле. Я назвал его hikaye.h

...

Вот ещё функции, которые тут пригождаются:

// чтение рассказа

void reading(string s[], int n, bool sel=false) {

           __(0);

           for(int i=0; i<n; i++) {

                           cout << s[i];

                           if(sel) { if(i==0) cin.get(); }

                           cin.get();

           }

}

Параметр sel нужен т.к. иногда сразу выводится 2 строки в начале- а нам нужно чтобы выводилась только одна. Если мы до чтения что-то вводили, то это как бы остаётся в памяти компьютера и ему кажется что ENTER нажат 2 раза. Для продолжения чтения мы по сути можем не только ENTER нажимать, но и любую клавишу- используется cin.get() для этого.

 

// выбор 1 из нескольких сюжетов + обработка ошибок если ввели что-то не то

int selection (string s[], int n) {

           int res= -1;

           do {

                           _(2);

                           for(int i=0; i<n; i++) cout << "(" << i << ") " << s[i] << endl;

                           cout << "--> "; cin>>res;

                           if((res<0)||(res>=n)) cout << "Ошибка ввода!" << endl;

           } while ((res<0)||(res>=n));

           return res;

}

А вот как все эти рассказы и функции чтения-выбора могут работать уже в исполняемом файле:

int key, k[5]={0,0,0,0,0}; // выбор сюжета

// НАЧАЛО ПРОГУЛКИ

           reading(opening,15);

           key= selection(opening_select,3);

           if(key==0) {

                           score+=2; hp-=10; // внутренние очки и здоровье- чтоб было интереснее

                           reading(open_vbros,9,true); // true т.к. до этого ввели key

                           k[0]= selection(open_vbros_select,5);

                           if(k[0]==0) {

                                           reading(open_tanishu,5,true);

                                           hp-=5;

                                           r= rand()%10; // случайный параметр- девочка захотела что-то там сделать или нет

                                           if(r<=5) {

                                                           reading(open_fail,3); // без true т.к. до этого ввода не было

                                                           hp-=(rand()%10 + 10);

                                           }

                                           if(r==6) {

                                                           reading(open_fail_x,3);

                                                           hp-=2*(rand()%10 + 10);

                                           }

... и так далее

                                               

Шифрование данных

Бывают такие данные, которые предпочтительно хранить в зашифрованном виде, например пароли доступа или передаваемые сообщения. Для этого мы можем написать программу-шифровальщик.

Есть много способов шифрования. Рассмотрим самый простой- конкретный посимвольный шифр. Суть в том что одному символу задаётся какой-то другой символ или последовательность конкретно нескольких символов.

Например, мы можем кодировать 16-ричное число в двоичное:

0 - 0000

1 - 0001

...

E- 1110

F - 1111

Каждой 16-ричной цифре соответствует 4 конкретные двоичные цифры. И если нам надо из двоичного числа расшифровать в 16-ричное, мы можем разбить всё число на группы из 4 цифры и сделать дешифровку.

Рассмотрим пример шифра по такому алгоритму. Мы вводим некую строку. Она разбивается на символы. Каждый символ будет кодироваться как 3 цифры: мы переведём его сначала по таблице ASCII и потом сделаем некие арифметические операции чтобы точно было 3 цифры.

// шифрование одного символа в 3 цифры (они хранятся в формате string)

string ShifrChar (char c) {

           int t= (int)c; // перевод по таблице ASCII

           t= 100 + 2*t + 3; // арифметическое преобразование

           string res= IntToString(t); // перевод в строку

           return res;

}

// шифрование всей строки в новую строку

string Shifr (string s) {

           string S="";

           for(int i=0; i<StringSize(s); i++) S+= ShifrChar(s[i]);

           return S;             

}

А вот функция, которая уже расшифровывает всё обратно. Мы разбиваем строку на массив строк из 3 символов, затем расшифровываем отдельно каждый элемент этого массива строк в массив исходных символов и из этого массива исходных символов собираем исходное слово.

string DeShifr (string s) {

           int n= StringSize(s)/3;

           string *data= new string [n];

           for(int i=0; i<n; i++) {

                           data[i]= "";

                           for(int j=0; j<=2; j++) data[i]+= IntToString(CharToInt(s[3*i + j]));

           }

           int *mine= new int [n];

           for(int i=0; i<n; i++) {

                           mine[i]= StringToInt(data[i]);

                           mine[i]= ( mine[i] - 103 ) / 2;

           }

           char *word= new char [n];

           for(int i=0; i<n; i++) {

                           word[i]= (char)mine[i];

           }

           return word;

}

Пример пароля: koshka_mus_ka_2716

Зашифрованный вид: 317325333311317297293321337333293317297293203213201211

Другой вариант конкретного посимвольного шифра- мы шифруем не в виде цифр, а в виде других символов. Например, которые стоят далее по алфавиту: а- б, б- в, в- г и т.д. Например, слово "привет" будет зашифровано в "рсйгёу". Расшифровать обратно тоже легко- надо просто сдвинуть все символы по алфавиту назад.

Можно усложнить этот шифр и делать сдвиг не на 1 единицу алфавита вперёд, а на неких n единиц, где n может принимать любое целое значение. Если оно отрицательно, то двигаемся назад. Если оно превысило количество разных символов в алфавите, то движемся по кругу.

Более сложный вариант- сделать это число n переменным. Т.е. 1-й символ в слове мы по алфавиту сдвигаем вперёд на 1, 2-й символ на 2 и т.д. Расшифровывать тоже понятно как в обратную сторону по тому же циклу. Причём n может быть и более сложной функцией, главное чтобы аргумент и значение были однозначны, т.е. для одного конкретного итератора один конкретный сдвиг и наоборот: n= f(i), где i- порядковый номер символа в слове.

Как ещё усложнить этот шифр? Можно чтобы функция сдвига была неоднозначной- тогда мы просто будем дешифровать несколько вариантов и выбирать из них тот (человек уже будет выбирать), который имеет больше смысла. Для шифровки и дешифровки осмысленного текста это годится, а вот для паролей и ключей уже нет.

...

Более сложный вариант посимвольного шифрования- иметь несколько шифров, которые будут чередоваться. Например, для чётных по счёту символов шифруем по одному шифру, а для нечётных по-другому. Ниже будет вариант такого шифра. При этом мы будем использовать в качестве 2 разных шифров 2 разные функции посимвольного сдвига. Главное чтобы эти функции не пересекались.

int sp[6]= {2,20,51,1,15,2}; // параметры функции посимвольного сдвига

 

// функция посимвольного сдвига- на сколько символов по таблице ASCII сдвигаться

int fshifr (int i) {

           if(i%2) return sp[0]*(i%sp[1]) + sp[2];

           else return sp[3]*(i%sp[4]) + sp[5];

}

 

// шифрование одного символа

char ShifrChar (char c, int i) {

           int x= (int)c;

           x+= fshifr(i);

           c= (char)x;

           return c;

}

 

// шифрование всей строки

string Shifr (string s) {

           string res= ""; // делаем так чтобы не было кракозябр в конце как в случае с динамическим массивом символов

           for (int i=0; i<StringSize(s); i++) res[i]+= ShifrChar(s[i],i);

           return res;

}

//

Дешифратор:

char DeShifrChar (char c, int i) {

           int x= (int)c;

           x-= fshifr(i);

           c= (char)x;

           return c;

}

string DeShifr (string s) {

           string res="";

           for (int i=0; i<StringSize(s); i++) res[i]+= DeShifrChar(s[i],i);

           return res;

}

 

Ещё более сложное шифрование- это иметь переменное значение этого поля:

int sp[6]= {2,20,51,1,15,2}; // параметры функции посимвольного сдвига

Оно будет зависеть например от размера символов в строке. И поэтому для строк с разным количеством символов шифры будут уникальны. Это делает шифры более сложные и поэтому их будет труднее расшифровать недоброжелателям.

...

Помимо конкретных посимвольных шифр есть и неконкретные. В первом случае у нас одному символу идёт n= const символов зашифрованных. А в неконкретных посимвольных шифрованиях одному символу может соответствовать переменное количество зашифрованных символов.

В простом варианте это может быть линейная функция- тогда будет легко написать дешифратор. Например, 1-й символ мы кодируем 1 символом, 2-й символ уже 3 символами, 3-й пятью символами и так далее- нужно лишь придумать что вносить в эти символы. Можно делать те же посимвольные сдвиги, но разные- меняя функцию сдвига для каждого следующего символа по некой тоже например линейной функции.

...

Это переменное количество зашифрованных элементов можно сделать в зависимости от количества символов в строке. Например, у нас N символов в строке. И тогда мы будем шифровать 1-й символ N+1 символом, 2-й символ- N+2 символами и так далее. Получится весьма длинное зашифрованное сообщение.

В дешифрации надо опять разбить зашифрованное на символы. Для этого надо понять, сколько было символов вообще:

(N+1) + (N+2) + ... (N+N) = N*N + (1+2+...+N) = N*N + N*(N+1)/2 = N*(3*N+1)/2 = F(N)

Т.е. для N исходных символов мы получаем F(N) зашифрованных символов. В дешифрации надо наоборот перевести из F(N) в N, для этого понадобится решить квадратное уравнение.

...

Важный момент напоследок стоит упомянуть- работа происходит только с латинскими символами, т.к. задействована таблица ASCII. Русские символы там тоже есть, но они уже не воспринимаются, дешифрация не происходит- потому что эти символы относятся уже к юникоду. И тут для русских букв можно при желании написать что-то отдельное, внося проверку и шифры-дешифры для каждой из 33 букв.

 

________________________________________________

РАЗДЕЛ 6

Математические приложения

 

Производные и интегралы

Предположим, у нас есть функция f(x) и нам нужно найти её производную в точке a, т.е. f'(a). С помощью программирования это делается легко и мгновенно:

double a; cin >> a;

double dx= 0.000001;

double pr= ( f(a+dx) - f(a) ) / dx;

cout << pr;

На примере функции x*x. Её производная в точке x будет равна 2*x. Это для проверки. А теперь программа:

#include <iostream>

using namespace std;

double f (double x) { return x*x; }

int main () {

           double x; cin >> x; double dx= 0.000001;

           double pr= ( f(x+dx) - f(x) ) / dx;

           cout << pr << endl;

           system ("pause");

}

Прикол в том, что вместо return x*x мы можем вписать любую, даже какую-нибудь очень сложную функцию, например: log(x*sin(x) - cos(pow(x,x*atan(x)))) * sqrt (x*cbrt(x+log2(2*x))); Для компьютера это как 2 пальца об асфальт, он всё равно найдёт ответ за долю секунды (если этот ответ существует). Вручную мы могли бы считать это примерно полчаса, если не больше.

...

Теперь посмотрим как считать производную от производной. Например, для функции куба f(x)= x^3 будет производная 3*x^2, а вторая производная: 6*х. Это для проверки.

Сделаем функцию, которая считает производную в точке х:

double ff (double x, const double dx= 0.000001) {

           return ( f(x+dx) - f(x) ) / dx;

}

И тогда сама вся программа:

#include <iostream>

#include <cmath> // для функции куба необязательно

using namespace std;

const double dx= 0.000001;

double f (double x) { return x*x*x; }

double ff (double x) { return ( f(x+dx) - f(x) ) / dx; }

int main () {

           double x; cin >> x;

           double pr= ( ff(x+dx) - ff(x) ) / dx;

           cout << pr << endl;

           system ("pause");

}

Тут уже заметна погрешность: для х=1 выводит 5.99987 вместо 6. Чтобы уменьшить погрешность, нам нужно уменьшить число dx. Проблема в том, что в вычислениях берётся только 6 знаков после запятой и в связи с этим число dx просто рубится до нуля, если сделать его меньше.

Можно записать программу чуть проще:

#include <iostream>

#include <cmath> // для функции куба необязательно

using namespace std;

const double dx= 0.000001;

double f (double x) {

           return x*x*x;

}

double f1 (double x) {

           return ( f(x+dx) - f(x) ) / dx;

}

double f2 (double x) {

           return ( f1(x+dx) - f1(x) ) / dx;

}

int main () {

           double x; cin >> x;

           cout << f2(x) << endl;

           system ("pause");

}

Если заменить приближение производной на ( f(x+dx) - f(x-dx) ) / 2*dx; то точность вычисления заметно возрастает.

Аналогичным продолжением можно посчитать третью производную и т.д. При каждой новой производной погрешность будет увеличиваться. Рекурсивный метод вычисления производной n-го порядка не найден.

...

Теперь посмотрим как считать определённый интеграл или площадь криволинейной трапеции.

Здесь существует 3 стандартных численных метода: прямоугольников, трапеций и симпсона (парабол). Первый даёт больше погрешности, последний- сложный и запутанный в реализации. Поэтому будем использовать наиболее интуитивно очевидный- метод трапеций.

Берётся некий шаг dx, который как можно ближе к нулю. Считается интеграл от функции f(x) от A до B. Тогда промежуток равен (B-A) и число разбиений примерно будет равно N= (B-A)/dx. Можно задать число разбиений точно, тогда шаг dx вычислится автоматически: dx= (B-A)/N. Начинаем с точки A и идём считать N слагаемых- площади маленьких трапеций.

double sum= 0;

for (int i=0; i<N; i++) sum+= ( f(A + i*dx) + f(A + (i+1)*dx) ) * dx /2;

Тогда в переменной sum будет значение интеграла.

Полная программа на примере функции x^2. Первообразная будет (x^3)/3, а интеграл (B^3 - A^3) / 3. Можно свериться.

#include <iostream>

#include <cmath> // для f(x)=x*x необязательно

using namespace std;

double f (double x) { return x*x; }

int main () {

           double A,B,N; cin>>A>>B>>N;

           double dx= (B-A)/N, sum= 0;

           for (int i=0; i<N; i++) sum+= ( f(A + i*dx) + f(A + (i+1)*dx) ) * dx /2;

           cout << sum << endl;

           system ("pause");

}

Вместо f(x) можно в return прописать любую интегрируемую функцию и ответ будет найден так же быстро. Чем больше разбиений (N), тем меньше погрешность, но тем дольше считает программа.

...

Дополнение: вычисление длины графика.

Чтобы вычислить длину графика на некотором промежутке, надо разбить его на большое количество (N) маленьких ломаных и длины этих ломаных сложить. Пусть у нас есть функция f(x). Тогда мы получаем функцию кусочка ломаной из теоремы Пифагора: L(x)= sqrt (1 + f'(x)) и далее это проинтегрировать. Мы поступим немножечко по другому.

Чтобы получить длину кусочка ломаной, нужно знать 2 точки (концы куска). Пусть промежуток от a до b, разбиваем на N кусков. Тогда шаг dx= (b-a)/N; И первая точка куска ломаной будет a;f(a), вторая a+dx;f(a+dx).  Расстояние между 2 точками запишем в функцию:

double q2 (double x) {

           return x*x;

}

double dist (double x1, double x2) {

           return sqrt ( q2(x2-x1) + q2(f(x2)-f(x1)) );

}

И далее само суммирование в цикле:

double sum=0;

for (int i=0; i<N; i++) sum+= dist (a+i*dx, a+(i+1)*dx);

И в значении sum получим приблизительную длину графика.

Пример полной программы, которая вычисляет длину параболы y=x*x на промежутке от A до B с разбиением на N кусков:

#include <iostream>

#include <cmath> // для f(x)=x*x необязательно

using namespace std;

double f (double x) { return x*x; } // сюда можно подставить любую непрерывную однозначную функцию

double q2 (double x) { return x*x; }

double dist (double x1, double x2) { return sqrt ( q2(x2-x1) + q2(f(x2)-f(x1)) ); }

int main () {

           double A,B,N; cin>>A>>B>>N;

           double dx= (B-A)/N, sum= 0;

           for (int i=0; i<N; i++) sum+= dist (A+i*dx, A+(i+1)*dx);

           cout << sum << endl;

           system ("pause");

}

...

Завершение: вычисление пределов.

Тут совсем всё просто- мы должны просто взять значение, наиболее близкое к предельному. Например, нужно вычислить предел sin(x)/x при x стремящемся к нулю. Как известно, это первый замечательный предел и он равен 1. Но мы можем посчитать его (и вообще любой предел) на компьютере: подставим х=0.000001. Получится 1. В данном случае чем ближе значение x к нулю, тем точнее будет вычислен предел. Можно даже вносить в отдельную функцию, например:

double f (double x) { return pow(2,x)/(x*log2(x)); }

И посчитаем предел при x стремящемся к бесконечности. Для этого надо подставить очень большое число, например x= 100000;

Полная программа:

#include <iostream>

#include <cmath>
using namespace std;

double f (double x) { return pow(2,x)/(x*log2(x)); }

int main() {

           cout << f(100000) << endl;

           system("pause");

}

Компилятор на devcpp (TDM-GCC 4.9.2) вывел inf, что значит "бесконечность", от слова infinity. Иногда ещё выводится nan (not a number)- неопределённость. В visual studio выводится 1.#INF что тоже значит бесконечность. Ты можешь потестировать с этим: попробуй сначала вывести 1/0, а потом 0/0.

Если x стремится к минус бесконечности, то нужно подставить что-то вроде f(-100000);

Если x стремится к 3, то мы можем подставлять как слева (2.9999), так и справа (3.0001). Например, для предела (x*x-9)/(x-3) при х=3 будет неопределённость 0/0. Имеем f(2.9999)= 5.9999, f(3.0001)= 6.0001. Нетрудно догадаться, что предел равен 6. Ручной способ тут тоже прост: нужно лишь разложить в числителе разность квадратов и сократить на знаменатель. Проблема в том, что не всегда попадаются такие лёгкие и понятные пределы- вот тогда-то на помощь может и прийти компьютер.

 

Дифференциальные уравнения

С помощью простого программирования можно решать даже дифференциальные уравнения первого порядка с заданным начальным условием. Общий вид решаемого уравнения: y' = f(x,y), где y(x0)= y0. Мы будем использовать самый простой метод- метод Эйлера.

Суть в том, что мы заменяем функцию на ломаную линию. И чем больше изломов, тем точнее результат. При этом куски ломаных будут параллельны соответствующим касательным в точках графика. Здесь мы находим именно координаты этих изломов, поэтому решением будет таблица чисел x, y.

На примере: dy/dx= x*x - 2*y, причём y(0)= 1. Решение ищем на отрезке [0;1].

Считаем f(x0,y0)= f(0,1)= -2

Вводим шаг dx= (B-A)/N, где [A;B]- заданный отрезок, N- число разбиений. Пусть dx= 0.1 для наглядности.

Тогда: dx*f(0,1)= -0.2

И далее:

x1= 0.1, y1= y0 + dx*f(x0,y0);

x2= 0.2, y2= y1 + dx*f(x1,y1);

Вносим всё в цикл:

_y= y0;

for (int i=1; i<=N; i++) {

           x= A + dx*i; y= _y + dx*f(A+dx*(i-1), _y);

           cout << "x= " << x << " --> y= " << y << endl;

           _y= y;

}

А теперь вся программа:

#include <iostream>

#include <cmath> // для функции x*x-2*y необязательно

using namespace std;

double f (double x, double y) { return x*x-2*y; }

int main() {

           double A, B, N; A=0, B=1, N=100; double dx= (B-A)/N;

           double x0,y0; x0=0, y0=1;

           double x,y, _y= y0;

           for (int i=1; i<=N; i++) {

                           x= A + dx*i; y= _y + dx*f(A+dx*(i-1), _y);

                           cout << "x= " << x << " --> y= " << y << " --> f(x,y)= " << f(x,y) << endl;

                           _y= y;

           }

           cout << endl; system("pause");

}

Метод можно усовершенствовать- чтобы касательные были не на левых краях, а посерединке каждого кусочка ломаной.

Тогда изменится следующее:

double x,y, _y= y0, _x= x0;

for (int i=1; i<=N; i++) {

           x= _x + dx; y= _y + dx*f(_x + dx/2, _y + (dx/2)*f(_x,_y));

           cout << "x= " << x << " --> y= " << y << " --> f(x,y)= " << f(x,y) << endl;

           _y= y; _x= x;

}

Ну и напоследок- более точный, но и более сложный метод: Рунге-Кутты.

double x,y, _y= y0, _x= x0; double k1,k2,k3,k4;

for (int i=1; i<=N; i++) {

           x= _x + dx;

           k1= f(_x,_y);

           k2= f(_x+dx/2, _y + dx*k1/2);

           k3= f(_x+dx/2, _y + dx*k2/2);

           k4= f(_x+dx, _y+dx*k3);

           y= _y + (dx/6)*(k1+2*k2+2*k3+k4);

           cout << "x= " << x << " --> y= " << y << " --> f(x,y)= " << f(x,y) << endl;

           _y= y; _x= x;

}

 

Простые числа

Ниже будут примеры функций, которые находят простые числа. Простое число- это число, не имеющее делителей (кроме себя самого и 1). Суть алгоритма решета Эратосфена в том, что мы берём какую-то область от 1 до некого числа, где ищем простые числа. Например от 1 до 1000000. Сначала зачёркиваем числа, кратные 2. Затем кратные 3, 4, 5 и т.д. Оставшиеся числа будут простыми.

// вывод простых чисел в промежутке [1;max]

void _SimpleIn (int max) {

           bool *z = new bool [max+1];

           for(int I=0;I<=max;I++) z[I]=true;

           for(int J=2;J<=max;J++) {

                           if(z[J]) cout << J << " ";

                           for(int I=J;I<=max;I+=J) z[I]=false;

           }

}

// количество простых чисел в промежутке [1;max]

int SimpleIn (int max) {

           bool *z = new bool [max+1];

           for(int I=0;I<=max;I++) z[I]=true;

           int n=0;

           for(int J=2;J<=max;J++) {

                           if(z[J]) n++;

                           for(int I=J;I<=max;I+=J) z[I]=false;

           }

           return n;

}

// х-ое простое число

int nSimple (int x) {

           int max = x*30; bool *z = new bool [max+1];

           for(int I=0;I<=max;I++) z[I]=true;

           int n=0;

           for(int J=2;J<=max;J++) {

                           if(z[J]) {

                                           n++; if(n==x) return J;

                           }

                           for(int I=J;I<=max;I+=J) z[I]=false;

           }

}

// вывести первые х простых чисел

void _nSimple (int x) {

           int max = x*30; bool *z = new bool [max+1];

           for(int I=0;I<=max;I++) z[I]=true;

           int n=0;

           for(int J=2;J<=max;J++) {

                           if(z[J]) {

                                           cout << J << " "; n++;

                           }

                           if(n==x) break;

                           for(int I=J;I<=max;I+=J) z[I]=false;

           }

}

 

Определитель матрицы

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

#include <iostream>

#include <cmath>

using namespace std;

// функция для генерации минора из матрицы

double* minor (int p, double A[], int n) {

           double *M= new double [(n-1)*(n-1)]; int s=0;

           for (int i=0; i<(n*n); i++) {

                           if((i%n==p%n)||(i/n==p/n)) continue;

                           M[s]=A[i]; s++;

           }

           return M;

}

// функция для высчитывания определителя

double det (double A[], int n) {

           if(n==1) return A[0];

           else {

                           double res=0;

                           for(int i=0;i<n;i++) {

                                           res+= A[i] * det(minor(i,A,n),n-1) * pow(-1,i);

                           }

                           return res;

           }

}

// главная функция

int main() {

           setlocale(0,"");

           while(true) {

                           system("CLS");

                           cout << "Размерность матрицы: "; int R; cin >> R;

                           double *a= new double [R*R];

                           // ввод

                           for (int i=0; i<R*R; i++ ) {

                                           cout << "Parca " << i+1 << ": "; cin >> a[i];

                           }

                           cout << endl;

                           // вывод

                           for (int i=0; i<R*R; i++) {

                                           cout << a[i] << " ";

                                           if(i%R==R-1) cout << endl;

                           }

                           cout << endl;

                           // результат

                           cout << "det("<<R<<")= " << det(a,R) << endl;

                           cout<<endl; system("pause");

           }

}

Теперь давайте подробно разберёмся что тут происходит. Рассмотрим на примере матрицы 3 порядка.

Предположим, у нас есть матрица с такими элементами:

(7) (-3) (5)

(2) (0) (8)

(-1) (6) (5)

В скобках- значения элементов матрицы. Как мы считаем определитель: берём элементы первой строки и умножаем на алгебраическое дополнение. Тут полезно бы обратиться к теории, как этот определитель вообще считается, и всё станет понятно что происходит в программе.

Т.е. определитель равен 7 * алгебр.доп ячейки 0 + (-3)* а.д.ячейки 1 + 5*а.д.ячейки 2. Мы всю матрицу загоним в одномерный массив и пронумеруем ячейки от 0 до 8. Алгебрическое дополнение- это минор, но с учётом знака. Знаки чередуются. В рекурсивной функции det ты можешь это видеть.

Теперь как сделать минор- он зависит от самой матрицы, её размерности и от позиции. Минор- это определитель новой матрицы, которая образуется путём зачёркивания элементов по той же вертикали и по той же горизонтали. Т.е. для ячейки 0 минором будет определитель матрицы:

(0) (8)

(6) (5)

Для ячейка 1 получится минорная матрица:

(2) (8)

(-1) (5)

И определитель такой матрицы считается по такому же принципу. Определителем матрицы 1 на 1 будет само значение элемента. Так мы получаем рекурсию.

Нужно лишь занести эти значения в минорную матрицу- надо пропустить ненужные значения. Для ячейки 1 мы пропускаем 0, 1, 2 по горизонтали и 1, 4, 7 по вертикали. По горизонтали- это целочисленное деление на 3, по вертикали- остаток при делении на 3. Теперь можешь взглянуть на функцию минора и сам уже в ней разобраться.

Теперь как решать системы линуров: объясню на простейшем примере 2х2:

7 *х1 + 5*х2 = -2

-3*х1 + 1*х2 = 0

Мы делаем матрицу системы:

(7) (5)

(-3) (1)

И далее матрицы путём замещения значениями справа. Для х1 будет матрица:

(-2) (5)

(0) (1)

И для х2 аналогично. Чтобы получить х1, нужно поделить определитель матрицы для х1 на определитель матрицы системы. Это называется методом Крамера. И его можно обобщить на системы любой размерности, где кол-во уравнений совпадает с кол-вом неизвестных.

Чтобы написать программу для решения системы линейных уравнений, нужно добавить новую функцию для создания замещённой матрицы и можно ещё сделать функцию для вычисления икса. Решение реализуется по методу Крамера.

Полная программа ниже:

#include <iostream>

#include <cmath>

using namespace std;

// матрица из минора на позиции p

double* minor (int p, double A[], int n) {

           double *M= new double [(n-1)*(n-1)];

           int s=0;

           for (int i=0; i<(n*n); i++) {

                           if((i%n==p%n)||(i/n==p/n)) continue;

                           M[s]=A[i]; s++;

           }

           return M;

}

// определитель

double det (double A[], int n) {

           if(n==1) return A[0];

           else {

                           double res=0;

                           for(int i=0;i<n;i++) {

                                           res+= A[i] * det(minor(i,A,n),n-1) * pow(-1,i);

                           }

                           return res;

           }

}

// матрица с замещением вектора-столбца на столбце p

double *zam (double A[], int n, int p, double B[]) {

           double *M= new double [n*n];

           for(int i=0; i<n*n; i++) {

                           if (i%n==p%n) M[i]= B[i/n];

                           else M[i]= A[i];

           }             

           return M;

}

double X (double A[], int n, int p, double B[]) { return det(zam(A,n,p,B),n) / det(A,n); }

int main() {

           setlocale(0,"");

           while(true) {

                           system("CLS");

                           cout << "Размерность системы: "; int R; cin >> R;

                           double *a= new double [R*R]; double *b= new double [R];

                           // ввод матрицы системы

                           for (int i=0; i<R*R; i++) { cout << "Parca " << i+1 << ": "; cin >> a[i]; }

                           cout << endl;

                           // ввод вектора справа от знака равно

                           for (int i=0; i<R; i++) { cout << "Droit " << i+1 << ": "; cin >> b[i]; }

                           // вывод

                           for (int i=0; i<R*R; i++) {

                                           cout << a[i] << " ";

                                           if(i%R==R-1) cout << " = " << b[i/R] << endl;

                           } cout << endl;

                           // результат

                           for (int i=0; i<R; i++) { cout << "x" << i+1 << "= " << X(a,R,i,b) << endl; }

                           cout<<endl; system("pause");

           }

}

 

Кубические уравнения

У нас есть уравнение вида A*(x^3) + В*(x^2) + C*x + D = 0. Нужно найти все действительные решения. Это кубическое уравнение (или кубур). Попробуем решить его аналитически в общем виде.

Сначала мы введём эти коэффициенты. Сделаем также функцию для вывода уравнение. Уравнение будем выводить в виде (A, B, C, D) * (x:3) = 0. По сути здесь скалярное произведение вектора коэффициентов на степенной базис.

void print (double a, double b, double c, double d, double x0) {

           cout << "(" << a << " ," << b << " ," << c << " ," << d << ") * (";

           if(x0==0) cout << "x:3)";

           if(x0>0) cout << "x-" << x0 << ":3)";

           if(x0<0) cout << "x+" << x0 << ":3)";

           cout << " = 0" << endl;

}

cout << "Решатель уравнения вида: A*(x^3) + В*(x^2) + C*x + D = 0" << endl;

cout << "А: "; cin >> A; cout << "B: "; cin >> B;

cout << "C: "; cin >> C; cout << "D: "; cin >> D;

cout << "Исходное уравнение: "; print(A,B,C,D,0);

Если A=0, то уравнение не кубическое- и мы ничего не решаем. Ставим это условие.

Далее делим всё уравнение на старший член: a= B/A; b= C/A; c= D/A; print(1,a,b,c,0);

Далее делаем замену х= у-а/3; И после раскрытия скобок второй член сократится. Получим:

cout << "Замена х= y";

if(a>0) cout << "-" << abs(a/3);

if(a<0) cout << "+" << abs(a/3);

cout << endl;

p= b - a*a/3; q= 2*a*a*a/27 - a*b/3 + c;

print (1,0,p,q,-a/3);

Далее можно сделать замену y= t - p/(3*t), получить квадратное уравнение, а из него уже формулу Кардано. Для упрощения вводится дискриминант: s= q*q/4 + p*p*p/27. От его знака будет зависеть результат решения.

Если дискриминант больше 0, то уравнение имеет 1 решение, которое высчитывается по формуле Кардано. Оставшиеся 2 комплексные корня можно высчитать через схему Горнера и квур, если это нужно.

Если дискриминант равен 0, тогда 2 решения, которые тоже можно формульно задать. Одно решение найдём через формулу Кардано, затем поделим схемой Горнера и получим квадратное уравнение с 1 корнем через выделение полного квадрата.

Если дискриминат меньше 0, тогда у нас 3 корня, причём что интересно- они будут действительные. Просто мнимые части сократятся.

m= -q/2;

if (s>0) { n= sqrt(s); cout << "x= " << cbrt(m-n) + cbrt(m+n) - a/3 << endl; }

На этом первая часть решения нашей задачи закончена. Теперь нужно разобрать ещё случаи когда дискриминант равен 0 или меньше 0.

Если равен 0, то первый корень найти не составит труда: x1= 2*cbrt(m) - a/3; Далее по схеме Горнера мы поделим на y^3 + p*y + q на y-y1 и получим:

y^2 + 2*cbrt(m) + cbrt(m)^2 = (y+cbrt(m))^2 = 0, отсюда y2= -cbrt(m) или x2= -cbrt(m) - a/3;

Т.к. компьютер считает с погрешностью, то иногда дискриминант может вместо нуля получиться очень маленьким числом. Поэтому пропишем так:

if ( (s==0) || (abs(s)<pow(10,-15)) )

Если дискриминант меньше 0, придётся обратиться в теорию комплексных чисел и тригонометрическую запись комплексного числа.

Число вида a+b*i, где i= sqrt(-1) можно представить в тригонометрическом виде, а именно- вынести модуль sqrt(a*a+b*b) за скобки.

M * (cos(F) + i*sin(F)), где F- тоже некое число, которое называют аргументом.

Тогда кубический корень из этого комплексного числа будет равен:

cbrt(M) * ( cos((F+2*pi*k)/3) + i*sin((F+2*pi*k)/3) ), где k принимает 3 значения: 0, 1, 2.

И далее подставив это в формулу Кардано, получим: 2*cbrt(M)*cos((F+2*pi*k)/3);

Здесь M= sqrt(m*m+n*n), n= sqrt(-s).

С аргументом формула чуть сложнее: если q<0, то F= atan(n/m). Если q>0, то надо ещё прибавить "пи", если q=0, то F= pi/2.

Таким образом, вся программа:

#include <iostream>

#include <cmath>

using namespace std;

const double pi= 3.1415926535;

void print (double a, double b, double c, double d, double x0) {

           cout << "(" << a << " ," << b << " ," << c << " ," << d << ") * (";

           if(x0==0) cout << "x:3)";

           if(x0>0) cout << "x-" << abs(x0) << ":3)";

           if(x0<0) cout << "x+" << abs(x0) << ":3)";

           cout << " = 0" << endl;

}

int main() {

           setlocale(0,""); double A,B,C,D,a,b,c,p,q,s,m,n,x,x1,x2,_x[3],M,F;

           while(true) {

                           system("CLS");

                           cout << "Решатель уравнений вида: A*(x^3) + Â*(x^2) + C*x + D = 0" << endl;

                           cout << "À: "; cin >> A;

                           cout << "B: "; cin >> B;

                           cout << "C: "; cin >> C;

                           cout << "D: "; cin >> D;

                           cout << "Исходное уравнение: "; print(A,B,C,D,0);

                           //

                           cout << "Деление на старший член: ";

                           a= B/A; b= C/A; c= D/A; print(1,a,b,c,0);

                           //

                           cout << "Замена х= y";

                           if(a>0) cout << "-" << abs(a/3);

                           if(a<0) cout << "+" << abs(a/3);

                           cout << endl;

                           p= b - a*a/3; q= 2*a*a*a/27 - a*b/3 + c;

                           print (1,0,p,q,-a/3);

                           //

                           cout << "Дискриминант: "; s= q*q/4 + p*p*p/27; cout << s << endl;

                           //

                           m= -q/2;

                           if (s>0) {

                                           n= sqrt(s); x= cbrt(m-n) + cbrt(m+n) - a/3;

                                           cout << "x= " << x << endl;

                           }

                           if ( (s==0) || (abs(s)<pow(10,-15)) ) {

                                           x1= 2*cbrt(m) - a/3; x2= -cbrt(m) - a/3;

                                           if (x1==x2) cout << "x= " << x1 << endl;

                                           else cout << "x1= " << x1 << endl << "x2= " << x2 << endl;                                                       }

                           if (s<0) {

                                      n= sqrt(-s); M= sqrt(m*m+abs(s));

                                           if (q<0) F= atan(n/m);

                                           if (q>0) F= atan(n/m)+ pi;

                                           if (q==0) F= pi/2;

                                           for (int i=0; i<=2; i++) {

                                                           _x[i]= 2*cbrt(M)*cos((F+2*pi*i)/3) - a/3;

                                                           cout << "x" << i+1 << "= " << _x[i] << endl;

                                           }

                           }

                           cout << endl; system("pause");

           }

}

 


Дата добавления: 2018-09-22; просмотров: 225; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!