深圳大學電信學院《C程式設計》期末大作業:《在二維封閉房間中的彈球模擬程式》
實驗材料
實驗任務
(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
也可以把這些值設定成常量。
*/
}
一部分說明
由於不讓調第三庫,所以不可能做出非常好的動畫效果。另一方面,在二維平面上球與球之間的碰撞是非常複雜的。哪怕是完全彈性碰撞,在能量守恆、動量守恆的前提下,考慮碰撞位置、衝量大小和方向的不同,可能出現無窮多解。
因此,我全部質點化處理,把球的碰撞直接處理為速度交換或不妥當的。
囿於當時的有限水平和悲傷心情,敬請諒解。
相關文章
- c語言程式設計學習之二維陣列C語言程式設計陣列
- c# 程式設計學習(二)C#程式設計
- 20201022-成信大-C語言程式設計-20201學期《C語言程式設計B》C-trainingExercises26C語言程式設計AI
- 【Java程式設計】使用Java模擬C/C++中的queue佇列Java程式設計C++佇列
- Python期末大作業Python
- 答題判題程式4 及 家居強電電路模擬程式1-2 PTA大作業 第二次總結blog
- 湘潭大學2018年上學期程式設計實踐模擬考試3 參考題解程式設計
- 湘潭大學2018年上學期程式設計實踐模擬考試2 參考題解程式設計
- 第十五屆浙江大學寧波理工學院程式設計大賽(同步賽)程式設計
- c# 程式設計學習(四)C#程式設計
- C#程式設計學習(一)C#程式設計
- 湘潭大學2018年軟體工程程式設計實踐第二次模擬考試題解軟體工程程式設計
- 如何學習shell程式設計?Linux運維學習shell程式設計是什麼程式設計Linux運維
- 大二上:《大學生創業基礎》期末作業創業
- 適合 C++ 新手學習的開源專案——在 GitHub 學程式設計C++Github程式設計
- 學程式設計C語言和Python之間有什麼不同?程式設計C語言Python
- 華中農業大學第十三屆程式設計競賽程式設計
- 乒乓球比賽計分程式模擬衝刺(Sprint)計劃
- CUDA C 程式設計權威指南 學習筆記:第二章 CUDA程式設計模型程式設計筆記模型
- 深圳Javaweb學習:程式設計師對程式設計名詞的通俗理解:假設你是個妹子JavaWeb程式設計師
- 《java學習二》併發程式設計Java程式設計
- 同構——程式設計中的數學程式設計
- 程式設計思路-球連球組成的群程式設計
- 「大學生學程式設計系列」第二篇:如何選擇第一門程式語言?程式設計
- FPGA:乒乓球比賽模擬機的設計FPGA
- 大規模C++程式設計 -- 基礎知識C++程式設計
- 程式設計師生活之路--來自程式設計師爸爸的一封信程式設計師
- 好程式設計師前端學習路線分享模擬JavaScript中物件導向技術程式設計師前端JavaScript物件
- java二階段總結(電路模擬程式)Java
- 為什麼在中國僅有20%的程式設計師買房?程式設計師
- 微信小程式:小程式碼、小程式二維碼、普通二維碼微信小程式
- shell程式設計學習筆記(二):Shell中變數的使用程式設計筆記變數
- 基於C++程式設計醫院醫學影像系統PACS實現C++程式設計
- 自學程式設計難度大嗎?程式設計
- java程式設計師3個月業餘時間學習 大資料路徑Java程式設計師大資料
- 計算機學院的老師實際程式設計能力如何?計算機程式設計
- 中國大學MOOC 浙江大學 翁愷老師《零基礎學Java語言》-第二週程式設計題Java程式設計
- 電腦科學和Python程式設計導論(二 ) Python簡介Python程式設計