使用 Microsoft Silverlight 的井字游戏






4.97/5 (10投票s)
这篇技巧展示了我们如何使用Silverlight的酷炫功能在网页上制作井字棋游戏。
引言
我正在使用Microsoft Silverlight开发井字棋游戏。我将展示如何在Web环境中以编程方式开发一个交互式简单游戏,并以一种酷炫的方式扩展这个游戏。
Using the Code
我不会花时间去开发一个花哨的代码;但是,我强烈建议你们遵循代码规范,如果可以的话,编写更模块化的代码。
首先,你需要创建一个Silverlight项目,请记住,如果Visual Studio抱怨创建这样的项目,你可能安装了错误的版本。我遇到过这个问题,因为Visual Studio自动将我定向到32位版本,而我的机器是64位的。另外,我意识到,如果你安装了ReSharper 8.2.0.2160(可用于在Visual Studio中重构代码),它可能会与Silverlight冲突,你需要重新安装Silverlight。
当你的Silverlight应用程序准备就绪后,修改用户控件的XAML代码,即`MainPage.XAML`,如下所示
<Grid ShowGridLines="False"
x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="70" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
</Grid>
以上代码会在你的用户控件上创建网格,如图1所示。
下一步是添加井字棋棋盘。我使用以下代码创建棋盘
<Grid ShowGridLines="False" x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="70" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1" ShowGridLines="True" Background="BlanchedAlmond" Name="GridBoard">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Width="30" Height="30" Content=""
Grid.Row="0" Grid.Column="0" Name="btn00"/>
<Button Width="30" Height="30" Content=""
Grid.Row="0" Grid.Column="1" Name="btn01"/>
<Button Width="30" Height="30" Content=""
Grid.Row="0" Grid.Column="2" Name="btn02"/>
<Button Width="30" Height="30" Content=""
Grid.Row="2" Grid.Column="0" Name="btn20"/>
<Button Width="30" Height="30" Content=""
Grid.Row="2" Grid.Column="1" Name="btn21"/>
<Button Width="30" Height="30" Content=""
Grid.Row="2" Grid.Column="2" Name="btn22"/>
<Button Width=" 30" Height="30" Content=""
Grid.Row="1" Grid.Column="0" Name="btn10"/>
<Button Width=" 30" Height="30" Content=""
Grid.Row="1" Grid.Column="1" Name="btn11"/>
<Button Width=" 30" Height="30" Content=""
Grid.Row="1" Grid.Column="2" Name="btn12"/>
</Grid>
</Grid>
结果如图2所示。你可以更改任何Row/ColumnDefinition中的“`Width`”和“`Height`”属性来更改棋盘单元格的大小/灵活性。
创建棋盘后,我在代码隐藏中添加一个方法,如下所示,并将此方法分配给棋盘上每个`Button`的`Click`事件。
private void btn_Click(object sender, RoutedEventArgs e)
{
if (game_ended)
return;
if ((sender as Button).Content != "X" && (sender as Button).Content != "O")
{
(sender as Button).Content = m_turn == 1 ? "X" : "O";
int len = (sender as Button).Name.Length;
int x = int.Parse((sender as Button).Name[len - 2].ToString());
int y = int.Parse((sender as Button).Name[len - 1].ToString());
if (m_board[x, y] == 0)
{
m_board[x, y] = m_turn;
moveNo++;
if (CheckWinner(x, y))
{
MessageBox.Show(string.Format("The winner is Player {0}", m_turn));
game_ended = true;
}
else
{
if (moveNo == MaxMoveNo)
{
MessageBox.Show("Game result is a draw");
game_ended = true;
}
m_turn = (m_turn % 2) + 1;
}
}
}
}
在上述方法中,我有一个名为`game_ended`的全局变量,用于在游戏已结束时停止游戏。检查之后,我检查调用此方法的对象是否是一个标签不等于'`X`'和'`O`'的`Button`,这意味着此按钮以前没有被使用过。如果条件为`true`,我使用名为`m_turn`的全局变量检查玩家轮次,然后查找按钮在网格中的索引,请记住我们将按钮命名为`btn00`、`btn01`……`btn22`,所以我使用一个技巧来解析按钮的名称,以获取其末尾的两位数字,这可以关联按钮的坐标。在找到网格中被点击按钮的坐标后,我检查一个全局变量`m_board`(表示实际游戏,最初包含0),如果在由`x`和`y`给定的索引处的值之前没有被占用,则我将该元素的值更改为`m_turn`的值。`moveNo`是另一个全局变量,包含迄今为止的总移动次数,如果它超过等于棋盘元素数量的`MaxMoveNo`,则应该以平局结果结束游戏。稍后,我将为所有按钮分配此通用行为,因此我避免为每个按钮上的每个点击事件定义不同的方法。
游戏GUI如图3所示。
我有一个如下所示的`CheckWinner`方法,用于检查当前玩家是否获胜
private bool CheckWinner(int x, int y) { int n = MaxRow; // check the row for (int i = 0; i < n; i++) { if (m_board[i, y] != m_turn) break; if (i == n - 1) return true; //already win } // check the col for (int i = 0; i < n; i++) { if (m_board[x, i] != m_turn) break; if (i == n - 1) return true; //already win } if (x == y) { // check the diag for (int i = 0; i < n; i++) { if (m_board[i, i] != m_turn) break; if (i == n - 1) return true; //already win } // check the anti diag for (int i = 0; i < n; i++) { if (m_board[i, n - i - 1] != m_turn) break; if (i == n - 1) return true; //already win } } return false; }
`CheckWinner`方法检查所有可能的获胜方式,包括行、列和(反)对角线。用户2获胜的情况如图4所示。
最后,我使用一些技巧来泛化更大/更小棋盘尺寸的游戏,使用一个名为`btnAddButton`的按钮,点击该按钮,我会隐藏棋盘并显示另一个网格,用户可以在其中定义新的棋盘尺寸。
GridBoard.Visibility = System.Windows.Visibility.Collapsed; gridInput.Visibility = System.Windows.Visibility.Visible;
`gridInput`定义如下
<Grid Grid.Column="2" Grid.Row="1" Height="239"
HorizontalAlignment="Left" Margin="9,8,0,0"
Name="gridInput" VerticalAlignment="Top"
Width="139" Visibility="Visible"
BindingValidationError="gridInput_BindingValidationError">
<TextBox Height="23" HorizontalAlignment="Left"
Margin="52,20,0,0" Name="txtRows"
VerticalAlignment="Top" Width="58"
Text="{Binding Rows, Mode=TwoWay, ValidatesOnExceptions=True,
NotifyOnValidationError=True, UpdateSourceTrigger=Explicit}"/>
<TextBox Height="23" HorizontalAlignment="Left"
Margin="52,57,0,0" Name="txtCols"
VerticalAlignment="Top" Width="58" />
<TextBlock Height="23" HorizontalAlignment="Left"
Margin="16,24,0,0" Name="textBlock1"
Text="Rows" VerticalAlignment="Top" />
<TextBlock Height="23" HorizontalAlignment="Left"
Margin="16,61,0,0" Name="textBlock2"
Text="Cols" VerticalAlignment="Top" />
<Button Content="Set" Height="23"
HorizontalAlignment="Left" Margin="35,95,0,0"
Name="btnSet" VerticalAlignment="Top"
Width="75" Click="btnSet_Click" />
<Button Content="Cancel" Height="23"
HorizontalAlignment="Left" Margin="35,136,0,0"
Name="btnCancel" VerticalAlignment="Top"
Width="75" Click="btnCancel_Click" />
</Grid>
点击`btnSet`按钮,`btnSet_Click`将被调用,其中包含以下代码
private void btnSet_Click(object sender, RoutedEventArgs e) { try { txtRows.GetBindingExpression(TextBox.TextProperty).UpdateSource(); } catch(Exception ex) { return; } if (_rows == 0) return; GridBoard.Children.Clear(); GridBoard.RowDefinitions.Clear(); GridBoard.ColumnDefinitions.Clear(); MaxRow = _rows;//int.Parse(txtRows.Text); for (int i = 0; i < MaxRow; i++) { GridBoard.RowDefinitions.Add(new RowDefinition()); GridBoard.ColumnDefinitions.Add(new ColumnDefinition()); } for (int i = 0; i < MaxRow; i++) for (int j = 0; j < MaxRow; j++) { Button btn = new Button(); btn.Width = 30; btn.Height = 30; btn.Content = ""; btn.SetValue(Grid.RowProperty, i); btn.SetValue(Grid.ColumnProperty, j); btn.Name = string.Format("btn{0}{1}", i, j); btn.Click += btn_Click; GridBoard.Children.Add(btn); } ResetBoard(); gridInput.Visibility = System.Windows.Visibility.Collapsed; GridBoard.Visibility = System.Windows.Visibility.Visible; }
当我创建5x5棋盘的情况如图5和6所示。
首先,`btnSet_Click`方法检查在`txtRows`中输入的棋盘行数是否正确。然后`_rows`(这是一个全局变量,包含行数,并以前与它们相关联,这是一个控件,用于输入所需的`txtRows`)。
public int Rows { get { return _rows; } set { if (value > 9 || value < 1) { _rows = 0; throw new Exception("Please enter a value between 1 - 11."); } _rows = value; } }
在XAML端,我们需要添加
<TextBox Height="23" HorizontalAlignment="Left" Margin="52,20,0,0"
Name="txtRows" VerticalAlignment="Top" Width="58"
Text="{Binding Rows, Mode=TwoWay, ValidatesOnExceptions=True,
NotifyOnValidationError=True, UpdateSourceTrigger=Explicit}"/>
尝试创建10x10棋盘时的错误信息如图7所示。
整个代码已附加。
祝你编程顺利。