2009年03月22日

[パズル]清一色 チンイ―ソウ

  
問題
マージャンの清一色となる待ち牌をすべて調べるプログラムを作ってください。
ただし、単純化するために、前面でチー、ポン、カンは一度もしていないものとします。また、データとしては数値だけを与えることとし、牌に関する他の情報は無視してかまいません。

---
僕は、マージャンに詳しくないので、用語などに自身がありません。無瑛やり英単語にしているので、これは自己流の用語ですのでご容赦を。

まずは、14個の牌が、清一色で上がっているかどうかを調べるメソッドを書きます。
IsFinishがそのメソッドですが、七対子(Is7Pair)と通常の上がり(IsNormalComplete)を別メソッドとして判断するようにしています。
その下請けメソッドとして、IsSeries3とIsSame3というメソッドを用意します。
IsSeries3は、順子かどうかを調べるメソッドですが、引数で数値を与えると、その数値から始まる連続した3つの数があるかどうかを調べるメソッドです。
IsSame3は、引数で与えた数値が3枚(以上)あるかどうかを調べます。

IsPair7もIsNormalCompleteも再帰処理を使っています。IsPair7は、もう少し簡単になりそうな気もするのですが...

IsNormalCompleteの中に、

3.Times(n => nums.Remove(i + n));

というコードがありますが、これは、

nums.Remove(i);
nums.Remove(i+1);
nums.Remove(i+2);

を1行にまとめてみました。かえって解りにくくなったような気もしますが、まあ、自己満足です。

ここまでできれば、待ち牌をすべて調べるコードはそれほど難しくありませんね。
BestPiecesメソッドがそのメソッドです。

Mainメソッドは、IsFinishとBestPiecesメソッドの動作を調べるテストコードです。

using System;
using System.Collections.Generic;
using System.Linq;

namespace FullFlash {
class Program {
static void Main(string[] args) {
Pieces t = new Pieces(new[] { 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5 });
Console.WriteLine(t.IsFinish());

t = new Pieces(new[] { 1, 2, 2, 2, 3, 3, 3, 1, 4, 4, 4, 5, 5, 6 });
Console.WriteLine(t.IsFinish());

t = new Pieces(new[] { 1, 3, 2, 3, 4, 2, 3, 4, 4, 2, 4, 5, 6, 7 });
Console.WriteLine(t.IsFinish());

t = new Pieces(new[] { 5, 1, 2, 3, 4, 5, 6, 5, 5, 6, 7, 6, 7, 8 });
Console.WriteLine(t.IsFinish());

t = new Pieces(new[] { 1, 2, 3, 4, 5, 5, 5, 5, 6, 7, 3, 6, 7, 8 });
Console.WriteLine(t.IsFinish());

t = new Pieces(new[] { 1, 2, 3, 3, 4, 5, 5, 6, 7, 6, 7, 8, 5, 2 });
Console.WriteLine(t.IsFinish());

t = new Pieces(new[] { 1, 2, 3, 4, 5, 5, 5, 3, 6, 7, 6, 7, 8, 3 });
Console.WriteLine(t.IsFinish());

Pieces tb = new Pieces(new[] { 1, 2, 3, 3, 4, 4, 5, 5, 6, 7, 6, 7, 8 });
tb.BestPieces().ToList().ForEach(n => Console.Write("{0} ", n));
Console.WriteLine();

t = new Pieces(new[] { 1, 1, 2, 6, 2, 6, 4, 4, 4, 7, 7, 9, 9 });
t.BestPieces().ToList().ForEach(n => Console.Write("{0} ", n));
Console.WriteLine();

}
}

// 手牌を表すクラス (上がりかどうかの判断もこのクラスで行う)
class Pieces {
private List<int> Numbers = new List<int>();

public Pieces(IEnumerable<int> nums) {
foreach (var n in nums)
Numbers.Add(n);
}

// 上りになる手を探す
public IEnumerable<int> BestPieces() {
for (int i = 1; i <= 9; i++) {
List<int> work = Numbers.ToList();
work.Add(i);
if (IsNormalComplete(work) || Is7Pair(work))
yield return i;
}
}

// 上がりか?
public bool IsFinish() {
return (IsNormalComplete(Numbers) || Is7Pair(Numbers));
}

// 七対子か?
private bool Is7Pair(List<int> nums) {
if (nums.Count() == 0)
return true;
for (int i = 1; i <= 9; i++) {
if (nums.FindAll(n => n == i).Count >= 2) {
2.Times(() => nums.Remove(i));
try {
if (Is7Pair(nums))
return true;
} finally {
2.Times(() => nums.Add(i));
}
}
}
return false;
}

// 七対子以外の上がり(なんて言うんだろう?)
private bool IsNormalComplete(List<int> nums) {
if (nums.Count == 2)
return (nums[0] == nums[1]);
for (int i = 1; i <= 9; i++) {
if (IsSame3(nums, i)) {
3.Times(() => nums.Remove(i));
try {
if (IsNormalComplete(nums.ToList()))
return true;
} finally {
3.Times(() => nums.Add(i));
}
}
if (IsSeries3(nums, i)) {
3.Times(n => nums.Remove(i + n));
try {
if (IsNormalComplete(nums.ToList()))
return true;
} finally {
3.Times(n => nums.Add(i + n));
}
}
}
return false;
}

// 順子(シュンツ)か?
private bool IsSeries3(List<int> nums, int i) {
return Enumerable.Range(i,3).All(x => nums.FindIndex(n => n == x) >= 0);
}

// 刻子(コーツ)か?
private bool IsSame3(List<int> nums, int i) {
return nums.FindAll(n => n == i).Count >= 3;
}
}

// intに対する拡張メソッドを提供。
public static class IntExtentions {
public static void Times(this int value, Action action) {
for (int i = 0; i < value; i++)
action();
}

public static void Times(this int value, Action<int> action) {
for (int i = 0; i < value; i++)
action(i);
}
}
}



※当記事に掲載したコードは、『ナノピコ教室・プログラミング問題集』(駒木悠二+有澤誠 編 共立出版株式会社)に掲載されている問題をGushwellがC#で解いたものです。


 

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/gushwell/51780386