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

📄 jldoc.cpp

📁 很经典的用C++编的空当接龙的程序
💻 CPP
📖 第 1 页 / 共 4 页
字号:
	AutoThrow();
	return GameOver() || Combine() || Splite();//先合并,合不了就拆开
}
/*-------------------------------------------合-------------------------------------------
对每一除回收列之外的有目标列的非空列
{
如果没有目标列
	如果此列是缓存列
		如果有空牌列
			如果拿下来能够节约空间
				拿下来,继续解答		
否则
	如果目标列只在缓存列(因为可能有两个目标都在缓存列)
		如果有空牌列
			将(任一)目标牌列拿到空牌列中,继续解答         // 从上往下把目标牌列合并到空牌列中

	否则如果此列是缓存列(至少有一个目标是牌列)
		拿到(任一)牌列目标上来,继续解答

	否则如果可以合并到牌列                                  (空间足够,缓存列和牌列合并到牌列)
		如果能在目标上得到更长的序列牌			// 即使是长度相同也不行
		    或目标牌列完全是以K开始的序列牌
			合并到目标牌列上,继续解答
}
返回假

继续解答:
	记录此次动作
	自动扔牌
	如果执行自动解答成功返回真
	撤销
	返回假
----------------------------------------------------------------------------------------*/
/*-----------------------------------------扔---------------------------------------------
拆:非空才拆

拆缓存列:	有空牌列就直接拿下来,否则失败
拆牌列:	此列是非完全序列牌
			先后拆到牌列空列空档,不够不拆。
		否则就是完全序列牌,
			全拆到空档,不够不拆。
			拆完后再拆另一个牌列或空档列(此空档列不能是刚才拆上去的列,不然循环了)
			如果找不到可以拆动的牌列,如果可以找到缓存列否则失败
----------------------------------------------------------------------------------------*/
//执行合并动作
bool CJLDoc::Combine()
{
	for(UINT i = 1; i <= 12; i++) {
		if(IsEmptyCol(i)) continue; 
		if(CombimeCol(i)) return true;
	}
	return false;
}
//执行拆分动作
bool CJLDoc::Splite()
{
	//对每个可拆的非完全序列进行拆分
	UINT cols[8+1], *pFirst = cols, *pLast = SortByActivity(cols);
	for(;pFirst < pLast; ++pFirst) {
		if(SpliteCol(*pFirst)) return true;
	}
	for(UINT i = 9; i <= 12; i++) {
		if(IsEmptyCol(i)) continue; 
		if(SpliteCol(i)) return true;
	}
	return false;
}
//合并牌列:将此列的(整个或部分)序列牌合并到其它列
bool CJLDoc::CombimeCol(UINT col)
{
	//合并的对象只可能是缓存列或牌列且是非空列
	ASSERT( IsCol(col) && ! ColInRecycle(col) && ! IsEmptyCol(col) );

	int desCol = 0, srcCol = 0, cntCards  = 0;

	int tar[2];
	GetTarget(col,tar);//寻找此列的目标列

	UINT ntar = 0;//计算目标列数目
	bool bAllTarInBuf = false;//是否所有目标都在缓存列
	if(tar[0]) {
		++ntar;
		bAllTarInBuf = ColInBuf(tar[0]);
		if(tar[1]) {
			++ntar;
			bAllTarInBuf = bAllTarInBuf && ColInBuf(tar[1]);
		}
	}
	//----------
	//没有目标列
	//----------
	if( ntar == 0 ) {
		//此列是非空牌列没有目标列
		if(!ColInBuf(col)) return false;
		//此列是缓存列没有目标列
		int a = CntEmptyBufs();//a是空档数
		int b = CntEmptyCardCols();//b是空牌列数
		//如果没有空牌列
		if(b == 0) return false;
		//如果有空牌列
		int c = (2*a+b)*(b+1)/2+1;//移动之前的空间
		++a,--b;//假设空档增加空牌列减少
		int d = (2*a+b)*(b+1)/2+1;//移动之后的空间
		if( c >= d ) return false;
		//能增加空间,拿到空牌列,继续解答
		srcCol = col;
		desCol = FindEmptyCardCol();
		cntCards  = 1;
		#ifdef DEBUG_ALERT
		ShowMessage("合并缓存列到空牌列,增加空间",srcCol,desCol,cntCards);
		#endif
		goto doAI;
	}
	//如果目标列都在缓存列
	//--------------------
	else if( bAllTarInBuf ) {
		int empCardCol = FindEmptyCardCol();
		if(!empCardCol)return false;//如果有空牌列
		//将(任一)目标牌列拿到空牌列中,继续解答
		srcCol = tar[0];
		desCol = empCardCol;
		cntCards  = 1;
		#ifdef DEBUG_ALERT
		ShowMessage("合并缓存列到空牌列,且其他牌可以合并到此牌",
			srcCol,desCol,cntCards);
		#endif
		goto doAI;
	}
	//至少有一个目标列在牌列
	//------------
	//此列是缓存列
	else if(ColInBuf(col)) {
		//缓存列拿到牌列目标上来,继续解答
		srcCol = col;
		desCol = tar[ ntar == 1 ? 0 : ( ColInCard(tar[0]) ? 0 : 1 ) ];
		ASSERT(tar[0]);
		cntCards  = 1;
		#ifdef DEBUG_ALERT
		ShowMessage("合并缓存列到牌列",srcCol,desCol,cntCards);
		#endif
		goto doAI;
	}
	//否则此列是牌列
	else {
		srcCol = col;
		if(ntar == 1) { //仅有一个目标列则此目标列肯定是牌列
			desCol = tar[0];
		} else { 
			//有两个目标列,则可能有一个或两个目标列是牌列
			if( !ColInCard(tar[0]) ) { //tar[0]不是牌列则tar[1]肯定是牌列
				desCol = tar[1];
			} else if(ColInCard(tar[1])) { //两个tar都是牌列
				desCol = tar[ CntSeriIn(tar[0]) > CntSeriIn(tar[1]) ? 0 : 1 ];
				//先合并到序列牌长的目标列
			} else {
				desCol = tar[0];
			};
		}
		cntCards = CntMaxMv(desCol,srcCol);
		ASSERT(cntCards > 0);
		//if( cntCards + CntSeriIn(desCol) <= CntSeriIn(srcCol) ) return false;
		//由长序列合并到短序列必须是移动后源列露出废牌才行
		if( cntCards + CntSeriIn(desCol) <= CntSeriIn(srcCol) )
			if(!Trashable(GetCard(srcCol,CntCardsIn(srcCol)-cntCards)))
				return false;
		//可以合并到牌列
		#ifdef DEBUG_ALERT
		ShowMessage("合并牌列到牌列,且能得到更长序列牌",srcCol,desCol,cntCards);
		#endif
		goto doAI;
	}
	return false;
doAI:	//有牌可以移动哦
	MoveCards(desCol,srcCol,cntCards);//移动
	Record(new COperations(desCol,srcCol,cntCards));//记录移动动作
	AutoThrow();//自动扔牌(自动记录动作)
	if(AICal())return true;//成功解答
	Undo();
	return false;
}

/*
扔:牌列 | 缓存 ---> 回收
合:牌列 | 缓存 ---> 牌列
  1. 序列牌整个拿到其他牌列露出空列或剩余牌
  2. 序列牌部分拿到其他牌列露出废牌
  3. 缓存牌拿到其他牌列
  4. 缓存牌拿到空列成为责任牌

拆:牌列 ---> 牌列 | 空档
*/
bool CJLDoc::SpliteCol(UINT col)
{
	//拆的对象只可能是缓存列或牌列且非空才拆
	ASSERT( IsCol(col) && ! ColInRecycle(col) && ! IsEmptyCol(col) );
	if(ColInBuf(col))//拆缓存列
	{
		//没有空牌列则不能拿下来
		UINT empCardCol = FindEmptyCardCol();
		if(empCardCol == 0)return false;
		//有空牌列
		//如果拿下来能给别的列提供合的机会就拿下来
		int tar[2];
		GetTarget(col,tar);//寻找目标列
		if(!tar[0] && !tar[1])return false;
		#ifdef DEBUG_ALERT
		ShowMessage("拆缓存列,有牌能合并到它上面",col,empCardCol,1);
		#endif
		MoveCards(empCardCol,col,1);//记录移动动作
		Record(new COperations(empCardCol,col,1));
		if(AICal())return true;
		Undo();
		return false;
	}
	//拆牌列
	UINT empCardCol = FindEmptyCardCol();
	if(empCardCol)//能够直接移动到空牌列?
	{
		int nCntCards     = CntCardsIn(col);
		int nFitFomula    = CntSeriIn(col);
		int nMovableCards = CntMaxMv(empCardCol,col);
		//实际可移动的牌肯定不大于序列牌数
		if(nMovableCards == nFitFomula)
		{
			//完全序列牌列直接移到空牌列没有意义
			if(nFitFomula == nCntCards)return false;
			#ifdef DEBUG_ALERT
			ShowMessage("拆序列牌到空牌列",col,empCardCol,nMovableCards);
			#endif
			//非完全序列牌列的全部序列牌直接移到空牌列
			MoveCards(empCardCol,col,nMovableCards);
			Record(new COperations(empCardCol,col,nMovableCards));
			if(AICal())return true;
			Undo();
			return false;
		}//else 序列牌不能直接拿到空牌列,则需要分批拆,看下面
	} else { //分批拆
	
		//先后拆到牌列,空列及空档,不够不拆。
		int inUse[12];//记录使用过的空间
		int steps    = 0;//记录使用了多少空间
		int nMoved   = 0;//记录移动过的牌数
		int nCntCard = CntCardsIn(col);
		int nCntSeri = CntSeriIn(col);
		int tarCol   = 0, empCardCol = 0, empBufCol  = 0;
		while(nMoved != nCntSeri)
		{
			int tar[2];
			GetTarget(col,tar);//寻找目标列
			bool t0 = ColInCard(tar[0]);
			bool t1 = ColInCard(tar[1]);
			//没有空牌列
			//如果可以部分合并到其他列时,合并到其他牌列
			if(t0 || t1)
				tarCol = tar[ t0 ? 0 : 1 ];
			else if((empCardCol = FindEmptyCardCol())!=0)//否则如果还有空牌列
				tarCol = empCardCol;
			else if((empBufCol = FindEmptyBuf())!=0)//否则如果还有空档
				tarCol = empBufCol;
			else//否则(拆不开,看下一列)
			{
				while(steps--)Undo();//撤销
				return false;
			}
			int n = CntMaxMv(tarCol,col);
			ASSERT(n>0);
			#ifdef DEBUG_ALERT
			ShowMessage("拆序列牌到牌列,空列及空档!",col,tarCol,n);
			#endif
  			MoveCards(tarCol,col,n);//记录移动动作
			Record(new COperations(tarCol,col,n));
			nMoved += n;//计算移走的牌数
			inUse[steps++] = tarCol;//记录当前使用的目标列
			//退出循环时,step记录使用了多少空间

			//部分序列拿走后露出废牌的这种情况在
			//合并函数中已经予以考虑过了,在此就不必再考虑了
			/*
			if(nMoved < nCntSeri) {
				UINT bc = BottCard(col);
				if(Trashable(bc)) {
					MoveCards(TYPE(bc)+13,col,1);
					Record(new COperations(TYPE(bc)+13,col,1));
					if(AICal()) return true;
					Undo();
				}
			}
			*/
		}
		//此列不完全是序列牌
		//------------------
		if(nCntCard != nCntSeri){
			if(AICal()) return true;
			while(steps--)Undo();//不成功则全部撤销
			return false;
		}
		//此列完全是序列牌
		//----------------
		//拆完后还再拆另一个列,它可能是牌列,也可能是缓存列,
		//但绝对不能是正在被使用的列,也不可能是拆掉了的当前列	
		for(int another = 1; another <= 12; another++) {
			ASSERT(IsEmptyCol(col));
			//col列此时为空所以可以被过滤掉
			if(IsEmptyCol(another))continue;
			bool isInUse = false;
			for(int j = 0; j < steps; j++){
				if(another != inUse[j])continue;
				isInUse = true;
				break;
			}
			//过滤掉刚刚被使用的列
			if(isInUse)continue;
			if(SpliteCol(another))return true;//成功解答
			while(steps--)Undo();//不成功则全部撤销
			return false;
		}
	}

	return false;
}

//找到一个空牌列
UINT CJLDoc::FindEmptyCardCol()
{
	for(UINT i=1;i<=8;i++) 
		if(m_iCards[i-1][19] == 0) return i;
	return 0;
}

//找到一个空档
UINT CJLDoc::FindEmptyBuf()
{
	for(UINT i=9;i<=12;i++) 
		if(m_iBuffer[i-9] == 0) return i;
	return 0;
}

//统计空牌列数
UINT CJLDoc::CntEmptyCardCols()
{
	int cnt = 0;
	for(UINT i=1;i<=8;i++)
		if(m_iCards[i-1][19] == 0) ++cnt;
	return cnt;
}

//统计空档数
UINT CJLDoc::CntEmptyBufs()
{
	int cnt = 0;
	for(UINT i=9;i<=12;i++)
		if(m_iBuffer[i-9] == 0) ++cnt;
	return cnt;
}


//为指定的源列寻找目标列,目标列可能有一个或两个,但是绝对不会超过两个
//如果没有目标列,则返回时,target[0]和target[1]都是零
//搜索非回收列来寻找目标列
void CJLDoc::GetTarget(int col, int *target)
{
	ASSERT(IsCol(col) && !ColInRecycle(col) && !IsEmptyCol(col));

	int *p = target;
	p[0] = p[1] = 0;
	for(UINT i = 1; i <= 12; i++) {
		if(i > 8) {
			int d = m_iBuffer[i-9];
			if(!IS_CARD(d)) continue;//忽略空档
			int s = BottCard(col);
			int n = NUM(d) - NUM(s);
			int nSeri = CntSeriIn(col);
			if(n>0 && n<=nSeri && n%2==(TYPE(s)+TYPE(d))%2) {
				*p++ = i;
			}
		} else if(m_iCards[i-1][19] && CntMaxMv(i,col)) {
			*p++ = i;//目标是牌列
		}
		if(p == target + 2) return;//目标列绝对不会超过两个
	}
}

#ifdef DEBUG_ALERT
//调试程序的时候,请将DEBUG_ALERT加入到预编译开关中
void CJLDoc::ShowMessage(char* pMsg,int src,int des,int cnt)
{
	ASSERT(pMsg!=NULL);

	CMsgDlg msgdlg;
	CString msg;
	msg.Format("%d--->%d(%d),%s",src,des,cnt,pMsg);

	msgdlg.m_strMsg = msg;
	msgdlg.DoModal();
}
#endif

//提示下一步
void CJLDoc::OnHelpNextstep() 
{
	// TODO: Add your command handler code here
	if(m_Hints.IsEmpty()) return;

	//提示前取消选中状态
	UnselectCardCol();

	//取出下一步动做的记录并提示玩家
	const COperation *pOp = m_Hints.NextHint();
	
	UINT cntSrc = CntCardsIn(pOp->src);
	UINT cntDes = CntCardsIn(pOp->des);
	CRect sR = RectOf(pOp->src, cntSrc - pOp->cnt + 1, pOp->cnt);
	CRect dR = RectOf(pOp->des, max(cntDes,1), 1);
	
	CJLView *pView = GetView();
	CClientDC cdc(pView);
	//提示过程就是闪烁源列和目列的牌
	cdc.InvertRect(sR); cdc.InvertRect(dR);//反色
	SendMessage(pView->m_hWnd,WM_PAINT,0,0);
	::Sleep(200); 
	cdc.InvertRect(sR); cdc.InvertRect(dR);//还原
	SendMessage(pView->m_hWnd,WM_PAINT,0,0);
	::Sleep(200);
	cdc.InvertRect(sR); cdc.InvertRect(dR);//反色
	SendMessage(pView->m_hWnd,WM_PAINT,0,0);
	::Sleep(200);
	cdc.InvertRect(sR); cdc.InvertRect(dR);//还原
}

CJLView* CJLDoc::GetView()
{
	POSITION pos= GetFirstViewPosition();
	CJLView *pView = (CJLView *)GetNextView(pos);
	ASSERT(pView);
	return pView;
}
//检查游戏是否结束

⌨️ 快捷键说明

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