Аркадна гра "гольф" з елементами трьохвимірної поверхні

Міністерство освіти інауки УкраїниФАКУЛЬТЕТ ІНФОРМАТИКИ
КАФЕДРАРеєстраційний №________
Дата ___________________КУРСОВА РОБОТА
Тема:
аркаднагра“гольф” з елементами трьох-вимірноїповерхніРекомендована до захисту
“____” __________ 2007р.
Робота захищена
“____” __________ 2007р.
з оцінкою
_____________________
Підписи членів комісії

ЗмістВступТеоріяПрактична частинаВисновкиЛітература

Вступ
Поставлена задача написати просту аркадну гру “гольф” зелементами трьох-вимірної поверхні. Для створення актуального програмногопродукту на цю тематику був обраний шлях написання універсальної програми – якаб могла запускатись з мінімальними потребами до пам”яті та інших ресурсів. Томув якості засобу розробки був обраний старий компілятор BORLAND C++ 3.0 і прийняте рішення невикористовувати графічні функції Windows.

ТеоріяЗасоби організації збереження іобробки даних для графічних програм/> Методи організації і збереження лінійних списків
Лінійний список — це кінцевапослідовність однотипних елементів (вузлів), можливо, з повтореннями. Кількістьелементів у послідовності називається довжиною списку, причому довжина впроцесі роботи програми може змінюватися.
Лінійний список F, що складається зелементів D1,D2,…,Dn, записують у виді послідовностізначень укладеної вкутові дужки F=, або представляють графічно.
Наприклад,F1=,F2=, F3=. Довжина списків F1, F2,F3 дорівнює відповідно 3,6,0.
При роботі зі списками на практицінайчастіше приходиться виконувати наступні операції:
— знайти елемент із заданою властивістю;
— визначити перший елемент у лінійномусписку;
— уставити додатковий елемент до абопісля зазначеного вузла;
— виключити визначений елемент зісписку;
— упорядкувати вузли лінійного списку увизначеному порядку.
У реальних мовах програмування немаєякої-небудь структури даних для представлення лінійного списку так, щоб усізазначені операції над ним виконувалися в однаковому ступені ефективно. Томупри роботі з лінійними списками важливим є представлення використовуваних упрограмі лінійних списків таким чином, щоб була забезпечена максимальнаефективність і за часом виконання програми, і по обсязі необхідної пам’яті.
Методи збереження лінійних списківрозділяються на методи послідовного і зв’язаного збереження. Розглянемонайпростіші варіанти цих методів для списку з цілими значеннями F=.
При послідовному збереженні елементилінійного списку розміщаються в масиві d фіксованих розмірів, наприклад, 100, ідовжина списку вказується в перемінної l, тобто в програмі необхідно матиоголошення виду
float d[100]; int l;
Розмір масиву 100 обмежує максимальнірозміри лінійного списку. Список F у масиві d формується так:
d[0]=7; d[1]=10; l=2;
При зв’язаному збереженні як елементизбереження використовуються структури, зв’язані по одній з компонентів уланцюжок, на початок якої (першу структуру) указує покажчик dl. Структураутворюючий елемент збереження, повинна крім відповідного елемента спискумістити і покажчик на сусідній елемент збереження.
Опис структури і покажчика в цьомувипадку може мати вид:
typedef struct snd /* структура елемента збереження */
{ float val; /* елемент списку */
struct snd *n; /* покажчик на елемент збереження */
} DL;
DL *p; /* покажчик поточного елемента */
DL *dl; /* покажчик на початок списку */
Для виділення пам’яті під елементизбереження необхідно користуватися функцією malloc(sizeof(DL)) абоcalloc(l,sizeof(DL)). Формування списку в зв’язаному збереженні можездійснюється операторами:
p=malloc(sizeof(DL));
p->val=10; p->n=NULL;
dl=malloc(sizeof(DL));
dl->val=7; dl->n=p;
В останньому елементі збереження (кінецьсписку) покажчик на сусідній елемент має значення NULL. Одержуваний списокзображений на мал.2. /> Операції зі списками при послідовному збереженні
При виборі методу збереження лінійногосписку варто враховувати, які операції будуть виконуватися і з якою частотою,час їхнього виконання й обсяг пам’яті, необхідний для збереження списку.
Нехай мається лінійний список з цілимизначеннями і для його збереження використовується масив d (з числом елементів100), а кількість елементів у списку вказується перемінної l. Реалізаціязазначених раніше операцій над списком представляється наступними фрагментамипрограм які використовують оголошення:
 float d[100];
int i,j,l;
1) печатка значення першого елемента (вузла)
if (іl) printf(“\n немає елемента”);
else printf(«d[%d]=%f »,і,d[і]);
2) видалення елемента, що випливає за i-тым вузлом
if (і>=l) printf(“\n немає наступного “);
l–;
for (j=і+1;j=l) printf(“\n немає сусіда”);
else printf(“\n %d %d”,d[і-1],d[і+1]);
4) додавання нового елемента new за i-тым вузлом
if (і==l || і>l) printf(“\n не можна додати”);
else
{ for (j=l; j>i+1; j–) d[j+1]=d[j];
d[i+1]=new; l++;
}
5) часткове упорядкування списку з елементами ДО1, ДО2,…, Кl у
список K1′,K2′,…,Ks,K1,Kt”,…,Kt”, s+t+1=l так, щоб K1’= K1.
 
{ int t=1;
float aux;
for (i=2; i
t++;
d[i]=aux;
}
}
Кількість дій Q, необхідних длявиконання приведених операцій над списком, визначаєтьсяспіввідношеннями: дляоперацій 1 і 2 — Q=1; для операцій 3,4 — Q=l; для операції 5 — Q=l*l.
Помітимо, що взагалі операцію 5 можнавиконати при кількості дій порядку l, а операції 3 і 4 для включення івиключення елементів наприкінці списку, що часто зустрічаються при роботі зістеками, — при кількості дій 1.
Більш складна організація операційпотрібно при розміщенні в масиві d декількох списків, або при розміщенні спискубез прив’язки його початку до першого елемента масиву. /> Операції зі списками при зв’язному збереженні
 
При простому зв’язаному збереженні коженелемент списку являє собою структуру nd, що складається з двох елементів: val — призначений для збереження елемента списку, n — для покажчика на структуру, щомістить наступний елемент списку. На перший елемент списку вказує покажчик dl.Для всіх операцій над списком використовується опис:
 typedef struct nd
{ float val;
struct nd * n; } ND;
int i,j;
ND * dl, * r, * p;
Для реалізації операцій можутьвикористовуватися наступні фрагменти програм:
1) печатка значення i-го елемента
 r=dl;j=1;
while(r!=NULL && j++n ;
if (r==NULL) printf(“\n немає вузла %d “,i);
else printf(“\n елемент %d дорівнює %f “,i,r->val);
2) печатка обох сусідів вузла(елемента),обумовленого покажчиком p (див. мал.4)
 if((r=p->n)==NULL) printf(“\n немає сусіда праворуч”);
else printf(“\n сусід праворуч %f”, r->val);
if(dl==p) printf(“\n немає сусіда ліворуч” );
else { r=dl;
while( r->n!=p ) r=r->n;
printf(“\n лівий сусід %f”, r->val);
}
3) видалення елемента, що випливає завузлом, на який указує р (див. мал.5)
 if ((r=p->n)==NULL) printf(“\n немає наступного”);
p->n=r->n; free(r->n);
4) вставка нового вузла зі значенням newза елементом, визначеним покажчиком р (див. мал.6)
r=malloc(1,sizeof(ND));
r->n=p->n; r->val=new; p->n=r;
5) часткове упорядкування списку впослідовність значень, s+t+1=l, так що K1’=K1; після упорядкування покажчик vуказує на елемент K1′ (див. мал.7)
 ND *v;
float k1;
k1=dl->val;
r=dl;
while( r->n!=NULL )
{ v=r->n;
if (v->valn=v->n;
v->n=dl;
dl=v;
}
else r=v;
}
Кількість дій, необхідних для виконаннязазначених операцій над списком у зв’язаному збереженні, оцінюєтьсяспіввідношеннями: для операцій 1 і 2 — Q=l; для операцій 3 і 4 — Q=1; дляоперації 5 — Q=l. /> Організація двузв‘язних списків
Зв’язане збереження лінійного спискуназивається списком із двома зв’язками або двузв‘язним списком, якщо коженелемент збереження має два компоненти покажчика (посилання на попередній інаступний елементи лінійного списку).
У програмі двузв‘язний список можнареалізувати за допомогою описів:
 typedef struct ndd
{ float val; /* значення елемента */
struct ndd * n; /* покажчик на наступний елемент */
struct ndd * m; /* покажчик на попередній елемент */
} NDD;
NDD * dl, * p, * r;
Графічна інтерпретація методу зв’язаногозбереження списку F= як списку з двома зв’язками приведена намал.8.
Вставка нового вузла зі значенням new заелементом, обумовленим покажчиком p, здійснюється за допомогою операторів:
 r=malloc(NDD);
r->val=new;
r->n=p->n;
(p->n)->m=r;
p->=r;
 Видалення елемента, що випливає завузлом, на який указує p
 p->n=r;
p->n=(p->n)->n;
( (p->n)->n )->m=p;
free(r);
 Зв’язане збереження лінійного спискуназивається циклічним списком, якщо його останній указує на перший елемент, апокажчик dl — на останній елемент списку.
Схема циклічного збереження спискуF= приведена на мал.9.
При рішенні конкретних задач можутьвиникати різні види зв’язаного збереження.
Нехай на вході задана послідовністьцілих чисел B1,B2,…,Bn з інтервалу від 1 до 9999, і нехай Fi (1
 При рішенні задачі в кожен момент часумаємо упорядкований список Fi і при введенні елемента Bi+1 уставляємо його впотрібне місце списку Fi, одержуючи упорядкований список Fi+1. Тут можливі триваріанти: у списку немає елементів; число вставляється в початок списку; числовставляється в кінець списку. Щоб уніфікувати всі можливі варіанти, початковийсписок організуємо як зв’язаний список із двох елементів .
 Розглянемо програму рішення поставленоїзадачі, у якій покажчики dl, r, p, v мають наступне значення: dl указує початоксписку; p, v — два сусідніх вузли; r фіксує вузол, що містить чергове введенезначення in.
 #include
#include
typedef struct str1
{ float val;
struct str1 *n; } ND;
main()
{ ND *arrange(void);
ND *p;
p=arrange();
while(p!=NULL)
{
printf(“\n %f “,p->val);
p=p->n;
}
}
ND *arrange() /* формування упорядкованого списку */
{ ND *dl, *r, *p, *v;
float in=1;
char *is;
dl=malloc(sizeof(ND));
dl->val=0; /* перший елемент */
dl->n=r=malloc(sizeof(ND));
r->val=10000; r->n=NULL; /* останній елемент */
while(1)
{
scanf(” %s”,is);
if(* is==’q’) break;
in=atof(is);
r=malloc(sizeof(ND));
r->val=in;
p=dl;
v=p->n;
while(v->valn;
}
r->n=v;
p->n=r;
}
return(dl);
}/> Стеки і черги
У залежності від методу доступу доелементів лінійного списку розрізняють різновиду лінійних списків називаністеком, чергою і двосторонньою чергою.
Стек — це кінцева послідовність деякиходнотипних елементів — скалярних перемінних, масивів, структур або об’єднань,серед яких можуть бути й однакові. Стік позначається у виді: S= і представляєдинамічну структуру даних; її кількість елементів заздалегідь не вказується й упроцесі роботи, як правило змінюється. Якщо в стеці елементів ні, то вінназивається порожнім і позначається S=.
Припустимими операціями над стеком є:
— перевірка стека на порожнечуS=,
— додавання нового елемента Sn+1 укінець стека — перетворення у ;
— вилучення останнього елемента зі стека- перетворення у ;
— доступ до його останнього елемента Sn,якщо стік не порожній.
Таким чином, операції додавання івидалення елемента, а також доступу до елемента виконуються тільки наприкінцісписку. Стік можна представити як стопку книг на столі, де додавання або узяттянової книги можливо тільки зверху.
Черга — це лінійний список, де елементивіддаляються з початку списку, а додаються наприкінці списку (як звичайна чергав магазині).
Двостороння черга — це лінійний список,у якого операції додавання і видалення елементів і доступу до елементів можливіяк спочатку так і наприкінці списку. Таку чергу можна представити якпослідовність книг на полку, так що доступ до них можливий з обох кінців.
Реалізація стеков і черг у програмі можебути виконана у виді послідовного або зв’язаного збереження. Розглянемоприклади організації стека цими способами.
Однієї з форм представлення виражень єпольський інверсний запис, що задає вираження так, що операції в ньомузаписуються в порядку виконання, а операнди знаходяться безпосередньо передоперацією.
Наприклад, вираз
(6+8)*5-6/2
у польському інверсному записі маєвигляд
6 8 + 5 * 6 2 / —
Особливість такого запису полягає втому, що значення вираження можна обчислити за один перегляд запису ліворучправоруч, використовуючи стек, що до цього повинний бути порожній. Кожне новечисло заноситься в стек, а операції виконуються над верхніми елементами стека,заміняючи ці елементи результатом операції. Для приведеного вираження динаміказміни стека буде мати вигляд
S = ; ; ; ; ; ;
; ; ; .
Нижче приведена функція eval, щообчислює значення вираження, заданого в масиві m у формі польського інверсногозапису, причому m[i]>0 означає ненегативне число, а значення m[i]
float eval (float *m, int l) { int p,n,i; float stack[50],c;
for(i=0; i
Розглянемо іншу задачу. Нехай потрібноввести деяку послідовність символів, що закінчується крапкою, і надрукувати неїв зворотному порядку (тобто якщо на вході буде «ABcEr-1.» те навиході повинне бути «1-rEcBA»). Представлена нижче програма спочаткууводить усі символи послідовності, записуючи них у стек, а потім уміст стекадрукується в зворотному порядку. Це основна особливість стека — чим пізнішеелемент занесений у стек, тим раніш він буде витягнутий зі стека. Реалізаціястека виконана в зв’язаному збереженні за допомогою покажчиків p і q на тип,іменований ім’ям STACK.
 #include
typedef struct st /* оголошення типу STACK */
{ char ch;
struct st *ps; } STACK;
main()
{ STACK *p,*q;
char a;
p=NULL;
do /* заповнення стека */
{ a=getch();
q=malloc(sizeof(STR1));
q->ps=p; p=q;
q->ch=a;
} while(a!=’.’);
do /* печатка стека */
{ p=q->ps;free(q);q=p;
printf(“%c”,p->ch);
} while(p->ps!=NULL);
}/> Стиснуте й індексне збереження лінійних списків
При збереженні великих обсягівінформації у формі лінійних списків небажано зберігати елементи з однаковимзначенням, тому використовують різні методи стиску списків.
Стиснуте збереження. Нехай у списку B=кілька елементів мають однакове значення V, а список В’= виходить з B заміноюкожного елемента Ki на пари Ki’=(і,Ki). Нехай далі B”= — підсписок В’, щовиходить викреслюванням усіх пар Ki=(і,V). Стиснутим збереженням У є методзбереження В”, у якому елементи зі значенням V. Розрізняють послідовнестиснуте збереження і зв’язане стиснуте збереження. Наприклад, для списку B=,що містить кілька вузлів зі значенням Х, послідовного стиснутого і зв’язанестиснуте збереження, з умовчуванням елементів зі значенням Х, представлені намал.22,23.
Достоїнство стиснутого збереження спискупри великому числі елементів зі значенням V полягає в можливості зменшенняобсягу пам’яті для його збереження.
Пошук i-го елемента в зв’язаномустиснутому збереженні здійснюється методом повного перегляду, при послідовномузбереженні — методом бінарного пошуку.
Переваги і недоліки послідовногостиснутого і зв’язаного стиснутого аналогічні перевагам і недолікампослідовного і зв’язаного збереження.
Розглянемо наступну задачу. На входізадані дві послідовності цілих чисел M=, N=, причому 92% елементівпослідовності М дорівнюють нулеві. Скласти програму для обчислення сумидобутків Mi * Ni, і=1,2,…,10000.
Припустимо, що список М зберігаєтьсяпослідовно стисло в масиві структур m з оголошенням:
 
struct
{ int nm;
float val; } m[10000];
Для визначення кінця списку додамо щеодин елемент із порядковим номером m[j].nm=10001, що називається стопером(stopper) і розташовується за останнім елементом стиснутого збереження списку вмасиві m.
 Програма для перебування шуканої сумимає вигляд:
 # include
 main()
{ int і,j=0;
float inp,sum=0;
struct /* оголошення масиву */
{ int nm; /* структур */
float val; } m[10000];
 
for(i=0;i
Індексне збереження використовується длязменшення часу пошуку потрібного елемента в списку і полягає в наступному.Вихідний список B = розбивається на трохи підсписків У1, У2, …, Вм таким чином,що кожен елемент списку В попадає тільки в один з підсписків, і додаткововикористовується індексний список з М елементами, що вказують на початоксписків У1, У2, …, Ум.
Вважається, що список зберігаєтьсяіндексно за допомогою підсписків B1,B2, …,Bm і індексного списку X =, деADGj — адреса початку підсписка Bj, j=1,M.
При індексному збереженні елемент Допідсписка Bj має індекс j. Для одержання індексного збереження вихідний списокУ часто перетвориться в список В’ шляхом включення в кожен вузол ще і йогопорядкового номера у вихідному списку В, а в j-ий елемент індексного списку Х,крім ADGj, може включатися деяка додаткова інформація про підсписок Bj.Розбивка списку В на підсписки здійснюється так, щоб всі елементи В, щоволодіють визначеною властивістю Рj, попадали в один підсписок Bj.
Достоїнством індексного збереження є те,що для перебування елемента К с заданою властивістю Pj досить переглянутитільки елементи підсписка Bj; його початок знаходиться по індексному списку Х,тому що для кожного ДО, що належить Bi, при і не рівному j властивість Pj невиконується.
У розбивці В часто використовуєтьсяіндексна функція G(K), що обчислює по елементі До його індекс j, тобто G(K)=j.Функція G звичайно залежить від позиції ДО, що позначається поз.K, у підспискуВ або від значення визначеної частини компоненти ДО — її ключа.
Розглянемо список B= з елементами
ДО1=(17,Y), K2=(23,H), K3=(60,I), K4=(90,S), K5=(66,T),
K6=(77,T), K7=(50,U), K8=(88,W), K9=(30,S).
Якщо для розбивки цього списку напідсписки як індексну функцію взяти Ga(K)=1+(поз.K-1)/3, то список розділитьсяна три підсписка:
B1a=,
B2a=,
B3a=.
Додаючи усюди ще і початкову позиціюелемента в списку, одержуємо:
B1a’=,
B2a’=,
B3а’=.
Якщо як індексну функцію вибрати іншуфункцію Gb(K)=1+(поз.K-1)%3, то одержимо списки:
B1b”=,
B2b”=,
B3b”=.
Тепер для перебування вузла K6 доситьпереглянути тільки одну з трьох послідовностей (списків). При використанніфункції Ga(K) це список B2а’, а при функції Gb(K) список B3b”.
Для індексної функції Gc(K)=1+K1/100, деK1 — перший компонент елемента ДО, знаходимо:
B1=,
B2=,
B3=,
B4=.
Щоб знайти тут вузол К с першимкомпонентом-ключем ДО1=77, досить переглянути список B2.
При реалізації індексного збереженнязастосовується методика А для збереження індексного списку Х (функція Ga(X) ) іметодика C для збереження підсписків B1,B2,…,Bm (функція Gc(Bi)), тобтовикористовується, так називане, A-C індексне збереження.
У практиці часто використовуєтьсяпослідовно-зв’язане індексне збереження. Тому що звичайно довжина спискуіндексів відома, те його зручно зберігати послідовно, забезпечуючи прямійдоступ до будь-якого елемента списку індексів. Підсписки B1,B2,…,Bmзберігаються пов’язано, що спрощує вставку і видалення вузлів(елементів).Зокрема, подібний метод збереження використовується в ЄС ЕОМ для організації,так званих, індексно-послідовних наборів даних, у яких доступ до окремихзаписів можливий як послідовно, так і за допомогою ключа.
Послідовно-зв‘язане індексне збереженнядля приведеного приклада зображене на мал.24, де X=.
 Розглянемо ще одну задачу. На входізадана послідовність цілих позитивних чисел, що закінчується нулем. Скластипроцедуру для введення цієї послідовності й організації її індексногозбереження таким чином, щоб числа, що збігаються в двох останніх цифрах,містилися в один підсписок.
Виберемо як індексну функціюG(K)=K%100+1, а як індексний список Х — масив з 100 елементів. Наступна функціявирішує поставлену задачу:
 #include
 #include
 typedef struct nd
{ float val;
struct nd *n; } ND;
int index (ND *x[100])
{ ND *p;
int i,j=0;
float inp;
for (i=0; ival=inp;
p->n=x[i];
x[i]=p;
scanf(“%d”,&inp);
}
return j;
}
Значенням функції, що повертається,index буде число оброблених елементів списку.
Для індексного списку також можевикористовуватися індексне збереження. Нехай, наприклад, мається список B= зелементами
K1=(338,Z), K2=(145,A), K3=(136,H), K4=(214,I), K5 =(146,C),
K6=(334,Y), K7=(333,P), K8=(127,G), K9=(310,O), K10=(322,X).
Потрібно розділити його на сімохпідсписків, тобто X= таким чином, щоб у кожен список B1,B2,…,B7 попадалиелементи, що збігаються в першому компоненті першими двома цифрами. Список Х, усвою чергу, будемо індексувати списком індексів Y=, щоб у кожен список Y1,Y2,Y3попадали елементи з X, у яких у першому компоненті збігаються перші цифри. Якщосписки B1,B2,…,B7 зберігати пов’язано, а списки індексів X,Y індексно, те такийспосіб збереження списку B називається зв’язаним індексним збереженням.Графічне зображення цього збереження приведене на мал.25.

Практична частина
 
Лістінг програми
 
Основний модуль golf.c
#include
#include
#include
#include
#include
#include
#define GRIDSIZE 80 /* Must be bigger than VIEWSIZE */
#define VIEWSIZE 61 /* MUST be odd */
#define DIFF (GRIDSIZE-VIEWSIZE)
#define DEF_DIST -1100
#define DEF_PITCH 122
#define DEF_HEIGHT 120
#define DEF_ROLL 315
#define sine(X) ((long)(sn_tbl[X]))
#define cosine(X) ((long)(sn_tbl[((X)+90) % 360]))
#define C_Plot(X,Y,C) pokeb(0xa000, (X) + 320U*(Y), C)
#define SHIFT 14
#define MASK (GRIDSIZE*GRIDSIZE)
/*
#define GetGrid(X,Y) ((unsigned)grid[((X) + GRIDSIZE*(Y)+idx) % MASK])
#define PutGrid(X,Y,C) grid[((X) + GRIDSIZE*(Y) +idx) % MASK]= (unsigned char)(C)
*/
#define CalcAddress(X,Y) (&grid[((X) + GRIDSIZE*(Y) +idx) % MASK])
extern void far *view_screen;
extern void far *screen;
extern int sn_tbl[360];
extern unsigned char grid[GRIDSIZE*GRIDSIZE];
extern unsigned rand_seed;
unsigned idx = 0;
extern long pitch_sine;
extern long pitch_cosine;
extern long roll_sine;
extern long roll_cosine;
int num_points = GRIDSIZE*GRIDSIZE;
#define START (DIFF/2)
int gx = START,gy = START;
unsigned char *gp;
int cz = DEF_DIST;
int cy = DEF_HEIGHT;
int roll = DEF_ROLL;
int cpitch = DEF_PITCH;
extern void SetMyMode(void);
extern void ClearMyScreen(void);
extern void Project(void);
extern void SwapScreens(void);
extern void DoPlasma(int,int,int,int);
extern int GetRand(void);
extern unsigned GetGrid(void);
extern void PutGrid(void);
#define _GetGrid(X,Y) (_AX = (X), _BX = (Y), GetGrid())
#define _PutGrid(X,Y,C) { _CX = (C); _AX = (X); _BX = (Y);PutGrid(); }
void SetMode(void)
{
         struct REGPACK regs;
         regs.r_ax = 0x13;
         intr(0x10, &regs);
}
void SetTextMode(void)
{
         struct REGPACK regs;
         regs.r_ax = 0x3;
         intr(0x10, &regs);
}
void SetPalette(void)
{
         register int i;
         register int j;
#define DEPTH(X) max((((X)*(3-j))/3), 3)
         for (j = 0; j
                   for (i = 0; i
                   {
                            if (i+j > 0)
                            {
                                      disable();
                                      outportb(0x3c8, (i>> 2)+64*j);
                                      outportb(0x3c9, 0);
                                      outportb(0x3c9, 0);
                                      outportb(0x3c9, DEPTH(2*i/3));
                                      enable();
                            }
                            disable();
                            outportb(0x3c8, (i >>2)+64*j+16);
                            outportb(0x3c9, DEPTH(i/2+10));
                            outportb(0x3c9, DEPTH(i/4+10));
                            outportb(0x3c9, DEPTH(i/6+10));
                            enable();
                            disable();
                            outportb(0x3c8, (i >>2)+64*j+32);
                            outportb(0x3c9,DEPTH(max(63/2+10-i,0)));
                            outportb(0x3c9,DEPTH(min(64/4+10+3*i/4,63)));
                            outportb(0x3c9,DEPTH(max(63/6+10-i,0)));
                            enable();
                            disable();
                            outportb(0x3c8, (i >>2)+64*j+48);
                            outportb(0x3c9, DEPTH(i));
                            outportb(0x3c9, DEPTH(63));
                            outportb(0x3c9, DEPTH(i));
                            enable();
                   }
}
/*
int RandPixel(int x,int y,int x1,int y1,int x2,int y2)
{
         int col;
         col = (GetRand()%200 — 100) * (abs(x-x1)+abs(y-y1))/ (GRIDSIZE/6)
                            +((_GetGrid(x1,y1)+_GetGrid(x2,y2))>> 1);
         if (col
         if (col > 255) col = 255;
         _PutGrid(x,y,col);
         return col;
}
*/
/*
void DoPlasma(int x1, int y1, int x2, int y2)
{
         int x,y,s,p;
         if (x2-x1
                   return;
         x = (x1+x2) >> 1;
         y = (y1+y2) >> 1;
         if ((p = _GetGrid(x, y1)) == 0)
                   p = RandPixel(x,y1,x1,y1,x2,y1);
         s = p;
         if ((p = _GetGrid(x2,y)) == 0)
                   p = RandPixel(x2,y,x2,y1,x2,y2);
         s += p;
         if ((p = _GetGrid(x,y2)) == 0)
                   p = RandPixel(x,y2,x1,y2,x2,y2);
         s += p;
         if ((p = _GetGrid(x1,y)) == 0)
                   p = RandPixel(x1,y,x1,y1,x1,y2);
         s += p;
         if (_GetGrid(x,y) == 0)
                   _PutGrid(x,y,s >> 2);
         DoPlasma(x1,y1,x,y);
         DoPlasma(x,y1,x2,y);
         DoPlasma(x1,y,x,y2);
         DoPlasma(x,y,x2,y2);
}
*/
void BlankGrid(int x1,int y1,int x2,int y2)
{
         register int x,y;
         for (y = y1; y
                   for (x = x1; x
                            _PutGrid(x,y,0);
}
void NewLand(int x1,int y1,int x2,int y2)
{
         unsigned av = 0;
         int val;
         int num = 0;
         if ((val = _GetGrid(x1,y1)) > 0)
         {
                   av += val;
                   num++;
         }
         if ((val = _GetGrid(x2,y1)) > 0)
         {
                   av += val;
                   num++;
         }
         if ((val = _GetGrid(x2,y2)) > 0)
         {
                   av += val;
                   num++;
         }
         if ((val = _GetGrid(x1,y2)) > 0)
         {
                   av += val;
                   num++;
         }
         if (!av || GetRand() % 32 == 0)
                   av = GetRand() % 256;
         else
                   av /= num;
         if (_GetGrid(x1,y1) == 0)
                   _PutGrid(x1,y1, av + (GetRand() % 80-40));
         if (_GetGrid(x2,y1) == 0)
                   _PutGrid(x2,y1, av + (GetRand() % 80-40));
         if (_GetGrid(x2,y2) == 0)
                   _PutGrid(x2,y2, av + (GetRand() % 80-40));
         if (_GetGrid(x1,y2) == 0)
                   _PutGrid(x1,y2, av + (GetRand() % 80-40));
         DoPlasma(x1,y1,x2,y2);
}
void Test(void)
{
         register int p;
         register int x;
         int y;
         for (y = 0,p = idx; y
                   for (x = 0; x
                            C_Plot(x,y,max(grid[p],63)>> 2);
         for (x = 0; x
         {
                   C_Plot(gx+x, gy, 0);
                   C_Plot(gx+x, gy+VIEWSIZE, 0);
                   C_Plot(gx, gy+x, 0);
                   C_Plot(gx+VIEWSIZE, gy+x, 0);
         }
/*
         for (y = 0, p = gp; y
                   for (x = 0; x
                            C_Plot(gx+x,gy+y,*p >> 3);
*/
}
void ClearScr(void)
{
         register unsigned i;
         for (i = 0; i
                   pokeb(0xa000,i,0);
}
void check_gx(void)
{
         if (gx
         {
                   idx = (idx-DIFF/2 + MASK) % MASK;
                   gx = START-1;
                   BlankGrid(0,0, DIFF/2-1, GRIDSIZE-1);
                   NewLand(0,0,DIFF/2,GRIDSIZE/4);
                   NewLand(0,GRIDSIZE/4,DIFF/2,2*GRIDSIZE/4);
                   NewLand(0,2*GRIDSIZE/4,DIFF/2,3*GRIDSIZE/4);
                   NewLand(0,3*GRIDSIZE/4,DIFF/2,GRIDSIZE-1);
         }
         else if (gx >= DIFF)
         {
                   idx = (idx+DIFF/2) % MASK;
                   gx = START+1;
                   BlankGrid(GRIDSIZE-DIFF/2,0, GRIDSIZE-1,GRIDSIZE-1);
                   NewLand(GRIDSIZE-DIFF/2-1,0,GRIDSIZE-1,GRIDSIZE/4);
                   NewLand(GRIDSIZE-DIFF/2-1,GRIDSIZE/4,GRIDSIZE-1,
                                                                                                                 2*GRIDSIZE/4);
                   NewLand(GRIDSIZE-DIFF/2-1,2*GRIDSIZE/4,GRIDSIZE-1,
                                                                                                                 3*GRIDSIZE/4);
                   NewLand(GRIDSIZE-DIFF/2-1,3*GRIDSIZE/4,GRIDSIZE-1,
                                                                                                                 GRIDSIZE-1);
         }
}
void check_gy(void)
{
         if (gy
         {
                   idx = (idx-DIFF/2*GRIDSIZE + MASK) % MASK;
                   gy = START-1;
                   BlankGrid(0,0, GRIDSIZE-1, DIFF/2-1);
                   NewLand(0,0,GRIDSIZE/4,DIFF/2);
                   NewLand(GRIDSIZE/4,0,2*GRIDSIZE/4,DIFF/2);
                   NewLand(2*GRIDSIZE/4,0,3*GRIDSIZE/4,DIFF/2);
                   NewLand(3*GRIDSIZE/4,0,GRIDSIZE-1,DIFF/2);
         }
         else if (gy >= DIFF)
         {
                   idx = (idx+DIFF/2*GRIDSIZE) % MASK;
                   gy = START+1;
                   BlankGrid(0,GRIDSIZE-DIFF/2,GRIDSIZE-1,GRIDSIZE-1);
                   NewLand(0,GRIDSIZE-DIFF/2-1,GRIDSIZE/4,GRIDSIZE-1);
                   NewLand(GRIDSIZE/4,GRIDSIZE-DIFF/2-1,
                                                                           2*GRIDSIZE/4,GRIDSIZE-1);
                   NewLand(2*GRIDSIZE/4,GRIDSIZE-DIFF/2-1,
                                                                           3*GRIDSIZE/4,GRIDSIZE-1);
                   NewLand(3*GRIDSIZE/4,GRIDSIZE-DIFF/2-1,
                                                                           GRIDSIZE-1,GRIDSIZE-1);
         }
}
void main(void)
{
         int rollspeed = 0;
         int xspeed = 0, yspeed = 0;
         int i;
         rand_seed = (unsigned) time(NULL);
/*       rand_seed = 2; */
         for (i = 0; i
                   sn_tbl[i]=(int)(sin((double)i /180.0*3.14159265) * (double)(1
         NewLand(0,0,GRIDSIZE-1,GRIDSIZE-1);
         SetMode();
         SetPalette();
//       goto skip;
         for (;;)
         {
                   Test();
                   switch(getch())
                   {
                            case 27:
                                      SetTextMode();
                                      exit(0);
                            case ‘e’:
                                      gx–;
                                      check_gx();
                                      break;
                            case ‘r’:
                                      gx++;
                                      check_gx();
                                      break;
                            case ‘w’:
                                      gy–;
                                      check_gy();
                                      break;
                            case ‘s’:
                                      gy++;
                                      check_gy();
                                      break;
                            case ‘ ‘:
                                      goto skip;
                   }
                   gp = CalcAddress(gx,gy);
                   while (kbhit())
                            getch();
         }
skip:
         ;
         SetMyMode();
         SetPalette();
         gp = CalcAddress(gx,gy);
//       yspeed = -1;
//       rollspeed = (rollspeed+358) % 360;
         for (;;)
         {
                   if (kbhit())
                   {
                            switch(getch())
                            {
                            case 27:
                                      SetTextMode();
                                      exit(0);
                            case ‘q’:
                                      cz += 50;
                                      break;
                            case ‘a’:
                                      cz -= 50;
                                      break;
                            case ‘u’:
                                      cy -= 50;
                                      break;
                            case ‘j’:
                                      cy += 50;
                                      break;
                            case ‘Q’:
                                      cpitch = (cpitch+1) %360;
                                      break;
                            case ‘A’:
                                      cpitch = (cpitch+359) %360;
                                      break;
                            case ‘E’:
                                      if (xspeed > -1)xspeed–;
                                      break;
                            case ‘R’:
                                      if (xspeed
                                      break;
                            case ‘e’:
                                      gx–;
                                      break;
                            case ‘r’:
                                      gx++;
                                      break;
                            case ‘W’:
                                      if (yspeed > -1)yspeed–;
                                      break;
                            case ‘S’:
                                      if (yspeed
                                      break;
                            case ‘w’:
                                      gy–;
                                      break;
                            case ‘s’:
                                      gy++;
                                      break;
                            case ‘i’:
                                      roll = (roll+1) % 360;
                                      break;
                            case ‘o’:
                                      roll = (roll+359) %360;
                                     break;
                            case ‘I’:
                                      rollspeed =(rollspeed+1) % 360;
                                      break;
                            case ‘O’:
                                      rollspeed =(rollspeed+359) % 360;
                                      break;
                            case ‘ ‘:
                                      rollspeed = 0;
                                      xspeed = yspeed = 0;
                                      cz = DEF_DIST;
                                      cy = DEF_HEIGHT;
                                      roll = DEF_ROLL;
                                      cpitch = DEF_PITCH;
                                      break;
                            }
                            while (kbhit())
                                      getch();
                   }
                   gy += yspeed;
                   gx += xspeed;
                   check_gx();
                   check_gy();
                   gp = CalcAddress(gx,gy);
                   roll = (roll+rollspeed) % 360;
                   roll_sine = sine(roll);
                   roll_cosine = cosine(roll);
                   pitch_sine = sine(cpitch);
                   pitch_cosine = cosine(cpitch);
                   ClearMyScreen();
                   Project();
                   SwapScreens();
         }
}

Робота з програмою
Далі наведено три скріншоти, які описують процес удару по м”ячику.
/>
/>

Висновки
Була розроблена комп’ютерна гра “Гольф 3D” з елементами трьохвимірної графікина основі функцій прямого доступу до відеопам’яті в системі MS DOS. При розробці програмивикористовувався пакет BORLAND C++ 3.0 табібліотека BGI.

Література.
[1] Касаткин А.И., Вальвачев А.Н.Профессиональное прогрпммирование на языке Си. Мн., 1992. 240 С.
[2] Нейбауэр А. Моя перваяпрограмма на С/С++. П., 1995. 368 С.
[3] Бруно Бабэ. Просто и ясно оBorland C++. М., 1996. 400 С.
[4] Шамас Н.К. Основы С++ иобьектно-ориентированного программирования. К., 1996. 448 С.
[5] Справочник по классам BorlandC++ 4.0. К., 1994. 256 С.
[6] ObjectWindows для C++. К., 1993., 208 С.
[7] Том Сван. Программированиедля Windows в Borland C++. М., 480 С.
[8] Н. Барканати.Программирование игр для Windows на Borland C++. М., 1994. 512 С.