From 548d9f9c183519208a7316b9518036f97e078092 Mon Sep 17 00:00:00 2001 From: tsubasa-alife Date: Sat, 3 Feb 2024 00:08:12 +0900 Subject: [PATCH 1/2] =?UTF-8?q?MyShogi=E5=B0=8E=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Plugins/MyShogi.meta | 8 + Assets/Plugins/MyShogi/Common.meta | 8 + .../Plugins/MyShogi/Common/Collections.meta | 8 + .../Common/Collections/ListExtensions.cs | 16 + .../Common/Collections/ListExtensions.cs.meta | 11 + .../Common/Collections/StringExtensions.cs | 262 ++ .../Collections/StringExtensions.cs.meta | 11 + .../Common/Collections/SynchronizedList.cs | 49 + .../Collections/SynchronizedList.cs.meta | 11 + Assets/Plugins/MyShogi/Core.meta | 8 + Assets/Plugins/MyShogi/Core/All.cs | 75 + Assets/Plugins/MyShogi/Core/All.cs.meta | 11 + Assets/Plugins/MyShogi/Core/BitOp.cs | 96 + Assets/Plugins/MyShogi/Core/BitOp.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Bitboard.cs | 1105 +++++++++ Assets/Plugins/MyShogi/Core/Bitboard.cs.meta | 11 + Assets/Plugins/MyShogi/Core/BoardType.cs | 253 ++ Assets/Plugins/MyShogi/Core/BoardType.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Color.cs | 103 + Assets/Plugins/MyShogi/Core/Color.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Direct.cs | 99 + Assets/Plugins/MyShogi/Core/Direct.cs.meta | 11 + .../Plugins/MyShogi/Core/EnteringKingRule.cs | 13 + .../MyShogi/Core/EnteringKingRule.cs.meta | 11 + Assets/Plugins/MyShogi/Core/EvalValue.cs | 188 ++ Assets/Plugins/MyShogi/Core/EvalValue.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Exception.cs | 35 + Assets/Plugins/MyShogi/Core/Exception.cs.meta | 11 + Assets/Plugins/MyShogi/Core/File.cs | 84 + Assets/Plugins/MyShogi/Core/File.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Hand.cs | 195 ++ Assets/Plugins/MyShogi/Core/Hand.cs.meta | 11 + Assets/Plugins/MyShogi/Core/HashKey.cs | 78 + Assets/Plugins/MyShogi/Core/HashKey.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Initializer.cs | 15 + .../Plugins/MyShogi/Core/Initializer.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Misc.cs | 50 + Assets/Plugins/MyShogi/Core/Misc.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Move.cs | 530 +++++ Assets/Plugins/MyShogi/Core/Move.cs.meta | 11 + Assets/Plugins/MyShogi/Core/MoveGen.cs | 132 + Assets/Plugins/MyShogi/Core/MoveGen.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Piece.cs | 256 ++ Assets/Plugins/MyShogi/Core/Piece.cs.meta | 11 + Assets/Plugins/MyShogi/Core/PieceNo.cs | 21 + Assets/Plugins/MyShogi/Core/PieceNo.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Position.cs | 2114 +++++++++++++++++ Assets/Plugins/MyShogi/Core/Position.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Rank.cs | 102 + Assets/Plugins/MyShogi/Core/Rank.cs.meta | 11 + .../Plugins/MyShogi/Core/RepetitionState.cs | 16 + .../MyShogi/Core/RepetitionState.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Sfens.cs | 33 + Assets/Plugins/MyShogi/Core/Sfens.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Square.cs | 240 ++ Assets/Plugins/MyShogi/Core/Square.cs.meta | 11 + Assets/Plugins/MyShogi/Core/SquareHand.cs | 169 ++ .../Plugins/MyShogi/Core/SquareHand.cs.meta | 11 + Assets/Plugins/MyShogi/Core/SquareWithWall.cs | 100 + .../MyShogi/Core/SquareWithWall.cs.meta | 11 + Assets/Plugins/MyShogi/Core/UInt128.cs | 111 + Assets/Plugins/MyShogi/Core/UInt128.cs.meta | 11 + Assets/Plugins/MyShogi/Core/Zobrist.cs | 97 + Assets/Plugins/MyShogi/Core/Zobrist.cs.meta | 11 + .../GameSceneScreen/GameSceneController.cs | 5 + Assets/Scripts/Shogi/GameState.cs | 19 +- 66 files changed, 7012 insertions(+), 11 deletions(-) create mode 100644 Assets/Plugins/MyShogi.meta create mode 100644 Assets/Plugins/MyShogi/Common.meta create mode 100644 Assets/Plugins/MyShogi/Common/Collections.meta create mode 100644 Assets/Plugins/MyShogi/Common/Collections/ListExtensions.cs create mode 100644 Assets/Plugins/MyShogi/Common/Collections/ListExtensions.cs.meta create mode 100644 Assets/Plugins/MyShogi/Common/Collections/StringExtensions.cs create mode 100644 Assets/Plugins/MyShogi/Common/Collections/StringExtensions.cs.meta create mode 100644 Assets/Plugins/MyShogi/Common/Collections/SynchronizedList.cs create mode 100644 Assets/Plugins/MyShogi/Common/Collections/SynchronizedList.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core.meta create mode 100644 Assets/Plugins/MyShogi/Core/All.cs create mode 100644 Assets/Plugins/MyShogi/Core/All.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/BitOp.cs create mode 100644 Assets/Plugins/MyShogi/Core/BitOp.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Bitboard.cs create mode 100644 Assets/Plugins/MyShogi/Core/Bitboard.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/BoardType.cs create mode 100644 Assets/Plugins/MyShogi/Core/BoardType.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Color.cs create mode 100644 Assets/Plugins/MyShogi/Core/Color.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Direct.cs create mode 100644 Assets/Plugins/MyShogi/Core/Direct.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/EnteringKingRule.cs create mode 100644 Assets/Plugins/MyShogi/Core/EnteringKingRule.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/EvalValue.cs create mode 100644 Assets/Plugins/MyShogi/Core/EvalValue.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Exception.cs create mode 100644 Assets/Plugins/MyShogi/Core/Exception.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/File.cs create mode 100644 Assets/Plugins/MyShogi/Core/File.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Hand.cs create mode 100644 Assets/Plugins/MyShogi/Core/Hand.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/HashKey.cs create mode 100644 Assets/Plugins/MyShogi/Core/HashKey.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Initializer.cs create mode 100644 Assets/Plugins/MyShogi/Core/Initializer.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Misc.cs create mode 100644 Assets/Plugins/MyShogi/Core/Misc.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Move.cs create mode 100644 Assets/Plugins/MyShogi/Core/Move.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/MoveGen.cs create mode 100644 Assets/Plugins/MyShogi/Core/MoveGen.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Piece.cs create mode 100644 Assets/Plugins/MyShogi/Core/Piece.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/PieceNo.cs create mode 100644 Assets/Plugins/MyShogi/Core/PieceNo.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Position.cs create mode 100644 Assets/Plugins/MyShogi/Core/Position.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Rank.cs create mode 100644 Assets/Plugins/MyShogi/Core/Rank.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/RepetitionState.cs create mode 100644 Assets/Plugins/MyShogi/Core/RepetitionState.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Sfens.cs create mode 100644 Assets/Plugins/MyShogi/Core/Sfens.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Square.cs create mode 100644 Assets/Plugins/MyShogi/Core/Square.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/SquareHand.cs create mode 100644 Assets/Plugins/MyShogi/Core/SquareHand.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/SquareWithWall.cs create mode 100644 Assets/Plugins/MyShogi/Core/SquareWithWall.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/UInt128.cs create mode 100644 Assets/Plugins/MyShogi/Core/UInt128.cs.meta create mode 100644 Assets/Plugins/MyShogi/Core/Zobrist.cs create mode 100644 Assets/Plugins/MyShogi/Core/Zobrist.cs.meta diff --git a/Assets/Plugins/MyShogi.meta b/Assets/Plugins/MyShogi.meta new file mode 100644 index 0000000..d7aff10 --- /dev/null +++ b/Assets/Plugins/MyShogi.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b45d8583e1b7e4354ad99ae67c7106a4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Common.meta b/Assets/Plugins/MyShogi/Common.meta new file mode 100644 index 0000000..8a40c69 --- /dev/null +++ b/Assets/Plugins/MyShogi/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dbcd787dc08b648a19794563d860fd60 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Common/Collections.meta b/Assets/Plugins/MyShogi/Common/Collections.meta new file mode 100644 index 0000000..6cc926e --- /dev/null +++ b/Assets/Plugins/MyShogi/Common/Collections.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a555323db5d82437baa0dd9c136fd229 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Common/Collections/ListExtensions.cs b/Assets/Plugins/MyShogi/Common/Collections/ListExtensions.cs new file mode 100644 index 0000000..a3e4b00 --- /dev/null +++ b/Assets/Plugins/MyShogi/Common/Collections/ListExtensions.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace MyShogi.Model.Common.Collections +{ + /// + /// generic classのListのextension methods + /// + public static class ListExtensions + { + public static IEnumerable ReverseIterator(this List list) + { + for (int i = list.Count - 1; i >= 0; --i) + yield return list[i]; + } + } +} diff --git a/Assets/Plugins/MyShogi/Common/Collections/ListExtensions.cs.meta b/Assets/Plugins/MyShogi/Common/Collections/ListExtensions.cs.meta new file mode 100644 index 0000000..5e4a33d --- /dev/null +++ b/Assets/Plugins/MyShogi/Common/Collections/ListExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15104a5f2ce6849238143b72594e07c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Common/Collections/StringExtensions.cs b/Assets/Plugins/MyShogi/Common/Collections/StringExtensions.cs new file mode 100644 index 0000000..9a9a4f1 --- /dev/null +++ b/Assets/Plugins/MyShogi/Common/Collections/StringExtensions.cs @@ -0,0 +1,262 @@ +using System.Text; + +namespace MyShogi.Model.Common.Collections +{ + /// + /// string型に対する、LeftやRightを提供するextensions + /// + /// nullに対して呼び出してもnullを返す。 + /// (System.Text.PadLeft()などはこの仕様になっていないので呼び出す前のnullチェックが必要になって使いづらい。) + /// + public static class StringExtensions + { + /// + /// string.IsNullOrEmpty()のショートカット。 + /// + /// + /// + public static bool Empty(this string s) + { + return string.IsNullOrEmpty(s); + } + + // これ用意するとLinqのほうとごっちゃになるのでやめとく。 +#if false + /// + /// Empty()の否定 + /// + /// + /// + public static bool Any(this string s) + { + return !string.IsNullOrEmpty(s); + } +#endif + + /// + /// 左からn文字切り出して返す。 + /// + /// + /// + /// + public static string Left(this string s , int n) + { + if (s == null) + return null; + + return s.Substring(0, System.Math.Min(s.Length, n)); + } + + /// + /// 右からn文字切り出して返す + /// + /// + /// + /// + public static string Right(this string s , int n) + { + if (s == null || n <= 0) + return null; + + return s.Substring(s.Length - n , n); + } + + /// + /// n文字目以降をm文字切り出す。 + /// + /// + /// + /// + public static string Mid(this string s , int n , int m) + { + if (s == null) + return null; + + // 切り出し始める箇所がsの長さを超えている + if (s.Length < n) + return string.Empty; + + // 切り出す文字数が多すぎてsの末尾を超えている + if (s.Length < n + m) + return s.Substring(n); + + return s.Substring(n,m); + } + + + /// + /// string.Left()と同じだが、全角スペースは2文字分として扱ってLeftする。 + /// n : 半角何文字分にして返すか。 + /// + /// + /// + /// + public static string LeftUnicode(this string s, int n) + { + if (s == null) + return null; + + var sb = new StringBuilder(); + foreach (var c in s) + { + n -= (c < 256) ? 1 : 2; + if (n < 0) + return sb.ToString(); + sb.Append(c); + } + return s; // 文字列丸ごとが、nの範囲に収まった。 + } + + /// + /// LeftUnicode()と同等だが、sの文字数がn - t.UnicodeLengthを超えたときにはtを出力する(「..」などを出力したい時に用いる) + /// + /// + /// + /// + /// + public static string LeftUnicode(this string s, int n , string t) + { + n -= t.UnicodeLength(); + var length = s.UnicodeLength(); + return (length <= n) ? + s.LeftUnicode(n): + $"{s.LeftUnicode(n)}{t}"; + } + + // 他、また気が向いたら書く。 + + /// + /// string.PadLeft()と同じだが、全角スペースは2文字分として扱ってPadLeftする。 + /// n : 半角何文字分にして返すか。 + /// + /// + /// + /// + public static string PadLeftUnicode(this string s , int n) + { + if (s == null) + return null; + + // まず全角を1文字として文字数を数える + int len = 0; + foreach (var c in s) + len += (c < 256) ? 1 : 2; + + // 全角文字の数だけ減らしてPadLeft()する。 + return s.PadLeft(System.Math.Max(n - (len - s.Length), 0)); + } + + /// + /// string.PadRight()と同じだが、全角スペースは2文字分として扱ってPadRightする。 + /// n : 半角何文字分にして返すか。 + /// + /// + /// + /// + public static string PadRightUnicode(this string s, int n) + { + if (s == null) + return null; + + // まず全角を1文字として文字数を数える + int len = 0; + foreach (var c in s) + len += (c < 256) ? 1 : 2; + + // 全角文字の数だけ減らしてPadRight()する。 + return s.PadRight(System.Math.Max(n - (len - s.Length), 0)); + } + + + /// + /// s と tの間に半角スペースを、全体が半角n文字になるようにpaddingする。 + /// + /// + /// + /// + public static string PadMidUnicode(this string s, string t , int n) + { + if (s == null) + return null; + + // まず全角を1文字として全体の文字数を数える + int len = 0; + foreach (var c in s) + len += (c < 256) ? 1 : 2; + foreach (var c in t) + len += (c < 256) ? 1 : 2; + + // n - lenの数だけスペースを放り込む + return $"{s}{new string(' ',System.Math.Max(n - len, 0))}{t}"; + } + + /// + /// 全角文字を2文字、半角文字を1文字としてカウントするLength + /// + /// + /// + public static int UnicodeLength(this string s) + { + if (s == null) + return 0; + + int n = 0; + foreach (var c in s) + n += (c < 256) ? 1 : 2; + return n; + } + + /// + /// 先頭の1文字を返す。 + /// + /// + /// + public static char FirstChar(this string s) + { + if (s == null || s.Length == 0) + return (char)0; + + return s[0]; + } + + /// + /// 全角文字を2文字、半角文字を1文字としてカウントして、lengthごとに tを挿入する。 + /// 改行文字列が入っていないstringに対して、改行文字列を一定文字数ごとに挿入したいときなどに用いる。 + /// + /// + /// + /// + /// + public static string UnicodeInsertEvery(this string s , string t , int length) + { + var sb = new StringBuilder(); + var len = 0; + var next_len = length; + foreach (var c in s) + { + len += (c < 256) ? 1 : 2; + sb.Append(c); + + // tの挿入位置になったのか? + if (len >= next_len) + { + sb.Append(t); + next_len += length; + } + } + return sb.ToString(); + } + + /// + /// 文字列を整数化する。ただし、整数化に失敗した場合は、引数で指定されているdefaultValueの値を返す。 + /// + /// + /// + /// + public static int ToInt(this string s , int defaultValue) + { + int result; + return int.TryParse(s, out result) ? result : defaultValue; + } + } +} diff --git a/Assets/Plugins/MyShogi/Common/Collections/StringExtensions.cs.meta b/Assets/Plugins/MyShogi/Common/Collections/StringExtensions.cs.meta new file mode 100644 index 0000000..f8f9000 --- /dev/null +++ b/Assets/Plugins/MyShogi/Common/Collections/StringExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8182315fe5379489a8afda28cdc0f4ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Common/Collections/SynchronizedList.cs b/Assets/Plugins/MyShogi/Common/Collections/SynchronizedList.cs new file mode 100644 index 0000000..28fe2a7 --- /dev/null +++ b/Assets/Plugins/MyShogi/Common/Collections/SynchronizedList.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; + +namespace MyShogi.Model.Common.Collections +{ + /// + /// 同期版のList + /// Javaでよく出てくるやつ。 + /// + public class SynchronizedList + { + /// + /// [async] List.Add(T t)と同じ。 + /// + /// + public void Add(T t) + { + lock (sync_object) + list.Add(t); + } + + // その他、気が向いたら追加する。とりま、いまAdd()しか要らない。 + + /// + /// [async] 内部的に保持しているListを返し、内部的に保持しているListは、 + /// List.Clear()を呼び出した状態に初期化される。 + /// + /// + public List GetList() + { + lock (sync_object) + { + var result = list; + list = new List(); // これで空に。 + return result; + } + } + + /// + /// 内部的に保持しているListの実体。 + /// 直接外部からアクセスしてはならない。 + /// + private List list = new List(); + + /// + /// lock用のobject + /// + private object sync_object = new object(); + } +} diff --git a/Assets/Plugins/MyShogi/Common/Collections/SynchronizedList.cs.meta b/Assets/Plugins/MyShogi/Common/Collections/SynchronizedList.cs.meta new file mode 100644 index 0000000..57c3e60 --- /dev/null +++ b/Assets/Plugins/MyShogi/Common/Collections/SynchronizedList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 780ea78e8bf264a2eb0680a08f0b753a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core.meta b/Assets/Plugins/MyShogi/Core.meta new file mode 100644 index 0000000..1f53420 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b7691c5585416446fa7d0f371ce27b38 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/All.cs b/Assets/Plugins/MyShogi/Core/All.cs new file mode 100644 index 0000000..b21a532 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/All.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// enum型に対するenumeratorの生成。 + /// + /// foreach(var c in All.Colors()) .. とか書けて気分いい。 + /// + public static class All + { + public static IEnumerable Colors() + { + for (var c = Color.ZERO; c < Color.NB; ++c) + yield return c; + } + + /// + /// int型が返ってくるColors() + /// + /// + public static IEnumerable IntColors() + { + for (var c = Color.ZERO; c < Color.NB; ++c) + yield return (int)c; + } + + public static IEnumerable Squares() + { + for (var sq = Square.ZERO; sq < Square.NB; ++sq) + yield return sq; + } + + public static IEnumerable Files() + { + for (var f = File.ZERO; f < File.NB; ++f) + yield return f; + } + + // 将棋とは関係ないがあると便利そうなのも追加しとく。 + + public static IEnumerable Bools() + { + yield return false; + yield return true; + } + + /// + /// foreach(var x in All.Int(5)) とすると x = 0,1,2,3,4でループを回る。 + /// + /// + /// + public static IEnumerable Int(int n) + { + for (int i = 0; i < n; ++i) + yield return i; + } + + /// + /// a <= i < b の範囲で回す + /// foreach(var x in All.Int(5,8)) とすると x = 5,6,7でループを回る。 + /// + /// + /// + /// + public static IEnumerable Int(int a , int b) + { + for (int i = a; i < b; ++i) + yield return i; + } + + + // 他、気が向いたら追加する。 + } +} diff --git a/Assets/Plugins/MyShogi/Core/All.cs.meta b/Assets/Plugins/MyShogi/Core/All.cs.meta new file mode 100644 index 0000000..c81b132 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/All.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 457b29837a9f44a288330c0cc1f10b1b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/BitOp.cs b/Assets/Plugins/MyShogi/Core/BitOp.cs new file mode 100644 index 0000000..e3dd379 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/BitOp.cs @@ -0,0 +1,96 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// BitOperation一式 + /// + public static class BitOp + { + /// + /// 2進数で見て1になっている一番下位のbit位置を返し、そのbitを0にする。 + /// + /// + /// + public static int LSB64(ref UInt64 n) + { + // cf. Bit Twiddling Hacks : http://graphics.stanford.edu/~seander/bithacks.html + + // Count the consecutive zero bits (trailing) on the right with multiply and lookup + UInt32 v = (UInt32)n; + if (v != 0) + { + int r = MultiplyDeBruijnBitPosition[((UInt32)((v & -v) * 0x077CB531U)) >> 27]; + n ^= (1UL << r); + return r; + } else + { + v = (UInt32)(n >> 32); + + // 上位32bitのどこかが非0であるはず + // assert(v != 0); + + int r = MultiplyDeBruijnBitPosition[((UInt32)((v & -v) * 0x077CB531U)) >> 27]; + r += 32; + n ^= (1UL << r); + return r; + } + } + + private static readonly int[] MultiplyDeBruijnBitPosition = + { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 + }; + + /// + /// for non-AVX2 : software emulationによるpext実装(やや遅い。とりあえず動くというだけ。) + /// + /// + /// + /// + public static UInt64 PEXT64(UInt64 val , UInt64 mask) + { + UInt64 res = 0; + for (UInt64 bb = 1; mask != 0 ; bb += bb) + { + if (((Int64)val & (Int64)mask & -(Int64)mask) != 0) + res |= bb; + // マスクを1bitずつ剥がしていく実装なので処理時間がbit長に依存しない。 + // ゆえに、32bit用のpextを別途用意する必要がない。 + mask &= mask - 1; + } + return res; + } + } + + /// + /// BitOpに関するextension methods + /// + public static class BitOpExtensions + { + /// + /// 2進数として見たときに1になっているbitの数を数える。 + /// ソフトウェア実装なのでそこそこ遅い。 + /// + /// + /// + public static int PopCount(this UInt64 n) + { + UInt32 n0 = (UInt32)n; + UInt32 n1 = (UInt32)(n >> 32); + + // cf. Checking CPU Popcount from C# : https://stackoverflow.com/questions/6097635/checking-cpu-popcount-from-c-sharp?lq=1 + ulong result0 = n0 - ((n0 >> 1) & 0x5555555555555555UL); + result0 = (result0 & 0x3333333333333333UL) + ((result0 >> 2) & 0x3333333333333333UL); + var r0 = (int)(unchecked(((result0 + (result0 >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56); + + ulong result1 = n1 - ((n1 >> 1) & 0x5555555555555555UL); + result1 = (result1 & 0x3333333333333333UL) + ((result1 >> 2) & 0x3333333333333333UL); + var r1 = (int)(unchecked(((result1 + (result1 >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56); + + return r0 + r1; + } + } + +} diff --git a/Assets/Plugins/MyShogi/Core/BitOp.cs.meta b/Assets/Plugins/MyShogi/Core/BitOp.cs.meta new file mode 100644 index 0000000..8058786 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/BitOp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5087a1ab4e1cb44b793609516ba16d45 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Bitboard.cs b/Assets/Plugins/MyShogi/Core/Bitboard.cs new file mode 100644 index 0000000..8fe8dee --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Bitboard.cs @@ -0,0 +1,1105 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// Bitboard + /// 駒の利きなどを表現する + /// やねうら王から移植 + /// + /// classではなくstructなので注意。 + /// + public struct Bitboard + { + /// + /// Bitboardの実体 + /// + public UInt128 p; + + // ------------------------------------------------------------------------- + // コンストラクタ + // ------------------------------------------------------------------------- + + /// + /// 128bit構造体で初期化するコンストラクタ + /// + /// + public Bitboard(UInt128 p_) + { + p = p_; + } + + /// + /// 64bit整数2つで初期化するコンストラクタ + /// + /// + public Bitboard(UInt64 p0_, UInt64 p1_) + { + p = new UInt128(p0_, p1_); + } + + /// + /// コピーコンストラクタ + /// + /// + public Bitboard(Bitboard b) + { + p = b.p; + } + + /// + /// sqの升が1のBitboardとして初期化する。 + /// + /// + public Bitboard(Square sq) + { + p = SQUARE_BB[sq.ToInt()].p; + } + + // ------------------------------------------------------------------------- + // bitboardに関するビット単位のand/or/xor演算 + // ------------------------------------------------------------------------- + + public static Bitboard operator &(Bitboard c1, Bitboard c2) + { + return new Bitboard(c1.p & c2.p); + } + + public static Bitboard operator |(Bitboard c1, Bitboard c2) + { + return new Bitboard(c1.p | c2.p); + } + + public static Bitboard operator ^(Bitboard c1, Bitboard c2) + { + return new Bitboard(c1.p ^ c2.p); + } + + // 単項演算子 + // → NOTで書くと、使っていないbit(p[0]のbit63)がおかしくなるのでALL_BBでxorしないといけない。 + public static Bitboard operator ~(Bitboard a) + { + return a ^ ALL_BB; + } + + + public static Bitboard operator &(Bitboard c1, Square sq) + { + return new Bitboard(c1.p & SQUARE_BB[sq.ToInt()].p); + } + + public static Bitboard operator |(Bitboard c1, Square sq) + { + return new Bitboard(c1.p | SQUARE_BB[sq.ToInt()].p); + } + + public static Bitboard operator ^(Bitboard c1, Square sq) + { + return new Bitboard(c1.p ^ SQUARE_BB[sq.ToInt()].p); + } + + public static Bitboard operator <<(Bitboard c1, int n) + { + // このbit shiftは、p[0]とp[1]をまたがない。 + return new Bitboard(c1.p << n); + } + + public static Bitboard operator >>(Bitboard c1, int n) + { + // このbit shiftは、p[0]とp[1]をまたがない。 + return new Bitboard(c1.p >> n); + } + + public static bool operator == (Bitboard lhs, Bitboard rhs) + { + return lhs.p == rhs.p; + } + + public static bool operator !=(Bitboard lhs , Bitboard rhs) + { + return lhs.p != rhs.p; + } + + public override bool Equals(object o) + { + return this.p == ((Bitboard)o).p; + } + + public override int GetHashCode() + { + return p.GetHashCode(); + } + + /// + /// 下位bitから1bit拾ってそのbit位置を返す。 + /// 少なくとも1bitは非0と仮定 + /// while(to = bb.Pop()) + /// Util.MakeMove(from,to); + /// のように用いる。 + /// + /// + public Square Pop() + { + Debug.Assert(!IsZero()); + return (p.p0 != 0) ? (Square)(BitOp.LSB64(ref p.p0)) : (Square)(BitOp.LSB64(ref p.p1) + 63); + } + + /// + /// 1になっている数を数える + /// + /// + public int PopCount() + { + return p.p0.PopCount() + p.p1.PopCount(); + } + + /// + /// 2bit以上あるかどうかを判定する。縦横斜め方向に並んだ駒が2枚以上であるかを判定する。この関係にないと駄目。 + /// この関係にある場合、Bitboard::merge()によって被覆しないことがBitboardのレイアウトから保証されている。 + /// + /// + /// + public static bool MoreThanOne(Bitboard bb) + { + // ASSERT_LV2(!bb.cross_over()); + return bb.Merge().PopCount() > 1; + } + + // 2升に挟まれている升を返すためのテーブル(その2升は含まない) + // この配列には直接アクセスせずにbetween_bb()を使うこと。 + // 配列サイズが大きくてcache汚染がひどいのでシュリンクしてある。 + private static Bitboard[] BetweenBB_; // =new Bitboard[785]; + private static UInt16 [,] BetweenIndex; // = new UInt16 [SQ_NB_PLUS1][SQ_NB_PLUS1]; + + /// + /// 2升に挟まれている升を表すBitboardを返す。sq1とsq2が縦横斜めの関係にないときはZERO_BBが返る。 + /// + /// + /// + /// + public static Bitboard BetweenBB(Square sq1, Square sq2) + { + return BetweenBB_[BetweenIndex[(int)sq1,(int)sq2]]; + } + + // 2升を通過する直線を返すためのテーブル + // 2つ目のindexは[0]:右上から左下、[1]:横方向、[2]:左上から右下、[3]:縦方向の直線。 + // この配列には直接アクセスせず、line_bb()を使うこと。 + private static Bitboard[,] LineBB_; //[SQ_NB][4]; + + /// + /// 2升を通過する直線を返すためのテーブル + /// 2つ目のindexは[0]:右上から左下、[1]:横方向、[2]:左上から右下、[3]:縦方向の直線。 + /// + /// + /// + /// + public static Bitboard LineBB(Square sq,int type) + { + return LineBB_[(int)sq, type]; + } + + /// + /// foreach(var sq in bb) .. のように書くためのもの。 + /// そこそこ遅いので速度が要求されるところで使わないこと。 + /// + /// + public IEnumerator GetEnumerator() + { + var bb = this; // BitboardはstructなのでこれはClone()相当 + + while (bb.IsNotZero()) + yield return bb.Pop(); + } + + // ------------------------------------------------------------------------- + // public methods + // ------------------------------------------------------------------------- + + /// + /// sqの升のbitが立っているかを判定する。 + /// + /// + /// + public bool IsSet(Square sq) + { + return (p & SQUARE_BB[sq.ToInt()].p).ToU() != 0; + } + + /// + /// すべてのbitが0であるかどうかを判定する。 + /// + /// + public bool IsZero() + { + return p.ToU() == 0; + } + + /// + /// 1bitでもbitが立っているかどうかを判定する。 + /// + /// + public bool IsNotZero() + { + return p.ToU() != 0; + } + + /// + /// 1になっているbitが1つだけである。 + /// + /// + public bool IsOne() + { + return PopCount() == 1; + } + + /// + /// bitboardを綺麗に出力する + /// + /// + public string Pretty() + { + var sb = new StringBuilder(); + + for (Rank r = Rank.RANK_1; r <= Rank.RANK_9; ++r) + { + for (File f = File.FILE_9; f >= File.FILE_1; --f) + { + sb.Append(IsSet(Util.MakeSquare(f, r)) ? '*' : '.'); + } + sb.AppendLine(); + } + + return sb.ToString(); + } + + // ------------------------------------------------------------------------- + // 利きを返すbitboardなど + // ------------------------------------------------------------------------- + + /// + /// すべての升が1であるBitboard + /// + /// + public static Bitboard AllBB() + { + return ALL_BB; + } + + /// + /// すべての升が0であるBitboard + /// + /// + public static Bitboard ZeroBB() + { + return ZERO_BB; + } + + /// + /// 筋を表現するbitboardを返す + /// + /// + public static Bitboard FileBB(File f) + { + return FILE_BB[f.ToInt()]; + } + + /// + /// 段を表すbitboardを返す + /// + /// + /// + public static Bitboard RankBB(Rank r) + { + return RANK_BB[r.ToInt()]; + } + + /// + /// sqの升が1であるbitboardを返す + /// + /// + /// + public static Bitboard SquareBB(Square sq) + { + return SQUARE_BB[(int)sq]; + } + + // ForwardRanksBBの定義) + // c側の香の利き = 飛車の利き & ForwardRanksBB[c][rank_of(sq)] + // + // すなわち、 + // color == BLACKのとき、n段目よりWHITE側(1からn-1段目)を表現するBitboard。 + // color == WHITEのとき、n段目よりBLACK側(n+1から9段目)を表現するBitboard。 + // このアイデアはAperyのもの。 + public static Bitboard ForwardRanks(Color c, Rank r) + { + return ForwardRanksBB[(int)c, (int)r]; + } + + // --- 遠方駒(盤上の駒の状態を考慮しながら利きを求める) + + /// + /// 角の右上と左下方向への利き + /// + /// + /// + /// + private static Bitboard BishopEffect0(Square sq, Bitboard occupied) + { + Bitboard block0 = new Bitboard(occupied & BishopEffectMask[0, (int)sq]); + return BishopEffectBB[0, BishopEffectIndex[0, (int)sq] + (int)OccupiedToIndex(block0, BishopEffectMask[0, (int)sq])]; + } + + /// + /// 角の左上と右下方向への利き + /// + /// + /// + /// + private static Bitboard BishopEffect1(Square sq, Bitboard occupied) + { + Bitboard block1 = new Bitboard(occupied & BishopEffectMask[1, (int)sq]); + return BishopEffectBB[1, BishopEffectIndex[1, (int)sq] + (int)OccupiedToIndex(block1, BishopEffectMask[1, (int)sq])]; + } + + /// + /// 角 : occupied bitboardを考慮しながら角の利きを求める + /// + /// + /// + /// + public static Bitboard BishopEffect(Square sq, Bitboard occupied) + { + return BishopEffect0(sq, occupied) | BishopEffect1(sq, occupied); + } + + /// + /// 馬 : occupied bitboardを考慮しながら香の利きを求める + /// + /// + /// + /// + public static Bitboard HorseEffect(Square sq, Bitboard occupied) + { + return BishopEffect(sq, occupied) | KingEffect(sq); + } + + // 指定した升(Square)が Bitboard のどちらの u64 変数の要素に属するか。 + // 本ソースコードのように縦型Bitboardにおいては、香の利きを求めるのにBitboardの + // 片側のp[x]を調べるだけで済むので、ある升がどちらに属するかがわかれば香の利きは + // そちらを調べるだけで良いというAperyのアイデア。 + private static int Part(Square sq) { return (Square.SQ_79 < sq) ? 1 : 0; } + + /// + /// 飛車の縦の利き + /// + /// + /// + /// + public static Bitboard RookFileEffect(Square sq, Bitboard occupied) + { + UInt64 occ = Part(sq) == 0 ? occupied.p.p0 : occupied.p.p1; + int index = (int)((occ >> Slide[(int)sq]) & 0x7f); + File f = sq.ToFile(); + return (f <= File.FILE_7) ? + new Bitboard(RookFileEffectBB[(int)sq.ToRank(), index] << (int)Util.MakeSquare(f, Rank.RANK_1), 0) : + new Bitboard(0, RookFileEffectBB[(int)sq.ToRank(), index] << (int)Util.MakeSquare((File)(f - File.FILE_8), Rank.RANK_1)); + } + + /// + /// 飛車の横の利き + /// + /// + /// + /// + public static Bitboard RookRankEffect(Square sq, Bitboard occupied) + { + // 将棋盤をシフトして、SQ_71 , SQ_61 .. SQ_11に飛車の横方向の情報を持ってくる。 + // このbitを直列化して7bit取り出して、これがindexとなる。 + // しかし、r回の右シフトを以下の変数uに対して行なうと計算完了まで待たされるので、 + // PEXT64()の第二引数のほうを左シフトしておく。 + int r = (int)sq.ToRank(); + UInt64 u = (occupied.p.p1 << 6 * 9) + (occupied.p.p0 >> 9); + UInt64 index = BitOp.PEXT64(u, 0b1000000001000000001000000001000000001000000001000000001UL << r); + return RookRankEffectBB[(int)sq.ToFile(), index] << r; + } + + /// + /// 飛 : occupied bitboardを考慮しながら飛車の利きを求める + /// + /// + /// + /// + public static Bitboard RookEffect(Square sq, Bitboard occupied) + { + return RookFileEffect(sq, occupied) | RookRankEffect(sq, occupied); + } + + /// + /// 香 : occupied bitboardを考慮しながら香の利きを求める + /// + /// + /// + /// + /// + public static Bitboard LanceEffect(Color c, Square sq, Bitboard occupied) + { + return RookFileEffect(sq, occupied) & LanceStepEffect(c, sq); + } + + /// + /// 龍 : occupied bitboardを考慮しながら香の利きを求める + /// + /// + /// + /// + public static Bitboard DragonEffect(Square sq, Bitboard occupied) + { + return RookEffect(sq, occupied) | KingEffect(sq); + } + + /// + /// sqに王をおいたときに利きがある升が1であるbitboardを返す + /// + /// + /// + public static Bitboard KingEffect(Square sq) + { + return KingEffectBB[sq.ToInt()]; + } + + /// + /// 歩の利き + /// + /// + /// + /// + public static Bitboard PawnEffect(Color c, Square sq) + { + return PawnEffectBB[(int)sq, (int)c]; + } + + // Bitboardに対する歩の利き + // color = BLACKのとき、51の升は49の升に移動するので、注意すること。 + // (51の升にいる先手の歩は存在しないので、歩の移動に用いる分には問題ないが。) + public static Bitboard PawnEffect(Color c, Bitboard bb) + { + // Apery型の縦型Bitboardにおいては歩の利きはbit shiftで済む。 + //ASSERT_LV3(is_ok(c)); + //return c == BLACK ? bb >> 1 : c == WHITE ? bb << 1 + // : ZERO_BB; + + return ZERO_BB; + } + + /// + /// 桂の利き + /// + /// + public static Bitboard KnightEffect(Color c, Square sq) + { + //ASSERT_LV3(is_ok(c) && sq <= SQ_NB); + return KnightEffectBB[(int)sq, (int)c]; + } + + /// + /// 銀の利き + /// + /// + /// + /// + public static Bitboard SilverEffect(Color c, Square sq) + { + //ASSERT_LV3(is_ok(c) && sq <= SQ_NB); + return SilverEffectBB[(int)sq, (int)c]; + } + + /// + /// 金の利き + /// + /// + /// + /// + public static Bitboard GoldEffect(Color c, Square sq) + { + //ASSERT_LV3(is_ok(c) && sq <= SQ_NB); + return GoldEffectBB[(int)sq, (int)c]; + } + + // --- 遠方仮想駒(盤上には駒がないものとして求める利き) + + /// + /// 盤上の駒を考慮しない角の利き + /// + /// + /// + public static Bitboard BishopStepEffect(Square sq) + { + //ASSERT_LV3(sq <= SQ_NB); + return BishopStepEffectBB[(int)sq]; + } + + /// + /// 盤上の駒を考慮しない飛車の利き + /// + /// + /// + public static Bitboard RookStepEffect(Square sq) + { + //ASSERT_LV3(sq <= SQ_NB); + return RookStepEffectBB[(int)sq]; + } + + /// + /// 盤上の駒を考慮しない香の利き + /// + public static Bitboard LanceStepEffect(Color c, Square sq) + { + //ASSERT_LV3(is_ok(c) && sq <= SQ_NB); + return LanceStepEffectBB[(int)sq, (int)c]; + } + + /// + /// 盤上sqに駒pc(先後の区別あり)を置いたときの利き。 + /// pc == QUEENだと馬+龍の利きが返る。 + /// + /// + public static Bitboard EffectsFrom(Piece pc, Square sq, Bitboard occ) + { + switch (pc) + { + case Piece.B_PAWN: return PawnEffect(Color.BLACK, sq); + case Piece.B_LANCE: return LanceEffect(Color.BLACK, sq, occ); + case Piece.B_KNIGHT: return KnightEffect(Color.BLACK, sq); + case Piece.B_SILVER: return SilverEffect(Color.BLACK, sq); + case Piece.B_GOLD: case Piece.B_PRO_PAWN: case Piece.B_PRO_LANCE: case Piece.B_PRO_KNIGHT: case Piece.B_PRO_SILVER: return GoldEffect(Color.BLACK, sq); + + case Piece.W_PAWN: return PawnEffect(Color.WHITE, sq); + case Piece.W_LANCE: return LanceEffect(Color.WHITE, sq, occ); + case Piece.W_KNIGHT: return KnightEffect(Color.WHITE, sq); + case Piece.W_SILVER: return SilverEffect(Color.WHITE, sq); + case Piece.W_GOLD: case Piece.W_PRO_PAWN: case Piece.W_PRO_LANCE: case Piece.W_PRO_KNIGHT: case Piece.W_PRO_SILVER: return GoldEffect(Color.WHITE, sq); + + // 先後同じ移動特性の駒 + case Piece.B_BISHOP: case Piece.W_BISHOP: return BishopEffect(sq, occ); + case Piece.B_ROOK: case Piece.W_ROOK: return RookEffect(sq, occ); + case Piece.B_HORSE: case Piece.W_HORSE: return HorseEffect(sq, occ); + case Piece.B_DRAGON: case Piece.W_DRAGON: return DragonEffect(sq, occ); + case Piece.B_KING: case Piece.W_KING: return KingEffect(sq); + case Piece.B_QUEEN: case Piece.W_QUEEN: return HorseEffect(sq, occ) | DragonEffect(sq, occ); + case Piece.NO_PIECE: case Piece.WHITE: return ZERO_BB; // これも入れておかないと初期化が面倒になる。 + + default: /*UNREACHABLE;*/ return ALL_BB; + } + } + + /// + /// 敵陣を表現するBitboard。 + /// + private static Bitboard[] EnemyFieldBB; // = new Bitboard[(int)Color.NB]{ RANK1_BB | RANK2_BB | RANK3_BB, RANK7_BB | RANK8_BB | RANK9_BB }; + + public static Bitboard EnemyField(Color c) + { + return EnemyFieldBB[(int)c]; + } + + // ------------------------------------------------------------------------- + // 以下、private methods / tables + // ------------------------------------------------------------------------- + + /// + /// p[0]とp[1]をbitwise orしたものを返す。toU()相当。 + /// + /// + private UInt64 Merge() { return p.p0 | p.p1; } + + // Haswellのpext()を呼び出す。occupied = occupied bitboard , mask = 利きの算出に絡む升が1のbitboard + // この関数で戻ってきた値をもとに利きテーブルを参照して、遠方駒の利きを得る。 + private static UInt64 OccupiedToIndex(Bitboard occupied, Bitboard mask) { return BitOp.PEXT64(occupied.Merge(), mask.Merge()); } + + /// + /// staticなテーブルの初期化 + /// 起動時にInitializerから一度だけ呼び出される。 + /// 普段は呼び出してはならない。 + /// + public static void Init() + { + ALL_BB = new Bitboard(0x7FFFFFFFFFFFFFFFUL, 0x3FFFFUL); + ZERO_BB = new Bitboard(0UL, 0UL); + + Bitboard FILE1_BB = new Bitboard((0x1ffUL) << (9 * 0), 0); + Bitboard FILE2_BB = new Bitboard((0x1ffUL) << (9 * 1), 0); + Bitboard FILE3_BB = new Bitboard((0x1ffUL) << (9 * 2), 0); + Bitboard FILE4_BB = new Bitboard((0x1ffUL) << (9 * 3), 0); + Bitboard FILE5_BB = new Bitboard((0x1ffUL) << (9 * 4), 0); + Bitboard FILE6_BB = new Bitboard((0x1ffUL) << (9 * 5), 0); + Bitboard FILE7_BB = new Bitboard((0x1ffUL) << (9 * 6), 0); + Bitboard FILE8_BB = new Bitboard(0, 0x1ffUL << (9 * 0)); + Bitboard FILE9_BB = new Bitboard(0, 0x1ffUL << (9 * 1)); + + FILE_BB = new Bitboard[(int)File.NB] + { FILE1_BB,FILE2_BB,FILE3_BB,FILE4_BB,FILE5_BB,FILE6_BB,FILE7_BB,FILE8_BB,FILE9_BB }; + + Bitboard RANK1_BB = new Bitboard((0x40201008040201UL) << 0, 0x201 << 0); + Bitboard RANK2_BB = new Bitboard((0x40201008040201UL) << 1, 0x201 << 1); + Bitboard RANK3_BB = new Bitboard((0x40201008040201UL) << 2, 0x201 << 2); + Bitboard RANK4_BB = new Bitboard((0x40201008040201UL) << 3, 0x201 << 3); + Bitboard RANK5_BB = new Bitboard((0x40201008040201UL) << 4, 0x201 << 4); + Bitboard RANK6_BB = new Bitboard((0x40201008040201UL) << 5, 0x201 << 5); + Bitboard RANK7_BB = new Bitboard((0x40201008040201UL) << 6, 0x201 << 6); + Bitboard RANK8_BB = new Bitboard((0x40201008040201UL) << 7, 0x201 << 7); + Bitboard RANK9_BB = new Bitboard((0x40201008040201UL) << 8, 0x201 << 8); + + RANK_BB = new Bitboard[(int)Rank.NB] + { RANK1_BB, RANK2_BB, RANK3_BB, RANK4_BB, RANK5_BB, RANK6_BB, RANK7_BB, RANK8_BB, RANK9_BB }; + + EnemyFieldBB = new Bitboard[(int)Color.NB]{ RANK1_BB | RANK2_BB | RANK3_BB, RANK7_BB | RANK8_BB | RANK9_BB }; + + SQUARE_BB = new Bitboard[(int)Square.NB_PLUS1]; + + ForwardRanksBB = new Bitboard[(int)Color.NB, (int)Rank.NB] + { + { ZERO_BB, RANK1_BB, RANK1_BB | RANK2_BB, RANK1_BB | RANK2_BB | RANK3_BB, RANK1_BB | RANK2_BB | RANK3_BB | RANK4_BB, + ~(RANK9_BB | RANK8_BB | RANK7_BB | RANK6_BB), ~(RANK9_BB | RANK8_BB | RANK7_BB), ~(RANK9_BB | RANK8_BB), ~RANK9_BB }, + { ~RANK1_BB, ~(RANK1_BB | RANK2_BB), ~(RANK1_BB | RANK2_BB | RANK3_BB), ~(RANK1_BB | RANK2_BB | RANK3_BB | RANK4_BB), + RANK9_BB | RANK8_BB | RANK7_BB | RANK6_BB, RANK9_BB | RANK8_BB | RANK7_BB, RANK9_BB | RANK8_BB, RANK9_BB, ZERO_BB } + }; + + // 2つの升のfileの差、rankの差のうち大きいほうの距離を返す。sq1,sq2のどちらかが盤外ならINT_MAXが返る。 + int dist(Square sq1, Square sq2) + { + return (!sq1.IsOk() || !sq2.IsOk()) ? int.MaxValue : + System.Math.Max(System.Math.Abs(sq1.ToFile() - sq2.ToFile()), System.Math.Abs(sq1.ToRank() - sq2.ToRank())); + } + + BetweenBB_ = new Bitboard[785]; + BetweenIndex = new UInt16 [(int)Square.NB_PLUS1,(int)Square.NB_PLUS1]; + + + // 1) SquareWithWallテーブルの初期化。 + + for (Square sq = Square.ZERO; sq < Square.NB; ++sq) + SquareWithWallExtensions.sqww_table[sq.ToInt()] = (SquareWithWall) + ((int)SquareWithWall.SQWW_11 + + sq.ToFile().ToInt() * (int)SquareWithWall.SQWW_L + + sq.ToRank().ToInt() * (int)SquareWithWall.SQWW_D); + + // 2) direct_tableの初期化 + + Util.direc_table = new Directions[(int)Square.NB_PLUS1 , (int)Square.NB_PLUS1]; + + for (var sq1 = Square.ZERO; sq1 < Square.NB; ++ sq1) + for (var dir = Direct.ZERO; dir < Direct.NB; ++dir) + { + // dirの方角に壁にぶつかる(盤外)まで延長していく。このとき、sq1から見てsq2のDirectionsは (1 << dir)である。 + var delta = (int)dir.ToDeltaWW(); + for (var sq2 = sq1.ToSqww() + delta; sq2.IsOk(); sq2 += delta) + Util.direc_table[(int)sq1,(int)sq2.ToSquare()] = dir.ToDirections(); + } + + // 3) Square型のsqの指す升が1であるBitboardがSquareBB。これをまず初期化する。 + + // SQUARE_BBは上記のRANK_BBとFILE_BBを用いて初期化すると楽。 + for (Square sq = Square.ZERO; sq < Square.NB; ++sq) + { + File f = sq.ToFile(); + Rank r = sq.ToRank(); + + // 筋と段が交差するところがSQUARE_BB + SQUARE_BB[sq.ToInt()] = FILE_BB[f.ToInt()] & RANK_BB[r.ToInt()]; + } + + // 4) 遠方利きのテーブルの初期化 + // thanks to Apery (Takuya Hiraoka) + + // 引数のindexをbits桁の2進数としてみなす。すなわちindex(0から2^bits-1)。 + // 与えられたmask(1の数がbitsだけある)に対して、1のbitのいくつかを(indexの値に従って)0にする。 + Bitboard indexToOccupied(int index, int bits, Bitboard mask) + { + var result = ZERO_BB; + for (int i = 0; i < bits; ++i) + { + Square sq = mask.Pop(); + if ((index & (1 << i)) != 0) + result ^= new Bitboard(sq); + } + return result; + } + + // Rook or Bishop の利きの範囲を調べて bitboard で返す。 + // occupied 障害物があるマスが 1 の bitboard + // n = 0 右上から左下 , n = 1 左上から右下 + Bitboard effectCalc(Square square, Bitboard occupied, int n) + { + Bitboard result = ZERO_BB; + + // 角の利きのrayと飛車の利きのray + + SquareWithWall[] deltaArray; + if (n == 0) + deltaArray = new SquareWithWall[2] + { SquareWithWall.SQWW_RU, SquareWithWall.SQWW_LD }; + else + deltaArray = new SquareWithWall[2] + { SquareWithWall.SQWW_RD, SquareWithWall.SQWW_LU }; + + foreach (var delta in deltaArray) + { + // 壁に当たるまでsqを利き方向に伸ばしていく + for (var sq = (SquareWithWall)(square.ToSqww().ToInt() + delta.ToInt()); sq.IsOk(); sq += delta.ToInt()) + { + result ^= sq.ToSquare(); // まだ障害物に当っていないのでここまでは利きが到達している + + if ((occupied & sq.ToSquare()).IsNotZero()) // sqの地点に障害物があればこのrayは終了。 + break; + } + } + return result; + } + + // pieceをsqにおいたときに利きを得るのに関係する升を返す + Bitboard calcBishopEffectMask(Square sq, int n) + { + Bitboard result; + result = ZERO_BB; + + // 外周は角の利きには関係ないのでそこは除外する。 + for (Rank r = Rank.RANK_2; r <= Rank.RANK_8; ++r) + for (File f = File.FILE_2; f <= File.FILE_8; ++f) + { + var dr = sq.ToRank() - r; + var df = sq.ToFile() - f; + // dr == dfとdr != dfとをnが0,1とで切り替える。 + if (System.Math.Abs(dr) == System.Math.Abs(df) + && ((((int)dr == (int)df) ? 1 : 0) ^ n) != 0) + result ^= Util.MakeSquare(f, r); + } + + // sqの地点は関係ないのでクリアしておく。 + result &= ~new Bitboard(sq); + + return result; + } + + // 角の利きテーブルの初期化 + for (int n = 0; n < 2; ++n) + { + int index = 0; + for (var sq = Square.ZERO; sq < Square.NB; ++sq) + { + // sqの升に対してテーブルのどこを見るかのindex + BishopEffectIndex[n, sq.ToInt()] = index; + + // sqの地点にpieceがあるときにその利きを得るのに関係する升を取得する + var mask = calcBishopEffectMask(sq, n); + BishopEffectMask[n, sq.ToInt()] = mask; + + // p[0]とp[1]が被覆していると正しく計算できないのでNG。 + // Bitboardのレイアウト的に、正しく計算できるかのテスト。 + // 縦型Bitboardであるならp[0]のbit63を余らせるようにしておく必要がある。 + //ASSERT_LV3(!(mask.cross_over())); + + // sqの升用に何bit情報を拾ってくるのか + int bits = mask.PopCount(); + + // 参照するoccupied bitboardのbit数と、そのbitの取りうる状態分だけ.. + int num = 1 << bits; + + for (int i = 0; i < num; ++i) + { + Bitboard occupied = indexToOccupied(i, bits, mask); + // 初期化するテーブル + BishopEffectBB[n, index + (int)OccupiedToIndex(occupied & mask, mask)] = effectCalc(sq, occupied, n); + } + index += num; + } + + // 盤外(SQ_NB)に駒を配置したときに利きがZERO_BBとなるときのための処理 + BishopEffectIndex[n, (int)Square.NB] = index; + + // 何番まで使ったか出力してみる。(確保する配列をこのサイズに収めたいので) + // cout << index << endl; + } + + // 5. 飛車の縦方向の利きテーブルの初期化 + // ここでは飛車の利きを使わずに初期化しないといけない。 + + for (Rank rank = Rank.RANK_1; rank <= Rank.RANK_9; ++rank) + { + // sq = SQ_11 , SQ_12 , ... , SQ_19 + Square sq = Util.MakeSquare(File.FILE_1, rank); + + const int num1s = 7; + for (int i = 0; i < (1 << num1s); ++i) + { + // iはsqに駒をおいたときに、その筋の2段~8段目の升がemptyかどうかを表現する値なので + // 1ビットシフトして、1~9段目の升を表現するようにする。 + int ii = i << 1; + Bitboard bb = ZERO_BB; + for (int r = sq.ToRank().ToInt() - 1; r >= (int)Rank.RANK_1; --r) + { + bb |= Util.MakeSquare(sq.ToFile(), (Rank)r); + if ((ii & (1 << r)) != 0) + break; + } + for (int r = sq.ToRank().ToInt() + 1; r <= (int)Rank.RANK_9; ++r) + { + bb |= Util.MakeSquare(sq.ToFile(), (Rank)r); + if ((ii & (1 << r)) != 0) + break; + } + RookFileEffectBB[(int)rank, i] = bb.p.p0; + // RookEffectFile[RANK_NB][x] には値を代入していないがC++の規約によりゼロ初期化されている。 + } + } + + // 飛車の横の利き + for (File file = File.FILE_1; file <= File.FILE_9; ++file) + { + // sq = SQ_11 , SQ_21 , ... , SQ_NBまで + Square sq = Util.MakeSquare(file, Rank.RANK_1); + + const int num1s = 7; + for (int i = 0; i < (1 << num1s); ++i) + { + int ii = i << 1; + Bitboard bb = ZERO_BB; + for (int f = (int)sq.ToFile() - 1; f >= (int)File.FILE_1; --f) + { + bb |= Util.MakeSquare((File)f, sq.ToRank()); + if ((ii & (1 << f)) != 0) + break; + } + for (int f = (int)sq.ToFile() + 1; f <= (int)File.FILE_9; ++f) + { + bb |= Util.MakeSquare((File)f, sq.ToRank()); + if ((ii & (1 << f)) != 0) + break; + } + + RookRankEffectBB[(int)file, i] = bb; + // RookRankEffect[FILE_NB][x] には値を代入していないがC++の規約によりゼロ初期化されている。 + } + } + + + // 6. 近接駒(+盤上の利きを考慮しない駒)のテーブルの初期化。 + // 上で初期化した、香・馬・飛の利きを用いる。 + + foreach (var sq in All.Squares()) + { + // 玉は長さ1の角と飛車の利きを合成する + KingEffectBB[(int)sq] = BishopEffect(sq, ALL_BB) | RookEffect(sq, ALL_BB); + } + + foreach (var c in All.Colors()) + foreach (var sq in All.Squares()) + // 障害物がないときの香の利き + // これを最初に初期化しないとlanceEffect()が使えない。 + LanceStepEffectBB[(int)sq, (int)c] = RookFileEffect(sq, ZERO_BB) & ForwardRanks(c, sq.ToRank()); + + foreach (var c in All.Colors()) + foreach (var sq in All.Squares()) + { + // 歩は長さ1の香の利きとして定義できる + PawnEffectBB[(int)sq,(int)c] = LanceEffect(c, sq, ALL_BB); + + // 桂の利きは、歩の利きの地点に長さ1の角の利きを作って、前方のみ残す。 + Bitboard tmp = ZERO_BB; + Bitboard pawn = LanceEffect(c, sq, ALL_BB); + if (pawn.IsNotZero()) + { + Square sq2 = pawn.Pop(); + Bitboard pawn2 = LanceEffect(c, sq2, ALL_BB); // さらに1つ前 + if (pawn2.IsNotZero()) + tmp = BishopEffect(sq2, ALL_BB) & RANK_BB[(int)pawn2.Pop().ToRank()]; + } + KnightEffectBB[(int)sq,(int)c] = tmp; + + // 銀は長さ1の角の利きと長さ1の香の利きの合成として定義できる。 + SilverEffectBB[(int)sq,(int)c] = LanceEffect(c, sq, ALL_BB) | BishopEffect(sq, ALL_BB); + + // 金は長さ1の角と飛車の利き。ただし、角のほうは相手側の歩の行き先の段でmaskしてしまう。 + Bitboard e_pawn = LanceEffect(c.Not() , sq, ALL_BB); + Bitboard mask = ZERO_BB; + if (e_pawn.IsNotZero()) + mask = RANK_BB[(int)e_pawn.Pop().ToRank()]; + GoldEffectBB[(int)sq,(int)c] = (BishopEffect(sq, ALL_BB) & ~mask) | RookEffect(sq, ALL_BB); + + // 障害物がないときの角と飛車の利き + BishopStepEffectBB[(int)sq] = BishopEffect(sq, ZERO_BB); + RookStepEffectBB[(int)sq] = RookEffect(sq, ZERO_BB); + } + + +#if false + // 7) 二歩用のテーブル初期化 + + for (int i = 0; i < 0x80; ++i) + { + Bitboard b = ZERO_BB; + for (int k = 0; k < 7; ++k) + if ((i & (1 << k)) == 0) + b |= FILE_BB[k]; + + PAWN_DROP_MASK_BB[i].p[0] = b.p[0]; // 1~7筋 + } + for (int i = 0; i < 0x4; ++i) + { + Bitboard b = ZERO_BB; + for (int k = 0; k < 2; ++k) + if ((i & (1 << k)) == 0) + b |= FILE_BB[k+7]; + + PAWN_DROP_MASK_BB[i].p[1] = b.p[1]; // 8,9筋 + } +#endif + + // 8) BetweenBB , LineBBの初期化 + { + UInt16 between_index = 1; + // BetweenBB[0] == ZERO_BBであることを保証する。 + + foreach (var s1 in All.Squares()) + foreach (var s2 in All.Squares()) + { + // 十字方向か、斜め方向かだけを判定して、例えば十字方向なら + // rookEffect(sq1,Bitboard(s2)) & rookEffect(sq2,Bitboard(s1)) + // のように初期化したほうが明快なコードだが、この初期化をそこに依存したくないので愚直にやる。 + + // これについてはあとで設定する。 + if (s1 >= s2) + continue; + + // 方角を用いるテーブルの初期化 + if (Util.DirectionsOf(s1, s2) != Directions.ZERO) + { + Bitboard bb = ZERO_BB; + // 間に挟まれた升を1に + int delta = (s2 - s1) / dist(s1, s2); + for (Square s = s1 + delta; s != s2; s += delta) + bb |= s; + + // ZERO_BBなら、このindexとしては0を指しておけば良いので書き換える必要ない。 + if (bb.IsZero()) + continue; + + BetweenIndex[(int)s1, (int)s2] = between_index; + BetweenBB_[between_index++] = bb; + } + } + + // ASSERT_LV1(between_index == 785); + + // 対称性を考慮して、さらにシュリンクする。 + foreach (var s1 in All.Squares()) + foreach (var s2 in All.Squares()) + if (s1 > s2) + BetweenIndex[(int)s1, (int)s2] = BetweenIndex[(int)s2, (int)s1]; + + + LineBB_ = new Bitboard[(int)Square.NB, 4]; + + for (var s1 = Square.ZERO; s1 < Square.NB; ++s1) + for (int d = 0; d < 4; ++d) + { + // BishopEffect0 , RookRankEffect , BishopEffect1 , RookFileEffectを用いて初期化したほうが + // 明快なコードだが、この初期化をそこに依存したくないので愚直にやる。 + + Square[] deltas = new Square[] { Square.SQ_RU, Square.SQ_R, Square.SQ_RD, Square.SQ_U }; + int delta = (int)deltas[d]; + Bitboard bb = new Bitboard(s1); + + // 壁に当たるまでs1から-delta方向に延長 + for (Square s = s1; dist(s, s - delta) <= 1; s -= delta) bb |= (s - delta); + + // 壁に当たるまでs1から+delta方向に延長 + for (Square s = s1; dist(s, s + delta) <= 1; s += delta) bb |= (s + delta); + + LineBB_[(int)s1, d] = bb; + } + } + + } + + /// + /// すべてのSquareが1であるBitboard + /// + private static Bitboard ZERO_BB; + + /// + /// すべてのSquareが1であるBitboard + /// + private static Bitboard ALL_BB; + + /// + /// 筋を表現するBitboard + /// + private static Bitboard[] FILE_BB; + + /// + /// 段を表現するBitboard + /// + private static Bitboard[] RANK_BB; + + /// + /// Bitboard(Square)で用いるテーブル + /// 配列のサイズはSquare.NB_PLUS1 + /// + private static Bitboard[] SQUARE_BB; + + + // ForwardRanksBBの定義) + // c側の香の利き = 飛車の利き & ForwardRanksBB[c][rank_of(sq)] + // + // すなわち、 + // color == BLACKのとき、n段目よりWHITE側(1からn-1段目)を表現するBitboard。 + // color == WHITEのとき、n段目よりBLACK側(n+1から9段目)を表現するBitboard。 + // このアイデアはAperyのもの。 + private static Bitboard[,] ForwardRanksBB; // = new Bitboard[(int)Color.NB, (int)Rank.NB] + + /// + /// 玉、金、銀、桂、歩の利き + /// + private static Bitboard[] KingEffectBB = new Bitboard[(int)Square.NB_PLUS1]; + private static Bitboard[,] GoldEffectBB = new Bitboard[(int)Square.NB_PLUS1,(int)Color.NB]; + private static Bitboard[,] SilverEffectBB = new Bitboard[(int)Square.NB_PLUS1,(int)Color.NB]; + private static Bitboard[,] KnightEffectBB = new Bitboard[(int)Square.NB_PLUS1,(int)Color.NB]; + private static Bitboard[,] PawnEffectBB = new Bitboard[(int)Square.NB_PLUS1,(int)Color.NB]; + + // 盤上の駒をないものとして扱う、遠方駒の利き。香、角、飛 + private static Bitboard[,] LanceStepEffectBB = new Bitboard[(int)Square.NB_PLUS1,(int)Color.NB]; + private static Bitboard[] BishopStepEffectBB = new Bitboard[(int)Square.NB_PLUS1]; + private static Bitboard[] RookStepEffectBB = new Bitboard[(int)Square.NB_PLUS1]; + + // 角の利き + private static Bitboard[,] BishopEffectBB = new Bitboard[2,1856+1]; + private static Bitboard[,] BishopEffectMask = new Bitboard[2,(int)Square.NB_PLUS1]; + private static int[,] BishopEffectIndex = new int[2,(int)Square.NB_PLUS1]; + + // 飛車の縦、横の利き + + // 飛車の縦方向の利きを求めるときに、指定した升sqの属するfileのbitをshiftし、 + // index を求める為に使用する。(from Apery) + private static Byte[] Slide = new Byte[(int)Square.NB_PLUS1] + { + 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , + 10, 10, 10, 10, 10, 10, 10, 10, 10, + 19, 19, 19, 19, 19, 19, 19, 19, 19, + 28, 28, 28, 28, 28, 28, 28, 28, 28, + 37, 37, 37, 37, 37, 37, 37, 37, 37, + 46, 46, 46, 46, 46, 46, 46, 46, 46, + 55, 55, 55, 55, 55, 55, 55, 55, 55, + 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , + 10, 10, 10, 10, 10, 10, 10, 10, 10, + 0 , // SQ_NB用 + }; + + private static UInt64[,] RookFileEffectBB = new UInt64[(int)Rank.NB + 1,128]; + private static Bitboard[,] RookRankEffectBB = new Bitboard[(int)File.NB + 1,128]; + + } +} diff --git a/Assets/Plugins/MyShogi/Core/Bitboard.cs.meta b/Assets/Plugins/MyShogi/Core/Bitboard.cs.meta new file mode 100644 index 0000000..c721f83 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Bitboard.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 950b8aae854f4420aa57134326c28c17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/BoardType.cs b/Assets/Plugins/MyShogi/Core/BoardType.cs new file mode 100644 index 0000000..56c3195 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/BoardType.cs @@ -0,0 +1,253 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 平手、二枚落ちなど盤面タイプを表現する + /// + public enum BoardType : Int32 + { + /// + /// 平手 + /// + // [LabelDescription(Label = "平手")] + NoHandicap, + + /// + /// 香落ち + /// + //[LabelDescription(Label = "香落ち")] + HandicapKyo, + + /// + /// 右香落ち + /// + //[LabelDescription(Label = "右香落ち")] + HandicapRightKyo, + + /// + /// 角落ち + /// + //[LabelDescription(Label = "角落ち")] + HandicapKaku, + + /// + /// 飛車落ち + /// + //[LabelDescription(Label = "飛車落ち")] + HandicapHisya, + + /// + /// 飛香落ち + /// + //[LabelDescription(Label = "飛香落ち")] + HandicapHisyaKyo, + + /// + /// 二枚落ち + /// + //[LabelDescription(Label = "二枚落ち")] + Handicap2, + + /// + /// 三枚落ち + /// + //[LabelDescription(Label = "三枚落ち")] + Handicap3, + + /// + /// 四枚落ち + /// + //[LabelDescription(Label = "四枚落ち")] + Handicap4, + + /// + /// 五枚落ち + /// + //[LabelDescription(Label = "五枚落ち")] + Handicap5, + + /// + /// 左五枚落ち + /// + //[LabelDescription(Label = "左五枚落ち")] + HandicapLeft5, + + /// + /// 六枚落ち + /// + //[LabelDescription(Label = "六枚落ち")] + Handicap6, + + /// + /// 八枚落ち + /// + //[LabelDescription(Label = "八枚落ち")] + Handicap8, + + /// + /// 十枚落ち + /// + //[LabelDescription(Label = "十枚落ち")] + Handicap10, + + /// + /// 歩三枚 + /// + //[LabelDescription(Label = "歩三枚")] + HandicapPawn3, + + /// + /// 詰将棋用の局面 + /// (玉は後手玉が51にいて、あとの手駒はすべて後手側に) + /// + Mate1, + + /// + /// 双玉詰将棋用の局面 + /// (玉が51,59にいて、あとの手駒はすべて後手側に) + /// + Mate2, + + /// + /// 双玉で玉以外すべて駒箱に + /// (玉が51,59にいて、あとの手駒はすべて駒箱に) + /// + Mate3, + + /// + /// それ以外の局面図 + /// + //[LabelDescription(Label = "任意局面")] + Others, + + /// + /// 現在の(画面上の)局面図 + /// + //[LabelDescription(Label = "現在の局面")] + Current, + + // 終わり + NB, + ZERO = 0, + } + + + /// + /// BoardTypeに対するextension methods + /// + public static class BoardTypeExtensions + { + /// + /// BoardType型が正当な値の範囲であるかをテストする + /// + /// + /// + public static bool IsOk(this BoardType boardType) + { + return BoardType.ZERO <= boardType && boardType < BoardType.NB; + } + + /// + /// BoardType型がBoardType.ToSfen()でsfen化できる範囲にあるかをテストする。 + /// + /// + /// + public static bool IsSfenOk(this BoardType boardType) + { + return BoardType.ZERO <= boardType && boardType < BoardType.Others; + } + + /// + /// BoardTypeに対応するsfen文字列を得る。 + /// BoardType.OthersとBoardType.Currentに対してはnullが返る。 + /// + /// + /// + public static string ToSfen(this BoardType boardType) + { + // 範囲外 + if (!boardType.IsSfenOk()) + return null; + + return SFENS_OF_BOARDTYPE[(int)boardType]; + } + + /// + /// BoardType型をInt32に変換する + /// + /// + /// + public static Int32 ToInt(this BoardType boardType) + { + return (Int32)boardType; + } + +#if false + /// + /// 駒落ちであるかを判定して返す。 + /// → この設計よくない。BoardType.Othersが駒落ちの局面である可能性がある。 + ///   position.Handicappedを用いるべき。 + /// + /// + /// + public static bool IsHandicapped(this BoardType boardType) + { + return !(boardType == BoardType.NoHandicap || boardType == BoardType.Current); + } +#endif + + public static string Pretty(this BoardType boardType) + { + if (PRETTY_TABLE.Length <= (int)boardType) + return "任意局面"; + return PRETTY_TABLE[(int)boardType]; + } + + /// + /// 平手、駒落ちなどのsfen文字列をひとまとめにした配列。BoardTypeのenumと対応する。 + /// + public static readonly string[] SFENS_OF_BOARDTYPE = + { + Sfens.HIRATE , Sfens.HANDICAP_KYO , Sfens.HANDICAP_RIGHT_KYO , Sfens.HANDICAP_KAKU , + Sfens.HANDICAP_HISYA , Sfens.HANDICAP_HISYA_KYO , + Sfens.HANDICAP_2 , Sfens.HANDICAP_3 , Sfens.HANDICAP_4 , Sfens.HANDICAP_5 , Sfens.HANDICAP_LEFT_5 , + Sfens.HANDICAP_6 , Sfens.HANDICAP_8 , Sfens.HANDICAP_10 , Sfens.HANDICAP_PAWN3 , Sfens.MATE_1 , Sfens.MATE_2, Sfens.MATE_3, + }; + + private static readonly string[] PRETTY_TABLE = + { + "平手","香落ち","右香落ち","角落ち","飛車落ち","飛香落ち","二枚落ち","三枚落ち","四枚落ち","五枚落ち", + "左五枚落ち","六枚落ち","八枚落ち","十枚落ち","歩三枚" + }; + } + + public static partial class Util + { + /// + /// 文字列化されたBoardTypeから、元のBoardTypeを復元する。 + /// + /// + /// + public static BoardType FromBoardTypeString(string s) + { + // あまり使いたくないが、enumからreflectionで取り出している。 + return (BoardType)Enum.Parse(typeof(BoardType), s); + } + + /// + /// sfen文字列がどのBoardTypeであるか判定する。 + /// 判定できなかったときは、BoardType.Others + /// + /// + /// + public static BoardType BoardTypeFromSfen(string sfen) + { + for (var boardType = BoardType.ZERO; boardType < BoardType.Others; ++boardType) + if (boardType.ToSfen() == sfen) + return boardType; + + return BoardType.Others; + } + } +} diff --git a/Assets/Plugins/MyShogi/Core/BoardType.cs.meta b/Assets/Plugins/MyShogi/Core/BoardType.cs.meta new file mode 100644 index 0000000..6e1332c --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/BoardType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5963baf06c47f492eb5cc512f699250c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Color.cs b/Assets/Plugins/MyShogi/Core/Color.cs new file mode 100644 index 0000000..e68b05c --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Color.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 先手・後手という手番を表す定数 + /// + public enum Color : Int32 + { + BLACK = 0, + WHITE = 1, + + ZERO = 0, + NB = 2, + } + + /// + /// Colorに関するextension methodsを書いておくクラス + /// + public static class ColorExtensions + { + /// + /// 正常な値であるかを検査する。assertで使う用。 + /// + /// + /// + public static bool IsOk(this Color c) + { + return Color.ZERO <= c && c < Color.NB; + } + + /// + /// 日本語文字列に変換する。(USI文字列ではない) + /// + /// + /// + public static string Pretty(this Color c) + { + return (c == Color.BLACK) ? "先手" : "後手"; + } + + /// + /// USI形式で手番を出力する + /// + /// + /// + public static string ToUsi(this Color c) + { + return (c == Color.BLACK) ? "b" : "w"; + } + + /// + /// Int32型に変換する。 + /// + /// + /// + public static Int32 ToInt(this Color c) + { + return (Int32)c; + } + + /// + /// 手番を相手の手番に変更する。 + /// + /// + /// + public static void Flip(ref this Color color) + { + color = (Color)(color.ToInt() ^ 1); + } + + /// + /// 先手なら後手、後手なら先手にする否定演算子 + /// + /// + /// + public static Color Not(this Color color) + { + return (Color)(color.ToInt() ^ 1); + } + } + + public static partial class Util + { + /// + /// USIの手番文字列からColorに変換する。 + /// 変換できないときはColor.NBが返る。 + /// + /// + /// + public static Color FromUsiColor(char c) + { + if (c == 'b') + return Color.BLACK; + if (c == 'w') + return Color.WHITE; + + return Color.NB; + } + } + +} diff --git a/Assets/Plugins/MyShogi/Core/Color.cs.meta b/Assets/Plugins/MyShogi/Core/Color.cs.meta new file mode 100644 index 0000000..7fbbdb5 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Color.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ed2572ef4f5f640efb4d0a9a9c2cb701 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Direct.cs b/Assets/Plugins/MyShogi/Core/Direct.cs new file mode 100644 index 0000000..e033bd3 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Direct.cs @@ -0,0 +1,99 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 方角を表す。遠方駒の利きや、玉から見た方角を表すのに用いる。 + /// bit0..右上、bit1..右、bit2..右下、bit3..上、bit4..下、bit5..左上、bit6..左、bit7..左下 + /// 同時に複数のbitが1であることがありうる。 + /// + public enum Directions : Byte + { + ZERO = 0, RU = 1, R = 2, RD = 4, + U = 8, D = 16, LU = 32, L = 64, LD = 128, + CROSS = U | D | R | L, + DIAG = RU | RD | LU | LD, + } + + /// + /// Directionsをpopしたもの。複数の方角を同時に表すことはない。 + /// おまけで桂馬の移動も追加しておく。 + /// + public enum Direct : Byte + { + RU, R, RD, U, D, LU, L, LD, + NB, ZERO = 0, RUU = 8, LUU, RDD, LDD, NB_PLUS4 + }; + + /// + /// Direct,Directionsに関するextension methods + /// + public static class DirectExtensions { + /// + /// DirectからDirectionsへの逆変換 + /// + /// + /// + public static Directions ToDirections(this Direct d) + { + return (Directions)(1 << (int)d); + } + + public static SquareWithWall ToDeltaWW(this Direct d) + { + /* ASSERT_LV3(is_ok(d)); */ + return Util.DirectToDeltaWW_[(int)d]; + } + } + + public partial class Util + { + /// + /// DirectionsOf()で使われるテーブル。 + /// Bitboard.Init()で初期化される。 + /// + public static Directions[,] direc_table; // = new Directions[(int)Square.NB_PLUS1 , (int)Square.NB_PLUS1]; + + /// + /// DirectをSquareWithWall型の差分値で表現したもの。 + /// ToDeltaWW(this Direct d)で用いる。 + /// Bitboard.Init()で初期化される。 + /// + public static SquareWithWall[] DirectToDeltaWW_ = + { SquareWithWall.SQWW_RU , SquareWithWall.SQWW_R , SquareWithWall.SQWW_RD , SquareWithWall.SQWW_U, + SquareWithWall.SQWW_D , SquareWithWall.SQWW_LU , SquareWithWall.SQWW_L , SquareWithWall.SQWW_LD, }; + + /// + /// sq1にとってsq2がどのdirectionにあるか。 + /// "Direction"ではなく"Directions"を返したほうが、縦横十字方向や、斜め方向の位置関係にある場合、 + /// DIRECTIONS_CROSSやDIRECTIONS_DIAGのような定数が使えて便利。 + /// + /// + /// + /// + public static Directions DirectionsOf(Square sq1, Square sq2) { return direc_table[(int)sq1,(int)sq2]; } + + /// + /// 与えられた3升が縦横斜めの1直線上にあるか。駒を移動させたときに開き王手になるかどうかを判定するのに使う。 + /// 例) 王がsq1, pinされている駒がsq2にあるときに、pinされている駒をsq3に移動させたときにaligned(sq1,sq2,sq3)であれば、 + /// pinされている方向に沿った移動なので開き王手にはならないと判定できる。 + /// ただし玉はsq3として、sq1,sq2は同じ側にいるものとする。(玉を挟んでの一直線は一直線とはみなさない) + /// + /// + /// + /// + /// + public static bool IsAligned(Square sq1, Square sq2, Square sq3/* is ksq */) + { + var d1 = DirectionsOf(sq1, sq3); + return d1!=Directions.ZERO ? d1 == DirectionsOf(sq2, sq3) : false; + } + + } + +#if false + + // Directionsに相当するものを引数に渡して1つ方角を取り出す。 + inline Direct pop_directions(Directions& d) { return (Direct)pop_lsb(d); } +#endif +} diff --git a/Assets/Plugins/MyShogi/Core/Direct.cs.meta b/Assets/Plugins/MyShogi/Core/Direct.cs.meta new file mode 100644 index 0000000..656aac7 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Direct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a7778d09f4284905b8bb8cebce8c1cb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/EnteringKingRule.cs b/Assets/Plugins/MyShogi/Core/EnteringKingRule.cs new file mode 100644 index 0000000..8218450 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/EnteringKingRule.cs @@ -0,0 +1,13 @@ +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 入玉ルール + /// + public enum EnteringKingRule + { + NONE , // 入玉ルールなし + POINT24 , // 24点法(31点以上で宣言勝ち) + POINT27 , // 27点法 == CSAルール + TRY_RULE , // トライルール(敵陣の(先手から見て)51の升に自玉が到達して、王手がかかっていなければ勝ち) + } +} diff --git a/Assets/Plugins/MyShogi/Core/EnteringKingRule.cs.meta b/Assets/Plugins/MyShogi/Core/EnteringKingRule.cs.meta new file mode 100644 index 0000000..0fc7a30 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/EnteringKingRule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 35931dfed6af84c03a56a921a29772c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/EvalValue.cs b/Assets/Plugins/MyShogi/Core/EvalValue.cs new file mode 100644 index 0000000..4968859 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/EvalValue.cs @@ -0,0 +1,188 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 評価値として用いる値。 + /// 探索部を用意していないので、表示に使う程度であるが…。 + /// + /// MatePlus = -MatedMinus + /// Mate = -Mated + /// + /// のように符号を反転させると先後入替えた側から見た評価値になることが保証されている。 + /// + public enum EvalValue : Int32 + { + // "score mate+"を表現する手数不明の詰み。 + MatePlus = Int32.MaxValue - 1, + + // 現局面で(敵玉が)詰んでいる時の評価値 + // N手詰めのときは、(Mate - N) + Mate = Int32.MaxValue - 2, + + Zero = 0, + + // 現局面で(自玉が)詰んでいる時の評価値 + // N手で詰まされるときは、(Mate + N) + Mated = Int32.MinValue + 3, + + // "score mate-"を表現する手数不明の詰まされ。 + MatedMinus = Int32.MinValue + 2, + + // この局面の評価値が存在しないことを意味する値 + // 形勢グラフなどには、この値のところは描画してはならない。 + NoValue = Int32.MinValue + 1, + + // この値は使わない。 + Unknown = Int32.MinValue, + } + + /// + /// ある評価値が、探索のupperbound(上界) , lowerbound(下界)の値であるかなどを表現する。 + /// + public enum ScoreBound + { + /// + /// 上界(真の評価値はこれ以下の値) + /// + Upper , + + /// + /// 下界(真の評価値はこれ以上の値) + /// + Lower , + + /// + /// ぴったり + /// + Exact , + } + + /// + /// EvalValueの値とScoreBoundの値を一纏めにした構造体 + /// + public class EvalValueEx + { + public EvalValueEx(EvalValue eval , ScoreBound bound) + { + Eval = eval; + Bound = bound; + } + + public EvalValue Eval; + public ScoreBound Bound; + + /// + /// 評価値を反転する。 + /// + public EvalValueEx negate() + { + EvalValue eval = (Eval != EvalValue.Unknown && Eval != EvalValue.NoValue) ? (EvalValue)(-(Int32)Eval) : Eval; + ScoreBound bound; + switch (Bound) + { + case ScoreBound.Upper: bound = ScoreBound.Lower; break; + case ScoreBound.Lower: bound = ScoreBound.Upper; break; + default: bound = Bound; break; + } + return new EvalValueEx(eval, bound); + } + } + + public static class EvalValueExtensions + { + /// + /// 評価値の値をわかりやすく文字列化する。 + /// + /// + /// + public static string Pretty(this EvalValue value) + { + if (value.IsSpecialValue()) + { + switch(value) + { + case EvalValue.Unknown : return "不明"; + case EvalValue.MatePlus : return "MATE 手数不明"; + case EvalValue.MatedMinus: return "MATED 手数不明"; + case EvalValue.NoValue : return ""; // これ表示するとおかしくなるので表示なしにしとく。 + } + + // int にキャストしないと 0手 が Zero手 と出力される + if (value > 0) + return $"MATE {(int)(EvalValue.Mate - value)}手"; + if (value < 0) + return $"MATED {(int)(value - EvalValue.Mated)}手"; + } + + // 0以外は符号付きで出力 + return ((int)value).ToString("+0;-0;0"); + } + + /// + /// 形勢判断の文字列に変換する + /// + /// + /// 駒落ちであるか + /// + public static string ToEvalJudgement(this EvalValue value , bool handicapped) + { + var black = handicapped ? "下手" : "先手"; + var white = handicapped ? "上手" : "後手"; + + if (value.IsSpecialValue()) + { + if (value > 0) + return $"{black}勝ち"; + else + return $"{white}勝ち"; + } + else + { + var v = (int)value; + if (v > 0) + return + (v >= 2000) ? $"{black}勝勢" : + (v >= 800) ? $"{black}優勢" : + (v >= 300) ? $"{black}有利" : + "形勢互角"; + else + return + (v <= -2000) ? $"{white}勝勢" : + (v <= -800) ? $"{white}優勢" : + (v <= -300) ? $"{white}有利" : + "形勢互角"; + } + } + + /// + /// EvalValueが通常の評価値の値ではなく、特殊な意味を持つ値であるかを判定する。 + /// ※ 通常の評価値の値は -1000000 ~ +1000000までであるものとする。 + /// + /// + /// + public static bool IsSpecialValue(this EvalValue value) + { + return !(-1000000 <= (int)value && (int)value <= +1000000); + } + + /// + /// ScoreBoundの値を文字列で表現する。 + /// + /// Chessの記法に倣う。 + /// + /// + /// + public static string Pretty(this ScoreBound bound) + { + switch(bound) + { + case ScoreBound.Exact: return ""; + case ScoreBound.Lower: return "++"; // 真の値は、この値以上のはずなので + case ScoreBound.Upper: return "--"; // 真の値は、この値以下のはずなので + } + return "??"; + } + + } +} diff --git a/Assets/Plugins/MyShogi/Core/EvalValue.cs.meta b/Assets/Plugins/MyShogi/Core/EvalValue.cs.meta new file mode 100644 index 0000000..1f586ee --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/EvalValue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b697938124ea54eca95079c6ebcebe0e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Exception.cs b/Assets/Plugins/MyShogi/Core/Exception.cs new file mode 100644 index 0000000..66e3ca9 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Exception.cs @@ -0,0 +1,35 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// sfen形式のデータの読み込み時に発生する例外 + /// + public class SfenException : Exception + { + public SfenException(){ } + + public SfenException(string msg) : base(msg) { } + + public SfenException(string message, Exception innerException) + : base(message, innerException) + { + } + } + + /// + /// PositionのDoMove()などで発生する例外 + /// + public class PositionException : Exception + { + public PositionException() { } + + public PositionException(string msg) : base(msg) { } + + public PositionException(string message, Exception innerException) + : base(message, innerException) + { + } + } + +} diff --git a/Assets/Plugins/MyShogi/Core/Exception.cs.meta b/Assets/Plugins/MyShogi/Core/Exception.cs.meta new file mode 100644 index 0000000..2f400b5 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Exception.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd6dae8b43fc94ded8ea800d42acceda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/File.cs b/Assets/Plugins/MyShogi/Core/File.cs new file mode 100644 index 0000000..106f73a --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/File.cs @@ -0,0 +1,84 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 将棋の「筋」(列)を表現する型 + /// 例) FILE_3なら3筋。 + /// + public enum File : Int32 + { + FILE_1, FILE_2, FILE_3, FILE_4, FILE_5, FILE_6, FILE_7, FILE_8, FILE_9, NB , ZERO = 0 + }; + + /// + /// Fileに関するextension methodsを書くクラス + /// + public static class FileExtensions + { + public static bool IsOk(this File f) + { + return File.ZERO <= f && f < File.NB; + } + + /// + /// Fileを綺麗に出力する(USI形式ではない) + /// 日本語文字での表示になる。例 → 8 + /// + /// + /// + public static string Pretty(this File f) + { + // C#では全角1文字が1つのcharなので注意。 + return "123456789".Substring((int)f.ToInt(),1); + } + + /// + /// USI文字列へ変換する。 + /// + /// + /// + public static string ToUsi(this File f) + { + return new string((char)((Int32)'1' + f.ToInt()),1); + } + + + /// + /// Int32型への変換子 + /// + /// + /// + public static Int32 ToInt(this File f) + { + return (Int32)f; + } + + /// + /// USIの指し手文字列などで筋を表す文字列をここで定義されたFileに変換する。 + /// + /// + /// + public static File ToFile(this char c) + { + return (File)(c - '1'); + } + } + + public static partial class Util + { + /// + /// 筋を表現するUSI文字列をFileに変換する + /// 変換できないときはFile.NBが返る。 + /// + /// + /// + public static File FromUsiFile(char c) + { + File f = (File)((int)c - (int)'1'); + if (!f.IsOk()) + f = File.NB; + return f; + } + } +} diff --git a/Assets/Plugins/MyShogi/Core/File.cs.meta b/Assets/Plugins/MyShogi/Core/File.cs.meta new file mode 100644 index 0000000..7e56aa3 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/File.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e9c125d5c6894d4285341bd6853405e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Hand.cs b/Assets/Plugins/MyShogi/Core/Hand.cs new file mode 100644 index 0000000..054ae93 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Hand.cs @@ -0,0 +1,195 @@ +using System; +using System.Diagnostics; +using System.Text; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 手駒を表現するenum + /// 歩の枚数を8bit、香、桂、銀、角、飛、金を4bitずつで持つ。 + /// こうすると16進数表示したときに綺麗に表示される。(なのはのアイデア) + /// + public enum Hand : Int32 + { + ZERO = 0, + ALL = 0x42244412, // 全駒持っている状態(使用していない駒を数える時などに用いる) + } + + + /// + /// Hand型に対するextension methods + /// + public static class HandExtensions + { + // 手駒の駒種 7枚 + private static readonly Piece[] PIECE_TYPE_ALL = + { + Piece.PAWN , Piece.LANCE , Piece.KNIGHT , Piece.SILVER , Piece.GOLD , Piece.BISHOP , Piece.ROOK, + }; + + /// + /// 手駒をUSI形式で出力する + /// colorの手番のほうの駒として出力する + /// + /// + /// + public static string ToUsi(this Hand hand , Color color) + { + var sb = new StringBuilder(); + + // 手駒の出力順はUSIプロトコルでは規定されていないが、 + // USI原案によると、飛、角、金、銀、桂、香、歩の順である。 + // sfen文字列を一意にしておかないと定跡データーをsfen文字列で書き出したときに + // 他のソフトで文字列が一致しなくて困るので、この順に倣うことにする。 + + for (int i= 0; i< 7; ++i) + { + Piece piece = PIECE_TYPE_ALL[6 - i]; + int c = hand.Count(piece); + + if (c == 0) + continue; + + // その種類の駒の枚数。1ならば出力を省略 + if (c != 1) + sb.Append(c.ToString()); + + sb.Append(Util.MakePiece(color, piece).ToUsi()); + } + return sb.ToString(); + } + + /// + /// 手駒を日本語形式で出力する。 + /// 例) "歩1 金3" + /// + /// + /// + public static string Pretty(this Hand hand) + { + var sb = new StringBuilder(); + foreach (var pr in PIECE_TYPE_ALL) + { + int c = hand.Count(pr); + // 0枚ではないなら出力。 + if (c != 0) + { + // 1枚なら枚数は出力しない。2枚以上なら枚数を後に出力 + sb.Append(pr.Pretty2()); + if (c != 1) + sb.Append(c.ToString()); + sb.Append(" "); + } + } + return sb.ToString(); + } + + /// + /// 手駒を日本語形式で出力する。持ち駒用。 + /// 例) "飛金二歩十" + /// + /// + /// + public static string Pretty2(this Hand hand) + { + var sb = new StringBuilder(); + Piece[] pieceType = + { + Piece.ROOK, Piece.BISHOP, Piece.GOLD, Piece.SILVER, Piece.KNIGHT, Piece.LANCE, Piece.PAWN + }; + foreach (var pr in pieceType) + { + int c = hand.Count(pr); + // 0枚ではないなら出力。 + if (c != 0) + { + // 1枚なら枚数は出力しない。2枚以上なら枚数を後に出力 + sb.Append(pr.Pretty2()); + // 18枚以下を想定した漢数字変換 + if (c >= 10) + { + sb.Append('十'); + } + if (c != 1) + { + c %= 10; + string[] nl = new string[] { "", "一", "二", "三", "四", "五", "六", "七", "八", "九" }; + sb.Append(nl[c]); + } + } + } + return sb.ToString(); + } + + /// + /// Int32型に変換する。 + /// + /// + /// + public static Int32 ToInt(this Hand hand) + { + return (Int32)hand; + } + + /// + /// 手駒のbit位置 + /// + private static readonly int[] PIECE_BITS = { 0, 0 /*歩*/, 8 /*香*/, 12 /*桂*/, 16 /*銀*/, 20 /*角*/, 24 /*飛*/ , 28 /*金*/ }; + + /// + /// その持ち駒を表現するのに必要なbit数のmask(例えば3bitなら2の3乗-1で7) + /// + private static readonly int[] PIECE_BIT_MASK = { 0, 31/*歩は5bit*/, 7/*香は3bit*/, 7/*桂*/, 7/*銀*/, 3/*角*/, 3/*飛*/, 7/*金*/ }; + + /// + /// 手駒pcの枚数を返す。 + /// + public static int Count(this Hand hand, Piece pr) + { + Debug.Assert(Piece.PAWN <= pr && pr < Piece.KING); + var p = (int)pr.ToInt(); + return ((int)hand.ToInt() >> PIECE_BITS[p]) & PIECE_BIT_MASK[p]; + } + + // Piece(歩,香,桂,銀,金,角,飛)を手駒に変換するテーブル + private static readonly Hand[] PIECE_TO_HAND = { + (Hand)0, + (Hand) (1 << PIECE_BITS[Piece.PAWN.ToInt()] ) /*歩*/, + (Hand) (1 << PIECE_BITS[Piece.LANCE.ToInt()] ) /*香*/, + (Hand) (1 << PIECE_BITS[Piece.KNIGHT.ToInt()]) /*桂*/, + (Hand) (1 << PIECE_BITS[Piece.SILVER.ToInt()]) /*銀*/, + (Hand) (1 << PIECE_BITS[Piece.BISHOP.ToInt()]) /*角*/, + (Hand) (1 << PIECE_BITS[Piece.ROOK.ToInt()] ) /*飛*/, + (Hand) (1 << PIECE_BITS[Piece.GOLD.ToInt()] ) /*金*/ + }; + + /// + /// 手駒にpcをc枚加える + /// + /// + /// + /// + /// + public static void Add(this ref Hand hand, Piece pr, int c = 1) + { + Debug.Assert(Piece.PAWN <= pr && pr < Piece.KING); + + hand = (Hand)(hand.ToInt() + (Int32)PIECE_TO_HAND[pr.ToInt()] * c); + } + + /// + /// 手駒からpcをc枚減ずる + /// + /// + /// + /// + public static void Sub(this ref Hand hand, Piece pr, int c = 1) + { + Debug.Assert(Piece.PAWN <= pr && pr < Piece.KING); + + hand = (Hand)(hand.ToInt() - (Int32)PIECE_TO_HAND[pr.ToInt()] * c); + } + + } + +} diff --git a/Assets/Plugins/MyShogi/Core/Hand.cs.meta b/Assets/Plugins/MyShogi/Core/Hand.cs.meta new file mode 100644 index 0000000..58bca0c --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Hand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bcc5855ad11a64dd2b5db2525a3c29cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/HashKey.cs b/Assets/Plugins/MyShogi/Core/HashKey.cs new file mode 100644 index 0000000..2d9da29 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/HashKey.cs @@ -0,0 +1,78 @@ +namespace MyShogi.Model.Shogi.Core +{ + /// + /// Positionクラスで同一局面の判定のために用いるHashKey + /// hash衝突を回避するため128bitで持つことにする。 + /// これで一局の将棋のなかでハッシュ衝突する確率は天文学的な確率のはず…。 + /// + public struct HASH_KEY + { + public UInt128 p; + + public HASH_KEY(UInt128 p_) + { + p = p_; + } + + public override bool Equals(object key) + { + return p.Equals(((HASH_KEY)key).p); + } + + public override int GetHashCode() + { + return p.GetHashCode(); + } + + public static bool operator ==(HASH_KEY lhs, HASH_KEY rhs) + { + return lhs.p == rhs.p; + } + + public static bool operator !=(HASH_KEY lhs, HASH_KEY rhs) + { + return lhs.p != rhs.p; + } + + /// + /// 16進数16桁×2で文字列化 + /// + /// + public string Pretty() + { + // 16進数16桁×2で表現 + return string.Format("{0,0:X16}:{1,0:X16}", p.p0, p.p1); + } + + public static HASH_KEY operator +(HASH_KEY c1, HASH_KEY c2) + { + return new HASH_KEY(c1.p + c2.p); + } + + public static HASH_KEY operator -(HASH_KEY c1, HASH_KEY c2) + { + return new HASH_KEY(c1.p - c2.p); + } + + public static HASH_KEY operator &(HASH_KEY c1, HASH_KEY c2) + { + return new HASH_KEY(c1.p & c2.p); + } + + public static HASH_KEY operator |(HASH_KEY c1, HASH_KEY c2) + { + return new HASH_KEY(c1.p | c2.p); + } + + public static HASH_KEY operator ^(HASH_KEY c1, HASH_KEY c2) + { + return new HASH_KEY(c1.p ^ c2.p); + } + + public static HASH_KEY operator *(HASH_KEY c1, int n) + { + return new HASH_KEY(c1.p * n); + } + + } +} diff --git a/Assets/Plugins/MyShogi/Core/HashKey.cs.meta b/Assets/Plugins/MyShogi/Core/HashKey.cs.meta new file mode 100644 index 0000000..a0b91a9 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/HashKey.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66e02d67e28814db1ae38c6ee38ca3a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Initializer.cs b/Assets/Plugins/MyShogi/Core/Initializer.cs new file mode 100644 index 0000000..5746a90 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Initializer.cs @@ -0,0 +1,15 @@ +namespace MyShogi.Model.Shogi.Core +{ + public static class Initializer + { + /// + /// 起動時の初期化一式 + /// static constructorで初期化したくないのでここでまとめて初期化する + /// + public static void Init() + { + Zobrist.Init(); + Bitboard.Init(); + } + } +} diff --git a/Assets/Plugins/MyShogi/Core/Initializer.cs.meta b/Assets/Plugins/MyShogi/Core/Initializer.cs.meta new file mode 100644 index 0000000..34c995e --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Initializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36bd11fc8cc584c40a378d5f91065fa6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Misc.cs b/Assets/Plugins/MyShogi/Core/Misc.cs new file mode 100644 index 0000000..19c3514 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Misc.cs @@ -0,0 +1,50 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 疑似乱数生成 + /// やねうら王で用いている疑似乱数と同一にしておく。 + /// + public class PRNG + { + public PRNG(UInt64 seed) { s = seed; } + + /// + /// 時刻などでseedを初期化する。 + /// + public PRNG() + { + // time値とか、thisとか色々加算しておく。 + s = (UInt64)DateTime.Now.ToBinary(); + } + + /// + /// 乱数を一つ取り出す。 + /// + /// + public UInt64 Rand() { return rand64(); } + + /// + /// 0からn-1までの乱数を返す。(一様分布ではないが現実的にはこれで十分) + /// + /// + /// + public UInt64 Rand(UInt64 n) { return Rand() % n; } + + /// + /// 内部で使用している乱数seedを返す。 + /// + /// + public UInt64 GetSeed() { return s; } + + private UInt64 s; + private UInt64 rand64() + { + s ^= s >> 12; + s ^= s << 25; + s ^= s >> 27; + return s * 2685821657736338717UL; + } + } +} diff --git a/Assets/Plugins/MyShogi/Core/Misc.cs.meta b/Assets/Plugins/MyShogi/Core/Misc.cs.meta new file mode 100644 index 0000000..a060c09 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Misc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d727ee89e9bff474e82b1372a995ed46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Move.cs b/Assets/Plugins/MyShogi/Core/Move.cs new file mode 100644 index 0000000..f26525a --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Move.cs @@ -0,0 +1,530 @@ +using System; +using System.Text; +using System.Collections.Generic; +using System.Diagnostics; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 指し手を表現するenum + /// 指し手 bit0..6 = 移動先のSquare、bit7..13 = 移動元のSquare(駒打ちのときは駒種)、bit14..駒打ちか、bit15..成りか + /// + public enum Move : UInt16 + { + NONE = 0, // 無効な移動 + + DROP = 1 << 14, // 駒打ちフラグ + PROMOTE = 1 << 15, // 駒成りフラグ + + // 将棋のある局面の合法手の最大数。593らしいが、保険をかけて少し大きめにしておく。 + MAX_MOVES = 600, + + // 以下は、やねうら王から変更して、USIの通常の指し手文字列から変換したときに取りえない特殊な値にしておく。 + // 以下のことをspecial moveと呼び、Move.IsSpecial()でtrueが返る。 + + SPECIAL = DROP + PROMOTE, + + NULL , // NULL MOVEを意味する指し手 + RESIGN , // << で出力したときに"resign"と表示する投了を意味する指し手。自分による手番時の投了。 + WIN , // 入玉時の宣言勝ちのために使う特殊な指し手 + WIN_THEM, // トライルールにおいて相手に入玉された局面であった + DRAW , // 引き分け。(CSAプロトコルにある) 引き分けの原因は不明。 + MATED , // 詰み(合法手がない)局面(手番側が詰まされていて合法手がない) + REPETITION , // 千日手(PSN形式で、DRAWかWINかわからない"Sennichite"という文字列が送られてくるのでその解釈用) + REPETITION_DRAW, // 千日手引き分け + REPETITION_WIN , // 千日手勝ち(相手の連続王手) + REPETITION_LOSE, // 千日手負け(自分の連続王手) この値は使うことはないはず + TIME_UP , // 時間切れによる負け + INTERRUPT , // ゲーム中断 + MAX_MOVES_DRAW , // 最大手数に達したために引き分け + ILLEGAL_MOVE , // 不正な指し手などによる反則負け + ILLEGAL_ACTION_WIN , // 相手の不正なアクション(非手番の時に指し手を送ったなど)による反則勝ち(CSAプロトコルにある) + ILLEGAL_ACTION_LOSE, // 自分の不正なアクション(手番時に送ってはいけない改行を送ったなど)による反則負け(CSAプロトコルにある) + + // 読み筋を表示するための特殊な指し手 + REPETITION_SUP , // 優等局面 + REPETITION_INF , // 劣等局面 + MATE_ENGINE_NO_MATE, // 不詰を表現している。"go mate"に対してcheckmate nomateが返ってきたときにこれを用いる。 + MATE_ENGINE_NOT_IMPLEMENTED, // 手番側に王手がかかっている局面の詰検討は出来ません + MATE_TIMEOUT // "go mate"で時間内に詰みを発見できなかった。 + } + + /// + /// special moveの指し手が勝ち・負け・引き分けのいずれに属するかを判定する時の結果 + /// + public enum MoveGameResult + { + WIN, // 勝ち + LOSE, // 負け + DRAW, // 引き分け + UNKNOWN, // 分類不可のもの + + // --- + + INTERRUPT, // LocalGameServerのイベントで使う用。(GameResult()などではこれを返さない) + } + + /// + /// Moveに関するextension methods + /// + public static class MoveExtensions + { + + /// + /// 指し手がおかしくないかをテストする + /// ただし、盤面のことは考慮していない。 + /// Move.NONEとspecial moveのみがfalse。その他はtrue。 + /// + /// + /// + public static bool IsOk(this Move m) + { + return !(m == Move.NONE || m.IsSpecial()); + } + + /// + /// Uint32型へ変換。 + /// + /// + /// + public static Int32 ToInt(this Move m) + { + return (Int32)m; + } + + /// + /// 指し手がSPECIALな指し手(DoMove出来ない)であるかを判定する。 + /// + /// + /// + public static bool IsSpecial(this Move m) + { + return m >= Move.SPECIAL; + } + + /// + /// mが、勝ち・負け・引き分けのいずれに属するかを返す。 + /// mは specail moveでなければならない。 + /// + /// 連続自己対局の時に結果の勝敗を判定する時などに用いる。 + /// m == INTERRUPTでもMoveGameResult.INTERRUPTではなくUNKNOWNが返るので注意。 + /// + /// + /// + public static MoveGameResult GameResult(this Move m) + { + Debug.Assert(m.IsSpecial()); + + switch (m) + { + case Move.WIN: + case Move.REPETITION_WIN: + case Move.ILLEGAL_ACTION_WIN: + return MoveGameResult.WIN; + + case Move.RESIGN: + case Move.MATED: + case Move.REPETITION_LOSE: + case Move.ILLEGAL_MOVE: + case Move.TIME_UP: + case Move.ILLEGAL_ACTION_LOSE: + case Move.WIN_THEM: + return MoveGameResult.LOSE; + + case Move.DRAW: + case Move.MAX_MOVES_DRAW: + case Move.REPETITION_DRAW: + return MoveGameResult.DRAW; + + case Move.NULL: // これもないと思うが.. + case Move.REPETITION: // 実際には使わない。PSNなどでこれがあるが、連続王手の千日手も含まれていて勝敗不明。 + case Move.INTERRUPT: // 中断も決着がついていないので不明扱い。 + return MoveGameResult.UNKNOWN; + + default: + return MoveGameResult.UNKNOWN; + } + } + + /// + /// Move.IsOk()ではない指し手(Move.NONEも含む)に対して棋譜ウィンドウで使うような文字列化を行う。 + /// + /// KIF2ではきちんと規定されていないのでこれらの特別な指し手は棋譜ウィンドウでの表示において、 + /// 自前で文字列化しなくてはならない。 + /// + /// + /// + public static string SpecialMoveToKif(this Move m) + { + if (!m.IsOk()) + switch (m) + { + // 棋譜ウィンドウへの出力では6文字目までしか入らないので + // 6文字目でちょん切られることを考慮して文字を決めないといけない。 + + case Move.NONE: return "none"; // これは使わないはず + case Move.NULL: return "null"; + case Move.RESIGN: return "投了"; + case Move.WIN: return "入玉宣言勝ち"; + case Move.WIN_THEM: return "入玉トライ勝ち"; + case Move.DRAW: return "引き分け"; + case Move.MATED: return "詰み"; + case Move.REPETITION: return "千日手"; + case Move.REPETITION_DRAW: return "千日手引分"; + case Move.REPETITION_WIN: return "千日手反則勝ち"; + case Move.REPETITION_LOSE: return "千日手反則負け"; + case Move.TIME_UP: return "時間切れ"; + case Move.INTERRUPT: return "中断"; + case Move.MAX_MOVES_DRAW: return "最大手数引分"; + case Move.ILLEGAL_MOVE: return "非合法手反則負け"; + case Move.ILLEGAL_ACTION_WIN : return "反則勝ち"; + case Move.ILLEGAL_ACTION_LOSE: return "反則負け"; + + case Move.REPETITION_SUP: return "優等局面"; + case Move.REPETITION_INF: return "劣等局面"; + case Move.MATE_ENGINE_NO_MATE: return "不詰"; // 詰将棋エンジンで用いる + case Move.MATE_ENGINE_NOT_IMPLEMENTED: return "手番側に王手がかかっている局面の詰検討は出来ません"; // 詰将棋エンジンで用いる + case Move.MATE_TIMEOUT: return "時間内に詰みが発見出来ませんでした。"; // 詰将棋エンジンで用いる + + default: return "UNKNOWN"; // おかしい。なんだろう.. + } + else + // エラーにはしないが.. + return "NonSpecialMove"; + } + + + /// + /// 見た目に、わかりやすい形式で表示する + /// (盤面情報がないので移動させる駒がわからない。デバッグ用) + /// + public static string Pretty(this Move m) + { + if (m.IsSpecial()) + return SpecialMoveToKif(m); + + if (m.IsDrop()) + return string.Format("{0}{1}打",m.To().Pretty() , m.DroppedPiece().Pretty2()); + else + return string.Format("{0}{1}{2}",m.From().Pretty() , m.To().Pretty() , m.IsPromote() ? "成" : ""); + } + + /// + /// 移動させた駒がわかっているときに指し手をわかりやすい表示形式で表示する。 + /// (盤面情報がないので、移動元候補の駒が複数ある場合は区別が出来ない。デバッグ用) + /// + public static string Pretty(this Move m, Piece movedPieceType) + { + if (m.IsDrop()) + return string.Format("{0}{1}打" , m.To().Pretty() , movedPieceType.Pretty() ); + else + return string.Format("{0}{1}{2}",m.To().Pretty() , movedPieceType.Pretty() , m.IsPromote() ? "成" : ""); + + } + + /// + /// 先手から見た勝敗文字列を返す。 + /// + /// 駒落ちのときは、出力する文字列を「先手」「後手」から、「上手」「下手」に変更する。 + /// + /// + /// + public static string Pretty(this MoveGameResult result , bool handicapped) + { + switch (result) + { + case MoveGameResult.WIN: return handicapped ? "下手勝ち":"先手勝ち"; + case MoveGameResult.LOSE: return handicapped ? "上手勝ち":"後手勝ち"; + case MoveGameResult.DRAW: return "引き分け"; + case MoveGameResult.UNKNOWN: return ""; // 中断などで勝敗がついていない。 + default: return ""; + } + } + + /// + /// 指し手をUSI形式の文字列にする。 + /// + /// 特殊な指し手は、Move.RESIGN , Move.WIN , Move.NULLしかサポートしていない。 + /// (USIでこれ以外の特殊な指し手は規定されていないため。 + /// "null"はUSIでサポートされていないが、Null Move Pruningを表現するのに使うことがあるので入れておく。) + /// + public static string ToUsi(this Move m) + { + if (m == Move.NONE) + return "none"; + + if (m.IsSpecial()) + return ((m == Move.RESIGN) ? "resign" : + (m == Move.WIN) ? "win" : + (m == Move.NULL) ? "null" : + ""); + + else if (m.IsDrop()) + return string.Format("{0}*{1}", m.DroppedPiece().ToUsi(), m.To().ToUsi()); + + else + return string.Format("{0}{1}{2}",m.From().ToUsi() , m.To().ToUsi() , m.IsPromote() ? "+" : ""); + } + + /// + /// 指し手の移動元の升を返す。 + /// + /// 指し手がIsOk()でなければSquare.NBが返る。 + /// + public static Square From(this Move m) + { + if (!m.IsOk()) + return Square.NB; + + // 駒打ちに対するmove_from()の呼び出しは不正。 + Debug.Assert((Move.DROP.ToInt() & m.ToInt()) == 0); + + return (Square)((m.ToInt() >> 7) & 0x7f); + } + + /// + /// 指し手の移動先の升を返す。 + /// + /// 指し手がIsOk()でなければSquare.NBが返る。 + /// + /// + /// + public static Square To(this Move m) + { + if (!m.IsOk()) + return Square.NB; + + return (Square)(m.ToInt() & 0x7f); + } + + /// + /// 指し手が駒打ちか? + /// + /// + /// + public static bool IsDrop(this Move m) + { + return (m.ToInt() & (UInt16)Move.DROP) != 0; + } + + /// + /// 指し手が成りか? + /// + public static bool IsPromote(this Move m) + { + return (m.ToInt() & (UInt16)Move.PROMOTE) != 0; + } + + /// + /// 駒打ち(is_drop()==true)のときの打った駒 + /// 先後の区別なし。PAWN~ROOKまでの値が返る。 + /// + public static Piece DroppedPiece(this Move m) + { + return (Piece)((m.ToInt() >> 7) & 0x7f); + } + } + + public static class MoveGameResultExtensions + { + /// + /// MoveGameResultをUSIプロトコルの"gameover"で用いる文字列として返す + /// + /// + /// + public static string ToUsi(this MoveGameResult result) + { + switch (result) + { + case MoveGameResult.DRAW: return "draw"; + case MoveGameResult.WIN: return "win"; + case MoveGameResult.LOSE: return "lose"; + default: return "unknown"; + } + } + + /// + /// 勝ちと負けを反転させて返す。 + /// + /// + /// + public static MoveGameResult Not(this MoveGameResult result) + { + return + result == MoveGameResult.WIN ? MoveGameResult.LOSE : + result == MoveGameResult.LOSE ? MoveGameResult.WIN : + result; + } + } + + /// + /// Model.Shogi用のヘルパークラス + /// + public static partial class Util + { + /// + /// fromからtoに移動する指し手を生成して返す(16bitの指し手) + /// + public static Move MakeMove(Square from, Square to) + { + return (Move)(to.ToInt() + (from.ToInt() << 7)); + } + + /// + /// fromからtoに移動する、成りの指し手を生成して返す(16bit) + /// + public static Move MakeMovePromote(Square from, Square to) + { + return (Move)(to.ToInt() + (from.ToInt() << 7) + Move.PROMOTE.ToInt()); + } + + /// + /// Pieceをtoに打つ指し手を生成して返す(16bitの指し手) + /// + public static Move MakeMoveDrop(Piece pt, Square to) + { + return (Move)(to.ToInt() + (pt.ToInt() << 7) + Move.DROP.ToInt()); + } + + /// + /// 指し手を生成する + /// + /// from : 盤上の升のみでなく手駒もありうる + /// to : 盤上の升 + /// promote : 成るかどうか + /// + /// + /// + /// + /// + public static Move MakeMove(SquareHand from , SquareHand to , bool promote) + { + // ありえないはずだが…。 + if (!to.IsBoardPiece()) + return Move.NONE; + + var to2 = (Square)to; + + if (from.IsHandPiece()) + { + // 打ちと成りは共存できない + if (promote) + return Move.NONE; + + return MakeMoveDrop(from.ToPiece(), to2); + } else + { + var from2 = (Square)from; + + if (promote) + return MakeMovePromote(from2, to2); + else + return MakeMove(from2,to2); + } + } + + /// + /// USIの指し手文字列からMoveに変換 + /// 変換できないときはMove.NONEが返る。 + /// 盤面を考慮していないので指し手の合法性は考慮しない。 + /// + /// + /// + public static Move FromUsiMove(string str) + { + // さすがに3文字以下の指し手はおかしいだろ。 + if (str.Length <= 3) + return Move.NONE; + + Square to = Util.FromUsiSquare(str[2], str[3]); + if (!to.IsOk()) + return Move.NONE; + + bool promote = str.Length == 5 && str[4] == '+'; + bool drop = str[1] == '*'; + + Move move = Move.NONE; + if (!drop) + { + Square from = Util.FromUsiSquare(str[0], str[1]); + if (from.IsOk()) + move = promote ? Util.MakeMovePromote(from, to) : Util.MakeMove(from, to); + } + else + { + for (int i = 0; i < 7; ++i) + if (USI_MAIN_PIECE[i] == str[0]) + { + move = Util.MakeMoveDrop((Piece)(i+1), to); + break; + } + } + return move; + } + + /// + /// 指し手のリストをUSIで使う指し手文字列に変換する。 + /// + /// + /// + public static string MovesToUsiString(List moves) + { + var sb = new StringBuilder(); + + foreach (var m in moves) + { + if (sb.Length != 0) + sb.Append(' '); + sb.Append(m.ToUsi()); + } + + return sb.ToString(); + } + + /// + /// USIのpositionで使うのと同等の文字列を生成する。 + /// "sfen ... moves ..."みたいなの。 + /// + /// このあとKifuManager.FromString()にそのまま渡せる。 + /// + /// + /// + /// + public static string RootSfenAndMovesToUsiString(string rootSfen,List moves) + { + var sfen = (moves == null || moves.Count == 0) ? + rootSfen : + $"sfen {rootSfen} moves { MovesToUsiString(moves) }"; + + return sfen; + } + + /// + /// 通常の指し手ならUSIの指し手文字列に変換する。 + /// special moveなら、enum値を文字列化して返す。 + /// + /// + /// + public static string MoveToString(this Move move) + { + if (move.IsOk()) + return move.ToUsi(); + + // あまり使いたくないが、enumのToString()を呼び出している。 + return move.ToString(); + } + + /// + /// 文字列から指し手を生成して返す。 + /// sは、special moveのはず。 + /// + /// + /// + public static Move MoveFromString(this string s) + { + // あまり使いたくないが、enumからreflectionで取り出している。 + return (Move)Enum.Parse(typeof(Move), s); + } + } + +} diff --git a/Assets/Plugins/MyShogi/Core/Move.cs.meta b/Assets/Plugins/MyShogi/Core/Move.cs.meta new file mode 100644 index 0000000..cd9e4d6 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Move.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0270aa29836a44c1d8304b57b72a9521 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/MoveGen.cs b/Assets/Plugins/MyShogi/Core/MoveGen.cs new file mode 100644 index 0000000..19b5619 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/MoveGen.cs @@ -0,0 +1,132 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 指し手生成 + /// + public static class MoveGen + { + /// + /// 合法な指し手を生成する。 + /// moves[startIndex]から使っていく。返し値をendIndexとして、 + /// moves[startIndex]...moves[endIndex-1]まで使うものとする。 + /// + /// + /// + /// + /// + public static int LegalAll(Position pos , Move[] moves, int startIndex) + { + /// Position.IsLegal()が完璧に非合法手を弾くので、NonEvalsion()で指し手を生成して + /// FilterNonLegalMoves()で排除する。少し遅くなるが、Evasionそんなにないからいいだろう…。 + + // 愚直に81升調べてもどうってことないはずだが最低限の高速化をしとく + var endIndex = startIndex; + + var us = pos.sideToMove; + var ourPieces = pos.Pieces(us); // 自駒 + Square from , to; + var enemyField = Bitboard.EnemyField(us); // 敵陣 + + // 自分から見た1段目 + var rank1_for_us = us == Color.BLACK ? Rank.RANK_1 : Rank.RANK_9; + var rank2_for_us = us == Color.BLACK ? Rank.RANK_2 : Rank.RANK_8; + + // 自駒がないところ(移動先の候補) + var movable = ~ pos.Pieces(us); + + while (ourPieces.IsNotZero()) + { + from = ourPieces.Pop(); + Piece pc = pos.PieceOn(from); // 移動元の駒 + Piece pt = pc.PieceType(); + + // pcに駒を置いたときの利きに移動できて、自駒があるところには移動できない + var target = Bitboard.EffectsFrom(pc, from, pos.Pieces()) & movable; + while (target.IsNotZero()) + { + to = target.Pop(); + + // pcをfromからtoに移動させる指し手を生成する + + var r = to.ToRank(); + + // 行き場のない升への移動は非合法手なのでそれを除外して指し手生成 + if (! + (((pt == Piece.PAWN || pt == Piece.LANCE) && r == rank1_for_us) + ||(pt == Piece.KNIGHT && (r == rank1_for_us || r== rank2_for_us))) + ) + + moves[endIndex++] = Util.MakeMove(from, to); + + // 成れる条件 + // 1.移動させるのが成れる駒 + // 2.移動先もしくは移動元が敵陣 + if ((Piece.PAWN <= pt && pt < Piece.GOLD) + && (enemyField & (new Bitboard(from) | new Bitboard(to))).IsNotZero()) + + moves[endIndex++] = Util.MakeMovePromote(from, to); + } + } + + // 駒打ちの指し手 + + var h = pos.Hand(us); + for (Piece pt = Piece.PAWN; pt < Piece.KING; ++pt) + { + // その駒を持っていないならskip + if (h.Count(pt) == 0) + continue; + + for (to = Square.ZERO; to < Square.NB; ++to) + { + // 駒がない升にしか打てない + if (pos.PieceOn(to) != Piece.NO_PIECE) + continue; + + // 行き場のない駒は打てない + var r = to.ToRank(); + if (((pt == Piece.PAWN || pt == Piece.LANCE) && r == rank1_for_us) + || (pt == Piece.KNIGHT && (r == rank1_for_us || r == rank2_for_us))) + continue; + + // 二歩のチェックだけしとく + if (pt == Piece.PAWN + && (pos.Pieces(us , Piece.PAWN) & Bitboard.FileBB(to.ToFile())).IsNotZero()) + continue; + + moves[endIndex++] = Util.MakeMoveDrop(pt, to ); + } + } + + // 非合法手を除外する。 + + int p = startIndex; + while (p < endIndex) + { + Move m = moves[p]; + if (pos.IsLegal(m)) + ++p; + else + moves[p] = moves[--endIndex]; // 非合法手でなかったので最後の指し手をここに埋める + } + return endIndex; + } + + /// + /// 現在の局面で合法手をすべて生成してそれを出力する(デバッグ用) + /// + /// + public static void GenTest(Position pos) + { + Move[] moves = new Move[(int)Move.MAX_MOVES]; + int endIndex = MoveGen.LegalAll(pos, moves, 0); + + for (int i = 0; i < endIndex; ++i) + Console.Write(moves[i].Pretty() + " "); + + Console.WriteLine(); + } + } +} diff --git a/Assets/Plugins/MyShogi/Core/MoveGen.cs.meta b/Assets/Plugins/MyShogi/Core/MoveGen.cs.meta new file mode 100644 index 0000000..1f2938a --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/MoveGen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32d7292d19a384d08b6a0342cf56db7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Piece.cs b/Assets/Plugins/MyShogi/Core/Piece.cs new file mode 100644 index 0000000..a688ac2 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Piece.cs @@ -0,0 +1,256 @@ +using System; +using System.Diagnostics; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 駒を表す定数 + /// + /// + /// PAWN : 歩 + /// LANCE : 香 + /// KNIGHT : 桂 + /// SILVER : 銀 + /// BISHOP : 角 + /// ROOK : 飛 + /// GOLD : 金 + /// KING : 王 + /// PRO_PAWN : 成歩(と) + /// PRO_LANCE : 成香 + /// PRO_KNIGHT : 成桂 + /// PRO_SILVER : 成銀 + /// HORSE : 馬 + /// DRAGON : 龍 + /// QUEEN : 成金(この駒は無いのでQUEENを当ててある) + /// + public enum Piece : Int32 + { + NO_PIECE, PAWN, LANCE, KNIGHT, SILVER, BISHOP, ROOK, GOLD, + KING, PRO_PAWN, PRO_LANCE, PRO_KNIGHT, PRO_SILVER, HORSE, DRAGON, QUEEN, + + PROMOTE = 8, // 成りを表す + WHITE = 16, // 後手を表す + + ZERO = 0, // Pieceの開始番号 + NB = 32, // Pieceの終端を表す + + // 以下、先後の区別のある駒(Bがついているのは先手、Wがついているのは後手) + B_PAWN = 1, B_LANCE, B_KNIGHT, B_SILVER, B_BISHOP, B_ROOK, B_GOLD, B_KING, B_PRO_PAWN, B_PRO_LANCE, B_PRO_KNIGHT, B_PRO_SILVER, B_HORSE, B_DRAGON, B_QUEEN, + W_PAWN = 17, W_LANCE, W_KNIGHT, W_SILVER, W_BISHOP, W_ROOK, W_GOLD, W_KING, W_PRO_PAWN, W_PRO_LANCE, W_PRO_KNIGHT, W_PRO_SILVER, W_HORSE, W_DRAGON, W_QUEEN, + + HAND_NB = KING, // 手駒になる駒種の最大+1 + + // --- Position::pieces()で用いる定数。空いてるところを順番に用いる。 + ALL_PIECES = 0, // 駒がある升を示すBitboardが返る。 + GOLDS = QUEEN, // 金と同じ移動特性を持つ駒のBitboardが返る。 + HDK, // H=Horse,D=Dragon,K=Kingの合体したBitboardが返る。 + BISHOP_HORSE, // BISHOP,HORSEを合成したBitboardが返る。 + ROOK_DRAGON, // ROOK,DRAGONを合成したBitboardが返る。 + SILVER_HDK, // SILVER,HDKを合成したBitboardが返る。 + GOLDS_HDK, // GOLDS,HDKを合成したBitboardが返る。 + PIECE_BB_NB, // デリミタ + + }; + + /// + /// Pieceに関するextension methodsを書いておくクラス + /// + public static class PieceExtensions + { + /// + /// 値が正常な範囲であるかを判定する。 + /// + /// + /// + public static bool IsOk(this Piece piece) + { + return Piece.ZERO <= piece && piece < Piece.NB; + } + + // "□"(四角)は文字フォントによっては半分の幅しかない。"口"(くち)にする。 + private static readonly string[] PIECE_KANJI = { + " 口"," 歩"," 香"," 桂"," 銀"," 角"," 飛"," 金"," 玉"," と"," 杏"," 圭"," 全"," 馬"," 龍"," 菌"," 王", + "^歩","^香","^桂","^銀","^角","^飛","^金","^玉","^と","^杏","^圭","^全","^馬","^龍","^菌","^王" }; + + /// + /// 日本語の文字列にする。 + /// 盤面表示用なので2文字から成る。 + /// + /// + /// + public static string Pretty(this Piece piece) + { + return PIECE_KANJI[piece.ToInt()]; + } + + /// + /// 手駒などを表示する用なのでpretty()とは異なり、漢字1文字で出力する。 + /// + /// + /// + public static char Pretty2(this Piece piece) + { + return PIECE_KANJI[piece.ToInt()][1]; + } + + private const string USI_PIECE = ". P L N S B R G K +P+L+N+S+B+R+G+.p l n s b r g k +p+l+n+s+b+r+g+k"; + + /// + /// USI文字列に変換する。 + /// + /// + /// + public static string ToUsi(this Piece piece) + { + if (!piece.IsOk()) + return "??"; + + var p = (int)piece.ToInt(); + + // 末尾の人力trim + var length = (USI_PIECE[p * 2 + 1] == ' ') ? 1 : 2; + return USI_PIECE.Substring(p * 2, length); + } + + /// + /// pが先手の駒であるか、後手の駒であるかを返す。 + /// p==EMPTYの場合、先手の駒扱いをする。 + /// + public static Color PieceColor(this Piece piece) + { + return (piece < Piece.WHITE) ? Color.BLACK : Color.WHITE; + } + + /// + /// 後手の歩→先手の歩のように、後手という属性を取り払った駒種を返す + /// + public static Piece PieceType(this Piece piece) + { + return ((Piece)((Int32)piece & ~(Int32)Piece.WHITE)); + } + + /// + /// 成ってない駒を返す。後手という属性も消去する。 + /// 例) 成銀→銀 , 後手の馬→先手の角 , 先手玉 → 先手の玉 + /// NO_PIECEはNO_PIECEが返る。 + /// + public static Piece RawPieceType(this Piece piece) + { + if (piece == Piece.NO_PIECE || piece == Piece.WHITE) + return Piece.NO_PIECE; + + // KINGがNO_PIECEになってしまうといけないので、1引いてから下位3bit取り出して、1足しておく。 + return (Piece)((((int)piece-1) & 7)+1); + } + + /// + /// 成れる駒であるか。 + /// 歩、香、桂、銀、角、飛のときのみtrueが返る。 + /// + /// + /// + public static bool CanPromote(this Piece piece) + { + var pt = piece.PieceType(); + return Piece.PAWN <= pt && pt < Piece.KING && pt != Piece.GOLD; + } + + /// + /// 成った駒を返す + /// + /// 渡していいのは、歩、香、桂、銀、角、飛のみ(後手の駒でも可) + /// + public static Piece ToPromotePiece(this Piece piece) + { + Debug.Assert(piece.CanPromote()); + + return piece + (int)Piece.PROMOTE; + } + + /// + /// 成り駒であるかどうかを判定する + /// Piece.KINGに対して呼び出すと成駒と判定されてしまうので注意。 + /// + public static bool IsPromote(this Piece piece) + { + return (piece.ToInt() & Piece.PROMOTE.ToInt()) != 0; + } + + /// + /// 先手の駒なら後手の駒にする。 + /// 後手の駒なら先手の駒にする。 + /// + /// + /// + public static Piece Inverse(this Piece piece) + { + return piece ^ Piece.WHITE; + } + + /// + /// pieceをInt32の値で取り出したいときに用いる。 + /// + /// + public static Int32 ToInt(this Piece piece) + { + return (Int32)piece; + } + } + + /// + /// Model.Shogi用のヘルパークラス + /// + public static partial class Util + { + /// + /// pcとして先手の駒を渡し、cが後手なら後手の駒を返す。cが先手なら先手の駒のまま。 + /// pcとしてNO_PIECEは渡してはならない。 + /// + /// + /// + /// + public static Piece MakePiece(Color c, Piece pc) + { + Debug.Assert(pc.PieceColor() == Color.BLACK && pc != Piece.NO_PIECE); + return (Piece)((c.ToInt() << 4) + pc.ToInt()); + } + + /// + /// 成り駒を返す。 + /// pcとして先手の駒を渡し、cが後手なら後手の駒(の成り駒)を返す。cが先手なら先手の駒(の成り駒)のまま。 + /// pcとしてNO_PIECEは渡してはならない。 + /// + /// + /// + /// + public static Piece MakePiecePromote(Color c, Piece pc) + { + Debug.Assert(pc.PieceColor() == Color.BLACK && pc != Piece.NO_PIECE && !pc.IsPromote()); + return (Piece)((c.ToInt() << 4) + pc.ToInt() + Piece.PROMOTE.ToInt()); + } + + /// + /// USIの駒文字列(1バイト文字列) + /// + public static readonly string USI_MAIN_PIECE = "PLNSBRGK"; + + /// + /// USI文字列の1バイト駒をPieceに変換する。 + /// + /// + /// + public static Piece FromUsiPiece(char c) + { + var c2 = char.ToUpper(c); + + for (int i = 0; i < 8; ++i) + if (USI_MAIN_PIECE[i] == c2) + return (Piece)(Piece.PAWN.ToInt() + i + ((c==c2) ? 0 : Piece.WHITE.ToInt()) ); + + // 見つからず + return Piece.NO_PIECE; + } + + } + +} diff --git a/Assets/Plugins/MyShogi/Core/Piece.cs.meta b/Assets/Plugins/MyShogi/Core/Piece.cs.meta new file mode 100644 index 0000000..91d7adc --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Piece.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ece4fa140133e44de93247e5082d831c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/PieceNo.cs b/Assets/Plugins/MyShogi/Core/PieceNo.cs new file mode 100644 index 0000000..07162d7 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/PieceNo.cs @@ -0,0 +1,21 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 駒番号 + /// 盤上のどの駒がどこに移動したかを追跡するために用いる + /// 1 ~ 40までの番号がついている。 + /// + public enum PieceNo : Int32 + { + // 駒がない場合 + NONE = 0, + + ZERO = 1, // これややこしいかな…。 + NB = 41, + + // 歩の枚数の最大 + PAWN_MAX = 18, + } +} diff --git a/Assets/Plugins/MyShogi/Core/PieceNo.cs.meta b/Assets/Plugins/MyShogi/Core/PieceNo.cs.meta new file mode 100644 index 0000000..276538d --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/PieceNo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ad8ac9acd6e94345a58aa8ee8fc04e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Position.cs b/Assets/Plugins/MyShogi/Core/Position.cs new file mode 100644 index 0000000..d9c2a69 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Position.cs @@ -0,0 +1,2114 @@ +using MyShogi.Model.Common.Collections; +using System; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// Position(局面)の付随情報を格納する構造体 + /// + public class StateInfo + { + /// + /// 現在の局面のhash key + /// + public HASH_KEY key; + + /// + /// この手番側の連続王手は何手前からやっているのか(連続王手の千日手の検出のときに必要) + /// + public int[] continuousCheck = new int[(int)Color.NB]; + + /// + /// Position.DoMove()する直前の指し手。 + /// デバッグ時などにおいてその局面までの手順を表示出来ると便利なことがあるのでそのための機能 + /// あと、棋譜を表示するときに「同歩」のように直前の指し手のto(行き先の升)が分からないといけないのでこれを用いると良い。 + /// + public Move lastMove; + + /// + /// 現局面で手番側に対して王手をしている駒のbitboard + /// + public Bitboard checkersBB; + + // 動かすと手番側の王に対して空き王手になるかも知れない駒の候補 + // チェスの場合、駒がほとんどが大駒なのでこれらを動かすと必ず開き王手となる。 + // 将棋の場合、そうとも限らないので移動方向について考えなければならない。 + // color = 手番側 なら pinされている駒(動かすと開き王手になる) + // color = 相手側 なら 両王手の候補となる駒。 + + /// + /// 自玉に対して(敵駒によって)pinされている駒 + /// + public Bitboard[] blockersForKing = new Bitboard[(int)Color.NB]; + + /// + /// 自玉に対してpinしている(可能性のある)敵の大駒。 + /// 自玉に対して上下左右方向にある敵の飛車、斜め十字方向にある敵の角、玉の前方向にある敵の香、… + /// + public Bitboard[] pinnersForKing = new Bitboard[(int)Color.NB]; + + /// + /// 自駒の駒種Xによって敵玉が王手となる升のbitboard + /// + public Bitboard[] checkSquares = new Bitboard[(int)Piece.WHITE]; + + /// + /// この局面で捕獲された駒。先後の区別あり。 + /// ※ 次の局面にDoMove()で進むときにこの値が設定される + /// + public Piece capturedPiece; + + /// + /// 一手前の局面へのポインタ + /// previous == null であるとき、これ以上辿れない + /// これを辿ることで千日手判定などを行う。 + /// + public StateInfo previous; + } + + /// + /// 局面クラスでsfenを生成するのに最小限必要なデータ構造。 + /// + /// Position.CreateRawPosition()でコピーして、書き換え後、 + /// Position.SfenFromRawPosition()でsfenを構築できる。 + /// + public class RawPosition + { + public Piece[] board = new Piece[(int)Square.NB_PLUS1]; + public Hand[] hands = new Hand[(int)Color.NB + 1]; + public Color sideToMove; + public int gamePly; + } + + /// + /// 盤面を表現するクラス + /// + public class Position + { + // ------------------------------------------------------------------------- + + /// + /// 盤面、81升分の駒 + 1 + /// + private Piece[] board = new Piece[(int)Square.NB_PLUS1]; + private PieceNo[] board_pn = new PieceNo[Square.NB_PLUS1.ToInt()]; + + /// + /// 手駒 + /// + private Hand[] hands = new Hand[(int)Color.NB + 1/*駒箱*/]; + private PieceNo[,,] hand_pn = new PieceNo[(int)Color.NB, (int)Piece.HAND_NB, (int)PieceNo.PAWN_MAX]; + // → どこまで使用してあるかは、Hand(Color,Piece)を用いればわかる。 + + // 使用しているPieceNoの終端 + public PieceNo lastPieceNo { get; private set; } + + /// + /// 手番 + /// + public Color sideToMove { get; private set; } = Color.BLACK; + + /// + /// 玉の位置 + /// + private Square[] kingSquare = new Square[Color.NB.ToInt()]; + + /// + /// (平手などの)初期局面からの手数(初期局面 == 1) + /// + /// "position sfen xxx [gamePly] moves ..."のような形で渡される可能性があるので、 + /// gamePly > 1だからと言って、UndoMove()できるとは限らないことに注意。 + /// + /// + public int gamePly { get; private set; } = 1; + + /// + /// 局面の付随情報 + /// st.previousで1手前の局面の情報が得られるので千日手判定などに用いる + /// + private StateInfo st; + + /// + /// 局面の付随情報 + /// st.previousで1手前の局面の情報が得られるので千日手判定などに用いる + /// + public StateInfo State() { return st; } + + /// + /// 現局面のhash key。 + /// + /// + public HASH_KEY Key() { return st.key; } + + // 盤上の先手/後手/両方の駒があるところが1であるBitboard + public Bitboard[] byColorBB = new Bitboard[(int)Color.NB]; + + // 駒が存在する升を表すBitboard。先後混在。 + // pieces()の引数と同じく、ALL_PIECES,HDKなどのPieceで定義されている特殊な定数が使える。 + public Bitboard[] byTypeBB = new Bitboard[(int)Piece.PIECE_BB_NB]; + + /// + /// 駒落ちであるかのフラグ + /// 盤面を初期化した時に、駒箱に駒があれば駒落ちと判定。 + /// (単玉は駒落ちとして扱わない) + /// + public bool Handicapped; + + // ------------------------------------------------------------------------- + + /// + /// このオブジェクトをコピーする。 + /// immutableなオブジェクトが欲しいときに用いる。 + /// + /// + public Position Clone() + { + var pos = new Position(); + + // C#では多次元配列もこの方法でCopy()出来ることは保証されている。 + + Array.Copy(board, pos.board, board.Length); + Array.Copy(board_pn, pos.board_pn, board_pn.Length); + Array.Copy(hands, pos.hands, hands.Length); + Array.Copy(hand_pn, pos.hand_pn, hand_pn.Length); + pos.lastPieceNo = lastPieceNo; + pos.sideToMove = sideToMove; + Array.Copy(kingSquare, pos.kingSquare, kingSquare.Length); + pos.gamePly = gamePly; + pos.st = st; // stの先は参照透明。DoMove()の時に新規に作られ、更新はこのタイミングでしか行われないので。 + Array.Copy(byColorBB, pos.byColorBB, byColorBB.Length); + Array.Copy(byTypeBB, pos.byTypeBB, byTypeBB.Length); + pos.Handicapped = Handicapped; + + return pos; + } + + /// + /// このクラスのsfenの構築に必要な部分のみをClone()して返す。 + /// Position.CreateRawPosition()でコピーして、書き換え後、Position.SfenFromRawPosition()でsfenを構築できる。 + /// + /// + public RawPosition CreateRawPosition() + { + var raw = new RawPosition(); + + Array.Copy(board, raw.board, board.Length); + Array.Copy(hands, raw.hands, hands.Length); + raw.sideToMove = sideToMove; + raw.gamePly = gamePly; + + return raw; + } + + // ------------------------------------------------------------------------- + + /// + /// 盤面上、sqの升にある駒の参照 + /// + /// + /// + public ref Piece PieceOn(Square sq) + { + Debug.Assert(sq.IsOkPlus1()); + return ref board[sq.ToInt()]; + } + + /// + /// 盤上、手駒上のsqの位置にある駒。 + /// 手駒の場合、その手駒を持っていなければPiece.NO_PIECEが返る。 + /// また、後手の場合、後手の駒(Piece.W_PAWNなど)が返る。 + /// SquareHand.NBに対してもPiece.NO_PIECEが返る。 + /// + /// + /// + public Piece PieceOn(SquareHand sq) + { + if (sq.IsBoardPiece()) + { + // -- 盤上の駒 + + return PieceOn((Square)sq); + } + else if (sq.IsHandPiece()) + { + // -- 手駒 + + var pt = sq.ToPiece(); + if (pt != Piece.NO_PIECE) + { + var c = sq.PieceColor(); + if (Hand(c).Count(pt) > 0) + return Util.MakePiece(c, pt); + } + + // この手駒を持っていないなら、ここを抜けてPiece.NO_PIECEが返る。 + + } else if (sq.IsPieceBoxPiece()) + { + // -- 駒箱の駒 + + // 駒箱を見て、1枚以上あるならそのpiece typeを返す。 + var pt = sq.ToPiece(); + return PieceBoxCount(pt) > 0 ? pt : Piece.NO_PIECE; + + } else // if (sq == SquareHand.NB) + { + // return Piece.NO_PIECE; + } + + return Piece.NO_PIECE; + } + + /// + /// 駒箱(使っていない駒を入れておく箱)にある駒の数を返す。 + /// + /// 銀が5枚のような局面はSetSfen()で例外が出るので存在しないはず。 + /// ただし玉が3枚あるような局面はありうる。そのときはPieceBoxCount(Piece.KING)は -1 が返る。 + /// + /// + /// Piece.NO_PIECE~KINGまで。Piece.NO_PIECEを渡した時は0が返る。 + /// + public int PieceBoxCount(Piece pt) + { + Debug.Assert(Piece.NO_PIECE <= pt && pt <= Piece.KING); + + if (pt == Piece.NO_PIECE) + return 0; + else if (pt == Piece.KING) + { + // bitboardから枚数を数える。 + var king_bb = Pieces(Piece.HDK) & ~(Pieces(Piece.BISHOP_HORSE) | Pieces(Piece.ROOK_DRAGON)); + var king = king_bb.PopCount(); // 玉の枚数がわかる + return 2 - king; + } + else + return hands[(int)Color.NB].Count(pt); + } + + /// + /// 盤面上、sqの升にある駒のPieceNoの参照 + /// + /// + /// + public ref PieceNo PieceNoOn(Square sq) + { + return ref board_pn[sq.ToInt()]; + } + + /// + /// c側の手駒ptのno枚目の駒のPieceNoの参照 + /// 駒の枚数自体はHand(Color).Count()で取得できるのでそちらを用いること。 + /// + /// + /// + /// + /// + public ref PieceNo HandPieceNo(Color c, Piece pt, int no) + { + return ref hand_pn[(int)c,(int)pt,no]; + } + + /// + /// c側の手駒の参照 + /// + /// c==Color.NBを渡すと駒箱にある駒が返る。 + /// + /// + /// + public ref Hand Hand(Color c) + { + return ref hands[(int)c]; + } + + /// + /// c側の玉のSquareへの参照 + /// + /// 玉が盤上にいない場合はSquare.NBが返ることが保証されている。 + /// + /// + /// + public ref Square KingSquare(Color c) + { + return ref kingSquare[(int)c]; + } + + /// + /// 現局面で王手がかかっているか + /// + /// + public bool InCheck() + { + return Checkers().IsNotZero(); + } + + /// + /// 合法な打ち歩か。 + /// 二歩でなく、かつ打ち歩詰めでないならtrueを返す。 + /// + /// + /// + /// + public bool LegalPawnDrop(Color us,Square to) + { + return !(((Pieces(us, Piece.PAWN) & Bitboard.FileBB(to.ToFile())).IsNotZero()) // 二歩 + || ((Bitboard.PawnEffect(us, to) == new Bitboard(KingSquare(us.Not())) && !LegalDrop(to)))); // 打ち歩詰め + } + + /// + /// toの地点に歩を打ったときに打ち歩詰めにならないならtrue。 + /// 歩をtoに打つことと、二歩でないこと、toの前に敵玉がいることまでは確定しているものとする。 + /// 二歩の判定もしたいなら、legal_pawn_drop()のほうを使ったほうがいい。 + /// + /// + /// + public bool LegalDrop(Square to) + { + var us = sideToMove; + + // 打とうとする歩の利きに相手玉がいることは前提条件としてクリアしているはず。 + // ASSERT_LV3(pawnEffect(us, to) == Bitboard(king_square(~us))); + + // この歩に利いている自駒(歩を打つほうの駒)がなければ詰みには程遠いのでtrue + if (!EffectedTo(us, to)) + return true; + + // ここに利いている敵の駒があり、その駒で取れるなら打ち歩詰めではない + // ここでは玉は除外されるし、香が利いていることもないし、そういう意味では、特化した関数が必要。 + Bitboard b = AttackersToPawn(us.Not(), to); + + // このpinnedは敵のpinned pieces + Bitboard pinned = PinnedPieces(us.Not()); + + // pinされていない駒が1つでもあるなら、相手はその駒で取って何事もない。 + if ((b & (~pinned | Bitboard.FileBB(to.ToFile()))).IsNotZero()) + return true; + + // 攻撃駒はすべてpinされていたということであり、 + // 王の頭に打たれた打ち歩をpinされている駒で取れるケースは、 + // いろいろあるが、例1),例2)のような場合であるから、例3)のケースを除き、 + // いずれも玉の頭方向以外のところからの玉頭方向への移動であるから、 + // pinされている方向への移動ということはありえない。 + // 例3)のケースを除くと、この歩は取れないことが確定する。 + // 例3)のケースを除外するために同じ筋のものはpinされていないものとして扱う。 + // 上のコードの  " | FILE_BB[file_of(to)] " の部分がそれ。 + + // 例1) + // ^玉 ^角 飛 + // 歩 + + // 例2) + // ^玉 + // 歩 ^飛 + // 角 + + // 例3) + // ^玉 + // 歩 + // ^飛 + // 香 + + // 玉の退路を探す + // 自駒がなくて、かつ、to(はすでに調べたので)以外の地点 + + // 相手玉の場所 + Square sq_king = KingSquare(us.Not()); + + // LONG EFFECT LIBRARYがない場合、愚直に8方向のうち逃げられそうな場所を探すしかない。 + + Bitboard escape_bb = Bitboard.KingEffect(sq_king) & ~Pieces(us.Not()); + escape_bb ^= to; + var occ = Pieces() ^ to; // toには歩をおく前提なので、ここには駒があるものとして、これでの利きの遮断は考えないといけない。 + while (escape_bb.IsNotZero()) + { + Square king_to = escape_bb.Pop(); + if (AttackersTo(us, king_to, occ).IsZero()) + return true; // 退路が見つかったので打ち歩詰めではない。 + } + + // すべての検査を抜けてきたのでこれは打ち歩詰めの条件を満たしている。 + return false; + } + + // ------------------------------------------------------------------------- + // occupied bitboardなど + // ------------------------------------------------------------------------- + + /// + /// 先手か後手か、いずれかの駒がある場所が1であるBitboardが返る。 + /// + /// + public Bitboard Pieces() + { + return byTypeBB[(int)Piece.ALL_PIECES]; + } + + /// + /// c == BLACK : 先手の駒があるBitboardが返る + /// c == WHITE : 後手の駒があるBitboardが返る + /// + /// + /// + public Bitboard Pieces(Color c) + { + return byColorBB[(int)c]; + } + + /// + /// 特定の駒種のBitboardを返す。 + /// + /// + /// + public Bitboard Pieces(Piece pr) + { + // ASSERT_LV3(pr + /// pr1とpr2の駒種を合成した(足し合わせた)Bitboardを返す。 + /// + /// + /// + /// + public Bitboard Pieces(Piece pr1, Piece pr2) + { + return Pieces(pr1) | Pieces(pr2); + } + + public Bitboard Pieces(Piece pr1, Piece pr2,Piece pr3) + { + return Pieces(pr1) | Pieces(pr2) | Pieces(pr3); + } + + public Bitboard Pieces(Piece pr1, Piece pr2, Piece pr3 ,Piece pr4) + { + return Pieces(pr1) | Pieces(pr2) | Pieces(pr3) | Pieces(pr4); + } + + public Bitboard Pieces(Piece pr1, Piece pr2, Piece pr3, Piece pr4 , Piece pr5) + { + return Pieces(pr1) | Pieces(pr2) | Pieces(pr3) | Pieces(pr4) | Pieces(pr5); + } + + /// + /// c側の駒種prのbitboardを返す + /// + /// + /// + /// + public Bitboard Pieces(Color c, Piece pr) + { + return Pieces(pr) & Pieces(c); + } + + public Bitboard Pieces(Color c, Piece pr1, Piece pr2) + { + return Pieces(pr1, pr2) & Pieces(c); + } + + public Bitboard Pieces(Color c, Piece pr1, Piece pr2, Piece pr3) + { + return Pieces(pr1, pr2, pr3) & Pieces(c); + } + + public Bitboard Pieces(Color c, Piece pr1, Piece pr2, Piece pr3, Piece pr4) + { + return Pieces(pr1, pr2, pr3, pr4) & Pieces(c); + } + + public Bitboard Pieces(Color c, Piece pr1, Piece pr2, Piece pr3, Piece pr4, Piece pr5) + { + return Pieces(pr1, pr2, pr3, pr4, pr5) & Pieces(c); + } + + + // 駒がない升が1になっているBitboardが返る + public Bitboard Empties() + { + return Pieces() ^ Bitboard.AllBB(); + } + + // --- 王手 + + /// + /// 原局面で王手している駒のBitboardが返る + /// + /// + public Bitboard Checkers() + { + return st.checkersBB; + } + + /// + /// 移動させると(相手側=非手番側)の玉に対して空き王手となる候補の(手番側)駒のbitboard。 + /// + /// + public Bitboard DiscoveredCheckCandidates() + { + return st.blockersForKing[(int)sideToMove.Not()] & Pieces(sideToMove); + } + + /// + /// ピンされているc側の駒。下手な方向に移動させるとc側の玉が素抜かれる。 + /// 手番側のpinされている駒はpos.pinned_pieces(pos.side_to_move())のようにして取得できる。 + /// + /// + /// + public Bitboard PinnedPieces(Color c) + { + // ASSERT_LV3(is_ok(c)); + return st.blockersForKing[(int)c] & Pieces(c); + } + + /// + /// 現局面で駒Ptを動かしたときに王手となる升を表現するBitboard + /// + /// + /// + public Bitboard CheckSquares(Piece pt) + { + // ASSERT_LV3(pt!= NO_PIECE && pt + /// 平手の初期局面のsfen形式 + /// + public static readonly string SFEN_HIRATE = Sfens.HIRATE; + + /// それ以外の駒落ちなどのsfen文字列については、 + /// BoardType.ToSfen()などで取得すること。 + + // ------------------------------------------------------------------------- + + /// + /// このクラスを特定の局面で初期化する + /// デフォルトでは平手で初期化 + /// boardTypeとして範囲外の値を指定した場合は例外が飛ぶ。 + /// + public void InitBoard(BoardType boardType = BoardType.NoHandicap) + { + if (!boardType.IsSfenOk()) + throw new PositionException("範囲外のBoardTypeを指定してPosition.init()を呼び出した"); + + // 平手で初期化 + SetSfen(boardType.ToSfen()); + } + + /// + /// 盤面を日本語形式で出力する。 + /// + /// + public string Pretty() + { + var sb = new StringBuilder(); + + for (Rank r = Rank.RANK_1; r <= Rank.RANK_9; ++r) + { + for (File f = File.FILE_9; f >= File.FILE_1; --f) + { + sb.Append(PieceOn(Util.MakeSquare(f, r)).Pretty()); + } + sb.AppendLine(); + } + + // 手番 + sb.Append("【"+sideToMove.Pretty() + "番】 "); + + // 手駒 + foreach (var c in All.Colors()) + { + sb.Append(c.Pretty() + ":"); + sb.Append(Hand(c).Pretty()); + sb.Append(" "); + } + sb.AppendLine(); + + // HashKey + sb.AppendLine(Key().Pretty()); + + // USI文字列出力 + sb.Append("sfen : "); + sb.AppendLine(ToSfen()); + + return sb.ToString(); + } + + /// + /// PieceNoがどうなっているか表示する。(デバッグ用) + /// + /// + public string PrettyPieceNo() + { + var sb = new StringBuilder(); + + for (Rank r = Rank.RANK_1; r <= Rank.RANK_9; ++r) + { + for (File f = File.FILE_9; f >= File.FILE_1; --f) + { + var pn = PieceNoOn(Util.MakeSquare(f,r)); + sb.Append(string.Format("{0:D2} ",(int)pn)); + } + sb.AppendLine(); + } + + foreach (var c in All.Colors()) + { + sb.Append(c.Pretty() + ":"); + for(Piece p = Piece.PAWN; p < Piece.HAND_NB; ++p) + { + int count = Hand(c).Count(p); + if (count == 0) + continue; + + sb.Append(p.Pretty()); + for (int i = 0; i < count; ++i) + { + var pn = HandPieceNo(c,p,i); + sb.Append(string.Format("{0:D2} ", (int)pn)); + } + } + sb.AppendLine(); + } + + return sb.ToString(); + } + + /// + /// SFEN形式で盤面を出力する。 + /// + /// + public string ToSfen() + { + var sb = new StringBuilder(); + + // --- 盤面 + int emptyCnt; + for (Rank r = Rank.RANK_1; r <= Rank.RANK_9; ++r) + { + for (File f = File.FILE_9; f >= File.FILE_1; --f) + { + // それぞれの升に対して駒がないなら + // その段の、そのあとの駒のない升をカウントする + for (emptyCnt = 0; f >= File.FILE_1 && PieceOn(Util.MakeSquare(f,r)) == Piece.NO_PIECE; --f) + ++emptyCnt; + + // 駒のなかった升の数を出力 + if (emptyCnt != 0) + sb.Append(emptyCnt.ToString()); + + // 駒があったなら、それに対応する駒文字列を出力 + if (f >= File.FILE_1) + sb.Append(PieceOn(Util.MakeSquare(f,r)).ToUsi()); + } + + // 最下段以外では次の行があるのでセパレーターである'/'を出力する。 + if (r < Rank.RANK_9) + sb.Append('/'); + } + + // --- 手番 + sb.Append(" " + sideToMove.ToUsi() + " "); + + // --- 手駒(UCIプロトコルにはないがUSIプロトコルにはある) + + bool found = false; + for (Color c = Color.BLACK; c <= Color.WHITE; ++c) + { + var h = Hand(c); + var s = h.ToUsi(c); + + if (!s.Empty()) + { + found = true; + sb.Append(s); + } + } + // 手駒がない場合 + sb.Append(found ? " " : "- "); + + // --- 初期局面からの手数 + sb.Append(gamePly.ToString()); + + return sb.ToString(); + } + + /// + /// sfen文字列でこのクラスを初期化する + /// + /// 読み込みに失敗した場合、SfenException例外が投げられる。 + /// ・2歩、行き場のない駒の配置、非手番側への王手、単玉、先手玉が2枚などのチェックはあえて行っていない。 + /// ・銀が5枚は不正。銀3枚(1枚は駒箱)は合法。 + /// ・玉は手駒に出来ない。 + /// + /// + public void SetSfen(string sfen) + { + st = new StateInfo() + { + previous = null + }; + + var split = sfen.Split( + new char[] { ' ' }, + StringSplitOptions.RemoveEmptyEntries); + if (split.Count() < 3) + throw new SfenException("SFEN形式の盤表現が正しくありません。"); + + // --- 盤面 + + KingSquare(Color.BLACK) = KingSquare(Color.WHITE) = Square.NB; + + // 各Bitboard配列のゼロクリア + Array.Clear(board, 0, board.Length); + Array.Clear(board_pn, 0, board_pn.Length); + Array.Clear(hand_pn, 0, hand_pn.Length); + Array.Clear(byColorBB, 0, byColorBB.Length); + Array.Clear(byTypeBB, 0, byTypeBB.Length); + + // 盤面左上から。Square型のレイアウトに依らずに処理を進めたいため、Square型は使わない。 + File f = File.FILE_9; + Rank r = Rank.RANK_1; + lastPieceNo = PieceNo.ZERO; + + bool promoted = false; + var board_sfen = split[0]; + foreach (var c in board_sfen) + { + if (r > Rank.RANK_9) + throw new SfenException("局面の段数が9を超えます。"); + + if (c == '/') + { + if (f.ToInt() >= 0) + throw new SfenException("SFEN形式の" + (r.ToInt()+1).ToString() + "段の駒数が合いません。"); + + r++; + f = File.FILE_9; + promoted = false; + } + else if (c == '+') + { + promoted = true; + } + else if ('1' <= c && c <= '9') + { + f -= (int)(c - '0'); + promoted = false; + } + else + { + if (f < File.FILE_1) + throw new SfenException("SFEN形式の" + (r.ToInt()+1).ToString() + "段の駒数が多すぎます。"); + + var piece = Util.FromUsiPiece(c); + if (piece == Piece.NO_PIECE) + throw new SfenException("SFEN形式の駒'" + c + "'が正しくありません。"); + + piece = piece + (promoted ? Piece.PROMOTE.ToInt() : 0); + + PutPiece(Util.MakeSquare( f , r) , piece , lastPieceNo ++); + f -= 1; + promoted = false; + } + } + + if (f.ToInt() >= 0) + throw new SfenException("SFEN形式の" + r.ToString() + "段の駒数が合いません。"); + + // --- 持ち駒を読み込む + + var hand_sfen = split[2]; + if (hand_sfen.Empty()) + throw new SfenException("SFEN形式の手駒がありません。"); + + Array.Clear(hands, 0, hands.Length); + + // 手駒なしでなければ + if (hand_sfen[0] != '-') + { + var count = 0; + foreach (var c in hand_sfen) + { + if ('0' <= c && c <= '9') + { + count = count * 10 + (c - '0'); + if (count == 0 || count > 18) + { + throw new SfenException("持ち駒の枚数指定が正しくありません。"); + } + } + else + { + var piece = Util.FromUsiPiece(c); + if (piece == Piece.NO_PIECE || piece.RawPieceType() == Piece.KING) + { + throw new SfenException($"SFEN形式の持ち駒、{piece.Pretty()}が正しくありません。"); + } + + var color = piece.PieceColor(); + var pr = piece.RawPieceType(); + + if (count == 0) count = 1; + + // 手駒を加算する + Hand(color).Add(pr, count); + + for(int i=0;i + /// 指し手で盤面を1手進める + /// + /// + public void DoMove(Move m) + { + // ---------------------- + // 盤面の更新処理 + // ---------------------- + + Debug.Assert(m.IsOk()); + + // 移動先の升 + Square to = m.To(); + Debug.Assert(to.IsOk()); + // DoMove()を呼ぶ前にMoveの正当性は何らかチェックするはずなのでそれがなされていないならコードの誤りであるから + // 例外を投げるのではなくassertで停止すべき。 + + // StateInfoの更新 + var newSt = new StateInfo + { + previous = st, + key = st.key + }; + st = newSt; + + var us = sideToMove; + var them = us.Not(); + + if (m.IsDrop()) + { + // --- 駒打ち + + // 盤上にその駒を置く。 + Piece pt = m.DroppedPiece(); + if (pt < Piece.PAWN || Piece.GOLD < pt || Hand(us).Count(pt) == 0) + { + Debug.Assert(false); + throw new PositionException("Position.DoMove()で持っていない手駒" + pt.Pretty2() + "を打とうとした"); + } + + Piece pc = Util.MakePiece(us, pt); + Hand(us).Sub(pt); + + // 打つ駒の駒番号取得して、これを盤面に置く駒に反映させておく。 + PieceNo pn = HandPieceNo(us, pt, Hand(us).Count(pt)); + + PutPiece(to, pc , pn); + + // hash keyの更新 + st.key -= Zobrist.Hand(us,pt); + st.key += Zobrist.Psq(to,pc); + + // 駒打ちは捕獲した駒がない。 + st.capturedPiece = Piece.NO_PIECE; + } + else + { + // -- 駒の移動 + + Square from = m.From(); + PieceNo pn = PieceNoOn(from); + Piece moved_pc = RemovePiece(from); + + // 移動元の駒ナンバーをクリア + PieceNoOn(from) = PieceNo.NONE; + + // 移動先の升にある駒 + + Piece to_pc = PieceOn(to); + if (to_pc != Piece.NO_PIECE) + { + // 駒取り + + // 自分の手駒になる + Piece pr = to_pc.RawPieceType(); + if (!(Piece.PAWN <= pr && pr <= Piece.GOLD)) + { + Debug.Assert(false); + throw new PositionException("Position.DoMove()で取れない駒を取った(玉取り?)"); + } + + // 取る駒のPieceNoを盤上に反映させておく + PieceNo pn2 = PieceNoOn(to); + HandPieceNo(us, pr, Hand(us).Count(pr)) = pn2; + + Hand(us).Add(pr); + + // 捕獲された駒が盤上から消えるので局面のhash keyを更新する + st.key -= Zobrist.Psq(to, to_pc); + st.key += Zobrist.Hand(us,pr); + + // toの地点から元あった駒をいったん取り除く + RemovePiece(to); + + // 駒打ちは捕獲した駒がない。 + st.capturedPiece = to_pc; + } else + { + st.capturedPiece = Piece.NO_PIECE; + } + + Piece moved_after_pc = (Piece)(moved_pc.ToInt() + (m.IsPromote() ? Piece.PROMOTE.ToInt() : 0)); + PutPiece(to, moved_after_pc , pn); + + // fromにあったmoved_pcがtoにmoved_after_pcとして移動した。 + st.key -= Zobrist.Psq(from, moved_pc ); + st.key += Zobrist.Psq(to , moved_after_pc); + } + + sideToMove = us.Not(); + + // -- update + + // PutPiece()などを呼び出したので更新する。 + UpdateBitboards(); + + // このタイミングで王手関係の情報を更新しておいてやる。 + SetCheckInfo(st); + + // 直前の指し手の更新 + st.lastMove = m; + + // Zobrist.sideはp1==0が保証されているのでこれで良い + st.key.p.p0 ^= Zobrist.Side.p.p0; + + gamePly++; + } + + /// + /// 指し手で盤面を1手戻す + /// + public void UndoMove() + { + // Usは1手前の局面での手番 + var us = sideToMove.Not(); + var m = st.lastMove; + + Debug.Assert(m.IsOk()); + + var to = m.To(); + Debug.Assert(to.IsOk()); + // 盤外(Square.NB)への移動はありえないのでIsOkPlus1()ではなくIsOk()で良い。 + + // --- 移動後の駒 + + Piece moved_after_pc = PieceOn(to); + + // 移動前の駒 + Piece moved_pc = m.IsPromote() ? (moved_after_pc - (int)Piece.PROMOTE) : moved_after_pc; + + if (m.IsDrop()) + { + // --- 駒打ち + + // toの場所にある駒を手駒に戻す + Piece pt = moved_after_pc.RawPieceType(); + + var pn = PieceNoOn(to); + HandPieceNo(us, pt, hands[(int)us].Count(pt)) = pn; + + hands[(int)us].Add(pt); + + // toの場所から駒を消す + RemovePiece(to); + PieceNoOn(to) = PieceNo.NONE; + } + else + { + // --- 通常の指し手 + + var from = m.From(); + Debug.Assert(from.IsOk()); + + // toの場所にあった駒番号 + var pn = PieceNoOn(to); + + // toの場所から駒を消す + RemovePiece(to); + + // toの地点には捕獲された駒があるならその駒が盤面に戻り、手駒から減る。 + // 駒打ちの場合は捕獲された駒があるということはありえない。 + // (なので駒打ちの場合は、st->capturedTypeを設定していないから参照してはならない) + if (st.capturedPiece != Piece.NO_PIECE) + { + Piece to_pc = st.capturedPiece; + Piece pr = to_pc.RawPieceType(); + + // 盤面のtoの地点に捕獲されていた駒を復元する + var pn2 = HandPieceNo(us, pr, hands[(int)us].Count(pr) - 1); + PutPiece(to, to_pc , pn2); + PutPiece(from, moved_pc , pn); + + // 手駒から減らす + hands[(int)us].Sub(pr); + } + else + { + PutPiece(from, moved_pc , pn); + PieceNoOn(to) = PieceNo.NONE; + } + } + + // PutPiece()などを呼び出したので更新する。 + UpdateBitboards(); + + // --- 相手番に変更 + sideToMove = us; // Usは先後入れ替えて呼び出されているはず。 + + // --- StateInfoを巻き戻す + st = st.previous; + + --gamePly; + } + + /// + /// USIのpositionコマンドの"position"以降を解釈してその局面にする + /// "position [sfen | startpos ] moves ... " + /// + /// 解釈で失敗した場合、例外が飛ぶ + /// + /// + public void UsiPositionCmd(string pos_cmd) + { + // スペースをセパレータとして分離する + var split = pos_cmd.Split( + new char[] { ' ' }, + StringSplitOptions.RemoveEmptyEntries); + + // どうなっとるねん.. + if (split.Length == 0) + return; + + // 現在の指し手が書かれている場所 split[cur_pos] + var cur_pos = 1; + if (split[0] == "sfen") + { + // "sfen ... moves ..."形式かな.. + // movesの手前までをくっつけてSetSfen()する + while( cur_pos < split.Length && split[cur_pos] != "moves") + { + ++cur_pos; + } + + if (!(cur_pos== 4 || cur_pos == 5)) + throw new PositionException("Position.UsiPositionCmd()に渡された文字列にmovesが出てこない"); + + if (cur_pos == 4) + SetSfen(string.Format("{0} {1} {2}", split[1], split[2], split[3])); + else // if (cur_pos == 5) + SetSfen(string.Format("{0} {1} {2} {3}", split[1], split[2], split[3], split[4])); + + } else if (split[0] == "startpos") + { + SetSfen(SFEN_HIRATE); + } + + // "moves"以降の文字列をUSIの指し手と解釈しながら、局面を進める。 + if (cur_pos < split.Length && split[cur_pos] == "moves") + for (int i = cur_pos + 1; i < split.Length; ++i) + { + // デバッグ用に盤面を出力 + //Console.WriteLine(Pretty()); + + var move = Util.FromUsiMove(split[i]); + if (!IsLegal(move)) + throw new PositionException(string.Format("{0}手目が非合法手です。", i - cur_pos)); + + DoMove(move); + } + } + + /// + /// ※ mがこの局面においてpseudo_legalかどうかを判定するための関数。 + /// + /// + /// + public bool IsLegal(Move m) + { + // SpeicalMoveが合法扱いされるとDoMoveできてしまって困るので弾いておく。 + if (!m.IsOk()) + return false; + + Color us = sideToMove; + Square to = m.To(); // 移動先 + + // 駒打ちと駒打ちでない指し手とで条件分岐 + + // toの場所に来るPieceType + Piece toPcType; + + if (m.IsDrop()) + { + // 打つ駒 + Piece pr = toPcType = m.DroppedPiece(); + + // 打てないはずの駒 + if (pr < Piece.PAWN || Piece.KING <= pr) + return false; + + // 打つ先の升が埋まっていたり、その手駒を持っていなかったりしたら駄目。 + if (PieceOn(to) != Piece.NO_PIECE || Hand(us).Count(pr) == 0) + return false; + + // --- 移動できない升への歩・香・桂打ちについて + switch (pr) + { + case Piece.PAWN: + // 歩のとき、二歩および打ち歩詰めであるなら非合法手 + if (!LegalPawnDrop(us, to)) + return false; + if (to.ToRank() == (us == Color.BLACK ? Rank.RANK_1 : Rank.RANK_9)) + return false; + + break; + + case Piece.LANCE: + if (to.ToRank() == (us == Color.BLACK ? Rank.RANK_1 : Rank.RANK_9)) + return false; + + break; + + case Piece.KNIGHT: + if ((us == Color.BLACK && to.ToRank() <= Rank.RANK_2) || + (us == Color.WHITE && to.ToRank() >= Rank.RANK_8)) + return false; + + break; + } + } + else + { + // 移動させる指し手 + + Square from = m.From(); + // 移動させる駒 + Piece pc = PieceOn(from); + + // 動かす駒が自駒でなければならない + if (pc == Piece.NO_PIECE || pc.PieceColor() != us) + return false; + + // toに移動できないといけない。 + // (fromに駒を置いたときにtoに利きがないと駄目) + if ((Bitboard.EffectsFrom(pc, from, Pieces()) & to).IsZero()) + return false; + + // toの地点に自駒があるといけない + if ((Pieces(us) & to).IsNotZero()) + return false; + + Piece pt = pc.PieceType(); + if (m.IsPromote()) + { + // --- 成る指し手 + + // 成れない駒の成りではないことを確かめないといけない。 + // static_assert(GOLD == 7, "GOLD must be 7."); + if (pt >= Piece.GOLD) + return false; + + // 移動先が敵陣でないと成れない。 + if ((Bitboard.EnemyField(us) & (new Bitboard(from) | new Bitboard(to))).IsZero()) + return false; + + } else + { + // --- 成らない指し手 + + // 先手の歩の1段目へ不成での移動などは出来ない。このチェック。 + + // --- 移動できない升への歩・香・桂打ちについて + switch (pt) + { + case Piece.PAWN: + case Piece.LANCE: + // 歩・香の2段目の不成も合法なので合法として扱う。 + if (to.ToRank() == (us == Color.BLACK ? Rank.RANK_1 : Rank.RANK_9)) + return false; + break; + + case Piece.KNIGHT: + if ((us == Color.BLACK && to.ToRank() <= Rank.RANK_2) || + (us == Color.WHITE && to.ToRank() >= Rank.RANK_8)) + return false; + + break; + } + + } + + // 王手している駒があるのか + if (InCheck()) + { + // このとき、指し手生成のEVASIONで生成される指し手と同等以上の条件でなければならない。 + + // 動かす駒は王以外か? + if (pc.PieceType() != Piece.KING) + { + // 両王手なら王の移動をさせなければならない。 + if (Bitboard.MoreThanOne(Checkers())) + return false; + + // 指し手は、王手を遮断しているか、王手している駒の捕獲でなければならない。 + // ※ 王手している駒と王の間に王手している駒の升を足した升が駒の移動先であるか。 + // 例) 王■■■^飛 + // となっているときに■の升か、^飛 のところが移動先であれば王手は回避できている。 + // (素抜きになる可能性はあるが、そのチェックはここでは不要) + if (((Bitboard.BetweenBB(Checkers().Pop(), KingSquare(us)) | Checkers()) & to).IsZero()) + return false; + } + } + + // 王の自殺チェック + if (pc.PieceType() == Piece.KING) + { + // もし移動させる駒が玉であるなら、行き先の升に相手側の利きがないかをチェックする。 + if (EffectedTo(us.Not(), to, from)) + return false; + } else + { + // 王手がされているとき/いないとき、共通の処理 + // 王以外を動かすケースについて + + var b = (PinnedPieces(us) & from).IsZero() // ピンされていない駒の移動は自由である + || Util.IsAligned(from, to, KingSquare(us)); // ピンされている方角への移動は合法 + + if (!b) + return false; + } + + toPcType = pc.PieceType(); + } + + // 王手がされているなら + // 王手回避手になっているかどうかのチェックが必要 + + if (InCheck() && toPcType != Piece.KING) + { + Bitboard target = Checkers(); + Square checksq = target.Pop(); + + // 王手している駒を1個取り除いて、もうひとつあるということは王手している駒が + // 2つあったということであり、両王手なので合い利かず。 + if (target.IsNotZero()) + return false; + + // 王と王手している駒との間の升に駒を打っていない場合、それは王手を回避していることに + // ならないので、これは非合法手。 + + // 王手している駒が1つなら、王手している駒を取る指し手であるか、 + // 遮断する指し手でなければならない + + if (!((Bitboard.BetweenBB(checksq, KingSquare(us)) & to).IsNotZero() || checksq == to)) + return false; + } + + // すべてのテストの合格したので合法手である + return true; + } + + /// + /// 連続王手の千日手等で引き分けかどうかを返す + /// 千日手でなければRepetitionState.NONEが返る。 + /// + /// + public RepetitionState IsRepetition() + { + // 現在の局面と同じhash keyを持つ局面が4回あれば、それは千日手局面であると判定する。 + +#if false + // Debug用にこの局面に至るまでのHash値をすべて表示させてみる + { + var s = st; + for(int i = 0; s != null ;++i) + { + Console.WriteLine($"gamePly = {gamePly-i} → {s.key.Pretty()}"); + s = s.previous; + } + } +#endif + + // n回st.previousを辿るlocal method + StateInfo prev(StateInfo si, int n) + { + for (int i = 0; i < n; ++i) + { + si = si.previous; + if (si == null) + break; + } + return si; + }; + + // 4手かけないと千日手にはならないから、4手前から調べていく。 + StateInfo stp = prev(st, 4); + // 遡った手数のトータル + int t = 4; + + // 同一である局面が出現した回数 + int cnt = 0; + + //Console.WriteLine("--Start--"); + //Console.WriteLine(st.key.Pretty()); + + while (stp != null) + { + //Console.WriteLine(stp.key.Pretty()); + + // HashKeyは128bitもあるのでこのチェックで現実的には間違いないだろう。 + if (stp.key == st.key) + { + // 同一局面が4回出現した時点で千日手が成立 + if (++cnt == 3) + { + // 自分が王手をしている連続王手の千日手なのか? + if (t <= st.continuousCheck[(int)sideToMove]) + return RepetitionState.LOSE; + + // 相手が王手をしている連続王手の千日手なのか? + if (t <= st.continuousCheck[(int)sideToMove.Not()]) + return RepetitionState.WIN; + + return RepetitionState.DRAW; + } + } + // ここから2手ずつ遡る + stp = prev(stp, 2); + t += 2; + } + + // 同じhash keyの局面が見つからなかったので…。 + return RepetitionState.NONE; + } + + /// + /// この局面で手番側が詰んでいるか(合法な指し手がないか) + /// 実際に指し手生成をして判定を行うので、引数として指し手生成バッファを渡してやる必要がある。 + /// + /// + public bool IsMated(Move[] moves) + { + return InCheck() && MoveGen.LegalAll(this, moves, 0) == 0; + } + + /// + /// 捕獲する指し手であるか + /// + /// + /// + public bool IsCapture(Move m) + { + return PieceOn(m.To()) != Piece.NO_PIECE; + } + + /// + /// 宣言勝ちできる局面であるかを判定する。 + /// + /// 宣言勝ちできる局面でなければMove.NONEが返る。 + /// 宣言勝ちできる局面であればMove.WINが返る。 + /// + /// ruleでトライルール(TRY_RULE)を指定している場合は、トライ(玉を51の升に移動させること)出来る条件を + /// 満たしているなら、その指し手を返す。 + /// → これやめる。非手番側が入玉したかを判定して返す。 + /// + /// + public Move DeclarationWin(EnteringKingRule rule) + { + switch (rule) + { + // 入玉ルールなし + case EnteringKingRule.NONE: return Move.NONE; + + // CSAルールに基づく宣言勝ちの条件を満たしているか + // 満たしているならば非0が返る。返し値は駒点の合計。 + // cf.http://www.computer-shogi.org/protocol/tcp_ip_1on1_11.html + case EnteringKingRule.POINT24: // 24点法(31点以上で宣言勝ち) + case EnteringKingRule.POINT27: // 27点法 == CSAルール + { + /* + 「入玉宣言勝ち」の条件(第13回選手権で使用のもの): + 次の条件が成立する場合、勝ちを宣言できる(以下「入玉宣言勝ち」と云う)。 + 条件: + (a) 宣言側の手番である。 + (b) 宣言側の玉が敵陣三段目以内に入っている。 + (c) 宣言側が(大駒5点小駒1点の計算で) + ・先手の場合28点以上の持点がある。 + ・後手の場合27点以上の持点がある。 + ・点数の対象となるのは、宣言側の持駒と敵陣三段目 + 以内に存在する玉を除く宣言側の駒のみである。 + (d) 宣言側の敵陣三段目以内の駒は、玉を除いて10枚以上存在する。 + (e) 宣言側の玉に王手がかかっていない。 + (詰めろや必死であることは関係ない) + (f) 宣言側の持ち時間が残っている。(切れ負けの場合) + 以上1つでも条件を満たしていない場合、宣言した方が負けとなる。 + (注) このルールは、日本将棋連盟がアマチュアの公式戦で使用しているものである。 + 以上の宣言は、コンピュータが行い、画面上に明示する。 + */ + // (a)宣言側の手番である。 + // → 手番側でこの関数を呼び出して判定するのでそうだろう。 + + Color us = sideToMove; + + // 敵陣 + Bitboard ef = Bitboard.EnemyField(us); + + // (b)宣言側の玉が敵陣三段目以内に入っている。 + if ((ef & KingSquare(us)).IsZero()) + return Move.NONE; + + // (e)宣言側の玉に王手がかかっていない。 + if (InCheck()) + return Move.NONE; + + + // (d)宣言側の敵陣三段目以内の駒は、玉を除いて10枚以上存在する。 + int p1 = (Pieces(us) & ef).PopCount(); + // p1には玉も含まれているから11枚以上ないといけない + if (p1 < 11) + return Move.NONE; + + // 敵陣にいる大駒の数 + int p2 = ((Pieces(us, Piece.BISHOP_HORSE, Piece.ROOK_DRAGON)) & ef).PopCount(); + + // 小駒1点、大駒5点、玉除く + // = 敵陣の自駒 + 敵陣の自駒の大駒×4 - 玉 + + // (c) + // ・先手の場合28点以上の持点がある。 + // ・後手の場合27点以上の持点がある。 + Hand h = Hand(us); + int score = p1 + p2 * 4 - 1 + + h.Count(Piece.PAWN) + h.Count(Piece.LANCE) + h.Count(Piece.KNIGHT) + h.Count(Piece.SILVER) + + h.Count(Piece.GOLD) + (h.Count(Piece.BISHOP) + h.Count(Piece.ROOK)) * 5; + + // rule==EKR_27_POINTならCSAルール。rule==EKR_24_POINTなら24点法(30点以下引き分けなので31点以上あるときのみ勝ち扱いとする) + if (score < (rule == EnteringKingRule.POINT27 ? (us == Color.BLACK ? 28 : 27) : 31)) + return Move.NONE; + + // 評価関数でそのまま使いたいので非0のときは駒点を返しておく。 + return Move.WIN; + } + + // 非手番側がトライルールの条件を満たしているか。 + case EnteringKingRule.TRY_RULE: + { + Color them = sideToMove.Not(); + Square king_try_sq = (them == Color.BLACK ? Square.SQ_51 : Square.SQ_59); + + Square king_sq = KingSquare(them); + /* + // 1) 初期陣形で敵玉がいた場所に自玉が移動できるか。 + if ((Bitboard.KingEffect(king_sq) & king_try_sq).IsZero()) + return Move.NONE; + + // 2) トライする升に自駒がないか。 + if ((Pieces(us) & king_try_sq).IsNotZero()) + return Move.NONE; + + // 3) トライする升に移動させたときに相手に取られないか。 + if (EffectedTo(us.Not(), king_try_sq, king_sq)) + return Move.NONE; + + // 王の移動の指し手により勝ちが確定する + return Util.MakeMove(king_sq, king_try_sq); + */ + if (king_try_sq == king_sq) + return Move.WIN_THEM; + + return Move.NONE; + } + + } + + return Move.NONE; + } + + /// + /// 盤面と手駒、手番を与えて、そのsfenを返す。 + /// + /// + /// + /// + /// + /// + /// + /// + public static string SfenFromRawdata(Piece[/*81*/] board, Hand[/*2 or 3*/] hands, Color turn, int gamePly) + { + // 内部的な構造体にコピーして、sfen()を呼べば、変換過程がそこにしか依存していないならば + // これで正常に変換されるのでは…。 + var pos = new Position(); + + Array.Copy(board, pos.board , 81); + Array.Copy(hands, pos.hands , 2); + pos.sideToMove = turn; + pos.gamePly = gamePly; + + return pos.ToSfen(); + + // ↑の実装、美しいが、いかんせん遅い。 + // 棋譜を大量に読み込ませて学習させるときにここがボトルネックになるので直接unpackする関数を書く。(べき) + } + + /// + /// RawPositionを与えて、sfen文字列を生成して返す。 + /// + /// + /// + public static string SfenFromRawPosition(RawPosition raw) + { + var pos = new Position(); + + Array.Copy(raw.board, pos.board, 81); + Array.Copy(raw.hands, pos.hands, 2); + pos.sideToMove = raw.sideToMove; + pos.gamePly = raw.gamePly; + + return pos.ToSfen(); + } + + /// + /// 局面が合法かどうかをチェックする。 + /// 次の4つをチェックする。 + /// ・二歩 + /// ・行き場のない駒 + /// ・非手番側への王手がかかっている + /// ・同じ手番側の玉が2枚ある。もしくは、3枚以上の玉がある。(引数のfor_mateがtrueのときは詰将棋用なので単玉は可) + /// 次の項目はチェックしない + /// ・千日手の成立 + /// ・駒が足りない(銀が3枚など) + /// + /// 詰将棋用であるか。trueであれば単玉は可。 + /// 合法であったならnull。非合法であったなら、その内容が文字列として返る。 + public string IsValid(bool for_mate = false) + { + // 1.二歩のチェック + foreach (var c in All.Colors()) + { + var pawn_bb = Pieces(c, Piece.PAWN); + foreach (var f in All.Files()) + { + if ((pawn_bb & Bitboard.FileBB(f)).PopCount() >= 2) + return $"{f.Pretty()}筋に{c.Pretty()}の歩が2枚あります。(二歩)"; + } + } + + // 2.行き場のない駒 + { + // 歩、香、桂に対してチェックするより、全駒を列挙して行き場が本当にないのかを + // チェックするほうが美しいコードになるが…。 + + // 駒種を限定して、1,2,3段目に限定して全駒列挙して行き場を調べるか…。 + var bb = Pieces(Piece.PAWN, Piece.LANCE, Piece.KNIGHT) & + (Bitboard.EnemyField(Color.BLACK) | Bitboard.EnemyField(Color.WHITE)); + + var zero_bb = Bitboard.ZeroBB(); + foreach(var sq in bb) + { + var pc = PieceOn(sq); + if (Bitboard.EffectsFrom(pc, sq, zero_bb).IsZero()) + return $"{sq.Pretty()}の{pc.PieceColor().Pretty()}の{pc.PieceType().Pretty2()}に移動できる升がないです。"; + } + } + + // 3.非手番側への王手 + { + var them = sideToMove.Not(); + var them_king = KingSquare(them); + if (them_king != Square.NB && EffectedTo(sideToMove, them_king)) + return $"非手番側である{them.Pretty()}に王手がかかっています。"; + } + + // 4.単玉など + { + var king_bb = Pieces(Piece.HDK) & ~(Pieces(Piece.BISHOP_HORSE) | Pieces(Piece.ROOK_DRAGON)); + int k = king_bb.PopCount(); + if (k == 0) + return "盤上に玉がありません。"; + + if (k == 1 && !for_mate) + return "盤上に玉が1枚しかありません。"; + + if (k == 2) + { + // 同じ手番側の玉が配置されているかも知れん。 + var k1_sq = king_bb.Pop(); + var k2_sq = king_bb.Pop(); + if (PieceOn(k1_sq) == PieceOn(k2_sq)) + return $"盤上に{PieceOn(k1_sq).PieceColor().Pretty()}側の玉が2枚あります。"; + } + + if (k >= 3) + return $"盤上に玉が{k}枚あります。"; + } + + return null; + } + + // ------------------------------------------------------------------------- + // 利き + // ------------------------------------------------------------------------- + + /// + /// 現局面でsqに利いているC側の駒を列挙する + /// + /// + /// + /// + public Bitboard AttackersTo(Color c, Square sq) + { + return AttackersTo(c, sq, Pieces()); + } + + public Bitboard AttackersTo(Color c, Square sq, Bitboard occ) + { + // assert(is_ok(c) && sq <= SQ_NB); + + Color them = c.Not(); + + // sの地点に敵駒ptをおいて、その利きに自駒のptがあればsに利いているということだ。 + // 香の利きを求めるコストが惜しいのでrookEffect()を利用する。 + return + ((Bitboard.PawnEffect(them, sq) & Pieces(Piece.PAWN)) + | (Bitboard.KnightEffect(them, sq) & Pieces(Piece.KNIGHT)) + | (Bitboard.SilverEffect(them, sq) & Pieces(Piece.SILVER_HDK)) + | (Bitboard.GoldEffect(them, sq) & Pieces(Piece.GOLDS_HDK)) + | (Bitboard.BishopEffect(sq, occ) & Pieces(Piece.BISHOP_HORSE)) + | (Bitboard.RookEffect(sq, occ) & ( + Pieces(Piece.ROOK_DRAGON) + | (Bitboard.LanceStepEffect(them, sq) & Pieces(Piece.LANCE)) + )) + // | (kingEffect(sq) & pieces(c, HDK)); + // → HDKは、銀と金のところに含めることによって、参照するテーブルを一個減らして高速化しようというAperyのアイデア。 + ) & Pieces(c); // 先後混在しているのでc側の駒だけ最後にマスクする。 + ; + + } + + /// + /// 現局面でsqに利いている駒を列挙する + /// + /// + /// + public Bitboard AttackersTo(Square sq) + { + return AttackersTo(sq, Pieces()); + } + + public Bitboard AttackersTo(Square sq, Bitboard occ) + { + // ASSERT_LV3(sq <= SQ_NB); + + // sqの地点に敵駒ptをおいて、その利きに自駒のptがあればsqに利いているということだ。 + return + // 先手の歩・桂・銀・金・HDK + (((Bitboard.PawnEffect(Color.WHITE, sq) & Pieces(Piece.PAWN)) + | (Bitboard.KnightEffect(Color.WHITE, sq) & Pieces(Piece.KNIGHT)) + | (Bitboard.SilverEffect(Color.WHITE, sq) & Pieces(Piece.SILVER_HDK)) + | (Bitboard.GoldEffect(Color.WHITE, sq) & Pieces(Piece.GOLDS_HDK)) + ) & Pieces(Color.BLACK)) + | + + // 後手の歩・桂・銀・金・HDK + (((Bitboard.PawnEffect(Color.BLACK, sq) & Pieces(Piece.PAWN)) + | (Bitboard.KnightEffect(Color.BLACK, sq) & Pieces(Piece.KNIGHT)) + | (Bitboard.SilverEffect(Color.BLACK, sq) & Pieces(Piece.SILVER_HDK)) + | (Bitboard.GoldEffect(Color.BLACK, sq) & Pieces(Piece.GOLDS_HDK)) + ) & Pieces(Color.WHITE)) + + // 先後の角・飛・香 + | (Bitboard.BishopEffect(sq, occ) & Pieces(Piece.BISHOP_HORSE)) + | (Bitboard.RookEffect(sq, occ) & ( + Pieces(Piece.ROOK_DRAGON) + | (Pieces(Color.BLACK, Piece.LANCE) & Bitboard.LanceStepEffect(Color.WHITE, sq)) + | (Pieces(Color.WHITE, Piece.LANCE) & Bitboard.LanceStepEffect(Color.BLACK, sq)) + // 香も、StepEffectでマスクしたあと飛車の利きを使ったほうが香の利きを求めなくて済んで速い。 + )); + } + + /// + /// 打ち歩詰め判定に使う。王に打ち歩された歩の升をpawn_sqとして、c側(王側)のpawn_sqへ利いている駒を列挙する。香が利いていないことは自明。 + /// + /// + /// + /// + public Bitboard AttackersToPawn(Color c, Square pawn_sq) + { + // ASSERT_LV3(is_ok(c) && pawn_sq <= SQ_NB); + + Color them = c.Not(); + Bitboard occ = Pieces(); + + // 馬と龍 + Bitboard bb_hd = Bitboard.KingEffect(pawn_sq) & Pieces(Piece.HORSE, Piece.DRAGON); + // 馬、龍の利きは考慮しないといけない。しかしここに玉が含まれるので玉は取り除く必要がある。 + // bb_hdは銀と金のところに加えてしまうことでテーブル参照を一回減らす。 + + // sの地点に敵駒ptをおいて、その利きに自駒のptがあればsに利いているということだ。 + // 打ち歩詰め判定なので、その打たれた歩を歩、香、王で取れることはない。(王で取れないことは事前にチェック済) + return + ((Bitboard.KnightEffect(them, pawn_sq) & Pieces(Piece.KNIGHT)) + | (Bitboard.SilverEffect(them, pawn_sq) & (Pieces(Piece.SILVER) | bb_hd)) + | (Bitboard.GoldEffect(them, pawn_sq) & (Pieces(Piece.GOLDS) | bb_hd)) + | (Bitboard.BishopEffect(pawn_sq, occ) & Pieces(Piece.BISHOP_HORSE)) + | (Bitboard.RookEffect(pawn_sq, occ) & Pieces(Piece.ROOK_DRAGON)) + ) & Pieces(c); + } + + /// + /// attackers_to()で駒があればtrueを返す版。(利きの情報を持っているなら、軽い実装に変更できる) + /// + /// + /// + /// + public bool EffectedTo(Color c, Square sq) + { + return AttackersTo(c, sq, Pieces()).IsNotZero(); + } + + /// + /// kingSqの地点からは玉を取り除いての利きの判定を行なう。 + /// + /// + /// + /// + /// + public bool EffectedTo(Color c, Square sq, Square kingSq) + { + return AttackersTo(c, sq, Pieces() ^ kingSq).IsNotZero(); + } + + // ------------------------------------------------------------------------- + // 以下、private methods + // ------------------------------------------------------------------------- + + /// + /// StateInfoの値を初期化する。 + /// やねうら王から移植 + /// + /// + private void SetState(StateInfo si) + { + // --- bitboard + + // この局面で自玉に王手している敵駒 + //st->checkersBB = attackers_to(~sideToMove, king_square(sideToMove)); + + // 王手情報の初期化 + //set_check_info < false > (si); + + // --- hash keyの計算 + si.key = sideToMove == Color.BLACK ? Zobrist.Zero : Zobrist.Side; + foreach (var sq in All.Squares()) + { + var pc = PieceOn(sq); + si.key += Zobrist.Psq(sq, pc); + } + foreach (var c in All.Colors()) + for (Piece pr = Piece.PAWN; pr < Piece.HAND_NB; ++pr) + si.key += Zobrist.Hand(c, pr) * Hand(c).Count(pr); // 手駒はaddにする(差分計算が楽になるため) + } + + /// + /// 盤面上のsqの升にpcを置く。 + /// そこが空き升でなければ例外を投げる + /// + /// + /// + private void PutPiece(Square sq, Piece pc , PieceNo pn) + { + if (!sq.IsOk() || PieceOn(sq) != Piece.NO_PIECE) + throw new PositionException("PutPiece(" + sq.Pretty() + "," + pc.Pretty() +")に失敗しました。"); + + PieceOn(sq) = pc; + PieceNoOn(sq) = pn; + + // 玉であれば、KingSquareを更新する + if (pc.PieceType() == Piece.KING) + KingSquare(pc.PieceColor()) = sq; + + XorPiece(pc, sq); + } + + /// + /// 盤上のsqの升から駒を取り除く。sqにあった駒が返る。 + /// そこに駒がなければ例外を投げる + /// + /// + /// + private Piece RemovePiece(Square sq) + { + if (!sq.IsOk() || PieceOn(sq) == Piece.NO_PIECE) + throw new PositionException("RemovePieceに失敗しました。"); + + Piece pc = PieceOn(sq); + PieceOn(sq) = Piece.NO_PIECE; + + // 玉であれば、KingSquareを更新する + if (pc.PieceType() == Piece.KING) + KingSquare(pc.PieceColor()) = Square.NB; + + XorPiece(pc, sq); + + return pc; + } + + /// + /// 駒を置く/取り除くときに呼び出すと、byColorBB,byTypeBBを更新する。 + /// + /// + /// + private void XorPiece(Piece pc, Square sq) + { + // 先手・後手の駒のある場所を示すoccupied bitboardの更新 + byColorBB[(int)pc.PieceColor()] ^= sq; + + // 先手 or 後手の駒のある場所を示すoccupied bitboardの更新 + byTypeBB[(int)Piece.ALL_PIECES] ^= sq; + + // 駒別のBitboardの更新 + // これ以外のBitboardの更新は、update_bitboards()で行なう。 + byTypeBB[(int)pc.PieceType()] ^= sq; + } + + /// + /// put_piece(),remove_piece(),xor_piece()を用いたあとに呼び出す必要がある。 + /// + void UpdateBitboards() + { + // 王・馬・龍を合成したbitboard + byTypeBB[(int)Piece.HDK] = Pieces(Piece.KING, Piece.HORSE, Piece.DRAGON); + + // 金と同じ移動特性を持つ駒 + byTypeBB[(int)Piece.GOLDS] = Pieces(Piece.GOLD, Piece.PRO_PAWN, Piece.PRO_LANCE, Piece.PRO_KNIGHT, Piece.PRO_SILVER); + + // 以下、attackers_to()で頻繁に用いるのでここで1回計算しておいても、トータルでは高速化する。 + + // 角と馬 + byTypeBB[(int)Piece.BISHOP_HORSE] = Pieces(Piece.BISHOP, Piece.HORSE); + + // 飛車と龍 + byTypeBB[(int)Piece.ROOK_DRAGON] = Pieces(Piece.ROOK, Piece.DRAGON); + + // 銀とHDK + byTypeBB[(int)Piece.SILVER_HDK] = Pieces(Piece.SILVER, Piece.HDK); + + // 金相当の駒とHDK + byTypeBB[(int)Piece.GOLDS_HDK] = Pieces(Piece.GOLDS, Piece.HDK); + } + + /// + /// 升sに対して、c側の大駒に含まれる長い利きを持つ駒の利きを遮っている駒のBitboardを返す(先後の区別なし) + /// ※ Stockfishでは、sildersを渡すようになっているが、大駒のcolorを渡す実装のほうが優れているので変更。 + /// [Out] pinnersとは、pinされている駒が取り除かれたときに升sに利きが発生する大駒である。これは返し値。 + /// また、升sにある玉は~c側のKINGであるとする。 + /// + /// + /// + /// + /// + public Bitboard SliderBlockers(Color c, Square s, Bitboard pinners) + { + Bitboard result = Bitboard.ZeroBB(); + + // pinnersは返し値。 + pinners = Bitboard.ZeroBB(); + + // cが与えられていないと香の利きの方向を確定させることが出来ない。 + // ゆえに将棋では、この関数は手番を引数に取るべき。(チェスとはこの点において異なる。) + + // snipersとは、pinされている駒が取り除かれたときに升sに利きが発生する大駒である。 + Bitboard snipers = + ((Pieces(Piece.ROOK_DRAGON) & Bitboard.RookStepEffect(s)) + | (Pieces(Piece.BISHOP_HORSE) & Bitboard.BishopStepEffect(s)) + // 香に関しては攻撃駒が先手なら、玉より下側をサーチして、そこにある先手の香を探す。 + | (Pieces(Piece.LANCE) & Bitboard.LanceStepEffect(c.Not(), s)) + ) & Pieces(c); + + while (snipers.IsNotZero()) + { + Square sniperSq = snipers.Pop(); + Bitboard b = Bitboard.BetweenBB(s, sniperSq) & Pieces(); + + // snipperと玉との間にある駒が1個であるなら。 + // (間にある駒が0個の場合、b == ZERO_BBとなり、何も変化しない。) + if (!Bitboard.MoreThanOne(b)) + { + result |= b; + if ((b & Pieces(c.Not())).IsNotZero()) + // sniperと玉に挟まれた駒が玉と同じ色の駒であるなら、pinnerに追加。 + pinners |= sniperSq; + } + } + return result; + } + + + /// + /// StateInfoの初期化(初期化するときに内部的に用いる) + /// + /// + private void SetCheckInfo(StateInfo si) + { + // --- bitboard + + // この局面で自玉に王手している敵駒 + st.checkersBB = AttackersTo(sideToMove.Not(), KingSquare(sideToMove)); + + // -- 王手情報の初期化 + + //: si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE),si->pinnersForKing[WHITE]); + //: si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK),si->pinnersForKing[BLACK]); + + // ↓Stockfishのこの部分の実装、将棋においては良くないので、以下のように変える。 + + //if (!doNullMove) + { + // null moveのときは前の局面でこの情報は設定されているので更新する必要がない。 + si.blockersForKing[(int)Color.WHITE] = SliderBlockers(Color.BLACK, KingSquare(Color.WHITE), si.pinnersForKing[(int)Color.WHITE]); + si.blockersForKing[(int)Color.BLACK] = SliderBlockers(Color.WHITE, KingSquare(Color.BLACK), si.pinnersForKing[(int)Color.BLACK]); + } + + Square ksq = KingSquare(sideToMove.Not()); + + // 駒種Xによって敵玉に王手となる升のbitboard + + // 歩であれば、自玉に敵の歩を置いたときの利きにある場所に自分の歩があればそれは敵玉に対して王手になるので、 + // そういう意味で(ksq,them)となっている。 + + Bitboard occ = Pieces(); + Color them = sideToMove.Not(); + + // この指し手が二歩でないかは、この時点でテストしない。指し手生成で除外する。なるべくこの手のチェックは遅延させる。 + si.checkSquares[(int)Piece.PAWN] = Bitboard.PawnEffect(them, ksq); + si.checkSquares[(int)Piece.KNIGHT] = Bitboard.KnightEffect(them, ksq); + si.checkSquares[(int)Piece.SILVER] = Bitboard.SilverEffect(them, ksq); + si.checkSquares[(int)Piece.BISHOP] = Bitboard.BishopEffect(ksq, occ); + si.checkSquares[(int)Piece.ROOK] = Bitboard.RookEffect(ksq, occ); + si.checkSquares[(int)Piece.GOLD] = Bitboard.GoldEffect(them, ksq); + + // 香で王手になる升は利きを求め直さずに飛車で王手になる升を香のstep effectでマスクしたものを使う。 + si.checkSquares[(int)Piece.LANCE] = si.checkSquares[(int)Piece.ROOK] & Bitboard.LanceStepEffect(them, ksq); + + // 王を移動させて直接王手になることはない。それは自殺手である。 + si.checkSquares[(int)Piece.KING] = Bitboard.ZeroBB(); + + // 成り駒。この初期化は馬鹿らしいようだが、gives_check()は指し手ごとに呼び出されるので、その処理を軽くしたいので + // ここでの初期化は許容できる。(このコードはdo_move()に対して1回呼び出されるだけなので) + si.checkSquares[(int)Piece.PRO_PAWN] = si.checkSquares[(int)Piece.GOLD]; + si.checkSquares[(int)Piece.PRO_LANCE] = si.checkSquares[(int)Piece.GOLD]; + si.checkSquares[(int)Piece.PRO_KNIGHT] = si.checkSquares[(int)Piece.GOLD]; + si.checkSquares[(int)Piece.PRO_SILVER] = si.checkSquares[(int)Piece.GOLD]; + si.checkSquares[(int)Piece.HORSE] = si.checkSquares[(int)Piece.BISHOP] | Bitboard.KingEffect(ksq); + si.checkSquares[(int)Piece.DRAGON] = si.checkSquares[(int)Piece.ROOK] | Bitboard.KingEffect(ksq); + + if (st.previous != null) + { + // DoMove()前の手番 == them + var us = them.Not(); + + // 王手しているなら連続王手の値を更新する + st.continuousCheck[(int)them] = (st.checkersBB.IsNotZero()) ? st.previous.continuousCheck[(int)them] + 2 : 0; + + // 相手番のほうは関係ないので前ノードの値をそのまま受け継ぐ。 + st.continuousCheck[(int)us] = st.previous.continuousCheck[(int)us]; + } + } + } +} diff --git a/Assets/Plugins/MyShogi/Core/Position.cs.meta b/Assets/Plugins/MyShogi/Core/Position.cs.meta new file mode 100644 index 0000000..7aee453 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Position.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ff02f2d7e5384bbe902f1b0a8ecda08 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Rank.cs b/Assets/Plugins/MyShogi/Core/Rank.cs new file mode 100644 index 0000000..4abd665 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Rank.cs @@ -0,0 +1,102 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 段を表現する型 + /// 例) RANK_4なら4段目。 + /// + public enum Rank : Int32 + { + RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, NB, ZERO = 0 + }; + + /// + /// Rankに関するextension methodsを書くクラス + /// + public static class RankExtensions + { + public static bool IsOk(this Rank r) + { + return Rank.ZERO <= r && r < Rank.NB; + } + + /// + /// Rankを綺麗に出力する(USI形式ではない) + /// 日本語文字での表示になる。例 → 八 + /// + /// + /// + public static string Pretty(this Rank r) + { + // C#では全角1文字が1つのcharなので注意。 + return "一二三四五六七八九".Substring((int)r.ToInt(), 1); + } + + /// + /// USI文字列に変換する。 + /// + /// + /// + public static string ToUsi(this Rank r) + { + return new string((char)((Int32)'a' + r.ToInt()), 1); + } + + /// + /// Int32型への変換子 + /// + /// + /// + public static Int32 ToInt(this Rank r) + { + return (Int32)r; + } + + /// + /// USIの指し手文字列などで筋を表す文字列をここで定義されたRankに変換する。 + /// + /// + /// + public static Rank ToRank(this char c) + { + return (Rank)(c - 'a'); + } + } + + /// + /// Model.Shogi用のヘルパークラス + /// + public static partial class Util + { + /// + /// 移動元、もしくは移動先の升のrankを与えたときに、そこが成れるかどうかを判定する。 + /// + /// + /// + /// + public static bool CanPromote(Color c, Rank fromOrToRank) + { + // ASSERT_LV1(is_ok(c) && is_ok(fromOrToRank)); + // 先手9bit(9段) + 後手9bit(9段) = 18bitのbit列に対して、判定すればいい。 + // ただし ×9みたいな掛け算をするのは嫌なのでbit shiftで済むように先手16bit、後手16bitの32bitのbit列に対して判定する。 + // このcastにおいて、VC++2015ではwarning C4800が出る。 + return (0x1c00007u & (1u << (int)((c.ToInt() << 4) + fromOrToRank.ToInt()))) != 0; + } + + /// + /// 段を表現するUSI文字列をRankに変換する + /// 変換できないときはRank.NBが返る。 + /// + /// + /// + public static Rank FromUsiRank(char c) + { + Rank r = (Rank)((int)c - (int)'a'); + if (!r.IsOk()) + r = Rank.NB; + return r; + } + } + +} diff --git a/Assets/Plugins/MyShogi/Core/Rank.cs.meta b/Assets/Plugins/MyShogi/Core/Rank.cs.meta new file mode 100644 index 0000000..161599b --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Rank.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5000e3a9897bf480b9ef47f6c8b5745c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/RepetitionState.cs b/Assets/Plugins/MyShogi/Core/RepetitionState.cs new file mode 100644 index 0000000..3e13646 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/RepetitionState.cs @@ -0,0 +1,16 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 千日手であるかを表現する + /// Position.IsRepetition()の返し値 + /// + public enum RepetitionState : Int32 + { + NONE = 0, // 千日手ではない + DRAW = 1, // 千日手 + WIN = 2, // 連続王手の千日手を相手が行った(ので手番側の勝ちの局面) + LOSE = 3, // 連続王手の千日手を自分が行った(ので手番側の負けの局面) + }; +} diff --git a/Assets/Plugins/MyShogi/Core/RepetitionState.cs.meta b/Assets/Plugins/MyShogi/Core/RepetitionState.cs.meta new file mode 100644 index 0000000..b875616 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/RepetitionState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dcda518276b024a27a1b3824e4d212f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Sfens.cs b/Assets/Plugins/MyShogi/Core/Sfens.cs new file mode 100644 index 0000000..07626b4 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Sfens.cs @@ -0,0 +1,33 @@ +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 平手、駒落ちなどのsfen文字列 + /// + public static class Sfens + { + /// + /// それぞれの意味については、BoardTypeのenumの定義を見ること + /// + public static readonly string HIRATE = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1"; + public static readonly string HANDICAP_KYO = "lnsgkgsn1/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_RIGHT_KYO = "1nsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_KAKU = "lnsgkgsnl/1r7/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_HISYA = "lnsgkgsnl/7b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_HISYA_KYO = "lnsgkgsn1/7b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_2 = "lnsgkgsnl/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_3 = "lnsgkgsn1/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_4 = "1nsgkgsn1/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_5 = "2sgkgsn1/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_LEFT_5 = "1nsgkgs2/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_6 = "2sgkgs2/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_8 = "3gkg3/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_10 = "4k4/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w - 1"; + public static readonly string HANDICAP_PAWN3 = "4k4/9/9/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w 3p 1"; + + public static readonly string MATE_1 = "4k4/9/9/9/9/9/9/9/9 b 18p4l4n4s4g2b2r 1"; + public static readonly string MATE_2 = "4k4/9/9/9/9/9/9/9/4K4 b 18p4l4n4s4g2b2r 1"; + public static readonly string MATE_3 = "4k4/9/9/9/9/9/9/9/4K4 b - 1"; + + // あとで何か追加するかも。 + } +} diff --git a/Assets/Plugins/MyShogi/Core/Sfens.cs.meta b/Assets/Plugins/MyShogi/Core/Sfens.cs.meta new file mode 100644 index 0000000..a05d35d --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Sfens.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07539cd7188334adb9375d4569cf4ff5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Square.cs b/Assets/Plugins/MyShogi/Core/Square.cs new file mode 100644 index 0000000..4bbe3e0 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Square.cs @@ -0,0 +1,240 @@ +using System; +using System.Diagnostics; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 盤上の升を表現する型 + /// + public enum Square + { + // 以下、盤面の右上から左下までの定数。 + // これを定義していなくとも問題ないのだが、デバッガでSquare型を見たときに + // どの升であるかが表示されることに価値がある。 + SQ_11, SQ_12, SQ_13, SQ_14, SQ_15, SQ_16, SQ_17, SQ_18, SQ_19, + SQ_21, SQ_22, SQ_23, SQ_24, SQ_25, SQ_26, SQ_27, SQ_28, SQ_29, + SQ_31, SQ_32, SQ_33, SQ_34, SQ_35, SQ_36, SQ_37, SQ_38, SQ_39, + SQ_41, SQ_42, SQ_43, SQ_44, SQ_45, SQ_46, SQ_47, SQ_48, SQ_49, + SQ_51, SQ_52, SQ_53, SQ_54, SQ_55, SQ_56, SQ_57, SQ_58, SQ_59, + SQ_61, SQ_62, SQ_63, SQ_64, SQ_65, SQ_66, SQ_67, SQ_68, SQ_69, + SQ_71, SQ_72, SQ_73, SQ_74, SQ_75, SQ_76, SQ_77, SQ_78, SQ_79, + SQ_81, SQ_82, SQ_83, SQ_84, SQ_85, SQ_86, SQ_87, SQ_88, SQ_89, + SQ_91, SQ_92, SQ_93, SQ_94, SQ_95, SQ_96, SQ_97, SQ_98, SQ_99, + + // ゼロと末尾 + ZERO = 0, NB = 81, + NB_PLUS1 = NB + 1, // 玉がいない場合、SQ_NBに移動したものとして扱うため、配列をSQ_NB+1で確保しないといけないときがあるのでこの定数を用いる。 + + // 方角に関する定数。StockfishだとNORTH=北=盤面の下を意味するようだが、 + // わかりにくいのでやねうら王ではストレートな命名に変更する。 + SQ_D = +1, // 下(Down) + SQ_R = -9, // 右(Right) + SQ_U = -1, // 上(Up) + SQ_L = +9, // 左(Left) + + // 斜めの方角などを意味する定数。 + SQ_RU = SQ_U + SQ_R, // 右上(Right Up) + SQ_RD = SQ_D + SQ_R, // 右下(Right Down) + SQ_LU = SQ_U + SQ_L, // 左上(Left Up) + SQ_LD = SQ_D + SQ_L, // 左下(Left Down) + SQ_RUU = SQ_RU + SQ_U, // 右上上 + SQ_LUU = SQ_LU + SQ_U, // 左上上 + SQ_RDD = SQ_RD + SQ_D, // 右下下 + SQ_LDD = SQ_LD + SQ_D, // 左下下 + } + + /// + /// Square型のためのextension methods + /// + public static class SquareExtensions + { + /// + /// 値の範囲が正常か調べる。 + /// + /// + /// + public static bool IsOk(this Square sq) + { + return Square.ZERO <= sq && sq < Square.NB; + } + + /// + /// sqが盤面の内側を指しているかを判定する。assert()などで使う用。玉は盤上にないときにSQ_NBを取るのでこの関数が必要。 + /// + /// + /// + public static bool IsOkPlus1(this Square sq) + { + return Square.ZERO <= sq && sq < Square.NB_PLUS1; + } + + // 与えられたSquareに対応する筋を返すテーブル。ToFile()で用いる。 + private static readonly File[] SquareToFile_ = + { + File.FILE_1, File.FILE_1, File.FILE_1, File.FILE_1, File.FILE_1, File.FILE_1, File.FILE_1, File.FILE_1, File.FILE_1, + File.FILE_2, File.FILE_2, File.FILE_2, File.FILE_2, File.FILE_2, File.FILE_2, File.FILE_2, File.FILE_2, File.FILE_2, + File.FILE_3, File.FILE_3, File.FILE_3, File.FILE_3, File.FILE_3, File.FILE_3, File.FILE_3, File.FILE_3, File.FILE_3, + File.FILE_4, File.FILE_4, File.FILE_4, File.FILE_4, File.FILE_4, File.FILE_4, File.FILE_4, File.FILE_4, File.FILE_4, + File.FILE_5, File.FILE_5, File.FILE_5, File.FILE_5, File.FILE_5, File.FILE_5, File.FILE_5, File.FILE_5, File.FILE_5, + File.FILE_6, File.FILE_6, File.FILE_6, File.FILE_6, File.FILE_6, File.FILE_6, File.FILE_6, File.FILE_6, File.FILE_6, + File.FILE_7, File.FILE_7, File.FILE_7, File.FILE_7, File.FILE_7, File.FILE_7, File.FILE_7, File.FILE_7, File.FILE_7, + File.FILE_8, File.FILE_8, File.FILE_8, File.FILE_8, File.FILE_8, File.FILE_8, File.FILE_8, File.FILE_8, File.FILE_8, + File.FILE_9, File.FILE_9, File.FILE_9, File.FILE_9, File.FILE_9, File.FILE_9, File.FILE_9, File.FILE_9, File.FILE_9, + File.NB, // 玉が盤上にないときにこの位置に移動させることがあるので + }; + + /// + /// Int32型に変換する。 + /// + /// + /// + public static Int32 ToInt(this Square sq) + { + return (Int32)sq; + } + + /// + /// その升の属する筋を返す。 + /// + /// Square.NBに対してはFile.NBが返る。 + /// + /// + /// + public static File ToFile(this Square sq) + { + Debug.Assert(sq.IsOkPlus1()); + + return SquareToFile_[sq.ToInt()]; + } + + /// + /// SquareからRankに変換するためのテーブル。 + /// + private static readonly Rank[] SquareToRank_ = + { + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.RANK_1, Rank.RANK_2, Rank.RANK_3, Rank.RANK_4, Rank.RANK_5, Rank.RANK_6, Rank.RANK_7, Rank.RANK_8, Rank.RANK_9, + Rank.NB, // 玉が盤上にないときにこの位置に移動させることがあるので + }; + + /// + /// その升の属する段を返す。 + /// + /// Square.NBに対してはRank.NBが返る。 + /// + /// + /// + public static Rank ToRank(this Square sq) + { + Debug.Assert(sq.IsOkPlus1()); + + return SquareToRank_[sq.ToInt()]; + } + + /// + /// 盤面を180°回したときの升目を返す + /// + /// + /// + public static Square Inv(this Square sq) + { + return (Square)(((int)Square.NB - 1) - sq.ToInt()); + } + + /// + /// 盤面をミラーしたときの升目を返す + /// + /// + /// + public static Square Mir(this Square sq) + { + return Util.MakeSquare( (File)(8 - sq.ToFile().ToInt()) , sq.ToRank()); + } + + /// + /// Squareを綺麗に出力する(USI形式ではない) + /// 日本語文字での表示になる。例 → 8八 + /// + /// + /// + public static string Pretty(this Square sq) + { + if (sq == Square.NB) + return "NB"; + + return sq.ToFile().Pretty() + sq.ToRank().Pretty(); + } + + /// + /// USI形式でSquareを出力する + /// + /// + /// + public static string ToUsi(this Square sq) + { + return sq.ToFile().ToUsi() + sq.ToRank().ToUsi(); + } + + /// + /// "76"のような半角数字形式の升表現にする。 + /// + /// + /// + public static string ToNumString(this Square sq) + { + return $"{((int)sq.ToFile() + 1).ToString()}{((int)sq.ToRank() + 1).ToString()}"; + } + } + + /// + /// Model.Shogi用のヘルパークラス + /// + public static partial class Util + { + /// + // 移動元、もしくは移動先の升sqを与えたときに、そこが成れるかどうかを判定する。 + /// + /// + /// + /// + public static bool CanPromote(Color c, Square fromOrTo) + { + return CanPromote(c, fromOrTo.ToRank()); + } + + /// + /// 筋と段から升を表す値を返す。 + /// + /// + /// + /// + public static Square MakeSquare(File f, Rank r) + { + return (Square)(f.ToInt() * 9 + r.ToInt()); + } + + /// + /// USIの升表現文字列をSquare型に変換する + /// 変換できないときはSquare.NBが返る。 + /// + /// + /// + public static Square FromUsiSquare(char c1 , char c2) + { + File f = Util.FromUsiFile(c1); + Rank r = Util.FromUsiRank(c2); + + if (!f.IsOk() || !r.IsOk()) + return Square.NB; + + return MakeSquare(f, r); + } + } +} diff --git a/Assets/Plugins/MyShogi/Core/Square.cs.meta b/Assets/Plugins/MyShogi/Core/Square.cs.meta new file mode 100644 index 0000000..b9b208c --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Square.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3c75cefdfc1d451b9abfe72842e708c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/SquareHand.cs b/Assets/Plugins/MyShogi/Core/SquareHand.cs new file mode 100644 index 0000000..05c5f4e --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/SquareHand.cs @@ -0,0 +1,169 @@ +using System.Diagnostics; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 盤上の升と手駒の位置を表現する型 + /// + /// Square型だと手駒の場所が表現できないので、マウスでクリックした駒を表現するのに表現力が不足している。 + /// このため、手駒も含めて表現できる型が必要となる。 + /// + /// 例) + /// Hand : 先手の駒台の駒以外の升の領域を表現する + /// HandBlack + Piece.PAWN = 先手の駒台の歩 + /// 同様に + /// Piece pcに対して PieceBox+(int)pc は駒箱のpc + /// + public enum SquareHand + { + // 0~80まではSquare型と同じ + SquareZero = 0, // 盤上の升 + SquareNB = 81, // 盤上の升の終端+1 + + // 手駒 + Hand = 81, // 手駒のスタート + HandBlack = 81, // 先手の手駒のスタート + HandWhite = 81 + 8, // 後手の手駒のスタート + HandNB = 81 + 16, // 手駒の終端+1 + + // 駒箱 + + PieceBox = HandNB, // 駒箱の駒(NO_PIECE、歩、香、桂、銀、角、飛、金、玉の9種) + PieceBoxNB = PieceBox + 9,// 駒箱の終端+1 + + // ゼロと末尾 + ZERO = 0, NB = PieceBoxNB, + } + + /// + /// Square型のためのextension methods + /// + public static class SquareHandExtensions + { + /// + /// 値の範囲が正常か調べる。 + /// + /// + /// + public static bool IsOk(this SquareHand sq) + { + return SquareHand.ZERO <= sq && sq <= SquareHand.NB; + } + + /// + /// sqに対してどちらのColorの手駒を表現しているのかを返す。 + /// 盤上、駒箱の升に対して呼び出してはならない。 + /// + /// + /// + public static Color PieceColor(this SquareHand sq) + { + Debug.Assert(IsHandPiece(sq)); + + return (sq < SquareHand.HandWhite) ? Color.BLACK : Color.WHITE; + } + + /// + /// 盤上の升であるかを判定する。 + /// + /// + /// + public static bool IsBoardPiece(this SquareHand sq) + { + return sq < SquareHand.SquareNB; + } + + /// + /// 手駒であるかを判定する + /// + /// + /// + public static bool IsHandPiece(this SquareHand sq) + { + return SquareHand.Hand <= sq && sq < SquareHand.HandNB; + } + + /// + /// 駒箱の駒であるか判定する + /// + /// + /// + public static bool IsPieceBoxPiece(this SquareHand sq) + { + return SquareHand.PieceBox <= sq && sq < SquareHand.PieceBoxNB; + } + + /// + /// sqの手駒に対して、その駒種を返す + /// sqは手駒か駒箱の駒でないといけない。 + /// + /// + /// + /// 返し値について先後の区別はない。 + /// 手駒に対しては、Piece.NO_PIECE ~ Piece.GOLDまでの値が返る。(Piece.KINGは返らない) + /// 駒箱の駒に対しては、Piece.NO_PIECE ~ Piece.KINGまでの値が返る。 + /// + public static Piece ToPiece(this SquareHand sq) + { + Debug.Assert(! IsBoardPiece(sq) ); + + if (IsHandPiece(sq)) + return (sq < SquareHand.HandWhite) + ? (Piece)((sq - SquareHand.HandBlack)) + : (Piece)((sq - SquareHand.HandWhite)); + // is BoxPiece + return (Piece)(sq - SquareHand.PieceBox); + + } + + /// + /// Squareを綺麗に出力する(USI形式ではない) + /// 日本語文字での表示になる。例 → 8八 , 先手歩 (先手の手駒の歩) + /// + /// + /// + public static string Pretty(this SquareHand sq) + { + var c = PieceColor(sq); + + if (c == Color.NB) + return ((Square)sq).Pretty(); + + return c.Pretty() + ToPiece(sq).Pretty(); + } + + } + + public static partial class Util + { + /// + /// 引数で指定したColorとPieceに相当するSquareHand型の駒台の駒の値を生成する。 + /// + /// pc == NO_PIECEも許容する。 + /// + /// + /// + /// + public static SquareHand ToHandPiece(Color c , Piece pc) + { + Debug.Assert(Piece.NO_PIECE <= pc && pc < Piece.KING); + return (c == Color.BLACK ? SquareHand.HandBlack : SquareHand.HandWhite) + (int)pc; + } + + /// + /// 引数で指定したColorとPieceに相当するSquareHand型の駒箱の駒の値を生成する。 + /// + /// pc == NO_PIECEも許容する。 + /// + /// + /// + /// + public static SquareHand ToPieceBoxPiece(Piece pc) + { + Debug.Assert(Piece.NO_PIECE <= pc && pc <= Piece.KING); + return SquareHand.PieceBox + (int)pc; + } + + } + +} \ No newline at end of file diff --git a/Assets/Plugins/MyShogi/Core/SquareHand.cs.meta b/Assets/Plugins/MyShogi/Core/SquareHand.cs.meta new file mode 100644 index 0000000..510f71c --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/SquareHand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0688780b4de9408b8cad18233fb011a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/SquareWithWall.cs b/Assets/Plugins/MyShogi/Core/SquareWithWall.cs new file mode 100644 index 0000000..76112a8 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/SquareWithWall.cs @@ -0,0 +1,100 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + + // -------------------- + // 壁つきの升表現 + // -------------------- + + // This trick is invented by yaneurao in 2016. + + // 長い利きを更新するときにある升からある方向に駒にぶつかるまでずっと利きを更新していきたいことがあるが、 + // sqの升が盤外であるかどうかを判定する簡単な方法がない。そこで、Squareの表現を拡張して盤外であることを検出 + // できるようにする。 + + // bit 0..7 : Squareと同じ意味 + // bit 8 : Squareからのborrow用に1にしておく + // bit 9..13 : いまの升から盤外まで何升右に升があるか(ここがマイナスになるとborrowでbit13が1になる) + // bit 14..18 : いまの升から盤外まで何升上に(略 + // bit 19..23 : いまの升から盤外まで何升下に(略 + // bit 24..28 : いまの升から盤外まで何升左に(略 + public enum SquareWithWall : Int32 + { + // 相対移動するときの差分値 + SQWW_R = Square.SQ_R - (1 << 9) + (1 << 24), + SQWW_U = Square.SQ_U - (1 << 14) + (1 << 19), + SQWW_D = -(SQWW_U), + SQWW_L = -(SQWW_R), + SQWW_RU = (SQWW_R) + (SQWW_U), + SQWW_RD = (SQWW_R) + (SQWW_D), + SQWW_LU = (SQWW_L) + (SQWW_U), + SQWW_LD = (SQWW_L) + (SQWW_D), + + // SQ_11の地点に対応する値(他の升はこれ相対で事前に求めテーブルに格納) + SQWW_11 = Square.SQ_11 | (1 << 8) /* bit8 is 1 */ | (0 << 9) /*右に0升*/ | + (0 << 14) /*上に0升*/ | (8 << 19) /*下に8升*/ | (8 << 24) /*左に8升*/, + + // SQWW_RIGHTなどを足して行ったときに盤外に行ったときのborrow bitの集合 + SQWW_BORROW_MASK = (1 << 13) | (1 << 18) | (1 << 23) | (1 << 28), + }; + + + public static class SquareWithWallExtensions + { + /// + /// int型への変換 + /// + /// + /// + public static Int32 ToInt(this SquareWithWall sqww) + { + return (int)sqww; + } + + /// + /// Square型への型変換 + /// + /// + /// + public static Square ToSquare(this SquareWithWall sqww) + { + return (Square)(sqww.ToInt() & 0xff); + } + + /// + /// 盤内か。壁(盤外)だとfalseになる。 + /// + /// + /// + public static bool IsOk(this SquareWithWall sqww) + { + return (sqww.ToInt() & (int)SquareWithWall.SQWW_BORROW_MASK) == 0; + } + + /// + /// 型変換。Square型からSquareWithWall型に。 + /// + /// + /// + public static SquareWithWall ToSqww(this Square sq) { return sqww_table[sq.ToInt()]; } + + /// + /// ToSqww()で必要となるテーブル + /// [SQ_NB_PLUS1]まで + /// Bitboard.init()で初期化される。 + /// + public static SquareWithWall[] sqww_table = new SquareWithWall[(int)Square.NB_PLUS1]; + + /// + /// SQの示す升を出力する + /// + /// + /// + public static string Pretty(this SquareWithWall sqww) + { + return sqww.ToSquare().Pretty(); + } + } + +} diff --git a/Assets/Plugins/MyShogi/Core/SquareWithWall.cs.meta b/Assets/Plugins/MyShogi/Core/SquareWithWall.cs.meta new file mode 100644 index 0000000..5b5a36f --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/SquareWithWall.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4943beecdc96149cc82605168dadef68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/UInt128.cs b/Assets/Plugins/MyShogi/Core/UInt128.cs new file mode 100644 index 0000000..9b64984 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/UInt128.cs @@ -0,0 +1,111 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + /// + /// 128bit型 + /// C#ではサポートされていないので自前実装 + /// + public struct UInt128 + { + public UInt64 p0; + public UInt64 p1; + + /// + /// 64bitの値を2つ指定して初期化できるコンストラクタ + /// + /// + /// + public UInt128(UInt64 p0_,UInt64 p1_) + { + p0 = p0_; + p1 = p1_; + } + + /// + /// 64bitの値 2つで初期化する + /// + /// + /// + public void Set(UInt64 p0_,UInt64 p1_) + { + p0 = p0_; + p1 = p1_; + } + + public override bool Equals(object key) + { + UInt128 k = (UInt128)key; + return p0 == k.p0 && p1 == k.p1; + } + + public override int GetHashCode() + { + return (int)(p0^p1); + } + + /// + /// 上位64bitと下位64bitをbitwise orして、64bit整数にする + /// Bitboardで、演算の結果、1bitでも立っているかどうかを判定するときに用いる + /// + /// + public UInt64 ToU() + { + return p0 | p1; + } + + public static UInt128 operator +(UInt128 c1, UInt128 c2) + { + return new UInt128(c1.p0 + c2.p0, c1.p1 + c2.p1); + } + + public static UInt128 operator -(UInt128 c1, UInt128 c2) + { + return new UInt128(c1.p0 - c2.p0, c1.p1 - c2.p1); + } + + public static UInt128 operator &(UInt128 c1, UInt128 c2) + { + return new UInt128(c1.p0 & c2.p0, c1.p1 & c2.p1); + } + + public static UInt128 operator |(UInt128 c1, UInt128 c2) + { + return new UInt128(c1.p0 | c2.p0, c1.p1 | c2.p1); + } + + public static UInt128 operator ^(UInt128 c1, UInt128 c2) + { + return new UInt128(c1.p0 ^ c2.p0, c1.p1 ^ c2.p1); + } + + public static UInt128 operator *(UInt128 c1, int n) + { + return new UInt128(c1.p0 * (UInt64)n , c1.p1 *(UInt64)n); + } + + public static UInt128 operator <<(UInt128 c1, int n) + { + // このbit shiftは、p[0]とp[1]をまたがない。 + return new UInt128(c1.p0 << n, c1.p1 << n); + } + + public static UInt128 operator >>(UInt128 c1, int n) + { + // このbit shiftは、p[0]とp[1]をまたがない。 + return new UInt128(c1.p0 >> n, c1.p1 >> n); + } + + public static bool operator ==(UInt128 lhs , UInt128 rhs) + { + return lhs.p0 == rhs.p0 && lhs.p1 == rhs.p1; + } + + public static bool operator !=(UInt128 lhs , UInt128 rhs) + { + return !(lhs.p0 == rhs.p0 && lhs.p1 == rhs.p1); + } + + + } +} diff --git a/Assets/Plugins/MyShogi/Core/UInt128.cs.meta b/Assets/Plugins/MyShogi/Core/UInt128.cs.meta new file mode 100644 index 0000000..a395052 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/UInt128.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07704cdc3d50a49cf81d34f8fe0846f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/MyShogi/Core/Zobrist.cs b/Assets/Plugins/MyShogi/Core/Zobrist.cs new file mode 100644 index 0000000..5f7bb11 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Zobrist.cs @@ -0,0 +1,97 @@ +using System; + +namespace MyShogi.Model.Shogi.Core +{ + // 局面のhash keyを求めるときに用いるZobrist key + public static class Zobrist + { + public static HASH_KEY Zero; // ゼロ(==0) + public static HASH_KEY Side; // 手番(==1) + private static HASH_KEY[,] psq = new HASH_KEY[Square.NB_PLUS1.ToInt(),Piece.NB.ToInt()]; // 駒pcが盤上sqに配置されているときのZobrist Key + private static HASH_KEY[,] hand = new HASH_KEY[Color.NB.ToInt(),Piece.HAND_NB.ToInt()]; // c側の手駒prが一枚増えるごとにこれを加算するZobristKey + + /// + /// sqの升にpcがあるときのZobrist Key + /// + /// + /// + /// + public static HASH_KEY Psq(Square sq , Piece pc) + { + return psq[sq.ToInt(), pc.ToInt()]; + } + + /// + /// c側の手駒hand_pcがあるときのZobrist Key + /// + /// + /// + /// + public static HASH_KEY Hand(Color c , Piece hand_pc) + { + return hand[c.ToInt(), hand_pc.ToInt()]; + } + + // static constructorで初期化するの、筋が良くないのでは…。 + /* + static Zobrist() + { + Init(); + } + */ + + /// + /// 上のテーブルを初期化する + /// これは起動時に自動的に行われる + /// + public static void Init() + { + var rng = new PRNG(20151225); // 開発開始日 == 電王トーナメント2015,最終日 + + // 手番としてbit0を用いる。それ以外はbit0を使わない。これをxorではなく加算して行ってもbit0は汚されない。 + SET_HASH(ref Side, 1, 0, 0, 0); + SET_HASH(ref Zero, 0, 0, 0, 0); + + // 64bit hash keyは256bit hash keyの下位64bitという解釈をすることで、256bitと64bitのときとでhash keyの下位64bitは合致するようにしておく。 + // これは定跡DBなどで使うときにこの性質が欲しいからである。 + // またpc==NO_PIECEのときは0であることを保証したいのでSET_HASHしない。 + // psqは、C++の規約上、事前にゼロであることは保証される。 + for (Piece pc = Piece.ZERO + 1; pc < Piece.NB; ++ pc) + for (Square sq = Square.ZERO; sq < Square.NB; ++ sq) + { + var r0 = rng.Rand() & ~1UL; + var r1 = rng.Rand(); + var r2 = rng.Rand(); + var r3 = rng.Rand(); + SET_HASH(ref psq[sq.ToInt(),pc.ToInt()], r0, r1, r2, r3); + } + + // またpr==NO_PIECEのときは0であることを保証したいのでSET_HASHしない。 + foreach (var c in All.IntColors()) + for (Piece pr = Piece.ZERO + 1; pr < Piece.HAND_NB; ++pr) + { + var r0 = rng.Rand() & ~1UL; + var r1 = rng.Rand(); + var r2 = rng.Rand(); + var r3 = rng.Rand(); + SET_HASH(ref hand[c , pr.ToInt()], r0, r1, r2, r3); + } + } + + /// + /// HASH_KEYに乱数を代入する + /// + /// + /// + /// + /// + /// + private static void SET_HASH(ref HASH_KEY h , UInt64 a,UInt64 b,UInt64 c , UInt64 d) + { + h.p.Set(a,b); + + // 残り128bitは使用しない + // 128bitで足りないなら、もしかしたら使うかも + } + } +} diff --git a/Assets/Plugins/MyShogi/Core/Zobrist.cs.meta b/Assets/Plugins/MyShogi/Core/Zobrist.cs.meta new file mode 100644 index 0000000..d1c7923 --- /dev/null +++ b/Assets/Plugins/MyShogi/Core/Zobrist.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b70f9d00b653d420c95e5752320b09c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/GameSceneScreen/GameSceneController.cs b/Assets/Scripts/GameSceneScreen/GameSceneController.cs index bdb654a..e42ef28 100644 --- a/Assets/Scripts/GameSceneScreen/GameSceneController.cs +++ b/Assets/Scripts/GameSceneScreen/GameSceneController.cs @@ -17,6 +17,11 @@ private void Awake() { Init(); } + + private void Start() + { + var gameState = new GameState(); + } private void Init() { diff --git a/Assets/Scripts/Shogi/GameState.cs b/Assets/Scripts/Shogi/GameState.cs index 30377b9..f87b937 100644 --- a/Assets/Scripts/Shogi/GameState.cs +++ b/Assets/Scripts/Shogi/GameState.cs @@ -1,22 +1,19 @@ using System.Collections.Generic; +using MyShogi.Model.Shogi.Core; +using UnityEngine; /// /// ゲームの状態を表すクラス /// public class GameState { - public PieceType[,] board; // 盤面 - public List capturedPieces; // 持ち駒 - public bool isCheck = false; // 王手がかかっているかどうか - - public GameState(BoardData boardData) + + public GameState() { - board = new PieceType[9,9]; - capturedPieces = new List(); - foreach (var data in boardData.boardData) - { - board[data.x, data.y] = PieceData.StrToPieceType(data.pieceType); - } + Initializer.Init(); + var position = new Position(); + position.InitBoard(); + Debug.Log(position.Pretty()); } /// From e173a98a8ea8c91a6c0572156f126dfde9319451 Mon Sep 17 00:00:00 2001 From: Tsubasa Hizono <71565122+tsubasa-alife@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:04:58 +0900 Subject: [PATCH 2/2] Update LICENSE --- LICENSE | 695 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 674 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index b80521f..f288702 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,674 @@ -MIT License - -Copyright (c) 2023 DevsOnTheBoard - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +.