真円プロジェクト
今回のタイトル名は「真円プロジェクト」に決定しました。
タイトル名に限らずキャラクターの名前とか魔法の名前とか、名前を付けるときって結構悩みます。「名は体を表す」とも言いますからね。それっぽい名前が浮かばないと、どこかムズ痒い気持ちになります。私は直感的に命名することがほとんどで、候補をいくつも並べて決めるなんてことはまずしません。今回のタイトル名も第2候補さえ考えずに浮かんだものを直感的に採用しました。理想としては、このソフトがどんどん広まって絵描きさんの必須アイテムになるイメージがあったので、それも含めてプロジェクトという言葉がしっくりきたわけです。まあ現実的にはそれほど広まるとは思えませんが(汗
プログラミングのほうは、前回書いたようにサラっとあっさりしています。ただ円を描いてその判定をするだけですからね。なのでビジュアル的にもシンプルに落ち着いたグレーで統一するようにしました。そこで気付いたのは、シンプルにすると逆にオシャレな感じになる!(今更!?今更気付いたの!?)今回はゲームとは呼べないものなので、より実用的にと考えたら、円を描くことに集中出来るように余計なエフェクトを省くことです。その結果シンプルになっただけの話ですが、個人的にはなかなか良い感じです。
アルゴリズムで困ったのは、「1周」の判定です。描き出しの地点(任意の点)からぐるっと回って「はい、ここで1周」とするにはどう判定すべきか迷いました。最終的に円を4分割して、その4つのエリアを全部通った後で最初の地点を越えたときを、中心点からの角度で判定することにしました。角度(ラジアン)は -3.14~3.14 の範囲でループするので、単純に最初の地点と現在の地点を比較出来ないんですよね。4分割することで -3.14 から 3.14 への遷移(逆も)が判定しやすくなります。右回りと左回りも考慮すると、なんとも解かりづらいコード(掲載は次回?)となりましたが、きっともっと簡単なアルゴリズムがあるはず・・・。
こんな感じでメインの円を描くところは出来上がりました。後は円の判定をして点数を出して…などなど細部をととのえて完成です。
サラッと
さて、次に取り掛かるプロジェクトですが、前回予定を変更してしまったタワーディフェンスを作るかと思いきや、ボリュームの少ないサラッとしたものを作りたいと考えています。
いやね。というのも、最近忙しくて大きなプロジェクトを立ち上げる気力がありません。少しずつ作るにしても、規模が大きいと忘れながら、思い出しながらプログラミングすることになるので、ややこしくなってきます。なら、ここはそんなストレスを抱えずに済む簡易的なもので挑もうじゃないか、と私ひとりの会議で決定しました。
とはいえ、簡易的なものであっても、何かしら新しい発想や技術を取り入れて、少なくとも私にとって新鮮なものに仕上げたい欲求はあります。今考えているのは「真円を描く」ただそれだけです。シンプルですが、絵を描いている、または絵の勉強がしたいといった人には需要があるかもしれない内容です。絵が上手くなるためには真円を描けるようになるのが良いという情報もあり、毎日真円の練習をしている人もいるらしいです。ということで何やら奥が深い話も出てきそうです。
具体的な内容としては、円を描いて中心点からの距離や描くスピードなどを点数化して、どれだけ上達したかを知るためのものになります。もしかしたら絵を描くことに興味がなくてもきれいな円を描きたい性格の人もいるかもしれないので(数学の教師とか)、そんな人はハマること間違い無し!?出来ればマウスではなくペンタブでチャレンジするのがベターですね。ちなみに私はペンタブ持っていません・・・(笑
Phase of Evolution 完成!
落ち物系パズル完成しました。タイトルはどんどん進化していくということで「Phase of Evolution」に決定!なんか言葉の響きがカッコイイのでこれにしました(笑
一応今回はまともなゲームなので多少凝りたかったわけですが、最終的には画像もサウンドも手抜きで落ち着きました(汗。本当は神秘的で洗練されたイメージに仕上げる予定でしたが、早々に自分の実力を痛感し(いや、最初からわかっていたけど)素人っぽさの残る作品となりました。
まあそれで好いんです。実際素人ですし。カッコイイ画像やサウンドが作れるわけでもないので…。そういったことが得意な人は、自作の画像やサウンドに書き換えてプレイしてください。全部 imgフォルダに入っているので、ファイル名が同じなら読み込まれます。で、神秘的で洗練されたイメージになったら、そのファイルを私にください!(爆。改めて新バージョンとして紹介しようと思います。
こんな感じで、演出部分はプログラミングで多少補う程度で本腰を入れずやっていこうと開き直りました。逆にシンプルにすることが味になることもありますからね、なんて言い訳も追加しておきます。
あと内容としては、参考にしたゲームよりもテンポの良さを追求(っていうほどのものではないが)しました。操作はちょっと慣れないとブロックが回転し過ぎたりするかもしれませんが、テンポを良くするとちょうど好いくらいだと思います。他にも落下速度を早くしたり、進化のエフェクトも短時間にしてあります。原作よりも広いエリアなので、プレイ時間も長くなります。そこでテンポが悪いとプレイしていて鬱陶しいですからね。唯一の拘りです。
個人的にはそこそこ楽しめるゲームだと思っています。ぷよぷよとか好きならプレイ感覚は近いのでハマるかもしれません。連鎖も割りと簡単です。
こちらのページからダウンロード出来ます。プレイ動画もあります。
↓
http://tenkomorituuhan2.com/products/phaseofevolution/top.html
ってことで Phase of Evolution のソースコードを掲載します。前回書いた再帰関数はだいぶ煩雑な感じに仕上がってます。というか私の書いたコードはプロの目にはどう映っているんですかね~。BASICっぽいコーディングは逆にわかり辛いんでしょうね。そもそもBASICだって極めたわけではないので、いい加減なコードに代わりはありませんが…。
素人が書いたものです。コードをてきとーに使用すると、巨神兵が眠りから醒め火の七日間を再現することになるかもしれません。そんな人類が絶滅の危機に瀕するようなことには関わりたくないので、わかる人が確認した上で使用してください。
#include "DxLib.h" #define WX 640 //ウインドウサイズ #define WY 480 #define WIDTH 10 //ブロック行 #define HEIGHT 10 //ブロック列 #define blockmax 20 int score; //スコア int scoret; //スコア計算用 int scored; //スコア表示用 int highscore[ 10 ] = { 0 }; //ハイスコア int level; //レベル int maxevo; //最大進化 int combo; //連鎖 int maxcombo; //最大連鎖 int relative[ blockmax ]; //出現率相関 int block[ 6 ]; //[0,1]操作ブロック [2,3],[4,5]ネクストブロック int blockx[ 2 ]; //操作ブロック位置 int blocky[ 2 ]; int totalflag; //制御フラグ int maxmode; //モードフラグ int area[ HEIGHT ][ WIDTH ]; //ブロックエリア int areaE[ HEIGHT ][ WIDTH ]; //進化ブロックエリア int totalbl; //隣接ブロック合計 int xp[ HEIGHT ][ WIDTH ]; //ゲームオーバー時ブロック描画用 int yp[ HEIGHT ][ WIDTH ]; int xv[ HEIGHT ][ WIDTH ]; int yv[ HEIGHT ][ WIDTH ]; int bl[ HEIGHT ][ WIDTH ]; //ハンドル int GHtitle; //タイトル int GHbg[ 2 ]; //背景 int GHblock[ blockmax ]; //ブロック int GHnext; //ネクストブロック int GHscore; //スコア int GHnum[ 10 ]; //数字 int GHfade; //フェドインアウト int SHbgm; //BGM int SHrotate; //回転 int SHmove; //移動 int SHfall; //落下 int SHevo1; //進化 int SHevo2; //プロトタイプ宣言 void Title(); void DrawTitle(); void Play(); void Draw(); void Extraction(); //出現ブロック抽出 void Control(); //操作 void Fall(); //ブロック落下 int Evolution(); //進化 void EvolutionSearch( int absx , int absy ); //進化可能ブロック探索(再帰 void DrawEvolution( int effect ); //進化エフェクト void FadeIn(); //フェードイン void FadeOut(); //フェードアウト int GameOverFlag(); //ゲームオーバー判定 void GameOver(); //ゲームオーバー int DrawGameOver(); //描画 void Init(); //スタート時初期化 void Save(); void Load(); //WinMain int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { SetOutApplicationLogValidFlag( FALSE ); //ログ出力しない SetMainWindowText( "PHASE OF EVOLUTION" ); //ウインドウタイトル指定 SetGraphMode( WX , WY , 32 ); //ウインドウの大きさとカラービット数指定 ChangeWindowMode( TRUE ); //ウインドウで表示 SetDrawScreen( DX_SCREEN_BACK ); //ちらつき防止設定 SetWaitVSyncFlag( TRUE ); //フレーム同期 SetMouseDispFlag( TRUE ); //マウスカーソル表示 SetWindowUserCloseEnableFlag( FALSE ); //×ボタンで終了しない if( DxLib_Init() == -1 ) return -1; Load(); //読込み //画像読込 GHtitle = LoadGraph( "img/title.png" ); GHbg[ 0 ] = LoadGraph( "img/bg.png" ); GHbg[ 1 ] = LoadGraph( "img/bg2.png" ); GHnext = LoadGraph( "img/next.png" ); GHscore = LoadGraph( "img/score.png" ); LoadDivGraph( "img/block.png" , blockmax , blockmax , 1 , 48 , 48 , GHblock ); LoadDivGraph( "img/num.png" , 10 , 10 , 1 , 16 , 32 , GHnum ); //音声読込み SHbgm = LoadSoundMem( "img/bgm_base.wav" ); ChangeVolumeSoundMem( 250 , SHbgm ); SHrotate = LoadSoundMem( "img/rotate.wav" ); SHmove = LoadSoundMem( "img/move.wav" ); SHfall = LoadSoundMem( "img/fall.wav" ); SHevo1 = LoadSoundMem( "img/evo1.wav" ); SHevo2 = LoadSoundMem( "img/evo2.wav" ); ChangeFont( "Impact" ); //メインループ///////////////////////////////// while( totalflag != -1 ){ switch( totalflag ){ case 0: Title(); break; case 1: Play(); break; } } Save(); DxLib_End(); return 0; } void Title(){ //タイトル画面/////////////////// DrawTitle(); FadeIn(); DrawTitle(); ScreenFlip(); WaitKey(); if( CheckHitKey( KEY_INPUT_ESCAPE ) == 1 ){ totalflag = -1; }else{ totalflag = 1; } if( CheckHitKey( KEY_INPUT_M ) == 1 ){ maxmode = 1; }else{ maxmode = 0; } FadeOut(); } void DrawTitle(){ //タイトル描画/////////////////// DrawBox( 0 , 0 , WX , WY , 0x005dc2 , TRUE ); DrawGraph( 70 , 20 , GHtitle , TRUE ); //ハイスコア描画 for( int i = 0 ; i<10 ; i++ ){ int sc = highscore[ i ]; for( int j = 1 ; sc > 0 ; j++ ){ DrawGraph( i / 5 * 160 - ( j * 16 ) + 440 , i % 5 * 40 + 270 , GHnum[ sc % 10 ] , TRUE ); sc /= 10; } } //最大進化描画 int me = maxevo; for( int j = 1 ; me > 0 ; j++ ){ DrawGraph( 250 - j * 16 , 322 , GHnum[ me % 10 ] , TRUE ); me /= 10; } //最大連鎖描画 int mc = maxcombo; for( int j = 1 ; mc > 0 ; j++ ){ DrawGraph( 250 - j * 16 , 382 , GHnum[ mc % 10 ] , TRUE ); mc /= 10; } DrawString( 80 , 240 , "any key to start!" , 0xffff00 ); DrawString( 80 , 260 , "「M」 key to Maxmode!!" , 0xffff00 ); DrawString( 80 , 280 , "「ESC」 key to close" , 0xffff00 ); DrawString( 340 , 240 , "HIGH SCORE" , 0xffffff ); DrawString( 80 , 330 , "MAX EVOLUTION" , 0xffffff ); DrawString( 80 , 390 , "MAX COMBO" , 0xffffff ); } void Play(){ //プレイ//////////////////////////// Init(); Extraction(); //出現ブロック抽出 Extraction(); //ネクストブロックを含め3回 Draw(); FadeIn(); Draw(); ScreenFlip(); PlaySoundMem( SHbgm , DX_PLAYTYPE_LOOP ); while( totalflag != 9 ){ //プレイループ Extraction(); //出現ブロック抽出 Control(); //ブロック操作 if( totalflag == -1 ) break; while( 1 ){ Fall(); //ブロック落下 if( Evolution() == 0 ) break; //進化 } totalflag = GameOverFlag(); //ゲームオーバー判定(戻り値9) WaitVSync( 20 ); } if( totalflag == -1 ) return; Draw(); ScreenFlip(); //GAVE OVER処理 GameOver(); totalflag = 0; } void Draw(){ //描画/////////////////////////////// DrawBox( 0 , 0 , WX , WY , 0x000000 , TRUE ); //背景・ブロック描画 for( int y = 0 ; y < HEIGHT ; y++ ){ for( int x = 0 ; x < WIDTH ; x++ ){ if( y > 1 ){ DrawGraph( x * 48 , y * 48 , GHbg[ 0 ] , TRUE ); }else{ DrawGraph( x * 48 , y * 48 , GHbg[ 1 ] , TRUE ); } if( area[ y ][ x ] > -1 ) DrawGraph( x * 48 , y * 48 , GHblock[ area[ y ][ x ] ] , TRUE ); } } //ネクストブロック描画 DrawGraph( WIDTH * 48 +16 , 48 , GHnext , TRUE ); for( int i = 0 ; i < 2 ; i++ ){ DrawGraph( WIDTH * 48 + i * 64 + 24 , 70 , GHblock[ block[ 2 + i * 2 ] ] , TRUE ); DrawGraph( WIDTH * 48 + i * 64 + 24 , 118 , GHblock[ block[ 3 + i * 2 ] ] , TRUE ); } //スコア描画 DrawGraph( WIDTH * 48 + 16 , 200 , GHscore , TRUE ); int sc = score; for( int i = 1 ; sc > 0 ; i++ ){ DrawGraph( WX - i * 16 - 16 , 232 , GHnum[ sc % 10 ] , TRUE ); sc /= 10; } } void Extraction(){ //出現ブロック抽出///////////////// int lv = level + 3; //出現ブロック種類 if( lv > blockmax ) lv = blockmax; if( ( lv + 1 > maxevo ) && ( maxmode == 0 ) ) maxevo = lv + 1; int re = 0; //出現ブロック //ネクストブロック入替え for( int i = 0 ; i < 4 ; i += 2 ){ block[ i ] = block[ i + 2 ]; block[ i + 1 ] = block[ i + 3 ]; } for( int j = 0 ; j < 2 ; j++ ){ int total = 0; //出現率の合計値 for( int i = 0 ; i < lv ; i++ ){ total += relative[ i ]; } //合計値の乱数取得 int r = GetRand( total -1 ); //乱数と相関を比較 for( int i = 0 ; i < lv ; i++ ){ if( relative[ i ] > r ){ re = i; break; } r -= relative[ i ]; } //出現- 否出現++ for( int i = 0 ; i < lv ; i++ ){ relative[ i ] += 2; } relative[ re ] -= level + 2; if( relative[ re ] < 1 ) relative[ re ] = 1; block[ 4 + j ] = re; } } void Control(){ //操作/////////////////////////////// combo = 1; int movex = 0; //横移動量 int movexv = 0; //横移動向き int mover = 0; //回転移動量 int moverst = 0; //回転段階(4段階) int moverx[ 2 ] = { 0 }; //回転移動向き横 int movery[ 2 ] = { 0 }; //回転移動向き縦 int key = 0; //左右入力 blockx[ 0 ] = WIDTH / 2; //ブロック出現位置初期化 blocky[ 0 ] = 0; blockx[ 1 ] = WIDTH / 2; blocky[ 1 ] = 1; while( 1 ){ //ブロック操作ループ //終了 if( CheckHitKey( KEY_INPUT_ESCAPE ) == 1 ){ totalflag = -1; break; } //左右の入力チェック key = CheckHitKey( KEY_INPUT_RIGHT ) - CheckHitKey( KEY_INPUT_LEFT ); if( ( key != 0 ) && ( movex == 0 ) ){ //範囲確認 if( ( -1 < blockx[ 0 ] + key ) && ( blockx[ 0 ] + key < WIDTH ) && ( -1 < blockx[ 1 ] + key ) && ( blockx[ 1 ] + key < WIDTH ) ){ //移動設定 blockx[ 0 ] += key; blockx[ 1 ] += key; movex = key * 48; movexv = key; PlaySoundMem( SHmove , DX_PLAYTYPE_BACK ); } } //「↑」の入力チェック if( ( CheckHitKey( KEY_INPUT_UP ) == 1 ) && ( mover < 1 ) ){ //回転段階++ moverst = ( moverst + 1 ) % 4; PlaySoundMem( SHrotate , DX_PLAYTYPE_BACK ); //段階に応じて移動設定 for( int i = 0 ; i < 2 ; i++ ){ moverx[ i ] = 0; movery[ i ] = 0; } switch( moverst ){ case 0: //block[1]が左下へ moverx[ 1 ] = -1; movery[ 1 ] = 1; blockx[ 1 ]--; blocky[ 1 ]++; break; case 1: //[0]が右下へ if( blockx[ 0 ] + 1 < WIDTH ){ moverx[ 0 ] = 1; movery[ 0 ] = 1; blockx[ 0 ]++; blocky[ 0 ]++; } else { movery[ 0 ] = 1; moverx[ 1 ] = -1; blocky[ 0 ]++; blockx[ 1 ]--; } break; case 2: //[1]が右上へ moverx[ 1 ] = 1; movery[ 1 ] = -1; blockx[ 1 ]++; blocky[ 1 ]--; break; case 3: //[0]が左上へ if( blockx[ 0 ] - 1 > -1 ){ moverx[ 0 ] = -1; movery[ 0 ] = -1; blockx[ 0 ]--; blocky[ 0 ]--; } else { movery[ 0 ] = -1; moverx[ 1 ] = 1; blocky[ 0 ]--; blockx[ 1 ]++; } break; } mover = 48; } //「↓」を押したら抜ける if( ( CheckHitKey( KEY_INPUT_DOWN ) == 1 ) && ( movex == 0 ) && ( mover < 1 ) ) break; //横移動 if( movex != 0 ) movex -= movexv * 4; //回転移動 if( mover > 0 ) mover -= 8; //描画 Draw(); for( int i = 0 ; i < 2 ; i++ ){ DrawGraph( blockx[ i ] * 48 - movex - moverx[ i ] * mover , blocky[ i ] * 48 - movery[ i ] * mover , GHblock[ block[ i ] ] , TRUE ); } ScreenFlip(); } area[ blocky[ 0 ] ][ blockx[ 0 ] ] = block[ 0 ]; area[ blocky[ 1 ] ][ blockx[ 1 ] ] = block[ 1 ]; } void Fall(){ //ブロック落下//////////////////// int fallflag = 0; int flag; do{ flag = 0; for( int y = HEIGHT - 2 ; y >-1 ; y-- ){ for( int x = 0 ; x < WIDTH ; x++ ){ //下に何もなければ1段落下 if( ( area[ y ][ x ] > -1 ) && ( area[ y + 1 ][ x ] == -1 ) ){ area[ y + 1 ][ x ] = area[ y ][ x ]; area[ y ][ x ] = -1; flag = 1; fallflag = 1; } } //for x } //for y Draw(); ScreenFlip(); }while( flag == 1 ); if( fallflag == 1 ) PlaySoundMem( SHfall , DX_PLAYTYPE_BACK ); } int Evolution(){ //進化/////////////////////////////// //初期化 for( int y = 0 ; y < HEIGHT ; y++ ){ for( int x = 0 ; x < WIDTH ; x++ ){ areaE[ y ][ x ] = 0; } } int evoflag = 0; for( int y = HEIGHT - 1 ; y > 1 ; y-- ){ for( int x = 0 ; x < WIDTH ; x++ ){ //進化基点探索 if( ( area[ y ][ x ] > -1 ) && ( areaE[ y ][ x ] == 0 ) ){ int flag = 0; if( x + 1 < WIDTH ){ if( area[ y ][ x ] == area[ y ][ x + 1 ] ){ flag = 1; } } if( area[ y ][ x ] == area[ y - 1 ][ x ] ){ flag = 1; } if( flag == 1 ){ //基点から隣接ブロック探索 totalbl = 1; //隣接ブロック合計 areaE[ y ][ x ] = 1; EvolutionSearch( x , y ); //再帰探索 if( totalbl >= 3 ){ //進化可能! for( int ye = 0 ; ye < HEIGHT ; ye++ ){ for( int xe = 0 ; xe < WIDTH ; xe++ ){ if( areaE[ ye ][ xe ] == 1 ) areaE[ ye ][ xe ] = 3; } //for xe } //for ye if( area[ y ][ x ] >= blockmax - 1 ){ areaE[ y ][ x ] = 3; }else{ areaE[ y ][ x ] = 4; } evoflag = 1; } else { //進化ならず… areaE[ y ][ x ] = 2; if( areaE[ y - 1 ][ x ] == 1 ) areaE[ y - 1 ][ x ] = 2; if( x + 1 < WIDTH ){ if( areaE[ y ][ x + 1 ] == 1 ) areaE[ y ][ x + 1 ] = 2; } } } } } //for x } //for y if( evoflag == 0 ) return 0; //進化!!! DrawEvolution( 1 ); int num = 0; for( int y = 0 ; y < HEIGHT ; y++ ){ for( int x = 0 ; x < WIDTH ; x++ ){ if( areaE[ y ][ x ] == 3 ){ num += area[ y ][ x ] + 1; areaE[ y ][ x ] = 0; area[ y ][ x ] = -1; } else if( areaE[ y ][ x ] == 4 ){ num += area[ y ][ x ] + 1; area[ y ][ x ]++; if( area[ y ][ x ] > level + 3 ) level++; } } } Draw(); ScreenFlip(); DrawEvolution( -1 ); Draw(); ScreenFlip(); score += num * ( 10 + level ) * combo; if( combo > maxcombo ) maxcombo = combo; combo++; WaitVSync( 10 ); return 1; } void EvolutionSearch( int absx , int absy ){ //隣接ブロック探索//////////////////// int xmin = absx - 1; int xmax = absx + 1; int ymin = absy - 1; int ymax = absy + 1; if( xmin > -1 ){ if( ( area[ absy ][ absx ] == area[ absy ][ xmin ] ) && ( areaE[ absy ][ xmin ] == 0 ) ){ totalbl++; areaE[ absy ][ xmin ] = 1; EvolutionSearch( xmin , absy ); } } if( xmax < WIDTH ){ if( ( area[ absy ][ absx ] == area[ absy ][ xmax ] ) && ( areaE[ absy ][ xmax ] == 0 ) ){ totalbl++; areaE[ absy ][ xmax ] = 1; EvolutionSearch( xmax , absy ); } } if( ymin > -1 ){ if( ( area[ absy ][ absx ] == area[ ymin ][ absx ] ) && ( areaE[ ymin ][ absx ] == 0 ) ){ totalbl++; areaE[ ymin ][ absx ] = 1; EvolutionSearch( absx , ymin ); } } if( ymax < HEIGHT ){ if( ( area[ absy ][ absx ] == area[ ymax ][ absx ] ) && ( areaE[ ymax ][ absx ] == 0 ) ){ totalbl++; areaE[ ymax ][ absx ] = 1; EvolutionSearch( absx , ymax ); } } } void DrawEvolution( int effect ){ //進化エフェクト///////////// int GHtemp = MakeGraph( WX , WY ); GetDrawScreenGraph( 0 , 0 , WX , WY , GHtemp ); DrawBox( 0 , 0 , WX , WY , 0x000000 , TRUE ); int imin; int imax; if( effect == 1 ){ imin = 0; imax = 16; PlaySoundMem( SHevo1 , DX_PLAYTYPE_BACK ); } else { imin = 16; imax = 0; PlaySoundMem( SHevo2 , DX_PLAYTYPE_BACK ); } for( int i = imin ; i != imax ; i += effect ){ DrawBox( 0 , 0 , WX , WY , 0x000000 , TRUE ); DrawGraph( 0 , 0 , GHtemp , TRUE ); SetDrawBlendMode( DX_BLENDMODE_ALPHA , 255 - i * 16 ); for( int y = 0 ; y < HEIGHT ; y++ ){ for( int x = WIDTH -1 ; x > -1 ; x-- ){ if( areaE[ y ][ x ] > 2 ){ DrawRotaGraph( x * 48 + 24 , y * 48 + 24 , ( double )i / 2 , 0 , GHblock[ area[ y ][ x ] ] , TRUE , FALSE ); } } //for x } //for y SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ); ScreenFlip(); } //for i } void FadeIn(){ //フェードイン//////////////////// GHfade = MakeGraph( WX , WY ); GetDrawScreenGraph( 0 , 0 , WX , WY , GHfade ); for( int i = 0 ; i < 255 ; i += 5 ){ ClearDrawScreen(); SetDrawBlendMode( DX_BLENDMODE_ALPHA , i ); DrawGraph( 0 , 0 , GHfade , TRUE ); ScreenFlip(); } SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ); DeleteGraph( GHfade ); } void FadeOut(){ //フェードアウト///////////////// GHfade = MakeGraph( WX , WY ); GetDrawScreenGraph( 0 , 0 , WX , WY , GHfade ); for( int i = 255 ; i > 0 ; i -= 5 ){ ClearDrawScreen(); SetDrawBlendMode( DX_BLENDMODE_ALPHA , i ); DrawGraph( 0 , 0 , GHfade , TRUE ); ScreenFlip(); } SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ); DeleteGraph( GHfade ); } int GameOverFlag(){ //ゲームオーバー判定/////////////////// int flag = 0; for( int x = 0 ; x < WIDTH ; x++ ){ if( area[ 1 ][ x ] > -1 ) flag = 1; } if( flag == 1 ) return 9; return 1; } void GameOver(){ //ゲームオーバー////////////// StopSoundMem( SHbgm ); //ハイスコア if( score > highscore[ 9 ] ){ highscore[ 9 ] = score; for( int i = 8 ; i > -1 ; i-- ){ if( score > highscore[ i ] ){ highscore[ i + 1 ] = highscore[ i ]; highscore[ i ] = score; } } } //初期化 for( int y = 0 ; y < HEIGHT ; y++ ){ for( int x = 0 ; x < WIDTH ; x++ ){ bl[ y ][ x ] = -1; } } //ひとつずつ弾ける for( int y = 0 ; y < HEIGHT ; y++ ){ for( int x = 0 ; x < WIDTH ; x++ ){ if( area[ y ][ x ] > -1 ){ xp[ y ][ x ] = x * 48; yp[ y ][ x ] = y * 48; yv[ y ][ x ] = -9; xv[ y ][ x ] = GetRand( 11 ) - 6; bl[ y ][ x ] = area[ y ][ x ]; area[ y ][ x ] = -1; } DrawGameOver(); DrawGameOver(); } //for x } //for y while( 1 ){ if( DrawGameOver() == 0 ) break; } Draw(); FadeOut(); } int DrawGameOver(){ //描画(Game over)////////////////// int flag = 0; Draw(); for( int y = 0 ; y < HEIGHT ; y++ ){ for( int x = 0 ; x < WIDTH ; x++ ){ if( area[ y ][ x ] > -1 ) flag = 1; if( bl[ y ][ x ] > -1 ){ DrawGraph( xp[ y ][ x ] , yp[ y ][ x ] , GHblock[ bl[ y ][ x ] ] , TRUE ); flag = 1; yv[ y ][ x ]++; yp[ y ][ x ] += yv[ y ][ x ]; xp[ y ][ x ] += xv[ y ][ x ]; if( yp[ y ][ x ] > WY ) bl[ y ][ x ] = -1; } } //for x } //for y ScreenFlip(); return flag; } void Init(){ //スタート時初期化////////////// score = 0; scoret = 0; if( maxmode == 1 ){ level = maxevo - 4; if( level < 0 ) level = 0; }else{ level = 0; } for( int i = 0 ; i < blockmax ; i++ ){ relative[ i ] = 1; } for( int y = 0 ; y < HEIGHT ; y++ ){ for( int x = 0 ; x < WIDTH ; x++ ){ area[ y ][ x ] = -1; } } } void Save(){ //書込み//////////////////////////// FILE *fp; errno_t error; if( ( error = fopen_s( &fp , "save.sv" , "wb" ) ) != 0 ){ //エラー }else{ fwrite( highscore , sizeof( int ) , 10 , fp ); fwrite( &maxevo , sizeof( int ) , 1 , fp ); fwrite( &maxcombo , sizeof( int ) , 1 , fp ); fclose( fp ); } } void Load(){ //読込み//////////////////////////// FILE *fp; errno_t error; if( ( error = fopen_s( &fp , "save.sv" , "rb" ) ) != 0 ){ //エラー }else{ fread( highscore , sizeof( int ) , 10 , fp ); fread( &maxevo , sizeof( int ) , 1 , fp ); fread( &maxcombo , sizeof( int ) , 1 , fp ); fclose( fp ); } } |
進化
前回、タワーディフェンスを作ろうか、な~んてことを書きましたが、いまいち良いアイディアが浮かばず、急遽予定変更です。まあ、気分で作っていくようなことは過去に書きましたが、自分でも全部を変更するとは思いませんでした(笑
で、何を作るかというと、実際に私がやってみて面白いと感じた落ち物パズルです。それをパクオマージュして同じシステムで作ります。どんな感じかというと、同じブロックを3つ以上隣接させると合体し一段階進化して、進化するごとに落とすブロックの種類が増えてきて複雑になっていきます。次の進化をさせるためのブロックは出てこないので、既存のブロックを進化させて上手く隣接させなければなりません。10種類くらいになるとかなり難しくなります。難しいので、従来の落ち物のように次から次へとブロックが落ちてくるものではなく、自分で落とすまでじっくり考える時間があります。
このゲームはブロックを落とすエリアが狭いほうが手頃な面白さになりますが、私はそれをちょっと広げてブロックが15種類くらいまで進化できるようにしようと考えています。というのも、狭いとなんだか物足りない感覚になるんですよね。その物足りなさで「もう1回やろう!」となるので悪いことではないんですが、私としては「もっとお腹いっぱい楽しみたい!」それなら「自分で作ろう!」となったわけです。
というわけでお腹いっぱいになる落ち物パズル作成中です。
実は少し前から取り掛かっています。ただ、特に記事にすることもないかな~と、ブログは放置のまま進めていました。ですが、ここでようやく書くことが見つかったので書いていきます。
これからブロックの隣接判定に入るところです。ここは正直なところどうしようかと悩みました。基点となるブロックから隣接ブロックがあれば1回ずつ範囲を広げて for文で探索という考え方が最初はありましたが、「これって再帰でイケる!」ということに気付き、隣接したブロックをまた基点として関数を再帰呼び出しすることにしました。元々の基点を進化させることを考えると微妙に複雑になりそうな予感です。まともに再帰を使うのは初めてですが、この方法でやってみます。
ざっくりとこんな感じ
void EvolusionSearch( int x , int y ){ 右のブロックが同じなら EvolutionSearch( x+1 , y ); 左のブロックが同じなら EvolutionSearch( x-1 , y ); 上のブロックが同じなら EvolutionSearch( x , y-1 ); 下のブロックが同じなら EvolutionSearch( x , y+1 ); } |
これに、無限再帰にならないための条件や、エリア内の判定、後から全部一度に消すためにマーキングなどを追加します。
それと、ブロックの出現率を乱数だけに任せるのではなく、ある程度均一にそれでいて新しいブロックは出現率を低めに設定したい(すぐに次の進化が出来ないように)ということから、出現率に相関関係を持たせることにしました。relative[ 20 ]をそれぞれのブロックの出現に合わせて増減させます。この値が高ければ出現確率が高いということです。一度出現したらマイナスして再度出現しにくくして、他はプラスします。まだ出てこないブロックは1のまま放置。マイナスよりもプラスを多くすることで、1回落とすごとに合計値は増えていきます。増えれば増えるほど新しいブロックの相対的な出現確率は低くなります。確率が低ければプラスされる確率は高くなるので、何回かブロックを落としていると他のブロックと同程度の確率になっていきます。
わかりやすく書いたつもりですが、文章だけでは理解しにくいかな?プログラミングをやったことない人には読んでも全然わからないんだろうな~、なんて思っています。経験者でもわかりづらいかもしれませんが(笑
言い訳します
どんな言い訳にしようかな~♪
次はまだプロジェクトファイルも作っていない状態です。
「とりあえずタワーディフェンスに取り掛かろうかな?」といった曖昧な構想だけがあります・・・。簡単なものを作るにしても、今までの作品と比べると、かなり手が込んだものになります。なので前もってシステムをしっかり整える必要があります。そのシステムを検討中という言い訳にしておきます(笑。
ありきたりのタワーディフェンスを作ってみるのも悪くありませんが、やはりオリジナリティも出しておきたいわけです。わがままでしょうか?「はい、わがままです(冷静)」でも、ただタワーを設置してレベルアップさせるだけでは味気ないですからね。ざっくりといくつか案はあります。ですが、どれもパっとしない感じなので、早くも低迷中です。
それとプログラミングのサイトを立ち上げようと準備中です。初心者向けのゲーム作成講座みたいなものです。まあ私自身初心者と同等の実力かもしれませんが、それだからこそ、初心者のわからないところも理解出来るつもりでいます。なので難しいところはすっ飛ばして、1つのゲームを完成させることに重点を置いて進めていきます。完成させるということが大切ですね。途中で投げ出すと、それ以降はやる気がなくなりますから。まずはどういったことが出来るのかを把握する程度で、応用や効率化は他のサイトに任せます。というか高度なことはまで解説出来ません!こんな私ですが(汗)あくまで初心者に理解しやすいサイトになればな~と思いながら執筆しています。ある程度まとまってきたら公開しますが、ここで紹介するかは謎です。
このサイト構築もプログラミングが遅れている言い訳です♪
「いち!にの!さんすう」完成!
完成しました。
こちらのページからダウンロード出来ます。
↓
http://tenkomorituuhan2.com/products/sansuu/top.html
内容についてそんなに複雑なことは何もないので、実際にやってみればすぐ理解出来ると思います。早速私も少しレベルを上げたところまで進めています。果たしてこのソフトでどれだけ計算力が上がるのか?まだ未知数ですが、息子にも毎日やらせてみて様子を見ます。
でも、こういうのって「やらされてる感」があると、上達の仕方も鈍くなってくる気がします。私は自分のためでもあるので積極的に取組みますが、息子は苦手な計算を強いられるわけですから、考え方も違ってやらされてる感が強く出てきそうです。なのでダウンロードページにも書きましたが、そこを補う意味でエサをちらつかせます。Ψ(`∀´)Ψケケケ
まあエサというと言葉が悪いかもしれませんが、ちゃんとやったことに対価を与えることは大事かなと考えています。逆にいえば、何もやらなければ何も得られないということですから。そういう基本的な思考は子どもの頃から身に付けていてほしいものです。これを口で説明しても、息子はまだ実感するのは難しいかもしれないので、それなら機会があるごとに実践すれば良いだけのことです。
計算だけではなく、今までもいろいろ実戦形式で試していますが、ことごとく中途半端に終わっています。だからこそ今回は長期目線で続けられるようにと、こなす量を少な目に見積もっています。1日に四則演算各10問、合計40問です。1問10秒に設定していますから、どんなに時間がかかっても400秒程度です。
これで少しは計算に対して苦手意識がなくなれば、作った甲斐もあるというものです。
では、いつものようにソースコードを掲載します。
※ただ漠然とコピペして使用すると、PCが宇宙と交信を始めて宇宙人が攻めてくる原因となりかねません。きちんと理解出来る人が確認した上で使用してください。
細かい処理にはコメントを添えていないことも多いので、何をしているのか分かりづらいかもしれませんが、それも楽しみのひとつということで…(笑)。
#include "DxLib.h" const int wX = 640; //ウインドウサイズ const int wY = 480; const int BSname = 33; //DATA.nameの文字数 const int numPlayer = 5; //プレーヤ数 const int numQ = 10; //問題数 const int absX = 120; //描画基準X const int absY = 130; //描画基準Y const int addY = 56; //描画加算 const int BGcolor = 0x00aaee; //背景色 //プレーヤ情報 struct tagDATA{ char name[ BSname ]; //名前 int lvl[ 4 ]; //レベル [0]:足算 [1]:引算 [2]:掛算 [3]:割算 int exp[ 4 ]; //経験値 [0]:足算... int play[ 4 ]; //挑戦回数 [0]:足算... int correct[ 4 ]; //正解数 [0]:足算... } DATA[ numPlayer ]; int player; //プレーヤ int cs; //カーソル位置 int csh; //カーソル位置横 int mondaiNo; //現在の問題数 int mondaiKind; //問題種類 int time; //制限時間 //キー割当て int key[ ] = { KEY_INPUT_NUMPAD0 , KEY_INPUT_NUMPAD1, KEY_INPUT_NUMPAD2 , KEY_INPUT_NUMPAD3, KEY_INPUT_NUMPAD4 , KEY_INPUT_NUMPAD5, KEY_INPUT_NUMPAD6 , KEY_INPUT_NUMPAD7, KEY_INPUT_NUMPAD8 , KEY_INPUT_NUMPAD9, KEY_INPUT_NUMPADENTER , KEY_INPUT_RETURN, KEY_INPUT_UP , KEY_INPUT_DOWN, KEY_INPUT_LEFT , KEY_INPUT_RIGHT }; int keyinput[ 16 ]; //キー入力状態 //グラフィックハンドル int GHtitle; //タイトル int GHnum[ 15 ]; //数字・記号 int GHwaku; //枠 int GHwaku2; int GHstart[ 5 ]; //問題開始 int GHeffect[ 5 ]; //エフェクト int GHfade; //画面キャプチャ用 //サウンドハンドル int SHselect; //選択 int SHdecision; //決定 int SHcorrect; //正解 int SHmiss; //間違い int SHtimeout; //時間切れ int SHlvlup; //レベルアップ int SHlvldown; //レベルダウン DATEDATA date; //日時取得用構造体 Year Mon Day Hour Min Sec //詳細テキスト出力用 int num1st[ numQ ]; //1つ目の数字 int num2nd[ numQ ]; //2つ目の数字 int numans[ numQ ]; //答え int ans[ numQ ]; //回答 int kindans[ numQ ]; //回答種別 =0:時間切 1:間違 2:正解 3:5秒以内正解 int kindclc; //計算の種別 char markans[ 4 ][ 3 ] = { "T" , "▲" , "〇" , "◎" }; //回答種別記号 char markclc[ 4 ][ 2 ] = { "+" , "-" , "*" , "/" }; //計算種別記号 int ansketa; //回答桁数 int totalflag; //全体のフラグ //プロトタイプ宣言 void Title(); //タイトル void DrawTitle( int flag ); void Menu(); //メニュー void DrawMenu(); void Seiseki(); //成績 void InputCs( int csMax ); //カーソル入力 void FadeIn(); //フェードイン・アウト void FadeOut(); void PrePlay(); //プレイ準備 void Play(); //プレイ void DrawPlay(); //プレイ画面描画 void NumInput(); //テンキー入力 void Effect( int flag ); //エフェクト描画 void Tasu(); //四則演算準備 void Hiku(); void Kakeru(); void Waru(); void Save(); //読み書き void Load(); void Log(); void Error( int flag ); //ファイルエラー処理 //WinMain int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { SetOutApplicationLogValidFlag( FALSE ); //ログ出力しない SetMainWindowText( "さんすう" ); //ウインドウタイトル指定 SetGraphMode( wX , wY , 32 ); //ウインドウの大きさとカラービット数指定 ChangeWindowMode( TRUE ); //ウインドウで表示 SetDrawScreen( DX_SCREEN_BACK ); //ちらつき防止設定 SetWaitVSyncFlag( TRUE ); //フレーム同期 SetMouseDispFlag( TRUE ); //マウスカーソル表示 SetWindowUserCloseEnableFlag( FALSE ); //×ボタンで終了しない SetAlwaysRunFlag( TRUE ); //ウインドウ非アクティブ時動作 if( DxLib_Init() == -1 ) return -1; //フォント設定 SetFontSize( 32 ); SetFontThickness( 1 ); ChangeFontType( DX_FONTTYPE_ANTIALIASING ); ChangeFont( "メイリオ" ); //グラフィック LoadDivGraph( "img/num.png" , 15 , 15 , 1 , 48 , 48 , GHnum ); LoadDivGraph( "img/start.png" , 5 , 1 , 5 , 200 , 64 , GHstart ); LoadDivGraph( "img/effect.png" , 5 , 5 , 1, 128 , 128 , GHeffect ); GHtitle = LoadGraph( "img/title.png" ); GHwaku = LoadGraph( "img/waku01.png" ); GHwaku2 = LoadGraph( "img/waku02.png" ); //サウンド SHselect = LoadSoundMem( "img/select.wav" ); SHdecision = LoadSoundMem( "img/decision.wav" ); SHcorrect = LoadSoundMem( "img/correct.wav" ); SHmiss =LoadSoundMem( "img/miss.wav" ); SHtimeout = LoadSoundMem( "img/timeout.wav" ); SHlvlup = LoadSoundMem( "img/lvlup.wav" ); SHlvldown = LoadSoundMem( "img/lvldown.wav" ); //プレーヤ情報読込み Load(); //メインループ while( totalflag >-1 ){ switch( totalflag ){ case 0: //タイトル Title(); break; case 1: //メニュー Menu(); break; case 2: //プレイ PrePlay(); break; case 3: //成績 Seiseki(); break; } FadeOut(); } Save(); DxLib_End() ; // DXライブラリ使用の終了処理 return 0 ; // ソフトの終了 } void Title(){ //タイトル画面/////////////////////////////// cs = 0; DrawTitle( 0 ); FadeIn(); while( 1 ){ InputCs( 6 ); //カーソル操作 //終了 if( ( cs == 5 )&&( ( keyinput[10] == 1 )||( keyinput[11] == 1 ) ) ){ totalflag = -1; break; } DrawTitle( 0 ); ScreenFlip(); //enterキー押下 if( ( keyinput[10] == 1 )||( keyinput[11] == 1 ) ){ PlaySoundMem( SHdecision , DX_PLAYTYPE_BACK ); //メニューへ if( DATA[cs].name[0] != '\0' ){ player = cs; totalflag = 1; break; } else { //名前入力 DrawTitle( 1 ); ScreenFlip(); ZeroMemory( &DATA[ cs ] , sizeof( tagDATA ) ); KeyInputString( absX+15 , absY+cs*addY+8 , BSname -1 , DATA[ cs ].name , FALSE ); DrawTitle( 0 ); ScreenFlip(); } } } } void DrawTitle( int flag ){ //タイトル画面描画///////////////////// DrawBox( 0 , 0 , wX , wY , BGcolor , TRUE ); DrawGraph( absX , 10 , GHtitle , TRUE ); //プレーヤ名描画 for( int i=0 ; i<6 ; i++ ){ DrawGraph( absX , absY+i*addY , GHwaku , TRUE ); if( i == 5 ){ DrawString( absX+15 , absY+i*addY+8 , "終了" , 0xffffff ); } else if( ( flag == 1 )&&( cs == i ) ){ continue; } else if( DATA[i].name[0] == '\0' ){ DrawString( absX+15 , absY+i*addY+8 , "新しく作成" , 0xffff00 ); } else { DrawString( absX+15 , absY+i*addY+8 , DATA[i].name , 0xffffff ); } } //カーソル描画 if( flag != 1 ){ SetDrawBlendMode( DX_BLENDMODE_ALPHA , 70 ); DrawBox( 0 , absY+cs*addY , wX , absY+cs*addY+48 , 0xffffff , TRUE ); SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ); } } void Menu(){ //メニュー画面//////////////////////////////// cs = 0; csh = 0; DrawMenu(); FadeIn(); while( 1 ){ InputCs( 4 ); //カーソル操作 //enterキー押下 if( ( keyinput[10] == 1 )||( keyinput[11] == 1 ) ){ PlaySoundMem( SHdecision , DX_PLAYTYPE_BACK ); switch( cs+csh*4 ){ case 0: //足し算 mondaiKind = 0; totalflag = 2; break; case 1: //引き算 mondaiKind = 1; totalflag = 2; break; case 2: //掛け算 mondaiKind = 2; totalflag = 2; break; case 3: //割り算 mondaiKind = 3; totalflag = 2; break; case 4: //四則演算 mondaiKind = 4; totalflag = 2; break; case 5: //成績 totalflag = 3; break; case 6: //消す SetDrawBlendMode( DX_BLENDMODE_ALPHA , 180 ); DrawBox( 0 , 0 , wX , wY , 0x000000 , TRUE ); DrawFormatString( 100 , 180 , 0xffffff , "%sさんの記録がすべて消えます" , DATA[ player ].name ); DrawString( 100 , 240 , "よろしいですか? はい[Y] いいえ[N]" , 0xffffff ); ScreenFlip(); while( 1 ){ WaitVSync( 1 ); if( CheckHitKey( KEY_INPUT_Y ) == 1 ){ PlaySoundMem( SHdecision , DX_PLAYTYPE_BACK ); ZeroMemory( &DATA[ player ] , sizeof( tagDATA ) ); totalflag = 0; break; } else if( CheckHitKey( KEY_INPUT_N ) == 1 ){ break; } } break; case 7: //戻る totalflag = 0; } break; } DrawMenu(); ScreenFlip(); } } void DrawMenu(){ //メニュー画面描画///////////////////// DrawBox( 0 , 0 , wX , wY , BGcolor , TRUE ); DrawFormatString( absX , 50 , 0xffffff , "%sさん" , DATA[ player ].name ); DrawFormatString( absX , 100 , 0x00ff00 , "LV(+)%d LV(-)%d" , DATA[ player ].lvl[ 0 ] , DATA[ player ].lvl[ 1 ] ); DrawFormatString( absX , 140 , 0x00ff00 , "LV(×)%d LV(÷)%d" , DATA[ player ].lvl[ 2 ] , DATA[ player ].lvl[ 3 ] ); char strdraw[ 8 ][ 15 ] = { "足し算10問" , "引き算10問" , "掛け算10問" , "割り算10問" , "四則演算40問" , "成績" , "消す" , "戻る" }; for( int x=0 ; x<2 ; x++ ){ for( int y=0 ; y<4 ; y++ ){ DrawGraph( absX+x*200 , absY+y*addY+90 , GHwaku2 , TRUE ); DrawString( absX+x*200+15 , absY+y*addY+98 , strdraw[ y+x*4 ] , 0xffffff ); } } //カーソル描画 SetDrawBlendMode( DX_BLENDMODE_ALPHA , 70 ); DrawBox( absX+csh*200 , absY+cs*addY+90 , absX+csh*200+190 , absY+cs*addY+138 , 0xffffff , TRUE ); SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ); } void Seiseki(){ //成績///////////////////////////////////// DrawBox( 0 , 0 , wX , wY , BGcolor , TRUE ); char str[ 4 ][ 7 ] = { "足し算" , "引き算" , "掛け算" , "割り算" }; DrawFormatString( absX , 50 , 0xffffff , "%sさんの成績" , DATA[ player ].name ); for( int i = 0 ; i < 4 ; i++ ){ DrawString( absX , 100+i*80 , &str[ i ][ 0 ] , 0xffffff ); int cr = DATA[ player ].correct[ i ]; int pl = DATA[ player ].play[ i ]; int per; if( pl == 0 ){ per = 0; } else { per = cr * 100 / pl; } DrawFormatString( absX , 140+i*80 , 0x00ff00 , "LV %d 正解 %d/%d 正解率 %d%" , DATA[ player ].lvl[ i ] , cr , pl , per ); } FadeIn(); WaitKey(); totalflag = 1; } void InputCs( int csMax ){ //カーソル操作//////////////////////////// int flag = 0; while( flag == 0 ){ //入力があるまで待機 WaitVSync( 1 ); for( int i=10 ; i<16 ; i++ ){ if( CheckHitKey( key[i] ) == 1 ){ keyinput[i]++; if( keyinput[i] == 1 ){ flag = 1; switch ( i ){ case 12: PlaySoundMem( SHselect , DX_PLAYTYPE_BACK ); cs--; if( cs < 0 ) cs = csMax-1; break; case 13: PlaySoundMem( SHselect , DX_PLAYTYPE_BACK ); cs = ( cs+1 )%csMax; break; case 14: PlaySoundMem( SHselect , DX_PLAYTYPE_BACK ); csh--; if( csh < 0 ) csh = 1; break; case 15: PlaySoundMem( SHselect , DX_PLAYTYPE_BACK ); csh = ( csh+1 )%2; break; } } } else { keyinput[i] = 0; } } } } void FadeIn(){ //フェードイン////////////////////////////// GHfade = MakeGraph( wX , wY ); GetDrawScreenGraph( 0 , 0 , wX-1 , wY-1 , GHfade ); for( int i=0 ; i<256 ; i = i+10 ){ DrawBox( 0 , 0 , wX , wY , 0xffffff , TRUE ); SetDrawBlendMode( DX_BLENDMODE_ALPHA , i ); DrawGraph( 0 , 0 , GHfade , TRUE ); SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ); ScreenFlip(); } DeleteGraph( GHfade ); } void FadeOut(){ //フェードアウト///////////////////////// GHfade = MakeGraph( wX , wY ); GetDrawScreenGraph( 0 , 0 , wX-1 , wY-1 , GHfade ); for( int i=255 ; i>0 ; i = i-10 ){ DrawBox( 0 , 0 , wX , wY , 0xffffff , TRUE ); SetDrawBlendMode( DX_BLENDMODE_ALPHA , i ); DrawGraph( 0 , 0 , GHfade , TRUE ); SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ); ScreenFlip(); } DeleteGraph( GHfade ); } void PrePlay(){ //プレイ準備//////////////////////////// mondaiNo = 0; DrawBox( 0 , 0 , wX , wY , BGcolor , TRUE ); FadeIn(); int roop = 1; if( mondaiKind == 4 ) roop = 4; for( int i = 0 ; i < roop ; i++ ){ kindclc = mondaiKind; if( kindclc == 4 ) kindclc = mondaiNo / numQ; Play(); } } void Play(){ //プレイ/////////////////////////////////////// switch( kindclc ){ //計算種別 case 0: Tasu(); break; case 1: Hiku(); break; case 2: Kakeru(); break; case 3: Waru(); break; } // "スタート!" 描画 for( int i = 3 ; i > 0 ; i-- ){ for( int t = 0 ; t < 60 ; t++ ){ DrawBox( 0 , 0 , wX , wY , BGcolor , TRUE ); DrawGraph( 110 , 100 , GHstart[ kindclc ] , TRUE ); DrawGraph( 310 , 100 , GHstart[ 4 ] , TRUE ); DrawExtendGraph( 256 , 200 , 384 , 328 , GHnum[ i ] , TRUE ); ScreenFlip(); } } for( int i=0 ; i < numQ ; i++ ){ //10問 int flagQ = 0; time = 600; ans[ i ] = 0; ansketa = 0; while( time > -1 ){ //タイムがなくなるまで NumInput(); //キー入力 for( int j = 0 ; j<16 ; j++ ){ if( keyinput[ j ] == 1 ){ switch( j ){ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: if( ansketa < 9 ){ ans[ i ] = ans[ i ]*10; //回答数字追加 ans[ i ] = ans[ i ] + j; ansketa++; } break; case 10: case 11: //回答決定 flagQ = 1; break; case 14: //回答全取消し ans[ i ] = 0; ansketa = 0; break; case 15: //回答1桁取消し ans[ i ] = ans[ i ]/10; ansketa = ( ansketa < 1 ) ? 0 : ansketa-1; break; } } } //画面描画 DrawBox( 0 , 0 , wX , wY , BGcolor , TRUE ); DrawPlay(); ScreenFlip(); //正否判定 if( flagQ == 1 ){ if( ans[ i ] == numans[ i ] ){ PlaySoundMem( SHcorrect , DX_PLAYTYPE_BACK ); Effect( 0 ); DATA[ player ].correct[ kindclc ]++; if( time < 300 ){ kindans[ i ] = 2; DATA[ player ].exp[ kindclc ]++; } else { kindans[ i ] = 3; DATA[ player ].exp[ kindclc ] += 2; } } else { PlaySoundMem( SHmiss , DX_PLAYTYPE_BACK ); Effect( 1 ); kindans[ i ] = 1; DATA[ player ].exp[ kindclc ]--; } break; } time--; } //時間切れ if( flagQ == 0 ){ PlaySoundMem( SHtimeout , DX_PLAYTYPE_BACK ); Effect( 2 ); kindans[ i ] = 0; DATA[ player ].exp[ kindclc ] -= 2; } mondaiNo++; DATA[ player ].play[ kindclc ]++; //レベルアップ int lv = kindclc+2; if( DATA[ player ].exp[ kindclc ] >= 5*lv ){ PlaySoundMem( SHlvlup , DX_PLAYTYPE_BACK ); Effect( 3 ); DATA[ player ].exp[ kindclc ] -= 5*lv; DATA[ player ].lvl[ kindclc ]++; } //レベルダウン if( DATA[ player ].exp[ kindclc ] < -4 ){ if( DATA[ player ].lvl[ kindclc ] > 0 ){ PlaySoundMem( SHlvldown , DX_PLAYTYPE_BACK ); Effect( 4 ); DATA[ player ].lvl[ kindclc ]--; } DATA[ player ].exp[ kindclc ] += 5; } } //ログ書込み Log(); totalflag = 1; } void DrawPlay(){ //プレイ画面描画///////////////////// int tempmondaiNo = mondaiNo % 10; DrawFormatString( 100 , 20 , 0xffff00 , "%d問目" , mondaiNo + 1 ); DrawBox( 98 , 198 , 502 , 242 , 0x666666 , TRUE ); DrawBox( 100 , 200 , 100+400*time/600 , 240 , GetColor( 255*(600-time)/600 , 255*time/600 , 0 ) , TRUE ); //問題描画 int numGraph[ 8 ]; //数字 int numtemp = num1st[ tempmondaiNo ]; int keta = 1; //桁数 int dspX = 0; //描画位置 while( 1 ){ //数値1描画 numGraph[ keta ] = numtemp%10; if( numtemp/10 == 0 ){ break; } else { numtemp = numtemp/10; keta++; } } for( int i = keta ; i > 0 ; i-- ){ DrawGraph( 50+dspX , 100 , GHnum[ numGraph[ i ] ] , TRUE ); dspX = dspX+40; } DrawGraph( 50+dspX , 100 , GHnum[ 10+kindclc ] , TRUE ); //記号描画 dspX = dspX+40; numtemp = num2nd[ tempmondaiNo ]; //数値2描画 keta = 1; while( 1 ){ numGraph[ keta ] = numtemp%10; if( numtemp/10 == 0 ){ break; } else { numtemp = numtemp/10; keta++; } } for( int i = keta ; i > 0 ; i-- ){ DrawGraph( 50+dspX , 100 , GHnum[ numGraph[ i ] ] , TRUE ); dspX = dspX+40; } DrawGraph( 50+dspX , 100 , GHnum[ 14 ] , TRUE ); //「=」描画 int tempans = ans[ mondaiNo%numQ ]; //回答描画 for( int i = 0 ; i < ansketa ; i++ ){ DrawGraph( 400-i*40 , 300 , GHnum[ tempans%10 ] , TRUE ); tempans = tempans/10; } DrawString( 50 , 380 , "テンキーで入力 [enter]で決定" , 0xffffff ); DrawString( 50 , 430 , "[←]:全取消し [→]:1桁取消し" , 0xffffff ); } void NumInput(){ //テンキー入力///////////////////////////// for( int i = 0 ; i < 16 ; i++ ){ if( CheckHitKey( key[ i ] ) == 1 ){ keyinput[ i ]++; } else { keyinput[ i ] = 0; } } } void Effect( int flag ){ //エフェクト描画////////////////////////// GHfade = MakeGraph( wX , wY ); GetDrawScreenGraph( 0 , 0 , wX-1 , wY-1 , GHfade ); int GHtemp; switch( flag ){ case 0: case 1: if( flag == 0 ){ GHtemp = GHeffect[ 0 ]; //正解 } else { GHtemp = GHeffect[ 1 ]; //ミス } for( int i = 64 ; i < 160 ; i += 8 ){ DrawGraph( 0 , 0 , GHfade , TRUE ); DrawExtendGraph( 320-i , 240-i , 320+i , 240+i , GHtemp , TRUE ); ScreenFlip(); } WaitVSync( 10 ); break; case 2: case 3: if( flag == 2 ){ GHtemp = GHeffect[ 2 ]; //時間切れ } else { GHtemp = GHeffect[ 3 ]; //レベルアップ } for( int i = 16 ; i < 400 ; i += 20 ){ DrawGraph( 0 , 0 , GHfade , TRUE ); DrawExtendGraph( 160 , wY-i , 480 , wY-i+320 , GHtemp , TRUE ); ScreenFlip(); } WaitVSync( 10 ); break; case 4: //レベルダウン for( int i = 16 ; i < 400 ; i += 20 ){ DrawGraph( 0 , 0 , GHfade , TRUE ); DrawExtendGraph( 160 , i-320 , 480 , i , GHeffect[ 4 ] , TRUE ); ScreenFlip(); } WaitVSync( 10 ); break; } DeleteGraph( GHfade ); } void Tasu(){ //足し算準備//////////////////////////////// int num = DATA[ player ].lvl[ 0 ] + 4; //レベルによる調整 for( int i=0 ; i<10 ; i++ ){ //10問 int r = GetRand( 9 ); if( r < 7 ){ //70%は数値が高くなるように設定 num1st[ i ] = num/2 + GetRand( num/2 ); } else { num1st[ i ] = GetRand( num ); } r = GetRand( 9 ); if( r < 7 ){ num2nd[ i ] = num/2 + GetRand( num/2 ); } else { num2nd[ i ] = GetRand( num ); } numans[ i ] = num1st[ i ] + num2nd[ i ]; } } void Hiku(){ //引き算準備/////////////////////////////// int num = DATA[ player ].lvl[ 1 ] + 4; //レベルによる調整 for( int i=0 ; i<10 ; i++ ){ //10問 int r = GetRand( 9 ); if( r < 7 ){ //70%は数値が高くなるように設定 num1st[ i ] = num/2 + GetRand( num/2 ); } else { num1st[ i ] = GetRand( num ); } r = GetRand( 9 ); if( r < 4 ){ //40% num2nd[ i ] = num/2 + GetRand( num/2 ); } else { num2nd[ i ] = GetRand( num ); } if( num2nd[ i ] > num1st[ i ] ){ //2番目の数値が大きければ入換え int temp = num1st[ i ]; num1st[ i ] = num2nd[ i ]; num2nd[ i ] = temp; } numans[ i ] = num1st[ i ] - num2nd[ i ]; } } void Kakeru(){ //掛け算準備//////////////////////////// int num = DATA[ player ].lvl[ 2 ] + 4; //レベルによる調整 for( int i=0 ; i<10 ; i++ ){ //10問 int r = GetRand( 9 ); if( r < 7 ){ //70%は数値が高くなるように設定 num1st[ i ] = num/2 + GetRand( num/2 ); } else { num1st[ i ] = GetRand( num ); } r = GetRand( 9 ); if( r < 7 ){ num2nd[ i ] = num/2 + GetRand( num/2 ); } else { num2nd[ i ] = GetRand( num ); } numans[ i ] = num1st[ i ] * num2nd[ i ]; } } void Waru(){ //割り算準備////////////////////////////// int num = DATA[ player ].lvl[ 3 ] + 4; //レベルによる調整 for( int i=0 ; i<10 ; i++ ){ //10問 int r = GetRand( 9 ); if( r < 7 ){ //70%は数値が高くなるように設定 numans[ i ] = num/2 + GetRand( num/2 ); } else { numans[ i ] = GetRand( num ); } r = GetRand( 9 ); if( r < 5 ){ //50% num2nd[ i ] = num/2 + GetRand( num/2 ); } else { num2nd[ i ] = GetRand( num ); } if( num2nd[ i ] == 0 ) num2nd[ i ] = num; //2番目の数値が0なら入換え num1st[ i ] = numans[ i ] * num2nd[ i ]; } } void Save(){ //書込み///////////////////////////////////// FILE *fp; errno_t error; if( (error = fopen_s( &fp , "data.sv" , "w" )) !=0 ){ Error( 0 ); } else { fwrite( DATA , sizeof( tagDATA ) , numPlayer , fp ); fclose( fp ); } } void Load(){ //読込み//////////////////////////////////// FILE *fp; errno_t error; if( ( error = fopen_s( &fp , "data.sv" , "r" )) != 0 ){ Error( 1 ); } else { fread( DATA , sizeof( tagDATA ) , numPlayer , fp ); fclose( fp ); } } void Log(){ //詳細ログ書込み//////////////// //登録名に".txt"を追加 char fn[50]; //ファイル名 char fntxt[5] = ".txt"; //ファイル拡張子 int index; for( index = 0 ; DATA[ player ].name[ index ] != '\0' ; index++ ){ fn[ index ] = DATA[ player ].name[ index ]; } for( int i=0 ; i<5 ; i++ ){ fn[ index+i ] = fntxt[ i ]; } GetDateTime( &date ); //日付を取得 //ログ書込み FILE *fp; errno_t error; if( ( error = fopen_s( &fp , fn , "a" )) != 0 ){ Error( 2 ); } else { fprintf( fp , "%d/%d/%d/%d:%d\n" , date.Year , date.Mon , date.Day , date.Hour , date.Min ); for( int i=0 ; i<numQ ; i++ ){ fprintf( fp , "%s %d%s%d=%d %d\n" , &markans[ kindans[ i ] ][ 0 ] , num1st[ i ] , &markclc[ kindclc ][ 0 ] , num2nd[ i ] , ans[ i ] , numans[ i ]); } fprintf( fp , "\n" ); fclose( fp ); } } void Error( int flag ){ //ファイルエラー処理 SetDrawBlendMode( DX_BLENDMODE_ALPHA , 180 ); DrawBox( 0 , 0 , wX , wY , 0x000000 , TRUE ); SetDrawBlendMode( DX_BLENDMODE_NOBLEND , 0 ); switch( flag ){ case 0: DrawString( 100 , 180 , "ファイルの書込みに失敗しました" , 0xffffff ); break; case 1: DrawString( 100 , 140 , "ファイルの読込みに失敗しました" , 0xffffff ); DrawString( 100 , 180 , "初回起動時はこのメッセージが表示されます" , 0xffffff ); break; case 2: DrawString( 100 , 180 , "ログの書込みに失敗しました" , 0xffffff ); break; } DrawString( 100 , 220 , "何かキーを押してください" , 0xffffff ); ScreenFlip(); WaitKey(); } |
けいさんソフト(仮) ⇒
「けいさんソフト(仮)」は「いち!にの!さんすう」という名前に正式に決定しました。
細かいことを拘らなければ、既に実用的なレベルまで完成しています。BGMをどうしようかと迷っています。出題時は邪魔になるので無しで良いのですが、タイトル画面にBGMを流すかどうか…。
まあ流すとしても、軽く聞き流せる程度のライトなものになりますが。一度作り始めましたが、あらぬ方向へ進んで、よほど計算に似つかわしくないBGMになってしまったので、そのまま放置しました(笑)。フリーの素材を借りるのも手ですが、基本的には自分で作りたい派です。何曲も作るわけではないですしね。なので借りるくらいなら無しでも良いかな?という考えです。
ちょっと今回は新たな発見がありました。といっても、知っている人はふつーに知っていることです。Cのコーディング画面で関数の左側に「-」が表示されていますが、そこを押すと、・・・な・な・なんと!?関数が折り畳める!!!これって何年か前にインストールしたときからある機能なんでしょうか?私が気付かなかっただけ!?折り畳むと関数の定義部分が隠れ関数名だけになります。表示される行が少なくなるので、「あの関数を変更しよう」なんてとき、目的の関数を探すのがとても楽です。
この機能は関数だけではなく、宣言や構造体などにも反映されます。長いプログラムも全部畳んでしまえば非常にスッキリします。それと、これは知らない人が多いかもしれませんが、折り畳んだ状態でコピーしてもペースト時に展開されます。私は更新したら日ごとにテキストファイルにバックアップを取っているので、試しに畳んだままコピペしたら「お~~~!」ってなりました。
いい加減に使っていますから、まだまだ知らない便利な機能がたくさんあるんでしょうね。