をきっかけに、今作っているマインスイーパーをDoc-Viewアーキテクチャにのっとって書き換えようとしています。
(書き換える前の状況はバックアップ済みです。)
Doc-Viewアーキテクチャって、Viewは描画関係の記述中心で、その他データ処理などはDocに記述するってことですよね?
元のソースは上記トピックにあるのでここでは提示しないことにしますが、マインスイーパーではだいたい以下のような感じでしょうか?
ViewはOnDrawでの描画処理と各種イベントハンドラー、その他必要な関数の呼び出しと再描画命令の記述する。
Docはマスの状況などを記憶させ、マスが押された時にマスの状況がどう変化するのか、ゲーム終了時の外部ファイルへの記録の書き出しなどを記述する。
Set/KillTimerもOnTimerがViewにあることからViewに記述しますよね?
なんて考えて今のところ以下のように分けてみました。
(長くなるのでスポイラーで表示しています。)
マインスイーパーView.cpp
► スポイラーを表示
// マインスイーパーView.cpp : CマインスイーパーView クラスの実装
//
#include "stdafx.h"
#include "マインスイーパー.h"
#include "マインスイーパーDoc.h"
#include "マインスイーパーView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CマインスイーパーView
IMPLEMENT_DYNCREATE(CマインスイーパーView, CView)
BEGIN_MESSAGE_MAP(CマインスイーパーView, CView)
// 標準印刷コマンド
ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONDBLCLK()
ON_WM_RBUTTONDOWN()
ON_WM_TIMER()
ON_COMMAND(ID_NEW_GAME, &CマインスイーパーView::NewGame)
END_MESSAGE_MAP()
// CマインスイーパーView コンストラクション/デストラクション
CマインスイーパーView::CマインスイーパーView()
{
// TODO: 構築コードをここに追加します。
}
CマインスイーパーView::~CマインスイーパーView()
{
}
BOOL CマインスイーパーView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: この位置で CREATESTRUCT cs を修正して Window クラスまたはスタイルを
// 修正してください。
return CView::PreCreateWindow(cs);
}
// CマインスイーパーView 描画
void CマインスイーパーView::OnDraw(CDC* pDC)
{
CマインスイーパーDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: この場所にネイティブ データ用の描画コードを追加します。
CDC windowDC;
windowDC.CreateCompatibleDC(pDC);
CBitmap make_picture;
make_picture.CreateCompatibleBitmap(pDC,pDoc->width,pDoc->height);
windowDC.SelectObject(&make_picture);
CPen white(PS_SOLID,1,RGB(255,255,255));
windowDC.SelectObject(&white);
windowDC.Rectangle(0,0,pDoc->width,pDoc->height);
char num[4];
sprintf(num,"%d",pDoc->timer);
windowDC.TextOutW(15,11,(CString)num);
CPen black(PS_SOLID,1,RGB(0,0,0));
windowDC.SelectObject(&black);
CBrush block_color;
for(int x=0;x<pDoc->blocks.x;x++){
for(int y=0;y<pDoc->blocks.y;y++){
if(pDoc->data[x][y]<0){
block_color.CreateSolidBrush(RGB(180,180,180));
}
else if(pDoc->data[x][y]==9){
block_color.CreateSolidBrush(RGB(255,0,0));
}
else{
block_color.CreateSolidBrush(RGB(210,210,210));
}
windowDC.SelectObject(&block_color);
windowDC.Rectangle(pDoc->block[x][y]);
if(pDoc->data[x][y]==-2){
block_color.DeleteObject();
block_color.CreateSolidBrush(RGB(0,255,0));
windowDC.SelectObject(&block_color);
windowDC.Ellipse(pDoc->block[x][y]);
}
else if(pDoc->data[x][y]==9){
windowDC.Ellipse(pDoc->block[x][y]);
}
else if(pDoc->data[x][y]==10){
block_color.DeleteObject();
block_color.CreateSolidBrush(RGB(0,0,255));
windowDC.SelectObject(&block_color);
windowDC.Ellipse(pDoc->block[x][y]);
}
else if(pDoc->data[x][y]==11){
block_color.DeleteObject();
block_color.CreateSolidBrush(RGB(255,255,0));
windowDC.SelectObject(&block_color);
windowDC.Ellipse(pDoc->block[x][y]);
}
else if(pDoc->data[x][y]>0){
switch(pDoc->data[x][y]){
case 8:
windowDC.SetTextColor(RGB(255,0,0));
break;
case 7:
windowDC.SetTextColor(RGB(255,85,0));
break;
case 6:
windowDC.SetTextColor(RGB(255,255,0));
break;
case 5:
windowDC.SetTextColor(RGB(0,255,0));
break;
case 4:
windowDC.SetTextColor(RGB(0,86,0));
break;
case 3:
windowDC.SetTextColor(RGB(0,255,255));
break;
case 2:
windowDC.SetTextColor(RGB(0,0,255));
break;
case 1:
windowDC.SetTextColor(RGB(255,0,255));
break;
}
sprintf(num,"%d",pDoc->data[x][y]);
windowDC.SetBkMode(TRANSPARENT);
windowDC.TextOutW(x*20+15,y*20+41,(CString)num);
}
block_color.DeleteObject();
}
}
pDC->BitBlt(0,0,pDoc->width,pDoc->height,&windowDC,0,0,SRCCOPY);
}
// CマインスイーパーView 印刷
BOOL CマインスイーパーView::OnPreparePrinting(CPrintInfo* pInfo)
{
// 既定の印刷準備
return DoPreparePrinting(pInfo);
}
void CマインスイーパーView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: 印刷前の特別な初期化処理を追加してください。
}
void CマインスイーパーView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: 印刷後の後処理を追加してください。
}
// CマインスイーパーView 診断
#ifdef _DEBUG
void CマインスイーパーView::AssertValid() const
{
CView::AssertValid();
}
void CマインスイーパーView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CマインスイーパーDoc* CマインスイーパーView::GetDocument() const // デバッグ以外のバージョンはインラインです。
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CマインスイーパーDoc)));
return (CマインスイーパーDoc*)m_pDocument;
}
#endif //_DEBUG
// CマインスイーパーView メッセージ ハンドラ
void CマインスイーパーView::OnLButtonDown(UINT nFlags, CPoint point)
{
CマインスイーパーDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
CView::OnLButtonDown(nFlags, point);
if(pDoc->game==-1){
SetTimer(1,1000,NULL);
}
pDoc->OnLButtonDown(point);
Invalidate(false);
}
void CマインスイーパーView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
CマインスイーパーDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
CView::OnLButtonDown(nFlags, point);
pDoc->OnLButtonDblClk(point);
Invalidate(false);
}
void CマインスイーパーView::OnRButtonDown(UINT nFlags, CPoint point)
{
CマインスイーパーDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
CView::OnRButtonDown(nFlags, point);
if(pDoc->game==-1){
SetTimer(1,1000,NULL);
}
pDoc->OnRButtonDown(point);
Invalidate(false);
}
void CマインスイーパーView::OnTimer(UINT_PTR nIDEvent){
CマインスイーパーDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
pDoc->OnTimer();
if(pDoc->game==-1){
KillTimer(1);
}
Invalidate(false);
}
void CマインスイーパーView::NewGame(){
CマインスイーパーDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
KillTimer(1);
pDoc->GameSetting();
Invalidate(false);
}
void CマインスイーパーView::ResetTimer(){
KillTimer(1);
SetTimer(1,1000,NULL);
}
► スポイラーを表示
// マインスイーパーDoc.cpp : CマインスイーパーDoc クラスの実装
//
#include "stdafx.h"
#include "マインスイーパー.h"
#include "マインスイーパーDoc.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CマインスイーパーDoc
IMPLEMENT_DYNCREATE(CマインスイーパーDoc, CDocument)
BEGIN_MESSAGE_MAP(CマインスイーパーDoc, CDocument)
END_MESSAGE_MAP()
// CマインスイーパーDoc コンストラクション/デストラクション
CマインスイーパーDoc::CマインスイーパーDoc()
{
// TODO: この位置に 1 度だけ呼ばれる構築用のコードを追加してください。
srand((unsigned)time(NULL));
LoadSetting();
GameSetting();
}
CマインスイーパーDoc::~CマインスイーパーDoc()
{
}
BOOL CマインスイーパーDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
// TODO: この位置に再初期化処理を追加してください。
// (SDI ドキュメントはこのドキュメントを再利用します。)
return TRUE;
}
// CマインスイーパーDoc シリアル化
void CマインスイーパーDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: 格納するコードをここに追加してください。
}
else
{
// TODO: 読み込むコードをここに追加してください。
}
}
// CマインスイーパーDoc 診断
#ifdef _DEBUG
void CマインスイーパーDoc::AssertValid() const
{
CDocument::AssertValid();
}
void CマインスイーパーDoc::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
}
#endif //_DEBUG
// CマインスイーパーDoc コマンド
void CマインスイーパーDoc::LoadSetting(){
{
std::ifstream ifs( "test.txt" );
if( !ifs.is_open() ) {
std::cout << "open error" << std::endl;
std::ofstream ofs( "test.txt" );
ofs <<1<<" "<<9<<" "<<9<<" "<<10<<std::endl<<1<<" "<<1<<" "<<0<< std::endl;
ofs.close();
}
}
std::ifstream ifs( "test.txt" );
ifs >>mode>>height>>width>>bomsr;
ifs >> record >> date >> same;
blocks.SetPoint(width,height);
width=blocks.x*20+20;
height=blocks.y*20+50;
if(mode==0){
;
}
else{
;
}
}
void CマインスイーパーDoc::GameSetting(){
map=(int **)malloc(sizeof(int *)*blocks.x);
data=(int **)malloc(sizeof(int *)*blocks.x);
block=(CRect **)malloc(sizeof(CRect *)*blocks.x);
for(int x=0;x<blocks.x;x++){
map[x]=(int *)malloc(sizeof(int)*blocks.y);
data[x]=(int *)malloc(sizeof(int)*blocks.y);
block[x]=(CRect *)malloc(sizeof(CRect)*blocks.y);
}
for(int x=0;x<blocks.x;x++){
for(int y=0;y<blocks.y;y++){
block[x][y].SetRect(x*20+10,y*20+40,x*20+30,y*20+60);
map[x][y]=0;
data[x][y]=-1;
}
}
int bom=0;
boms=bomsr;
while(bom<boms){
int x=rand()%blocks.x;
int y=rand()%blocks.y;
while(map[x][y]!=0){
x=rand()%blocks.x;
y=rand()%blocks.y;
}
map[x][y]=9;
for(int x2=x-1;x2<=x+1;x2++){
for(int y2=y-1;y2<=y+1;y2++){
if((x2>-1 && x<blocks.x) && (y2>-1 && y<blocks.y) && map[x2][y2]!=9){
map[x2][y2]++;
}
}
}
bom++;
}
marks=0;
game=-1;
timer=0;
}
void CマインスイーパーDoc::OnLButtonDown(CPoint point){
if(game==-1){
timer=1;
game=0;
}
if(game==0){
for(int x=0;x<blocks.x;x++){
for(int y=0;y<blocks.y;y++){
if(PtInRect(block[x][y],point)==1){
if(data[x][y]!=-2){
data[x][y]=map[x][y];
bool check=true;
for(int x0=0;x0<blocks.x;x0++){
for(int y0=0;y0<blocks.y;y0++){
if(data[x0][y0]<0 && map[x0][y0]!=9){
check=false;
break;
}
}
if(check==false){
break;
}
}
if(check==true && data[x][y]!=9){
GameClear();
return;
}
else{
if(data[x][y]==0){
open(*new CPoint(x,y));
}
else if(data[x][y]==9){
bom.x=x;
bom.y=y;
GameOver();
}
}
}
}
}
}
}
}
void CマインスイーパーDoc::OnLButtonDblClk(CPoint point){
if(game==0){
for(int x=0;x<blocks.x;x++){
for(int y=0;y<blocks.y;y++){
if(PtInRect(block[x][y],point)==1){
if(data[x][y]>0){
int count=0;
for(int x2=x-1;x2<=x+1;x2++){
for(int y2=y-1;y2<=y+1;y2++){
if((x2>-1 && x2<9) && (y2>-1 && y2<9) && data[x2][y2]==-2){
count++;
}
}
}
if(count==data[x][y]){
for(int x2=x-1;x2<=x+1;x2++){
for(int y2=y-1;y2<=y+1;y2++){
if((x2>-1 && x2<9) && (y2>-1 && y2<9) && data[x2][y2]!=-2){
data[x2][y2]=map[x2][y2];
if(data[x2][y2]==0){
open(*new CPoint(x2,y2));
bool check=true;
for(int x0=0;x0<blocks.x;x0++){
for(int y0=0;y0<blocks.y;y0++){
if(data[x0][y0]<0 && map[x0][y0]!=9){
check=false;
break;
}
}
if(check==false){
break;
}
}
if(check==true && data[x][y]!=9){
bom.x=x;
bom.y=y;
GameClear();
return;
}
}
else if(data[x2][y2]==9){
GameOver();
}
}
}
}
}
}
}
}
}
}
}
void CマインスイーパーDoc::open(CPoint point){
if(data[point.x][point.y]==0){
for(int x=point.x-1;x<=point.x+1;x++){
for(int y=point.y-1;y<=point.y+1;y++){
if((x>-1 && x<blocks.x) && (y>-1 && y<blocks.y) && !(x==point.x && y==point.y) && data[x][y]==-1){
data[x][y]=map[x][y];
bool check=true;
for(int x2=0;x2<blocks.x;x2++){
for(int y2=0;y2<blocks.y;y2++){
if(data[x2][y2]<0 && map[x2][y2]!=9){
check=false;
break;
}
}
if(check==false){
break;
}
}
if(check==true){
GameClear();
return;
}
else{
open(*new CPoint(x,y));
}
}
}
}
}
else{
return;
}
}
void CマインスイーパーDoc::OnRButtonDown(CPoint point){
if(game==-1){
timer=1;
game=0;
}
if(game==0){
for(int x=0;x<blocks.x;x++){
for(int y=0;y<blocks.y;y++){
if(PtInRect(block[x][y],point)==1){
if(data[x][y]==-1){
data[x][y]=-2;
marks++;
}
else if(data[x][y]==-2){
data[x][y]=-1;
marks--;
}
}
}
}
}
}
void CマインスイーパーDoc::OnTimer(){
switch(game){
case 0:
if(timer<999){
timer++;
}
break;
case 1:
for(int x=0;x<blocks.x;x++){
for(int y=0;y<blocks.y;y++){
if(map[x][y]==9){
data[x][y]=10;
}
}
}
if(record==true){
SaveRecord();
}
game=-1;
break;
case 2:
if(boms>0 || marks>0){
int n=1;
int bomc=0;
while(bom.x-n>-1 || bom.x+n<9 || bom.y-n>-1 || bom.y+n<9){
for(int x=bom.x-n;x<=bom.x+n;x++){
for(int y=bom.y-n;y<=bom.y+n;y++){
if((x>-1 && x<9) && (y>-1 && y<9) && (y==bom.y-n || x==bom.x-n || y==bom.y+n || x==bom.x+n)){
if(map[x][y]==9){
bomc++;
if(bomc>=boms){
if(data[x][y]==-2){
data[x][y]=10;
marks--;
}
else{
data[x][y]=map[x][y];
}
boms--;
return;
}
}
else if(data[x][y]==-2){
data[x][y]=11;
marks--;
return;
}
}
}
}
n++;
}
}
else{
if(record==true){
SaveRecord();
}
game=-1;
}
break;
}
}
void CマインスイーパーDoc::GameOver(){
boms--;
game=2;
}
void CマインスイーパーDoc::GameClear(){
game=1;
}
void CマインスイーパーDoc::SaveRecord(){
CTime ctime;
ctime=CTime::GetCurrentTime();
FILE *fp;
fp=fopen("time.score","r");
if(fp==NULL){
fp=fopen("time.score","w");
int grade=1;
while(grade<4){
if(grade==1){
fprintf(fp,"初級\n");
}
else if(grade==2){
fprintf(fp,"中級\n");
}
else if(grade==3){
fprintf(fp,"上級\n");
}
fprintf(fp,"プレイ回数0 クリア回数0 クリア率0% 連勝数0 連敗数0 現在0\n");
for(int n=0;n<10;n++){
fprintf(fp,"999 名無し 0000/00/00 00:00:00\n");
}
grade++;
}
fclose(fp);
fp=fopen("time.score","r");
}
char grade[3][5],numbers[3][100],records[3][10][50];
for(int n=0;n<3;n++){
fscanf(fp,"%s",grade[n]);
fscanf(fp,"%s",numbers[n]);
for(int m=0;m<10;m++){
fscanf(fp,"%s",records[n][m]);
}
}
fclose(fp);
int play,clear,late,renwin,renrose,now;
sscanf(numbers[mode+1],"プレイ回数%d クリア回数%d クリア率%d% 連勝数%d 連敗数%d 現在%d",&play,&clear,&late,&renwin,&renrose,&now);
play++;
if(game==1){
clear++;
if(now>0){
now++;
if(renwin<now){
renwin=now;
}
}
else{
now=1;
}
}
else{
if(now<0){
now--;
if(renrose<abs(now)){
renrose=abs(now);
}
}
else{
now=-1;
}
}
late=(int)(((float)clear/(float)play)*100.0);
sprintf(numbers[mode+1],"プレイ回数%d クリア回数%d クリア率%d% 連勝数%d 連敗数%d 現在%d\n",play,clear,late,renwin,renrose,now);
if(game==1){
int rank,time[11];
char data[50];
for(int n=0;n<10;n++){
sscanf(records[mode+1][n],"%d. %d %s",&rank,&time[n],data);
}
time[10]=timer;
rank=9;
while(rank>=0){
if(time[rank+1]<time[rank]){
time[rank]=time[rank+1];
rank--;
}
else{
break;
}
}
if(rank<9){
char name[20];
sprintf(records[mode+1][10],"%d %s %d/%d/%d %d:%d:%d",timer,name,ctime.GetYear(),ctime.GetMonth(),ctime.GetDay(),ctime.GetHour(),ctime.GetMinute(),ctime.GetSecond());
int n=9;
while(n>rank){
strcpy(data,records[mode+1][n]);
strcpy(records[mode+1][n],records[mode+1][n+1]);
strcpy(records[mode+1][n+1],data);
}
}
}
fp=fopen("time.score","w");
for(int n=0;n<3;n++){
fprintf(fp,"%s\n",grade[n]);
fprintf(fp,"%s\n",numbers[n]);
for(int m=0;m<10;m++){
fprintf(fp,"%s\n",records[n][m]);
}
}
fclose(fp);
}
それも、たまにではなくほとんどです。
デバッグしてみると「マインスイーパー.exe の 0x00ad49f5 でハンドルされていない例外が発生しました: 0xC0000005: 場所 0xfdfdfe11 を読み込み中にアクセス違反が発生しました。」と言われ、マインスイーパーDoc.cppの135行目に矢印がありました。
自動変数にあるmap[x2]を見てみると、「CXX0030: エラーです: 式を評価できません」とあり、どうやらmallocに失敗しているみたいなのですが、何が原因なのでしょうか?
(mapはint**型で宣言していて、mallocで9*9(私が作るマインスイーパーのデフォルトで作った場合)の2次元配列にしています。)
Viewで書いていた時にはこのようなことは一度もありませんでした。
また、そもそもこのような書き方であっていると言えるのでしょうか?