challenge マルバツゲーム:賢いプレイヤー

#6190 の続編です。
マルバツゲームで、賢いプレイヤーの思考ルーチンを実装してください。

賢いといってもいろいろありますが、
1.負けない
2.できるだけ勝つ
という条件でいってみたいと思います。

ランダムプレイヤーと1万回バトルした結果(勝ち・負け・分け)を表示してください。
先攻になっても後攻になっても無敗!となれば言うことなしです。

Posted feedbacks - C#

総当りでやってみました。
ただ、1手目から総当りで評価すると時間がかかりすぎるので1~3手目は決めうちしてます。
以下の評価で場所を決めます。
・相手が勝てるパターンがない
・自分が相手よりも先に勝てる
・自分が勝てるパターンが多い
相手のリーチが2箇所になる場合の評価が別になってしまいましたが、統一的にできるのかもしれません。

結果

Aho Aho
{ result = Aho, count = 5873 }
{ result = Aho, count = 2914 }
{ result = draw, count = 1213 }
00:00:02.4866065

Aho Kasikoi
{ result = Kasikoi, count = 8790 }
{ result = draw, count = 1210 }
00:15:10.8550190

Kasikoi Aho
{ result = Kasikoi, count = 8927 }
{ result = draw, count = 1073 }
00:02:20.3465250

Kasikoi Kasikoi
{ result = draw, count = 10000 }
00:15:05.6982845
  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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using Strategy = System.Func<System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Drawing.Point, bool>>, bool, System.Drawing.Point>;
using Board = System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Drawing.Point, bool>>;
using System.Diagnostics;

namespace DoukakuMarubatsu
{
    public static class Marubatsu
    {
        internal static void Start()
        {
            var strategySet = new[] 
            { 
                new Strategy [] { Aho, Aho },
                new Strategy [] { Aho, Kasikoi },
                new Strategy [] { Kasikoi, Aho },
                new Strategy [] { Kasikoi, Kasikoi },
            };

            foreach( var set in strategySet )
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                Debug.WriteLine( string.Format( "{0} {1}", set[ 0 ].Method.Name, set[ 1 ].Method.Name ) );
                foreach( var result in from i in Enumerable.Range( 1, 10000 )
                                       let result = Start( set[ 0 ], set[ 1 ] )
                                       let dummy = Console.WriteLine( i ) is object
                                       group result by result into g
                                       let count = g.Count()
                                       orderby count descending
                                       select new { result = g.Key.HasValue ? ( g.Key.Value ? set[ 0 ].Method.Name : set[ 1 ].Method.Name ) : "draw", count } )
                    Debug.WriteLine( result );
                Debug.WriteLine( sw.Elapsed );
            }

            Console.ReadLine();
        }

        static bool? Start( Strategy getNext1, Strategy getNext2 )
        {
            var board = new Dictionary<Point, bool>();
            return new[] { new { player = true, getNext = getNext1 }, new { player = false, getNext = getNext2 } }
                .Cycle()
                .Select( p =>
                {
                    board.Add( p.getNext( board, p.player ), p.player );
                    return GetWinner( board, p.player );
                } )
                .First( winner => winner.HasValue || board.Count == 9 );
        }

        static bool? GetWinner( Board board )
        {
            return GetWinner( board, true ) ?? GetWinner( board, false );
        }
        static bool? GetWinner( Board board, bool player )
        {
            Func<Func<int, Point>, bool> check = toPoint => new[] { 0, 1, 2 }.All( i => board.Any( p => p.Key == toPoint( i ) && p.Value == player ) );
            if( new[] { 0, 1, 2 }.Any( i => check( x => new Point( x, i ) ) || check( y => new Point( i, y ) ) )
                || check( i => new Point( i, i ) )
                || check( i => new Point( 2 - i, i ) ) )
                return player;
            else
                return null;
        }

        static IEnumerable<Point> EnumeratePoints()
        {
            foreach( var y in new[] { 0, 1, 2 } )
                foreach( var x in new[] { 0, 1, 2 } )
                    yield return new Point( x, y );
        }

        static void TraceBoard( Board board )
        {
            foreach( var p in EnumeratePoints() )
                Console.Write( ToMark( board.Contains( p ) ? (bool?)board.First( pp => pp.Key == p ).Value : null ) + ( p.X == 2 ? "\n" : "" ) );
            Console.WriteLine( "----" );
        }

        static string ToMark( bool? player )
        {
            return player.HasValue ? ( player.Value ? "○" : "×" ) : "□";
        }

        static Random _rnd = new Random();
        static Point Aho( Board board, bool player )
        {
            var putable = board.EnumeratePutable().ToArray();
            return putable[ _rnd.Next( putable.Length ) ];
        }

        #region kasikoi

        static Point Kasikoi( Board board, bool player )
        {
            {//本来は不要だが総当りの回数を減らすために決めうち
                    if( board.Count() == 0 ) return new Point( 1, 1 );
                if( board.Count() == 1 ) return !board.Contains( new Point( 1, 1 ) ) ? new Point( 1, 1 ) : new Point( 0, 0 );
                if( board.Count() == 2 ) return !board.Contains( new Point( 1, 1 ) ) ? new Point( 1, 1 )
                    : new[] { 0, 2 }.SelectMany( y => new[] { 0, 2 }.Select( x => new Point( x, y ) ) ).First( kado => !board.Contains( kado ) );
            }

            //b:boardPattern d:dictionary p:player/keyValuePair/point o:otherPlayer g:group c:candidate
            var candidates0 = ( from b in EnumeratePatterns( board, Enumerable.Empty<KeyValuePair<Point, bool>>(), player )
                                let d = board.Concat( b ).ToDictionary( p => p.Key, p => p.Value )
                                let winner = GetWinner( d )
                                let count = d.Count
                                group new { winner, count } by b.First().Key into g
                                let pMin = g.Min( c => c.winner == player ? c.count : 9 )
                                let oMin = g.Min( c => c.winner == !player ? c.count : 9 )
                                let pCnt = g.Count( c => c.winner == player )
                                let oCnt = g.Count( c => c.winner == !player )
                                orderby pCnt + ( oCnt == 0 ? 1000 : 0 ) descending
                                select new { point = g.Key, pMin, oMin } ).ToArray();

            var katimenasi = candidates0.All( c => c.pMin > c.oMin );
            var candidates = candidates0.Where( c => ( katimenasi || c.pMin <= c.oMin ) && CanPut( board, c.point, player ) );
            return candidates.Count() > 0 ? candidates.First().point : board.EnumeratePutable().First();
        }

        static bool CanPut( Board board, Point point, bool player )
        {
            var virtualBoard = board.Add( point, player );
            return virtualBoard.EnumeratePutable().All( otherPlayerPoint =>
            {
                var virtualBoard2 = virtualBoard.Add( otherPlayerPoint, !player );
                //相手が次の次で勝てるポイント一覧
                    var list = virtualBoard2.EnumeratePutable()
                    .Where( otherPlayerPoint2 => GetWinner( virtualBoard2.Add( otherPlayerPoint2, !player ), !player ) == !player );
                //相手が勝てるポイントが複数あってもそこに自分が置いて勝てるなら問題なし
                    return list.Count() < 2 || list.Any( p => GetWinner( virtualBoard2.Add( p, player ), player ) == player );
            } );
        }

        static IEnumerable<Board> EnumeratePatterns( Board board, Board points, bool player )
        {
            var putable = board.EnumeratePutable().Except( points.Select( pp => pp.Key ) );
            if( putable.Count() == 0 || GetWinner( board ).HasValue )
                yield return points;
            else
                foreach( var p in putable )
                    foreach( var points2 in EnumeratePatterns( board.Add( p, player ), points.Add( p, player ), !player ) )
                        yield return points2;
        }

        #endregion

        #region extension

        public static IEnumerable<Point> EnumeratePutable( this Board board )
        {
            return EnumeratePoints().Except( board.Select( pp => pp.Key ) );
        }

        public static Board Add( this Board board, Point p, bool player )
        {
            return board.Concat( Enumerable.Repeat( new KeyValuePair<Point, bool>( p, player ), 1 ) );
        }

        public static bool Contains( this Board board, Point point )
        {
            return board.Any( p => p.Key == point );
        }

        public static IEnumerable<T> Cycle<T>( this IEnumerable<T> src )
        {
            while( true )
                foreach( T t in src )
                    yield return t;
        }

        #endregion
    }
}

C# 2.0で。
ダブルリーチになるところを探して狙う/防ぐようにしています。
 賢 vs 乱 ≒ 97: 0: 3
 乱 vs 賢 ≒  0:86:14
結構勝ってると思いますが、#6257のミニマックス戦略を用いた「先攻勝率99%」には負けた・・・。
ダブルリーチをチェックしても、例のダブルダブルリーチ(先攻が斜め角に置いていく手)は防げなかったので別対応しています。
  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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
using System;
using System.Collections.Generic;
using System.Text;

namespace MaruBatu
{
    enum Cell { Empty, Maru, Batu, Draw };
    //盤面
    class Board
    {
        Cell[] cells;
        int fillCount;
        public int FillCount {
            get {
                return this.fillCount;
            }
        }
        public Board() {
            cells = new Cell[9];
        }
        public void Clear() {
            for (int i = 0; i < 9; i++) {
                cells[i] = Cell.Empty;
            }
            fillCount = 0;
        }
        public Cell this[int index] {
            get {
                return cells[index];
            }
            set {
                if (value == Cell.Empty && cells[index] != Cell.Empty) {
                    fillCount--;
                } else if (value != Cell.Empty && cells[index] == Cell.Empty) {
                    fillCount++;
                }
                cells[index] = value;
            }
        }
        public Cell Winner {
            get {
                for (int x = 0; x < 3; x++) {
                    if (cells[x] != Cell.Empty
                        && cells[x + 3] == cells[x]
                        && cells[x + 6] == cells[x]) {
                        return cells[x];
                    }
                }
                for (int y = 0; y < 9; y += 3) {
                    if (cells[y] != Cell.Empty
                        && cells[y + 1] == cells[y]
                        && cells[y + 2] == cells[y]) {
                        return cells[y];
                    }
                }
                if (cells[4] != Cell.Empty) {
                    if (cells[0] == cells[4]
                        && cells[8] == cells[4]) {
                        return cells[4];
                    }
                    if (cells[2] == cells[4]
                        && cells[6] == cells[4]) {
                        return cells[4];
                    }
                }
                if (fillCount == 9) {
                    return Cell.Draw;
                }
                return Cell.Empty;
            }
        }
        public override String ToString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 9; i++) {
                if (cells[i] != Cell.Empty) {
                    sb.Append(cells[i] == Cell.Maru ? "O" : "X");
                    sb.Append(" ");
                } else {
                    sb.Append(i + 1);
                    sb.Append(" ");
                }
                if ((i % 3) == 2)
                    sb.Append("\n");
            }
            return sb.ToString();
        }
    }
    //プレイヤー型
    interface IPlayer
    {
        int GetNext(Board brd);
        String Name {
            get;
            set;
        }
    }
    //ランダムプレイヤー
    class RandomPlayer : IPlayer
    {
        Random rand;
        public RandomPlayer() {
            rand = new Random();
        }
        public int GetNext(Board brd) {
            int random = rand.Next(9 - brd.FillCount);
            for (int i = 0; i < 9; i++) {
                if (brd[i] == Cell.Empty) {
                    if (random == 0) {
                        return i;
                    }
                    random--;
                }
            }
            throw new ApplicationException();
        }
        public string Name {
            get { return "ランダムプレイヤー"; }
            set { }
        }
    }
    //賢いプレイヤー
    class WisePlayer : IPlayer
    {
        Cell self;
        public WisePlayer(Cell self) {
            this.self = self;
        }
        int[] priority = new int[] { 4, 0, 2, 6, 8, 1, 3, 5, 7 };
        protected virtual int[] Priority {
            get {
                return priority;
            }
        }
        public int GetNext(Board brd) {
            int reach = -1;
            Cell enemy = (self == Cell.Maru ? Cell.Batu : Cell.Maru);
            //自分のリーチを検索
            reach = getReach(brd, self);
            if (reach != -1) {
                return reach;   //勝ちに行く
            }

            //相手のリーチを検索
            reach = getReach(brd, enemy);
            if (reach != -1) {
                return reach;   //防ぐ
            }

            if (self == Cell.Maru) {
                //自分の二重リーチを探す(先攻)
                reach = getDoubleReach(brd, self);
                if (reach != -1) {
                    return reach;   //ゲット!
                }
            } else {
                //対角ダブダブチェック
                if (brd[4] == self
                    && ((brd[0] == enemy && brd[8] == enemy)
                         || (brd[2] == enemy && brd[6] == enemy))) {
                    //辺をとる
                    foreach (int cell in new int[] { 1, 3, 5, 7 }) {
                        if (brd[cell] == Cell.Empty) {
                            return cell;
                        }
                    }
                }

                //相手の二重リーチを探す(後攻)
                reach = getDoubleReach(brd, enemy);
                if (reach != -1) {
                    return reach;   //させねぇ!
                }
            }

            foreach (int cell in Priority) {
                if (brd[cell] == Cell.Empty) {
                    return cell;
                }
            }

            throw new ApplicationException();   //ここまで来ない
        }
        int getDoubleReach(Board brd, Cell target) {
            int reach = -1;
            for (int i = 0; i < 9 && reach == -1; i++) {
                if (brd[i] != Cell.Empty)
                    continue;
                brd[i] = target;  //仮置き
                int reach1 = getReach(brd, target);
                if (reach1 != -1) {
                    brd[reach1] = Cell.Draw;    //リーチをつぶす
                    int reach2 = getReach(brd, target);
                    if (reach2 != -1) {
                        reach = i;
                    }
                    brd[reach1] = Cell.Empty;
                }
                brd[i] = Cell.Empty;
            }
            return reach;
        }
        int[][] lines = new int[][] {
                new int[] {0,1,2},new int[] {3,4,5},new int[] {6,7,8},    //横
                new int[] {0,3,6},new int[] {1,4,7},new int[] {2,5,8},    //縦
                new int[] {0,4,8},new int[] {2,4,6}                       //斜め
            };
        int getReach(Board brd, Cell target) {
            int[] cellCount = new int[3];   //自分、相手、空き
            foreach (int[] line in lines) {
                int reach = checkReach(brd, target, line);
                if (reach != -1) {
                    return reach;
                }
            }

            return -1;
        }
        int checkReach(Board brd, Cell target, int[] cells) {
            int targetCount = 0, emptyCount = 0;
            int empty = -1;
            foreach (int cell in cells) {
                if (brd[cell] == target) {
                    targetCount++;
                } else if (brd[cell] == Cell.Empty) {
                    emptyCount++;
                    empty = cell;
                }
            }
            if (targetCount == 2
                && emptyCount == 1) {
                return empty;
            } else {
                return -1;
            }
        }
        public virtual string Name {
            get { return "賢いプレイヤー"; }
            set { }
        }
    }
    //人間プレイヤー
    class HumanPlayer : IPlayer
    {
        public int GetNext(Board brd) {
            Console.WriteLine("");
            Console.WriteLine(brd);
            while (true) {
                Console.Write("次の手を選んでください:");
                String input = Console.ReadLine();
                int cell = -1;
                try {
                    cell = int.Parse(input) - 1;
                } catch {
                }
                if (cell < 0 || 8 < cell) {
                    continue;
                } else if (brd[cell] != Cell.Empty) {
                    Console.WriteLine("そのマスは空いてません");
                    continue;
                } else {
                    return cell;
                }
            }
        }
        public string Name {
            get { return "あなた"; }
            set { }
        }
    }
    //1試合
    class Game
    {
        Board board;
        IPlayer[] players;
        int gameCount;
        const int Win = 0;
        const int Lose = 1;
        const int Draw = 2;
        int[,] score;

        public Game(IPlayer p1, IPlayer p2) {
            board = new Board();
            players = new IPlayer[2] { p1, p2 };
            score = new int[2, 3];
            gameCount = 0;
        }
        public string Result {
            get {
                String ret = String.Format("{0}試合の結果\n", gameCount);
                for (int i = 0; i < 2; i++) {
                    ret += String.Format(
                        "{0:30} {1}勝{2}敗{3}分\n",
                        players[i].Name,
                        score[i, Win], score[i, Lose], score[i, Draw]);
                }
                return ret;
            }
        }
        public void Play(bool showResult) {
            int turn = 0;
            gameCount++;
            board.Clear();

            Cell winner;
            while ((winner = board.Winner) == Cell.Empty) {
                int next = players[turn].GetNext(board);
                board[next] = (turn % 2) == 0 ? Cell.Maru : Cell.Batu;
                turn = (turn + 1) % 2;
            }
            switch (winner) {
                case Cell.Draw:
                    score[0, Draw]++;
                    score[1, Draw]++;
                    break;
                case Cell.Maru:
                    score[0, Win]++;
                    score[1, Lose]++;
                    break;
                case Cell.Batu:
                    score[0, Lose]++;
                    score[1, Win]++;
                    break;
            }

            if (showResult) {
                Console.WriteLine(board);
                Console.WriteLine(Result);
            }
        }
    }
    class Program
    {
        static void Main(string[] args) {
            //Game game = new Game(new RandomPlayer(), new RandomPlayer());
            //            Game game = new Game(new WisePlayer(Cell.Maru), new HumanPlayer());
            //            Game game = new Game(new RandomPlayer(), new HumanPlayer());
                        Game game = new Game(new RandomPlayer(), new WisePlayer(Cell.Batu));
            //            Game game = new Game(new WisePlayer(Cell.Maru), new RandomPlayer());
            //            Game game = new Game(new WisePlayer(Cell.Maru), new WisePlayer(Cell.Batu));
            for (int i = 0; i < 100000; i++) {
                //                game.Play(true);
                game.Play(false);
            }
            Console.WriteLine(game.Result);
            Console.ReadLine();
        }
    }
}

Index

Feed

Other

Link

Pathtraq

loading...