FireMonkey3D之中国象棋程序(一)界面设计

这篇具有很好参考价值的文章主要介绍了FireMonkey3D之中国象棋程序(一)界面设计。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。

  本章目标:

  • 制作一个可操作的图形界面

  第一步我们设计图形界面,显示初始化棋局。效果如下图:

FireMonkey3D之中国象棋程序(一)界面设计

 我们先做个3D象棋子控件(请看我的博客关于FireMonkey3D的文章:万能控件Mesh详解),源码如下:

unit ChessPiece;

interface

uses
  System.SysUtils,System.Types,System.UITypes,System.Classes, FMX.Types, FMX.Controls3D, FMX.Objects3D,FMX.Types3D,
  FMX.Materials,System.Math.Vectors,FMX.Graphics,System.Math,System.RTLConsts;
type
  TChessPiece = class(TControl3D)
  private
    FMat:TLightMaterial;
    FBitmap:TTextureBitmap;
	  FChessName:string;
    FSide,FID:Byte;//ID为棋子序号
    FColor:TAlphaColor;
    procedure SetChessName(const Value:string);
    procedure SetSide(const Value:Byte);
	  procedure SetID(const Value:Byte);
    procedure DrawPiece;
  protected
    procedure Render; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property ChessName:string  read FChessName write SetChessName;
	  property Side:Byte  read FSide write SetSide default 0;
	  property id:Byte read FID write SetID;
    property Cursor default crDefault;
    property DragMode default TDragMode.dmManual;
    property Position;
    property Scale;
    property RotationAngle;
    property Locked default False;
    property Width;
    property Height;
    property Depth nodefault;
    property Opacity nodefault;
    property Projection;
    property HitTest default True;
    property VisibleContextMenu default True;
    property Visible default True;
    property ZWrite default True;
    property OnDragEnter;
    property OnDragLeave;
    property OnDragOver;
    property OnDragDrop;
    property OnDragEnd;
    property OnClick;
    property OnDblClick;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheel;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnKeyDown;
    property OnKeyUp;
    property OnRender;
  end;
procedure Register;

implementation
procedure TChessPiece.DrawPiece;
var
  Rect:TRectF;
begin
  with FBitmap do
  begin
	Canvas.BeginScene;
  Clear($FFFFFFFF);
  Rect:=TRectF.Create(2,2,98,98);
  Canvas.Stroke.Thickness:=2;
  Canvas.Stroke.Color:=FColor;
	Canvas.DrawEllipse(Rect,1);
  Canvas.Fill.Color:=FColor;
	Canvas.FillText(Rect,FChessName,false,1,[TFillTextFlag.RightToLeft],TTextAlign.Center,TTextAlign.Center);
	Canvas.EndScene;
  end;
  Repaint;
end;

constructor TChessPiece.Create(AOwner: TComponent);
begin
  inherited;
  FColor:=$FFFF0000;
  FChessName:='车';
  FMat:=TLightMaterial.Create;
  FMat.Emissive:=TAlphaColorRec.Burlywood;
  FBitmap:=TTextureBitmap.Create;
  with FBitmap do
  begin
    SetSize(100,200);
    Canvas.Font.Family:='方正隶书繁体';
	  Canvas.Font.Size:=85;
  end;
  DrawPiece;
end;

destructor TChessPiece.Destroy;
begin
  FMat.Free;
  FBitmap.Free;
  inherited;
  
end;
procedure TChessPiece.SetChessName(const Value:string);
begin
  if FChessName <> Value then
  begin
    FChessName := Value;
    DrawPiece;
  end;
end;
procedure TChessPiece.SetSide(const Value:Byte);
begin
  if FSide <> Value then
  begin
    FSide := Value;
    case FSide of
         0:  FColor:=$FFFF0000;
         1:  FColor:=$FF24747D;
    end;
    DrawPiece;
  end;
end;
procedure TChessPiece.SetID(const Value:Byte);
begin
  if FID<>value then
     FID:=Value;
end;
procedure TChessPiece.Render;
var
  i,j,k,VH,VW,AA,BB,M:Integer;
  indice:array of  Integer;
  P,P1:TPoint3D;
  Ver:TVertexBuffer;
  Idx:TIndexBuffer;
  Pt:TPointF;
  Angle,H,D,R:Single;//H:前后圆的半径Height/2,R:棋子周边圆弧的半径,D棋子的厚度Height/5
begin
  VH:=32;VW:=12;
  indice:=[0,1,3,0,3,2];
  H:=0.5*Height;
  D:=0.2*Height;
  R:=D/sin(DegToRad(48));
  FMat.Texture:=nil;
  FMat.Texture:=FBitmap.Texture;
  Ver:=TVertexBuffer.Create([TVertexFormat.Vertex,TVertexFormat.Normal,TVertexFormat.TexCoord0],VH*VW*4+VH*2);
  Idx:=TIndexBuffer.Create(VH*6*VW+VH*6-12,TIndexFormat.UInt32);
  AA:=0;BB:=0;
  //Around棋子周边
  for I := 0 to VH-1 do
  for J := 0 to VW-1 do
  begin
    for k := 0 to 1 do
    begin
      Angle:=DegToRad((318-(j+k)*8));
      P:=Point3D(0,R*sin(Angle),R*Cos(Angle));
      P1:=P/R;
      P.Offset(0,-R*Sin(DegToRad(318))-H,0);
      Ver.Vertices[AA+k*2]:=P*TMatrix3D.CreateRotationZ(2*Pi/VH*i);
      Ver.Normals[AA+k*2]:=P1*TMatrix3D.CreateRotationZ(2*Pi/VH*i);
	    Ver.Vertices[AA+k*2+1]:=P*TMatrix3D.CreateRotationZ(2*Pi/VH*(i+1));
      Ver.Normals[AA+k*2+1]:=P1*TMatrix3D.CreateRotationZ(2*Pi/VH*(i+1));
	    //按横向、纵向细分一个贴图
      Ver.TexCoord0[AA+k*2]:=PointF(1/12*(J+k),I/128+0.5);
      Ver.TexCoord0[AA+k*2+1]:=PointF(1/12*(J+k),(I+1)/128+0.5);
    end;
    inc(AA,4);
    for k :=0 to 5 do
    begin
      Idx.Indices[BB]:=indice[k]+4*(BB div 6);
      inc(BB);
    end;
  end;
  //Front Back 前后圆
  M:=AA;
  for I := 0 to VH-1 do
  begin
    P:=Point3D(0,-H,-D);
    Ver.Vertices[AA]:=P*TMatrix3D.CreateRotationZ(2*Pi/VH*i);
    Ver.Normals[AA]:=Point3D(0,0,-1);
    Pt:=PointF(0,-0.5).Rotate(2*Pi/VH*i);
    Pt.Offset(0.5,0.5);
    Ver.TexCoord0[AA]:=PointF(Pt.x,Pt.y/2);;
    P:=Point3D(0,-H,D);
    Ver.Vertices[AA+1]:=P*TMatrix3D.CreateRotationZ(2*Pi/VH*i);
    Ver.Normals[AA+1]:=Point3D(0,0,1);
    Ver.TexCoord0[AA+1]:=PointF(Pt.x,Pt.y/2+0.5);
    Inc(AA,2);
  end;
  for I := 0 to VH-3 do
  begin
    Idx.Indices[BB]:=M+2+I*2;
    Idx.Indices[BB+1]:=M+4+I*2;
    Idx.Indices[BB+2]:=M;
    Idx.Indices[BB+3]:=M+5+I*2;
    Idx.Indices[BB+4]:=M+3+i*2;
    Idx.Indices[BB+5]:=M+1;
    Inc(BB,6);
  end;
  Context.DrawTriangles(ver,idx,FMat,Opacity);
  Ver.Free;
  Idx.Free;
end;
procedure Register;
begin
  RegisterComponents('3D Others', [TChessPiece]);
end;

end.

  1.1 棋盘表示

  中国象棋有10行9列,很自然地想到可以用10×9矩阵表示棋盘。界面左侧棋盘为Image3D控件,加载一个做好的“棋盘.png”,设定其width、height分别为9、10,3D里的单位不是像素,根据估算,1个单位相当于50像素。同时放置一个TDummy控件,Name=PieceDy,用来放棋子。由于3D控件的特性,其MouseUp事件并不能确定位纵横坐标值,所有我定义了csLy:array [0..9,0..8]  of TLayout3D控件,对应10×9矩阵,这样通过点击TLayout3D就可以知道所在格子的纵横坐标。先把棋盘做好:

var
  I,J:Integer;
  csLy:array[0..9,0..8]of TLayOut3D;
begin
  ChessBg.Bitmap.LoadFromFile('棋盘.png');
  for i := 0 to 9 do
    for j := 0 to 8 do
    begin
      csLy[i,j]:=TLayout3D.Create(self);
      csLy[i,j].Parent:=PieceDy;
      csLy[i,j].Position.Point:=Point3D(j-4,i-4.5,0);//3D物体的原点在其中心,所以csLy要偏移
      csLy[i,j].SetSize(1,1,0);
      csLy[i,j].OnClick:=csBoard;//Click事件统一使用csBoard。
    end;
end;

1.2、棋子表示

为了与象棋巫师兼容,使用整数表示棋子: 

 //棋子编号
 const 
  csName: string = '车马相仕帅仕相马车炮炮兵兵兵兵兵车马象士将士象马车炮炮卒卒卒卒卒';
  PcCode: array[0..31] of Byte=    //棋子的编号
   (4,3,2,1,0,1,2,3,4,5,5,6,6,6,6,6,
    4,3,2,1,0,1,2,3,4,5,5,6,6,6,6,6);

  PIECE_KING    = 0;    //帅(将)
  PIECE_ADVISOR = 1;    //士(仕)
  PIECE_BISHOP  = 2;    //相(象)
  PIECE_KNIGHT  = 3;    //马
  PIECE_ROOK    = 4;    //车
  PIECE_CANNON  = 5;    //炮
  PIECE_PAWN    = 6;    //兵(卒) 

3D程序设计时,32个棋子必须要有自己的id,否则不好调用,0-15为红棋,16-31为黑棋,其顺序按csName排列,现在把32个棋子建立起来: 

var chess:array[0..31] of TChessPiece;
for I :=0 to 31 do
  begin
    chess[i]:=TChessPiece.Create(Self);
    chess[i].Parent:=PieceDy;
    if i>15 then
       chess[i].side:=1;//side表示红黑走棋方
    chess[i].ChessName:=csName[i+1];
    chess[i].ID:=i;
    chess[i].Height:=0.8;
    chess[i].OnClick:=csBoard;//同上
  end;

  1.3 字符串(或数组)表示局面

  根据UCCI标准,我们用一行字符串表示一个局面,这就是FEN文件格式串。中国象棋的初始局面可表示为:

rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1

  红色区域,表示棋盘布局,小写表示黑方,大写表示红方。一个字母表示一个棋子,与上面定义的首字母对应,数字表示有n个空。本程序只解析红色部分。“/”用来区分每一行,共10行。1c5c1解析起来就是:□炮□□□□□炮□,代码:

procedure FromFen(FEN:string);
var
  i,j,k,id:Integer;
  Str,CODE:string;
  ss:TArray<string>;
begin
  CODE:='RNBAKABNRCCPPPPPrnbakabnrccppppp';
  ss:=FEN.Substring(0,FEN.IndexOf(' ')).Split(['/']);
  for I := 0 to 31 do
  begin
    chess[i].Visible:=False;
  end;
  for I := 0 to 9 do
  begin
    Str:=ss[i];
    k:=0;
    for j := 1 to Length(Str) do
    begin
      id:=CODE.IndexOf(Str[j]);
      if id>=0 then
      begin
        chess[id].Visible:=True;
        chess[id].Position.Point:=Point3D(k-4,i-4.5,-0.16);
        CODE[id+1]:=' ';
        Inc(k);
      end
      else
        Inc(k,ord(Str[j])-$30);
    end;
  end;
end;  

 其实用数组简单得多:

var
  i:Integer;
  P:TPoint;
const
  startPos: array[0..31] of Byte =//棋子的初始位置
    ($09, $19, $29, $39, $49, $59, $69, $79, $89, $17, $77, $06, $26, $46, $66, $86,
     $00, $10, $20, $30, $40, $50, $60, $70, $80, $12, $72, $03, $23, $43, $63, $83);
  
begin
  for I := 0 to 31 do
  begin
    chess[i].Visible:=False;
    chess[i].ResetRotationAngle;
    P:=Point(startPos[i] shr 4,startPos[i] and $F);
    chess[i].Position.Point:=Point3D(P.X-4,P.Y-4.5,-0.16);
    chess[i].Visible:=true;
   end;
end;

  1.4 走动棋子

我们让棋子可以通过点击实现走棋。点击时,要考虑源点和目标点,选中的是哪个棋子等细节。定义全局变量:selecti=32,因为棋子的id是从0-31,零已被占用,就用32表示未选中棋子。棋子从源点的位置走到目标点的位置,即实现走棋。

首先要定义两个函数:

var  
  selecti:Byte=32;
function GetPos(Pt:TPosition3D):TPoint;
begin
  Result:=Point(Round(Pt.X+4),Round(Pt.Y+4.5));
end;
function GetChessPos(i:Byte):TPoint;
var
  Pt:TPosition3D;
begin
  Pt:=chess[i].Position;
  Result:=Point(Round(Pt.X+4),Round(Pt.Y+4.5));
end;
//GetPos根据3D控件的位置,获取其坐标
//GetChessPos根据chess的id,获取其坐标 

   然后,定义csBoard鼠标点击事件,我们已经把chess和csLy的点击事件关联到了csBoard,这样只需要写一个函数即可实现所有控件的点击事件。这里要实现红黑方轮流走棋,就要定义Player,0表示红棋,1表黑棋,完成走棋后必须切换Player。我们用动画实现走棋,体现走棋的过程。代码如下:

var
  player:Byte=0;//默认0为红,黑为1;
  Animator:TAnimator;//动画控件
procedure TChessForm.MoveAni(i:integer;dest:TPoint);
begin
  Animator.AnimateFloat(chess[i],'Position.X',csLy[dest.Y,dest.X].Position.X,0.1);
  Animator.AnimateFloat(chess[i],'Position.Y',csLy[dest.Y,dest.X].Position.Y,0.1);
  Animator.AnimateFloatWait(chess[i],'Position.Z',-0.16,0.1);
end;
procedure ChangeSide;
begin
  Player:=1-Player;//换边
end;
procedure csBoard(Sender: TObject);
var
  id:Byte;
  src,dest:TPoint;
begin
  if selecti=32 then
  begin
    if Sender is TLayout3D then Exit; //未选棋且点击空白处
    id:=TChessPiece(Sender).id;
    if Player<>chess[id].Side then Exit; //未轮到某方走棋
    chess[id].Position.Z:=-0.5;//选中的棋“抬”起来,类似天天象棋的效果
    selecti:=id;
    Exit;
  end;
  if Sender is TChessPiece then   //已选中棋子,且dest点也是棋子
  begin
    id:=TChessPiece(Sender).id;
    if id=selecti then Exit;      //与选中棋子相同
    if Player=chess[id].Side then //与选中棋子同属一个阵营
    begin
      chess[id].Position.Z:=-0.5;
      chess[selecti].Position.Z:=-0.16;
      selecti:=id;
      Exit;
    end;
  end;
  src:=GetChessPos(selecti);
  dest:=GetPos(TControl3D(Sender).Position);
  MoveAni(selecti,dest);
   if  Sender is TChessPiece then
  begin
    chess[id].Visible:=False;
  end;
  selecti:=32;
  changeSide;
end;

  说明:为了便于程序后续设计,所有常量及全局变量放在csCommn单元内,实现走棋的函数全面放在csPieceMove单元。csPieceMove定义了一个record:

type TPieceMove=record
  Player:Integer;//轮到谁走,0=红方,1=黑方
  procedure Startup;                   //初始化棋盘
  procedure FromFen(FEN:string);       //从棋谱开局初始化棋盘
  procedure ChangeSide;                //换边
end;

  使用记录将函数、变量进行封装,调用起来就非常简单,不需要声明函数(为何不用calss,class需要Create和Free,哪有记录使用方便)。

  下一章实现目标:

  • 实现中国象棋规则

  本程序关键部分已讲解,整个源码共享在百度网盘:

  链接:中国象棋程序设计(一)界面设计
  提取码:1234

 文章来源地址https://www.toymoban.com/news/detail-807406.html

到了这里,关于FireMonkey3D之中国象棋程序(一)界面设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FireMonkey3D之中国象棋程序设计(四)水平效应、检查重复局面

    声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。 上一章我们的程序终于会走棋了,不过很多时候它很低能。由于水平线效应,任何变化都只搜索固定的深度。还有,有时它会长将。我们能做哪些改进呢? 本章的目标: 用Zobrist校验码技术

    2024年01月20日
    浏览(38)
  • Qt CMake 中国象棋程序实现

    C++自学精简实践教程 目录(必读) C++数据结构与算法实现(目录) Qt 入门实战教程(目录) 为学习 Qt 的人提供一个合适的有一定难度的综合型练习项目。 在学会写代码之前,先看别人怎么写的代码。深入其中,扩展完善。 最大限度的模拟企业开发的真实场景。 运行效果 中

    2024年02月09日
    浏览(38)
  • 从0开始写中国象棋-创建棋盘与棋子

    考虑到象棋程序,其实就是数据结构与算法实现。 所以和界面相关的QT部分我们先放一放。 我们从控制台版本开始。这样大家更容易接受,也不影响开发。 后面我们会把控制台嫁接到QT上完成完整的游戏,那时候自然就水到渠成了。 中国象棋的棋盘是一个宽9列,长 5+5 = 10

    2024年02月07日
    浏览(36)
  • 中国象棋AI库AlphaZero_ChineseChess

    AlphaZero_ChineseChess是一个基于AlphaZero算法的中国象棋AI库,它是开源的,使用Python语言编写,托管在GitHub上。以下是对AlphaZero_ChineseChess库的详细介绍: 算法原理 AlphaZero_ChineseChess基于AlphaZero算法,这是一种基于自我对弈的强化学习算法,能够让AI自主学习棋局的优劣、评估策略

    2024年02月14日
    浏览(115)
  • C++实现双人中国象棋(一)——算法篇(附完整代码)

    最近突发奇想,要使用C++做一个双人象棋的程序,昨天肝了一天,终于把算法部分完成了,下面把开发过程中的经验分享一下。 开发环境:Visual Studio 2019 语言标准:C++11及以上 纠错:暂无 知识要求: 熟练掌握C++语言面向对象编程的知识(继承,多态) 掌握STL的基本操作 了

    2024年02月04日
    浏览(56)
  • C++900行代码实现中国象棋游戏规则以及相关功能

    本文章通过C++中的900行代码实现中国象棋游戏以及相关功能,主要的内容如下: 1.设置未进入游戏前的主页面; 2.绘制棋盘(如果有刚好尺寸的图片也可直接加载),包括棋盘网格,炮与兵的特殊标记绘制; 3.绘制和创建棋子,并令其初始化在棋盘的相应位置; 4.游戏开始,

    2024年02月09日
    浏览(49)
  • AI人工智能(调包侠)速成之路十五(中国象棋AI网络机器人:AI模型部署)

    神经网络模型动态加解密的技术这个以后再写吧  书接上文: AI人工智能(调包侠)速成之路十四(中国象棋AI网络机器人:AI技术综合应用实现) 神经网络模型的存储格式         我们训练的神经网络就是一堆模拟神经元的参数集合,给(他/她/它)一个输入信息就会得到

    2024年01月23日
    浏览(47)
  • 从0实现基于Alpha zero的中国象棋AI(会分为多个博客,此处讲解蒙特卡洛树搜索)

    ​ 题主对于阿尔法狗的实现原理好奇,加上毕业在即,因此选择中国象棋版的阿尔法zero,阿尔法zero是阿尔法狗的升级版。在完成代码编写的历程中,深刻感受到深度学习环境的恶劣,网络上固然资料繁多,但要么水平不行,不知所云,要么国外课程,门槛过高。因而碰壁良

    2024年02月06日
    浏览(48)
  • html Antv L7 + mapbox 实现3D地图 3D中国地图 不限于中国地图

    echarts的3D地图实在太丑了,还一堆bug 使用阿里的Antv可视化库L7,实现3D地图,底图是mapbox 参考示例:https://l7.antv.antgroup.com/zh/examples/polygon/3d#floatMap 如果不需要底图样式,可把Scene的style设置为blank 直接上代码了,vue的就不说了,项目是html的 mapbox依赖 L7依赖 body元素 实现

    2024年02月14日
    浏览(50)
  • ThreeJS——在3D地球上标记中国地图板块

    地球预览视频效果 TweenJS (动画库)用来做相机转场的动画 Jquery (这里只用到一个 each 循环方法,可以使用 js 去写) ThreeJS (3D 地球制作) 100000.json (全国城市经纬度) d3.v6.js 用来设置平面转3D效果(本来考虑做成3D的中国地图板块,最后因效果看起来比较美观还是考虑用线条嵌入球体

    2024年02月12日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包