Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9701

[C#][WPF]ライフゲームを作ってみるver.1

$
0
0

自分の勉強の為と興味本位でC#(WPF)でライフゲームを作成してみます。

なぜC#で、かつWPFで作るのかと問われると特に意味はありません。何かの縛りプレイだと思っていただくほかないです。
最初はMVVMを守って作ろうかと思ってましたが、DataGridのバインディングがクセ強かったのでとりあえず全部コードビハインドに書いてまずは動かしていきます。

ライフゲームとは

セル・オートマトンの一種
 以下のルールに従って、生命の誕生、進化、淘汰などのプロセスを簡易的なモデルで再現したシミュレーションゲーム

  • 誕生:死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。

  • 生存:生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。

  • 過疎:生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。

  • 過密:生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。

ライフゲーム by Wikipedia

WPFで作るにあたって

使用するコントロールはDataGridで、各セルを黒(生存)と白(死滅)で塗り分けていきます。

MainWindow.xaml
<Windowx:Class="WpfTestView.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"mc:Ignorable="d"ResizeMode="NoResize"Title="MainWindow"Height="650"Width="600"><GridName="parentGridSample"><StackPanel><GridName="gridSample"><Grid.ColumnDefinitions><ColumnDefinitionWidth="*"/><ColumnDefinitionWidth="*"/><ColumnDefinitionWidth="*"/></Grid.ColumnDefinitions><ButtonGrid.Column="0"Content="Refresh"Click="RefreshButton_Click"/><ButtonGrid.Column="1"Content="Random"Click="RandomButton_Click"/><ButtonGrid.Column="2"Content="Start"Click="StartButton_Click"/></Grid><DataGridName="dgSample"HeadersVisibility="None"IsReadOnly="True"SelectionMode="Single"SelectionUnit="Cell"MouseUp="dgSample_MouseUp"SizeChanged="dgSample_SizeChanged"><DataGrid.Columns><DataGridTextColumn><DataGridTextColumn.CellStyle><StyleTargetType="DataGridCell"><SetterProperty="Background"Value="White"/></Style></DataGridTextColumn.CellStyle></DataGridTextColumn></DataGrid.Columns></DataGrid></StackPanel></Grid></Window>

全てイベントドリブンとして実装、コードビハインドでコントロールを操作しているので全く持ってWinFormsと違いありません。
これが上手く動けばMVVMにリファクタリングしていく予定です。

MainWindow.xaml.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Data;usingSystem.Diagnostics;usingSystem.Linq;usingSystem.Reflection;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Controls;usingSystem.Windows.Controls.Primitives;usingSystem.Windows.Input;usingSystem.Windows.Media;namespaceWpfTestView{/// <summary>/// MainWindow.xaml の相互作用ロジック/// </summary>publicpartialclassMainView:Window{// 内部的なデータテーブルprivateDataTable_dataTable=newDataTable();// 次元privatereadonlyint_rank=50;// 前世代privateDictionary<int,List<bool>>preGeneration=newDictionary<int,List<bool>>();// 現世代privateDictionary<int,List<bool>>curGeneration=newDictionary<int,List<bool>>();publicMainView(){InitializeComponent();InitializeDataGrid();InitializeGeneration();}privatevoidInitializeDataGrid(){dgSample.Columns.Clear();_dataTable.Rows.Clear();_dataTable.Columns.Clear();for(inti=0;i<_rank;i++){_dataTable.Columns.Add();_dataTable.Rows.Add();}dgSample.ItemsSource=newDataView(_dataTable);ResizeDataGrid();}privatevoidInitializeGeneration(){preGeneration.Clear();curGeneration.Clear();preGeneration=Enumerable.Range(0,_rank).ToDictionary(_=>_,_=>Enumerable.Repeat(false,_rank).ToList());curGeneration=Enumerable.Range(0,_rank).ToDictionary(_=>_,_=>Enumerable.Repeat(false,_rank).ToList());}privatevoidResizeDataGrid(){if(_dataTable.Columns.Count==0)return;doubleparentHeghit=parentGridSample.ActualHeight;doubleparentWidth=parentGridSample.ActualWidth;doublebuttonHeight=gridSample.ActualHeight;// ボタンを画面上部に付けているのでその分を全体から引いて、行数で均等に割るdgSample.MinRowHeight=0;dgSample.RowHeight=(parentHeghit-buttonHeight)/_dataTable.Rows.Count;// 何故か親ウィンドウの幅をそのまま割ると少しズレるので-2というマジックナンバーをつけているforeach(varcolindgSample.Columns){col.MinWidth=0;col.Width=(parentWidth-2)/_dataTable.Columns.Count;}}privatevoiddgSample_SizeChanged(objectsender,SizeChangedEventArgse){Trace.WriteLine(MethodBase.GetCurrentMethod().Name);ResizeDataGrid();}privatevoidRefreshButton_Click(objectsender,RoutedEventArgse){Trace.WriteLine(MethodBase.GetCurrentMethod().Name);InitializeDataGrid();InitializeGeneration();}privatevoiddgSample_MouseUp(objectsender,MouseButtonEventArgse){Trace.WriteLine(MethodBase.GetCurrentMethod().Name);// マウスクリックされたセルを取得して色を反転させるvarcurCellInfo=dgSample.CurrentCell;varcurCell=curCellInfo.Column.GetCellContent(curCellInfo.Item).ParentasDataGridCell;if(curCell==null)return;introwIdx=dgSample.Items.IndexOf(curCellInfo.Item);intcolIdx=curCellInfo.Column.DisplayIndex;curGeneration[rowIdx][colIdx]=!curGeneration[rowIdx][colIdx];ToggleCellColor(curCell);}privatevoidStartButton_Click(objectsender,RoutedEventArgse){Trace.WriteLine(MethodBase.GetCurrentMethod().Name);// とりあえず100世代ライフゲームを実行するintgen=0;Task.Factory.StartNew(()=>{do{UpdateGeneration();System.Threading.Thread.Sleep(200);if(curGeneration.All(_n=>_n.Value.All(_=>!_)))break;}while(++gen<100);});}privatevoidUpdateGeneration(){for(inti=0;i<_rank;i++){for(intj=0;j<_rank;j++){// データのコピーpreGeneration[i][j]=curGeneration[i][j];}}for(inti=0;i<_rank;i++){for(intj=0;j<_rank;j++){intabove=i==0?_rank-1:i-1;intbelow=i==_rank-1?0:i+1;intleft=j==0?_rank-1:j-1;intright=j==_rank-1?0:j+1;intaliveCell=0;if(preGeneration[above][left])aliveCell++;if(preGeneration[above][j])aliveCell++;if(preGeneration[above][right])aliveCell++;if(preGeneration[i][left])aliveCell++;if(preGeneration[i][right])aliveCell++;if(preGeneration[below][left])aliveCell++;if(preGeneration[below][j])aliveCell++;if(preGeneration[below][right])aliveCell++;if(!preGeneration[i][j]){if(aliveCell==3){// 誕生curGeneration[i][j]=true;}}elseif(aliveCell<=1){// 過疎curGeneration[i][j]=false;}elseif(aliveCell<=3){// 生存curGeneration[i][j]=true;}elseif(aliveCell>=4){// 過密curGeneration[i][j]=false;}}}for(inti=0;i<_rank;i++){for(intj=0;j<_rank;j++){// 前世と現世で状態が変われば反転させるif(preGeneration[i][j]!=curGeneration[i][j]){if(Application.Current.Dispatcher.CheckAccess()){ToggleCellColor(GetCell(i,j));}else{Application.Current.Dispatcher.Invoke(()=>{ToggleCellColor(GetCell(i,j));});}}}}}privatevoidRandomButton_Click(objectsender,RoutedEventArgse){Trace.WriteLine(MethodBase.GetCurrentMethod().Name);try{InitializeGeneration();// 全体の1/3のセルを生存状態にするinttotalCellCount=_rank*_rank;varrandomList=GetRandomRange(0,totalCellCount,totalCellCount/3);for(inti=0;i<_rank;i++){for(intj=0;j<_rank;j++){DataGridCellcell=GetCell(i,j);SolidColorBrushbrush=cell.BackgroundasSolidColorBrush;if(randomList.Any(_r=>_r/_rank==i&&_r%_rank==j)){// 黒curGeneration[i][j]=true;if(brush!=Brushes.Black)cell.Background=Brushes.Black;}else{// 白curGeneration[i][j]=false;if(brush==Brushes.Black)cell.Background=Brushes.White;}}}}catch(Exceptionex){MessageBox.Show(ex.ToString(),"Exception Occurred !!");}}privateList<int>GetRandomRange(intmin,intmax,intcount){if(min>max||count<=0)returnnull;Randomrandom=newRandom(DateTime.Now.Millisecond);List<int>list=newList<int>();while(list.Count<count){intr=random.Next(min,max);if(list.Contains(r))continue;list.Add(r);}returnlist;}privateDataGridCellGetCell(inti,intj){DataGridRowrow=dgSample.ItemContainerGenerator.ContainerFromIndex(i)asDataGridRow;DataGridCellsPresenterpresenter=GetVisualChild<DataGridCellsPresenter>(row);returnpresenter.ItemContainerGenerator.ContainerFromIndex(j)asDataGridCell;}privateTGetVisualChild<T>(Visualparent)whereT:Visual{Tchild=default(T);intvisCt=VisualTreeHelper.GetChildrenCount(parent);for(inti=0;i<visCt;i++){Visualv=VisualTreeHelper.GetChild(parent,i)asVisual;child=vasT;if(child==null)child=GetVisualChild<T>(v);if(child!=null)break;}returnchild;}privatevoidToggleCellColor(DataGridCellcell){SolidColorBrushcurCellColor=cell.BackgroundasSolidColorBrush;if(curCellColor==Brushes.Black){cell.Background=Brushes.White;}else{cell.Background=Brushes.Black;}}}}

ちなみに途中記載している行数・列数からセルを取得するメソッドについては、簡便化の為エラー処理は一切していない。
もし実際に書く場合にはもう少し丁寧に書いた方がいいです。

上記コードで実際に動かしてみたのがこちら
LaifeGame1.gif

今回はもう開き直って全部xaml.csに書きましたが、性分的にはxaml.csには1行も書きたくない派なので
次回、MVVMパターンに則って書き直しを行っていきます。


Viewing all articles
Browse latest Browse all 9701

Trending Articles