简单互动
为了增加趣味性,增加了简单的互动功能,即实现了一个简单的华容道游戏。在HrdGame中有两个鼠标操作的函数,在传入的控件中调用这个两个函数就可以了。
代码如下:
Click事件
private void pnl_GameBoard_MouseClick(object sender, MouseEventArgs e) => _hrdGame.Click(e);
MouseMove事件
private void pnl_GameBoard_MouseMove(object sender, MouseEventArgs e)
{
if(cbx_ByHuman.Checked)//增加了一个界面控制CheckBox
_hrdGame.MouseMove(e);
}
mouseMove中的代码相对简单,并没有有抓取棋子的操作,只是在滑过的棋子做了高亮处理,这样显得稍微生动一些。
Click 事件核心代码
internal void Click(MouseEventArgs e)
{
var openPcsLst = GetOpenPcs();
// select an open piece
var basicPcsArr = GetPieces();
// pay attention to here , after the serilization !!!!!!!!!!!!!!
// 检查鼠标是否在点击了 Open Piece,如果是则认为是选择了该 棋子,如果不是,则认为是把之前的棋子移动到这个位置。
foreach (var openPcs in openPcsLst)
{
var idx = openPcs.piece.idx;
var pcs = basicPcsArr[idx];
Rectangle pcsRect = new Rectangle((int)pcs.PcsLoc.X, (int)pcs.PcsLoc.Y, pcs.PcsSize.Width, pcs.PcsSize.Height);
if (pcsRect.Contains(e.Location))
{
gameState.selPcs = pcs;
RefreshLayout();
break;
}
}
// 移动到鼠标点击位置的合理性判断,如果是空白区域,则进行移动处理。
if (gameState.selPcs == null)
{
return;
}
var blnkPcs = GetBlanks();
foreach (var blkPcs in blnkPcs)
{
//var pcs = openPcs.piece;
Rectangle pcsRect = new Rectangle((int)blkPcs.PcsLoc.X, (int)blkPcs.PcsLoc.Y, blkPcs.PcsSize.Width,blkPcs.PcsSize.Height);
if (pcsRect.Contains(e.Location))
{
var selPcs = gameState.selPcs;
var selPos = selPcs.GetHrdPos();
var blkPos = blkPcs.GetHrdPos();
if (Math.Abs(selPos.X - blkPos.X) < 2 + selPcs.PcsSize.Width && Math.Abs(selPos.Y - blkPos.Y) < 2 + selPcs.PcsSize.Height)
{ // might move the selected block and into the initial state
if (selPcs.MoveDir != "")
{
//移动时做一个简单的动画处理
MoveWithAnimation(selPcs, blkPcs);
RefreshLayout();
break; ;
}
}
}
}
var tmpHasCode = GetMyHashCodeV1(gameState);
_curHashCode = tmpHasCode;
}
简单动画
说明:代码是在GPT给的代码基础上进行改写的。
利用Timer事件,每隔一段时间显示一个画面,并进行痕迹的擦除。使用了大量的成员变量进行传值,这样简化俩Timer函数参数问题。另外对鼠标事件的多次快速点击没有做处理,因此会有重入发生,导致效果有些混乱。
核心代码如下:
internal void MovePcsWithAnimation(Piece selPcs, HrdPoint srcPos, HrdPoint dstPos, HrdPoint anotherPos, Piece emptySp, Piece anotherES)
{
curSelPcs=selPcs;
srcPcsHrdLoc = HrdPosToLoc(srcPos);
targeHrdLocLst = new List<PointF>();
if(selPcs.GetHrdType()==HRDGame.TYPE_SMALL_PIECE && Math.Abs(srcPos.X - dstPos.X)+Math.Abs(srcPos.Y-dstPos.Y) == 2)
{
// need to move to the another piece first
//anotherPos = new HrdPointHrdPosToLoc(dstPos);
var anotherLoc = HrdPosToLoc(anotherPos);
targeHrdLocLst.Add(anotherLoc);
}
targeHrdLocLst.Add(HrdPosToLoc(dstPos));
srcBkgdRect = new Rectangle((int)selPcs.PcsLoc.X, (int)selPcs.PcsLoc.Y, selPcs.PcsSize.Width, selPcs.PcsSize.Height);
animotionLocLstIdx = 0;
timerTuple.selPcs = selPcs;
timerTuple.srcPos = srcPos;
timerTuple.dstPos= dstPos;
timerTuple.anotherPos= anotherPos;
timerTuple.emptySp = emptySp;
timerTuple.anotherES = anotherES;
timer.Start(); // Start the animation
}
timer事件调用 movePcs函数,函数没有优化,因此代码比较冗长。核心代码如下
private void MovePcs()
{
int speed = 5; // Adjust the speed of the animation
//Refresh the background ;
//float oldX,oldY;
Rectangle tailRec= new Rectangle(0,0,0,0);// used to clear the back ground
if(animotionLocLstIdx>= targeHrdLocLst.Count)
{
return;
}
var targeHrdLoc= targeHrdLocLst[animotionLocLstIdx];
//计算移动后的棋子的坐标和大小,并计算移动后需要擦除的区域
//#############################################
if (srcPcsHrdLoc.X < targeHrdLoc.X)
{
tailRec.X = (int)srcPcsHrdLoc.X;
tailRec.Y = (int)srcPcsHrdLoc.Y;
tailRec.Width = speed;
tailRec.Height = curSelPcs.PcsSize.Height;
srcPcsHrdLoc.X += speed;
}
else if ...
}
//
//#############################################
// 根据上述计算结果,进行绘制
curSelPcs.PcsLoc = srcPcsHrdLoc;
var g=_ctrl.CreateGraphics();
DrawRegularBox(g, curSelPcs);
g.FillRectangle(new SolidBrush(Color.Gray), tailRec);
//srcBkgdRect = tRec;
if (Math.Abs(srcPcsHrdLoc.X - targeHrdLoc.X) <= 5 && Math.Abs(srcPcsHrdLoc.Y - targeHrdLoc.Y) <= 5)
{
animotionLocLstIdx++;
if (animotionLocLstIdx < targeHrdLocLst.Count && animotionLocLstIdx>0)
{
var tLoc = targeHrdLocLst[animotionLocLstIdx-1];
//curSelPcs pcs.PcsSize.Width,pcs.PcsSize.Height
srcBkgdRect = new Rectangle((int)(tLoc.X), (int)(tLoc.Y), curSelPcs.PcsSize.Width, curSelPcs.PcsSize.Height);
}
//判断是否移动可以移动两步(一个正方形)
if (animotionLocLstIdx >= targeHrdLocLst.Count)
{
.....
}
}
}
Timer 的初始化代码
private void InitializeTimer()
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 10; // Adjust the interval based on the desired smoothness
timer.Tick += Timer_Tick;
}
移动效果的目的是为了让过程更清晰一些,并没有对移动逻辑进行严格的分析和约束,因此移动过程不是很优雅。如果笔者有时间或者感兴趣会优化一下。
笔者在完成上述功能后,又增加了一个简单设计的功能,这样就可以利用这个程序进行一点儿华容道各种不同布局的探索以及之间关系的检测。(待续)
Marasun BJFWDQ
2024-03-10