オセロをつくります。コンピュータとの対戦型にしていますが、着手を決めるこれといったアルゴリズムはないのでメチャクチャ弱いです。
追記:コンピュータのアルゴリズムを改良して作り直しました。こちらのページがおすすめです。
以下は古いコンテンツです。
使用するのは毎度おなじみのPictureBoxです。オセロの各マスは白か黒、なにも置かれていない状態のいずれかです。
デザイナで[スタート」メニューとステータスバーを作成しました。ステータスバーには手番がどちらにあるかを表示させます。
まずPictureBoxを継承してStoneというクラスを作成します。Stoneクラスは盤面のどの位置にあるのかを管理するRowプロパティとColumプロパティをもちます。
それからStoneColorプロパティで色を変えたり色を取得できるようにしています。
またクリックされたらこれをForm1クラスに伝えることができるようにStoneClickイベントを作成しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public class Stone : PictureBox { StoneColor stoneColor = StoneColor.None; public int Colum { protected set; get; } = 0; public int Row { protected set; get; } = 0; public Stone(int colum, int row) { Colum = colum; Row = row; Click += Stone_Click; } public delegate void StoneClickHandler(int x, int y); public event StoneClickHandler StoneClick; private void Stone_Click(object sender, EventArgs e) { StoneClick?.Invoke(Colum, Row); } public StoneColor StoneColor { get { return stoneColor; } set { stoneColor = value; if(value == StoneColor.Black) BackColor = Color.Black; if(value == StoneColor.White) BackColor = Color.White; if(value == StoneColor.None) BackColor = Color.Green; } } } |
1 2 3 4 5 6 |
public enum StoneColor { None = 0, Black = 1, White = 2, } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public partial class Form1 : Form { Point leftTopPoint = new Point(30, 30); Stone[,] StonePosition = new Stone[8, 8]; public Form1() { InitializeComponent(); CreatePictureBoxes(); GameStart(); } void CreatePictureBoxes() { for(int row=0; row<8; row++) { for(int colum = 0; colum < 8; colum++) { Stone stone = new Stone(colum, row); stone.Parent = this; stone.Size = new Size(40, 40); stone.BorderStyle = BorderStyle.FixedSingle; stone.Location = new Point(leftTopPoint.X +colum * 40, leftTopPoint.Y + row * 40); StonePosition[colum, row] = stone; stone.StoneClick += Box_PictureBoxExClick; stone.BackColor = Color.Green; } } } void GameStart() { var stones = StonePosition.Cast<Stone>(); foreach(Stone stone in stones) stone.StoneColor = StoneColor.None; StonePosition[3, 3].StoneColor = StoneColor.Black; StonePosition[4, 4].StoneColor = StoneColor.Black; StonePosition[3, 4].StoneColor = StoneColor.White; StonePosition[4, 3].StoneColor = StoneColor.White; isYour = true; toolStripStatusLabel1.Text = "あなたの手番です。"; } private void startToolStripMenuItem_Click(object sender, EventArgs e) { GameStart(); } } |
とりあえずこれで実行してみましょう。ちょっと見た目がチープです。
そこで石はPNGファイルをつかって表し、StoneクラスのStoneColorプロパティも少し変更しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Stone : PictureBox { public StoneColor StoneColor { get { return stoneColor; } set { SizeMode = PictureBoxSizeMode.StretchImage; stoneColor = value; if(value == StoneColor.Black) Image = OthelloApp1.Properties.Resources.black; if(value == StoneColor.White) Image = OthelloApp1.Properties.Resources.white; if(value == StoneColor.None) Image = null; } } } |
これでややオセロらしくなりました。
オセロのルールは
相手の石を挟むと裏返して自分の石にできる
石を置ける場所は相手の石を挟むことができる場所でなければならない。
石を置ける場所がない場合はパスをする。ただし石を置ける場所がある場合はパスはできない。
盤面が全部埋まるか、双方がパスをした場合はゲーム終了
ゲームが終了したとき、自分の石の数が多いほうが勝ち
です。
そこでまず着手しようとしたときは本当に着手が許される場所なのかチェックする必要があります。
CreatePictureBoxesメソッドのなかでピクチャーボックスを作成してクリックされた場合はイベントハンドラBox_PictureBoxExClickがよばれます。引数は8×8の盤面のなかにおけるピクチャーボックスの位置です。
手番でないのに着手することはできません。そこでフィールド変数 bool isYour を用意しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { async private void Box_PictureBoxExClick(int x, int y) { // 自分の手番か確認する if(!isYour) return; // 着手可能な場所か調べる List<Stone> stones = GetRevarseStones(x, y, StoneColor.Black); // 以降は後述 } } |
クリックされた場所は着手可能な場所か調べる必要があります。上下左右、斜め、全部で8方向考える必要があります。そこで以下のようなメソッドを作成しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { List<Stone> GetRevarseStones(int x, int y, StoneColor stoneColor) { List<Stone> stones = new List<Stone>(); stones.AddRange(GetReverseOnPutUp(x, y, stoneColor)); // 上方向に挟めているものを取得 stones.AddRange(GetReverseOnPutDown(x, y, stoneColor)); // 下 stones.AddRange(GetReverseOnPutLeft(x, y, stoneColor)); // 左 stones.AddRange(GetReverseOnPutRight(x, y, stoneColor)); // 右 stones.AddRange(GetReverseOnPutLeftTop(x, y, stoneColor)); // 左上 stones.AddRange(GetReverseOnPutLeftDown(x, y, stoneColor)); // 左下 stones.AddRange(GetReverseOnPutRightTop(x, y, stoneColor)); // 右上 stones.AddRange(GetReverseOnPutRightDown(x, y, stoneColor)); // 右下 return stones; } } |
隣の石がない場合、自分の石の場合は挟むことはできません。相手の石の場合、その延長上に自分の石があれば挟むことができていることになります。延長上に自分の石がみつかる前に盤面の外に出てしまったり、石が置かれていないマスが見つかった場合は挟めていないということになります。
挟むことができているStoneオブジェクトをリストに格納して取得できるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
public partial class Form1 : Form { List<Stone> GetReverseOnPutUp(int x, int y, StoneColor color) { List<Stone> stones = new List<Stone>(); StoneColor enemyColor = StoneColor.None; if(color == StoneColor.Black) enemyColor = StoneColor.White; else enemyColor = StoneColor.Black; if(y - 1 < 0) return stones; var s = StonePosition[x, y - 1]; if(s.StoneColor == color || s.StoneColor == StoneColor.None) return stones; stones.Add(s); for(int i=0; ; i++) { if(y - 1 - i < 0) return new List<Stone>(); var s1 = StonePosition[x, y - 1 - i]; if(s1.StoneColor == enemyColor) { stones.Add(s1); continue; } if(s1.StoneColor == color) return stones; if(s1.StoneColor == StoneColor.None) return new List<Stone>(); } } List<Stone> GetReverseOnPutDown(int x, int y, StoneColor color) { List<Stone> stones = new List<Stone>(); StoneColor enemyColor = StoneColor.None; if(color == StoneColor.Black) enemyColor = StoneColor.White; else enemyColor = StoneColor.Black; if(y + 1 > 7) return stones; var s = StonePosition[x, y + 1]; if(s.StoneColor == color || s.StoneColor == StoneColor.None) return stones; stones.Add(s); for(int i = 0; ; i++) { if(y + 1 + i > 7) return new List<Stone>(); var s1 = StonePosition[x, y + 1 + i]; if(s1.StoneColor == enemyColor) { stones.Add(s1); continue; } if(s1.StoneColor == color) return stones; if(s1.StoneColor == StoneColor.None) return new List<Stone>(); } } List<Stone> GetReverseOnPutLeft(int x, int y, StoneColor color) { List<Stone> stones = new List<Stone>(); StoneColor enemyColor = StoneColor.None; if(color == StoneColor.Black) enemyColor = StoneColor.White; else enemyColor = StoneColor.Black; if(x - 1 < 0) return stones; var s = StonePosition[x-1, y]; if(s.StoneColor == color || s.StoneColor == StoneColor.None) return stones; stones.Add(s); for(int i = 0; ; i++) { if(x - 1 - i < 0) return new List<Stone>(); var s1 = StonePosition[x-1-i, y]; if(s1.StoneColor == enemyColor) { stones.Add(s1); continue; } if(s1.StoneColor == color) return stones; if(s1.StoneColor == StoneColor.None) return new List<Stone>(); } } List<Stone> GetReverseOnPutRight(int x, int y, StoneColor color) { List<Stone> stones = new List<Stone>(); StoneColor enemyColor = StoneColor.None; if(color == StoneColor.Black) enemyColor = StoneColor.White; else enemyColor = StoneColor.Black; if(x + 1 > 7) return stones; var s = StonePosition[x + 1, y]; if(s.StoneColor == color || s.StoneColor == StoneColor.None) return stones; stones.Add(s); for(int i = 0; ; i++) { if(x + 1 + i > 7) return new List<Stone>(); var s1 = StonePosition[x + 1 + i, y]; if(s1.StoneColor == enemyColor) { stones.Add(s1); continue; } if(s1.StoneColor == color) return stones; if(s1.StoneColor == StoneColor.None) return new List<Stone>(); } } List<Stone> GetReverseOnPutLeftTop(int x, int y, StoneColor color) { List<Stone> stones = new List<Stone>(); StoneColor enemyColor = StoneColor.None; if(color == StoneColor.Black) enemyColor = StoneColor.White; else enemyColor = StoneColor.Black; if(x - 1 < 0) return stones; if(y - 1 < 0) return stones; var s = StonePosition[x - 1, y-1]; if(s.StoneColor == color || s.StoneColor == StoneColor.None) return stones; stones.Add(s); for(int i = 0; ; i++) { if(x - 1 - i < 0) return new List<Stone>(); if(y - 1 - i < 0) return new List<Stone>(); var s1 = StonePosition[x - 1 - i, y - 1 - i]; if(s1.StoneColor == enemyColor) { stones.Add(s1); continue; } if(s1.StoneColor == color) return stones; if(s1.StoneColor == StoneColor.None) return new List<Stone>(); } } List<Stone> GetReverseOnPutRightTop(int x, int y, StoneColor color) { List<Stone> stones = new List<Stone>(); StoneColor enemyColor = StoneColor.None; if(color == StoneColor.Black) enemyColor = StoneColor.White; else enemyColor = StoneColor.Black; if(x + 1 > 7) return stones; if(y - 1 < 0) return stones; var s = StonePosition[x + 1, y - 1]; if(s.StoneColor == color || s.StoneColor == StoneColor.None) return stones; stones.Add(s); for(int i = 0; ; i++) { if(x + 1 + i > 7) return new List<Stone>(); if(y - 1 - i < 0) return new List<Stone>(); var s1 = StonePosition[x + 1 + i, y - 1 - i]; if(s1.StoneColor == enemyColor) { stones.Add(s1); continue; } if(s1.StoneColor == color) return stones; if(s1.StoneColor == StoneColor.None) return new List<Stone>(); } } List<Stone> GetReverseOnPutRightDown(int x, int y, StoneColor color) { List<Stone> stones = new List<Stone>(); StoneColor enemyColor = StoneColor.None; if(color == StoneColor.Black) enemyColor = StoneColor.White; else enemyColor = StoneColor.Black; if(x + 1 > 7) return stones; if(y + 1 > 7) return stones; var s = StonePosition[x + 1, y + 1]; if(s.StoneColor == color || s.StoneColor == StoneColor.None) return stones; stones.Add(s); for(int i = 0; ; i++) { if(x + 1 + i > 7) return new List<Stone>(); if(y + 1 + i > 7) return new List<Stone>(); var s1 = StonePosition[x + 1 + i, y + 1 + i]; if(s1.StoneColor == enemyColor) { stones.Add(s1); continue; } if(s1.StoneColor == color) return stones; if(s1.StoneColor == StoneColor.None) return new List<Stone>(); } } List<Stone> GetReverseOnPutLeftDown(int x, int y, StoneColor color) { List<Stone> stones = new List<Stone>(); StoneColor enemyColor = StoneColor.None; if(color == StoneColor.Black) enemyColor = StoneColor.White; else enemyColor = StoneColor.Black; if(x - 1 < 0) return stones; if(y + 1 > 7) return stones; var s = StonePosition[x - 1, y + 1]; if(s.StoneColor == color || s.StoneColor == StoneColor.None) return stones; stones.Add(s); for(int i = 0; ; i++) { if(x - 1 - i < 0) return new List<Stone>(); if(y + 1 + i > 7) return new List<Stone>(); var s1 = StonePosition[x - 1 - i, y + 1 + i]; if(s1.StoneColor == enemyColor) { stones.Add(s1); continue; } if(s1.StoneColor == color) return stones; if(s1.StoneColor == StoneColor.None) return new List<Stone>(); } } } |
これで GetRevarseStonesが返した要素の数が0でなければ着手できるということになります。クリックした場所が着手可能な位置であれば石を置き、挟んだ石をひっくり返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public partial class Form1 : Form { bool isYour = true; async private void Box_PictureBoxExClick(int x, int y) { // 自分の手番か確認する if(!isYour) return; // 着手可能な場所か調べる List<Stone> stones = GetRevarseStones(x, y, StoneColor.Black); // 着手可能であれば石を置き、挟んだ石をひっくり返す if(stones.Count != 0) { StonePosition[x, y].StoneColor = StoneColor.Black; stones.Select(xx => xx.StoneColor = StoneColor.Black).ToList(); isYour = false; // コンピュータの手番(次回) } else toolStripStatusLabel1.Text = "ここには打てません"; } } |
自分の着手が終わったら次はコンピュータの手番になりますが、長くなったので次回にします。
未経験でC#でオセロを作成しており、こちらのサイトにお世話になっております!
コンピュータ対戦型ではなく2人のプレイヤーが同じコンピュータを操作する形で対戦できるプログラムにアレンジしたいのですが、アドバイスいただけますと幸いです!
よろしくお願いいたします!
まず現在黒番なのか白番なのかを示すフラグをつくります。あとはvoid Box_PictureBoxExClick(int x, int y)を少し変更すればいいのではないでしょうか?
注意点として
片方が着手したあともう片方のプレイヤーはパスするしかない場合があるのでその判定の処理が必要
両方ともパスするしかない場合は試合終了とすること
試合終了であるにもかかわらずクリックすると石が置けてしまう問題を解決すること
があります。
一例として
OnGameset()メソッドの最後に
をいれる。
GameStart()メソッドの最後に
を入れる
・・・というのはどうでしょうか?
C#でオセロを作成しており、こちらのサイトにお世話になっております!
ソースが途切れて見にくいので全体がわかるものがあればお願いできますでしょうか。
よろしくお願いします。
たしかにそうですね。コードの説明をしたいと思うけど、それをすると説明をソースが途切れて見にくくなってしまいます。リクエストしていただければソース全体をZIPでダウンロードできるようにすることも可能です。しばらくお待ちください。
問い合わせがあると記事も読まれているんだなと思うこともでき、こちらのモチベーションのアップに繋がります。コメントいただきありがとうございました。できるだけ早急に対応しますのでしばらくお待ちください。
ありがとうございます。ZIPファイルをお願いします。
これでどうでしょうか?
https://lets-csharp.com/zip/Othello.zip
丁寧に対応していただきありがとうございました。
ありがとうございました。無事にソースコードを確認できました。
いつも大変参考にさせていただいています。
このオセロロジックをベースに簡単なソフトを組んでるのですが、ソースコードのライセンスはどのようなものになりますでしょうか?
商用ということはないと思いますが、コードをお借りした旨明記してフリーソフトなどで配布することは可能ですか?
特に問題ありません。できれば参考にしたサイトとして当サイトを紹介していただけるとありがたいです。
現在c#でオセロゲームの作成しているものです。とても参考にさせて頂いております、
Listの中でreturnでstonesを返している時とnew List<Stone>()を返している時の違いはなんでしょうか?
stonesを返している時とnew List<Stone>()を返しているときの違いですが、new List<Stone>()を返しているときはリストのなかが空です。stonesを返している時は中に何かが入っている場合があります。
この盤面だと左上が0になるのですが右上を0にしたい場合はどのプログラムを変更すればよろしいでしょうか?
すみません。質問の意味がわかりません。
「右上を0に」とはどのような状態でしょうか?
たとえばGetReverseOnPutUpの下記の「int i = 0」は、「int i = 1」ではないのでしょうか。
for(int i=0; ; i++)
iが0のときStonePosition[x, y – 1 – i]はStonePosition[x, y – 1]になります。
for文に入る前にStonePosition[x, y – 1]をAddしているので、StonePosition[x, y – 1]を二重にAddすることになるように見えます。
動作に影響はないのかもしれませんが。
StoneClick?.Invoke(Colum, Row);
この行はどのような処理が行われますか?
Invokeの機能自体は知ってますが、
その前に来る”?.”の意味がよくわかりません。
>その前に来る”?.”の意味がよくわかりません。
「StoneClick が null でないなら」という意味です。