ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

C言語とC++言語コミュのプログラムに対する等、アドバイスをお願いします。(ぷよぷよ)

  • mixiチェック
  • このエントリーをはてなブックマークに追加
初めまして、現在独学でC言語を勉強している者です。

まず、苦しんで覚えるC言語(http://9cguide.appspot.com/)などの入門ページを読んで、入門ページくらいの内容ならわかるようになりました。

次にステップアップの為に、
http://www.nhk.or.tv/kow/program/index.php
に掲載されていますテトリスを自分なりに理解して、そこからぷよぷよを作ってみようと挑戦してみました。

ぷよぷよなら
http://www13.plala.or.jp/kymats/study/game_other.html
こちらにお手本が掲載されていたのですが、まだ理解出来ない部分が多くわかりませんでした。
けど、何とか今の知識だけで出来ないものかと挑戦して作ってみました。

プログラムを載せて長くなってしまうのですが、諸先輩方にこうすればいいなどのアドバイスを頂きたく投稿させて頂きました。
mixiの投稿だとtabキーや半角スペースがうまく反映出来なくて、大変読みづらくなってしまいます。申し訳ありません。

なるべく従来のぷよぷよに近いアクションになるようにしてみました。
全くの初心者が作ってみたので、関数のつけ方や変数名などが私独自になっていると思います。
もし、慣例などでこう付けたほうがいいとか、読みやすくなる書き方などあったら是非教えて下さい。


今の時点で疑問に思っていることです。

1,読みづらいプログラムになっているかもしれないと思い、余分にコメントをつけています。
他人にも読みやすいようにコメントはどの程度つければよいのでしょうか?

2,1行のfor文では、{}は省略した方がいいですか?入門ページには省略出来る場合でもつけた方がよいとのことで今回はつけました。

3,main関数は短い方がいいのでしょうか?お手本にさせてもらったテトリスのサイト(http://www.nhk.or.tv/kow/program/index.php)では、
どんどん関数をまとめています。私も、まとめた方がわかりやすいと思う部分はまとめたのですが、まとめすぎるとプログラムが追いづらいと思いました。
もしもmain関数は短い方がいいのなら、理由も教えて頂けると助かります。

4,多少なのですが、省略して書ける場所もあったのですがわかりやすさ(自分があとから見て)を選びました。
おそらく省略しても全体のパフォーマンスには影響ないだろうという場合、それでもいいから若干でも動作が軽い方か、見てわかりやすいプログラムか、どちらを選べばいいのでしょうか?

5,一番悩んだのが、ぷよぷよが4個以上揃っているのを判定するところです。
本プログラムでは、縦か横か4個以上揃っているのと、4個の固まりのパターンを全て(5パターン)検出しそれと隣合うものは4個以上揃っていると判定しました。
もっと簡潔な方法があれば是非教えて頂きたいです。

6,これは興味本位なのですが、もしもある程度C言語が書ける方でしたら、こういう仕様でぷよぷよを作ってくれと言われてどれくらいで出来るものですか?
当たり前ですが、私は相当悩みました。。。


それでは、下記がプログラムです。
ほんの些細なことでもよいので、ばしばし突っ込んで頂いてかまいません。
よろしくお願い致します。

<操作方法>
左移動=s
右移動=f
下移動=d
回転=スペースキー

--------------------------------------------


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>

#define T 14 /*ゲーム画面の縦の幅*/
#define Y 8 /*ゲーム画面の横の幅*/

//グローバル変数
int x; /*ぷよぷよの横軸の位置座標*/
int y; /*ぷよぷよの縦軸の位置座標*/
int gameover; /*1のときはゲームオーバー*/

int stage[14][8]={}; /*ステージの状態、0=空白、1〜5=あ〜お、7=消す候補、9=壁*/
int puyo[3][3]={}; /*ぷよぷよの状態*/
int field[14][8]={}; /*ステージとぷよぷよの状態、0=空白、1〜5=あ〜お、7=消す候補、9=壁*/

//関数プロトタイプ宣言
void Syokika(); /*ゲーム画面の初期化*/
void ShowGameField(); /*画面の表示*/
int CreatePuyo(); /*ぷよぷよの作成*/

int CheckStage(int,int); /*ぷよぷよが移動出来るかのチェック*/
void MovePuyo(int,int); /*ぷよぷよの移動*/
int DropPuyo(); /*ぷよぷよの下に空白があれば移動*/
void RenewStage(); /*ステージを更新(フィールドを代入)*/
void ControlPuyo(); /*ぷよぷよをキーボードで操作、s=左に移動、d=下に移動、f=右に移動、スペースキー=回転*/
void TurnPuyo(); /*ぷよぷよの回転*/

void CheckPuyo(); /*同じぷよぷよで4個以上揃っているものをチェックし空白に変える関数をまとめた関数*/
void CheckFourLine(); /*縦又は横にぷよぷよが4個揃っているかをチェック*/
void CheckKatamari(); /*4個の固まりづつに4個又は3個揃っているものがあるかをチェック*/
/*パターンは5種類(ぷよぷよは●、空白は○)*/
/*|●●|●●|●●|●○|○●|*/
/*|●●|●○|○●|●●|●●|*/

int PatternOne(int,int,int); /*CheckKatamari()で3個揃っているものに隣接する同じぷよぷよを消す候補にする4パターン*/
int PatternTwo(int,int,int);
int PatternThree(int,int,int);
int PatternFour(int,int,int);

void KeshiKouho(int); /*消す候補の上下左右が同じぷよぷよかをチェックする*/
void KeshiPuyo(); /*消す候補を空白にする*/



int main(void)
{
int time=0; //時間調整用の変数

Syokika(); //ステージとフィールドに壁と空白を代入
gameover=CreatePuyo(); //フィールドに新しいぷよぷよを代入
ShowGameField(); //画面にフィールドを表示

while(!gameover){ //gameoverが1になるまでループ(CreatePuyo()でチェックされる)
if(kbhit()){ //キーボードが入力されているかをチェック
ControlPuyo(); //ぷよぷよの操作
}
if(time<20000){ //時間調整
time++;
}
else{
//ぷよぷよが1段下に移動出来るか
if(!CheckStage(x,y+1)){ //移動出来る場合0、出来ない場合1が返ってくる
MovePuyo(x,y+1); //ぷよぷよを1段下に移動
}
//ぷよぷよが1段下に移動出来ない場合
else{
DropPuyo(); //ぷよぷよの下に空白があれば移動
CheckPuyo(); //同じぷよぷよで4個以上揃っているものをチェックし空白に変える
gameover=CreatePuyo(); //フィールドに新しいぷよぷよを代入
ShowGameField(); //画面にフィールドを表示
}
time=0; //時間調整の初期化
}
}
return 0;
}


/*ゲーム画面の初期化*/
void Syokika()
{
int i,j; //forループ制御用の変数
for(i=0;i<T;i++){
for(j=0;j<Y;j++){
if(j==0||j==7||i==13){ //左端、右端、底辺だったら壁
stage[i][j]=field[i][j]=9;
}
else{ //壁以外は空白
stage[i][j]=field[i][j]=0;
}
}
}
}


/*フィールドに新しいぷよぷよを代入*/
int CreatePuyo()
{
int i,j; //forループ制御用の変数
x=(Y-3)/2; //xとyの座標を初期化。xは横幅の中心を初期位置にする
y=0;

//ぷよぷよの初期化
for(i=0;i<3;i++){
for(j=0;j<3;j++){
puyo[i][j]=0;
}
}

//乱数を発生させ、その乱数を5で割った余り+1で1〜5までのぷよぷよの種類を決定
srand((unsigned)time(NULL));
puyo[0][1]=rand()%5+1;
srand((unsigned)time(NULL)+rand()%5); //これで完全なランダムになっているのか??
puyo[1][1]=rand()%5+1;

//初期位置にすでにぷよぷよがある場合はゲームオーバー
if(field[y+1][x+1]!=0){
return 1;
}

//フィールドにぷよぷよを代入
else{
field[y][x+1]=puyo[0][1];
field[y+1][x+1]=puyo[1][1];
}
return 0;
}


/*画面にフィールドを表示*/
void ShowGameField()
{
int i,j; //forループ制御用の変数
system("cls"); //画面をクリアする

for(i=1;i<T;i++){ //1行目は画面外でぷよぷよはあるけど表示させない
for(j=0;j<Y;j++){
switch(field[i][j]){
case 0:
printf(" ");
break;
case 1:
printf("あ");
break;
case 2:
printf("い");
break;
case 3:
printf("う");
break;
case 4:
printf("え");
break;
case 5:
printf("お");
break;
case 9:
printf("■");
break;
}
}
printf("\n");
}
if(gameover==1){
Syokika();
system("cls"); //画面をクリアする
for(i=1;i<T;i++){
for(j=0;j<Y;j++){
switch(field[i][j]){
case 0:
printf(" ");
break;
case 9:
printf("■");
break;
}
}
printf("\n");
if(i==5){
printf("■ GAME OVER ■\n");
i++;
}
}
}
}


/*ぷよぷよが移動出来るかをチェック*/
int CheckStage(int x2,int y2)
{
int i,j; //forループ制御用の変数
for(i=0;i<3;i++){
for(j=0;j<3;j++){
if(puyo[i][j]!=0){ //ぷよぷよがある場合
if(stage[y2+i][x2+j]!=0){ //移動先が空白かをチェック
return 1; //移動出来ないので1を返す
}
}
}
}
return 0; //移動出来るので0を返す
}


/*ぷよぷよの移動*/
void MovePuyo(int x2,int y2)
{
int i,j; //forループ制御用の変数

//フィールドにあるぷよぷよを消す
for(i=0;i<3;i++){
for(j=0;j<3;j++){
field[y+i][x+j]-=puyo[i][j];
}
};

//移動先を代入
x=x2;
y=y2;

//フィールドに移動先のぷよぷよを代入
for(i=0;i<3;i++){
for(j=0;j<3;j++){
if(field[y+i][x+j]==0){ //空白の場合だけ代入
field[y+i][x+j]=puyo[i][j];
}
}
}
ShowGameField();
}


/*ステージにぷよぷよを固定する*/
void LockPuyo()
{
int i,j; //forループ制御用の変数
for(i=0;i<3;i++){
for(j=0;j<3;j++){
if((stage[y+i][x+j]==0)&&(field[y+i][x+j]!=0)){ //ステージが空白かつフィールドにぷよぷよがある場合
stage[y+i][x+j]=field[y+i][x+j];
}
}
}
}


/*キーボード操作*/
void ControlPuyo()
{
char key;
key=getch();
switch(key){
case 'f': //右
if(!CheckStage(x+1,y)){
MovePuyo(x+1,y);
}
break;
case 's': //左
if(!CheckStage(x-1,y)){
MovePuyo(x-1,y);
}
break;
case 'd': //下
if(!CheckStage(x,y+1)){
MovePuyo(x,y+1);
}
break;
case ' ': //左回りに90度回転
TurnPuyo();
}
}


/*ぷよぷよを左回りに90度回転*/
void TurnPuyo()
{
int i,j; //forループ制御用の変数
int temp[3][3]={0}; //ぷよぷよの回転する前を一時的に保存する配列

//回転する前を保存
for(i=0;i<3;i++){
for(j=0;j<3;j++){
temp[i][j]=puyo[i][j];
}
}

//ぷよぷよを90度回転させる
for(i=0;i<3;i++){
for(j=0;j<3;j++){
puyo[i][j]=temp[2-j][i];
}
}

//回転出来るかチェック
if(!CheckStage(x,y)){
//回転出来るのでフィールドにあるぷよぷよを消す
for(i=0;i<3;i++){
for(j=0;j<3;j++){
field[y+i][x+j]-=temp[i][j];
}
}

//フィールドに回転したものを代入
for(i=0;i<3;i++){
for(j=0;j<3;j++){
if(field[y+i][x+j]==0){ //空白にだけ代入
field[y+i][x+j]=puyo[i][j];
}
}
}
ShowGameField();
}

//回転出来ないのでぷよぷよの状態を戻す
else{
for(i=0;i<3;i++){
for(j=0;j<3;j++){
puyo[i][j]=temp[i][j];
}
}
}
}


/*同じぷよぷよで4個以上揃っているものをチェックし空白に変える*/
void CheckPuyo()
{
int i; //forループ制御用の変数
int flag=0; //ぷよぷよの下に空白があるかのチェック変数

while(!flag){ //DropPuyoが実行されなかったらループから抜ける

//ぷよぷよ5種類をチェックする( 1=あ、2=い、3=う、4=え、5=お)
for(i=1;i<=5;i++){
CheckFourLine(i); //フィールドで縦か横に4個揃っているものがあるかチェック
CheckKatamari(i); //フィールドで4個固まりづつ、4個又は3個揃っているものがあるかチェック
KeshiKouho(i); //消す候補の上下左右が同じぷよぷよかをチェックし、あれば消す候補にする
KeshiPuyo(); //フィールドの消す候補を空白にする
}

//ぷよぷよの下に空白があれば移動する
if(DropPuyo()){
CheckPuyo();
}
else{
flag=1; //全てのぷよぷよの下に空白がない
}
}
RenewStage(); //ステージを更新する(フィールドを代入する)
}


/*縦又は横に4個揃っているものがあるかをチェック*/
void CheckFourLine(int key)
{
int i,j; //forループ制御用の変数

//縦に4個並びがあるかをチェック
for(j=1;j<Y-1;j++){ //壁は除く
for(i=1;i<T-4;i++){ //底から更に3引いたところまでをチェック
if((field[i][j]==key)&&(field[i+1][j]==key)&&(field[i+2][j]==key)&&(field[i+3][j]==key)){
field[i][j]=field[i+1][j]=field[i+2][j]=field[i+3][j]=7;
i+=3; //チェックを進める
}
}
}

//横に4個並びがあるかをチェック
for(i=1;i<T-1;i++){ //底辺は除く
for(j=1;j<Y-4;j++){ //右壁から更に3引いたところまでをチェック
if((field[i][j]==key)&&(field[i][j+1]==key)&&(field[i][j+2]==key)&&(field[i][j+3]==key)){
field[i][j]=field[i][j+1]=field[i][j+2]=field[i][j+3]=7;
j+=3;//チェックを進める
}
}
}
}


/*4個の固まりづつに4個又は3個揃っているものがあるかをチェック*/
void CheckKatamari(int key)
{
int i,j; //forループ制御用の変数

for(i=1;i<T-2;i++){ //底辺から更に1引いたところまでをチェック
for(j=1;j<Y-2;j++){ //右壁から更に1引いたところまでをチェック

//4個が揃っているパターン
//●●
//●●
if((field[i][j]==key)&&(field[i][j+1]==key)&&(field[i+1][j]==key)&&(field[i+1][j+1]==key)){
field[i][j]=field[i][j+1]=field[i+1][j]=field[i+1][j+1]=7;
}

//3個が揃っているパターン1
//●●
//●○
else if((field[i][j]==key)&&(field[i][j+1]==key)&&(field[i+1][j]==key)){
field[i][j]=PatternOne(j-1,i-1,key);
}

//3個が同じパターン2
//●●
//○●
else if((field[i][j]==key)&&(field[i][j+1]==key)&&(field[i+1][j+1]==key)){
field[i][j]=PatternTwo(j+2,i-1,key);
}

//3個が同じパターン3
//●○
//●●
else if((field[i][j]==key)&&(field[i+1][j]==key)&&(field[i+1][j+1]==key)){
field[i][j]=PatternThree(j-1,i+2,key);
}

//3個が同じパターン4
//○●
//●●
else if((field[i][j]!=key)&&(field[i][j+1]==key)&&(field[i+1][j]==key)&&(field[i+1][j+1]==key)){
//ここだけfield[i][j]が空白なので、戻り値を受けるのをfield[i][j+1]にしている
field[i][j+1]=PatternFour(j+2,i+2,key);
}
}
}
}


/*CheckKatamari()で3個揃っているものに隣接する同じぷよぷよを消す候補にする*/
/*パターン1*/
int PatternOne(int x2,int y2,int key)
{
int i=0,j; //forループ制御用の変数
for(j=1;j<4;j++){
if(j==3){
i++;
}
if((field[y2+i][x2+j]==key)||(field[y2+j][x2+i]==key)){
return 7; //消す候補の値である7を返す
}
}
return key; //元の値を返す
}

/*パターン2*/
int PatternTwo(int x2,int y2,int key)
{
int i=0,j; //forループ制御用の変数
for(j=-1;j>-4;j--){
if(j==-3){
i++;
}
if((field[y2+i][x2+j]==key)||(field[y2-j][x2-i]==key)){
return 7; //消す候補の値である7を返す
}
}
return key; //元の値を返す
}

/*パターン3*/
int PatternThree(int x2,int y2,int key)
{
int i=0,j; //forループ制御用の変数
for(j=1;j<4;j++){
if(j==3){
i--;
}
if((field[y2+i][x2+j]==key)||(field[y2-j][x2-i]==key)){
return 7; //消す候補の値である7を返す
}
}
return key; //元の値を返す
}

/*パターン4*/
int PatternFour(int x2,int y2,int key)
{
int i=0,j; //forループ制御用の変数
for(j=-1;j>-4;j--){
if(j==-3){
i--;
}
if((field[y2+i][x2+j]==key)||(field[y2+j][x2+i]==key)){
return 7; //消す候補の値である7を返す
}
}
return key; //元の値を返す
}


/*消す候補(field[][]=7)の上下左右が同じぷよぷよかをチェックする*/
void KeshiKouho(int key)
{
int i,j; //forループ制御用の変数
int ido=0; //上か左に移動するかをチェックする変数

for(i=1;i<T-1;i++){
for(j=1;j<Y-1;j++){
if(field[i][j]==7){ //フィールドの7は4個以上揃っているものなので、7と隣あっている同じぷよぷよは7にする
if(field[i][j-1]==key){ //1個左が7かチェック
field[i][j-1]=7;
ido=1; //左に移動フラグ
}
if((i>1)&&(field[i-1][j]==key)){ //1個上が7かチェック
field[i-1][j]=7;
ido=2; //上に移動フラグ(左に移動より上に移動する方が優先なので、左チェックの後にチェックしている)
}
if(field[i+1][j]==key){ //1個下が7かチェック
field[i+1][j]=7;
}
if(field[i][j+1]==key){ //1個右が7かチェック
field[i][j+1]=7;
}
}

//左に移動するか
if(ido==1){
j-=2;
ido=0; //移動フラグの初期化
}
//上に移動するか
if((ido==2)&&(i>1)){ //上に移動かつ縦が画面外(0)じゃない場合
i--; j--;
ido=0; //移動フラグの初期化
}
}
}
}


/*消す候補(field[][]=7)を空白にする*/
void KeshiPuyo()
{
int i,j; //forループ制御用の変数
for(i=1;i<T-1;i++){ //底辺は除く
for(j=1;j<Y-1;j++){ //壁はのぞく
if(field[i][j]==7){
stage[i][j]=field[i][j]=0; //空白を代入
}
}
}
}


/*ぷよぷよの下に空白があれば移動する*/
int DropPuyo()
{
int i,j,k; //forループ制御用の変数
int flag=0; //ぷよぷよの移動があるかのチェック変数
int time; //時間調整用の変数
for(i=0;i<T-2;i++){
for(j=1;j<Y-1;j++){
time=0; //時間調整の初期化
if((field[i][j]>0)&&(field[i+1][j]==0)){ //ぷよぷよの下に空白があるか
for(k=i;k>=0;){
flag=1; //一度でも移動があったら1を代入
field[k+1][j]=field[k][j]; //ぷよぷよを1個下の空白に移動
field[k][j]=0; //移動したぷよぷよのもとの位置に空白を代入
k--; //縦を1段戻る
if(field[k][j]==0){ //移動したぷよぷよのもとの位置の一段上にブロックがあるか
ShowGameField(); //ゲーム画面を空白に移動した状態にする
if(time<200000){ //時間調整
time++;
}
else{
break;
}
}
}
}
}
}
return flag; //ぷよぷよの移動があれば1,なければ0を返す
}


/*ステージを更新する*/
void RenewStage()
{
int i,j;
for(i=0;i<T-1;i++){
for(j=1;j<Y-1;j++){
stage[i][j]=field[i][j];
}
}
}


--------------------------------------------

コメント(29)

入門課題をマスターした次にぷよぷよってのは一気にレベルアップしすぎ。
同じゲームでも、五目並べとかオセロとかみたいな描画/入力タイミング制約の緩い,非リアルタイムゲームあたりがよろしいかと。

お絵かきロジックあたりどうですか?それでもかなり難しいけど。
まあこのくらい書ければ概ねオッケーじゃないかな、くらいのコードに見えますけれど。気になったのは、

・グローバル変数の x/y にもう少し長い名前を付けた方がいい。
・グローバル変数は static にしてファイルローカルにした方がいい。
・main 以外の関数も static にしてファイルローカルにした方がいい。
・マジックナンバ的な 3 (4-1 なんでしょう)とか5(ぷよぷよの数)もシンボルにした方がいい。

というあたり、です。
>>1 saitohさん
何をしたらいいのかわからず、同じ系統で落ちゲーならテトリスを拡張すれば出来るかなと思い作ってみました。
実際考え方のほとんどはテトリスと同じになってしまってます…

お絵描きロジックはやりがいありそうですね。挑戦したいと思います。
ありがとうございます。
>>2 ジャンリュック藤田さん
static というのがわからなくて調べたのですが、
『変数や関数が有効となる範囲をファイル内にとどめて、そのファイルに対してローカル(プライベート)にしたい場合に用います。』とのことですが、
想像ですが static にするというのは、今後拡張したときなどに意図しない改変が起こらないように他ファイルからアクセス出来ないようにするという理由でしょうか?(的外れな質問だったらごめんなさい。)

なるほど。変更の可能性があるものは#define で指定した方がいいということですね!
ありがとうございます。
4> static にするというのは、今後拡張したときなどに意図しない改変が起こらないように他ファイルからアクセス出来ないようにするという理由

それもありますが、ソースファイルが複数ある場合に、static なシンボルは他のファイルで使われている可能性を考慮する必要が無い、というのが大きいと思います。見る範囲が限定出来るということです。

また、シンボルがグローバルになっていると、グローバルに使われている他のシンボルと名前がかぶってしまう可能性もあります。有効範囲が自分が書いたファイルの中だけに留まっていれば、そういうことは起きません。

変数や関数は、出来るだけ狭いスコープで有効になるように宣言する習慣を早いうちから身に付けた方がいいと思います。


4> 変更の可能性があるものは#define で指定した方がいい

ということもありますが、数字が直接書かれているとコードを読む時に記憶に余計な負荷が掛かるんです。「この3はこの配列の大きさで、この3はあの数字の範囲の下限で ...」みたく、いちいちその意味するところを思い出さないといけません。分かり易い名前がついていれば、これが楽になります。

また、名前を付ける方法は define に限りません。C では整数にしか使えませんが、enum (列挙型)を使って宣言すれば C のスコープルールに従ったシンボルとして定数を宣言できます。名前の有効範囲を出来るだけ必要最小限の範囲にとどめるためには、enum を使った方がベターです。特に、関数の中だけで有効な定数に名前を付けたいときは enum の方が重宝します。


.... 私、トピ冒頭文で書かれている質問には一個も答えてないですね ^^;

コーディングスタイルについて書いている記事はネットにも山ほどありますから、そういうのを読んで知識を増やすのがいいように思います。流派がいくつかありますが、それぞれ根拠はあるものですから、自分で性に合うと思うやり方を選べばいいでしょう。

> 3,main関数は短い方がいいのでしょうか?

mainに限らず、関数は、一行のコメントで機能を書き表せるように定義する、のがいいと思います。その点、ご提示のプログラムはなかなかいい線行っているように見えます。この指針に従えば結果としてそれほど長くなることは無いでしょう。また、1つの機能をただ長いからという理由だけで二つ以上の関数に無理に分ける必要は無いと私は思いますが、長すぎると感じるようなら機能分割が不完全なのかも知れません。

まあぜひ他の方の意見も聞いてみて下さい。
>>5 ジャンリュック藤田さん
enum はその様な使い方に使えるんですね。
enum もそうですが、いろいろな命令文の有用な使い方がいまいちわからないんですよね。
解説を読んでてもこんなの何に使うんだ?ってその時は思い、あとで他の方が書いたコードを読んで、
『おお、こんな使い方をするんだ。』って驚くばかりです。

ご丁寧に回答ありがとうございます。
今の段階だとわからない言葉も多くて、基本的な知識が足りないと感じました。
もう少し基本を勉強したいと思います。
素晴らしい。
あまりわからなくても、このようにまず作ってみるのが上達の近道だと思います。

趣味でプログラムをたまにする程度の素人ですが、ざっとみた限りの個人的な意見を書きます。
・英語名や日本語名など名前の付け方が統一されていなくて見にくい。統一した方がいいかも
・関数をいくつかの分類に分けて、分類毎に別ファイルにまとめた方がいいかも
 その分類でのみ使用するグローバル変数をstatic宣言して使います。
・コメントの付け方ですが、人それぞれだと思います。
 僕は関数の宣言だけを記したヘッダファイルに関数の使い方についての説明を詳細に書きます。
 このコメントには関数の中身のコードの記述よりも時間をかけるほどです。
 関数の中ではあまりコメントを書きません。
 関数は中身を見なくても使い方がわかることが重要だという考えです。
・関数で分けすぎると順に追っていくのが大変だというのは、分け方が良くないだけだと思います。
 上のように関数の中身を見なくてもその関数が使えるようにしておけば、
 そもそも順に追う必要はないためです。
 個人的には「いかに整理するか」がプログラムの真髄のような気がします。
はじめまして。
僕も長年プログラマをやっているわけではないで、
実際にやってみての感想程度のことを述べさせていただきます。

>1.
コメントは多すぎても見る側の注意が散漫になるのでよくありません。
もちろん少なすぎるのも意味がわからないことになりますが、
その点、トピ主さんのコードはちょうどいいコメント量だと思います。
この「ちょうどよい」というのは結局は経験以上でも以下でもない点があります。
一番良い判断方法は3ヶ月後に自分の書いたコードを読むことです。
プログラマの格言としてよく「三ヶ月後の自分は他人」というものがありますが、
三か月後にもなると普通はコードの細かいところなどは忘れてしまうということです。
ですのでどういったコメントはいらんのか、もしくはすごく重要なのかを
実感できると思います。

>2.
基本的にどちらでもよいです。
有名な論点は2点あります。
一つはぶらさがり if 問題というものです。
{}でちゃんと区切ってやらないと if else の論理文脈についてバグが生じやすいという
話があります。(細かいコード例などはここではしませんが「ぶらさがりif」などで検索かけてみてください。)
余談ですが、 python ではそういったミスがおきないようにインデントによる矯正ルールが
取られています。

もう一点は無駄な行を減らしたいという点です。
if(condition)
{
statement;
}
といった書き方をすると「意味のある文字の少ない行」が生じますが
慣れてくるとこういった行はかなりうっとうしく感じたりします。

だいたいこの2点ですがこの話題は昔から論争が絶えないので、別に現場で矯正されない限りは
好きなように書けばいいと思います。

>3.
一般には一つの関数の行数が短い方がよいとされています。
ただ個人的には c 言語の場合には無駄なアクセサ関数を作ったりするのはやり過ぎなように思っていたりします。無理やりな関数名をつけてもコードの可読性を落とすことになりますし、
そういった場合にはべたなコードを張りつけたりした方が可読性はあります。
もちろんあまりに多くの場所で同じようなことをしている場合には
まとめておいた方がよいです。
「変更」に対して一か所で済む、単体テストを組むことで安心して変更を反映させることができるなどの
大きなメリットがあります。
(ここに可読性と変更簡易性のトレードオフがある。)
僕は方針として最初の直感でまとめられると感じられない場合にはまとめる必要はなく、まとめる必要が出た場合にまとめるようにしています。
(これは多分よい方法ではないですがコツがわかるまでは仕方ないのかなと半分あきらめてたりします。)

>4.
昔に比べると最近は可読性重視というのがいわれているらしいです。僕もそう思います。
速度を上げることは可読性のあるコードなら十分できますが、最適化されたコードの可読性をあげることは基本的に不可能です。

>5.
すいません。このコードをまだ読んでいません。
読んでからまたコメントさせてください。

>6.
どうなんでしょうか。。。こういうリアルタイム処理は僕は慣れていないので
2週間くらいは欲しいところですけど。
慣れているプロの方なら 1日あれば余裕でできそうな気はします。
少なくともゲームプログラマーとしてやっていこうという人ならばそれくらい要求されることは覚悟した方がいいと思います。

最後に一言だけ。
ここに書いてあるコードはページの都合でこういうインデントになっているのかもしれませんが、
インデントは読みやすさにおいてかなり重要だということは指摘させてください。

長々と失礼しました。
> {}でちゃんと区切ってやらないと if else の論理文脈についてバグが生じやすいという

そんなものちゃんとindentを通していれば間違えようがない。
習慣を構築する根拠としては不適切。
10> そんなものちゃんとindentを通していれば間違えようがない

知らない人のために出しゃばり解説すると、indent というのは自動でインデントしてくれる Unix 由来のツールです。ソースプログラムは必ずそれ(もしくは同類の整形ツール)を通す習慣にしておけば、中括弧をいちいちつける習慣にしなくても間違えようがない、ということだと思います。

# よね?
どういたしまして。

ちなみに私はインデントはエディタ任せです。
ぷよぷよにトライするあたり、
ゲームプログラミングに興味がおありでしょうか?


ソースコードの書き方に関しては概ねよいと思います。
多少気になる点もありますがそれについては皆様が既にコメントして下さっています。

設計寄りの部分で指摘させていただくならば、
・ ぷよを消すのに必要な連結数を容易に変えられるか?
・ ぷよの種類を容易に増やせるか?
この辺りに気を払うとよいかもしれません。
ゲームというのは組みあがってから数字やら仕様やら変わりまくるものなので
(たとえ仕様を自分で考えていたとしても)。

連結のカウント方法については、
多少理解が難しくてもご自身でリンクを張られているページを参考にするとよいでしょう。
ややトリッキーな面もありますが、
この仕様の上であれば最もシンプルでよい方法に見えます。
「再帰」という、言語に関係なくプログラミング上重要な概念が使われています
(それゆえ難しいのでしょうが)ので、
頑張って理解する価値はあると思います。


ともあれ、これ以上に複雑なプログラムを組むのであればポインタ/構造体あたりを理解すべきだと思いますので、積極的に使ってみることをお勧めします。
//void CheckFourLine();
//void CheckKatamari();
void CheckFourLine(int);
void CheckKatamari(int);
とお行儀よくしましょう。

//char key;
//key=getch();
  int key;
  key=getch();
ですね。

参考までですが、私が書くとしたらこんな感じかな。
(完成させていません、イメージです)
typedef struct tag_GAMEFIELD {
  int stage[HEIGHT][WIDTH]; /* ステージの状態 */
  int field[HEIGHT][WIDTH]; /* ステージとぷよぷよの状態 */
} GAMEFIELD;
typedef struct tag_PUYO {
  POINT pt; /* ぷよぷよの位置 */
  STATE st; /* ぷよぷよの状態 */
} PUYO;

void PlayPuyo(void)
{
  GAMEFIELD Gf = {0};
  PUYO Py = {0};
  bool bNeedShow = false;
  
  InitGameField(&Gf);
  while (CreatePuyo(&Gf, &Py)) {
    bNeedShow = true;
    for (;;) {
      if (bNeedShow) {
        ShowGameField(&Gf, &Py);
        bNeedShow = false;
      }
      if (CatchPlayerAction()) {
        // Player action (KB 入力) があれば
        bNeedShow |= MovePuyoByPlayerAction(&Gf, &Py);
      }
      if (IsActionTiming()) {
        // アクションタイミングになったら
        if (IsMovableSituation(&Gf, &Py)) {
          // 1段下に移動できれば移動する
          bNeedShow |= MovePuyoBySituation(&Gf, &Py);
        } else {
          // ぷよぷよの昇華にトライ
          bNeedShow |= TrySublimePuyo(&Gf, &Py);
          break;
        }
      } else {
        Sleep(SLEEP_TIME);
      }
    }
  }
  ShowGameOver(&Gf);
}
>>7 イシミヤ@トミーさん
コメントありがとうございます。
同じようなfor文が多いので for(;;) for(;;) for(;;) 処理; とまとめてすっきりさせられそうですね。
あと、後述されてます#defineも使い方次第でいろいろ出来そうですね。
>疑問5
再帰もまだわからないという理由で避けてしましました。勉強したいと思います。
>>8 まささん
コメントありがとうございます。
確かに英語と日本語混じってます…。
基本的に英語で付けた方がいいんだろうな、と思いつつも英語のみにするとなんだかわからなくなるんだろうなとの葛藤の末、中途半端な結果になってしまいました。
気を付けたいと思います。

>関数は中身を見なくても使い方がわかることが重要だという考えです。
そうですね。何のために関数を使うかということを意識して書いてみたいと思います。
>>9 トニさん
コメントありがとうございます。
「三ヶ月後の自分は他人」というのは面白いですね。
自分しか見ないと思って自由に書いていたので、あとあと苦労するところでした…。

インデントの件、申し訳ありません。
mixiの投稿の仕様でしょうか、tabと半角スペースの連続が全部消されてしまうみたいなんですよね…。全角スペースで見栄えだけ揃えるか、、、それだともしもコピーして実行する際不都合があるか、、、などと迷いました。
>>14 NKDさん
コメントありがとうございます。
特にゲームだけを作りたいということではなくて、ゲームに限らず思い通りにいろいろ作れるようになりたいと思ってます。そこで、なんとなく取っ付き安そうなもので手始めとしてぷよぷよに挑戦してみました。

まずは、if文やfor文などの初級の一山目くらいは登れたと思うので、ポインタ、構造体、再帰が次の山になりそうですね。勉強したいと思います。
>>Sahmaroさん
具体的なコードを記載して下さり、ありがとうざいます。
まだわからないものが多いですね…。ただこう具体的に書いて頂いて大変勉強になります。
調べながら読み解いて吸収したいと思います。
ありがとうございます。
> 15

そうか、C の場合は

int CreatePuyo(); /*ぷよぷよの作成*/

じゃなくて

int CreatePuyo(void); /*ぷよぷよの作成*/

と宣言しないと、引数の無い関数という意味にはならないんでしたね。


16> 同じようなfor文が多いので for(;;) for(;;) for(;;) 処理; とまとめてすっきりさせられそうですね。

まだ理解できないと思いますけど、この辺は、ループ処理の枠組みを関数に切り出して、実行部を関数ポインタで渡すとすっきり書けそうです。こんな風に。

typdef void (*ProcPtr)(int i, int j);

void DoMatrix(int max_i, int max_j, ProcPtr func)
{
 int i, j;
 for( i = 0; i < max_i; ++i ) {
  for( j = 0; j < max_j; ++j ) {
   (*func)(i,j);
  }
 }
}

int main()
{
 ....
 DoMatrix(T,Y,Syokika);
 DoMatrix(3,3,CreatePuyo);
 ....
}

(この先のお楽しみの)ご参考まで。
10> そんなものちゃんとindentを通していれば間違えようがない

そう言えば、昔・・・・
そのインデントが間違っていて、嵌められた事があったなぁ(^^;
>>21 ジャンリュック藤田さん
ありがとうございます。勉強の参考にさせて頂きます。

トピックの話題からそれてしまいますが、今C言語だけだと実用的なことが出来ないと感じて…C言語でプログラムの基礎概念(?)を理解し、C++を並行して勉強しだしました。


みなさんコメントありがとうございました。
大変参考になりました。
>> 24 お前がす紀田!!さん

コメントありがとうございます。

http://mixi.jp/view_bbs.pl?id=61191119&comm_id=2880
にて、皆様のアドバイスを元に改善しC++で書きなおしたのを投稿させて頂きました。
mixi でのソースコード貼り付け に 全角スペース活用は賛成ですよ。
エディタにコピペして、半角空白文字に置換すればいいだけですからw
>>26.猫山猫宗 さん
ちょっとコードが長いのも問題なのですよね(^^;
短めのコードの質問のときはそうしてみます。ありがとうございます。

>>27.Dの食卓塩 さん
gist 調べて見ました。これすごいですね!

https://gist.github.com/901688

のように利用すればいいのですかね?
保存期間が気になりますが、、、うぷろだを利用するより全然いいですね。

ありがとうございます。

ログインすると、残り3件のコメントが見れるよ

mixiユーザー
ログインしてコメントしよう!

C言語とC++言語 更新情報

C言語とC++言語のメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング