⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 jldoc.cpp

📁 很经典的用C++编的空当接龙的程序
💻 CPP
📖 第 1 页 / 共 4 页
字号:
bool CJLDoc::GameOver()
{
	//如果所有牌都在回收列了则游戏结束
	for(UINT i=13;i<=16;i++) {
		if( m_iRecycle[i-13][13] == 13 ) continue;
		return false;
	}
	return true;
}

//按照规则计算此列有几张牌是顺序存放的
UINT CJLDoc::CntSeriIn(UINT col)
{
	//我们认为缓存列和回收列的序列牌长为1
	ASSERT(IsCol(col) && !IsEmptyCol(col));
	UINT nSeri = 1;//非空的缓存列序列牌数为1
	if(col <= 8) {
		UINT *pTop,*p1, *p2;
		pTop  = &m_iCards[col-1][0];
		p1 = &pTop[pTop[19]-1];//指向底牌
		p2 = p1 - 1;//p2指向p1上面的牌
		while(p2 >= pTop && FitFormula(*p2--,*p1--)) ++nSeri;
	}
	return nSeri;
}

//计算给定的牌列中有多少张牌
UINT CJLDoc::CntCardsIn(UINT col)
{
	ASSERT(IsCol(col));

	if(col <= 8) {
		return m_iCards[col-1][19];
	} else if(col <= 12) {
		return m_iBuffer[col-9] ? 1 : 0;
	} else {
		return m_iRecycle[col-13][13];
	}
}


//  | 1 ... 13 | 14 ... 26 | 27 ... 39  | 40 ... 52 |
//  | A ...  K |  A ...  K |  A ...  K  |  A ...  K |
//  | -黑桃- |  -红桃- |  -梅花-  |  -方块- |
UINT CJLDoc::Num(UINT card) { return (card-1)%13+1; }//点数 1-13
UINT CJLDoc::Type(UINT card) { return (card-1)/13; }//花色: 0黑桃 1红桃 2梅花 3方块

//假设目前有连续的无数张牌等待移动
//计算目前空出的无牌列(可用空间)最多可供一次性移动多少张牌
//OccupyAnEmptyCol指出在计算时是否使用全部可用空间
//如果否,则在计算最多允许一次性移动多少张牌时牌列中的可用
//空间会少计一个
UINT CJLDoc::CntMaxSuppliment(bool OccupyAnEmptyCol)
{
	int a = 0, b = 0;
	//统计空牌列数 
	for(UINT i = 1; i <= 8; i++) { 
		if(m_iCards[i-1][19] == 0) ++b;
	}
	//统计空档数
	for(i=9;i<=12;i++) { 
		if(m_iBuffer[i-9] == 0) ++a;
	}
	//有一个空列将会被作为目标牌列
	if(OccupyAnEmptyCol) {
		//可往空牌列移动的牌数在由人工来玩牌时只与空档有关
		if(!m_bAICalRunning) return a+b;
		//其他任何情况下都一样
		ASSERT(b);
		--b;
	}
	return a*(b+1)+b*(b+1)/2+1;
}
//取出给定列的底牌
UINT CJLDoc::BottCard(UINT col)
{
	ASSERT(IsCol(col) && !IsEmptyCol(col));

	if(col <= 8) {
		UINT *pTop = &m_iCards[col-1][0];
		return pTop[pTop[19]-1];//指向底牌
	} else if(col <= 12) {
		return m_iBuffer[col-9];
	} else {
		UINT *pTop = &m_iRecycle[col-13][0];
		return pTop[pTop[13]-1];//指向底牌
	}
}

bool CJLDoc::ColInCard(UINT col) {
	return (col<=8 && col>=1);
}

bool CJLDoc::ColInBuf(UINT col) {
	return (col<=12 && col>=9);
}

bool CJLDoc::ColInRecycle(UINT col) {
	return (col<=16 && col>=13);
}

bool CJLDoc::IsCard(UINT card)
{
	return (card >= 1 && card <= 52);
}

void CJLDoc::InvalidateRect(CRect r)
{
	//如果正在自动解答,那么有可能你最需要的是计算速度而不是
	//时实看到牌局状态,所以自动解答时,如果你关闭时实显示牌局
	//这一选项,就能大大加快解答速度

	//在时实显示牌局的条件下虽然解答速度较慢,但是
	//大部分的(大概有90%以上的)牌局通常都能在很短的时间内解答出来
	//“实时显示牌局状态”这一选项默认是打开的,毕竟自动解答时牌局的
	//演变过程看起来还是有点意思的。我们还是希望知道电脑都在干些什么。
	//计算出无效区域并刷新
	if(m_bAICalRunning && !m_bRealTimeUpdate)return;
	CJLView *pView = GetView();
	pView->InvalidateRect(r);
	SendMessage(pView->m_hWnd,WM_PAINT,0,0);
}

void CJLDoc::Record(CObject *thisStep)
{
	//增加一步记录并刷新步数信息
	m_pOps->AddTail(thisStep);
	InvalidateRect(RectOfStep());
}
//撤消
void CJLDoc::OnUndo() 
{
	Undo();
	GetHints();
}
//执行撤消动作
void CJLDoc::Undo()
{
	if(m_pOps->IsEmpty()) return;

	//撤销一步
	COperations *pOpsLast = (COperations*)m_pOps->GetTail();
	CObList *pOps = pOpsLast->pOps;
	POSITION posMov = pOps->GetHeadPosition();
	while(posMov) {
 		COperation *pOp = (COperation*)pOps->GetNext(posMov);
		MoveCards(pOp->src,pOp->des,pOp->cnt);
	}
	pOpsLast->ClrOps();
	delete pOpsLast;
	m_pOps->RemoveTail();

	InvalidateRect(RectOfStep());
}

//游戏返回到开头但是保留步骤记录,准备回放
void CJLDoc::BackHome() 
{
	int nSteps = m_pOps->GetCount();

	//还原牌局但保留步骤记录
	while(nSteps > 0) {
		POSITION posStep = m_pOps->FindIndex(--nSteps);
		COperations *pOpsLast = (COperations*)m_pOps->GetAt(posStep);
		CObList *pOps = pOpsLast->pOps;
		POSITION posMov = pOps->GetHeadPosition();
		while(posMov) {
			COperation *pOp = (COperation*)pOps->GetNext(posMov);
			MoveCards(pOp->src,pOp->des,pOp->cnt);
		}
		InvalidateRect(RectOfStep());
	}
}

void CJLDoc::OnSetting() 
{
	// TODO: Add your command handler code here

	CDlgSettings dlg;
	//set values of settings
	dlg.m_bEnableAlert = m_bEnableAlert;
	dlg.m_bEnableDBClick = m_bEnableDbClick;
	dlg.m_bQuickMove = m_bQuickMove;
	dlg.m_bMaxMove = m_bMaxMove;
	dlg.m_nDDASpeed = m_nDDASpeed;

	//prompt
	dlg.DoModal();

	//get settings
	m_bEnableAlert = dlg.m_bEnableAlert;
	m_bEnableDbClick = dlg.m_bEnableDBClick;
	m_bQuickMove = dlg.m_bQuickMove;
	m_bMaxMove = dlg.m_bMaxMove;
	m_nDDASpeed = dlg.m_nDDASpeed;
}

void CJLDoc::DeleteContents() 
{
	// TODO: Add your specialized code here and/or call the base class
	ClrOpsRecords();

	CDocument::DeleteContents();
}

//计算第col列从第idx张牌开始的nCards张牌占据的矩形
//它可为选择,移动和重画函数提供绘图信息
CRect CJLDoc::RectOf(UINT col, UINT idx ,UINT nCards)
{
	ASSERT(IsCol(col));
	CPoint org = ptOrg;

	CRect r;
	if(col <= 8) {
		org.x += CARD_INT;
		org.y += CARD_HEI+PILE_VINT;
		r.left   = org.x + (CARD_WID+CARD_INT)*(col-1);
		r.right  = r.left + CARD_WID;
		//超过十三张牌后每增加一张牌
		//露出部分的高度就减少一个象素
		int n = m_iCards[col-1][19]-13;
		int h = CARD_UNCOVER - max(n,0);
		r.top    = org.y  + (idx-1)*h;
		r.bottom = r.top  + (nCards-1)*h + CARD_HEI;
	} else if(col <= 12) {
		r.left   = org.x + (col-9)*CARD_WID;
		r.top    = org.y;
		r.right  = r.left + CARD_WID;
		r.bottom = r.top  + CARD_HEI; 
	} else {
		org.x += 4*CARD_WID+PILE_HINT;

		r.left   = org.x + (col-13)*CARD_WID;
		r.top    = org.y;
		r.right  = r.left + CARD_WID;
		r.bottom = r.top  + CARD_HEI;
	}
	return r;
}

#include "mainfrm.h"
#include "JLView.h"

//绘制自动扔牌过程动画的函数LineDDACallback
VOID CALLBACK LineDDACallback(int x, int y, LPARAM lparam)
{
	//取得文档、视图指针,视图设备环境和动画牌
	POSITION tmplPos = AfxGetApp()->GetFirstDocTemplatePosition();
	CDocTemplate* pTmpl = AfxGetApp()->GetNextDocTemplate(tmplPos);
	POSITION docPos = pTmpl->GetFirstDocPosition();
	CJLDoc  *pDoc  = (CJLDoc*)pTmpl->GetNextDoc(docPos);
	CJLView *pView = pDoc->GetView();
	CClientDC cdc(pView);
	UINT card = lparam;

	static CRect rbk(0,0,0,0);//用于记录上次绘制的位置

	//当前位置
	CRect r(x,y,x+CARD_WID,y+CARD_HEI), R, rInter;

	//第一点处不绘制牌面,只记录位置
	if(rbk.IsRectEmpty()) { 
		rbk = r;
		return;
	}
	//当两个矩形的重叠面积达到牌面积75%时并且将要绘制的
	//矩形不和回收牌列相交时绘制新的矩形
	//这一算法是经过多次摸索之后得到的,算法达到的效果还
	//不错,我不知道windows的空当接龙是什么算法。
	//
	//百分比和动画速度的关系:
	//
	//	动画速度:慢速 -----中速-----> 快
	//	百分比  :100% ------50%-----> 1%
	//
	//上次绘制与本次绘制的两个矩形的重叠区域

#define SquareIsOK(a,b) (a*b <= CARD_WID*CARD_HEI*(1-pDoc->m_nDDASpeed/100.))

	rInter.IntersectRect(r,rbk);//重叠矩形肯定不为空
	if(SquareIsOK(rInter.Width(),rInter.Height())) { //满足面积关系
		//擦去上次的绘制
		pView->InvalidateRect(rbk);
		//重叠的部分不必刷新,因为新绘制的矩形包括这部分
		pView->ValidateRect(rInter);
		//在当前位置重新绘制
		pView->DrawCard(r.TopLeft(),card,&cdc);
		//cdc.BitBlt(r.left,r.top,r.Width(),r.Height(),&cdc,rbk.left,rbk.top,SRCCOPY);
		//强制立即刷新无效区域
		pView->SendMessage(WM_PAINT,0,0);
		//绘制完毕。备份当前矩形坐标
		rbk = r;
	}
	//接近回收处时可能在最后一点前不再绘制
	R = pDoc->RectOf(TYPE(card)+13,1,1);
	rInter.IntersectRect(rbk,R);
	if(!SquareIsOK(R.Width(),R.Height())) {
		pView->InvalidateRect(rbk);//擦去上次的绘制
		//rbk.SetRectEmpty();
		return;
	}
}

//取第col列第idx张牌
UINT CJLDoc::GetCard(UINT col, UINT idx)
{
	ASSERT(IsCol(col) && !IsEmptyCol(col) && idx > 0 && idx <= CntCardsIn(col));
	if(col <= 8) {
		UINT *pTop = &m_iCards[col-1][0];
		return pTop[--idx];
	} else if(col <= 12) {
		ASSERT(idx == 1);
		return m_iBuffer[col-9];//与idx无关
	} else {
		UINT *pTop = &m_iRecycle[col-13][0];
		return pTop[--idx];
	}
}

//存档
void CJLDoc::OnSave() 
{
	// TODO: Add your command handler code here
	CFileDialog dlg(FALSE,"rep",GetTitle(),dwFlags,filter);
	if(dlg.DoModal() == IDCANCEL) return;
	CFile file(dlg.GetPathName(),modeCrWr);
	CArchive ar(&file,CArchive::store);
	Serialize(ar);
	ar.Close();
	file.Close();
}

//读档
void CJLDoc::OnLoad() 
{
	// TODO: Add your command handler code here
	if(!GiveUp()) return;

	//选择文件
	CFileDialog dlg(TRUE,"rep",NULL,dwFlags,filter);
	if(dlg.DoModal() == IDCANCEL) return;
	CFile file(dlg.GetPathName(),modeRead);

	/////////////////////////////////////////////////////////////////
	//考虑存档文件有可能是一个不完整的自定义牌局
	int nGameNumber;
	file.Read(&nGameNumber,sizeof(int));
	if(nGameNumber == -1) {
		AfxMessageBox("请将自定义牌局【" + dlg.GetFileName() + "】编辑完整!\n");
		return;
	}
	file.SeekToBegin();
	/////////////////////////////////////////////////////////////////
	m_dlgScore.UpdateScore();//记录战况

	//读档
	CArchive ar(&file,CArchive::load);
	Serialize(ar);
	ar.Close();
	file.Close();

	//刷新牌局
	UpdateAllViews(NULL);

	//设置窗框标题为当前牌局代号或自定义牌局的文件名
	CString title;
	if(m_nCurGameNumber > 0) {
		title.Format("%d",m_nCurGameNumber);
		SetTitle(title);
	} else {
		SetTitle(dlg.GetFileName());
	}

	m_dlgScore.InitScore();//记录战况
	CheckGame();//看看此局是否已经结束
}

void CJLDoc::ClrOpsRecords()
{
	if(m_pOps == NULL) return;
	////////////////////////////////
	//清除原来的操作记录
	while(!m_pOps->IsEmpty())
		delete m_pOps->RemoveHead();
	m_pOps->RemoveAll();
}
//自动解答
void CJLDoc::OnAi() 
{
	// TODO: Add your command handler code here
	UnselectCardCol();//取消选中状态
	m_Hints.ClrHints();//清除提示的记录

	CDlgAICal dlgAICal;//自动解答
	dlgAICal.DoModal();
	UpdateAllViews(NULL);//可能使用了快速解答,所以要刷新界面

	if(!dlgAICal.m_bSuccess) {
		AfxMessageBox("抱歉,自动解答未能成功!");
	}
	CheckGame();//答案已经找到,从头开始演示
}

/*自动解答算法使用全局的递归算法和局部的回溯法算法相结合,步骤如下:
自动解答算法:
(1)  对1-4和9-16列中 每一个 有牌可以移动到其他列的 列 执行(2)
     否则(无牌可以移动)看还有没有空档,没有则返回false(此路不通),
     如果有空档则(表示1-4和9-16列全是空的,解答成功)返回true,
(2)  对此列牌的每一个目标列(目标列可能不止一列)都使用以下算法:
	(3)  如果此列可以合并到其他列,则将此列合并到其他列。如果
	     有两个列可以合并它,则
	(4)  自动扔牌
	(5)  调用自动解答算法
	     如果它返回true则返回true
	     否则撤销本次所有的移动(即:(3)(4)中的移牌动作)并返回false
(6)  返回false
*/

//-----------------------------------------------
volatile bool g_bStopAICal;//玩家停止了自动解答
//-----------------------------------------------
//自动解答
bool CJLDoc::AICal()
{
	//玩家等的不耐烦了
	if(g_bStopAICal) {
		//撤销过程的动画效果关闭,这样能在瞬间
		//撤销所有的解答步骤
		//m_bRealTimeUpdate = false;
		return false;
	}
	//自动解答得到的步数超过正常值,尽快结束这种局面
	//此局很可能导致算法无限死循环
	if( !m_pOps->IsEmpty() && m_pOps->GetCount() > 200 ) {
		//g_bStopAICal = true;
		return false;
	}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -