F# 中的国际象棋棋子设计






4.18/5 (4投票s)
本文介绍如何在 F# 中使用 GDI+ 设计国际象棋棋子。
引言
通常,我们使用各种图形软件来设计游戏角色或资源。但在本文中,我们将使用 F# 语言和 GDI+ 图形库来设计国际象棋棋子。由于 F# 是一种 .Net 语言,我们可以轻松访问出色的 GDI+ 图形库。GDI+ 库有许多类和方法可用于处理复杂的矢量和光栅图形。
主窗口
让我们从创建 MainWindow
类开始,它将代表我们项目的窗口。
在 F# 中,类定义以 type 关键字开头 -
type MainWindow() =
inherit Form()
MainWindow
类继承自 System.Windows.Forms.Form
类。因此,请不要忘记在程序开头打开以下命名空间 -
open System;
open System.Windows;
open System.Windows.Forms;
以及用于使用 GDI+ 的这些命名空间 -
open System.Drawing;
open System.Drawing.Drawing2D;
我们还需要通过 Visual Studio 的引用管理器为这些命名空间添加所有引用 -
Visual Studio 的引用管理器 -
然后,我们在 MainWindow
类中创建一个初始化函数,并设置一些窗口属性 -
member this.Init() =
this.Text <- "ChessPiece Design In F#"
this.Size <- new System.Drawing.Size(550, 575)
this.StartPosition <- FormStartPosition.CenterScreen
//this.FormBorderStyle <- FormBorderStyle.Fixed3D
this.MaximizeBox <- false
this.BackColor <- System.Drawing.Color.FromArgb(255, 225, 235)
在初始化这些属性后,我们将 DoubleBuffer
控件样式设置为 true,以防止重绘时闪烁 -
this.SetStyle(ControlStyles.DoubleBuffer, true)
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true)
this.SetStyle(ControlStyles.UserPaint, true)
// . . . . . . .
为窗口添加一个绘制事件处理程序 -
this.Paint.AddHandler(new Windows.Forms.PaintEventHandler(fun s pe -> this.Event_Paint(s, pe)))
我们的绘制事件处理程序将如下所示 -
member this.Event_Paint(sender : System.Object, e : PaintEventArgs) =
e.Graphics.SmoothingMode <- SmoothingMode.HighQuality
e.Graphics.CompositingQuality <- CompositingQuality.HighQuality
// Drawing codes . . . . .
在程序入口点函数中,我们将以这种方式创建窗口 -
let mainWindow = new MainWindow()
// Here we initialize our main window through calling this member function.
mainWindow.Init()
// Lets run our application
Application.Run(mainWindow)
Application.Run
方法将运行一个标准的应用程序消息循环,直到我们通过关闭按钮退出程序。
现在,我们将加载一个国际象棋棋盘图像文件,并将其作为主窗口的背景绘制。只是为了让我们的项目看起来不错。
在 MainWindow
类中,我们创建一个名为 board 的变量,并将其初始值设为 null -
let mutable board = null
然后,在类的初始化函数中,我们通过调用 GDI+ Image 类的静态方法 FromFile
来加载棋盘图像,并将图像赋给 board 变量 -
board <- Image.FromFile(".\ChessBoard.bmp")
在绘制事件处理程序中,我们使用 Graphics
类的 DrawImage
方法绘制图像 -
e.Graphics.DrawImage(board, 0, 0)
设计棋子
在国际象棋游戏中,有六种不同的棋子。王、后、马、象、车和兵。
为此,我们将使用 F# 的面向对象技术。这意味着我们将为每种棋子创建一个类。例如 PieceRook 类、PieceKing 类、PiecePawn 类等。所有棋子类都将继承一个名为 ChessPiece 的类。
让我们看看 ChessPiece 类的定义 -
type ChessPiece() =
使用 let 绑定,我们在类中创建私有字段。
let mutable bitmap = null
let mutable ofg = null
bitmap
和 ofg
就是这样的私有字段。我们将它们初始值设为 null,因为我们将通过成员函数调用 init
来初始化它们 -
member this.init() =
bitmap <- new Bitmap(100, 100)
我们将 bitmap 变量初始化为 GDI+ Bitmap
类对象。我们将使用这个 Bitmap
对象进行离屏绘制。
为了在离屏位图中绘制棋子,我们必须从 bitmap
创建一个 GDI+ Graphics
对象。我们可以通过使用 Graphics
类的静态成员函数 FromImage
来实现 -
ofg <- Graphics.FromImage(bitmap)
实际上,我们不是直接绘制到主窗口,而是为每个棋子创建一个 100x100 的离屏 Bitmap
对象,并通过 ofg
Graphics
对象在 bitmap
中绘制我们的棋子。然后,我们将所有位图绘制到主窗口的图形中。
您可能想知道为什么我们要使用这种技术?
嗯,因为我们将通过 GDI+ 的线条、曲线、椭圆等对象来绘制棋子。以后要调整棋子大小或移动棋子将非常困难。所以在这种情况下,绘制到位图有一些优点 -
- 我们可以通过拉伸位图轻松调整棋子大小。
- 我们也可以非常容易地移动棋子。
让我们看看如何 -
member this.draw(g :System.Drawing.Graphics, x :int, y :int) =
let dest = new Rectangle(x, y, 82, 82) // x, y are position
// Set the streching quality to high
g.InterpolationMode <- InterpolationMode.HighQualityBicubic
g.DrawImage(bitmap, dest, 0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel)
dest
rectangle 存储了 bitmap
的位置和大小。bitmap
的原始大小为 100x100。但我们将其拉伸到 82x82 以适应国际象棋棋盘图像。我们还将 InterpolationMode
属性设置为 HighQualityBicubic
,以便我们的缩放图像质量很高。我们将在主窗口的绘制事件处理程序中调用 draw 成员函数。
现在,我们有了 ChessPiece
类,它将被我们的其他棋子类继承。
让我们先实现 RookPiece
类 -
在 F# 中,我们通过 inherit 关键字继承类。
type PieceRook() =
inherit ChessPiece()
然后,我们这样在类中实现一个 init
成员函数 -
member this.init() =
base.init()
我们通过 base 关键字调用 ChessPiece
类的 init
函数来初始化离屏位图和离屏图形。
现在,我们将我们的 Rook 棋子绘制到 ChessPiece
类的离屏图形中。由于 ChessPiece
类的 ofg
字段是私有的,我们无法这样访问它 -
let g = base.ofg
所以,我们需要一个名为 getGraphics
的属性在 ChessPiece
类中来访问离屏图形对象 -
member this.getGraphics = ofg
现在我们可以使用该属性来访问离屏图形 -
let g = base.getGraphics
我们将使用 GDI+ 的 GraphicsPath
类来简化绘图。
let path = new GraphicsPath()
GraphicsPath
类非常有助于绘制一系列连接的线条和曲线。我们通过调用 GraphicsPath.StartFigure
方法开始绘制我们的车 -
path.StartFigure()
我们将通过向路径添加线条和曲线来绘制 Rook -
path.AddCurve(ptsRookTop)
path.AddLines(ptsRook)
根据 MSDN
一个由一系列连接的线条和曲线组成的图形(其起点和终点可能重合)是一个开放图形,除非显式关闭。可以使用 CloseFigure
方法显式关闭一个图形,该方法通过将一条线从终点连接到起点来关闭当前图形。由几何形状基元组成的图形是一个封闭图形。
因此,我们通过调用 GraphicsPath.CloseFigure
方法显式关闭我们的图形 -
path.CloseFigure()
由于我们的 Rook 图形是一个封闭的形状,因此我们可以使用 Graphics.FillPath
方法轻松地用浅灰色填充它 -
g.FillPath(Brushes.LightGray, path)
我们还为路径绘制边框,使 Rook 看起来不错。
g.DrawPath(Pens.Brown, path)
pstRookTop
和 ptsRook
代表在 PieceRook
类中声明的 PointF
结构体数组。ptsRook
点将构成我们的 Rook 主体,而 pstRookTop
点将构成 Rook 棋子的顶部曲线。
// Points for rook body
let ptsRook =
[|
PointF(20.0f, 23.0f)
PointF(20.0f, 50.0f)
PointF(32.0f, 50.0f)
PointF(32.0f, 85.0f)
PointF(15.0f, 85.0f)
PointF(15.0f, 95.0f)
PointF(80.0f, 95.0f)
PointF(80.0f, 85.0f)
PointF(63.0f, 85.0f)
PointF(63.0f, 50.0f)
PointF(75.0f, 50.0f)
PointF(75.0f, 23.0f)
|]
// Points for rook top curve
let ptsRookTop =
[|
PointF(75.0f, 23.0f)
PointF(57.0f, 27.0f)
PointF(35.0f, 27.0f)
PointF(20.0f, 23.0f)
|]
在 F# 中,我们可以通过多种方式创建数组。但在上面的代码中,我们使用了 F# 语言最简单的数组创建技术。请注意,我们在每个 PointF
构造参数值中使用 'f' 后缀,因为 PointF
对 X
和 Y
字段使用浮点类型。
现在,为了绘制 Knight 棋子,我们将使用线条和椭圆 -
我们的 KnightPiece
类 -
type PieceKnight() =
inherit ChessPiece()
下面的 PointF
数组代表我们的 Knight 棋子主体 -
let ptsKnight =
[|
PointF(72.0f, 95.0f)
PointF(15.0f, 95.0f)
PointF(15.0f, 80.0f)
PointF(30.0f, 80.0f)
PointF(38.0f, 60.0f)
PointF(20.0f, 62.0f)
PointF(16.0f, 58.0f)
PointF(23.0f, 53.0f)
PointF(14.0f, 56.0f)
PointF(14.0f, 50.0f)
PointF(35.0f, 32.0f)
PointF(34.0f, 26.0f)
PointF(39.0f, 23.0f)
PointF(58.0f, 27.0f)
PointF(58.0f, 80.0f)
PointF(72.0f, 80.0f)
|]
然后我们将所有点添加到 GraphicsPath
对象,并用浅灰色和棕色填充并绘制路径 -
member this.init() =
base.init()
let g = base.getGraphics
let path = new GraphicsPath()
path.StartFigure()
path.AddLines(ptsKnight)
path.CloseFigure()
g.FillPath(Brushes.LightGray, path)
g.DrawPath(Pens.Brown, path)
我们为 Knight 棋子添加了一个简单的眼睛 -
g.FillEllipse(Brushes.White, 35, 37, 7, 7)
g.DrawEllipse(Pens.Brown, 35, 37, 7, 7)
g.FillEllipse(Brushes.Black, 35, 39, 4, 4)
为了绘制 Bishop,我们使用 4 个 PointF
数组来表示 Bishop 主体 -
type PieceBishop() =
inherit ChessPiece()
let ptsBishop =
[|
PointF(51.0f, 64.0f)
PointF(53.0f, 80.0f)
PointF(67.0f, 80.0f)
PointF(67.0f, 95.0f)
PointF(20.0f, 95.0f)
PointF(20.0f, 80.0f)
PointF(36.0f, 80.0f)
PointF(38.0f, 64.0f)
|]
let ptsBishop2 =
[|
PointF(38.0f, 64.0f)
PointF(26.0f, 56.0f)
PointF(28.0f, 44.0f)
|]
let ptsBishop3 =
[|
PointF(28.0f, 44.0f)
PointF(42.0f, 53.0f)
PointF(42.0f, 47.0f)
PointF(30.0f, 40.0f)
PointF(42.0f, 22.0f)
|]
let ptsBishop4 =
[|
PointF(42.0f, 22.0f)
PointF(60.0f, 46.0f)
PointF(51.0f, 64.0f)
|]
member this.init() =
base.init()
let g = base.getGraphics
let mutable path = new GraphicsPath()
path.StartFigure()
path.AddLines(ptsBishop)
path.AddCurve(ptsBishop2)
path.AddLines(ptsBishop3)
path.AddCurve(ptsBishop4)
path.CloseFigure()
g.FillPath(Brushes.LightGray, path)
g.DrawPath(Pens.Brown, path)
g.FillEllipse(Brushes.Red, 38, 20, 8, 8)
g.DrawEllipse(Pens.Blue, 38, 20, 8, 8)
我们通过向两个 GraphicsPath
对象添加线条来绘制 King 棋子 -
type PieceKing() =
inherit ChessPiece()
这些点代表 King 棋子的顶部十字 -
let ptsKingTop =
[|
PointF(45.0f, 30.0f)
PointF(45.0f, 24.0f)
PointF(39.0f, 24.0f)
PointF(39.0f, 19.0f)
PointF(45.0f, 19.0f)
PointF(45.0f, 14.0f)
PointF(51.0f, 14.0f)
PointF(51.0f, 19.0f)
PointF(57.0f, 19.0f)
PointF(57.0f, 24.0f)
PointF(51.0f, 24.0f)
PointF(51.0f, 30.0f)
|]
以下点代表 King 的主体 -
let ptsKing =
[|
PointF(15.0f, 30.0f)
PointF(20.0f, 55.0f)
PointF(40.0f, 55.0f)
PointF(30.0f, 80.0f)
PointF(15.0f, 80.0f)
PointF(15.0f, 95.0f)
PointF(80.0f, 95.0f)
PointF(80.0f, 80.0f)
PointF(65.0f, 80.0f)
PointF(55.0f, 55.0f)
PointF(75.0f, 55.0f)
PointF(80.0f, 30.0f)
|]
member this.init() =
base.init()
let g = base.getGraphics
由于我们需要两个 GraphicsPath
对象,所以最好创建可变对象 -
let mutable path = new GraphicsPath()
将顶部形状添加到路径 -
path.StartFigure()
path.AddLines(ptsKingTop)
path.CloseFigure()
将其绘制到离屏图形 -
g.FillPath(Brushes.Blue, path)
g.DrawPath(Pens.Yellow, path)
创建一个新的 GraphicsPath
对象,并将身体形状点添加到新路径 -
path <- new GraphicsPath()
path.StartFigure()
path.AddLines(ptsKing)
path.CloseFigure()
然后将其绘制到离屏图形 -
g.FillPath(Brushes.LightGray, path)
g.DrawPath(Pens.Brown, path)
下面的类代表我们的 Queen 棋子 -
type PieceQueen() =
inherit ChessPiece()
下面的 PointF
数组构建了我们的 Queen 棋子 -
let ptsQueen =
[|
PointF(23.0f, 30.0f)
PointF(23.0f, 55.0f)
PointF(40.0f, 55.0f)
PointF(35.0f, 80.0f)
PointF(15.0f, 80.0f)
PointF(15.0f, 95.0f)
PointF(80.0f, 95.0f)
PointF(80.0f, 80.0f)
PointF(60.0f, 80.0f)
PointF(55.0f, 55.0f)
PointF(73.0f, 55.0f)
PointF(73.0f, 30.0f)
|]
然后,我们实现 init
函数,将 PointF
数组添加到 GraphicsPath
对象,并用 LightGray
颜色填充路径,同时通过调用 GraphicsPath
类的 DrawPath
方法绘制其边框 -
member this.init() =
base.init()
let g = base.getGraphics
let path = new GraphicsPath()
path.StartFigure()
path.AddLines(ptsQueen)
path.CloseFigure()
g.FillPath(Brushes.LightGray, path)
g.DrawPath(Pens.Brown, path)
g.FillEllipse(Brushes.Orange, 24, 22, 8, 8)
g.DrawEllipse(Pens.Brown, 24, 22, 8, 8)
g.FillEllipse(Brushes.Orange, 40, 22, 6, 6)
g.DrawEllipse(Pens.Yellow, 40, 22, 6, 6)
g.FillEllipse(Brushes.Orange, 50, 22, 6, 6)
g.DrawEllipse(Pens.Yellow, 50, 22, 6, 6)
g.FillEllipse(Brushes.Orange, 65, 22, 8, 8)
g.DrawEllipse(Pens.Brown, 65, 22, 8, 8)
g.FillEllipse(Brushes.Red, 43, 12, 10, 10)
g.DrawEllipse(Pens.Blue, 43, 12, 10, 10)
这是我们 PiecePawn
类的实现。我们通过向 GraphicsPath
对象添加线条,并绘制矩形和椭圆来绘制 Pawn -
type PiecePawn() =
inherit ChessPiece()
下面的 PointF
数组代表我们的 Pawn 的三角形形状 -
let ptsPawn =
[|
PointF(68.0f, 80.0f)
PointF(30.0f, 80.0f)
PointF(48.0f, 30.0f)
|]
然后,我们通过将点数组添加到 GraphicsPath
对象来绘制三角形 -
let g = base.getGraphics
let mutable path = new GraphicsPath()
path.StartFigure()
path.AddLines(ptsPawn)
path.CloseFigure()
g.FillPath(Brushes.LightGray, path)
g.DrawPath(Pens.Brown, path)
g.FillRectangle(Brushes.LightGray, 18, 80, 62, 15)
g.DrawRectangle(Pens.Brown, 18, 80, 62, 15)
g.FillEllipse(Brushes.Yellow, 36, 22, 26, 26)
g.DrawEllipse(Pens.DarkOrange, 36, 22, 26, 26)
// Left eye
g.DrawLine(Pens.Brown, 42, 26, 47, 29)
g.FillEllipse(Brushes.LightGreen, 42, 30, 6, 6)
g.FillEllipse(Brushes.Black, 43, 32, 4, 4)
g.DrawEllipse(Pens.Pink, 42, 30, 6, 6)
// Right eye
g.DrawLine(Pens.Brown, 57, 26, 51, 29)
g.FillEllipse(Brushes.LightGreen, 50, 30, 6, 6)
g.FillEllipse(Brushes.Black, 51, 32, 4, 4)
g.DrawEllipse(Pens.Pink, 50, 30, 6, 6)
path <- new GraphicsPath()
let ptsMouth =
[|
PointF(45.0f, 41.0f)
PointF(47.0f, 39.0f)
PointF(53.0f, 41.0f)
|]
path.AddLines(ptsMouth)
g.DrawPath(Pens.Brown, path)
创建完所有棋子后,我们在主窗口类中为每个类创建对象 -
type MainWindow() =
inherit Form()
let rook = new PieceRook()
let knight = new PieceKnight()
let bishop = new PieceBishop()
let king = new PieceKing()
let queen = new PieceQueen()
let pawn = new PiecePawn()
在主窗口的 init
成员函数中,我们调用每个 ChessPiece
对象的 init
成员函数 -
rook.init()
knight.init()
bishop.init()
king.init()
queen.init()
pawn.init()
最后,我们在主窗口的绘制事件处理程序中通过调用 ChessPiece
类的 draw 成员函数来绘制我们的棋子 -
member this.Event_Paint(sender : System.Object, e : PaintEventArgs) =
e.Graphics.SmoothingMode <- SmoothingMode.HighQuality
e.Graphics.CompositingQuality <- CompositingQuality.HighQuality
rook.draw(e.Graphics, 135, 125)
knight.draw(e.Graphics, 265, 125)
bishop.draw(e.Graphics, 197, 187)
king.draw(e.Graphics, 135, 247)
queen.draw(e.Graphics, 260, 247)
pawn.draw(e.Graphics, 194, 310)
关注点
我认为这篇文章对于我们学习 GDI+ 和 F# 语言来说是一个很好的实践。
结论
总而言之,我想说的是,本文可能没有展示通过编程设计国际象棋棋子的最佳技术。但肯定的是,您可以学习 GDI+ 在 F# 语言中的高级用法。