出張で無事死んでいました...
記事いっぱい書いていかないと...
オセロって使うもの自体は少ないんだけど、満たす要件だったりロジックが複雑だったりするのでわりと頭の体操によくやったりします。(書くことないのでこれを書きます。)
オセロの説明は言うまでもないと思うので省略
オセロの要件
- 8×8の緑に黒線のボードで行う。
- ターンがあり、●と〇が交互に打っていく。
- すでに石があるところに石はおけず、またひっくり返せない所には石はおけない。
- 自分の色で相手の色を挟んだらひっくり返す。
- 最後に多い方の勝ち。
- 途中で打てなくなった場合Passをする。
- 途中で石がなくなった場合その時点で負け
実装
まずは、xaml側から。
こちらは8×8の緑に黒線のボードを用意してあげて、Passボタンと現在のターン表示と一応黒と白の現在の数を用意してあげましょう。
<Windowx:Class="osero_wpf.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:osero_wpf"mc:Ignorable="d"Title="MainWindow"Height="500"Width="600"><Window.Resources><StyleTargetType="Button"><SetterProperty="Width"Value="50"></Setter><SetterProperty="Height"Value="50"></Setter><SetterProperty="Background"Value="Green"></Setter><SetterProperty="BorderBrush"Value="Black"></Setter></Style></Window.Resources><Grid><StackPanelOrientation="Vertical"HorizontalAlignment="Center"VerticalAlignment="Center"Name="Board"><StackPanelOrientation="Horizontal"Tag="1"><ButtonContent=""Click="Button_Click"Tag="1"></Button><ButtonContent=""Click="Button_Click"Tag="2"></Button><ButtonContent=""Click="Button_Click"Tag="3"></Button><ButtonContent=""Click="Button_Click"Tag="4"></Button><ButtonContent=""Click="Button_Click"Tag="5"></Button><ButtonContent=""Click="Button_Click"Tag="6"></Button><ButtonContent=""Click="Button_Click"Tag="7"></Button><ButtonContent=""Click="Button_Click"Tag="8"></Button></StackPanel><StackPanelOrientation="Horizontal"Tag="2"><ButtonContent=""Click="Button_Click"Tag="1"></Button><ButtonContent=""Click="Button_Click"Tag="2"></Button><ButtonContent=""Click="Button_Click"Tag="3"></Button><ButtonContent=""Click="Button_Click"Tag="4"></Button><ButtonContent=""Click="Button_Click"Tag="5"></Button><ButtonContent=""Click="Button_Click"Tag="6"></Button><ButtonContent=""Click="Button_Click"Tag="7"></Button><ButtonContent=""Click="Button_Click"Tag="8"></Button></StackPanel><StackPanelOrientation="Horizontal"Tag="3"><ButtonContent=""Click="Button_Click"Tag="1"></Button><ButtonContent=""Click="Button_Click"Tag="2"></Button><ButtonContent=""Click="Button_Click"Tag="3"></Button><ButtonContent=""Click="Button_Click"Tag="4"></Button><ButtonContent=""Click="Button_Click"Tag="5"></Button><ButtonContent=""Click="Button_Click"Tag="6"></Button><ButtonContent=""Click="Button_Click"Tag="7"></Button><ButtonContent=""Click="Button_Click"Tag="8"></Button></StackPanel><StackPanelOrientation="Horizontal"Tag="4"><ButtonContent=""Click="Button_Click"Tag="1"></Button><ButtonContent=""Click="Button_Click"Tag="2"></Button><ButtonContent=""Click="Button_Click"Tag="3"></Button><ButtonContent=""Click="Button_Click"Tag="4"></Button><ButtonContent=""Click="Button_Click"Tag="5"></Button><ButtonContent=""Click="Button_Click"Tag="6"></Button><ButtonContent=""Click="Button_Click"Tag="7"></Button><ButtonContent=""Click="Button_Click"Tag="8"></Button></StackPanel><StackPanelOrientation="Horizontal"Tag="5"><ButtonContent=""Click="Button_Click"Tag="1"></Button><ButtonContent=""Click="Button_Click"Tag="2"></Button><ButtonContent=""Click="Button_Click"Tag="3"></Button><ButtonContent=""Click="Button_Click"Tag="4"></Button><ButtonContent=""Click="Button_Click"Tag="5"></Button><ButtonContent=""Click="Button_Click"Tag="6"></Button><ButtonContent=""Click="Button_Click"Tag="7"></Button><ButtonContent=""Click="Button_Click"Tag="8"></Button></StackPanel><StackPanelOrientation="Horizontal"Tag="6"><ButtonContent=""Click="Button_Click"Tag="1"></Button><ButtonContent=""Click="Button_Click"Tag="2"></Button><ButtonContent=""Click="Button_Click"Tag="3"></Button><ButtonContent=""Click="Button_Click"Tag="4"></Button><ButtonContent=""Click="Button_Click"Tag="5"></Button><ButtonContent=""Click="Button_Click"Tag="6"></Button><ButtonContent=""Click="Button_Click"Tag="7"></Button><ButtonContent=""Click="Button_Click"Tag="8"></Button></StackPanel><StackPanelOrientation="Horizontal"Tag="7"><ButtonContent=""Click="Button_Click"Tag="1"></Button><ButtonContent=""Click="Button_Click"Tag="2"></Button><ButtonContent=""Click="Button_Click"Tag="3"></Button><ButtonContent=""Click="Button_Click"Tag="4"></Button><ButtonContent=""Click="Button_Click"Tag="5"></Button><ButtonContent=""Click="Button_Click"Tag="6"></Button><ButtonContent=""Click="Button_Click"Tag="7"></Button><ButtonContent=""Click="Button_Click"Tag="8"></Button></StackPanel><StackPanelOrientation="Horizontal"Tag="8"><ButtonContent=""Click="Button_Click"Tag="1"></Button><ButtonContent=""Click="Button_Click"Tag="2"></Button><ButtonContent=""Click="Button_Click"Tag="3"></Button><ButtonContent=""Click="Button_Click"Tag="4"></Button><ButtonContent=""Click="Button_Click"Tag="5"></Button><ButtonContent=""Click="Button_Click"Tag="6"></Button><ButtonContent=""Click="Button_Click"Tag="7"></Button><ButtonContent=""Click="Button_Click"Tag="8"></Button></StackPanel></StackPanel><StackPanelOrientation="Vertical"VerticalAlignment="Top"HorizontalAlignment="Left"><StackPanelOrientation="Horizontal"><TextBlockText="黒:"></TextBlock><TextBlockText=""Name="ViewBlackCount"></TextBlock></StackPanel><StackPanelOrientation="Horizontal"><TextBlockText="白:"></TextBlock><TextBlockText=""Name="ViewWhiteCount"></TextBlock></StackPanel></StackPanel><StackPanelVerticalAlignment="Center"HorizontalAlignment="Left"Orientation="Vertical"><StackPanelOrientation="Horizontal"><TextBlockText=""Name="TurnText"></TextBlock><TextBlockText="のターンです。"></TextBlock></StackPanel><ButtonContent="Pass"Click="Button_Click_1"Background="AliceBlue"></Button></StackPanel></Grid></Window>
コード側でやってあげたほうがよかったかもしれないですが、面倒なのでxaml側でばーっとやっちゃいました。
コード側
publicpartialclassMainWindow:Window{privateboolIsBlackTurn=true;privateint[,]BoardInfo=newint[8,8];publicMainWindow(){InitializeComponent();Init();}}
ターンの状態と、ボードの情報をMainWindowクラスで保持。ボードの情報に関してはint型で保持し、
1は黒、0はなし、-1は白とする。
InitではBoardInfoにすべて0を入れてから、4,4に黒、4,5に白、5,4に白、5,5に黒を入れてやる。
privatevoidInit(){for(vari=0;i<8;i++){for(varj=0;j<8;j++){BoardInfo[i,j]=0;}}SetBoardInfo(4,4,1);SetBoardInfo(4,5,-1);SetBoardInfo(5,4,-1);SetBoardInfo(5,5,1);ReflectBoardInfoToXaml();}
SetBoardInfoは4,4,1であれば[4,4]に黒を入れてやる、の意。
わざわざメソッド化をしてあげる理由は配列が0から始まるが、見かけ上は1からはじまるため。
(ここは別にTag付けを0から始めればよかったか)
ReflectBoardInfoToXamlメソッドはBoardInfoの情報をxaml側に反映させてあげるメソッド。
privatevoidReflectBoardInfoToXaml(){introw=0;intcol=0;intBlackCount=0;intWhiteCount=0;intNoCount=0;foreach(varChildreninBoard.Children){varPanel=ChildrenasStackPanel;foreach(varButinPanel.Children){varButt=ButasButton;Butt.Content=ConvertIntInfoToStringInfo(BoardInfo[row,col]);if(BoardInfo[row,col]==1)BlackCount++;elseif(BoardInfo[row,col]==-1)WhiteCount++;elseNoCount++;col++;}col=0;row++;}if(BlackCount==0){MessageBox.Show("白の勝ち");Init();}elseif(WhiteCount==0){MessageBox.Show("黒の勝ち");Init();}elseif(NoCount==0){if(WhiteCount<BlackCount){MessageBox.Show("黒の勝ち");Init();}elseif(WhiteCount>BlackCount){MessageBox.Show("白の勝ち");Init();}else{MessageBox.Show("引き分け");Init();}}ViewBlackCount.Text=BlackCount.ToString();ViewWhiteCount.Text=WhiteCount.ToString();TurnText.Text=IsBlackTurn?"黒":"白";}
基本的に最初のforeachの終わりまでが反映作業。
foreachのところのChildrenに関しては子要素を取得。
なので、全体のStackPanelの子要素を取得して、8枚のStackPanelを取り出す。
その後、各StackPanelからButtonを取り出してそこのContentに情報を入れていく。
その後の勝ち負けの処理を入れるのはforeachですべての情報を回してるここでやると便利なため。
オセロのボタンをクリックしたときの処理。
privatevoidButton_Click(objectsender,RoutedEventArgse){varbutton=senderasButton;varstackPanel=button.ParentasStackPanel;intCol=int.Parse(button.Tag.ToString());intRow=int.Parse(stackPanel.Tag.ToString());if(BoardInfo[Row-1,Col-1]!=0){MessageBox.Show("そこにはすでに置かれています。");return;}varIsTurnChange=CheckValidBoardInfo(Row,Col);if(!IsTurnChange){MessageBox.Show("そこには置けません。");return;}SetBoardInfo(Row,Col,ConvertTurnToIntInfo(IsBlackTurn));ReflectBoardInfoToXaml();IsBlackTurn=!IsBlackTurn;}
CheckValidBoardInfoは挟めることができたらtrueを返し、できなければfalseを返す。
まぁなので名前としてはTryAndReverseみたいなの方が妥当だろうか。
そして、無事ひっくり返すことができたあとに、Row,Colに自分の石を置き、ターンを変える。
そしてオセロを実装する上で一番の面倒なところが石を挟んだらひっくり返す処理だろう。
石を挟むという言葉もなかなか抽象的である。なので、より具体的な言葉で言ってあげるならば
座標(x, y)がzであり、(x + a * 1, y + b * 1)から(x + a * (n - 1), y + b * (n - 1))が-zで(x + a * n, y + b * n)がzである場合(x + a * 1, y + b * 1)から(x + a * (n - 1), y + b * (n - 1))をzに変更する。(0 <= x <= 8, 0 <= y <= 8, a,b は1,0,-1のいずれか。2 <= n <= 7)
という言葉になる。
まぁ簡単に言えば方向と挟む数と挟む回数の順番でfor文を回してやり適宜チェックを行ってやればよい。
それをコードで表すと
privateboolCheckValidBoardInfo(intRow,intCol){intInfo=ConvertTurnToIntInfo(IsBlackTurn);intRowDirection;intColDirection;varIsTurnChange=false;//方向for(vari=1;i<=8;i++){//石の数for(varj=7;j>=2;j--){(RowDirection,ColDirection)=GetDirection(i);//ひとつでも変更があればturnChangeFlgをtrueにする。if(!CheckRangeValid(Row+j*RowDirection,Col+j*ColDirection)||!CheckReverse(Row,Col,Info,i,j))continue;elseIsTurnChange=true;//チェックが通ったものの場合j - 1個ひっくり返す。for(vark=1;k<j;k++){SetBoardInfo(Row+k*RowDirection,Col+k*ColDirection,Info);}}}returnIsTurnChange;}
という風になる。
全体のコードを示すと
usingSystem.Windows;usingSystem.Windows.Controls;namespaceosero_wpf{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>publicpartialclassMainWindow:Window{privateboolIsBlackTurn=true;privateint[,]BoardInfo=newint[8,8];publicMainWindow(){InitializeComponent();Init();}/// <summary>/// 初期化/// </summary>privatevoidInit(){for(vari=0;i<8;i++){for(varj=0;j<8;j++){BoardInfo[i,j]=0;}}SetBoardInfo(4,4,1);SetBoardInfo(4,5,-1);SetBoardInfo(5,4,-1);SetBoardInfo(5,5,1);ReflectBoardInfoToXaml();}/// <summary>/// オセロの諸々の処理実行/// </summary>/// <param name="sender"></param>/// <param name="e"></param>privatevoidButton_Click(objectsender,RoutedEventArgse){varbutton=senderasButton;varstackPanel=button.ParentasStackPanel;intCol=int.Parse(button.Tag.ToString());intRow=int.Parse(stackPanel.Tag.ToString());if(BoardInfo[Row-1,Col-1]!=0){MessageBox.Show("そこにはすでに置かれています。");return;}varIsTurnChange=CheckValidBoardInfo(Row,Col);if(!IsTurnChange){MessageBox.Show("そこには置けません。");return;}SetBoardInfo(Row,Col,ConvertTurnToIntInfo(IsBlackTurn));ReflectBoardInfoToXaml();IsBlackTurn=!IsBlackTurn;}privateboolCheckValidBoardInfo(intRow,intCol){intInfo=ConvertTurnToIntInfo(IsBlackTurn);intRowDirection;intColDirection;varIsTurnChange=false;//方向for(vari=1;i<=8;i++){//石の数for(varj=7;j>=2;j--){(RowDirection,ColDirection)=GetDirection(i);//ひとつでも変更があればturnChangeFlgをtrueにする。if(!CheckRangeValid(Row+j*RowDirection,Col+j*ColDirection)||!CheckReverse(Row,Col,Info,i,j))continue;elseIsTurnChange=true;//チェックが通ったものの場合j - 1個ひっくり返す。for(vark=1;k<j;k++){SetBoardInfo(Row+k*RowDirection,Col+k*ColDirection,Info);}}}returnIsTurnChange;}/// <summary>/// 配列の範囲をチェック/// </summary>/// <param name="RowLimit">Row</param>/// <param name="ColLimit">Col</param>/// <returns>配列の範囲を越えなければtrue超えればfalse</returns>privateboolCheckRangeValid(intRowLimit=0,intColLimit=0){RowLimit--;ColLimit--;returnRowLimit<8&&ColLimit<8&&0<=RowLimit&&0<=ColLimit;}/// <summary>/// /// </summary>/// <param name="Row"></param>/// <param name="Col"></param>/// <param name="Info">現在のtrunをintで表したもの</param>/// <param name="Direction">チェックする方向</param>/// <param name="Length">チェックの範囲</param>/// <returns>ひっくり返すことができればtrueそうでなければfalse</returns>privateboolCheckReverse(intRow,intCol,intInfo,intDirection,intLength){intRowDirection;intColDirection;(RowDirection,ColDirection)=GetDirection(Direction);varIsOneReverse=false;for(vari=1;i<Length;i++){IsOneReverse=BoardInfo[Row+RowDirection*i-1,Col+ColDirection*i-1]==-Info;if(!IsOneReverse)returnfalse;}returnBoardInfo[Row+RowDirection*Length-1,Col+ColDirection*Length-1]==Info;}/// <summary>/// /// </summary>/// <param name="Direction">方向</param>/// <returns>方向をRow,Colの二つに分ける</returns>private(int,int)GetDirection(intDirection){intRowDirection;intColDirection;if(Direction==1){RowDirection=0;ColDirection=1;}elseif(Direction==2){RowDirection=1;ColDirection=1;}elseif(Direction==3){RowDirection=1;ColDirection=0;}elseif(Direction==4){RowDirection=1;ColDirection=-1;}elseif(Direction==5){RowDirection=0;ColDirection=-1;}elseif(Direction==6){RowDirection=-1;ColDirection=-1;}elseif(Direction==7){RowDirection=-1;ColDirection=0;}else{RowDirection=-1;ColDirection=1;}return(RowDirection,ColDirection);}/// <summary>/// boolのターン情報をintに変換(intにしといたほうがいいかも?)/// </summary>/// <param name="Turn">現在のターン黒か白か</param>/// <returns></returns>privateintConvertTurnToIntInfo(boolTurn){if(Turn){return1;}else{return-1;}}/// <summary>/// 黒か白かをintの情報からstringに変換/// </summary>/// <param name="info">1ならば黒,-1ならば白,0ならばなし</param>/// <returns></returns>privatestringConvertIntInfoToStringInfo(intinfo){if(info==1){return"●";}elseif(info==-1){return"〇";}else{return"";}}/// <summary>/// BoardInfoにsetするとき-1のずれが発生するのでメソッド化する。/// </summary>/// <param name="row">縦</param>/// <param name="col">横</param>/// <param name="info">黒か白かを1か-1かで表したもの</param>privatevoidSetBoardInfo(introw,intcol,intinfo){BoardInfo[row-1,col-1]=info;}/// <summary>/// ボードの情報をxamlに反映/// </summary>privatevoidReflectBoardInfoToXaml(){introw=0;intcol=0;intBlackCount=0;intWhiteCount=0;intNoCount=0;foreach(varChildreninBoard.Children){varPanel=ChildrenasStackPanel;foreach(varButinPanel.Children){varButt=ButasButton;Butt.Content=ConvertIntInfoToStringInfo(BoardInfo[row,col]);if(BoardInfo[row,col]==1)BlackCount++;elseif(BoardInfo[row,col]==-1)WhiteCount++;elseNoCount++;col++;}col=0;row++;}if(BlackCount==0){MessageBox.Show("白の勝ち");Init();}elseif(WhiteCount==0){MessageBox.Show("黒の勝ち");Init();}elseif(NoCount==0){if(WhiteCount<BlackCount){MessageBox.Show("黒の勝ち");Init();}elseif(WhiteCount>BlackCount){MessageBox.Show("白の勝ち");Init();}else{MessageBox.Show("引き分け");Init();}}ViewBlackCount.Text=BlackCount.ToString();ViewWhiteCount.Text=WhiteCount.ToString();TurnText.Text=IsBlackTurn?"黒":"白";}/// <summary>/// 置けない時にパスするためのボタン。/// </summary>/// <param name="sender"></param>/// <param name="e"></param>privatevoidButton_Click_1(objectsender,RoutedEventArgse){IsBlackTurn=!IsBlackTurn;}}}
という感じになる。