Skip to content

Instantly share code, notes, and snippets.

@karno
Created May 15, 2010 21:02
Show Gist options
  • Save karno/402395 to your computer and use it in GitHub Desktop.
Save karno/402395 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Std.HeavenlyHand.Data;
using Std.HeavenlyHand.Criteria;
namespace Std.HeavenlyHand.Logic
{
/// <summary>
/// キャッシュ付きの牌ソート・聴牌形列挙サブシステムです。<para />
/// Player型に組み込まれて利用されます。
/// </summary>
public class TilesManager
{
/// <summary>
/// 牌管理システムのインスタンスを作成します。
/// </summary>
public TilesManager()
{
tiles = new List<Tile>();
readyFormCache = new List<ReadyForm>();
fours = new List<IEnumerable<Tile>>();
}
/// <summary>
/// 和了型のキャッシュ
/// </summary>
protected List<ReadyForm> readyFormCache;
/// <summary>
/// 槓子組
/// </summary>
protected List<IEnumerable<Tile>> fours;
/// <summary>
/// 登録されている牌一覧
/// </summary>
protected List<Tile> tiles;
/// <summary>
/// 牌の列挙を取得します。
/// </summary>
public IEnumerable<Tile> Tiles
{
get { return tiles; }
}
#region tiles collection control
/// <summary>
/// 登録されている牌を削除します。
/// </summary>
public void ClearTiles()
{
tiles.Clear();
}
/// <summary>
/// 登録されている牌一覧をソートします。
/// </summary>
/// <remarks>
/// 筒子→索子→萬子→字牌(東→中,季節牌) の順にソートします。<para/>
/// 同じ牌でドラあり・なしが違う場合、ドラを優先します。
/// </remarks>
private void SortTiles()
{
tiles.Sort(new Comparison<Tile>(TileComparer));
}
/// <summary>
/// 牌のソートを行います。
/// </summary>
/// <param name="a">引数a</param>
/// <param name="b">引数b</param>
/// <returns><0:a < b, >0: a > b, =0: a == b </returns>
protected virtual int TileComparer(Tile a, Tile b)
{
if (a == b) //同じならすぐ戻る
return 0;
else if (a.TileType != b.TileType) //牌種が違ったらそれを比較
return (int)a.TileType - (int)b.TileType;
else if (a.Value != b.Value) //牌種が同じで値が違ったらそれを比較
return a.Value - b.Value;
else if (a.Dra ^ b.Dra) //牌種と値が同じでドラありなしが違えばドラを比較
return a.Dra ? -1 : 1;
else //両者は同じです。
return 0;
}
/// <summary>
/// 配牌を設定します。<para />
/// 牌登録キャッシュを全てクリアし、セットし、ソートします。
/// </summary>
/// <param name="tile"></param>
public void SetDealingTiles(IEnumerable<Tile> tile)
{
ClearTiles();
tiles.AddRange(tile);
SortTiles();
}
/// <summary>
/// 牌を追加します。
/// </summary>
/// <param name="tile">追加する牌</param>
public void AddPicked(Tile tile)
{
tiles.Add(tile);
}
/// <summary>
/// 牌を切ります。
/// </summary>
/// <param name="index">左からの番目</param>
public void Discard(int index)
{
tiles.RemoveAt(index);
}
#endregion
#region ready form generator
/// <summary>
/// 聴牌形を確認します。
/// </summary>
/// <returns>聴牌しているかどうか</returns>
public bool UpdateReadyForm()
{
readyFormCache.Clear();
//聴牌形でない
if (tiles.Count % 3 != 1) return false;
//和了形格納変数
ReadyForm rf = new ReadyForm(this.tiles);
//槓子をあらかじめ格納
rf.Sets.AddRange(this.fours);
//pivot:雀頭にする牌の位置
for (int pivot = 0; pivot < tiles.Count; pivot++)
{
//一次変数の生成
List<Tile> tempTiles = new List<Tile>(tiles);
var ready = rf.Clone();
//雀頭の設定
var head = PickAndRemove(ref tempTiles, pivot);
//pivotを雀頭に待ちを調べる(待ちはpivotと同じ牌となる)
CheckReadyForm(ref tempTiles, ref ready, true);
//自分のいたインデックスが同じ種類・同じ値の牌であったら、それで雀頭が出来る
if (pivot < tempTiles.Count - 1)
{
int pivotincl = 0;
var ntile = tempTiles[pivot];
//同じ種類・値の牌は全てスキップする
for (; ntile == head; pivotincl++)
ntile = tempTiles[pivot + pivotincl];
if (pivotincl > 0)
{
//雀頭として削除
tempTiles.RemoveAt(pivot);
CheckReadyForm(ref tempTiles, ref ready, false);
}
pivot += pivotincl;
}
}
return readyFormCache.Count > 0;
}
/// <summary>
/// 待ちの形を精査します。
/// </summary>
/// <param name="_tile">牌データのリファレンス</param>
/// <param name="_ready">待ち形データのリファレンス</param>
/// <param name="paiFilled">待ち牌を考慮しない</param>
private void CheckReadyForm(ref List<Tile> _tile, ref ReadyForm _ready, bool paiFilled)
{
if (_tile.Count == 0)
{
//全ての牌を構成できます
readyFormCache.Add(_ready.Clone());
return;
}
//作業用変数コピー
var ready = _ready.Clone();
var tile = new List<Tile>(_tile);
//リスト1番目の牌を利用する
var cPivot = PickAndRemove(ref tile, 0);
//手牌から順子/刻子の構成
if (tile.Count >= 2)
{
//刻子
if (tile[0] == cPivot && tile[1] == cPivot)
{
var pick2 = PickAndRemove(ref tile, 0);
var pick3 = PickAndRemove(ref tile, 0);
//刻子の追加
ready.Sets.Add(new[] { cPivot, pick2, pick3 });
//サブルーチンへ
CheckReadyForm(ref tile, ref ready, paiFilled);
//元に戻す
ready = _ready.Clone();
tile = new List<Tile>(_tile);
}
//順子
if (cPivot.TileType != Tile.TileTypes.Honors && cPivot.Value < 8)
{
//とりあえず順子の左端を構成できる牌であることを確認
int findValue = cPivot.Value + 1;
for (int i = 0; i < tile.Count; i++)
{
//+1(2番目)の牌の検索
if ((int)tile[i].TileType == (int)cPivot.TileType && tile[i].Value == findValue)
{
//次の牌はヒット
int findVal2 = findValue + 1;
for (int j = i; j < tile.Count; j++)
{
//+2(3番目)の牌の検索
if ((int)tile[j].TileType == (int)cPivot.TileType &&
tile[j].Value == findVal2)
{
//ヒット!
var pick2 = PickAndRemove(ref tile, i);
var pick3 = PickAndRemove(ref tile, j);
//順子の追加
ready.Sequences.Add(new[] { cPivot, pick2, pick3 });
//サブルーチンへ
CheckReadyForm(ref tile, ref ready, paiFilled);
//元に戻す
ready = _ready.Clone();
tile = new List<Tile>(_tile);
break;
}
else if (tile[j].Value > findVal2 ||
(int)tile[j].TileType > (int)cPivot.TileType)
{
//ソート順の関係上、この先の探索は無駄
break;
}
}
break;
}
else if (tile[i].Value > findValue || (int)tile[i].TileType > (int)cPivot.TileType)
{
//ソート順の関係上、この先の探索は無駄
break;
}
}
}
}
//手牌からの構成 おわり
//待ちを考慮した構成
if (!paiFilled && tile.Count >= 1)
{
//刻子
if (tile[0] == cPivot)
{
var pick2 = PickAndRemove(ref tile, 0);
ready.Waiting = pick2;
//刻子の追加
ready.Sets.Add(new[] { cPivot, pick2, pick2 });
//サブルーチンへ
CheckReadyForm(ref tile, ref ready, true);
//元に戻す
ready = _ready.Clone();
tile = new List<Tile>(_tile);
}
//順子
if (cPivot.TileType != Tile.TileTypes.Honors &&
cPivot.Value <= 8)
{
for (int i = 0; i < tile.Count; i++)
{
int nextValue = cPivot.Value + 1;
if (
tile[i].TileType == cPivot.TileType &&
tile[i].Value == nextValue)
{
var pick2 = PickAndRemove(ref tile, i);
//順子を構成できました。
if (cPivot.Value > 1) //左端に追加する場合
{
//待ちタイル
var newTile = new Tile(cPivot.TileType, cPivot.Value - 1);
ready.Waiting = newTile;
//順子の追加
ready.Sequences.Add(new[] { newTile, cPivot, pick2 });
//サブルーチンへ
CheckReadyForm(ref tile, ref ready, true);
//readyだけ元に戻す
ready = _ready.Clone();
}
if (pick2.Value < 9) //右端に追加する場合
{
//待ちタイル
var newTile = new Tile(cPivot.TileType, pick2.Value + 1);
ready.Waiting = newTile;
//順子の追加
ready.Sequences.Add(new[] { cPivot, pick2, newTile });
//サブルーチンへ
CheckReadyForm(ref tile, ref ready, true);
}
break;
}
else if (tile[i].TileType > cPivot.TileType ||
tile[i].Value > nextValue)
{
//この先は検索しても何もない
break;
}
}
//おしまい
}
}
}
/// <summary>
/// 指定したリストのindex番目のアイテムを削除して、返します。
/// </summary>
/// <param name="list">リスト</param>
/// <param name="index">削除/取得するアイテムのインデックス</param>
/// <returns>削除したアイテム</returns>
private Tile PickAndRemove(ref List<Tile> list, int index)
{
var pick = list[index];
list.RemoveAt(index);
return pick;
}
#endregion
}
/// <summary>
/// 待ち型を表します。
/// </summary>
public class ReadyForm : ICloneable
{
/// <summary>
/// 待ち方クラスを生成します。
/// </summary>
/// <param name="parent"></param>
public ReadyForm(IEnumerable<Tile> parent)
{
this.Tiles = parent;
Sequences = new List<IEnumerable<Tile>>();
Sets = new List<IEnumerable<Tile>>();
LinkingCriteriaBase = new List<CriteriaBase>();
}
/// <summary>
/// 牌一覧
/// </summary>
public IEnumerable<Tile> Tiles { get; set; }
/// <summary>
/// 待ち牌
/// </summary>
public Tile Waiting { get; set; }
/// <summary>
/// 待ちの形一覧
/// </summary>
public enum WaitingStyles {
/// <summary>
/// 両面待ち
/// </summary>
EitherTwoGates,
/// <summary>
/// 嵌張待ち
/// </summary>
MiddleInSequence,
/// <summary>
/// 辺張待ち
/// </summary>
ThreeOrSeven,
/// <summary>
/// 双碰待ち
/// </summary>
Triplet,
/// <summary>
/// 単騎待ち
/// </summary>
OneOfPair
}
/// <summary>
/// 待ちの形
/// </summary>
public WaitingStyles WaitingStyle { get; set; }
/// <summary>
/// 順子牌組
/// </summary>
public List<IEnumerable<Tile>> Sequences;
/// <summary>
/// 刻子/槓子牌組
/// </summary>
public List<IEnumerable<Tile>> Sets;
/// <summary>
/// この和了形で満たせる役一覧
/// </summary>
public List<CriteriaBase> LinkingCriteriaBase;
/// <summary>
/// データをすべてクリアします。
/// </summary>
public void Clear()
{
Tiles = null;
Waiting = null;
Sequences.Clear();
Sets.Clear();
LinkingCriteriaBase.Clear();
}
#region ICloneable メンバー
public ReadyForm Clone()
{
var clone = new ReadyForm(Tiles);
clone.Waiting = Waiting;
clone.Sequences.AddRange(Sequences);
clone.Sets.AddRange(Sets);
clone.LinkingCriteriaBase.AddRange(LinkingCriteriaBase);
return clone;
}
object ICloneable.Clone()
{
return this.Clone();
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment