📄 jldoc.cpp
字号:
//如果没有结束则计算提示步骤
//否则就让玩家选择是否开局或回放存档
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 + -