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

📄 jldoc.cpp

📁 很经典的用C++编的空当接龙的程序
💻 CPP
📖 第 1 页 / 共 4 页
字号:
//如果没有结束则计算提示步骤
//否则就让玩家选择是否开局或回放存档
void CJLDoc::CheckGame()
{
	if(!GameOver()) {
		GetHints();
		if(m_Hints.IsEmpty()) {
			AfxMessageBox("没法走下一步了!\n你可以撤销几步再试试。");
			return;
		}
		return;
	}
	//本局结束了

	CPassedDlg dlg;
choice:	if(IDCANCEL == dlg.DoModal())
		return;
	switch(dlg.m_nChoice)
	{
	case RandGame:	OnRand();     break;
	case PrevGame:	OnPrevGame(); break;
	case NextGame:	OnNextGame(); break;
	case ThisGame:  OnAgain();    break;
	case SaveGame:	OnSave();     goto choice;
	case PlayShow:	
		//游戏返回到开头但是保留步骤记录
		BackHome();

		CDlgAIShow dlgAIShow;
		dlgAIShow.DoModal();//回放对话框
		goto choice;
	}
}
//执行自动解答
bool CJLDoc::DoAICal(void)
{
	//备份“快速移动”选项的值
	BOOL quickmv_bk = m_bQuickMove;

	//自动扔牌时的动画效果必须暂时关闭
	m_bQuickMove = TRUE;

	//开始解答
	m_bAICalRunning = true;
	g_bStopAICal    = false;
	m_bRealTimeUpdate = false;
	bool bSuccess   = AICal();
	m_bAICalRunning = false;
	g_bStopAICal    = true;

	//恢复“快速移动”选项的值
	m_bQuickMove = quickmv_bk;

	return bSuccess;
}

//计算出要显示步数信息的矩形
CRect CJLDoc::RectOfStep()
{
	CRect r;
	r.UnionRect(RectOf(12,1,1),RectOf(13,1,1));

	int inflateY = r.Height() - stepFont * 4 / 3;
	r.InflateRect(-CARD_WID-1, 0, -CARD_WID-1, -inflateY);
	return r;
}

//牌索引按钮的矩形位置
CRect CJLDoc::RectOf(UINT card)
{
	CRect r,r11,r14;
	r11 = RectOf(11,1,1);
	r14 = RectOf(14,1,1);
	r.left   = r11.left+CARD_WID/2;

	UINT rWid = (r14.right - r11.left - CARD_WID)/13;
	r.left  += (13-NUM(card))*rWid;
	r.right  = r.left + rWid + 1;
	r.top    = 0;
	r.bottom = ptOrg.y-1;
	return r;
}

//计算下一步的所有可能的动作并记录它(们)
void CJLDoc::GetHints()
{
	//清除原来的记录
	m_Hints.ClrHints();
	if(GameOver()) return;

	UINT nMove,i,j;
	//考虑合并和回收废牌
	for(i = 1; i <= 12; i++) {
		if(IsEmptyCol(i)) continue;
		UINT bc = BottCard(i);
		if(Trashable(bc)) {
			m_Hints.AddOperation(Type(bc)+13,i,1);//提示回收底牌(废牌)
		}
	}
	for(i = 1; i <= 12; i++) { 
		if(IsEmptyCol(i)) continue;
		for(j = 1; j <= 8; j++) { 
			if(IsEmptyCol(j) || !(nMove = CntMaxMv(j,i))) continue;
			//合并要得到较长序列或有废牌可以扔掉
			if( CntSeriIn(i)-nMove < CntSeriIn(j) ||
				Trashable( GetCard( i, CntCardsIn(i)-nMove ) ) ) {
				m_Hints.AddOperation(j,i,nMove);
			}
		}
	}
	if(!m_Hints.IsEmpty()) return;

	//考虑拆掉非完全序列牌
	for(i = 1; i <= 8; i++) {
		if(IsEmptyCol(i) || CntSeriIn(i) == CntCardsIn(i)) continue;
		for(j = 1; j <= 12; j++) { //不考虑回收列因为无废牌
			if(!IsEmptyCol(j)) continue;
			m_Hints.AddOperation(j,i,CntMaxMv(j,i));
			break;//可能有多个空列,但只提示移动到其中之一就够了
		}
	}
}
/*
////////////////////////////////////////////////////////////////
能增加空间的列最先考虑拆掉:
////////////////////////////////////////////////////////////////

// 拆分兼具合并
·对每一非完全序列牌牌列:
	·按照活牌所占剩余牌的比例由高到低进行排序,对排序后的每一列:
	·如果序列牌能完全移走(通过合并到其他牌列,拆到空牌列,或移动到空档)
		·全拆

·根据缓存列的牌从大到小的顺序
	// 增加空间且增加责任牌
	·如果此牌可以拿到牌列则拿下来
	// 增加空间且增加责任牌
	·否则如果此牌拿下来后能增加空间则拿下来
	// 增加责任牌但减少空间
	·如果此牌拿下来后能成为责任牌则拿下来

// 增加空间但减少责任牌
·对每一完全序列牌牌列:
	·如果序列牌能完全移到空档和牌列
		·能增加空间
			·全拆
		·如果没有空牌列
			·拿到空档以留出一个空牌列

·调用自动解答
*/
//关于非空牌列的有关信息:牌列,牌数目,序列牌长,活牌数
struct COL_INF { 
	UINT col,nCards,nSeri,act;
	void Set(UINT a,UINT b,UINT c,UINT d) {
		col = a; nCards = b; nSeri = c; act = d; 
	}
};
/*
int CmpAct( const void *arg1, const void *arg2 ) 
{
	COL_INF *p1 = (COL_INF*)arg1;
	COL_INF *p2 = (COL_INF*)arg2;

	int res = p1->act - p2->act;

	if(p1->act == 0) {
		if(p2->act == 0) { //都无活牌就先拆牌少的列
			return p2->nCards - p1->nCards;
		} else { //有活牌的列比无活牌的列先拆
			return res;
		}
	} else {
		if(p2->act == 0) { //有活牌的列比无活牌的列先拆
			return res;
		} else { //都有活牌
			int diff = p1->act * (p2->nCards - p2->nSeri) - p2->act * (p1->nCards - p1->nSeri);
			if(diff == 0) { //活牌比例一样大
				if(p1->act == p2->act) { //活牌一样多
					return p2->nSeri - p1->nSeri; //先拆序列牌短的列
				} else { //活牌不一样多
					return res; //拆活牌多的列
				}
			} else { //否则先拆活牌(占剩余牌的)比例较大的列
				return diff;
			}
		}
	}

	return res;
}
*/
//对所有的非空牌列,根据活牌数对每个非空牌列进行排序
//活牌数最多的列排在最前
UINT* CJLDoc::SortByActivity(UINT *pCols)
{
	char bc[8+1],rc[4+1],*p,i;
	for(p = bc, i = 1 ; i <= 8 ; i++) { //获取底牌集
		if(!IsEmptyCol(i)) *p++ = BottCard(i); 
	} *p = 0;
	for(p = rc, i = 13; i <= 16; i++) { //获取废牌集
		if(!IsEmptyCol(i)) *p++ = BottCard(i);
		else *p++ = 0;
	} *p = 0;

/*
	//统计非空牌列的有关信息:牌列,牌数目,序列牌长,活牌数
	COL_INF f[8+1],*pLast = f;
	for(i = 1 ; i <= 8 ; i++) {
		if(IsEmptyCol(i)) continue;

		UINT *pTop = &m_iCards[i-1][0];//pTop指向此列牌的顶部
		UINT nCards = pTop[19], nSeri = CntSeriIn(i);
		UINT *pCur = pTop + nCards-nSeri - 1 ;//pCur指向剩余牌的底牌

		char b[8+1], r[4+1]; //复制底牌集和废牌集分别到b,r
		strcpy(b,bc); 
		strcpy(r,rc);

		UINT nAct = 0; //统计此列的活牌数目
		while(pCur >= pTop) { //遍历剩余牌以统计活牌数
			char *pTarg = FindActiveCard(*pCur,b,r);
			if(pTarg) {
				*pTarg = *pCur;//模拟此牌被拿开到其他列(拿到其他牌列或空档或回收列)
				++nAct;//统计此列的活牌数目
			}
			--pCur;
		}
		pLast++->Set(i,nCards,nSeri,nAct);//保存有关此列牌的信息
	} pLast->col = 0;
	//对牌列按照活牌数排序
	qsort( (void*)f, (pLast-f)/sizeof(COL_INF), sizeof(COL_INF), CmpAct);
	//拷贝排序后的牌列到参数数组中
	for(COL_INF * pFirst = f; pFirst < pLast; ) {
		*pCols++ = pFirst++->col;
	} *pCols = 0;
	return pCols;
*/
	//统计非空牌列的有关信息:牌列,牌数目,序列牌长,活牌数
	COL_INF f[8+1],*pLast = f;
	for(i = 1 ; i <= 8 ; i++) {
		if(IsEmptyCol(i)) continue;

		UINT nCards = CntCardsIn(i);
		UINT nSeri = CntSeriIn(i);
		UINT nAct = 0;

		//统计此列的活牌数目
		char b[8+1],r[4+1];  
		strcpy(b,bc); 
		strcpy(r,rc);//复制底牌集和废牌集分别到b,r

		UINT *pTop = &m_iCards[i-1][0];//pTop指向此列牌的顶部
		UINT *pCur = pTop+pTop[19]-1 - nSeri;//pCur指向剩余牌的底牌
		while(pCur >= pTop) {
			char *pAct = FindActiveCard(*pCur,b,r);
			if(pAct) {
				*pAct = *pCur;
				++nAct;//统计此列的活牌数目
			}
			--pCur;
		}
		//保存有关此列牌的信息
		pLast++->Set(i,nCards,nSeri,nAct);
	}
	pLast->col = 0;
	
	//对牌列按照活牌数排序
	COL_INF *pFirst = f;
	while(pFirst < pLast) { 
		COL_INF *p = pLast-1;
		while(--p >= pFirst) {
			if(p->act < (p+1)->act) {
				COL_INF t = p[0]; 
				p[0] = p[1]; 
				p[1] = t; //交换位置
			}
		}
		++pFirst;//活牌数最最多的列已经放到最前了
	}
	//拷贝排序后的牌列到参数数组中
	for(pFirst = f; pFirst < pLast; ) {
		*pCols++ = pFirst++->col;
	} *pCols = 0;
	return pCols;
}

//看看此牌是否可以回收
bool CJLDoc::Trashable(UINT card)
{
	ASSERT(IsCard(card));

	UINT type = TYPE(card);
	if(IsEmptyCol(type+13)) return NUM(card) == 1;//只有A可以放入空列

	return FitTrash(card,BottCard(type+13));//必须花色、点数都相符才行
}

//看看card是否为活牌
char * CJLDoc::FindActiveCard(UINT card, char *b, char *r)
{
	ASSERT(IsCard(card));

	//查看底牌集,看是否可能拿到其他牌列
	for(;*b;++b) if(FitFormula(*b,card)) return b;

	//查看废牌集,看是否符合回收规则
	UINT type = TYPE(card);
	UINT num  = NUM(card);
	if(r[type] == 0) return num == 1 ? &r[type] : 0; //只有A可以放入空列

	return FitTrash(card, r[type]) ? &r[type] : 0;//必须花色、点数都相符才行
}

//利用clock与rand,返回一个随机数
//我们可以产生一个随机数,即使srand每次设置同一个种子,
//调用此函数后也不会得到相同的随机数,这样比较好,
//因为这才是真正的随机开局呀
int CJLDoc::Random(void)
{
	int n = ( (rand() << 16) | (clock() & 0xFFFF) ) & MAX_GAME_NUMBER;
	return max(n,1);
}

BOOL CJLDoc::CanCloseFrame(CFrameWnd* pFrame) 
{
	// TODO: Add your specialized code here and/or call the base class
	if(!GameOver() && !m_pOps->IsEmpty())
		if(IDNO == AfxMessageBox("不玩了么?",MB_YESNO))
			return FALSE;

	return CDocument::CanCloseFrame(pFrame);
}

BOOL CJLDoc::GiveUp() 
{
	//当本局为新局或已结束的时候,可以开始下一局
	if(m_pOps->IsEmpty() || GameOver()) return true;
	//否则要提醒玩家是否放弃当前局
	return IDYES == AfxMessageBox("放弃当前游戏?",MB_YESNO);
}

//随机开局,但不再产生已经出现过的局
void CJLDoc::OnRand() 
{
	// TODO: Add your command handler code here
	if(!GiveUp()) return;
	int nUniqueGame;
	while(m_dlgScore.IsOldGameNumber(nUniqueGame = Random())) ;
	StartGame(nUniqueGame);
}
//上一局
void CJLDoc::OnPrevGame() 
{
	// TODO: Add your command handler code here
	if(!GiveUp()) return;
	StartGame(max(m_nCurGameNumber-1,MIN_GAME_NUMBER));
}
//下一局
void CJLDoc::OnNextGame()
{
	if(!GiveUp()) return;
	StartGame(min(m_nCurGameNumber+1,MAX_GAME_NUMBER));
}
//重玩
void CJLDoc::OnAgain() 
{
	// TODO: Add your command handler code here
	if(!GiveUp()) return;
	if(m_nCurGameNumber > 0) {
		StartGame(m_nCurGameNumber); 
	} else {
		while(!m_pOps->IsEmpty()) {
			Undo();//撤销到开头
		}
	}
}

//战况
void CJLDoc::OnScore() 
{
	m_dlgScore.DoModal();
}

//选局
void CJLDoc::OnSelectgamenumber()
{
	if(!GiveUp()) return;

	CDlgSelGame dlg;
	dlg.m_nGameNumber = m_nCurGameNumber;
	if(dlg.DoModal() != IDOK) return;

	StartGame(dlg.m_nGameNumber);
}
//根据给定的牌局代号开始此局
void CJLDoc::StartGame(int gameNumber) 
{
	m_dlgScore.UpdateScore();//记录战况

	ClrOpsRecords();//清除动作记录
	UnselectCardCol();//清除选中标志
	m_Hints.ClrHints();//清除提示
	
	m_nCurGameNumber = gameNumber;
	Shuffle();//洗牌发牌
	GetHints();
	UpdateAllViews(NULL);//绘制界面
	
	CString title;//设置窗框标题为当前牌局代号
	title.Format("%10d",m_nCurGameNumber);
	SetTitle(title);

	m_dlgScore.InitScore();//记录战况
}
//根据给定的标签搜索此标签指定的牌
CARD_POS * CJLDoc::FindCardForLabel(UINT cardLabel , CARD_POS * pos)
{
	for(UINT col = 1; col <= 8; col++) {
		int n = CntCardsIn(col);
		for(UINT idx = 1; idx <= n; idx++) {
			if(NUM(cardLabel) == NUM(GetCard(col,idx))) {
				pos->col = col;
				pos->idx = idx;
				++pos;
			}
		}
	}
	return pos;
}
//自定义牌局
void CJLDoc::OnEdit() 
{
	CDlgDefGame dlg;
	dlg.DoModal();
}

⌨️ 快捷键说明

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