65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.18/5 (4投票s)

2015年10月23日

CPOL

7分钟阅读

viewsIcon

16239

downloadIcon

257

本文介绍如何在 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

bitmapofg 就是这样的私有字段。我们将它们初始值设为 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)

pstRookTopptsRook 代表在 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' 后缀,因为 PointFXY 字段使用浮点类型。 

现在,为了绘制 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# 语言中的高级用法。 

© . All rights reserved.