#概要
・オセロの基本ルールは省略。
・プレイヤー同士の対戦は不可。
・プレイヤーは先手番として、白を使う。
・プレイヤーが駒を置くと、自動で相手が駒を置く。
・対戦AIは適当(左上から順番に見て、置けるところに置くだけ)
・置ける場所がわかるようなガイドを入れる。
##開発環境
・メインの開発環境はVisualStudioCode
・言語はwpf(C#,Xaml)
##詳細
ソース一覧
ファイル | 概要 |
---|---|
Constants.cs | 定数定義をまとめたクラス(いらなかった) |
MainWindow.xaml | メインの画面クラス |
MainWindow.xaml.cs | メインの画面クラスの処理 |
osero-illust1.png | オセロの駒(黒)画像 |
osero-illust2.png | オセロの駒(黒)画像 |
メソッド一覧
MainWindow.xaml.cs
メソッド | 概要 |
---|---|
MainWindow() | コンストラクタ 色々初期化する |
drawBoard() | 盤面描画処理 盤面状態の配列を元にGridに駒を配置していく |
BoardSet() | 行、列を指定して駒を置く。また、勝利判定、手番入れ替えも行う |
DoEvents() | 画面の再描画処理用1※ |
ExitFrames() | 画面の再描画処理用2※ |
Grid_MouseDown() | Grid上でのクリック時処理、押された場所に応じて、BoardSet()を呼び出す |
Check_OseroSet() | オセロの配置可能か判定する。また、囲んだ場合にひっくり返す |
ClearGrid() | Gridの要素をすべて削除する |
EnemyPlay() | 相手プレイヤーの動作を行う |
※参考にした記事
https://www.ipentec.com/document/csharp-wpf-implement-application-doevents
Constants.cs
using System;
namespace OseroTest
{
public class Constants
{
public const int OSERO_SIZE_HEIGHT = 40;
public const int OSERO_SIZE_WIDTH = 40;
}
}
MainWindow.xaml
<Window x:Class="OseroTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:OseroTest"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="600">
<StackPanel x:Name="StackOut" Height="600" Width="600" HorizontalAlignment="Center">
<Grid x:Name="GridMessage" Height="50" Width="500" >
<Label x:Name="MessageLabel" HorizontalAlignment="center" FontSize="36"/>
</Grid>
<Grid x:Name="GridBoard" Height="400" Width="400" Background="Green" MouseDown="Grid_MouseDown" >
<Grid.ColumnDefinitions>
<!-- * :「全体幅」から「幅指定列が確保した幅の合計」を引いた
残りの幅を、"*" 指定がある列で 均等割りにします。-->
<!--第1列--><ColumnDefinition Width="*"/>
<!--第2列--><ColumnDefinition Width="*"/>
<!--第3列--><ColumnDefinition Width="*"/>
<!--第4列--><ColumnDefinition Width="*"/>
<!--第5列--><ColumnDefinition Width="*"/>
<!--第6列--><ColumnDefinition Width="*"/>
<!--第7列--><ColumnDefinition Width="*"/>
<!--第8列--><ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<!--第1行--><RowDefinition Height="*"/>
<!--第2行--><RowDefinition Height="*"/>
<!--第3行--><RowDefinition Height="*"/>
<!--第4行--><RowDefinition Height="*"/>
<!--第5行--><RowDefinition Height="*"/>
<!--第6行--><RowDefinition Height="*"/>
<!--第7行--><RowDefinition Height="*"/>
<!--第8行--><RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>
<Grid x:Name="GridMessage2" Height="50" Width="500" >
<Label x:Name="MessageLabel2" />
</Grid>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections;
using System.Windows.Media.Animation;
using System.Threading;
using System.Windows.Threading;
namespace OseroTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public int[,] arrayBoard = null;
public int[,] beforeBoard = null;
public int TurnStatus = 0;//手番ステータス
public int whiteCount = 0;//白カウント
public int BlackCount = 0;//黒カウント
public int ableCount = 0;//置ける数カウント
public MainWindow()
{
InitializeComponent();
// メッセージ表示
MessageLabel.Content = "白の手番です";
TurnStatus = -1;//白
MessageLabel.FontSize = 20;
// 盤面状態の配列を初期化
arrayBoard = new int[8, 8] {
{ 0, 0 ,0 ,0 ,0 ,0 ,0 ,0},
{ 0, 0 ,0 ,0 ,0 ,0 ,0 ,0},
{ 0, 0 ,0 ,0 ,0 ,0 ,0 ,0},
{ 0, 0 ,0 ,0 ,0 ,0 ,0 ,0},
{ 0, 0 ,0 ,0 ,0 ,0 ,0 ,0},
{ 0, 0 ,0 ,0 ,0 ,0 ,0 ,0},
{ 0, 0 ,0 ,0 ,0 ,0 ,0 ,0},
{ 0, 0 ,0 ,0 ,0 ,0 ,0 ,0},
};
// 初期配置セット
arrayBoard[3,3] = (int)OseroKind.Black ;
arrayBoard[3,4] = (int)OseroKind.White ;
arrayBoard[4,3] = (int)OseroKind.White ;
arrayBoard[4,4] = (int)OseroKind.Black ;
drawBoard();
}
/// <summary>
/// 盤面描画
/// </summary>
/// <returns>なし</returns> }
private void drawBoard()
{
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
ClearGrid();
ableCount = 0;
whiteCount = 0;
BlackCount = 0;
for(int row = 0 ; row < 8 ; row++){
for(int col = 0 ; col < 8 ; col++){
// 枠線の設定
Border border = new Border();
border.BorderBrush = new SolidColorBrush(Colors.Black);
border.BorderThickness = new System.Windows.Thickness(1);
border.SetValue(Grid.RowProperty, row);
border.SetValue(Grid.ColumnProperty, col);
GridBoard.Children.Add(border);
int kind = arrayBoard[row,col];
String imgFileName = "";
// Gridの要素削除
GridBoard.Children.Remove(this);
Boolean ableFlg = false;
// 駒配置
if(kind == (int)OseroKind.Black ){
// Console.WriteLine(arrayBoard[row,col] + ": Black");
imgFileName = "osero-illust1.png";
BlackCount ++;
}else if(kind == (int)OseroKind.White ){
// Console.WriteLine(arrayBoard[row,col] + ": White");
imgFileName = "osero-illust2.png";
whiteCount ++;
}else{
// 手番プレイヤーが配置可能かどうかを判定
if(Check_OseroSet(col,row,TurnStatus,0)){
ableFlg = true;
ableCount ++;
if(TurnStatus == (int)OseroKind.Black){
imgFileName = "osero-illust1.png";
}else if(TurnStatus == (int)OseroKind.White){
imgFileName = "osero-illust2.png";
}
}else{
continue;
}
}
Image img = new Image();
BitmapImage imgSrc = new BitmapImage();
imgSrc.BeginInit();
imgSrc.UriSource = new Uri(imgFileName,UriKind.Relative);
imgSrc.CacheOption = BitmapCacheOption.OnLoad;
imgSrc.EndInit();
img.Source = imgSrc;
img.Name="img"+ row + col;
img.Height=Constants.OSERO_SIZE_HEIGHT;
img.Width=Constants.OSERO_SIZE_HEIGHT;
img.SetValue(Grid.RowProperty, row);
img.SetValue(Grid.ColumnProperty, col);
this.RegisterName(img.Name, img);
if(ableFlg){
// 配置可能の場合は、透過した画像を設定
img.Opacity = 0.2;
}
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 0;
myDoubleAnimation.To = 200;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(2));
Storyboard.SetTargetName(myDoubleAnimation, img.Name);
Storyboard.SetTargetProperty(myDoubleAnimation,
new PropertyPath(Rectangle.WidthProperty));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
BeginStoryboard myBeginStoryboard = new BeginStoryboard();
myBeginStoryboard.Storyboard = myStoryboard;
EventTrigger myEnterTrigger = new EventTrigger();
myEnterTrigger.Actions.Add(myBeginStoryboard);
GridBoard.Children.Add(img);
}
}
}
/// <summary>
/// 駒を置く処理
/// </summary>
private void BoardSet(int col,int row)
{
if(!Check_OseroSet(col,row,TurnStatus,1)){
// チェック結果false
//MessageLabel2.Content = "そこはおけません";
return;
}
arrayBoard[row,col] = TurnStatus ;
// 駒を置いたら手番を入れ替える
TurnStatus = TurnStatus * -1;
drawBoard();
if(whiteCount + BlackCount == 64){
// ゲーム終了
String result = string.Format("白:{0} 黒:{1} ", whiteCount , BlackCount) ;
if(whiteCount < BlackCount){
MessageLabel.Content = result + "★★黒の勝ちです★★";
}else if(whiteCount > BlackCount){
MessageLabel.Content = result +"☆☆白の勝ちです☆☆";
}else{
MessageLabel.Content = "!!引き分けです!!";
}
return;
}
if (ableCount == 0){
// 置ける数が0なら、手番を再度入れ替え
MessageBox.Show("置ける場所がないため、パスします");
TurnStatus = TurnStatus * -1;
drawBoard();
}
if(TurnStatus == -1){
MessageLabel.Content = "白の手番です";
}else{
MessageLabel.Content = "黒の手番です";
DoEvents();
EnemyPlay();
}
}
/// <summary>
/// 画面再描画処理1
/// </summary>
private void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
var callback = new DispatcherOperationCallback(ExitFrames);
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, callback, frame);
Dispatcher.PushFrame(frame);
}
/// <summary>
/// 画面再描画処理2
/// </summary>
private object ExitFrames(object obj)
{
((DispatcherFrame)obj).Continue = false;
return null;
}
/// <summary>
/// Grid上でのマウスのボタン押下イベント(タッチ操作含む)
/// </summary>
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
Point pos = e.GetPosition(GridBoard);
int col = (int)Math.Floor(pos.X / 50);
int row =(int)Math.Floor(pos.Y / 50);
BoardSet(col,row);
}
/// <summary>
/// オセロ配置判定処理
/// </summary>
/// <param name="col">対象カラム</param>
/// <param name="rows">対象行</param>
/// <param name="kind">駒種別</param>
/// <param name="chkFlg">チェックフラグ(0:チェックのみ、1:チェック+駒返し)</param>
private Boolean Check_OseroSet(int col,int row, int kind, int chkFlg )
{
if(arrayBoard[row ,col] != (int)OseroKind.None){
Console.WriteLine("駒がすでに存在");
return false;
// 駒がおいてたらfalse
}
int nowTurn = kind;//今の手番
int target = 0;
Boolean chkResult = false;
// 8方向について、それぞれ配置可能か判定する
for(int i = 0; i < 8 ; i++){
// 最大値を決め、その数まで、特定方向に対してチェックする
int iMax = 0;
int rowAdd = 0;
int colAdd = 0;
switch(i){
case 0: //下 方向
iMax = row;
rowAdd = 1;
colAdd = 0;
break;
case 1: //右下方向
iMax = (row > col) ? row : col;
rowAdd = 1;
colAdd = 1;
break;
case 2: //右 方向
iMax = col;
rowAdd = 0;
colAdd = 1;
break;
case 3: //右上方向
iMax = ( (7 - row) > col) ? (7 - row) : col;
rowAdd = -1;
colAdd = 1;
break;
case 4: //上 方向
iMax = 7 - row;
rowAdd = -1;
colAdd = 0;
break;
case 5: //左上方向
iMax = ( (7 - row) > (7 - col) ) ? (7 - row) : (7 - col);
rowAdd = -1;
colAdd = -1;
break;
case 6: //左 方向
iMax = 7 - col;
rowAdd = 0;
colAdd = -1;
break;
case 7: //左下方向
iMax = ( row > (7 - col) ) ? row : (7 - col);
rowAdd = 1;
colAdd = -1;
break;
}
Console.WriteLine("方向:" + i);
ArrayList reverseList = new ArrayList();
for(int j = 1 ; j + iMax < 8 ; j ++){
target = arrayBoard[row + (j * rowAdd) , col + (j * colAdd) ];
if(j == 1 && target*nowTurn != -1){
// 一つ目が相手の駒以外(自分の駒or無し)なら、チェック終了
break;
}
// ひっくり返しリストに格納しておく
int[] stack = new int[2];
stack[0] = row + (j * rowAdd) ;
stack[1] = col + (j * colAdd) ;
reverseList.Add(stack);
if(target == 0){
// 2つ目以降、空があればそこで終了
break;
}
if(target == nowTurn ){
// 2つ目以降、自分の駒が見つかればtrue(空は除く)
chkResult = true;
foreach(int[] stackArray in reverseList){
// リストのデータをひっくり返す
if(chkFlg == 1 ){
arrayBoard[stackArray[0],stackArray[1]] = nowTurn;
}
}
}
}
}
return chkResult;
}
/// <summary>
/// グリッド削除
/// </summary>
private void ClearGrid()
{
for (int i = GridBoard.Children.Count - 1; i >= 0; i--)
{
GridBoard.Children.RemoveAt(i);
}
}
/// <summary>
/// 相手動作
/// </summary>
/// <returns>なし</returns> }
private void EnemyPlay()
{
Thread.Sleep(1000);
for(int row = 0 ; row < 8 ; row++){
for(int col = 0 ; col < 8 ; col++){
if(Check_OseroSet(col,row,TurnStatus,0)){
// 置くところが見つかったら、無条件で置く
BoardSet(col,row);
return;
}
}
}
}
}
/// <summary>
/// enum OseroKind
/// </summary>
public enum OseroKind { White=-1, None=0, Black=1 };
}
##動作確認
##感想
何か作ろうと思って、なんとなくオセロ作ってみました。
wpfを久々に触ったので、リハビリにはちょうど良かった気がします。
最低限の作成なので、気が向いたらViewModelを作成したり、AIをもう少し強化などやってみようと思います。