锻造-准备规则(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/toolsmithing-the-rule-of-ready-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 7 分钟阅读 - 3402 个词 阅读量 0锻造-准备规则(译文)
原文地址:https://www.codeproject.com/Articles/671007/Toolsmithing-The-Rule-of-Ready
原文作者:Christopher Stevenson
译文由本站 robot-v1.0 翻译
前言
The Rule of Ready project continues with some utility functionality
准备规则项目继续提供一些实用程序功能
介绍(Introduction)
这是<准备规则>项目的第二篇文章,该项目是用C#编写的开源日本麻将游戏项目.一世(This is the second article for The Rule of Ready project, an open-source Japanese Mahjong game project written in C#. I) 先前(previously) 谈到代表游戏中图块的类(talked about the class that represents a tile in the game, the) MahjongTile
这两种类型的图块的基类和子类.(base class and subclasses for the two types of tiles.)
有关日本麻将的更多信息,请访问以下站点:(For more information on Japanese Mahjong, here are some sites to visit:)
- 维基(A wiki) 有关日本麻将的信息(for information about Japanese Mahjong)
- 详尽的PDF(A thorough PDF) 从仅被称为Barticle的玩家那里详尽详尽地详述了游戏的规则和日语条款(detailing both the rules and the Japanese terms for the game, in exhaustive detail, from a player only known as Barticle)
- ReachMahjong.com(ReachMahjong.com) ,是游戏专业玩家的社区网站,上面有日本专业玩家的翻译文章(, a community site for professional players of the game, with translated articles from professional Japanese players)
- 日本麻将(A Japanese Mahjong) Flash游戏(Flash game)
组合工具(Putting Together Tools)
首先,我创建了一个(First, I created a) TileEngine
某些核心功能的类:创建游戏所需的磁贴,并维护列表以供参考.我打算(class for some core functionality: creating the tiles needed for a game, and maintain lists for reference. I’m intending the) TileEngine
成为存储使用通用功能的地方(to be the place to store common functionality for using) MahjongTile
对象.至少对我而言,软件项目的挑战之一是保持组织有序.我最喜欢的启发式方法是"知道在哪里找到东西",然后将代码放在将来可以找到的地方.如果我很难在一周或一个月后找到一些代码,那么我将重新评估放置代码的位置.(objects. One of the challenges of a software project–for me, at least–is keeping organized. My favored heuristic for this is “knowing where to find something,” and then putting code where I’ll expect to find it in the future. If I have a hard time finding some code in the week or month later, then I reassess where I put the code.)
诚然,Visual Studio有多种工具可帮助查找代码,但是我认为这些工具在组织良好的情况下甚至可以更好地工作.(Admittedly, Visual Studio has a variety of tools to assist in finding code, but I feel that these tools work even better with good organization.)
的(The) MahjongSequence
和(and) MahjongPartialSequence
类是作为获胜手的一部分的图块的集合.我将在下一篇文章中深入探讨它们.我还在此类中找到了一些领域知识:尤其是用于日本麻将游戏的特定磁贴.(classes are collections of tiles that are part of a winning hand. I’ll go into depth on them in my next article. I’ve also located some domain knowledge in this class: specifically the specific tiles that are used for a game of Japanese Mahjong.)
瓷砖引擎(TileEngine)
public class TileEngine
{
#region Private Fields
/// <summary>
/// Constant: number of identical tiles of each tile type in the game
/// </summary>
private const int TilesPerTypeInGame = 4;
/// <summary>
/// When red bonus tiles are used (Japanese: akidora),
/// this is the suit number used for them
/// </summary>
private const MahjongSuitNumber redBonusNumber = MahjongSuitNumber.Five;
/// <summary>
/// When the red bonus tiles are used, these are the number of tiles per suit
///(there are normally 4 tiles)
/// </summary>
private readonly IReadOnlyDictionary<MahjongSuitType, int> numRedTilesPerSuit =
new Dictionary<MahjongSuitType, int>()
{
{MahjongSuitType.Bamboo, 1},
{MahjongSuitType.Character, 1},
{MahjongSuitType.Dot, 2},
};
#endregion
#region Public (read-only) Properties
/// <summary>
/// A list of Honor types, in order
/// </summary>
public IEnumerable<MahjongHonorType> HonorTileTypes { get; private set; }
/// <summary>
/// A list of Suit types, in order
/// </summary>
public IEnumerable<MahjongSuitType> SuitTileTypes { get; private set; }
/// <summary>
/// A list of suit numbers, in order
/// </summary>
public IEnumerable<MahjongSuitNumber> SuitTileNumbers { get; private set; }
/// <summary>
/// A list of all Terminals (suit tiles that are 1 or 9)
/// (Japanese: Rōtōhai)
/// </summary>
public IEnumerable<MahjongTile> TerminalTiles { get; private set; }
/// <summary>
/// A list of all Major tiles, (Terminals + Honors)
/// (Japanese: Yaochūhai)
/// </summary>
public IEnumerable<MahjongTile> MajorTiles { get; private set; }
/// <summary>
/// A list of all Simple tiles (suit tiles from 2 thru 8)
/// (also known as Minor or Middle Tiles)
/// (Japanese: Chunchanpai or Tanyaohai)
/// </summary>
public IEnumerable<MahjongTile> SimpleTiles { get; private set; }
/// <summary>
/// A Dictionary, keyed by Suit Type of all possible sequences
/// </summary>
public IReadOnlyDictionary<MahjongSuitType, IEnumerable<MahjongSequence>>
SequencesBySuit { get; private set; }
/// <summary>
/// A list of all sequences: 3 consecutive tiles of the same suit,
/// (e.g. 3-dot 4-dot 5-dot)
/// </summary>
public IEnumerable<MahjongSequence> Sequences
{
get { return SequencesBySuit.Values.SelectMany(seq => seq); }
}
/// <summary>
/// A list of all partial sequences: 2 tiles out of a sequence
/// </summary>
public IEnumerable<MahjongPartialSequence> PartialSequences
{
get { return Sequences.SelectMany(seq => seq.PartialSequences); }
}
#endregion
#region Constructor
/// <summary>
/// Create a new Tile Engine. (This is a somewhat heavy object)
/// </summary>
public TileEngine()
{
this.HonorTileTypes = Enum.GetValues
(typeof(MahjongHonorType)).Cast<MahjongHonorType>();
this.SuitTileTypes = Enum.GetValues
(typeof(MahjongSuitType)).Cast<MahjongSuitType>();
this.SuitTileNumbers = Enum.GetValues
(typeof(MahjongSuitNumber)).Cast<MahjongSuitNumber>();
this.TerminalTiles = this.GenerateTerminalTiles();
this.SimpleTiles = this.GenerateSimpleTiles();
this.MajorTiles = this.HonorTileTypes.Select
(honorType => new MahjongHonorTile(honorType))
.Concat(this.TerminalTiles);
this.SequencesBySuit = new Dictionary<MahjongSuitType, IEnumerable<MahjongSequence>>(3)
{
{MahjongSuitType.Bamboo,
this.GenerateSequencesForSuit(MahjongSuitType.Bamboo)},
{MahjongSuitType.Character,
this.GenerateSequencesForSuit(MahjongSuitType.Character)},
{MahjongSuitType.Dot,
this.GenerateSequencesForSuit(MahjongSuitType.Dot)}
};
}
#endregion
#region Public Methods
/// <summary>
/// Create a new tile set of 136 tiles, 4 of each type
/// </summary>
/// <returns></returns>
public IList<MahjongTile> CreateGameTileSet()
{
return CreateGameTileSet(useRedBonusTiles:false);
}
/// <summary>
/// Create a new tile set of 136 tiles, 4 of each type,
/// optionally swapping in the red tiles
/// </summary>
/// <param name="useRedBonusTiles">Swap in red tiles?</param>
/// <returns></returns>
public IList<MahjongTile> CreateGameTileSet(bool useRedBonusTiles)
{
var tileSet = new List<MahjongTile>();
foreach (MahjongSuitType suitType in this.SuitTileTypes)
{
foreach (MahjongSuitNumber suitNumber in this.SuitTileNumbers)
if (!useRedBonusTiles || !(suitNumber == TileEngine.redBonusNumber))
tileSet.AddRange(CreateTilesForSet(suitType, suitNumber));
else
tileSet.AddRange(CreateRedTilesForSet(suitType, suitNumber));
}
foreach (MahjongHonorType honorType in this.HonorTileTypes)
{
tileSet.AddRange(CreateTilesForSet(honorType));
}
return tileSet;
}
#endregion
#region Private Methods
/// <summary>
/// Create tiles for the given suit and number for the game
/// </summary>
/// <param name="suitType">suit to create</param>
/// <param name="suitNumber">number to create</param>
/// <returns></returns>
private IEnumerable<MahjongTile> CreateTilesForSet(MahjongSuitType suitType,
MahjongSuitNumber suitNumber)
{
return Enumerable.Repeat
(new MahjongSuitTile(suitType, suitNumber), TileEngine.TilesPerTypeInGame);
}
/// <summary>
/// Create tile for the given honor for the game
/// </summary>
/// <param name="honorType">honor to create</param>
/// <returns></returns>
private IEnumerable<MahjongTile> CreateTilesForSet(MahjongHonorType honorType)
{
return Enumerable.Repeat(new MahjongHonorTile(honorType),
TileEngine.TilesPerTypeInGame);
}
/// <summary>
/// Create tiles for the given suit and number for the game,
/// with Red Bonus tiles swapped in as
/// appropriate
/// </summary>
/// <param name="suitType">suit to create</param>
/// <param name="suitNumber">number to create</param>
/// <returns></returns>
private IEnumerable<MahjongTile> CreateRedTilesForSet(MahjongSuitType suitType,
MahjongSuitNumber suitNumber)
{
if (suitNumber != TileEngine.redBonusNumber)
return this.CreateTilesForSet(suitType,suitNumber);
int numRedTiles = this.numRedTilesPerSuit[suitType];
int numNormalTiles = TileEngine.TilesPerTypeInGame - numRedTiles;
return Enumerable.Repeat(new MahjongSuitTile(suitType, suitNumber, isRedBonus: true),
numRedTiles)
.Concat(Enumerable.Repeat(new MahjongSuitTile(suitType, suitNumber),
numNormalTiles));
}
/// <summary>
/// Generates a list of all sequences for a suit
/// </summary>
/// <param name="suitType">suit to make the sequences</param>
/// <returns></returns>
private IEnumerable<MahjongSequence> GenerateSequencesForSuit(MahjongSuitType suitType)
{
IList<MahjongSuitTile> tiles =
this.SuitTileNumbers
.Select(number => new MahjongSuitTile(suitType, number))
.ToList();
for(int startingIdx = 0; tiles.Count - startingIdx >= 3; startingIdx += 1)
{
yield return new MahjongSequence(tiles.Skip(startingIdx).Take(3));
}
}
/// <summary>
/// Generate all of the Terminal tiles (1 and 9 of each suit)
/// </summary>
/// <returns>list of Terminals</returns>
private IEnumerable<MahjongTile> GenerateTerminalTiles()
{
int lowTerminal = 1;
int highTerminal = this.SuitTileNumbers.Count();
foreach (MahjongSuitType suitType in this.SuitTileTypes)
{
yield return new MahjongSuitTile(suitType, lowTerminal);
yield return new MahjongSuitTile(suitType, highTerminal);
}
}
/// <summary>
/// Generate all of the Simple Tiles (2 thru 8 of each suit)
/// </summary>
/// <returns>list of Simples</returns>
private IEnumerable<MahjongTile> GenerateSimpleTiles()
{
int lowestSimple = 2;
int highestSimple = this.SuitTileNumbers.Count() - 1;
foreach (MahjongSuitType suitType in this.SuitTileTypes)
{
for (int suitNumber = lowestSimple; suitNumber <= highestSimple; suitNumber++)
{
yield return new MahjongSuitTile(suitType, suitNumber);
}
}
}
#endregion
}
其次,我创建了集合类本身所需的一些功能,这些功能不属于.NET的一部分:对对象列表进行改组,并为(Second, I created some functionality needed for collection classes themselves that isn’t part of .NET: shuffling a list of objects, and some helper methods for a) LinkedList
.我正在使用(. I’m looking at using a) LinkedList
成为游戏的墙,从正面绘制瓷砖,从背面绘制死墙.我本可以使用队列,但我更喜欢使用或构建与任务具有直观联系的数据类型-(to be the Wall of the game, drawing tiles from the front, and the Dead Wall from the back. I could have used a queue, but I prefer to use or build data types that have an intuitive connection to the task–and a) LinkedList
在这种情况下最适合任务.(fits the task best in this case.)
堆栈溢出(StackOverflow) 有一些有用的答案,我觉得这里很有用.(had some helpful answers that I found useful here.)
- C#随机数生成器线程安全吗?(Is C# Random Number Generator thread safe?) :创建伪随机数生成器的绝佳答案,这些伪随机数生成器本身是随机种子的(: An elegant answer for creating pseudo-random number generators that are themselves seeded randomly)
- 在C#中随机化列表(Randomize a List in C#) :用于对列表进行混排的简单线性算法(: A simple linear algorithm for shuffling a list)
有趣的是这里锁定了种子的来源,因此没有两个实例(The interesting note here is the lock on the source of seeds, so that no two instances of)
ThreadSafeRandom
将使用相同的静态结果(will use the same result of the static)Random
.我自由地承认我复制了(. I freely admit that I copied the)public
方法(methods of)Random
-包括文档.(–to include the documentation.)(我宁愿感到缺乏((I rather felt the lack of an)IRandom
.NET中的界面.)(interface in .NET.))
线程安全随机(ThreadSafeRandom)
/// <summary>
/// A Random number generator that's seeded randomly.
/// </summary>
public class ThreadSafeRandom
{
#region Private Fields
/// <summary>
/// Source of seeds
/// </summary>
private static readonly Random globalRandom = new Random();
/// <summary>
/// A field for storing the internal (local) random number generator
/// </summary>
[ThreadStatic]
private static Random localRandom;
#endregion
#region Constructor
/// <summary>
/// Create a new pseudo-random number generator that is seeded randomly
/// </summary>
public ThreadSafeRandom()
{
if (ThreadSafeRandom.localRandom == null)
{
int seed;
lock (ThreadSafeRandom.globalRandom)
{
seed = ThreadSafeRandom.globalRandom.Next();
}
localRandom = new Random(seed);
}
}
#endregion
#region Public Methods
/// <summary>
/// Returns a nonnegative number
/// </summary>
/// <returns>A 32-bit signed integer greater than
/// or equal to zero and less than int.MaxValue.
/// </returns>
public int Next()
{
return ThreadSafeRandom.localRandom.Next();
}
/// <summary>
/// Returns a nonnegative random number less than the specified maximum.
/// </summary>
/// <param name="maxValue">
/// The exclusive upper bound of the random number to be generated.
/// maxValue must be greater than or equal to zero.
/// </param>
/// <returns>
/// A 32-bit signed integer greater than or equal to zero, and less than maxValue;
/// that is, the range of return values ordinarily includes zero but not maxValue.
/// However, if maxValue equals zero, maxValue is returned.
/// </returns>
public int Next(int maxValue)
{
return ThreadSafeRandom.localRandom.Next(maxValue);
}
/// <summary>
/// Returns a random number within a specified range.
/// </summary>
/// <param name="minValue">The inclusive lower bound of the random number returned.
/// </param>
/// <param name="maxValue">
/// The exclusive upper bound of the random number returned.
/// maxValue must be greater than or equal to minValue.
/// </param>
/// <returns>
/// 32-bit signed integer greater than or equal to minValue and less than maxValue;
/// that is, the range of return values includes minValue but not maxValue.
/// If minValue equals maxValue, minValue is returned.
/// </returns>
public int Next(int minValue, int maxValue)
{
return ThreadSafeRandom.localRandom.Next(minValue, maxValue);
}
/// <summary>
/// Fills the elements of a specified array of bytes with random numbers.
/// </summary>
/// <param name="buffer">An array of bytes to contain random numbers. </param>
public void NextBytes(byte[] buffer)
{
ThreadSafeRandom.localRandom.NextBytes(buffer);
}
/// <summary>
/// Returns a random number between 0.0 and 1.0.
/// </summary>
/// <returns>
/// A double-precision floating point number greater than or equal to 0.0,
/// and less than 1.0.
/// </returns>
public double NextDouble()
{
return ThreadSafeRandom.localRandom.NextDouble();
}
#endregion
}
这是我的集合扩展方法.对于(And here’s my extension methods for collections. For) Shuffle
,我需要执行收藏(, I needed the collection to implement) IList<T>
才能使用索引器.否则,该算法将变得慢得多.为了(to be able to use an indexer. Without that, this algorithm would be much slower. For the) LinkedList
方法,我用LINQ方法(methods, I use the LINQ method) <a href="http://msdn.microsoft.com/en-us/library/bb337697.aspx">Any()</a>
确定是否有任何元素.(to determine whether there are any elements.)
我个人觉得(I personally feel that) !list.Any()
的意图比(is has a clearer intent than) list.FirstOrDefault() == null
或(假设清单工具(or (assuming list implements) ICollection<T>
)()) list.Count == 0
.(.)
IList .Shuffle()(IList.Shuffle())
public static class CollectionExtensions
{
private static readonly ThreadSafeRandom random = new ThreadSafeRandom();
/// <summary>
/// In place shuffle of a list (based on Fisher-Yates shuffle)
/// </summary>
/// <typeparam name="T">type of list to shuffle</typeparam>
/// <param name="list">list to shuffle</param>
public static void Shuffle<T>(this IList<T> list)
{
int shuffleToIdx = list.Count;
while (shuffleToIdx > 1)
{
shuffleToIdx -= 1;
int shuffleFromIdx = random.Next(shuffleToIdx + 1);
T value = list[shuffleFromIdx];
list[shuffleFromIdx] = list[shuffleToIdx];
list[shuffleToIdx] = value;
}
}
/// <summary>
/// Removes and returns the first element of the list
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <returns></returns>
public static T PopFirst<T>(this LinkedList<T> list)
{
if (list == null || !list.Any())
return default(T);
T element = list.First.Value;
list.RemoveFirst();
return element;
}
/// <summary>
/// Removes and returns the first element of the list
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <returns></returns>
public static T PopLast<T>(this LinkedList<T> list)
{
if (list == null || !list.Any())
return default(T);
T element = list.Last.Value;
list.RemoveLast();
return element;
}
}
下一步是什么?(What’s Next?)
下次,我将介绍如何在游戏中代表麻将场景.您可以在以下位置找到完整的源(Next time, I’ll go into how I’ll be representing Mahjong sets in the game. You can find the complete source at) ruleofready.codeplex.com(ruleofready.codeplex.com) .(.)
历史(History)
- 20(20)日(th)2013年10月:初始版本(October, 2013: Initial version)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
.NET4.5 .NET Visual-Studio Dev 新闻 翻译