using System; using System.Collections.Generic; using System.Linq; using static Games.Common.IO.Strings; namespace Games.Common.IO; /// /// Reads from input and assembles a given number of values, or tokens, possibly over a number of input lines. /// internal class TokenReader { private readonly TextIO _io; private readonly Predicate _isTokenValid; private TokenReader(TextIO io, Predicate isTokenValid) { _io = io; _isTokenValid = isTokenValid ?? (t => true); } /// /// Creates a which reads string tokens. /// /// A instance. /// The new instance. public static TokenReader ForStrings(TextIO io) => new(io, t => true); /// /// Creates a which reads tokens and validates that they can be parsed as numbers. /// /// A instance. /// The new instance. public static TokenReader ForNumbers(TextIO io) => new(io, t => t.IsNumber); /// /// Reads valid tokens from one or more input lines and builds a list with the required quantity. /// /// The string used to prompt the user for input. /// The number of tokens required. /// The sequence of tokens read. public IEnumerable ReadTokens(string prompt, uint quantityNeeded) { if (quantityNeeded == 0) { throw new ArgumentOutOfRangeException( nameof(quantityNeeded), $"'{nameof(quantityNeeded)}' must be greater than zero."); } var tokens = new List(); while (tokens.Count < quantityNeeded) { tokens.AddRange(ReadValidTokens(prompt, quantityNeeded - (uint)tokens.Count)); prompt = "?"; } return tokens; } /// /// Reads a line of tokens, up to , and rejects the line if any are invalid. /// /// The string used to prompt the user for input. /// The maximum number of tokens to read. /// The sequence of tokens read. private IEnumerable ReadValidTokens(string prompt, uint maxCount) { while (true) { var tokensValid = true; var tokens = new List(); foreach (var token in ReadLineOfTokens(prompt, maxCount)) { if (!_isTokenValid(token)) { _io.WriteLine(NumberExpected); tokensValid = false; prompt = ""; break; } tokens.Add(token); } if (tokensValid) { return tokens; } } } /// /// Lazily reads up to tokens from an input line. /// /// The string used to prompt the user for input. /// The maximum number of tokens to read. /// private IEnumerable ReadLineOfTokens(string prompt, uint maxCount) { var tokenCount = 0; foreach (var token in Tokenizer.ParseTokens(_io.ReadLine(prompt))) { if (++tokenCount > maxCount) { _io.WriteLine(ExtraInput); break; } yield return token; } } }