ページ 11

アクションRPGを作るには。

Posted: 2007年2月12日(月) 22:16
by りむ
こんにちは、初めまして。りむと申します。
今、私は、Win32API関数のみを用いた簡単なゲーム作りの次のステップとして、
以前から作りたかったアクションRPGの製作をしようとしています。
そのため、こちらのサイトを参考にさせていただきたいと思っています。

構想の上での特徴としましては、
・マリオのような2Dマップで、攻撃や防御で敵を倒したりアイテムをつかったりして進んでゆく
・攻撃は数値でダメージを表示して倒すアクションRPG風
・ジャンプも出来て段差や高い位置の足場…というように、動きはまさにマリオ風

というものを作りたいのです。

その最初の足がかりとして、マリオの2Dマップを再現しようと、
ゲームプログラミングの館にあるサンプルを参考に
左右移動とその場でジャンプ、までは出来そうです。
ですが、それからの処理がよくわかりません。
「ジャンプして、高い位置の足場に乗る」
「歩いていて段差から落ちて低い位置の地面に降り立つ」
「画面の常時スクロール(自キャラを中心とした画面の位置調整)」
というものが、イメージは出来ても実際の処理、プログラムが想像つきません。。。

よろしかったらアドバイス等お願いします。

Re:アクションRPGを作るには。

Posted: 2007年2月12日(月) 22:35
by バグ
足元に床が有るか無いかの判定をするには、常に見続けていなければいけませんよね。
マリオなんかだと、画面の背景を16×16ドットで構成されたパーツを並べて作っているので、自キャラが表示されている座標と、乗る事の出来るパーツの座標を比較して判断しているんじゃないかと思います。
しかし、いきなりアクションRPGですか…なかなかハードルが高いかもしれませんよ(;^_^A

Re:アクションRPGを作るには。

Posted: 2007年2月12日(月) 23:18
by Justy
@りむさん

 いきなりARPGですか。結構大変だと思いますが、頑張って下さい。


ジャンプして、高い位置の足場に乗る
 これにはまず実際のマップのに関する属性のデータが必要になります。
 属性というのは例えば、プレイヤーがその領域に進入不可能かどうか、破壊可能かどうか、触れるとダメージを受けるかどうか、などの情報です。
 これらの情報とプレイヤーの位置を照らし合わせて、プレイヤーが通過不可能であれば、プレイヤーがその先に進めないようにします。
(属性のデータはバグさんの書かれているとおり、ある程度の領域(例えば 16x16とか 32x32)をブロック化してもっておくといいと思います)

 足場に乗るという処理もこの延長線上にあり、まずプレイヤーに対して常に下向きの「重力」をかけておきます。
 そうするとジャンプした時最初はジャンプによる加速度が重力より高いので、上に飛び上がりますが、直に上向きの加速度が重力によって打ち消され落下を始めるので、落下した先が通過不可能な場所であれば、その上に乗ることができます。


歩いていて段差から落ちて低い位置の地面に降り立つ
 これも同じです。
 常に下向きの重力がかかっていれば、進入不可能な足下の地面がなくなれば、自動的に下に落ちていきます。
 落ちた先が進入不可能な地面なら、その場で落下は止まり立てるはずです。


画面の常時スクロール
 プレイヤーやマップの位置を管理を通常であれば絶対座標系で管理していると思います。
 右へ移動すればX方向が変化し、上下に移動すればY方向が変化するような。

 プレイヤーを画面の中央に配置するには、その絶対座標をプレイヤー中心の相対座標系で考える必要があります。

 といっても2Dなのでさほど難しくはありません。
 プレイヤーがそのマップ上で (5120, 600)の位置にいるのなら、マップはその位置が画面の中央になるように配置すればいいのです。
 画面のサイズが 640x480だとするなら、画面にマップは
(5120 - 640 / 2, 600 - 480 / 2)から (5120 + 640 / 2, 600 + 480 / 2)の領域、つまり (4800, 360)から (5440, 840)のマップを表示すればOKです。


 高度な方法としてはこの処理をシステム化して「カメラ」という概念を取り入れて、カメラはプレイヤーを常に捉えるように動かします。
 で、全てのマップ上に配置されるオブジェクト(プレイヤー、エネミー、アイテムなど)はそのカメラを通して描画(カメラの座標を元に位置を計算)するようにすれば、いろいろと面白いことができると思います。

 カメラをプレイヤの数フレーム前の座標を狙うようにすればカメラがプレイヤーを少し遅れて追っかけるように見えますし、、ステージ開始時とかで、どこか別の場所を映してから素早くカメラがプレイヤーの元に移動するとかの演出などに使えるかと。

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 00:07
by 管理人
これからゲーム作成ですか!わくわくしますね^^

しかし、いきなりアクションRPGですか、難しいでしょうけど、頑張ってください☆

えぇと、マリオとかの場合、1辺が16ピクセルの正方形の集合で出来ているように思います。
ゲームプログラミングの館で「行けない場所を作る」で説明しているとおり、
全ての世界を配列で表し、現在どの配列に属する場所にいるかで、次にいけるかいけないかを判定すればよいように思います。
文字で説明しても難しいと思うので、考え方はゲームプログラミングの館を参考にしてください。
しかし、あれは、1回入力で1区間ずつ歩くので実際あのプログラムのままではいけません。
概念を説明で理解して応用してみてください。

重力に関する事もゲームプログラミングの館に書いてありますので、参考にしてください。
しかしマリオの場合は、9.8m/S^2の重力加速度ではないようです。
ゲーム性に合わせて加速度を色々かえてみましょう。

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 00:08
by 管理人
あ、3人で同じ事言ってしまってる部分ありましたね、よく読まずに書いてしまってごめんなさい^^;

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 00:12
by りむ
早速の返事ありがとうございます。
アクションRPGということですが、まずはアクションを作ってそれからRPG成分を付与していきたいと思っているので、まずはアクションを作成する、ということです。
とりあえず左右移動のみは作ることが出来ました。

>属性というのは例えば、プレイヤーがその領域に進入不可能かどうか、破壊可能かどうか、触れるとダメージを受けるかどうか、などの情報です。
これは、ゲーム基本編の第23・24節の行けない所を作る、の作り方と一緒と考えてよろしいですか?
それに常に「y += 0.98」のように毎フレーム加算するようにし、通行不可能のIDの時は加算されても変化しないようにする…でいいのでしょうか。
あと、属性を持たせる、ということですが、タイルセットの画像に「image[6]は通行不可だ」というような情報を付け加えて、それを背景としてならべればいいのでしょうか。
ただそうなると、画像を分割して読み込む方法でいいのか、その画像に対する情報はどう保持するのか、
いろいろと難しい問題がでてきますね…。

>プレイヤーがそのマップ上で (5120, 600)の位置にいるのなら....
その概念はわかりやすくて理解できました。ということは、マップは最初に画面に入りきらないところも含め全部を読み込んでおく必要がありますね。
となると凄く膨大な配列要素になりそうですね…。

ただその処理をシステム化するというのがよくわかりません。カメラというのはゲームをやる上でどういうものかわかるのですが、それをどう実現するか、なんていうのは、ちょこっとしたゲームしか作ったことのない私では全く想像もつきません。…大変ですね…。

あとひとつ質問したいのですが、Win32APIで作るときのように、
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
LPSTR lpsCmdLine, int nCmdShow)
{
MSG msg;
BOOL bRet;

if (!InitApp(hCurInst))
return FALSE;
if (!InitInstance(hCurInst, nCmdShow))
return FALSE;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (bRet == -1) {
break;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
として、実際の処理はウィンドウプロシージャで行う、と言う形にして、
switch (msg) {
case WM_TIMER:
.......
と言う形で処理を行うのは、DXライブラリを使用する上では、まずいのでしょうか。
個人的には先に学んでいる分、こちらのやり方のほうが慣れているのですが、
もし何か不都合や利点があるなら、こちらにするべきでしょうし…。
こちらも意見をお願いします。

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 00:26
by 管理人
例えば
image[0]はマリオで言う、空の画像(通行可)
image[1]はマリオで言う、山の画像(通行可)
image[2]はマリオで言う、ドラム缶(通行不可)
・・・
などであったとしましょう。

int map[3][10]={
{0,0,0,0,0,0,0,0,0,0},
{0,1,0,0,0,1,0,0,2,0},
{1,1,1,0,1,1,1,0,2,0}};

こんな風にマップの情報を作ったとしますね。
ドラム缶のとこだけ通行できないわけですから、
x%16==0の時、map[(x%16)-1][y%16]==2の時、左にはいけない
と言った感じにすればいいのではないでしょうか。
配列要素がマイナスにならないように注意です。
本当はものすごい量の配列になるはずです。マリオとか配列要素数は何百とかなんじゃないでしょうか(?_?

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 00:35
by 管理人
>それに常に「y += 0.98」のように毎フレーム加算するようにし

これは、重力加速度ですか?
重力は「加速度」であって速度ではないので、常に変わる値なので、この計算方法ではよくないように思います><
これだと等速運動をしてしまうように思います。
まぁ、最初のうちは、簡単な計算式で作ってみるって手もありだとおもいますけど。

詳細の計算式については館をご覧下さい。

>マップは最初に画面に入りきらないところも含め全部を読み込んでおく

読み込んでおくという意味はいろいろにとれてしまう気がします。
読み込んでおくのは配列の情報だけであって、表示や計算は
実際に画面に表示する部分だけでOKです。
マップチップってご存知ですか?
16x16のチップを配列の画像マップ情報にしたがって張っていけばいいのです。

特定の常に変更するカメラ視点から見たゲームを作った事が無いのですが、
常に自分のいる位置を画面の中央か、どこかに固定して、背景や敵をスクロールさせて、
自分が進んでいるように見せればいいのではないでしょうか。
もしも画面の左最大・右最大まできたら、カメラを固定して、そこより左・右に行かないようにすればいいように思います。

もし突然アクションを作るのが難しければ、サウンドノベルゲームやのんびりしたRPGゲームから作って、
ゲームプログラムになれ、
弾の処理や常に動く座標の計算が必要になるシューティングを作ってから、アクションを作ればよりハードルが低いかもしれません。
ガッツでアクションを作る!というのでしたら、頑張ってください^^
マリオが出すファイヤはシューティングゲームの知識が必要ですね☆

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 01:14
by Justy
ゲーム基本編の第23・24節の行けない所を作る、の作り方と一緒と考えてよろしいですか?
 同じと考えていいです。
 上で管理人さんが書かれているような感じで。


行不可能のIDの時は加算されても変化しない
 どういうふうに作っているのかによってちょっと変わってきますが、基本的にはそうです。
 正確には「変化しない」ではなく、「めり込まないように」です。
 変化しないだと宙に浮いたり、透明壁にぶつかったかのようになってしまいますから。


画像を分割して読み込む方法でいいのか
 既に指摘されているようにマップチップを使う手もありますし、そうでないのなら数画面分だけメモリに読み込んで移動するたびに先のマップを動的に読み込んでいくという手もあります。


膨大な配列要素になりそうですね…
 どんな情報を持っておくかとか、どれだけデータを纏めるかとかによっても変わってきますけど、それなりに大きくなりますね。
 数Kバイトとか。

 規模が大きくなったらこういうのは手作業でコツコツと配列を作るようなことはしないで、マップの配置ツールをつくって、その上でマップの絵や属性を設定して事前にデータを出力しておいて、ゲーム本体ではそれを読み込んで使う、という方法が一般的です。
 そこまでやるかどうかは、データ量が手作業で準備できるかどうかにかかってきますが。


マップは最初に画面に入りきらないところも含め全部を読み込んでおく必要がありますね
 最初から全部読んでおく必要はないですよ。
 読み込んだマップデータをどこに配置するのかさえわかっていれば。

ただその処理をシステム化するというのがよくわかりません
 一番簡単な方法だと、単純に「オブジェクトの絶対座標と大きさの情報を与えたら、カメラ位置情報からそのオブジェクトがカメラに写っているかどうか(画面上にあるかどうか)、画面上にあるならその画面上の座標が返ってくる関数」を作れば事足りると思います。
 で、各オブジェクトは画面内にあると判定されたのなら取得した画面座標にオブジェクトを表示する、という感じで。

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 01:20
by りむ
返信ありがとうございます。

>まぁ、最初のうちは、簡単な計算式で作ってみるって手もありだとおもいますけど。
仰るとおり、例として挙げただけの数値として読み飛ばしてしまってください^^;

>16x16のチップを配列の画像マップ情報にしたがって張っていけばいいのです。
RPGツクールは初代からXPまで全て触っているので、どういうものかはよくわかります。
それで作るマップチップ並べ、をプログラムで再現するということですよね?
となると、マップ配列情報map[10] = {0,1,0,2,0,1,3,1,0,0}のようになっていたら、

LoadDivGraph("bat_char_01_right.png", 64, 8, 8, 32, 32, image);

for(i=0;i<10;i++){
get_image = image[map];
DrawGraph( x, y, graph_img, TRUE);
}
のように、分割画像と同じように表示してやればいいのでしょうか。
二次元配列の場合はfor文も二重ですね。

>常に自分のいる位置を画面の中央か、どこかに固定して、背景や敵をスクロールさせて
これぞマリオですね。となるとこれを実現するには、
左右キーを押したら動くものがたくさんになるので処理の把握が大変そうですね…。
敵のスクロールは敵の座標の変化で済むと思いますが、
マップのスクロールはJustyさんの言う
「プレイヤーがそのマップ上で (5120, 600)の位置にいるのなら、マップはその位置が画面の中央になるように配置すればいいのです。
 画面のサイズが 640x480だとするなら、画面にマップは
(5120 - 640 / 2, 600 - 480 / 2)から (5120 + 640 / 2, 600 + 480 / 2)の領域、つまり (4800, 360)から (5440, 840)のマップを表示すればOKです」
というように、プレイヤーの位置を(mx,my)としてそれからマップの表示領域を直接指定すればいいでしょうか?もっと良いやり方等あるでしょうか?

あとマップ情報の地面に関することですが、
int map[3][10]={
{0,0,0,0,0,0,0,0,0,0},
{0,1,0,0,0,1,0,0,2,0},
{1,1,1,0,1,1,1,0,2,0}};
の2の上の0に居る場合も、map[x%16][(y%16)+1]==2の時、下には行けない、とすればいいのでしょうが、
下に向かって常に加速している、とすると、上の条件の場合、加算されないようにする…とすればいいのでしょうか。…うむむ、ちょっとこんがらがってきたので、もう少し考えてみます

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 03:25
by 管理人
現在携帯しかないので沢山かけませんが、ちょこっとだけ。

>map[x%16][(y%16)+1]==2の時、下には行けない、とすればいいのでしょうが、

そうなんですが斜めに移動している時は斜めもみる必要があると思いますし、
1フレームで16ピクセル以上移動する事があるならさらにとなりの配列要素もみてやる必要があります。
なかなか難しいですね。

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 03:40
by 管理人
なお、なぜ私が
「map[(x%16)-1][y%16]==2の時、左にはいけない」
と書かずに
「x%16==0の時、map[(x%16)-1][y%16]==2の時、左にはいけない」
と書いたか、ですが、前者の条件式では、区間の中にいる時まですすめなくなってしまうからです。
もし座標が16の倍数をふまない時、例えば3ずつ座標がふえるような場合はこの条件式ではいけません。
意味がわかるでしょうか?
次の区間がすすめない区間だとしてもすすめる区間の端まではすすめないといけないわけですね。
私も現在、長期休みを利用してゲームを作っています。
今まで一人で遊ぶものばかり作ってたんでオンライン対戦できるボンバーマン作ってます☆
結局ボンバーマンでもいけるとこ、いけないとこの判定は配列で行っていますよ。
現在ボンバーマンのコンピュータの動きをどうプログラムしたらよいか試行錯誤していますw
いや~昔からあるゲームで単純そうに見えるわりには私にはかなり難しいですf^_^;
コンピュータは完璧に絶対しなないように作れるんですが、それではおもしろくなくて、適度にアホな操作をさせる具合がなんとも難しいですw

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 15:54
by りむ
あれから試行錯誤して、基本的に左右移動・ジャンプ&自由落下まで出来ました。
高い足場を作るように、
int map[15][20] = {
{ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 },
{ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1 },
{ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 },
{ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 },
};
として、1が下に来るときは下への加速度を0になるようにしました。

         if(flag == 2){ // 着地した後の硬直時間
t++;
if(muki==1){
graph_img=image[7];
}
if(muki==3){
graph_img=image3[4];
}
if(t>=20){
t=0;
flag = 0;
}
}

if(can_or_cannot(x,y,0,map)==1) vy=1; // 上の壁に当たったら落下開始

if(can_or_cannot(x,y,2,map)==1){
if(flag ==1){
flag = 2; //ジャンプフラグがあれば消去
}
vy = 0; //地面に着地したら速度0
}

if(flag ==0 && can_or_cannot(x,y,2,map)==1 && Key[ KEY_INPUT_UP ] == 1){
flag=1; //地面に着いていてフラグがなくキーが押されたらジャンプ
vy = -12;
}
if (can_or_cannot(x,y,2,map)!=1){
flag=1;
vy += 0.5; //常にジャンプor空中は速度に下向きの重力を加算
}
y += vy;

という感じです。マップの判定には24節の行けない所を作る、のをそのまま使用しています。ですが、
>斜めに移動している時は斜めもみる必要があると...
と言われているように、高いところからの落下したり、高い足場の下側の角に突撃するようにジャンプすると、足場にめり込んでしまいます。
…ううむ、これの回避はななめでみればいいのでしょうか。ただ斜めで見る場合はまた複雑になりますね…。
ちょっとその部分は思いつきませんでした。

あとマップの描画は
          for(j=0;j<15;j++){
for(i=0;i<20;i++){
map_img = image4[map[j]];
DrawGraph( i*32, j*32, map_img, TRUE);
}
}
で出来ました。あとはマップ情報の指定をうまくするだけですね。

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 16:57
by 管理人
斜めの判定はどうすれば最善かわかりませんが、ちょろっと考えて出来そうな設定は

x方向に進んでいる向きをx_mukiとして、
xの正の方向に進んでいる時は1
xの負の方向に進んでいる時は-1
x方向にとまっている時は0

y方向に進んでいる向きをy_mukiとして、
yの正の方向に進んでいる時は1
yの負の方向に進んでいる時は-1
y方向にとまっている時は0

と設定します。

if(map[(y%16)+y_muki][x%16+x_muki]==いける場所)

とすれば、斜めの判定が出来るように思います。
他にいい方法はいくらでもあると思いますが・・。

Re:アクションRPGを作るには。

Posted: 2007年2月13日(火) 17:02
by 管理人
ちなみに、ウィンドウは上がy=0なので、
「yの正の向き」とは下方向の事です。お間違いの無いよう。

着々と出来ていますね、頑張ってください☆

私も私でコンピュータ対戦のアルゴリズムに試行錯誤中です(&#9801;&#3642;_ &#9801;&#3642; )

Re:アクションRPGを作るには。

Posted: 2007年2月14日(水) 00:37
by Justy
 えーと。今後のことも考えるとキャラクタが高速で移動した場合も考慮しないと
いけないかもしれません。

 16x16ピクセルを1つのセル(マップチップ)として属性データを構築していた場合、
1フレームに 16ピクセルより大きな速度でキャラクタが移動した場合、
属性データが進入不可能であっても、壁が壁抜け・地面抜けすることがあります。

 属性データを「分厚く」すればそれを避けることは出来ますが、限界がありますし、
自由なマップ配置が難しくなります。

 なので。
 プレイヤーの前の座標(移動前)を A、新しい座標(移動先)を Bとしたとき、
まずその Aと Bの間に線を引ます。
 Aの側からその線が通過する属性を順番に辿って進入可能かどうかをチェックして、
進めるところまで線を辿っていく、という方法がベストだと思います。

 図ですと、1から順番にヒットするかどうか調べていくと6で黒い進入不可能属性に
ぶつかるので、5まで移動できるということになります。

Re:アクションRPGを作るには。

Posted: 2007年2月14日(水) 03:17
by りむ
>管理人さん
ボンバーマンですか~。楽しいですよね。
友人の先輩もMMORPGを作ってました。まだまだまともなプレイができる段階にも至ってないようですが…。
AIはいずれ私も作らないといけなくなりますね。
敵キャラが出てきたらまた大変な作業になりそうです…。
頑張りましょう><;

斜め判定は右+上同時に判定、という感じってことですね?
その場合の判定式「if(map[(y%16)+y_muki][x%16+x_muki]==いける場所)」は、
従来の判定に追加する形ですか?
その場合は右下や左下の部分の判定の際、左右移動しているときも地面の部分に判定が引っかかりそうな気がするのですが…気のせいならいいのですが。。。


>Justyさん
>今後のことも考えるとキャラクタが高速で移動した場合も考慮しないと
いけないかもしれません。
ええ、すでにこの問題に直面しています…。地面に埋まる埋まる。
ダッシュ機能もつけたいので、早めに回避したいところです。

>プレイヤーの前の座標(移動前)を A、新しい座標(移動先)を Bとしたとき、
まずその Aと Bの間に線を引ます。
 Aの側からその線が通過する属性を順番に辿って進入可能かどうかをチェックして、
進めるところまで線を辿っていく
…理論はよーくわかりました。おっしゃりたい事も理解したのですが、
どうあの図の判定を書けばいいのか…。
上下左右に対して直線的に判定するのなら、ほんの少しイメージは出来るのですが…
よろしければもう少し助言をいただけませんか…?

ちなみにプレイヤーのキャラは64×64ピクセル、マップは32×32ピクセルという変なサイズを使ってます。
(RPGツクールのマップチップ素材がその規格なので)<!--2

学校の課題なんですが

Posted: 2007年3月10日(土) 16:24
by 素人
下記プログラムで生年月日の入出力は,YYYY/MM形式で行い,西暦が数字4桁で,西暦と月の間に'/'があり,月が01~12の範囲の数字の2桁になっている7文字の入力のみ受け付け,最初の4文字が数字で5文字目がスラッシュで6、7文字目で1~12の数字である以外は再入力させたるプログラムです。
やってみたのですが入力はできるのですが判定ができません。
どなたか教えてもらえませんか。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define MAXNAME 11  /* 名前の最大入力文字数 10文字 + '\0' */
#define MYBIRTH 9  /* 生年月日の年の形*/
#define MAXCASE 10 /* 最大ユーザー件数 */

void get_string(char *p_str, int xMaxChar); 

struct user
{
    char Name[MAXNAME];   /* ユーザ名 */
    char MyBirth[MYBIRTH];/* 生年月日*/
};
void main()
{
    struct user s_user[MAXCASE]; 
    int Manth; 
    int i, j = 0; 


    while( j < MAXCASE ){/* データ入力 */
    
        printf("名前:"); 
        get_string(s_user[j].Name, MAXNAME);
        
        
        if ( s_user[j].Name[0] == '0' ) {/* 1文字目が'0'のとき */
            break;
        }
        
        
        if ( s_user[j].Name[0] == '\0' ) {/* 改行のみのとき */
            printf("再入力\n");
            continue;
        }
        
       
        /* 生年月日入力 */
        printf("生年月日:");
         
        get_string(s_user[j].MyBirth, MYBIRTH);
        
        
        

        /* 年の数字チェック */
        for (i = 0; i < 4; i++) {/*  各桁の文字が0~9である*/
            if(  isupper(s_user[j].MyBirth ) != 0 ){
                 
                 
            }
            else{
                printf("数字ではありません\n");
                break;
            }
        }
        
        while( s_user[j].MyBirth[5] == '/'){/*5行目が/である*/
            
            if( s_user[j].MyBirth[6] == '0'){/*6行目が0である*/
                
                if( isupper(s_user[j].MyBirth[7] ) != 0){/*7行目が数字である*/
                    
                }
                else if (s_user[j].MyBirth[6] == '1'){/*6行目が1である*/
                    
                    if( '0' <= s_user[j].MyBirth[7] && s_user[j].MyBirth[7] <= 2){/*7行目が0~2である*/
                        
                    }
                    else{
                        
                        printf("数字ではありません\n");
                        continue;
                }
             }
             
            }
            
        continue;
        }
        
        printf("累計件数 = %d件\n\n", ++j); 
    }
    
    printf("\n入力データ表示\n"); 
    
    
    
    for (i = 0; i < j; i++) {
        printf("%d件目\n", i + 1); 
        printf("名前 = %s\n", s_user.Name); 
        printf("生年月日 = %s\n", s_user.MyBirth);
    }
}

void get_string(char *p_str, int xMaxChar)
{
    int i = 0; 
    int C; 
    
    while ( (C = getchar()) != EOF && C != '\n' ){
        if ( i < xMaxChar - 1 ) {
            *p_str = C; 
            p_str++; 
            i++; 
        }
    }
    *p_str = '\0'; 
}

Re:学校の課題なんですが

Posted: 2007年3月10日(土) 16:56
by バグ
こんにちは(^-^)

まず、確認したい事があるのですが…判定ができないというのは、どのように判定したいのに出来ないという事なのでしょうか?

こういう場合に、現状ではこんな現象が起こっていて、こうしたいのに出来ない。というのを詳しく教えていただけますか?

あ、それから、生年月日の月の判定は、strtol関数を利用されてはいかがでしょうか?
文字列をlong型の数値に変換する関数なので、変換した値が1~12の範囲に納まっていなければ、再入力するようにすれば、スッキリしそうな気がしますよ(^-^)

Re:学校の課題なんですが

Posted: 2007年3月10日(土) 17:27
by box
要するに、ある文字列を"/"で区切って、4桁(1000~9999の範囲)と
2桁(1~12の範囲)の数値を取り出したいわけですから、
strtok関数とstrtol関数あたりを使ってみてはいかがでしょうか。

Re:学校の課題なんですが

Posted: 2007年3月10日(土) 17:29
by 素人
バグ様、返信有難うございます。

生年月日を1234/12と入力しても数字では数字ではありませんと出てしまうのです。それに再入力にもなりません。再入力の仕方もよくわかりません。

123/09や1234/13や1234/111などのYYYY/MM式にあってないときにエラーとして再入力をさせたいのです。

strtol関数の使い方が解りません。MyBirth[6]とMyBirth[7]でも扱えるのでしょうか。

乱文ですいませんが教えていただけないでしょうか

Re:学校の課題なんですが

Posted: 2007年3月10日(土) 18:07
by バグ
ということは、年は必ず4文字、スラッシュは必ず5文字目、月は必ず2文字でないと駄目という事でいいですか?

Re:学校の課題なんですが

Posted: 2007年3月10日(土) 18:45
by 素人
はい。そうです。YYYY/MMの7文字入力するよにしたいのです。

Re:学校の課題なんですが

Posted: 2007年3月10日(土) 20:43
by バグ
まず、年の判定ですが、user構造体のメンバ変数であるMyBirthの[0]~[3]までの4文字が全て数字であった場合にOKという判定になりますよね。という事は、文字が数値であるかどうかを判定する事ができれば解決するはずです。素人さんのソースを見ていると、isupper関数を使われていますが、この関数は英大文字を判定するものですので、今回の件では数字かどうかを判定するisdigit関数を使うのが良いかと思います。

それから、スラッシュ( '/' )の判定ですが、[5]の値を参照していますが、[0]から数えて[4]番目の値になるはずですから、ここも直してください。

最後に月の判定ですが、前の書き込みで紹介しましたstrtol関数を利用するのが良いかと思います。

char *pCheck
nMonth = strtol( &s_user[j].MyBirth[5] , &pCheck , 10 );

使い方はこんな感じです。1番目の引数は調べたい文字列の先頭アドレス、2番目は調べた文字列の最終アドレスが格納されるポインタのポインタになります。3番目は基数となり、今回は10進数ですから10と入力してください。これで、nMonthに文字列からlong型に変更された値が入力されていますので、nMonthの値が1~12でない場合NGとすればいい訳です。

これらの3つの判定が全てOKであれば、この文字列は問題無いと判断できると思いますので、この3つ全てがOKになるまでは再入力を繰り返すようにすればいけるかと思います。

Re:学校の課題なんですが

Posted: 2007年3月10日(土) 20:45
by バグ
>>文字が数値

すみません、『文字が数字』の間違いでした(^_^;)

Re:学校の課題なんですが

Posted: 2007年3月11日(日) 18:41
by 素人
バク様返信ありがとうございます。


>char *pCheck
>nMonth = strtol( &s_user[j].MyBirth[5] , &pCheck , 10 );

なのですが、MyBirth[5] の値がnMonth にlong型に変更された入るとMyBirth[6]番目の値はどうすればいいのでしょうか。

Re:学校の課題なんですが

Posted: 2007年3月11日(日) 18:59
by バグ
strtol関数は指定したアドレスから、数字以外の文字が見つかるまで調べてくれる関数なんですね。

つまり、今回は[5]のアドレスを引数として渡していますので、[5]から調査を開始して[6]、[7]、[8]…と数字以外が見つかるまでどんどんと調べる訳です。

例えば【2007/03】こんな文字列だった場合…

2…[0]
0…[1]
0…[2]
7…[3]
/…[4]
1…[5]
2…[6]
0…[7]

こんな感じで格納されていますから、[5]の'1'から調査を開始して、[7]の'0'で終了する訳です。

Re:学校の課題なんですが

Posted: 2007年3月11日(日) 19:29
by box
> 例えば【2007/03】こんな文字列だった場合…
>
> 1…[5]
> 2…[6]
> 0…[7]

質問者のかたが混乱しそうです。

'0'…[5]
'3'…[6]
'\0'…[7]

Re:学校の課題なんですが

Posted: 2007年3月11日(日) 19:49
by バグ
あら?携帯からだと¥0が0しか表示されないみたいですね…(;^_^A
失礼しましたm(__)m

使ってやってください。

Posted: 2007年3月12日(月) 14:04
by 大阪慕情
#include <stdio.h>
#include <string.h>
#define NUM 7
#define ON 1
#define NO 0
int check(char *cp){
int ia=ZERO;ic=ZERO;
char *kp;
ib=strlen(cp);
kp=cp;
if(strlen(cp)==NUM){
for(;*kp!='\n';kp++){
         switch(kp-cp){
case 0:if(*kp=='1') ia++;break;
case 1:if(*kp=='9') ia++;break;
case 2:if(*kp>='0' && *kp<='9') ia++;break;
case 3:if(*kp>='0' && *kp<='9') ia++;break;
case 4:if(*kp=='/') ia++;break;
case 5:if(*kp=='1') ia++;break;
case 6:if(*kp>='1' && *kp<='2') ia++;break;
default:;break;
}
}
if(ia==7) ic=1;
else ic=ZERO;
}
else ic=ZERO;
return(ic);
}
int main(void){
char *ap;
int ib;
printf("Year?=");scanf("%s",ap);printf("\n");
ib=check(ap);
if(ib=ON) printf("%s\n",*ap);
else return(0);
return(0);
}

Re:学校の課題なんですが

Posted: 2007年3月12日(月) 16:35
by 素人
皆様の意見を参考に組んでみたのですが、12345/12でもエラーにならずに通過してしまいます。
後、処理が全てOKでなら無限ループを抜けたいのですがそれもうまくできていません。
処理の仕方に問題があると思うのですがどこを直せばいいのか検討がつきません。
教えていただけないでしょか。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define MAXNAME 11  /* 名前の最大入力文字数 10文字 + '\0' */
#define MYBIRTH 7  /* 生年月日の年の形*/
#define MAXCASE 10 /* 最大ユーザー件数 */

void get_string(char *p_str, int xMaxChar); 

struct user{
    
    char Name[MAXNAME];   /* ユーザ名 */
    char MyBirth[MYBIRTH];/* 生年月日*/
    
    };

void main(){
    
    struct user s_user[MAXCASE]; 
    int nMonth; 
    int i, j = 0; 


    while( j < MAXCASE ){/* データ入力 */
    
        printf("名前:"); 
        get_string(s_user[j].Name, MAXNAME);
        
        
        if ( s_user[j].Name[0] == '0' ) {/* 1文字目が'0'のとき */
            break;
        }
        
        
        if ( s_user[j].Name[0] == '\0' ) {/* 改行のみのとき */
            printf("再入力\n");
            continue;
        }
        
       while(1){
        /* 生年月日入力 */
        printf("生年月日:");
         
        get_string(s_user[j].MyBirth, MYBIRTH);
        
        
        

        /* 年の数字チェック */
        for (i = 0; i < 4; i++) {/*  各桁の文字が0~9である*/
            if(  isdigit(s_user[j].MyBirth ) != 0 ){
                 
                 
            }
            else{
                printf("数字ではありません\n");
                break;
            }
        }
        
        while( s_user[j].MyBirth[4] == '/'){/*5行目が/である*/
            
            char *pCheck;
            nMonth = strtol( &s_user[j].MyBirth[5] , &pCheck , 10 ); 
            
            if( 1 <= nMonth || nMonth <= 12){
                
                /*無限ループ抜ける処理がしたいです*/
            }
            
        //continue;
        }
    
        printf("累計件数 = %d件\n\n", ++j); 
    }
    
    printf("\n入力データ表示\n"); 
    
    
    
    for (i = 0; i < j; i++) {
        printf("%d件目\n", i + 1); 
        printf("名前 = %s\n", s_user.Name); 
        printf("生年月日 = %s\n", s_user.MyBirth);
    }
}

void get_string(char *p_str, int xMaxChar){
    
    int i = 0; 
    int C; 
    
    while ( (C = getchar()) != EOF && C != '\n' ){
        if ( i < xMaxChar - 1 ) {
            *p_str = C; 
            p_str++; 
            i++; 
        }
    }
    *p_str = '\0'; 
}

Re:学校の課題なんですが

Posted: 2007年3月12日(月) 16:55
by バグ
while( s_user[j].MyBirth[4] == '/'){/*5行目が/である*/


この行は何を見ているのでしょうか?(^_^;)
もし、スラッシュかどうかの判定をしたいのであれば…

if( s_user[j].MyBirth[4] == '/' )
{
// OK時の処理
}
else
{
// NG時の処理
}

こんな感じでいけると思いますよ(^-^)

Re:学校の課題なんですが

Posted: 2007年3月12日(月) 17:18
by box
> #define MYBIRTH 7  /* 生年月日の年の形*/
> 
>     char MyBirth[MYBIRTH];/* 生年月日*/

"YYYY/MM"の形式のデータを格納するには1バイト不足しています。
終端の'\0'の分を追加して、8バイト確保してください。

Re:学校の課題なんですが

Posted: 2007年3月12日(月) 17:25
by 素人
バグ様、早い返信ありがとうございます。

while( s_user[j].MyBirth[4] == '/'){/*5行目が/である*/

char *pCheck;
nMonth = strtol( &s_user[j].MyBirth[5] , &pCheck , 10 );

if( 1 <= nMonth || nMonth <= 12){

/*無限ループ抜ける処理がしたいです*/


この処理で5行目が/の場合、月の処理が1~12かを調べることをさせたいのですが、おかしいのでしょうか。

Re:学校の課題なんですが

Posted: 2007年3月12日(月) 17:51
by バグ
if( 1 <= nMonth || nMonth <= 12)

この判定文がおかしいです。

nMonthが1より小さい、もしくは、12よりも小さい場合に真となっています。つまり、1以下でないと真となりません。

今回の場合でしたら、1以上でなおかつ、12以下の場合に真としなければいけませんよね?

Re:学校の課題なんですが

Posted: 2007年3月12日(月) 18:33
by 素人
返信ありがとうございます。

if( nMonth >= 1 && nMonth <= 12) と直しましたが12345/12でもエラーになりません。

5行目が/ではないの何故エラーにならないのでしょうか。

Re:学校の課題なんですが

Posted: 2007年3月12日(月) 19:56
by むつ
 以下のソースを掲示する事で混乱してしまうかもしれませんが、
頭のリフレッシュと思ってください。
 構造体に格納する前に入力文字のチェックを済ませておきたいので
キーを押した段階で入力チェックしています。(西暦のチェックは省略しています)
使用コンパイラ:Borland C++Compiler 5.5
// ============================================================================
// 年月入力判定 part.2
//
// ※入力を受け付けない場合、ウィンドウを閉じる
// ============================================================================
#include <stdio.h>
#include <conio.h>

int main( void )
{
	int num, key;
	char birthday[ 8 ];
	// 変数の初期化( num = 0は1番目の入力を指す )
	num = 0;
	printf( "When is your birthday?(YYYY/MM) " );
	while( num < 7 ) {
		// 0~9 または / 入力  キーコードをkeyに代入
		key = getch();
		//  case num: 何番目の入力か
		switch( num ) {
		// '/'の入力
		case 4:
			if( key == 47 ) {
				birthday[ 4 ] = putchar( key );
				num++;
			}
			break;
		case 5:
			// 0と1以外はスルー
			if( key == 48 || key == 49 ) {
				birthday[ 5 ] = putchar( key );
				num++;
			}
			break;
		case 6:
			// 1~9月の場合
			if( birthday[ 5 ] == 48 ) {
				if( key > 48 && key <= 57 ) {
					birthday[ 6 ] = putchar( key );
					num++;
				}
			}
			// 10~12月の場合
			if( birthday[ 5 ] == 49 ) {
				if( key >= 48 && key <= 50 ) {
					birthday[ 6 ] = putchar( key );
					num++;
				}
			}
			break;
		// 0~9の入力
		default:
			if( key >= 48 && key <= 57 ) {
				birthday[ num ] = putchar( key );
				num++;
			}
		}
	}
	printf( "\nYour birthday is.. %s\n", birthday );
	return 0;
}

Re:学校の課題なんですが

Posted: 2007年3月13日(火) 13:07
by 素人
むつさんソースありがとうございます。
でも、結構混乱しています。
やりたいことは、格納してからのチェックなのです・・・。
チェックしていく流れは少しわかりました。ありがとうございます。

Re:学校の課題なんですが

Posted: 2007年3月13日(火) 19:15
by 素人
先生に課題で入出力以外のライブリ関数以外の仕様を禁止されてしまいました。
また始めの段階に戻ってしまいました。全てのチェックにおいて正しくないかを判定し再入力をするように変更したいです。

チェック項目は

1.入力された文字が7文字でない

2.配列のi番目の文字が数字でない

3.配列の0~3番目の文字に不正な文字が含まれていたらエラー

4.配列の4番目の文字がスラッシュでない

5.配列の5,6番目の文字が00~12でない


誰か書き方をご教授お願いします。

Re:学校の課題なんですが

Posted: 2007年3月14日(水) 11:19
by Yuki
とりあえず、どんな状況かわからないので、
今できているソースを載せてみてください。

うまくいかないところ等も具体的に書いてもらえると、
アドバイスしやすいです。

Re:学校の課題なんですが

Posted: 2007年3月14日(水) 22:30
by 素人
Yuki様、回答ありがとうございます。

今できているのはこのような感じです。
生年月日の再入力のところでcontinue文を使ってしまったため名前の再入力まで戻ってしまいます。
これを生年月日だけの再入力にしたいです。

7文字入力してもエラーになってしまいます。57行目でif(i <= 8 )が間違っていると思うのですがよくわかりません。


月の判定でが00~12までか判定仕様として6、7文字目が数字の文字でないかどうかをチェックすると同時に数値化を行い、そのあとで、数値化した値が、0~12までかどうか判定したいのですが、ここはまったく書き方がわかりません。

乱文ですいませんが、よろしくおねがいします。

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

#define MAXNAME 11  /* 名前の最大入力文字数 10文字 + '\0' */
#define MYBIRTH 9  /* 生年月日の年の形*/
#define MAXCASE 10 /* 最大ユーザー件数 */

void get_string(char *p_str, int xMaxChar); 

struct user{
    char Name[MAXNAME];     /* ユーザ名 */
    char MyBirth[MYBIRTH];
};

void main(){
    
    
    struct user s_user[MAXCASE]; 
    int Manth; 
    int i, j = 0;
    
    
    while( j < MAXCASE ){// データ入力 
        
        
        printf("名前:"); 
        get_string(s_user[j].Name, MAXNAME);
        
        
        if ( s_user[j].Name[0] == '0' ) {// 1文字目が'0'のとき 
            
            break;
        }
        
        
        if ( s_user[j].Name[0] == '\0' ) {//改行のみのとき
            
            printf("再入力\n");
            continue;
        }
        
        
        printf("生年月日:");
        
        
        get_string(s_user[j].MyBirth, MYBIRTH);
         
         /* 年の数字チェック */
        if(i <= 8 ){
            printf("7文字数値が入力されていません。\n");
            continue;
        }
            for (i = 0; i < 4; i++) {//各桁の文字が0~9以外のときエラー 
            if( s_user[j].MyBirth < '0' || '9' < s_user[j].MyBirth ){
                printf("数字以外が入力されました\n");
                continue; 
            }
        }
        
        if(s_user[j].MyBirth[4] != '/'){
            
            printf("正しい入力でありません\n");
            continue;
        }
        
       /* for( i=5; i <7; i++){
            if(s_user[j].MyBirth < '0' || '9' < s_user[j].MyBirth){
                
                printf("月の入力が違います\n");
                continue;
            }
        }*/
        
        printf("累計件数 = %d件\n\n", ++j);
    }
    
    printf("\n入力データ表示\n"); 
    
    
    
    for (i = 0; i < j; i++) {
        printf("%d件目\n", i + 1); 
        printf("名前 = %s\n", s_user.Name); 
        printf("生年月日 = %s/\n", s_user[j].MyBirth); 
    }
}

void get_string(char *p_str, int xMaxChar){
    int i = 0; 
    int C; 
    
    while ( (C = getchar()) != EOF && C != '\n' ){
        if ( i < xMaxChar - 1 ) {
            *p_str = C; 
            p_str++; 
            i++; 
        }
    }
    *p_str = '\0'; 
}

Re:学校の課題なんですが

Posted: 2007年3月14日(水) 23:27
by box
年月チェックの例です。適当に修正しちゃってください。

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

#define NG (0)
#define OK (!NG)

int is_valid_y_m(char *s);

int main(void)
{
	char *str[/url] = {
		"2007/01",
		"2007/09",
		"2007/10",
		"2007/12",
		"0001/01",
		"1000/01",
		"9999/12",
		"2007/00",
		"2007/13",
		"2007/20",
		"200a/01",
		"2007/0b",
		"2007.01",
		"2007/010",
		"999/12",
		"10000/01",
	};
	int i;
	
	for (i = 0; i < sizeof(str) / sizeof(str[0]); i++) {
		printf("'%s' → ", str);
		if (is_valid_y_m(str))
			printf("%d年%d月\n",
				(int) strtol(str,   NULL, 10),
				(int) strtol(str+5, NULL, 10));
		else
			printf("NG\n");
	}
	return 0;
}

int is_valid_y_m(char *s)
{
	char *p;
	int n;
	
	for (p = s, n = 0; *p; p++, n++) {
		switch (n) {
		case 0: case 1: case 2: case 3:
			if (*p < '0' || '9' < *p) return NG;
			break;
		case 4:
			if (*p != '/') return NG;
			break;
		case 5:
			if (*p != '0' && *p != '1') return NG;
			break;
		case 6:
			if (*(p-1) == '0') {
				if (*p < '1' || '9' < *p) return NG;
			}
			else
				if (*p < '0' || '2' < *p) return NG;
			break;
		default:
			return NG;
		}
	}
	return OK;
}

Re:学校の課題なんですが

Posted: 2007年3月15日(木) 08:46
by Yuki
>生年月日の再入力のところでcontinue文を使って...

正しい生年月日が入力するまで、再度入力させるループを
新しく追加してみてはいかがでしょう。


>7文字入力してもエラーになってしまいます。57行目で...

ここでiは何に使用していますか?
想像するに、入力された文字数を確認したいのだと思いますが、
入力用のget_string関数内で宣言されているiとmain関数のiは
別のものになります。
get_string関数の戻り値として文字数を返し、main関数のiに
格納してみてはいかがでしょう。

ちなみに、if文の直前にiの値がどうなっているかprintfで
見てみるとわかりやすいですよ。


>月の判定でが00~12までか判定仕様として...

サンプルです。
char ymd_str[/url] = "2007/03/15";
char buf[5];
int man;

if(( ymd_str[5] >= '0' &&  ymd_str[5] <= '9' )&&( ymd_str[6] >= '0' &&  ymd_str[6] <= '9' )){
	printf( "月は数字です。\n" );
	sprintf( buf, "%.2s", &ymd_str[5] );
	man = atoi( buf );
	printf( "man:%d\n", man );
}
else{
	printf( "月は数字以外です。\n" );
}

Re:学校の課題なんですが

Posted: 2007年3月15日(木) 11:31
by 素人
box様、Yuki様回答ありがとうございます。

atoi関数を使わずに処理しなければいけないので、s_user[j].MyBirth[5]とs_user[j].MyBirth[6]を数字とだけ判定はしたのですが、それからは、MyBirth[5]が0か1か判定し0ならMyBirth[6]が1~9かMyBirth[5]が1ならMyBirth[6]が0~2かで判定しなければいけないのでしょか。

>get_string関数の戻り値として文字数を返し、main関数のiに格納してみてはいかがでしょう。

どのようにiに格納すればよいのでしょか。文字数は、get_stringのMYBIRTHには入っていないのですか。関数の仕組みがよくわかっていないもので教えていただけないでしょか。

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

#define MAXNAME 11  /* 名前の最大入力文字数 10文字 + '\0' */
#define MYBIRTH 9  /* 生年月日の年の形*/
#define MAXCASE 10 /* 最大ユーザー件数 */

void get_string(char *p_str, int xMaxChar); 

struct user{
    char Name[MAXNAME];     /* ユーザ名 */
    char MyBirth[MYBIRTH];
};

void main(){
    
    
    struct user s_user[MAXCASE]; 
    int Manth; 
    int j = 0;
    int i;
    
    
    while( j < MAXCASE ){// データ入力 
        
        
        printf("名前:"); 
        get_string(s_user[j].Name, MAXNAME);
        
        
        if ( s_user[j].Name[0] == '0' ) {// 1文字目が'0'のとき 
            
            break;
        }
        
        
        if ( s_user[j].Name[0] == '\0' ) {//改行のみのとき
            
            printf("再入力\n");
            continue;
        }
        
        printf("i値:%d\n",i);
        printf("生年月日:");
        
        for(;;){
            get_string(s_user[j].MyBirth, sizeof(s_user[j].MyBirth));
                
                if(i <= 8 ){
                    
                    printf("7文字数値が入力されていません。\n");
                    continue;
                    
                    }
                    for (i = 0; i < 4; i++) {//各桁の文字が0~9以外のときエラー 
                        if( s_user[j].MyBirth < '0' || '9' < s_user[j].MyBirth ){
                             printf("数字以外が入力されました\n");
                             continue;
                             }
                    }
                    
                    if(s_user[j].MyBirth[4] != '/'){
                        
                        printf("正しい入力でありません\n");
                        continue;
                    }
                    
                    if(( s_user[j].MyBirth[5] >= '0' &&  s_user[j].MyBirth[5] <= '9' )&&( s_user[j].MyBirth[6] >= '0' &&  s_user[j].MyBirth[6] <= '9' )){
                            
                            printf("月の入力正解\n");
                            
                        }
                        
                    else{
                            printf("月の入力が違います\n");
                            continue;
                    }
                    break;
        }
        
        printf("累計件数 = %d件\n\n", ++j);
        }
    
    printf("\n入力データ表示\n"); 
    
    
    
    for (i = 0; i < j; i++) {
        printf("%d件目\n", i + 1); 
        printf("名前 = %s\n", s_user.Name); 
        printf("生年月日 = %s/\n", s_user[j].MyBirth); 
    }
}

void get_string(char *p_str, int xMaxChar){
    int i = 0; 
    int C; 
    
    while ( (C = getchar()) != EOF && C != '\n' ){
        if ( i < xMaxChar - 1 ) {//配列の(xMaxChar -1)文字まで有効
            *p_str = C; 
            i++; 
            p_str++;
        }
    }
    *p_str = '\0'; 
}

Re:学校の課題なんですが

Posted: 2007年3月15日(木) 12:47
by box
> atoi関数を使わずに処理しなければいけない

atoi関数もstrtol関数も使っていないサンプルです。
is_valid_y_m関数では、戻り値がゼロ以外の場合に限り、
第2引数(年)と第3引数(月)の内容が有効です。


#include <stdio.h>

#define NG (0)
#define OK (!NG)

int is_valid_y_m(char *s, int *y, int *m);

int main(void)
{
	char *str[/url] = {
		"2007/01",
		"2007/09",
		"2007/10",
		"2007/12",
		"0001/01",
		"1000/01",
		"9999/12",
		"2007/00",
		"2007/13",
		"2007/20",
		"200a/01",
		"2007/0b",
		"2007.01",
		"2007/010",
		"999/12",
		"10000/01",
	};
	int year, month, i;
	
	for (i = 0; i < sizeof(str) / sizeof(str[0]); i++) {
		printf("'%s' → ", str);
		if (is_valid_y_m(str, &year, &month))
			printf("%d年%d月\n", year, month);
		else
			printf("NG\n");
	}
	return 0;
}

int is_valid_y_m(char *s, int *y, int *m)
{
	char *p;
	int n;
	
	for (p = s, *y = *m = n = 0; *p; p++, n++) {
		switch (n) {
		case 0: case 1: case 2: case 3:
			if (*p < '0' || '9' < *p) return NG;
			*y = *y * 10 + (*p - '0');
			break;
		case 4:
			if (*p != '/') return NG;
			break;
		case 5:
			if (*p != '0' && *p != '1') return NG;
			*m = *m * 10 + (*p - '0');
			break;
		case 6:
			if (*(p-1) == '0') {
				if (*p < '1' || '9' < *p) return NG;
			}
			else
				if (*p < '0' || '2' < *p) return NG;
			*m = *m * 10 + (*p - '0');
			break;
		default:
			return NG;
		}
	}
	return OK;
}

Re:学校の課題なんですが

Posted: 2007年3月15日(木) 13:08
by Yuki
>atoi関数を使わずに処理しなければいけないので...

atoi関数を使わないのであれば、それが1番よさそうですね。



>どのようにiに格納すればよいのでしょか。文字数は、get_stringのMYBIRTHには入っていないのですか。関数の仕組みがよくわかっていないもので教えていただけないでしょか。

get_string関数が終了する直前に変数iをprintfしてみてください。
【入力された文字数】が入っていると思います。

この数値を利用してmain関数のif(i <= 8 )をしたいのだと思うのですが、
main関数とget_string関数の変数iは同じ名前でも、異なる変数ですので、
main関数のiには【入力された文字数】はありません。


これを解決するために、get_string関数の型を現在voidとなっているものを
intに変更し、関数が終わる直前にreturn i;を追加してください。

次に、main関数でget_string関数を呼び出す際に、戻り値をiで受けてください。
i = get_string(s_user[j].MyBirth, sizeof(s_user[j].MyBirth));

これで関数間の値の受け渡しができます。


ちなみに、MYBIRTHは【最大入力文字数】なので、【入力された文字数】とは異なります。

Re:学校の課題なんですが

Posted: 2007年3月17日(土) 13:10
by 素人
box様、Yuki様回答ありがとうございます。

組み直しなのですが、7文字以上かを判定するところで7文字入力しているのにエラーになってしまいます。
iの値も確認したのですが7が表示されるのですが何故エラーになってしまうのでしょか。
>【最大入力文字数】なので、【入力された文字数】
が違うのでしょうか。


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

#define MAXNAME 11 /* 名前の最大入力文字数 10文字 + '\0' */
#define MYBIRTH 9 /* 生年月日の年の形*/
#define MAXCASE 10 /* 最大ユーザー件数 */

int get_string(char *p_str, int xMaxChar);

struct user{
char Name[MAXNAME]; /* ユーザ名 */
char MyBirth[MYBIRTH];
};

void main(){


struct user s_user[MAXCASE];
int Manth;
int j = 0;
int i;


while( j < MAXCASE ){// データ入力


printf("名前:");
get_string(s_user[j].Name, MAXNAME);


if ( s_user[j].Name[0] == '0' ) {// 1文字目が'0'のとき

break;
}


if ( s_user[j].Name[0] == '\0' ) {//改行のみのとき

printf("再入力\n");
continue;
}


printf("生年月日:");

for(;;){
//get_string(s_user[j].MyBirth, MYBIRTH);
i = get_string(s_user[j].MyBirth, sizeof(s_user[j].MyBirth)-1);
printf("i値:%d\n",i);
if(i <= 8 ){//8文字以上の入力の場合エラー

printf("7文字以上が入力されました。\n");
continue;

}
for (i = 0; i < 4; i++) {//各桁の文字が0~9以外のときエラー
if( s_user[j].MyBirth < '0' || '9' < s_user[j].MyBirth ){
printf("数字以外が入力されました\n");
continue;
}
}

if(s_user[j].MyBirth[4] != '/'){//4文字目が/じゃない

printf("正しい入力でありません\n");
continue;
}

if( s_user[j].MyBirth[5] >= '0' && s_user[j].MyBirth[5] <= '9' ){//5桁目の文字が0~9である
if(s_user[j].MyBirth[5] == '0'){//5桁目が0の時
if( s_user[j].MyBirth[6] >= '1' && s_user[j].MyBirth[6] <= '9' ){//6桁目は1~9である
break;
}
}

else if(s_user[j].MyBirth[5] == '1'){//5桁目が1の時
if( s_user[j].MyBirth[6] >= '0' && s_user[j].MyBirth[6] <= '2' ){//6桁目は0~2でる
break;
}
}
}

else{
printf("月の入力が違います\n");
continue;
}

}

printf("累計件数 = %d件\n\n", ++j);
}

printf("\n入力データ表示\n");



for (i = 0; i < j; i++) {
printf("%d件目\n", i + 1);
printf("名前 = %s\n", s_user.Name);
printf("生年月日 = %s/\n", s_user[j].MyBirth);
}
}

int get_string(char *p_str, int xMaxChar){
int i = 0;
int C;

while ( (C = getchar()) != EOF && C != '\n' ){
if ( i < xMaxChar - 1 ) {//配列の(xMaxChar -1)文字まで有効
*p_str = C;
i++;
p_str++;
}
}
*p_str = '\0';
return i;
}

Re:学校の課題なんですが

Posted: 2007年3月17日(土) 13:26
by box
> 組み直しなのですが、7文字以上かを判定するところで7文字入力しているのにエラーになってしまいます。
> iの値も確認したのですが7が表示されるのですが何故エラーになってしまうのでしょか。

中身のロジックはほとんど見ていませんので、気がついたところだけ。

> 				if(i <= 8 ){//8文字以上の入力の場合エラー
> 					
> 					printf("7文字以上が入力されました。\n");

ここの記述は、
 ・if文の判定条件
 ・コメントの内容
 ・printf関数中のメッセージ
のお互いが食い違っています。そろえてください。

Re:学校の課題なんですが

Posted: 2007年3月17日(土) 15:12
by 素人
box様すいませんでした。直しました。

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

#define MAXNAME 11 /* 名前の最大入力文字数 10文字 + '\0' */
#define MYBIRTH 9 /* 生年月日の年の形*/
#define MAXCASE 10 /* 最大ユーザー件数 */

int get_string(char *p_str, int xMaxChar);

struct user{
char Name[MAXNAME]; /* ユーザ名 */
char MyBirth[MYBIRTH];
};

void main(){


struct user s_user[MAXCASE];
int Manth;
int j = 0;
int i;


while( j < MAXCASE ){// データ入力


printf("名前:");
get_string(s_user[j].Name, MAXNAME);


if ( s_user[j].Name[0] == '0' ) {// 1文字目が'0'のとき

break;
}


if ( s_user[j].Name[0] == '\0' ) {//改行のみのとき

printf("再入力\n");
continue;
}


printf("生年月日:");

for(;;){
//get_string(s_user[j].MyBirth, MYBIRTH);
i = get_string(s_user[j].MyBirth, sizeof(s_user[j].MyBirth)-1);
printf("i値:%d\n",i);
if(i < 8 ){//7文字以上の入力の場合エラー

printf("7文字以上が入力されました。\n");
continue;

}
for (i = 0; i < 4; i++) {//各桁の文字が0~9以外のときエラー
if( s_user[j].MyBirth < '0' || '9' < s_user[j].MyBirth ){
printf("数字以外が入力されました\n");
continue;
}
}

if(s_user[j].MyBirth[4] != '/'){//4文字目が/じゃない

printf("正しい入力でありません\n");
continue;
}

if( s_user[j].MyBirth[5] >= '0' && s_user[j].MyBirth[5] <= '9' ){//5桁目の文字が0~9である
if(s_user[j].MyBirth[5] == '0'){//5桁目が0の時
if( s_user[j].MyBirth[6] >= '1' && s_user[j].MyBirth[6] <= '9' ){//6桁目は1~9である
break;
}
}

else if(s_user[j].MyBirth[5] == '1'){//5桁目が1の時
if( s_user[j].MyBirth[6] >= '0' && s_user[j].MyBirth[6] <= '2' ){//6桁目は0~2でる
break;
}
}
}

else{
printf("月の入力が違います\n");
continue;
}

}

printf("累計件数 = %d件\n\n", ++j);
}

printf("\n入力データ表示\n");



for (i = 0; i < j; i++) {
printf("%d件目\n", i + 1);
printf("名前 = %s\n", s_user.Name);
printf("生年月日 = %s/\n", s_user[j].MyBirth);
}
}

int get_string(char *p_str, int xMaxChar){
int i = 0;
int C;

while ( (C = getchar()) != EOF && C != '\n' ){
if ( i < xMaxChar - 1 ) {//配列の(xMaxChar -1)文字まで有効
*p_str = C;
i++;
p_str++;
}
}
*p_str = '\0';
return i;
}

Re:学校の課題なんですが

Posted: 2007年3月19日(月) 11:18
by Yuki
> printf("i値:%d\n",i);
> if(i < 8 ){//7文字以上の入力の場合エラー
> printf("7文字以上が入力されました。\n");

このif文で真になるケースはiが8より小さい場合ですよね。
なのにメッセージが「7文字以上が入力されました。」はおかしくないですか?



また生年月日に「2007/03/19」を入力したい場合、

> //get_string(s_user[j].MyBirth, MYBIRTH);
> i = get_string(s_user[j].MyBirth, sizeof(s_user[j].MyBirth)-1);

> while ( (C = getchar()) != EOF && C != '\n' ){
> if ( i < xMaxChar - 1 ) {//配列の(xMaxChar -1)文字まで有効

だと7文字までしか受け付けてもらえないようなのですが・・・。
プログラムの仕様を見直す必要があると思います。

Re:学校の課題なんですが

Posted: 2007年3月19日(月) 13:31
by 素人
Yuki様回答ありがとうございます。
メッセージはおかしかったので直しました。
プログラムは年と月だけなので大丈夫です。
無事にできました。
色々教えてくださって、ありがとうございました。