#define _WIN32_WINNT 0x0400 /// for WM_MOUSEWHEEL #define _WIN32_WINDOWS 0x0401 /// for WM_MOUSEWHEEL /* winuser.h ( windows.h が include する) #if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) #define WM_MOUSEWHEEL 0x020A */ #include #include #include #include"resource.h" #include"WaveResource.h" #include"MidiResource.h" // winmm.lib をリンクする #pragma comment(lib,"winmm") /// 曲を変える(かもしれない)時にメインスレッドに送る /// (MCIはオープンしたスレッド以外からは操作できない) #define WM_CHANGE_BGM WM_APP /// ゲームオーバーした時にメインスレッドに送る /// (WAVE & MIDI 再生を要求) #define WM_GAMEOVER WM_APP+1 /// 入力キー取得ウィンドウを閉じる時にキーコンフィグダイアログに送るメッセージ /// ( wParam , lParam には WM_KEYDOWN のそれが入る) #define WM_INPUT WM_APP+2 // メインスレッドにミューテックスの所有権取得を要求するメッセージ #define WM_MUTEX WM_APP+3 // ミューテックスオブジェクトの名前 #define MUTEX_NAME "MutexObject of TOKOPUYO" #define OJAMA 0x007f7f7f /// おじゃまぷよ #define KATA 0x00ffffff /// 固ぷよ // ピースの横と縦のマス数 #define PW 3 #define PH 3 // ゲームフィールドの横と縦のマス数 #define FW 6 #define FH 13 /// 最上段は表示しない // マスのピクセル数 #define CW 30 #define CH 30 /* MovePiece 関数の引数 */ #define MOVE_LEFT 2 #define MOVE_RIGHT 4 #define MOVE_DOWN 8 /// TurnPiece 関数の引数 #define TURN_LEFT 16 #define TURN_RIGHT 32 #define TURN_OVER 64 /// 反転 DWORD Field[FW][FH]={0}; /// ゲームフィールドの色 DWORD Piece[PW][PH]={0}; /// 移動中のピースの色 POINT Pos={0,0}; /* 移動中のピースの位置 */ DWORD Next[PW][PH]={0}; /// 次のピースの色 DWORD Add=0; /// 新しく獲得した点数 DWORD Score=0; // 獲得点数の合計 DWORD PlayTime=0; // プレイ時間 BOOL GameOver=FALSE; // TRUE になるのはゲームオーバーからニューゲーム開始前まで char State[32]="いっきま〜す"; /// 状態明示文字列 /// プレイヤー設定項目 DWORD Ojama=0; /// おじゃまぷよの有無と種類 DWORD OjaMax=4; /// おじゃまぷよが一度に出現する最大数 DWORD OjaRand=10; /// おじゃまぷよの頻度 DWORD Variety=5; /// 何種類の色ぷよが出現するのか? DWORD Delete=4; /// いくつ以上色ぷよが隣接すれば消えるのか? DWORD Speed=800; /// 初期落下スピード typedef enum{TL=0,TR,TO,ML,MR,MD,PA,NG,KC,KEYNUM}KEY; /// キー操作定数 /// 操作キー WPARAM Key[KEYNUM]={'A','F',VK_SPACE,VK_LEFT,VK_RIGHT,VK_DOWN,VK_ESCAPE,VK_RETURN,VK_F1}; BOOL MouseControl=TRUE; /// マウスコントロールの自動(TRUE) or 無効(FALSE) HINSTANCE hInst; /// インスタンスハンドル HFONT hFont; /// フォントハンドル /// WAVE HWAVEOUT hWaveOut[9]; WAVEFORMATEX wfe[9]; WAVEHDR whdr[9]; /// MIDI const enum{SETUP=0,PLAY,PINCH}; MCI_OPEN_PARMS Mop[3]; MCI_PLAY_PARMS Mpp; /* Piece[][] 内のぷよの最上部の位置を返す */ int GetPieceTop(void) { for(int y=0;y=0;y--){ for(int x=0;x=0;x--){ for(int y=0;y=0 , (Pos.y)+y>=0 for(x=0;x=0 && (Pos.y)+y>=0 && Field[(Pos.x)+x-1][(Pos.y)+y]) return FALSE; } // ↑一つ左にぷよがいる } Pos.x--; return TRUE; case MOVE_RIGHT: right=GetPieceRight(); if((Pos.x)+right >= FW-1) return FALSE; for(y=0;y=0 for(x=0;x=0 && Field[(Pos.x)+x+1][(Pos.y)+y]) return FALSE; } // ↑一つ右にぷよがいる } Pos.x++; return TRUE; case MOVE_DOWN: bottom=GetPieceBottom(); if((Pos.y)+bottom >= FH-1) return FALSE; for(y=0;y=0 , (Pos.y)+y+1=0 && (Pos.y)+y+1=FW || iy>=FH || (iy>=0 && Field[ix][iy])) return FALSE; // iy>=0 は添字の有効性を調べている↑ } } } /// 回転可能 for(y=0;y=0 && Field[x-1][y]==c) Count(x-1,y,n); if(y-1>=0 && Field[x][y-1]==c) Count(x,y-1,n); } /// ぷよを消す(Count関数の応用) /// f[FW][FH]:ゲームフィールドの色 /// x:現在のx座標 , y:現在のy座標 void Vanish(DWORD f[FW][FH],int x,int y) { DWORD c=f[x][y]; /// 自分の色 f[x][y]=0; /// 色ぷよを消す /// 固ぷよ→おじゃまぷよ or おじゃまぷよ→消す if(x+1=0){ if(f[x-1][y]==KATA) f[x-1][y]=OJAMA; else if(f[x-1][y]==OJAMA) f[x-1][y]=0; } if(y-1>=0){ if(f[x][y-1]==KATA) f[x][y-1]=OJAMA; else if(f[x][y-1]==OJAMA) f[x][y-1]=0; } if(x+1=0 && f[x-1][y]==c) Vanish(f,x-1,y); if(y-1>=0 && f[x][y-1]==c) Vanish(f,x,y-1); } /// ゲームフィールドの色をコピーする /// to[FW][FH]:コピー先の配列 , from[FW][FH]:コピー元の配列 void CopyField(DWORD to[FW][FH],DWORD from[FW][FH]) { for(int y=0;y=Delete){Vanish(f,x,y); d+=n;} /// 消去した色ぷよの数をカウント } /// ↑ぷよを消す } } CopyField(Field,f); /// 処理結果を反映させる return d; } /// 消去する時のエフェクトを Field[][] の最上位バイトに指定する /// werrrrrr /// w:Weepフラグ /// e:Expansionフラグ /// r:膨張量(eが1の時だけ使う) /// 泣き顔を表示させる /// sub[FW][FH]:これから消えるぷよの集合 void Weep(DWORD sub[FW][FH]) { for(int y=0;y7)?7:rensa; waveOutWrite(hWaveOut[magic],&whdr[magic],sizeof(WAVEHDR)); Add=d*40*(DWORD)pow(2,rensa-1); Score+=Add; rensa++; for(int y=0;y=pinch){ /// 通常からピンチ mciSendCommand(Mop[PLAY].wDeviceID,MCI_STOP,0,0); mciSendCommand(Mop[PLAY].wDeviceID,MCI_SEEK,MCI_SEEK_TO_START,0); mciSendCommand(Mop[PINCH].wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD_PTR)&Mpp); }else if(i>=pinch && n=0;y--){ /// 下から調べる if(Field[x][y]==0){ /// 空のマスが見つかれば上にある(かもしれない)ぷよを探す for(iy=y-1;iy>=0 && Field[x][iy]==0;iy--); /// 空のマスを読み飛ばす if(iy<0) break; /// 上にぷよが無い(次の列へ) n++; /// ぷよを落とした列数 for(iy=y;iy>=0;iy--){ /// ぷよを1マスだけ落とす if(iy-1>=0) Field[x][iy]=Field[x][iy-1]; else Field[x][iy]=0; /* 0 行より上はないので 0 で埋める */ } break; /// 次の列へ } } } return n; } /// 浮いているを1マスずつ落とす /// hWnd:ウィンドウのハンドル /// 戻り値:待機した合計時間 DWORD FallPuyoAuto(HWND hWnd) { for(DWORD sleep=0;FallPuyo();sleep+=100){ /// 浮いているぷよが無くなるまで続ける InvalidateRect(hWnd,NULL,FALSE); Sleep(100); } return sleep; } /// おじゃまぷよをランダムに作って落とす /// hWnd:ウィンドウのハンドル /// 戻り値:待機した合計時間 DWORD FallOjama(HWND hWnd) { if(Ojama==0) return 0; if(rand()%OjaRand) return 0; /// ランダムに生成 int r=rand()%OjaMax+1; /// 作りたいおじゃまぷよの数 int fx[FW],n,a,b; DWORD sleep=0; while(1){ n=0; for(int x=0;x=0 && pt.x=0 && pt.y=0 は添字の有効性を調べている if(Piece[x][y] && (Pos.y)+y>=0){ /// 最上段より上にはみ出す可能性を考慮 Field[(Pos.x)+x][(Pos.y)+y]=Piece[x][y]; } Piece[x][y]=0; } } } // 再初期化する // hWnd:ウィンドウのハンドル void ReInitialize(HWND hWnd) { for(int y=0;y>31 ) #define EXP(field) ( (field & 0x40000000)>>30 ) #define RAD(field) ( (field & 0x3f000000)>>24 ) // 描画する // hdc:デバイスコンテキストのハンドル void Paint(HDC hdc) { int x,y,ptx,pty,r; HPEN hPen,hOldPen; HBRUSH hBrush,hOldBrush; // ゲームフィールドのぷよ for(y=1;y=60*1000){ // ↓一分ごとに待機時間を減らしてゆく if(sleep+500>Speed && sleep>100) sleep-=100; /// Speed-500 <= sleep <= Speed (sleep>=100) else sleep=Speed; /// 元のスピードに戻す minute=0; } if(!MovePiece(MOVE_DOWN)){ // 現在移動中のブロックが下段に達したら以下を実行 PieceToField(); FallPuyoAuto(hWnd); /// 浮いているぷよを1マスずつ落とす do{ n=DeletePuyoEffect(hWnd); /// 条件を満たせばぷよを消す SendMessage(hWnd,WM_CHANGE_BGM,0,0); /// 曲のチェック if(n>0){ Add=0; /// 新しく獲得した点数を 0 に戻す InvalidateRect(hWnd,NULL,FALSE); Sleep(500); /// 合計1500ms待機したことになる FallPuyoAuto(hWnd); /// 落としてから100ms待機 } }while(n>0); /// 次の連鎖に移行するまで最低1600msかかる if(Field[2][1]){ /* ゲーム終了 */ SendMessage(hWnd,WM_GAMEOVER,0,0); /// WAVE & MIDI 再生 progress=timeGetTime()-beforeTime; /// 最終的なプレイ時間を計算する PlayTime+=progress; wsprintf(State,"ばたんきゅ〜"); InvalidateRect(hWnd,NULL,FALSE); ReleaseMutex(hMutex); // ミューテックスの所有権を解放 if(hMutex) CloseHandle(hMutex); // 全てのハンドルを閉じない限り SendMessage(hWnd,WM_MUTEX,0,0); // ミューテックスは破棄されない GameOver=TRUE; return 0; // ゲーム終了でスレッドを破棄する } FallOjama(hWnd); /// おじゃまぷよを作って落とす(ランダムに生成) NewPiece(hWnd,FALSE); } InvalidateRect(hWnd,NULL,FALSE); if(signal==WAIT_OBJECT_0){ // メインスレッドからの介入により待機を解除した ReleaseMutex(hMutex); // ミューテックスの所有権を解放 SendMessage(hWnd,WM_MUTEX,0,0); // メインスレッドにミューテックスの所有権取得を要求 } } } /// マウス制御スレッド /// カーソルによる左右下移動に対応させる(左右中ボタンは関与しない=常に有効) /// lpParameter:ウィンドウのハンドル DWORD WINAPI MouseProc(LPVOID lpParameter) { HWND hWnd=(HWND)lpParameter; POINT pt; while(!GameOver){ Sleep(50); if(!MouseControl) continue; /// マウスコントロール無効 if(hWnd!=GetForegroundWindow()) continue; GetCursorPos(&pt); /// ↑ウィンドウがアクティブになるまで待つ ScreenToClient(hWnd,&pt); /// ↓ゲームフィールド内部 if(pt.x>=CW && pt.x<(FW+1)*CW && pt.y>=CH && pt.y= (Pos.x+3)*CW) PostMessage(hWnd,WM_KEYDOWN,VK_RIGHT,0); if(pt.y >= (Pos.y+2)*CH) PostMessage(hWnd,WM_KEYDOWN,VK_DOWN,0); } } return 0; } /// ルール設定ダイアログボックスのプロシージャ LRESULT CALLBACK RuleDlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam) { static DWORD oja,max,rnd,var,del,spd; static MCI_PLAY_PARMS mpp; switch(uMsg){ case WM_INITDIALOG: mpp.dwCallback=(DWORD_PTR)hDlg; mciSendCommand(Mop[SETUP].wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD_PTR)&mpp); oja=Ojama,max=OjaMax,rnd=OjaRand,var=Variety,del=Delete,spd=Speed; switch(oja){ case 0: SendMessage(GetDlgItem(hDlg,IDC_OJAMA_0),BM_SETCHECK,BST_CHECKED,0); break; case 1: SendMessage(GetDlgItem(hDlg,IDC_OJAMA_1),BM_SETCHECK,BST_CHECKED,0); break; case 2: SendMessage(GetDlgItem(hDlg,IDC_OJAMA_2),BM_SETCHECK,BST_CHECKED,0); break; case 3: SendMessage(GetDlgItem(hDlg,IDC_OJAMA_3),BM_SETCHECK,BST_CHECKED,0); break; } switch(max){ case 2: SendMessage(GetDlgItem(hDlg,IDC_MAXOJA_2 ),BM_SETCHECK,BST_CHECKED,0); break; case 4: SendMessage(GetDlgItem(hDlg,IDC_MAXOJA_4 ),BM_SETCHECK,BST_CHECKED,0); break; case 6: SendMessage(GetDlgItem(hDlg,IDC_MAXOJA_6 ),BM_SETCHECK,BST_CHECKED,0); break; case 12: SendMessage(GetDlgItem(hDlg,IDC_MAXOJA_12),BM_SETCHECK,BST_CHECKED,0); break; } switch(rnd){ case 20: SendMessage(GetDlgItem(hDlg,IDC_OJARAND_20),BM_SETCHECK,BST_CHECKED,0); break; case 15: SendMessage(GetDlgItem(hDlg,IDC_OJARAND_15),BM_SETCHECK,BST_CHECKED,0); break; case 10: SendMessage(GetDlgItem(hDlg,IDC_OJARAND_10),BM_SETCHECK,BST_CHECKED,0); break; case 5: SendMessage(GetDlgItem(hDlg,IDC_OJARAND_5 ),BM_SETCHECK,BST_CHECKED,0); break; } switch(var){ case 2: SendMessage(GetDlgItem(hDlg,IDC_VAR_2),BM_SETCHECK,BST_CHECKED,0); break; case 3: SendMessage(GetDlgItem(hDlg,IDC_VAR_3),BM_SETCHECK,BST_CHECKED,0); break; case 4: SendMessage(GetDlgItem(hDlg,IDC_VAR_4),BM_SETCHECK,BST_CHECKED,0); break; case 5: SendMessage(GetDlgItem(hDlg,IDC_VAR_5),BM_SETCHECK,BST_CHECKED,0); break; case 6: SendMessage(GetDlgItem(hDlg,IDC_VAR_6),BM_SETCHECK,BST_CHECKED,0); break; } switch(del){ case 2: SendMessage(GetDlgItem(hDlg,IDC_DEL_2),BM_SETCHECK,BST_CHECKED,0); break; case 3: SendMessage(GetDlgItem(hDlg,IDC_DEL_3),BM_SETCHECK,BST_CHECKED,0); break; case 4: SendMessage(GetDlgItem(hDlg,IDC_DEL_4),BM_SETCHECK,BST_CHECKED,0); break; case 5: SendMessage(GetDlgItem(hDlg,IDC_DEL_5),BM_SETCHECK,BST_CHECKED,0); break; case 6: SendMessage(GetDlgItem(hDlg,IDC_DEL_6),BM_SETCHECK,BST_CHECKED,0); break; } switch(spd){ case 1200: SendMessage(GetDlgItem(hDlg,IDC_SPEED_0),BM_SETCHECK,BST_CHECKED,0); break; case 1000: SendMessage(GetDlgItem(hDlg,IDC_SPEED_1),BM_SETCHECK,BST_CHECKED,0); break; case 800: SendMessage(GetDlgItem(hDlg,IDC_SPEED_2),BM_SETCHECK,BST_CHECKED,0); break; case 600: SendMessage(GetDlgItem(hDlg,IDC_SPEED_3),BM_SETCHECK,BST_CHECKED,0); break; case 400: SendMessage(GetDlgItem(hDlg,IDC_SPEED_4),BM_SETCHECK,BST_CHECKED,0); break; } return TRUE; case MM_MCINOTIFY: if(wParam==MCI_NOTIFY_SUCCESSFUL){ mciSendCommand((MCIDEVICEID)lParam,MCI_SEEK,MCI_SEEK_TO_START,0); mciSendCommand((MCIDEVICEID)lParam,MCI_PLAY,MCI_NOTIFY,(DWORD_PTR)&mpp); }else{ mciSendCommand((MCIDEVICEID)lParam,MCI_STOP,0,0); mciSendCommand((MCIDEVICEID)lParam,MCI_SEEK,MCI_SEEK_TO_START,0); } return TRUE; case WM_COMMAND: switch(LOWORD(wParam)){ case IDOK: SendMessage(hDlg,MM_MCINOTIFY,MCI_NOTIFY_ABORTED,Mop[SETUP].wDeviceID); Ojama=oja; OjaMax=max; OjaRand=rnd; Variety=var; Delete=del; Speed=spd; EndDialog(hDlg,IDOK); return TRUE; case IDCANCEL: SendMessage(hDlg,MM_MCINOTIFY,MCI_NOTIFY_ABORTED,Mop[SETUP].wDeviceID); EndDialog(hDlg,IDCANCEL); return TRUE; case IDC_KEY: DialogBox(hInst,"KEY",hDlg,(DLGPROC)KeyDlgProc); return TRUE; case IDC_OJAMA_0: oja=0; return TRUE; /// ↑キーコンフィグダイアログ case IDC_OJAMA_1: oja=1; return TRUE; case IDC_OJAMA_2: oja=2; return TRUE; case IDC_OJAMA_3: oja=3; return TRUE; case IDC_MAXOJA_2: max= 2; return TRUE; case IDC_MAXOJA_4: max= 4; return TRUE; case IDC_MAXOJA_6: max= 6; return TRUE; case IDC_MAXOJA_12: max=12; return TRUE; case IDC_OJARAND_20: rnd=20; return TRUE; case IDC_OJARAND_15: rnd=15; return TRUE; case IDC_OJARAND_10: rnd=10; return TRUE; case IDC_OJARAND_5: rnd= 5; return TRUE; case IDC_VAR_2: var=2; return TRUE; case IDC_VAR_3: var=3; return TRUE; case IDC_VAR_4: var=4; return TRUE; case IDC_VAR_5: var=5; return TRUE; case IDC_VAR_6: var=6; return TRUE; case IDC_DEL_2: del=2; return TRUE; case IDC_DEL_3: del=3; return TRUE; case IDC_DEL_4: del=4; return TRUE; case IDC_DEL_5: del=5; return TRUE; case IDC_DEL_6: del=6; return TRUE; case IDC_SPEED_0: spd=1200; return TRUE; case IDC_SPEED_1: spd=1000; return TRUE; case IDC_SPEED_2: spd= 800; return TRUE; case IDC_SPEED_3: spd= 600; return TRUE; case IDC_SPEED_4: spd= 400; return TRUE; } return FALSE; } return FALSE; } /// キーコンフィグダイアログボックスのプロシージャ LRESULT CALLBACK KeyDlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam) { static KEY button; static BOOL mouse; static WPARAM wp[KEYNUM]; static char tempKeyText[KEYNUM][32]; static char keyText[KEYNUM][32]={"A","F","Space","Left","Right","Down","Esc","Enter","F1"}; const char ope[KEYNUM][32]={"左回転","右回転","反転","左移動","右移動","下移動", "ポーズ","ニューゲーム","キーコンフィグ"}; const DWORD IDC[KEYNUM]={IDC_BTL,IDC_BTR,IDC_BTO,IDC_BML,IDC_BMR,IDC_BMD,IDC_BPA,IDC_BNG,IDC_BKC}; char str[64]; RECT rc; int i,k; switch(uMsg){ case WM_INITDIALOG: for(i=0;ilpCreateParams)); GetClientRect(hWnd,&rc); rc.top=100; paint=TRUE; SetTimer(hWnd,1,500,NULL); return 0; case WM_TIMER: paint=!paint; InvalidateRect(hWnd,NULL,TRUE); return 0; case WM_PAINT: hdc=BeginPaint(hWnd,&ps); SetTextColor(hdc,RGB(0,255,0)); SetBkColor(hdc,RGB(0,0,0)); if(paint) wsprintf(str,"%s\n\n%s",params,explain); else strcpy(str,params); DrawText(hdc,str,-1,&rc,DT_CENTER); EndPaint(hWnd,&ps); /// ↓タスクバーの項目をクリックする事で return 0; /// ↓再び最前面になった場合はフォーカスを失っている case WM_WINDOWPOSCHANGED: SetForegroundWindow(hWnd); return 0; case WM_KEYDOWN: PostMessage(GetWindow(hWnd,GW_OWNER),WM_INPUT,wParam,lParam); KillTimer(hWnd,1); DestroyWindow(hWnd); return 0; } return DefWindowProc(hWnd,uMsg,wParam,lParam); } int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR lpCmdLine,int nCmdShow) { WNDCLASS wc; MSG msg; hInst=hInstance; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); wc.hCursor = LoadCursor(NULL,IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = __FILE__; if(!RegisterClass(&wc)) return 0; /// 入力キー取得ウィンドウ wc.lpfnWndProc = InputWindowProc; wc.lpszClassName = "Input"; wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); if(!RegisterClass(&wc)) return 0; // 指定したクライアント領域を確保するために必要なウィンドウ座標を計算する RECT rc={0,0,(FW+PW+3)*CW,(FH+1)*CH}; AdjustWindowRect(&rc,WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME,FALSE); HWND hWnd=CreateWindow( __FILE__,"とことんぷよぷよ", (WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX) | WS_VISIBLE, CW_USEDEFAULT,CW_USEDEFAULT, rc.right-rc.left,rc.bottom-rc.top, NULL,NULL,hInstance,NULL); if(hWnd==NULL) return 0; BOOL bRet; while((bRet=GetMessage(&msg,NULL,0,0))!=0){ if(bRet==-1) break; DispatchMessage(&msg); } return (int)msg.wParam; }