用 Silverlight 开发围棋在线对弈程序

作者: Neil Chen

第一部分:UI雏形

首先,介绍下围棋的简单规则:黑白双方交替落子,以占据棋盘上交叉点多者为胜。同时,双方为了争夺地盘,可能会发生“对杀”。一个棋子周围接触的空白交叉点数目叫做“气”,如果一个或多个棋子周围的气都被对方封死,气数=0,则这些棋子就称为死棋,需要从棋盘上移去。

一个围棋棋谱大致如下图所示(截图自Tom围棋网站):

在上图中,棋子上的数字一般在棋谱中显示,用于帮助了解棋局进行的次序。

下面我们来尝试用 Silverlight 2.0 开发一个围棋在线对弈程序。

首先,我们来创建围棋程序的 UI 部分。毕竟,这是最直观的东西。而且我喜欢边做边重构的开发方式,这样,不至于因为花了过多的时间做设计,而减慢了实际开发的进度。让我们先从一个小小的原型起步,然后不断的应用设计思维去改进它,最终达到目标。正如一部电影里的台词所说的:

Aim small, miss small.

好了,现在大概分析一下:

1.       我们打算在界面的左侧显示棋盘,而右侧是功能区域。

2.       棋盘是由19道横线,19道竖线,以及9个星位的标志组成的。为了方便查找棋盘上的位置,我们在棋盘的四周可能需要加上坐标。目前我们先只在左侧和上方加上坐标。右边和下面的位置留在那里。

对于棋盘的显示,我们打算用一个 Canvas 来实现。而其中的线条,圆点,棋子等视觉元素,只需往其中添加相应的 Line, Ellipse, Label 即可。

我们假定整个程序的大小为 800 * 600 (以后也许再考虑是否有必要支持任意比例的缩放)。现在,跟随直觉,我写了下面一些代码用于构建 UI:

Page.xaml:

<UserControl

x:Class="WoodFoxWeiQi.UI.Page"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Width="800"

Height="600">

<Grid

x:Name="LayoutRoot"

Background="White">

<Grid.ColumnDefinitions>

<ColumnDefinition

Width="0.75*" />

<ColumnDefinition

Width="0.25*" />

</Grid.ColumnDefinitions>

<!-- 棋盘区域 -->

<Border

Grid.Column="0">

<!-- 棋盘 -->

<Canvas

x:Name="canvasBoard"

Background="LightYellow"

Margin="10">

</Canvas>

</Border>

<!-- 操作区域 -->

<Border

Grid.Column="1">

<StackPanel

Margin="20"

Orientation="Vertical">

<Button

x:Name="btnGo"

Content="Go" />

</StackPanel>

</Border>

</Grid>

</UserControl>

Page.xaml.cs:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

namespace WoodFoxWeiQi.UI

{

public partial class Page : UserControl

{

public Page()

{

InitializeComponent();

canvasBoard.MouseLeftButtonDown += new MouseButtonEventHandler(canvasBoard_MouseLeftButtonDown);

canvasBoard.SizeChanged += new SizeChangedEventHandler(canvasBoard_SizeChanged);

btnGo.Click += new RoutedEventHandler(btnGo_Click);

}

// 因为 Canvas 的尺寸是根据父控件的尺寸在运行时计算得到的,所以需要在 SizeChanged 方法里

// 才能获得其实际尺寸

void canvasBoard_SizeChanged(object sender, SizeChangedEventArgs e)

{

canvasBoard.Children.Clear();

CreateBoardElements();

}

double boardSize;    // 棋盘宽度(不包含坐标)

double cellSize;     // 网格宽度

double starSize;     // 星位的小圆点的直径

double stoneSize;    // 棋子直径

// 创建棋盘上的网格线,星位,坐标等显示元素

private void CreateBoardElements()

{

// 确保使用一个正方形区域作为棋盘显示区域

boardSize = Math.Min(canvasBoard.ActualHeight, canvasBoard.ActualWidth);

// 根据棋盘尺寸计算出相应的其他尺寸

cellSize = boardSize / 20;

starSize = cellSize / 4;

stoneSize = cellSize * 0.8;

for (int i = 1; i <= 19; i++)

{

// 添加水平网格线

var lineHorizontal = new Line();

lineHorizontal.X1 = cellSize;

lineHorizontal.X2 = cellSize * 19;

lineHorizontal.Y1 = lineHorizontal.Y2 = cellSize * i;

lineHorizontal.Stroke = new SolidColorBrush(Colors.Black);

lineHorizontal.StrokeThickness = 1.0;

canvasBoard.Children.Add(lineHorizontal);

// 添加垂直网格线

var lineVertical = new Line();

lineVertical.Y1 = cellSize;

lineVertical.Y2 = cellSize * 19;

lineVertical.X1 = lineVertical.X2 = cellSize * i;

lineVertical.Stroke = new SolidColorBrush(Colors.Black);

lineVertical.StrokeThickness = 1.0;

canvasBoard.Children.Add(lineVertical);

}

// 添加9个星位的标志

for (int i = 4; i <= 16; i += 6)

{

for (int j = 4; j <= 16; j += 6)

{

double x = i * cellSize - starSize / 2;

double y = j * cellSize - starSize / 2;

Ellipse ellipseStar = new Ellipse();

ellipseStar.Stroke = new SolidColorBrush(Colors.Black);

ellipseStar.Fill = new SolidColorBrush(Colors.Black);

ellipseStar.Width = ellipseStar.Height = starSize;

ellipseStar.SetValue(Canvas.LeftProperty, x);

ellipseStar.SetValue(Canvas.TopProperty, y);

canvasBoard.Children.Add(ellipseStar);

}

}

// 画横坐标

for (int i = 1; i <= 19; i++)

{

var txtLabel = new TextBlock();

txtLabel.FontSize = 11.0;

txtLabel.FontWeight = FontWeights.Thin;

txtLabel.Text = i.ToString();

txtLabel.SetValue(Canvas.LeftProperty, i * cellSize - txtLabel.ActualWidth / 2);

txtLabel.SetValue(Canvas.TopProperty, cellSize / 2 - txtLabel.ActualHeight / 2);

txtLabel.Text = i.ToString();

canvasBoard.Children.Add(txtLabel);

}

// 画纵坐标

char c = 'A';

for (int i = 1; i <= 19; i++)

{

var txtLabel = new TextBlock();

txtLabel.FontSize = 11.0;

txtLabel.FontWeight = FontWeights.Thin;

txtLabel.Text = i.ToString();

txtLabel.SetValue(Canvas.LeftProperty, cellSize / 2 - txtLabel.ActualWidth / 2);

txtLabel.SetValue(Canvas.TopProperty, i * cellSize - txtLabel.ActualHeight / 2);

txtLabel.Text = (c++).ToString();

canvasBoard.Children.Add(txtLabel);

}

}

void canvasBoard_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

{

var pos = e.GetPosition(canvasBoard);

MessageBox.Show("Clicked on board, X: " + pos.X + ", Y: " + pos.Y);

}

private void btnGo_Click(object sender, RoutedEventArgs e)

{

// 放置一个测试的棋子(白子)

Ellipse e1 = new Ellipse();

e1.Stroke = new SolidColorBrush(Colors.Black);

e1.Fill = new SolidColorBrush(Colors.White);

e1.Width = e1.Height = stoneSize;

double x = 17 * cellSize - stoneSize / 2;

double y = 4 * cellSize - stoneSize / 2;

e1.SetValue(Canvas.LeftProperty, x);

e1.SetValue(Canvas.TopProperty, y);

canvasBoard.Children.Add(e1);

// 再放一个黑子,带手数显示的

Ellipse e2 = new Ellipse();

e2.Stroke = new SolidColorBrush(Colors.Black);

e2.Fill = new SolidColorBrush(Colors.Black);

e2.Width = e2.Height = stoneSize;

double x2 = 16 * cellSize - stoneSize / 2;

double y2 = 4 * cellSize - stoneSize / 2;

e2.SetValue(Canvas.LeftProperty, x2);

e2.SetValue(Canvas.TopProperty, y2);

canvasBoard.Children.Add(e2);

// 绘制手数显示的 Label

TextBlock lbl2 = new TextBlock();

lbl2.FontSize = 10.0;

lbl2.FontWeight = FontWeights.Thin;

lbl2.Text = "203";

lbl2.Foreground = new SolidColorBrush(Colors.White);

lbl2.SetValue(Canvas.LeftProperty, 16 * cellSize - lbl2.ActualWidth / 2);

lbl2.SetValue(Canvas.TopProperty, 4 * cellSize - lbl2.ActualHeight / 2);

canvasBoard.Children.Add(lbl2);

}

}

}

运行一下看看效果如何:

看起来不赖。在这个界面中,如果点击 ”Go” 按钮,则会在棋盘上摆放两个测试用的棋子,其中黑棋上还标有表示棋步的数字。但是,我们的目标是要做一个能下棋的程序,因此,我们下面要加一些控制代码,比如,在用户点击某个位置的时候,落下棋子(如果该位置是允许落子的),以及控制棋局的开始、结束、认输等操作的按钮以及相关动作处理逻辑。

不过,在开始之前,有必要重构一下上面的 UI 代码,因为它看起来比较乱,一个方法里包含了太多的代码,如果这样继续下去的话,程序很快会变成一堆乱麻而难以为继。

由于很多对象的创建过程是类似的,因此我们可以将它提取到独立的方法中加以重用。另外,因为我们需要能够控制某些界面元素的显示/隐藏(比如坐标),将这些对象保存到当前窗体的字段里是一个不错的主意。

我们还添加了一个 CheckBox,用来控制坐标的显示和隐藏。Xaml 中添加的代码如下:

<CheckBox

x:Name="chkShowAxisLabels"

Content="Show Axis Labels"

Margin="0,10,0,0"

IsChecked="true" />

重构后的代码 Page.xaml.cs (每个方法代码大概5~10行左右):

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

namespace WoodFoxWeiQi.UI

{

public partial class Page : UserControl

{

public Page()

{

InitializeComponent();

canvasBoard.MouseLeftButtonDown += canvasBoard_MouseLeftButtonDown;

canvasBoard.SizeChanged += canvasBoard_SizeChanged;

btnGo.Click += btnGo_Click;

chkShowAxisLabels.Checked += chkShowAxisLabels_Checked;

chkShowAxisLabels.Unchecked += chkShowAxisLabels_Checked;

}

#region Fields

private readonly Brush brush_White = new SolidColorBrush(Colors.White);

private readonly Brush brush_Black = new SolidColorBrush(Colors.Black);

private readonly List<TextBlock> yAxisLabels = new List<TextBlock>(20);

private readonly List<TextBlock> xAxisLabels = new List<TextBlock>(20);

double boardSize;    // 棋盘宽度(不包含坐标)

double cellSize;     // 网格宽度

double starSize;     // 星位的小圆点的直径

double stoneSize;    // 棋子直径

#endregion

// 因为 Canvas 的尺寸是根据父控件的尺寸在运行时计算得到的,所以需要在 SizeChanged 方法里

// 才能获得其实际尺寸

void canvasBoard_SizeChanged(object sender, SizeChangedEventArgs e)

{

canvasBoard.Children.Clear();

CreateBoardElements();

}

void canvasBoard_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

{

var pos = e.GetPosition(canvasBoard);

MessageBox.Show("Clicked on board, X: " + pos.X + ", Y: " + pos.Y);

}

void btnGo_Click(object sender, RoutedEventArgs e)

{

// 放置一个测试的棋子(白子)

var e1 = BuildCircle(stoneSize, 17 * cellSize, 4 * cellSize, brush_Black, brush_White);

// 再放一个黑子,带手数显示的

var e2 = BuildCircle(stoneSize, 16 * cellSize, 4 * cellSize, brush_Black, brush_Black);

// 绘制手数显示的 Label

var lbl2 = BuildLabel("203", brush_White, 10.0, 16 * cellSize, 4 * cellSize);

}

// 显示或隐藏坐标轴

void chkShowAxisLabels_Checked(object sender, RoutedEventArgs e)

{

var show = chkShowAxisLabels.IsChecked.HasValue && chkShowAxisLabels.IsChecked.Value;

foreach (var label in xAxisLabels.Union(yAxisLabels))

{

label.Visibility = show ? Visibility.Visible : Visibility.Collapsed;

}

}

#region Builder methods for children elements

// 创建棋盘上的网格线,星位,坐标等显示元素

void CreateBoardElements()

{

CalculateSizes();

BuildGridLines();

BuildStarPointMarks();

BuildXAxisLabels();

BuildYAxisLabels();

}

// 计算必要的一些尺寸定义值

void CalculateSizes()

{

// 确保使用一个正方形区域作为棋盘显示区域

boardSize = Math.Min(canvasBoard.ActualHeight, canvasBoard.ActualWidth);

// 根据棋盘尺寸计算出相应的其他尺寸

cellSize = boardSize / 20;

starSize = cellSize / 4;

stoneSize = cellSize * 0.8;

}

// 添加网格线

void BuildGridLines()

{

for (var i = 1; i <= 19; i++)

{

// 添加水平网格线

BuildLine(cellSize, cellSize * i, cellSize * 19, cellSize * i);

// 添加垂直网格线

BuildLine(cellSize * i, cellSize, cellSize * i, cellSize * 19);

}

}

// 添加9个星位的标志

void BuildStarPointMarks()

{

for (var i = 4; i <= 16; i += 6)

{

for (var j = 4; j <= 16; j += 6)

{

BuildCircle(starSize, i * cellSize, j * cellSize, brush_Black, brush_Black);

}

}

}

// 画横坐标

void BuildXAxisLabels()

{

for (var i = 1; i <= 19; i++)

{

var lbl = BuildLabel(i.ToString(), brush_Black, 11.0, i * cellSize, cellSize / 2);

xAxisLabels.Add(lbl);

}

}

// 画纵坐标

void BuildYAxisLabels()

{

var c = 'A';

for (var i = 1; i <= 19; i++)

{

var text = (c++).ToString();

var lbl = BuildLabel(text, brush_Black, 11.0, cellSize / 2, i * cellSize);

yAxisLabels.Add(lbl);

}

}

#endregion

#region Basic builder methods

Line BuildLine(double x1, double y1, double x2, double y2)

{

var line = new Line {

X1 = x1,

X2 = x2,

Y1 = y1,

Y2 = y2,

Stroke = brush_Black,

StrokeThickness = 1.0

};

canvasBoard.Children.Add(line);

return line;

}

Ellipse BuildCircle(double diameter, double centerX, double centerY, Brush stroke, Brush fill)

{

var ellipse = new Ellipse { Stroke = stroke, Fill = fill };

ellipse.Width = ellipse.Height = diameter;

ellipse.SetValue(Canvas.LeftProperty, centerX - diameter / 2);

ellipse.SetValue(Canvas.TopProperty, centerY - diameter / 2);

canvasBoard.Children.Add(ellipse);

return ellipse;

}

// 创建 Label

TextBlock BuildLabel(string text, Brush foreground, double fontSize,

double centerX, double centerY)

{

var lbl = new TextBlock {

FontSize = fontSize,

FontWeight = FontWeights.Thin,

Text = text,

Foreground = foreground

};

lbl.SetValue(Canvas.LeftProperty, centerX - lbl.ActualWidth / 2);

lbl.SetValue(Canvas.TopProperty, centerY - lbl.ActualHeight / 2);

canvasBoard.Children.Add(lbl);

return lbl;

}

#endregion

}

}

[第二部分:MVC]

用 Silverlight 开发围棋在线对弈程序(一)UI 雏形相关推荐

  1. 用 Silverlight 开发围棋在线对弈程序(二)MVC

    用 Silverlight 开发围棋在线对弈程序 作者: Neil Chen 第二部分:MVC 为了重用代码,并且开始开发围棋程序的界面控制功能,我们考虑用 MVC 架构来对前面的程序进行一点小的修改 ...

  2. SilverLight开发系列第2步:使用vs2008和Blend 2.5打造Hellow World程序

    上一篇:SilverLight开发系列第1步:搭建开发环境 在VS2008里面创建Silverlight(以下简称SL)应用程序 必须在.Net Framework 3.5环境下,以前版本的.Net ...

  3. STM32在线烧录程序的开发

    STM32在线烧录程序的开发 2013年03月10日 17:07:38 vishtvro 阅读数 2515 STM32在线烧录程序的开发<梅川酷子原创> STM32在芯片生产的阶段固化了一 ...

  4. 二开版优化新紫色UI云开发新款壁纸小程序源码支持用户投稿在线审核

    本壁纸表情包头像小程序采用(dcloud云开发)所以无需服务器与域名 无需服务器.无需域名.云开发直接上线 特点:支持用户投稿,后台审核后会发订阅消息给用户提示作品审核状态,增加用户粘性,支持后端修改 ...

  5. 从零开始完整开发基于websocket的在线对弈游戏【五子棋】,只用几十行代码完成全部逻辑。

    五子棋是规则简单明了的策略型游戏,先形成五子连线者获胜. 本课程习作采用两人在线对弈的方式进行比赛,拿着手机在上下班路上玩特别合适. 整个过程在众触低代码应用平台进行,使用表达式描述游戏逻辑(高度简化 ...

  6. ArcGIS API for Silverlight开发入门

    ArcGIS API for Silverlight开发入门 你用上3G手机了吗?你可能会说,我就是喜欢用nokia1100,ABCDEFG跟我 都没关系.但你不能否认3G是一种趋势,最终我们每个人都 ...

  7. 用Visual Studio 2008进行Silverlight开发

    微软的 Silverlight浏览器插件使得开发者能够运行富因特网程序(RIAs)--包括动画,矢量图形和视频回放等等.看看如何进行Silverlight开发,并且感受一下这种新的开发方式吧.本文代码 ...

  8. Silverlight开发中遇到的几个小问题

    1,程序发布时遇到错误: "Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'Sy ...

  9. Silverlight C# 游戏开发:Silverlight开发环境

    Silverlight C# 游戏开发:Silverlight开发环境 所谓工欲善其事必先利其器,没有好的工具也没有办法做事,我以前曾经想学习C++以外的程序语言,当时有java和C#来选择,当时考虑 ...

最新文章

  1. zabbix的主动模式和被动模式、添加监控主机、添加自定义模板、处理图形中的乱码、自动发现...
  2. Apache 2.0性能优化—MPM的选择与配置
  3. 笔记本 win7 装vs2010 报错 注册表 拒绝访问等
  4. 安全威胁建模综述_如何使用威胁建模分析应用程序的安全性
  5. IDEA 配置 SpringBoot 启动端口
  6. php json 特殊字符,PHP转义Json里的特殊字符的函数
  7. 21次课(安装软件包的三种方法、rpm包介绍、rpm工具用法、yum工具用法、 yum搭建本地仓库)...
  8. TortoiseGit--小乌龟git项目中的使用简介
  9. Linux 命令行快捷键
  10. java 拼音搜索功能设计与实现
  11. 运用R语言绘制火山图
  12. 20190131 Ubuntu18.10连接Android蓝牙串口助手
  13. Kafka 之 HW 与 LEO
  14. java dozer_java – Dozer双向映射(String,String)与自定义转换器不可能?
  15. 长安“战疫”网络安全卫士守护赛部分writeup
  16. 代码量?项目经验?面试官你到底要看程序员哪一点
  17. 判断质数和合数python代码_小学五年级数学《质数和合数》测试题
  18. 0711 练习 百分制成绩记入与十分制成绩记入方式转换
  19. react--随笔3
  20. 基于java的企业人事管理系统设计--软件工程课程设计(含源码与论文设计).rar

热门文章

  1. 抖音短视频KOL玩法.优质抖音KOL
  2. 我的大数据学习知录(1)-Hadoop
  3. 迅雷网心云赚钱宝3代【Pro】8核性能神器,真实收益有多高?
  4. 2. 嵌入式Linux系统移植 - 交叉编译工具集
  5. Git命令之批量分支
  6. 集合高德地图搜索--导出Excel数据 工具
  7. 如何搭建大规模机器学习平台?以阿里和蚂蚁的多个实际场景为例
  8. 存在重复元素 II(简单题)
  9. 教你学会买便宜的机票
  10. 内网渗透-Linux权限维持