public byte GetMove(Field field, TimeSpan min, TimeSpan max)
		{
			byte ply = (byte)(field.Count + 1);
			if (depth <= ply) { depth = (byte)(ply + 1); }
			var redToMove = (ply & 1) == 1;

			if (depth > MaximumDepth) { depth = MaximumDepth; }

			Sw.Restart();
			Logger.Clear();
			Max = max;

			var candidates = new MoveCandidates(redToMove);
			candidates.Add(field, ply, this);
			var move = candidates.GetMove();
			
			Root = GetNode(field, ply);
			Root.Add(candidates);

			for (/**/; depth <= MaximumDepth; depth++)
			{
				Root.Apply(depth, this, Scores.InitialAlpha, Scores.InitialBeta);

				move = candidates.GetMove();

				var log = new PlyLog(ply, move, Root.Score, depth, Sw.Elapsed);
				Logger.Append(log).AppendLine();

				// Don't spoil time.
				if (Sw.Elapsed > min || !TimeLeft) { break; }
			}
			return move;
		}
		public SearchTreeBookNode(Field field, byte ply, int score) : base(field, ply, score) 
		{
			m_Value = score;
			m_IsWinning = Scores.IsWinning(score);
		}
		public SearchTreeRedNode(Field field, byte depth, int value) : base(field, depth, value) { }
		public SearchTreeKnownNode(Field field, int score)
			: base(field, (byte)(field.Count + 1), score) { }
		/// <summary>Gets the score for a field.</summary>
		/// <param name="field">
		/// The field to evaluate.
		/// </param>
		/// <param name="ply">
		/// The current ply.
		/// </param>
		/// <returns>
		/// A integer representing the score.
		/// </returns>
		/// <remarks>
		/// 
		/// Winning positions
		/// 
		/// 1. Two instant threats
		/// 
		///    0,0,0,0,0,0,0
		///    0,0,0,0,0,0,0
		///    0,0,0,0,0,0,0
		///    0,0,0,2,0,0,0
		///    0,0,0,2,0,0,0
		///    0,*,1,1,1,*,0
		///    
		/// 2. Two connected threats
		/// 
		///    0,0,0,0,0,0,0
		///    0,0,0,0,0,0,0
		///    0,0,2,1,0,0,0
		///    0,0,2,2,1,0,0
		///    0,0,2,1,1,1,*
		///    0,2,1,1,2,2,*
		/// 
		/// 3. Two 'lowest' treats in different columns
		/// 
		///    0,0,0,0,0,0,0
		///    0,0,0,0,0,0,0
		///    0,0,2,1,1,0,0
		///    1,*,2,2,2,*,1
		///    1,0,1,1,2,0,1
		///    2,0,1,1,2,0,2
		/// </remarks>
		public static int GetScore(Field field, byte ply)
		{
#if !DEBUG
			unchecked
			{
#endif
				var redToMove = (ply & 1) == 1;

				ulong fieldRed = field.GetRed();
				ulong fieldYel = field.GetYellow();

				ulong threatRed = 0;
				ulong threatYel = 0;

				var trippleRed = 0;
				var tripplYel = 0;

				#region Detect threats

				for (var index = 0; index < 69; index++)
				{
					var mask = Field.Connect4[index];
					var matchRed = fieldRed & mask;
					if (matchRed == mask) { return Scores.RedWins[ply - 1]; }

					var matchYel = fieldYel & mask;
					if (matchYel == mask) { return Scores.YelWins[ply & ~1]; }

					if (matchRed != 0)
					{
						// Both have a match. Skip it.
						if (matchYel != 0) { continue; }

						var base3 = index << 2;

						// 3 out of 4, so add threat.
						if (matchRed == Field.Connect3Out4[base3] ||
							matchRed == Field.Connect3Out4[base3 | 1] ||
							matchRed == Field.Connect3Out4[base3 | 2] ||
							matchRed == Field.Connect3Out4[base3 | 3])
						{
							trippleRed++;
							threatRed |= mask;
						}
					}
					else
					{
						var base3 = index << 2;

						// 3 out of 4, so add threat.
						if (matchYel == Field.Connect3Out4[base3] ||
							matchYel == Field.Connect3Out4[base3 | 1] ||
							matchYel == Field.Connect3Out4[base3 | 2] ||
							matchYel == Field.Connect3Out4[base3 | 3])
						{
							tripplYel++;
							threatYel |= mask;
						}
					}
				}
				#endregion

				// No threats, so no score.
				if (trippleRed == 0 && tripplYel == 0) { return 0; }

				var fieldMix = fieldRed | fieldYel;

				// filled cells can not be threats, so clean that.
				threatRed &= ~fieldMix;
				threatYel &= ~fieldMix;

				// predicted forced wins based on double threat.
				var forcedWinRed = NotSet;
				var forcedWinYel = NotSet;

				// number of direct threats.
				var threatInstantRed = 0;
				var threatInstantYel = 0;

				// The row of the strongest single threat.
				var threatStrongRowRed = NotSet;
				var threatStrongRowYel = NotSet;

				// The column of the strongest single threat.
				var threatStrongColRed = NotSet;
				var threatStrongColYel = NotSet;

				var threatRedLowerYel = 0;
				var threatYelLowerRed = 0;
				var threatLowerRow0 = NotSet;
				var threatLowerRow1 = NotSet;

				for (var col = 0; col < 7; col++)
				{
					var colRed = threatRed & Field.ColumnMasks[col];
					var colYel = threatYel & Field.ColumnMasks[col];

					// Column without threats.
					if (colRed == 0 && colYel == 0) { continue; }

					var colMix = fieldMix & Field.ColumnMasks[col];

					var colHighestFilled = -1;
					var threatLowestColumnRed = NotSet;
					var threatLowestColumnYel = NotSet;

					for (var row = 0; row < 6; row++)
					{
						ulong mask = 1UL << ((row << 3) | col);

						// The cell is already filled, so no threat.
						if ((colMix & mask) != 0)
						{
							colHighestFilled = row;
							continue;
						}

						var gap = row - colHighestFilled;

						// A red threat in the cell.
						if ((colRed & mask) != 0)
						{
							// Instant threat.
							if (gap == 1)
							{
								if (redToMove)
								{
									return Scores.RedWins[ply];
								}
								else
								{
									threatInstantRed++;
								}
							}
							// we found the first threat in this column.
							if (threatLowestColumnRed == NotSet)
							{
								threatLowestColumnRed = row;

								// Strong odd-row threat (zero based).
								if ((row & 1) == 0 && row < threatStrongRowRed)
								{
									threatStrongRowRed = row;
									threatStrongColRed = col;
								}
							}
							// Two connected threats in the same column and
							// the lowest of the two is exclusive for red.
							else if (
								threatLowestColumnRed + 1 == row &&
								threatLowestColumnRed <= threatLowestColumnYel)
							{
								// the cells to fill in the turns to come.
								var delay = 0;
								var distance = threatLowestColumnRed - colHighestFilled;
								var otherOptions = SearchTree.MaximumDepth - (5 - colHighestFilled) - ply;
								if (redToMove)
								{
									distance--;
									delay = DoubleThreatDelay[distance | (Math.Min(5, otherOptions) << 3)];
								}
								else
								{
									delay = DoubleThreatDelay[distance | (Math.Min(5, otherOptions) << 3)];
									delay--;
								}

								var forced = ply + delay;

								// We found potentially a quick win.
								if (forced < forcedWinRed)
								{
									forcedWinRed = forced;
									break;
								}
							}
							// further searching is meaningless.
							else if (row >= threatStrongRowRed)
							{
								// This column is for red.
								if (gap > 1 && threatLowestColumnRed < threatLowestColumnYel)
								{
									threatRedLowerYel++;
									if (row < threatLowerRow0)
									{
										threatLowerRow1 = threatLowerRow0;
										threatLowerRow0 = row;
									}
									else if (row < threatLowerRow1)
									{
										threatLowerRow1 = row;
									}
								}
								break;
							}
						}
						// A yellow threat in the cell.
						if ((colYel & mask) != 0)
						{
							// Instant threat.
							if (gap == 1)
							{
								if (redToMove)
								{
									threatInstantYel++;
								}
								else
								{
									return Scores.YelWins[ply];
								}
							}
							// we found the first threat in this column.
							if (threatLowestColumnYel == NotSet)
							{
								threatLowestColumnYel = row;

								// Strong even-row threat (zero based).
								if ((row & 1) == 1 && row < threatStrongRowYel)
								{
									threatStrongRowYel = row;
									threatStrongColYel = col;
								}
								// This column is for yellow.
								if (gap > 1 && threatLowestColumnYel < threatLowestColumnRed)
								{
									threatYelLowerRed++;
									if (row < threatLowerRow0)
									{
										threatLowerRow1 = threatLowerRow0;
										threatLowerRow0 = row;
									}
									else if (row < threatLowerRow1)
									{
										threatLowerRow1 = row;
									}
								}
							}
							// Two connected threats in the same column and
							// the lowest of the two is exclusive for red.
							else if (
								threatLowestColumnYel + 1 == row &&
								threatLowestColumnYel <= threatLowestColumnRed)
							{
								// the cells to fill in the turns to come.
								var delay = 0;
								var distance = threatLowestColumnYel - colHighestFilled;
								var otherOptions = SearchTree.MaximumDepth - (5 - colHighestFilled) - ply;
								if (!redToMove)
								{
									distance--;
									delay = DoubleThreatDelay[distance | (Math.Min(5, otherOptions) << 3)];
								}
								else
								{
									delay = DoubleThreatDelay[distance | (Math.Min(5, otherOptions) << 3)];
									delay--;
								}

								var forced = ply + delay;

								// We found potentially a quick win.
								if (forced < forcedWinYel)
								{
									forcedWinYel = forced;
									break;
								}
							}
							// further searching is meaningless.
							else if (row >= threatStrongRowYel)
							{
								break;
							}
						}
					}
				}

				// Two spots to claim victory
				if (threatInstantRed > 1 && threatInstantYel == 0)
				{
					return Scores.RedWins[ply + 1];
				}
				if (threatInstantYel > 1 && threatInstantRed == 0)
				{
					return Scores.YelWins[ply + 1];
				}

				if (forcedWinRed != NotSet || forcedWinYel != NotSet)
				{
					if (forcedWinRed < forcedWinYel)
					{
						return Scores.RedWins[forcedWinRed];
					}
					else
					{
						return Scores.YelWins[forcedWinYel];
					}
				}

				var score = 0;

				// Double lower threat for red.
				if (threatRedLowerYel > 1 && threatYelLowerRed == 0)
				{
					//var turn = SearchTree.MaximumDepth - 12 + threatLowerRow0 + threatLowerRow1;
					//return Scores.RedWins[turn | 1];
					score += Scores.StrongThreat;
					
				}
				// Double lower threat for yel.
				if (threatYelLowerRed > 1 && threatRedLowerYel == 0)
				{
					//var turn = SearchTree.MaximumDepth - 12 + threatLowerRow0 + threatLowerRow1;
					//return Scores.RedWins[turn & ~1];
					score -= Scores.StrongThreat;
				}

				if (threatStrongRowRed != NotSet)
				{
					// Lowest threat is red
					if (threatStrongRowRed < threatStrongRowYel)
					{
						score += Scores.StrongThreat;
					}
					// Lowest threat in same column for yellow.
					else if (threatStrongColYel == threatStrongColRed)
					{
						score -= Scores.StrongThreat;

					}
					// Lowest threat for yellow but different column.
					else
					{
						score += Scores.StrongThreat >> 1;
					}
				}
				// Only yellow has a strong threat.
				else if (threatStrongRowYel != NotSet)
				{
					score -= Scores.StrongThreat;
				}
				score += trippleRed;
				score -= tripplYel;
				return score;
#if !DEBUG
			}
#endif
		}
		public Field[] GetMoves(Field field, bool IsRed)
		{
			return Generator.GetMoves(field, IsRed);
		}
		/// <summary>Gets a node with the field to search for.</summary>
		/// <param name="search">
		/// The field to search for.
		/// </param>
		/// <param name="ply">
		/// The current ply. This should be 1 higher than the discs at the field.
		/// </param>
		/// <returns>
		/// An existing node if already existing, otherwise a new one.
		/// </returns>
		public ISearchTreeNode GetNode(Field search, byte ply)
		{
			ISearchTreeNode node;

			var redToMove = (ply & 1) == 1;

			if (!tree[ply].TryGetValue(search, out node))
			{
				// Losses and draws are already added, so the missing a wins.
				if (ply == 9)
				{
					if (search.IsScoreYellow())
					{
						node = new SearchTreeEndNode(9, Scores.YelWins[9]);
					}
					else
					{
						node = new SearchTreeBookNode(search, 9, Scores.RedWin >> 3);
					}
				}
				else
				{
					var score = Evaluator.GetScore(search, ply);

					// If the node is final for the other color, no need to search deeper.
					if ((!redToMove && score == Scores.RedWins[ply -1]) ||
						(redToMove && score == Scores.YelWins[ply -1]))
					{
						node = new SearchTreeEndNode(ply, score);
					}
					// Game is done.
					else if (ply == MaximumDepth)
					{
						node = new SearchTreeEndNode(MaximumDepth, 0);
					}
					else if (redToMove)
					{
						node = new SearchTreeRedNode(search, ply, score);
					}
					else
					{
						node = new SearchTreeYellowNode(search, ply, score);
					}
					tree[ply][search] = node;
				}
			}
			else
			{
				trans[ply]++;
			}
			return node;
		}