Unityによる大富豪の作り方 4 ~役、手札の評価~
スポンサードリンク
評価値の計算
場にどのカードを出すか決定する為に必要な3つの評価値 - 役評価値、手札評価値、優先評価値 - の計算について説明します。 主に以下のサイトを参考にしました。
コンピュータ大貧民におけるヒューリスティック戦略の実装と効果
役評価値
役自体の強さを表します。役が単体かグループか階段なのかで計算方法が異なります。
単体
対象のカードよりランクが高く、相手が保持している可能性のあるカードのランクの総数をnとしたき役評価値は 100 - n × 30となります。
例
対象が[ ♠️Q ]
ですでに♠️K ♠️1 ♦️1 ♣️1 ♠️2 ♦️2 ♣️2 ❤️2
が使われている場合、役評価値は100 - 2 × 30 = 40 となります。
グループ
- そのグループと同じ枚数で、対象よりランクが高く、相手が保持している可能性のあるカード(H)を抽出します。
- Hに含まれる各ランク(Ri)ごとに以下の計算をします。
- Si = Riの総数 - 計算対象の枚数 - プレイ中の人数 + プレイヤー数
- Siの値によってNiの値を決定します。
- Si <= 0 なら Ni = 4
- Si = 1 なら Ni =9
- Si = 2 なら Ni =15
- Si >= 3 なら Ni =24
- 役評価値は 100 - Niの総和 となります。
対象が
[ ♠️J ♦️J ♣️J ]
ですでに♠️K ♠️1 ♦️1 ♣️1 ♠️2 ♦️2 ♣️2 ❤️2
が使われておりプレイ人数4人で誰も上がっていない場合Jより高く、相手が保持している可能性のあるカード(H)はQ、K。
Q、KそれぞれのNiの値Nq、Nkは
Sq = 4 - 3 - 4 + 4 = 1 より Nq = 9
Sk = 3 - 3 - 4 + 4 = 0 より Nk = 4
これより、役評価値は 100 - 9 + 4 = 87 となります。階段
対象のカードよりランクが高く、相手が保持している可能性のある階段の総数をnとしたき、役評価値は 100 - n × (プレイヤー数 + 1 - プレイ中の人数) となります。
例
対象が[ ♠️9 ♠️10 ♠️J ]
ですでに♦️Q ♣️Q ♣️K ❤️Q ❤️K ❤️1
が使われておりプレイ人数4人で誰も上がっていない場合
相手が保持している可能性のある対象より高ランクの階段は[ ♠️Q ♠️K ♠️1 ] [♠️K ♠️1 ♠️2 ] [ ♦️K ♦️1 ♦️2 ]
の3種類なので
役評価値は 100 - 3 × (4 + 1 - 4) = 97 となります。
手札評価値
手札にどれだけ弱いカードが残っているかを表します。弱い役が多いほど値が大きくなります。役評価値より各役に点数(S)を付けます。
- 役評価値が 1以上30以下の場合 S = 2
- 役評価値が 31以上60以下の場合 S = 1
- 役評価値が 61以上90以下の場足 S = 0
- 役評価値が 91以上かつ対象の役よりランクが高いカードを相手が保持している可能性がある場合 S = -1
- 役評価値が 91以上かつ対象の役よりランクが高いカードを相手が保持している可能性がない場合 S = 対象の役の枚数 × -1
手札評価値は、Sの総和となります。
例
手札が♠️3 ♦️9 ♦️10 ♦️J ♦️2 ♣️2
でどのカードも使われていなく、プレイ人数4人で誰も上がっていない場合
[ ♠️3 ]
の役評価値は1なので S = 2
[ ♦️9 ♦️10 ♦️J ]
の役評価値は94で、相手が高ランクの役を保持している可能性があるので S = -1
[ ♦️2 ♣️2 ]
の役評価値は100で、相手が高ランクの役を保持している可能性がないので S = -2
手札評価値は 2 - 1 - 2 = -1 となります。
優先評価値
親のときに場に出す優先順位を表します。革命できる役を持っているかどうかで計算方法が異なります。
革命できない場合
まず、各役が「場を流せそうな役」なのか、手札が「あがれそうな手札」なのかを計算します。
- 場を流せそうな役
- 役評価値が86以上の役。
- あがれそうな手札
- 手札内の役の数 - 場を流せそうな役の数 が1以下の手札。
あがれそうな手札の場合、場を流せない役の優先評価値を低くし、場を流せる役を優先的に場に出し続け上がりきるようにします(ずっと俺のターン!)。
あがれそうにない手札の場合、役ランク(役を構成するカード内で最も高いランク)が低く、手札内に同一枚数のものが多いほど高くします。
- 場を流せそうな役の優先評価値は 110 - 役評価値 となります。
- 場を流せなそうな役の優先評価値は
- 上がれそうな手札の場合は 2 となります。
- そうでない場合は 40 + (13 - 役ランク) × 2 + 手札内の同じ枚数の役の数 × 4 となります。
例1
手札が❤️3 ❤️4 ❤️5 ♦️6 ♣️7 ♠️7 ♠️8 ♦️8 ♣️8 ♦️J ♣️J ❤️2
でどのカードも使われていなく、プレイ人数4人で誰も上がっていない場合
[ ❤️3 ❤️4 ❤️5 ]
の役評価値は86
[ ♦️6 ]
の役評価値は1
[ ♣️7 ♠️7 ]
の役評価値は12
[ ♠️8 ♦️8 ♣️8 ]
の役評価値は51
[ ♦️J ♣️J ]
の役評価値は46
[ ❤️2 ]
の役評価値は100
手札内の役が6つ、場を流せそうな役が2つなのであがれそうな手札ではありません。よって、
[ ❤️3 ❤️4 ❤️5 ]
の優先評価値は 110 - 86 = 24
[ ♦️6 ]
の優先評価値は 40 + (13 - 3) × 2 + 2 × 4 = 68
[ ♣️7 ♠️7 ]
の優先評価値は 40 + (13 - 4) × 2 + 2 × 4 = 66
[ ♠️8 ♦️8 ♣️8 ]
の優先評価値は 40 + (13 - 5) × 2 + 1 × 4 = 60
[ ♦️J ♣️J ]
の優先評価値は 40 + (13 - 8) × 2 + 2 × 4 = 58
[ ❤️2 ]
の優先評価値は 110 - 100 = 10
となります。
例2
手札が❤️3 ❤️4 ❤️5 ♠️6 ♠️7 ♠️8 ♣️9
でどのカードも使われていなく、プレイ人数4人で誰も上がっていない場合
[ ❤️3 ❤️4 ❤️5 ]
の役評価値は74
[ ♠️6 ♠️7 ♠️8 ]
の役評価値は81
[ ♣️9 ]
の役評価値は1
場を流せる役はないので上がれそうな手札ではありません。よって、
[ ❤️3 ❤️4 ❤️5 ]
の優先評価値は 40 + (13 - 2) × 2 + 2 × 4 = 68
[ ♠️6 ♠️7 ♠️8 ]
の優先評価値は 40 + (13 - 5) × 2 + 2 × 4 = 64
[ ♣️9 ]
の優先評価値は 40 + (13 - 6) × 2 + 1 × 4 = 58
となります。
例3
手札が♠️3 ♠️4 ♠️5 ♦️8 ♦️9 ♦️10 ♣️2
でどのカードも使われていなく、プレイ人数4人で誰も上がっていない場合
[ ♠️3 ♠️4 ♠️5 ]
の役評価値は74
[ ♦️8 ♦️9 ♦️10 ]
の役評価値は89
[ ♣️2 ]
の役評価値は100
手札内の役が3つ、場を流せそうな役が2つなのであがれそうな手札です。よって、
[ ♠️3 ♠️4 ♠️5 ]
の優先評価値は2
[ ♦️8 ♦️9 ♦️10 ]
の優先評価値は110 - 89 = 21
[ ♣️2 ]
の優先評価値は110 - 100 = 10
革命できる場合
- 2枚以下のカードで構成される役の役評価値が90以下 かつ
- 出した後の手札評価値 < 出す前の手札評価値
の場合
- 革命の役の優先評価値を90
- 階段以外で場を流せそうな役の優先評価値を91
- 残りは革命できない場合と同様
とします。上記条件を満たさない場合は革命役の優先評価値を1とし革命しないようにします。
実装
public class Hand { private static readonly ulong MAll = 0x000fffffffffffff; private static BitUtility bit; private readonly ulong bitHand; private readonly int sameQuantity; private readonly int rank; //場を流せる役かどうか public bool CanEndTurn { get => Score >= 86; } public int Rank { get => rank; } //役の構成枚数 public int Quantity { get => bit.CountBit(bitHand); } //役評価値 public float Score { get; private set; } //優先評価値 public float Priority { get; set; } public int EnemyHigherRankQuantity { get; private set; } public Hand(ulong bitHand, ulong playerCard, GameData gameData, int same = 0) { this.bitHand = bitHand; bit = BitUtility.Instance; //役のランクを求める。革命時は逆になる rank = gameData.IsRevolutionalizing ? 12 - (int)(bit.BitScanForward(bitHand) / 4) : (int)(bit.BitScanReverse(bitHand) / 4); //場に出ていないカードを取得 var enemyCard = ~(playerCard | gameData.BitUsedCard) & MAll; //場に出ていないカードの中で、役より強いカードを取得 var enemyHigherCard = bit.GetHigherBitCard(enemyCard, bitHand, gameData.IsRevolutionalizing); EnemyHigherRankQuantity = CountRank(enemyHigherCard); //手札内にある同じ枚数の役の数 sameQuantity = same; Score = EvaluateHand(enemyHigherCard, gameData); } //役評価値を計算 public float EvaluateHand(ulong higher, GameData gameData) { float score = 0f; if (Quantity == 1) { score = EvaluateSingleHand(); } else if (bit.IsSequence(bitHand, Quantity)) { score = EvaluateSequenceHand(higher, Quantity, gameData); } else if (bit.IsGroup(bitHand, Quantity)) { score = EvaluateGroupHand(higher, Quantity, gameData); } return Mathf.Max(score, 1); } //優先評価値を求める public void SetPriority(bool canEndGame) { if (canEndGame) { if (!CanEndTurn) { Priority = 2; return; } } if (CanEndTurn) { Priority = 110 - Score; } else { Priority = 40 + (13 - Rank) * 2 + sameQuantity * 4; } } //単体のカードの強さ評価値を計算 private float EvaluateSingleHand() { return 100 - EnemyHigherRankQuantity * 30; } //階段の強さ評価値を計算 private float EvaluateSequenceHand(ulong higher, int cnt, GameData gameData) { var seqs = bit.GetSequenceList(higher, cnt); return 100 - seqs.Count * (gameData.PlayerNumber + 1 - gameData.PlayingNumber); } //グループの強さの評価値を計算 private float EvaluateGroupHand(ulong higher, int cnt, GameData gameData) { int score = 0; var cnts = CountOverQuantityEveryRank(higher, cnt); for (int i = 0; i < cnts.Length; i++) { if (cnts[i] == 0) continue; var s = cnts[i] - cnt - gameData.PlayingNumber + gameData.PlayerNumber; int n; if (s <= 0) n = 4; else if (s == 1) n = 9; else if (s == 2) n = 15; else n = 24; score += n; } return 100 - score; } private int CountRank(ulong bitCard) { var qpCard = bit.ToQPCard(bitCard); return bit.CountBit(qpCard); } //指定の枚数以上あるランク内で、何枚カードがあるか数える private int[] CountOverQuantityEveryRank(ulong higher, int cnt) { int n = cnt - 1; var noless = 16 - (1ul << n); var cntMask = BitUtility.M0001 * noless; var qpOver = bit.ToQPCard(higher) & cntMask; var cnts = new int[13]; for (int i = 0; i < 13; i++) { var c = (qpOver >> i * 4) & 15ul; if (c == 0) cnts[i] = 0; else cnts[i] = (int)Math.Log((int)c, 2) + 1; } return cnts; } }
public class PlayerCard { //手札内のすべての役 private List<Hand> hands; //あがれる手札かどうか public bool CanEndGame { get { int cnt = hands.Count; int end = hands.Count(h => h.CanEndTurn); return cnt == 0 || cnt - end <= 1; } } //手札評価値を取得 public float Score { get { float score = 0f; hands.ForEach(h => { if (h.Score >= 1 && h.Score <= 30) { score += 2; } else if (h.Score >= 31 && h.Score <= 60) { score += 1; } else if (h.Score >= 91 && h.EnemyHigherRankQuantity > 0) { score += -1; } else if (h.Score >= 91 && h.EnemyHigherRankQuantity == 0) { score += -h.Quantity; } }); return score; } } } //優先評価値を計算 private void SetPriority(GameData gameData) { var canEnd = CanEndGame; //革命役を取得 var rev = hands.Find(h => h.CanRevolutionalize); if (rev != null) { SetPriorityOnRevolution(rev, canEnd, gameData); } else { hands.ForEach(h => h.SetPriority(canEnd)); } } //革命できる場合の優先評価値を計算 private void SetPriorityOnRevolution(Hand rev, bool canEnd, GameData gameData) { var next = CreateNextPlayerCard(rev.BitHand, gameData); if (hands.Exists(h => h.Quantity <= 2 && h.Score > 90) || Score < next.Score) { rev.Priority = 1; foreach (var h in hands.Where(h => !h.CanRevolutionalize)) { h.SetPriority(canEnd); } } else { rev.Priority = 90; foreach (var h in hands.Where(h => !h.CanRevolutionalize)) { if (!bit.IsSequence(h.BitHand) && h.CanEndTurn) { h.Priority = 91; } else { h.SetPriority(canEnd); } } } }