📄 jldoc.cpp
字号:
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 + -