このプログラムは最適化を全く行っていないので,動作は遅い.もし,諸君が中間試験以
降,作成するプログラムの参考にするならば,最適化を図った方が良いだろう.
1 #include <stdio.h> 2 #include <GL/glut.h> 3 #include <math.h> 4 #include <stdlib.h> 5 #include <time.h> 6 7 #define H_WIN 400 // ウィンドウの幅 8 #define W_WIN 300 // ウィンドウの高さ 9 10 #define W2_HODAI 10 // 砲台の横幅の半分 11 #define H_HODAI 15 // 砲台の上面のy座標 12 #define L_HODAI 5 // 砲台の下面のy座標 13 #define L_E_BEAM 20 // 防衛軍のビームの長さ 14 #define V_E_BEAM 1.5 // 防衛軍のビームの速度 15 #define N_E_BEAM 1 // 防衛軍のビームの画面上の最大数 16 17 #define L_I_BEAM 10 // インベーダー軍のビームの長さ 18 #define V_I_BEAM 0.8 // インベーダー軍のビームの速度 19 #define P_I_BEAM 500 // インベーダー軍のビームの初期発射確率 20 21 #define N_I_BEAM 20 // インベーダー軍のビームの画面上の最大数 22 #define NXIV 9 // インベーダー軍の列の数 23 #define NYIV 4 // インベーダー軍の行の数 24 #define V_INVADER 0.1 // インベーダー軍の速度 25 26 #define NOT_DECIDE 0 27 #define INVADER 1 28 #define HUMAN 2 29 30 //---- プロトタイプ宣言 ----- 31 void initialize(void); // 初期化 32 33 void draw(void); // 図を描く 34 void draw_result(void); // 結果表示 35 void draw_hodai(void); // 防衛軍の砲台の描画 36 void draw_e_beam(void); // 防衛軍のビームの描画 37 void draw_i_beam(void); // インベーダー軍のビームの描画 38 void draw_invader(void); // インベーダー軍の描画 39 40 void change_state(void); // 状態変化に関する処理 41 void state_e_beam(void); // 防衛軍のビームの状態変化 42 void state_invader(void); // インベーダー軍の状態変化 43 void state_i_beam(void); // インベーダー軍のビーム状態変化 44 45 void mouse_xy(int x, int y); 46 void shoot(unsigned char key, int x, int y); // 防衛軍ビーム発射 47 48 void resize(int w, int h); // サイズの調整 49 void set_color(void); // 塗りつぶし色の設定 50 51 //---- グローバル変数 ------- 52 double xc = 100.0; // マウスのx座標 53 54 typedef struct{ 55 unsigned char status; // 0:dead 1:alive 56 double x, y; // 中心座標 57 }invader; 58 59 invader invd[NXIV][NYIV]; // インベーダー 60 int alive_inv=NXIV*NYIV; // 生きているインベーダーの数 61 double inv_vx=V_INVADER; // インベーダーの横方向の速度 62 63 64 typedef struct{ 65 char status; // 0:格納庫 1:砲台の上 2:移動中 66 double x; // ビームのx座標 67 double y0, y1; // ビームのy座標 y0:先頭 y1:最後尾 68 double vy; // ビームの速度 69 }beam; 70 71 beam e_beam[N_E_BEAM]; // 地球防衛軍のビーム 72 beam *p_e_beam1; // 地球防衛軍の次に発射可能なビーム 73 beam i_beam[N_I_BEAM]; // インベーダー軍のビーム 74 75 int winner = NOT_DECIDE; 76 char *win="You won a game."; 77 char *lost="You lost a game."; 78 //==================================================================== 79 // main関数 80 //==================================================================== 81 int main(int argc, char *argv[]) 82 { 83 84 initialize(); 85 glutInitWindowPosition(100,200); // 初期位置(x,y)指定 86 glutInitWindowSize(W_WIN,H_WIN); // 初期サイズ(幅,高さ)指定 87 glutInit(&argc, argv); // GLUT 初期化 88 glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); // 表示モードの指定 89 glutCreateWindow("space invader modoki"); // windowをタイトルを付けてを開く 90 glutDisplayFunc(draw); // イベントにより呼び出し 91 glutReshapeFunc(resize); // サイズ変更のときに呼び出す関数指定 92 glutIdleFunc(change_state); // 暇なときに実行(状態の変化) 93 glutPassiveMotionFunc(mouse_xy); // マウスイベント(砲台の移動) 94 glutKeyboardFunc(shoot); // キーボードイベント(ビームを発射) 95 set_color(); // 塗りつぶす色指定 96 glutMainLoop(); // GLUTの無限ループ 97 98 return 0; 99 } 100 101 //==================================================================== 102 // 初期化 103 //==================================================================== 104 void initialize(void) 105 { 106 int i, j; 107 108 srand((unsigned int)time(NULL)); // 乱数を発生させるため 109 110 for(i=0; i<N_E_BEAM; i++){ 111 e_beam[i].status=0; 112 e_beam[i].y0=H_HODAI+L_E_BEAM; 113 e_beam[i].y1=H_HODAI; 114 e_beam[i].vy=0.0; 115 } 116 117 e_beam[0].status=1; // 砲台にのせる 118 p_e_beam1=&e_beam[0]; 119 120 for(i=0; i<N_I_BEAM; i++){ 121 i_beam[i].status = 0; 122 i_beam[i].y0 = 0; 123 i_beam[i].y1 = 0; 124 i_beam[i].vy = V_I_BEAM; 125 } 126 127 for(i=0; i<NXIV; i++){ 128 for(j=0; j<NYIV; j++){ 129 invd[i][j].status=1; 130 invd[i][j].x = 20*(i+1); // x,yとも20ピクセル間隔 131 invd[i][j].y = H_WIN - NYIV*20+10+20*j; 132 } 133 } 134 } 135 136 137 //==================================================================== 138 // 図を描く 139 //==================================================================== 140 void draw(void) 141 { 142 glClear(GL_COLOR_BUFFER_BIT); 143 144 if(winner != NOT_DECIDE) draw_result(); 145 146 draw_hodai(); // 砲台を描く関数呼び出し 147 draw_e_beam(); // 地球防衛軍のビームを描く関数の呼び出し 148 draw_i_beam(); // インベーダー軍のビームを描く関数の呼び出し 149 draw_invader(); // インベーダーを描く関数の呼び出し 150 151 glutSwapBuffers(); // 描画 152 } 153 154 155 //==================================================================== 156 // 勝者の表示 157 //==================================================================== 158 void draw_result(void) 159 { 160 int i=0; 161 162 glColor3d(0.0, 1.0, 0.0); 163 164 if(winner==HUMAN){ 165 while(win[i]!='\0'){ 166 glRasterPos2i(50+15*i,100); 167 glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24,win[i]); 168 i++; 169 } 170 }else if(winner==INVADER){ 171 while(lost[i]!='\0'){ 172 glRasterPos2i(50+15*i,100); 173 glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24,lost[i]); 174 i++; 175 } 176 } 177 } 178 179 180 //==================================================================== 181 // 地球防衛軍の砲台の描画 182 //==================================================================== 183 void draw_hodai(void) 184 { 185 glColor3d(0.5, 1.0, 0.5); // 線の色指定(RGB) 186 glBegin(GL_POLYGON); 187 glVertex2d(xc-W2_HODAI, L_HODAI); 188 glVertex2d(xc+W2_HODAI, L_HODAI); 189 glVertex2d(xc+W2_HODAI, H_HODAI); 190 glVertex2d(xc-W2_HODAI, H_HODAI); 191 glEnd(); 192 } 193 194 195 //==================================================================== 196 // 地球防衛軍のビーム砲の描画 197 //==================================================================== 198 void draw_e_beam(void) 199 { 200 int i; 201 202 for(i=0;i<N_E_BEAM;i++){ 203 if(e_beam[i].status != 0){ 204 glColor3d(1.0, 0.0, 0.0); // 線の色指定(RGB) 205 glBegin(GL_LINES); 206 glVertex2d(e_beam[i].x, e_beam[i].y0); 207 glVertex2d(e_beam[i].x, e_beam[i].y1); 208 glEnd(); 209 } 210 } 211 } 212 213 214 //==================================================================== 215 // インベーダー軍のビームの描画 216 //==================================================================== 217 void draw_i_beam(void) 218 { 219 int i; 220 221 for(i=0; i<N_I_BEAM; i++){ 222 if(i_beam[i].status == 2){ 223 glColor3d(0.0, 0.0, 1.0); // 線の色指定(RGB) 224 glBegin(GL_LINES); 225 glVertex2d(i_beam[i].x, i_beam[i].y0); 226 glVertex2d(i_beam[i].x, i_beam[i].y1); 227 glEnd(); 228 } 229 } 230 } 231 232 233 //==================================================================== 234 // インベーダー軍の描画 235 //==================================================================== 236 void draw_invader(void) 237 { 238 int i, j; // インベーダーのi列j行 239 240 for(i=0; i<NXIV; i++){ 241 for(j=0; j<NYIV; j++){ 242 if(invd[i][j].status==1){ // 生きているインベーダーのみを描く 243 244 //------ 胴体 ---------------------- 245 glColor3d(1.0, 1.0, 1.0); 246 glBegin(GL_POLYGON); 247 glVertex2d(invd[i][j].x-8, invd[i][j].y); 248 glVertex2d(invd[i][j].x-3, invd[i][j].y-4); 249 glVertex2d(invd[i][j].x+3, invd[i][j].y-4); 250 glVertex2d(invd[i][j].x+8, invd[i][j].y); 251 glVertex2d(invd[i][j].x+3, invd[i][j].y+4); 252 glVertex2d(invd[i][j].x-3, invd[i][j].y+4); 253 glEnd(); 254 255 //------- 手 足 触覚 ----------------------- 256 glBegin(GL_LINES); 257 glVertex2d(invd[i][j].x-7, invd[i][j].y); // 左手 258 glVertex2d(invd[i][j].x-7, invd[i][j].y+6); 259 glVertex2d(invd[i][j].x+7, invd[i][j].y); // 右手 260 glVertex2d(invd[i][j].x+7, invd[i][j].y+6); 261 glVertex2d(invd[i][j].x-4, invd[i][j].y-4); // 左足 262 glVertex2d(invd[i][j].x-6, invd[i][j].y-8); 263 glVertex2d(invd[i][j].x+4, invd[i][j].y-4); // 右足 264 glVertex2d(invd[i][j].x+6, invd[i][j].y-8); 265 glVertex2d(invd[i][j].x-2, invd[i][j].y+4); // 左触覚 266 glVertex2d(invd[i][j].x-5, invd[i][j].y+6); 267 glVertex2d(invd[i][j].x+2, invd[i][j].y+4); // 右触覚 268 glVertex2d(invd[i][j].x+5, invd[i][j].y+6); 269 glEnd(); 270 271 //------- 目玉 ---------------- 272 glColor3d(0.0, 0.0, 0.0); 273 glBegin(GL_POLYGON); // 左目 274 glVertex2d(invd[i][j].x-3, invd[i][j].y); 275 glVertex2d(invd[i][j].x-1, invd[i][j].y); 276 glVertex2d(invd[i][j].x-1, invd[i][j].y+2); 277 glVertex2d(invd[i][j].x-3, invd[i][j].y+2); 278 glEnd(); 279 glBegin(GL_POLYGON); // 右目 280 glVertex2d(invd[i][j].x+3, invd[i][j].y); 281 glVertex2d(invd[i][j].x+1, invd[i][j].y); 282 glVertex2d(invd[i][j].x+1, invd[i][j].y+2); 283 glVertex2d(invd[i][j].x+3, invd[i][j].y+2); 284 glEnd(); 285 } 286 } 287 } 288 } 289 290 291 //==================================================================== 292 // リサイズ 293 // この関数は window のサイズが変化したら呼び出される 294 // 引数 295 // w:ウィンドウの幅 296 // h:ウィンドウの高さ 297 //==================================================================== 298 void resize(int w, int h) 299 { 300 glLoadIdentity(); // 変換行列を単位行列に 301 gluOrtho2D(0, W_WIN, 0, H_WIN); // world座標系の範囲 302 glViewport(0, 0, w, h); // ウィンドウ座標系を指定 303 } 304 305 306 //==================================================================== 307 // PCが暇なときに実行する.これが実行されると状態が変化する 308 //==================================================================== 309 void change_state(void) 310 { 311 312 if(winner == NOT_DECIDE){ 313 state_e_beam(); // 地球防衛軍のビームの処理 314 state_invader(); // インベーダー軍の処理 315 state_i_beam(); // インベーダー軍のビームの処理 316 } 317 318 glutPostRedisplay(); 319 } 320 321 322 //==================================================================== 323 // 地球防衛軍のビームの状態の処理 324 //==================================================================== 325 void state_e_beam(void) 326 { 327 int i,l,m; 328 int st0=0; 329 int rdy=0; 330 int nshoot=0; // 発射済みの地球防衛軍の玉の数 331 double min_y=H_WIN+L_E_BEAM; // 最もしたのミサイルの先のy座標 332 double ydis; // 最も下のミサイルと発射台の距離 333 334 for(i=0; i<N_E_BEAM; i++){ 335 switch(e_beam[i].status){ 336 337 //-------- 格納庫にあるビームの処理 ------------------------ 338 case 0: 339 st0=i; // 次に発射可能なビームを設定 340 break; 341 342 //-------- 砲台にあるビームの処理 ------------------------ 343 case 1: 344 e_beam[i].x = xc; // x方向に移動 345 rdy=1; // 砲台にビームがあることを示すフラグをON 346 break; 347 348 //-------- すでに発射されたビームの処理 ------------------------ 349 case 2: 350 nshoot++; // 発射されているビームをカウント 351 e_beam[i].y0 += e_beam[i].vy; // ビームの移動 352 e_beam[i].y1 += e_beam[i].vy; 353 354 // ------ インベーダーにビームが衝突したことを確認して処理 ------ 355 for(l=0; l<NXIV; l++){ 356 for(m=0; m<NYIV; m++){ 357 if(invd[l][m].status==1){ 358 if((invd[l][m].x-8 < e_beam[i].x) && 359 (e_beam[i].x < invd[l][m].x+8) && 360 (invd[l][m].y-4 < e_beam[i].y0) && 361 (e_beam[i].y1 < invd[l][m].y+4)){ 362 invd[l][m].status=0; // インベーダーの死亡 363 alive_inv--; // 生きているインベーダーの数を-1 364 if(alive_inv==0)winner=HUMAN; 365 e_beam[i].status=0; // ビームは格納庫へ 366 e_beam[i].y0=H_HODAI+L_E_BEAM; // ビームの初期化 367 e_beam[i].y1=H_HODAI; 368 } 369 } 370 } 371 } 372 373 374 // ---- 画面から地球防衛軍のビームがはみ出た場合の処理 -------- 375 if(H_WIN+L_E_BEAM < e_beam[i].y0){ 376 e_beam[i].status = 0; 377 e_beam[i].y0 = H_HODAI+L_E_BEAM; 378 e_beam[i].y1 = H_HODAI; 379 e_beam[i].vy = 0.0; 380 } 381 if(e_beam[i].y0 < min_y) min_y=e_beam[i].y0; 382 break; 383 default: 384 printf("e_beam status error!!\n"); 385 exit(1); 386 } 387 } 388 389 390 // --- 地球防衛軍の新たな発射可能なビームの処理 ----- 391 ydis = min_y-H_HODAI; 392 if( (2.5*L_E_BEAM < ydis) && (rdy==0) && (nshoot<N_E_BEAM) ){ 393 e_beam[st0].status=1; 394 p_e_beam1=(beam *)&e_beam[st0]; // 発射可能なビームをポインターで表現 395 } 396 } 397 398 399 //==================================================================== 400 // インベーダー軍の状態の処理 401 //==================================================================== 402 void state_invader(void) 403 { 404 int i, j, k; 405 double ivmin_x=W_WIN, ivmax_x=0; 406 double ivmin_y=H_WIN, ivmax_y=0; 407 int can_attack; 408 409 for(i=0; i<NXIV; i++){ 410 can_attack=1; 411 for(j=0; j<NYIV; j++){ 412 if(invd[i][j].status==1){ // インベーダーの生死のチェック 413 invd[i][j].x += inv_vx; // インベーダーの横方向移動 414 // ---- インベーダー軍のビーム発射の処理 ------ 415 if(can_attack == 1 && rand()%P_I_BEAM == 0){ // 発射条件 416 for(k=0; k<N_I_BEAM; k++){ 417 if(i_beam[k].status !=2){ // 発射可能なビームを探す 418 i_beam[k].status =2; // ビームの発射 419 i_beam[k].x = invd[i][j].x; 420 i_beam[k].y0 = invd[i][j].y; 421 i_beam[k].y1 = invd[i][j].y-L_I_BEAM; 422 break; 423 } 424 } 425 } 426 // --- インベーダー軍の左右上下の端の座標 ------- 427 if(invd[i][j].x < ivmin_x) ivmin_x=invd[i][j].x; // 左端 428 if(invd[i][j].x > ivmax_x) ivmax_x=invd[i][j].x; // 右端 429 if(invd[i][j].y < ivmin_y) ivmin_y=invd[i][j].y; // 下の端 430 if(invd[i][j].y > ivmax_y) ivmax_y=invd[i][j].y; // 上の端 431 can_attack=0; 432 } 433 } 434 } 435 436 437 if(ivmin_x < 10) inv_vx = V_INVADER; // 左端に達したとき 438 if(ivmax_x > W_WIN-10) inv_vx = -V_INVADER; // 右端に達したとき 439 440 if((ivmin_x < 10) || (ivmax_x > W_WIN-10)){ // 左右の端に達しとき 441 for(i=0; i<NXIV; i++){ 442 for(j=0; j<NYIV; j++){ 443 invd[i][j].y -= 10; // 下に降りる 444 } 445 } 446 } 447 } 448 449 450 //==================================================================== 451 // インベーダー軍のビームの状態の処理 452 //==================================================================== 453 void state_i_beam(void) 454 { 455 int i; 456 457 for(i=0; i<N_I_BEAM; i++){ 458 if(i_beam[i].status ==2){ 459 i_beam[i].y0 -= i_beam[i].vy; 460 i_beam[i].y1 -= i_beam[i].vy; 461 462 if(i_beam[i].y1 < 0) i_beam[i].status=0; 463 464 if((xc-W2_HODAI < i_beam[i].x) && 465 (i_beam[i].x < xc+W2_HODAI) && 466 (L_HODAI < i_beam[i].y0) && 467 (i_beam[i].y1 < H_HODAI)){ 468 winner=INVADER; // 地球防衛軍の負け 469 } 470 } 471 } 472 } 473 474 475 //==================================================================== 476 // マウスイベントの処理 477 //==================================================================== 478 void mouse_xy(int x, int y) 479 { 480 xc=x; // マウスのx座標をグローバル変数の xc へ代入 481 } 482 483 484 //==================================================================== 485 // キーボードイベントの処理 486 // スペースキーが押されたら地球防衛軍のビームを発射 487 //==================================================================== 488 void shoot(unsigned char key, int x, int y) 489 { 490 //--- スペースキーが押されて,発射可能なビームがあるとき ---- 491 if(key==' ' && p_e_beam1 != NULL){ 492 p_e_beam1->status = 2; // ビームを発射の状態にする 493 p_e_beam1->vy = V_E_BEAM; // ビームの速度を設定 494 p_e_beam1 = NULL; // 発射可能なビームが無い 495 } 496 } 497 498 499 //==================================================================== 500 // 色の指定 501 //==================================================================== 502 void set_color(void) 503 { 504 glClearColor(0.0, 0.0, 0.0, 1.0); //赤緑青と透明度 505 }
この乱数は,ゲームのプログラムでは必須の項目であろう.ここでは,インベーダーがビー ムの発射は乱数で決めている.
C言語のプログラムでは,rand()関数を使って,乱数を発生させる.例えば,次のよ うにすると,rand()関数が呼び出される度に,それが乱数を返し,配列a[i]に 格納される.
for(i=0; i<ndata; i++){ a[i]=rand(); }
コンピューターは正確に言われたとおり(プログラムのとおり)に計算を行うことは,諸君 もよく知っているはずである.そのため,コンピューターはめちゃくちゃな順序で数が並 んでいる乱数を発生させることは苦手である.先ほどのrand()関数は,ある初期値 4を使って,計算により乱数を決めている. 同じ初期値を使って,rand()関数を呼び出すと,同じ数列が発生するこのになる. これでは,乱数とは言い難いので,初期値を毎回変更するのがよい.
初期値も値が毎回異なる整数を決める必要があるが,現在の暦時刻を返すtime()関 数を用いるのが一般的である.初期値の設定は,srand()関数に引数(符号無し整数) を渡すことにより可能である.次のようにすれば,毎回異なる初期値を決めることができ る.
srand((unsigned int)time(NULL));ただし,1秒以内であれば,timeは同じ値となり,同じ初期値となり,同じ乱数とな ることに注意が必要である.(unsigned int)は,キャストと呼ばれる強制型変換で, 引き続く値の型を変換している.time()関数の引数は暦時刻で,暦時刻がポインター で格納される.暦時刻を格納する必要がないときには,NULLと空ポインターを指定 する.
以上から,乱数を発生させるためには,rand()とsrand(),time()関数が 必要であることが分かった.これらの関数を使うためには,関数の宣言が書かれているヘッ ダーファイルが必要である.rand()とsrand()にはstdlib.hが, time()にはtime.hが必要となる.以上をまとめると,配列a[i]に1024個の乱数 を発生させるためには,次のように書く.
#include <stdlib.h> #include <time.h> int main(void){ int a[1024], i; srand((unsigned int)time(NULL)); for(i=0; i<1024; i++){ a[i]=rand(); } return 0; }
リスト1のプログラムでは,108行目で乱数の初期値を決めている.そ して,415行目で乱数を発生させて,インベーダーがビームを発射するか,否かを決めて いる.415行目は,
rand()%P_I_BEAM == 0