0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

WPFでオセロ作ってみました

Last updated at Posted at 2021-11-02

#概要
・オセロの基本ルールは省略。
・プレイヤー同士の対戦は不可。
・プレイヤーは先手番として、白を使う。
・プレイヤーが駒を置くと、自動で相手が駒を置く。
・対戦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 };
}

##動作確認

・初期表示画面
 薄い駒画像が、置ける場所候補。
画像1.png

・駒がおけない場合のメッセージボックス
画像5.png

・結果画面
画像4.png

##感想

何か作ろうと思って、なんとなくオセロ作ってみました。
wpfを久々に触ったので、リハビリにはちょうど良かった気がします。
最低限の作成なので、気が向いたらViewModelを作成したり、AIをもう少し強化などやってみようと思います。

0
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?