C
端末
ブロック崩し
curses
C言語Day 13

C言語でシンプルすぎるブロック崩しを書いた

More than 3 years have passed since last update.

主にC言語のcursesというライブラリを使用して、端末上で動くブロック崩しを作らされ作りました。何か大した事していないのに、無駄に長くなってしまいました。。ソースコードは一応こちらにあります。

https://github.com/hyde2able/BreakOut


cursesとは


cursesは端末に依存せずに適切な方法で画面を更新したりするためのライブラリです。curses.hをインクルードすると、端末上の文字の色を変えたり、座標を指定して文字を配置できたりします。ターミナル上で動くプログラム、例えばviエディタなどはこれを使用しています。



Cursesライブラリの関数

ライブラリにいろいろと関数が用意されているが、その中で比較的使うものと使えそうなものを簡単にまとまる。


カーソルの移動

int move(int y, int x);                 // カーソルを(x, y)に移動させる

int wmove(WINDOW *win, int y, int x); // ある*winのカーソルを(x,y)に移動させる

一つのウィンドウのみなら上のmove関数だけでいいけど、複数のウィンドウを取り扱うなら下も使えるらしい。今回はwmoveは使ってないから正直複数ウィンドウはよくわからない。


文字出力

今回のブロック崩しの主戦力関数達。

int printw(char *format [, arg] ...);    // 現在のカーソル位置からprintf()みたいに描画

int addch(chtype ch); // 現在のカーソル位置で文字を1文字上書きする。その後カーソル一つ進む
int insch(chtype ch); // 現在のカーソル位置に文字を挿入する。後ろの文字達はその分ずれる

基本的にカーソル移動とこの文字出力で描画はできるけど、面倒だからってことで、合体した関数もある。

int mvprintw(int y, int x, char *format [, arg]...);  // (x, y)にprintwする

int mvaddch(int y, int x, chtype ch); // (x, y)に文字を上書き
int mvinsch(int y, int x, chtype ch); // (x, y)に文字を挿入

さらに、ウィンドウを指定して、そのウィンドウの(x, y)に描画するmvprintw()とかmvaddsch()とかもみたいな関数もあるらしいけれど、それは割愛。


文字削除

書ければ消せるよね。

int delch(void);            // 現在のカーソル位置の文字を削除する

int mvdelch(int y, int x); // (x, y)の文字を削除する

ただ文字出力するときとは勝手が違って、1文字ずつしか消去できなさそうなので、今回は使わなかった。ちょっと強引な感じで消したように見せてる。


画面のクリア

人生だって画面だってクリアして0からやり直したいこともあるさ

int erace(void);      // 画面の全ての位置にスペースを出力する

int clear(void); // 画面をクリアする。端末コマンドからクリアするのでeraseより確実らしい
int clrtobot(void); // 現在のカーソルの位置以降の画面をクリアする
int clrtoeol(void); // 現在のカーソルの位置以降の行をクリアする


おまじない

initsrc();     // 最初にやるおまじない。端末の初期化。必須

endwin(); // 最後にやるおまじない。端末の終了。必須
cbreak(); // 実行すると、入力文字を即時利用できる。ほとんど必須
noecho()/echo(); // 入力文字を端末に表示するかしないか。viエディタ的なのを作るならecho();
keypad(window, bf); // キーパッドを設定する。


色変更

端末上ならおしゃれに色もかえられる。

int start_color(void);       // カラーの設定

bool has_colors(void); // 端末がカラー表示できるかどうか。返り値は T / F
int init_pair(int num, COLOR_BLACK, COLOR_WHITE); // 色番号numを黒文字の白地に設定
int COLOR_PAIR(int num); // 色番号numに表示する文字色を指定する

デフォルトの色を赤色にしたかったらこんな感じ。

start_color();

init_pair(1, COLOR_RED, COLOR_RED); //色1に赤文字の赤地をセット
init_pair(2, COLOR_BLUE, COLOR_BLACK); //色2に青文字の黒地をセット
bkgd(COLOR_PAIR(1)); // 色1を背景色に
attrset(COLOR_PAIR(2)); // 色2を文字色にセット

ちなみに使える色はこれだけ

定数

COLOR_BLACK
黒色

COLOR_RED
赤色

COLOR_BLUE
青色

COLOR_GREEN
緑色

COLOR_CYAN
水色

COLOR_YELLOW
黄色

COLOR_MAGENTA
紫色

COLOR_WHITE
白色


あとなんか使えるやつ

getmaxyx(stdscr, h, w);     // 変数hに端末の高さ, 変数wに端末の幅を代入してくれる

getch(); // こいつなかったらやってられない。キーパッドから入力した文字を取得できる


さっそくブロック崩し作ろう!

以上の関数を使って、ブロック崩しを作っていきましょう!ブロック崩しを作ったのは1年前なので、変な記述等があるかもしれません。書きながら少しは修正していきますが。。


プログラムの流れ

基本的なプログラムの流れはかなりシンプルで


main.c

void run();            //ここで実行。後で中身定義

int main(int argc, char **argv){
initscr(); //cursesを初期化する
noecho(); //入力文字を出力しないようにする
cbreak(); //入力した文字を即時利用可能にする
keypad(stdscr,TRUE); //キーパッドを設定する
run(); //実行
endwin(); //cursesを終了する
return 0;
}


とcursesを初期化して入力した文字をコンソールに表示しないようにと、すぐに利用できるように設定してrun関数の中で

mvaddstr(x, y, str);               // (x,y)に文字列strを描画

mvprintw(x, y, format, data, ...) // (x,y)に文字列を描画。後半はprintf()と書き方同じ

この2つの関数を頑張って使って、後は決まった時間ごとにループさせて、キー入力によってバーを動かして、その都度、ボールとの当たり判定をして...の繰り返しです。


完成イメージ


スタート画面

opening.png

cursesにはある問題点が。出力文字の大きさをいじれない。文字色はいじれるのに、大きさはいじれない。。てなわけで文字ちっさいです。BREAK OUTの部分は頑張りました。


プレイ中の画面

play1.png

これを打つとー

play2.png

当たるとー

play3.png

シンプルですね。1年前に書いたやつですが、レトロというかシンプルというか。。。愛着湧いてきますよ。


ゲームオーバー画面

gameover.png

3回落ちたらゲームオーバーということで、またも頑張ってGAME OVERを表示しました。まじで地道な作業です。


使う関数を定義しておきましょう


function.h

#ifndef _FUNCTION_h_

#define _FUNCTION_h_

#define WALL_R (COLS - 20) //右側の壁
#define WALL_L 2 //左側の壁
#define TOP 0 //上の壁
#define UNDER (LINES - 5) //下の壁

//ブロックの構造体
struct BLOCK{
double X;
double Y; //ブロックの座標
int life; //何回当たれば壊れるか
struct BLOCK *next; //次のブロックへのポインタ
};

// ボールの構造体
struct Ball{
double X;
double Y; // 座標
double Dx;
double Dy; // それぞれの速度ベクトル
int waitCount; // 速度
};

// バーの構造体
struct Bar {
double X; // バーの位置
double Y;
int width; // バーの長さ
int shoot; // ボールを発射するか(1)しないか(0)
char addBar[12]; // バーの描画部分
char eraseBar[12]; // バーの消す部分
};

// レコードの構造体
struct Record{
int level; //レベル
double score; //総スコア
double score1; //ボール一個目のスコア
double score2; //ボール二個目のスコア
double score3; //ボール三個目のスコア
int time; //合計生存タイム
int time1; //ボール一個目の生存タイム
int time2; //ボール二個目の生存タイム
int time3; //ボール三個目の生存タイム
};

void freeBlocks(struct BLOCK *block); // 全ブロックのメモリを解放する
void makeBlock(struct BLOCK **block,double x,double y); // (x,y)にブロック生成
void makeBlocks(struct BLOCK **block); // 全ブロックを生成
void showBlocks(struct BLOCK *block); // 全ブロックを描画
int breakBlock(struct BLOCK **block,double x,double y,double *Dx,double *Dy,int *count); // ブロックとボールの当たり判定
int CollisionDetection(struct Ball *ball, struct Bar *bar); // ボールとバーの当たり判定と落ちたかどうか
void moveBar(struct Bar *bar, struct Ball *ball, int ch); // 一定時間ごとにバーを移動
void Level(struct Bar *bar, int level); // レベルによってバーの長さを変える
void printRecord(struct Record rec); // レコードを描画
void printScore(int time, int level, double score, int life); // 記録を描画
void printGameOver(); // GameOver描画
void printBreakOut(); // スタート画面描画
void printWall(); // 左右の壁を描画

#endif


いきなりですが、今回作る関数とデータとかはこんな感じです。ブロック崩しのブロックはそれぞれをリスト構造で保持するので、構造体に次のブロックを示す*nextをポインタで用意しています。ボールとバーは記録は構造体だけで。前作ったやつはレコードをTOP5位まで保存する感じだったのですが、そこまで書くと長くなりそうなので、割愛で。すでにここまででかなり長い。。


いざコーディング


ブロック生成して表示

ブロックを生成して、描画するためのmakeblocks関数とshowblocks関数とそのメモリを解放するfreeBlocks関数を


function.c

void freeBlocks(struct BLOCK *block)

{
struct BLOCK *p;
while(p != NULL){ // 全ブロックのメモリを解放
p = block->next;
free(block);
block = p;
}
}

void makeBlock(struct BLOCK **block,double x,double y)
{
struct BLOCK *ptr,*new;
new = (struct BLOCK *)malloc(sizeof(struct BLOCK)); // メモリ確保
if(*block == NULL){ // もし最初のブロックなら
*block = new; // 新しく生成
}else{ // そうでないなら
ptr = *block; // そのリストの最後にブロックをセット
while(ptr->next != NULL){
ptr = ptr->next;
}
ptr->next = new;
}

// 座標(x, y)とライフ1と次のブロックはとりあえずNULLに
new->X = x;
new->Y = y;
new->life = 1;
new->next = NULL;
}

void makeBlocks(struct BLOCK **block)
{
double x, y;
for(y = 2*LINES/15; y < 5*LINES/15; y += 5){
for(x = ((WALL_R - 8)%9)/2 + 6; x <= WALL_R-10; x += 11){
makeBlock(block, x, y);
}
}
}

void showBlocks(struct BLOCK *block)
{
while(block != NULL){
mvaddstr(block->Y, block->X, "#####"); // ブロック#####を描画
block = block->next; // 全部描画するまでループ
}
}



ブロックとボール、バーとボールの当たり判定


function.c

//ブロックに触れたかを判定。当たったらブロックのメモリ解放。全て破壊されていたらreturn 1;

int breakBlock(struct BLOCK **block,double x,double y,double *Dx,double *Dy,int *count)
{
struct BLOCK *ptr,*tmp;
ptr = *block;
tmp = ptr;

if(ptr == NULL){
return 1;
}

while(ptr != NULL){
if(ptr->Y == y){
//もしブロックの真ん中あたりに当たったら
if((ptr->X < x) && (x < ptr->X+4)){
*Dy *= -1;
break;
}

//もしブロックの左端にあたったら
if(ptr->X == x){
//左側から来たボールが左端に当たった場合
if(*Dx == 1){
*Dx *= -1;
break;
//右側から来たボールが左端に当たった場合
}else{
*Dy *= -1;
break;
}
}

//もしブロックの右端に当たったら
if(x == ptr->X + 4){
//左側から来たボールが右端に当たった場合
if(*Dx == 1){
*Dy *= -1;
break;
//右側から来たボールが左端に当たった場合
}else{
*Dx *= -1;
break;
}
}
}
ptr = ptr->next;
}
/* while終了 */

/* 当たっていなければptrはNULLのはず。当たっていたら当たったところでbreakしているはず */
if(ptr != NULL){

if (ptr->life == 1){
*count += 1;

//一番初めのブロックだった場合
if(ptr == *block){
*block = ptr->next;
mvaddstr(ptr->Y,ptr->X," ");
free(ptr);

//一番初めブロック以外だった場合
}else{
//ptrの手前まで移動
while(tmp->next != ptr) tmp=tmp->next;

mvaddstr(ptr->Y,ptr->X," ");
tmp->next = ptr->next;
free(ptr);
}
clear();
}
}
return 0;
}

// ボールとバーの当たり判定(下に落ちた時だけ1を返す)
int CollisionDetection(struct Ball *ball, struct Bar *bar)
{
//ボールが下に落ちたら1を返す
if((*ball).Y > LINES-1) return 1;

//左にぶつかったら
if((*ball).X < WALL_L + 2){
(*ball).X = WALL_L+2;
(*ball).Dx = 1;
}

//右にぶつかったら
else if((*ball).X >= WALL_R - 1){
(*ball).X = WALL_R - 2;
(*ball).Dx = -1;
}

//上にぶつかったら
else if((*ball).Y < TOP){
(*ball).Y = 1;
(*ball).Dy = 1;
}

//バーにボールが当たった時
else if(((*ball).Y == (*bar).Y) && ((*ball).X >= (*bar).X) && ((*ball).X <= (*bar).X + ((*bar).width-1))){
(*ball).Y = (*bar).Y - 1;
(*ball).Dy = -1;
}
return 0;
}



バーの長さやバーの動きを制御


function.c

//バーを動かす

void moveBar(struct Bar *bar, struct Ball *ball, int ch)
{
mvaddstr((*bar).Y, (*bar).X, (*bar).eraseBar);
switch(ch){
case KEY_LEFT:
if((*bar).X <= WALL_L+3) (*bar).X = WALL_L+3;
(*bar).X -= 1;
break;

case KEY_RIGHT:
if((*bar).X >= WALL_R - ((*bar).width+1)) (*bar).X = WALL_R - ((*bar).width+1);
(*bar).X += 1;
break;

case KEY_UP: // 上キーを押すと速度UP
if((*ball).waitCount > 500) (*ball).waitCount -= 500;
break;

case KEY_DOWN: // 下キーを押すと速度DOWN
if((*ball).waitCount < 9000) (*ball).waitCount += 500;
break;

default:
break;
}
mvaddstr((*bar).Y, (*bar).X, (*bar).addBar);
}

//LEVEL別にバーの長さを変更
void Level(struct Bar *bar, int level)
{
if(level == 1){
strcpy((*bar).addBar, "========");
strcpy((*bar).eraseBar, " ");
(*bar).width = 8;
}
else if(level == 2){
strcpy((*bar).addBar, "=======");
strcpy((*bar).eraseBar, " ");
(*bar).width = 7;
}
else if(level== 3){
strcpy((*bar).addBar, "======");
strcpy((*bar).eraseBar, " ");
(*bar).width = 6;
}
else if(level== 4){
strcpy((*bar).addBar, "=====");
strcpy((*bar).eraseBar, " ");
(*bar).width = 5;
}
else if(level == 5){
strcpy((*bar).addBar, "====");
strcpy((*bar).eraseBar, " ");
(*bar).width = 4;
}
}



描画する関数

スタート画面とかゲームオーバー画面とかゲーム結果、左右の壁を描画


function.c

void printRecord(struct Record rec)

{
clear();
mvprintw(LINES/2 - 3,2*COLS/7,"1st ball survived for %d[s]",rec.time1);
mvprintw(LINES/2 - 2,2*COLS/7,"2st ball survived for %d[s]",rec.time2);
mvprintw(LINES/2 - 1,2*COLS/7,"3st ball survived for %d[s]",rec.time3);
mvprintw(LINES/2 - 3,4*COLS/7,"Summary, You survived for %d[s]",rec.time);
mvprintw(LINES/2 ,2*COLS/7,"Your 1st Score %6.1f",rec.score1);
mvprintw(LINES/2 + 1,2*COLS/7,"Your 2st Score %6.1f",rec.score2);
mvprintw(LINES/2 + 2,2*COLS/7,"Your 3st Score %6.1f",rec.score3);
mvprintw(LINES/2 ,4*COLS/7,"Summary, Your total Score %6.1f",rec.score);
refresh();
sleep(5);
}

void printScore(int timer, int level, double score, int life)
{
mvprintw(5,WALL_R+1,"Time %d[s]",timer);
mvprintw(6,WALL_R+1,"Level %d",level);
mvprintw(7,WALL_R+1,"SCORE %5.1f",score);
mvprintw(8,WALL_R+1,"Ball %d",life);
}

void printGameOver(struct Record rec)
{
/* 端末の幅によって表示方法変更 */
if(COLS > 150){
mvprintw(LINES/2-4,(COLS-70)/2," GGGG AAA M M EEEEE OOOO V V EEEEE RRRR ");
mvprintw(LINES/2-3,(COLS-70)/2,"G G A A MM MM E O O V V E R R ");
mvprintw(LINES/2-2,(COLS-70)/2,"G A A M M M M E O O V V E R R ");
mvprintw(LINES/2-1,(COLS-70)/2,"G AAAAA M M M EEEEE O O V V EEEEE RRRRR ");
mvprintw(LINES/2 ,(COLS-70)/2,"G A A M M E O O V V E R R ");
mvprintw(LINES/2+1,(COLS-70)/2,"G GGG A A M M E O O V V E R R ");
mvprintw(LINES/2+2,(COLS-70)/2,"G G A A M M E O O V V E R R ");
mvprintw(LINES/2+3,(COLS-70)/2," GGGG A A M M EEEEE OOOO V EEEEE R R ");
}else{
mvprintw(LINES/2,(WALL_R-10)/2,"GAME OVER");
}
refresh();
sleep(3);
clear();

/* レコード表示 */
printRecord(rec);
/* THANK YOU FOR PLAYING */
clear();
mvprintw(LINES/2 ,(COLS-20)/2,"Thank you for playing");
mvprintw(LINES/2+2,(COLS-10)/2,"[r]: restart");
mvprintw(LINES/2+4,(COLS-10)/2,"[q]: quit");
}

void printBreakOut()
{
clear();
mvprintw(LINES/2-4,(COLS-72)/2,"BBBB RRRRR EEEE AAAAA K K OOOO U U TTTTTTT ");
mvprintw(LINES/2-3,(COLS-72)/2,"B B R R E A A K K O O U U T ");
mvprintw(LINES/2-2,(COLS-72)/2,"B B R R E A A K K O O U U T ");
mvprintw(LINES/2-1,(COLS-72)/2,"BBBB RRRR EEEE AAAAA KK O O U U T ");
mvprintw(LINES/2 ,(COLS-72)/2,"B B R R E A A K K O O U U T ");
mvprintw(LINES/2+1,(COLS-72)/2,"B B R R E A A K K O O U U T ");
mvprintw(LINES/2+2,(COLS-72)/2,"B B R R E A A K K O O U U T ");
mvprintw(LINES/2+3,(COLS-72)/2,"BBBBB R R EEEE A A K K OOOO UUUU T ");
}

void printWall()
{
int i = 0;
//枠の作成
for(i=0;i<LINES;i++){
mvaddch(i,WALL_L,' ');
mvaddch(i,WALL_R,' ');
mvaddch(i,WALL_L,'|');
mvaddch(i,WALL_R,'|');
}
}


こんな感じで使用する関数は全部設定。分かりにくいコメントを所々書いてるので、これで分かるかは分からないですが分かると信じて次に進みます。『分かる』のオンパレードですね。


定義した関数でメインのrun関数を


block.c

#include "function.h"

#include <curses.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

void run();

int main(int argc,char **argv)
{
initscr(); //curses
noecho(); //curses
cbreak(); //curses
keypad(stdscr,TRUE); //curses
run(); //実行
endwin(); //curses
return 0;
}

///////////////////////////////////////////////////////////////////
// //
// run関数 //
// //
///////////////////////////////////////////////////////////////////
void run()
{
struct Bar bar; //バーの位置情報
struct Ball ball; //ボールの位置情報
struct Record rec; //今回のゲームを記録と一時的な保持記録
struct BLOCK *block = NULL; //ブロック

int ch; //キーボード入力用
int i = 0;
int count = 0; //ブロックがどれだけ破壊されたか
int loop = 0; //これによってボールの時間を制御
time_t t1,t2,t3; //時間
int life = 3; //文字通りLIFE
double score; //スコア
double level_score=0; //一時的なスコアを記録
double tmp_score=0;

// 記録の設定
rec.level = 1; //レベル

// バーの設定
bar.Y = LINES - 5; //バーの初期位置
bar.X = WALL_R / 2;
bar.width = 7; //バーの長さの初期設定
bar.shoot = 0; //ボールを発射するか(1) or しないか(0)
strcpy(bar.addBar, "=======");
strcpy(bar.eraseBar, " ");

// ボールの設定
ball.X = WALL_R / 2; //ボールの初期位置
ball.Y = LINES - 5;
ball.Dx = 1; //ボールの方向ベクトルの初期設定
ball.Dy = -1; //斜め右上45度に進む
ball.waitCount = 3000; //bポールの速さ

timeout(0);

/////////////////////////////////////////////
// //
// イントロ //
// //
/////////////////////////////////////////////
INTRO:
// BREAKOUTを表示
printBreakOut();
mvprintw(LINES/2+5,(COLS-10)/2,"[s]: start");
mvprintw(LINES/2+7,(COLS-10)/2,"[q]: quit");

/* キー入力待機。[s]を押したらスタート。[q]を押したら終了*/
while(1){
ch = getch();
if(ch == 's'){
clear();
goto START;
}
else if(ch == 'q') return;
}

/////////////////////////////////////////////
// //
// ゲームスタート //
// //
/////////////////////////////////////////////
START:
clear();
makeBlocks(&block); // ブロック生成
/////////////////////////////////////////////
// //
// 初期状態 //
// //
/////////////////////////////////////////////
INIT:
while((ch = getch()) != 'q'){
showBlocks(block);
mvaddch(ball.Y,ball.X,' ');
//shootが0の時はバーと一緒に移動
if(bar.shoot == 0){
ball.X = bar.X + 1;
ball.Y = bar.Y - 1;
//スペースが入力されたら発射(shoot=1)
if(ch == ' ') bar.shoot = 1;
} else if(bar.shoot == 1) {
t1 = time(NULL);
goto GAME;
}
printWall();
printScore(0, rec.level, score, life);
moveBar(&bar, &ball, ch);
mvaddch(ball.Y, ball.X, '@');
}
goto END;

/////////////////////////////////////////////
// //
// メインループ //
// //
/////////////////////////////////////////////
GAME:
while((ch = getch()) != 'q'){
//ボールの時間を進める。
loop++;
showBlocks(block);
mvaddch(ball.Y, ball.X, ' ');
printWall();

moveBar(&bar, &ball, ch);

t2 = time(NULL);
score = tmp_score + (count * 1000)*(rec.level/2.0) + (100000.0/ball.waitCount)*(int)(t2-t1);

// scoreが条件を満たしたらレベルアップ
if((score - level_score) > 8000*rec.level){
tmp_score = score;
rec.level++;
count = 0;
mvprintw(LINES/2,WALL_R/2,"LEVEL UP!!");
t3 = time(NULL);
if(ball.waitCount > 500) ball.waitCount -= 500;
level_score = score;
Level(&bar, rec.level);
}

// 3秒経ったら、LEVEL UPを消す。
if((int)(t2-t3) == 3) mvprintw(LINES/2, WALL_R/2, " ");
// 右側にスコア表示
printScore((int)(t2 - t1), rec.level, score, life);

if(delay % ball.waitCount == 0){
ball.X += ball.Dx;
ball.Y += ball.Dy;

if( CollisionDetection(&ball, &bar) == 1 ){
//ボールをバーの位置に。向きなども初期値に戻してlifeを減らす。
bar.shoot = 0;
ball.X = bar.X; //ボールの位置を初期化
ball.Y = bar.Y;
ball.Dx = 1; //ボールの速度ベクトルも初期化
ball.Dy = -1;
loop = 0; //時間も初期化
life--; //ライフを減らす

if(life == 2){
rec.time1 = t2 - t1;
rec.score1 = score;
tmp_score = score;
count = 0;
goto INIT;
}
else if(life == 1){
rec.time2 = t2 - t1;
rec.score2 = score - rec.score1;
tmp_score = score;
count = 0;
goto INIT;
}
else if(life == 0){
rec.time3 = t2 - t1;
rec.score3 = score - rec.score2 - rec.score1;
rec.time = rec.time1 + rec.time2 + rec.time3;
rec.score = score;
/* ゲームオーバーしたら3秒待機して記録を表示 */
printGameOver(rec);
goto RESTART;
}
}
}

//壊れたブロックがないかの判定。そしてそれを壊す
if(breakBlock(&block,ball.X,ball.Y,&ball.Dx,&ball.Dy,&count) == 1){
clear();
mvprintw(LINES/2,COLS/2,"GAME CLEAR");
sleep(5);
return;
}

mvaddch(ball.Y,ball.X,'@'|COLOR_PAIR(2));
}/* ここでwhile文閉じている */
goto END;

/////////////////////////////////////////////
// //
// リスタート待機 //
// //
/////////////////////////////////////////////
RESTART:
/* データの初期化 */
life = 3;
tmp_score = 0;
count = 0;
ball.waitCount = 3000;
score = 0;
rec.level = 1;
freeBlocks(block); //前回のブロックをすべて開放してする。
block = NULL;
bar.X = WALL_R / 2;
bar.Y = LINES - 5;
while(1){
ch = getch();
if(ch == 'r') goto START;
if(ch == 'q') goto END;
}

/////////////////////////////////////////////
// //
// 終了 //
// //
/////////////////////////////////////////////
END:
clear();
return;
}


だんだん雑になってきてただのコピペですが、ゲームの遷移はgotoでコードを行き来してラベルごとにwhile文でループさせてます。そのループの中で毎回loopを+1して、waitCount(初期値:3000)ごとにボールを移動させています。大きいループの中で、loopを+1するだけでなく、キーパッドからの入力した値を常に変数chに格納して、それが十字キーならmoveBar関数の中でバーを動かしたりの処理を、qを押したら強制終了。常に両側の壁やバー、ボールはスペースで上書きして、再描画を繰り返す。。。この繰り返しでブロック崩しを実現しています。


最後にC言語ならではのコンパイル

Makefileで一気にコンパイルしちゃいましょう。


makefile

block:block.o function.o

gcc block.o function.o -o block -lncurses

function.o:function.h function.c
gcc -c function.c -lncurses

block.o:function.h block.c
gcc -c block.c -lncurses


これでそれぞれをコンパイルしてくれます。cursesライブラリはコンパイルのさいに -lncurses を追加しないといけないらしいのでそれだけ忘れずに。



Makefile

LDLIBS := -lncurses

block: block.o function.o
block.o function.o: function.h

clean:
$(RM) *.o
$(RM) block



C言語ならMake使おうよを参考というか丸々引用させていただきました!


どちらのMakefileでもコンパイルは出来るのですが、後者の方が簡潔だしmake cleanで危険性減るしメリット大ですね。

$ make       // makeコマンドでコンパイル

$ ./block // これで実行。端末上にBREAK OUTの文字が出現するはずです。

*** missing separator. Stop.

後者のmakeだとこんな感じのエラーが出るかも知れませんが、その場合は$(RM)の前がタブ1つではなくてスペース4つになってるかもしれないので、タブに置き換えればコンパイルできますb

ざっとこんな感じでシンプルなブロック崩しは出来上がりです。機能はシンプルなので、enchant.jsとかを使って書けば多分100行にも満たないクソブロック崩しですが、以上1年前にC言語でブロック崩しを書かされ書いたはなしでした。話ってよりはただのコードだけど。

ソースコードはGitHubにあるのでここからcloneしたら恐らくすぐに遊べるはずです!もしかしたらcursesライブラリを入れなきゃいけないかも。なんかバグあったら教えてください。以上でした。