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