實驗材料
實驗任務
(1) 進一步掌握陣列的定義與使用;進一步掌握函式的定義和函式呼叫方法;
(2) 學習和掌握結構體的定義和使用方法,
(3) 進一步掌握 C 語言的編程方法;學習影片程式的基本設計思想和方法,
(4) 編譯并運行你的程式,除錯正確后將原程式工程檔案目錄壓縮后提交到 Blackboard,其中壓縮檔案名稱的前兩個字母為你的姓與名的拼音的首字母,
(5) 提交正式的實驗報告
設計思想
在計算機中如何生成影片?
所謂影片,實際上是按照一定的時間間隔顯示的影像,在這些影像的每一幀之間都有一些不同,在計算機中,每一幀影像是以記憶體中的一個二維陣列的形式存盤的,陣列中的每一個元素的值代表影像中的一個像素值,由于在 VC6 的集成開發環境的控制臺視窗中可以顯示 25 行 80 列的字符,因此該視窗一幀影像的大小最大為 2580 個像素,在本次試驗中,影片中影像的大小規定為 24 行 79 列,因此,可以定義一個 2480 的二維陣列,該陣列的最后 1 列存盤字串結束標志”\0”,以便可以使用字串函式的形式顯示二維陣列中的每一行字符,
要想使一個影像序列在連續顯示時看起來像影片,每一幀影像在螢屏上的停留時間要基本與人眼的視覺暫留時間相適應,因此在顯示每一幀影像以后,還要繼續適當延時,然后再進行下一幀影像的顯示,因此你的模擬程式中需要有一個延時函式,以控制每一幀影像的顯示時間,
要想讓影片連續不斷地進行,還需要設計一個不限定回圈次數的回圈,
如何在一個二維陣列中繪制一幅影像?
首先,需要對陣列元素進行初始化,初始化的實質是將背景影像重新寫入到二維陣列中,
然后,將要繪制的圖形以像素點的形式寫入對應的二維陣列元素中,二維陣列中的每個元素對應于一個像素點,
如何顯示二維陣列中的影像?
影像顯示的實質,就是將二維字符陣列中存盤的每個字符輸出到螢屏上,在本次實驗的程式中,實際就是輸出到控制臺視窗中,由于影像以字符陣列的形式存盤在二維陣列中,因此,可以用一個字串輸出的回圈實作,
在本次實驗的程式中,為了加快字符陣列的顯示程序,在二維陣列的每一行的最后一個元素中,可以寫入字串結束標志:”\n”,然后用字串輸出函式顯示二維陣列的每一行字符,
如何讓一個彈球運動?
- 定義描述一個彈球的結構體 BALL,一種可能的形式如下:
struct BALL{
char body[2]; //兩個不同的字符,分別代表兩個不同顏色的球
int sel; //當前球的顏色,0表示第一種顏色,1表示第二種顏色
int wX; //在二維陣列中,球在x方向的實際顯示位置(整數)
int wY; //在二維陣列中,球在y方向的實際顯示位置(整數)
double X; //球在x方向的精確位置(實數)
double Y; //球在y方向的精確位置(實數)
double dX; //球在x方向的速度(實數)
double dY; //球在y方向的速度(實數)
};
其中,結構體中的每一個成員的說明如上所示,
- 對彈球 BALL 結構體的每一個元素進行初始化
為了使模擬程式看起來更自然,我們可以用亂數對其進行初始化:
隨機生成0、1最為當前彈球的顏色值 sel;
隨機生成 1-22 之間的亂數,最為當前彈球的行坐標位置 wX,X;
隨機生成 1-77 之間的亂數,最為當前彈球的列坐標位置 wY,Y;
每個彈球的速度大小都是1,但速度的方向θ是一個0-359之間的亂數,表示角度,這樣它的
X、Y方向的速度分量分別為:
dX = cos(πθ/180);
dY = sin(πθ/180);
- 彈球根據自己的速度,移動一步
彈球運動的實質是改變彈球當前的位置,由于彈球在X、Y方向的速度分量dX、dY都為 < 1 的值,因此彈球一步運動后的精確位置是兩個實數分量:
X = X + dX;
Y = Y + dY;
但是,彈球在二維陣列影像中的顯示位置是二維陣列的行、列兩個下標,只能是整數值,因此, 需要對彈球當前的精確實數位置進行四舍五入取整,得到實際顯示的陣列行、列位置wX、wY,可以用下面的方法實作四舍五入取整:
wX = (int)( X + 0.5);
wY = (int)( Y + 0.5);
如何檢測彈球撞到了墻壁?如何彈回來?
假設,彈球當前的位置是(X,Y),彈球運動一步以后的位置是:
X = X + dX;
Y = Y + dY;
假設表示影像的二維字符陣列有24行,則若 X<0,則說明彈球撞到了上面的墻壁;X>23,則說明彈球撞到了下面的墻壁,
檢測到彈球撞墻壁后,彈球應該被彈回,也就是說彈球的速度分量需要改變方向,并且被彈回到上次的位置,具體可用下面數學模型實作:
dX = - dX; X = X + dX;
對彈球在左、右方向(即 Y方向)的撞墻檢測,以及被彈回的原理同上,
如何檢測兩個彈球相撞?
首先,根據兩個彈球的當前位置(X1,Y1)、(X2,Y2),計算它們之間的距離:
dist = sqrt((X1-X2)^2 + (Y1 – Y2)^2);
然后,若 dist < 1,則可判定兩個彈球相撞,
如何讓彈球的速度方向改變 90 度?
若彈球當前的速度矢量為(dX1,dY1),則方向改變90度后的速度矢量(dX2,dY2)為:
dX2 = dY1
dY2 = dX1
實驗源代碼
由于是C語言程式設計課程,老師不允許使用c++的封裝方法,也不允許呼叫圖形庫,因此代碼寫得艱難,其中一些條條框框我認為不妥,例如碰撞后90°拐彎,明顯與常識不符,
有基于此,我并沒有嚴格按照實驗要求完成,而是做了部分調整,用每秒鐘40幀的重繪頻率,嘗試完成了此實驗,
實驗中設計了球與球的完全彈性碰撞、實作了球與邊界的碰撞,并且統計了與下邊界的次數(實驗中有要求),源代碼和注釋如下:
//此間彼方流浪,分不清決絕和迷惘
//2020.6.19
//曹弈軒 2019282129
#include<stdio.h>
#include<math.h>
#include<Windows.h>
#include<time.h>
#include<stdlib.h>
//界面的長和寬
#define HIGN 10
#define WIDTH 40
//暫定球與球之間的距離≤1時視為碰撞
#define REACH 1
#define PI 3.14159//圓周率
#define NUM 10 //球的最大數量
int COUNT = 0;
struct BALL {
char body;//單個字符,表示球在dos控制臺應有的形態
int sel; //當前球的顏色,0表示第一種顏色,1表示第二種顏色
int wX; //在二維陣列中,球在x方向的實際顯示位置(整數)
int wY; //在二維陣列中,球在y方向的實際顯示位置(整數)
double X; //球在x方向的精確位置(實數)
double Y; //球在y方向的精確位置(實數)
double dX; //球在x方向的速度(實數)
double dY; //球在y方向的速度(實數)
};
void Manage(struct BALL*, int);//每一個周期進行的一次處理
void print_pos(struct BALL*, int);//一組球的輸出函式
void swap(double*, double*);//double型別的交換函式
void color(const unsigned short);//設定顏色的函式
int main() {
srand(time(NULL));
printf("請輸入球的個數:");
int num;//球的個數
scanf("%d", &num);
if (num > NUM)num = NUM;
struct BALL* ball = (struct BALL*)malloc(sizeof(struct BALL) * num);
for (int i = 0; i < num; i++) {
(ball + i)->sel = rand() % 15 + 1; //顏色
(ball + i)->X = rand() % WIDTH + 1; //x精確坐標
(ball + i)->Y = rand() % HIGN + 1; //y精確坐標
//此判斷看似多余,其實是為了防止有些時候,球被“撞”出邊界,
//以至于常年平行于邊界低速運動,按正常的四舍五入無法顯示出來
if ((ball + i)->X < 1) //邊界情況
(ball + i)->wX = 1;
else if ((ball + i)->X >WIDTH) //邊界情況
(ball + i)->wX = WIDTH;
else
(ball + i)->wX = (int)((ball + i)->X+0.5); //四舍五入
if ((ball + i)->Y < 1) //邊界情況
(ball + i)->wY = 1;
else if ((ball + i)->Y > HIGN) //邊界情況
(ball + i)->wY = HIGN;
else
(ball + i)->wY = (int)((ball + i)->Y+0.5); //四舍五入
(ball + i)->body = 'o';//球是圓的,所以直接全部設為小寫字母o
//速度的初始化,大小為一個單位,方向隨機生成
double xita = rand() % 360;
(ball + i)->dX = cos(PI * xita / 180);
(ball + i)->dY = sin(PI * xita / 180);
}
while (TRUE)
{
// system("CLS");
//清屏,但我不用此法,下為更優方法,來自周宇航大佬,
/**************************************************************/
HANDLE hOut;
COORD pos={0,0};
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOut,pos);//重設列印起點
CONSOLE_CURSOR_INFO cci;
GetConsoleCursorInfo(hOut, &cci);
cci.bVisible = FALSE;
SetConsoleCursorInfo(hOut, &cci);//隱藏游標
/**************************************************************/
print_pos(ball, num);
Manage(ball, num);
printf("落地次數:%d", COUNT);
Sleep(25);//休眠25毫秒
}
free(ball); //其實這條是多余的
return 1; //這個程式不可能會有正常的回傳值0,所以如果回傳,則一定是非0的
}
//顯示操作臺和某球的實際位置
void print_pos(struct BALL* p, int num) {
//上邊界
for (int i = 0; i < WIDTH + 2; i++)
putchar('*');
putchar('\n');
//中間部分
for (int i = 1; i <= HIGN; i++) {
putchar('|');
for (int j = 1; j <= WIDTH; j++) {
short flag = 1;
for (int k = 0; k < num; k++) {
//這個回圈的目的是,看一看是否在該位置已有一個(或多個)球
//如果有一個球,馬上break;
//如果多個球,在第一個球就已經break,了,這一瞬間兩球重影(肉眼無法察覺,)
//這樣做看似不美觀不簡潔,但是不這樣做,可能導致右邊界被“撞出”,
if ((p + k)->wX == j && (p + k)->wY == i) {
color((p + k)->sel);
putchar((p + k)->body);
color(7);
flag = 0;
break;
}
}
if (flag)
putchar(' ');
}
putchar('|');
putchar('\n');
}
//下邊界
for (int i = 0; i < WIDTH + 2; i++)
putchar('*');
putchar('\n');
}
void Manage(struct BALL* p, int num) {
//這里簡便起見,直接將球設為質點,采用對心碰撞,
//考慮球與球之間的相撞,不妨假設球的質量是一樣的,無能量損失,動量守恒,即速度交換,
for (int i = 1; i < num; i++)
for(int j=0;j<num-i;j++)
if (pow((p + i)->X - (p + j)->X, 2) + pow((p + i)->Y - (p + j)->Y, 2) <= pow(REACH,2))
{
swap(&(p + i)->dX, &(p + j)->dX);
swap(&(p + i)->dY, &(p + j)->dY);
}
//以下采用指標的方式,以便處理多個球
for (int i = 0; i < num; i++){
//考慮左右碰壁的情況
if ((p + i)->X <= 1 || (p + i)->X >= WIDTH) {
(p + i)->dX = -(p + i)->dX;
}
//考慮上方碰壁的情況
if ((p + i)->Y <= 1) {
(p + i)->dY = -(p + i)->dY;
}
//考慮下方碰壁的情況
if ((p + i)->Y >= HIGN) {
(p + i)->dY = -(p + i)->dY;
putchar('\7');//發出聲音
COUNT++;//記錄落地次數
}
//球的位置在此發生改變了,改變數為速度乘以一個時間單位
(p + i)->X += (p + i)->dX;
(p + i)->Y += (p + i)->dY;
//球的顯示位置隨實際位置相應改變
if ((p + i)->X < 1)
(p + i)->wX = 1;
else if ((p + i)->X > WIDTH)
(p + i)->wX = WIDTH;
else
(p + i)->wX = (int)((p + i)->X + 0.5);
if ((p + i)->Y < 1)
(p + i)->wY = 1;
else if ((p + i)->Y > HIGN)
(p + i)->wY = HIGN;
else
(p + i)->wY = (int)((p + i)->Y + 0.5);
}
}
void swap(double* x, double* y) {
double temp = *x;
*x = *y;
*y = temp;
}
void color(const unsigned short color1)
{
/*僅限改變0-15的顏色;如果在0-15,那么實作對應的顏色,因為如果超過15,則默認白色,*/
if (color1 >= 0 && color1 <= 15)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color1);
/*如果不在0-15的范圍顏色,那么改為默認的顏色白色;*/
else
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
/*顏色對應值:
0=黑色 8=灰色
1=藍色 9=淡藍色
2=綠色 10=淡綠色 0xa
3=湖藍色 11=淡淺綠色 0xb
4=紅色 12=淡紅色 0xc
5=紫色 13=淡紫色 0xd
6=黃色 14=淡黃色 0xe
7=白色 15=亮白色 0xf
也可以把這些值設定成常量,
*/
}
一部分說明
由于不讓調第三庫,所以不可能做出非常好的影片效果,另一方面,在二維平面上球與球之間的碰撞是非常復雜的,哪怕是完全彈性碰撞,在能量守恒、動量守恒的前提下,考慮碰撞位置、沖量大小和方向的不同,可能出現無窮多解,
因此,我全部質點化處理,把球的碰撞直接處理為速度交換或不妥當的,
程式運行的效果如下:

囿于當時的有限水平和悲傷心情,不足之處,敬請諒解,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/225858.html
標籤:其他
下一篇:《資料結構與演算法經典》、《吃透演算法只為面試》、《程式員代碼面試指南 》,王者上分必備,憑借這些筆記,拿下多個大廠offer
