public void SavePGN(Stream outputStream) { if (null == outputStream) { throw new ArgumentNullException(nameof(outputStream)); } using (StreamWriter sw = new StreamWriter(outputStream, Encoding.ASCII)) { // Write Mandatory Tags sw.WriteLine(GetPGNTag("GameType", EnumUtils.GetExpansionPiecesString(Metadata.GameType))); sw.WriteLine(GetPGNTag("Date", Metadata.Date)); sw.WriteLine(GetPGNTag("Event", Metadata.Event)); sw.WriteLine(GetPGNTag("Site", Metadata.Site)); sw.WriteLine(GetPGNTag("Round", Metadata.Round)); sw.WriteLine(GetPGNTag("White", Metadata.White)); sw.WriteLine(GetPGNTag("Black", Metadata.Black)); sw.WriteLine(GetPGNTag("Result", Metadata.Result.ToString())); // Write Optional Tags foreach (KeyValuePair <string, string> tag in Metadata.OptionalTags) { sw.WriteLine(GetPGNTag(tag.Key, tag.Value)); } if (GameBoard.BoardHistory.Count > 0) { sw.WriteLine(); WritePGNMoveCommentary(sw, 0); // Write Moves int count = 1; foreach (BoardHistoryItem item in GameBoard.BoardHistory) { sw.WriteLine("{0}. {1}", count, NotationUtils.NormalizeBoardSpaceMoveString(item.MoveString)); WritePGNMoveCommentary(sw, count); count++; } } // Write Result if (EnumUtils.GameIsOver(Metadata.Result)) { sw.WriteLine(); sw.WriteLine(Metadata.Result.ToString()); } } }
public static GameRecording LoadSGF(Stream inputStream, string fileName = null) { if (null == inputStream) { throw new ArgumentNullException(nameof(inputStream)); } GameMetadata metadata = new GameMetadata(); List <string> moveList = new List <string>(); Dictionary <string, Stack <string> > backupPositions = new Dictionary <string, Stack <string> >(); string rawResult = ""; bool lastMoveCompleted = true; bool whiteTurn = true; using (StreamReader sr = new StreamReader(inputStream)) { string line = null; while ((line = sr.ReadLine()) != null) { line = line.Trim(); if (!string.IsNullOrWhiteSpace(line)) { // Line has contents Match m = null; if ((m = Regex.Match(line, @"SU\[(.*)\]")).Success) { metadata.SetTag("GameType", m.Groups[1].Value.ToUpper().Replace("HIVE", EnumUtils.NoExpansionsString).Replace("-", "+")); } else if ((m = Regex.Match(line, @"EV\[(.*)\]")).Success) { metadata.SetTag("Event", m.Groups[1].Value); } else if ((m = Regex.Match(line, @"PC\[(.*)\]")).Success) { metadata.SetTag("Site", m.Groups[1].Value); } else if ((m = Regex.Match(line, @"RO\[(.*)\]")).Success) { metadata.SetTag("Round", m.Groups[1].Value); } else if ((m = Regex.Match(line, @"DT\[(.+)\]")).Success) { string rawDate = m.Groups[1].Value; metadata.SetTag("SgfDate", rawDate); rawDate = Regex.Replace(rawDate, @"(\D{3}) (\D{3}) (\d{2}) (\d{2}):(\d{2}):(\d{2}) (.{3,}) (\d{4})", @"$1 $2 $3 $4:$5:$6 $8"); if (DateTime.TryParseExact(rawDate, SgfDateFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsed)) { metadata.SetTag("Date", parsed.ToString("yyyy.MM.dd")); } else { foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.AllCultures)) { if (DateTime.TryParseExact(rawDate, SgfDateFormats, ci, DateTimeStyles.None, out parsed)) { metadata.SetTag("Date", parsed.ToString("yyyy.MM.dd")); break; } } } } else if ((m = Regex.Match(line, @"P0\[id ""(.*)""\]")).Success) { metadata.SetTag("White", m.Groups[1].Value); } else if ((m = Regex.Match(line, @"P1\[id ""(.*)""\]")).Success) { metadata.SetTag("Black", m.Groups[1].Value); } else if ((m = Regex.Match(line, @"RE\[(.+)\]")).Success) { rawResult = m.Groups[1].Value; metadata.SetTag("SgfResult", m.Groups[1].Value); } else if ((m = Regex.Match(line, @"GN\[(.+)\]")).Success) { metadata.SetTag("SgfGameName", m.Groups[1].Value); } else if ((m = Regex.Match(line, @"((move (w|b))|(dropb)|(pdropb)) ([a-z0-9]+) ([a-z] [0-9]+) ([a-z0-9\\\-\/\.]*)", RegexOptions.IgnoreCase)).Success) { // Initial parse string movingPiece = m.Groups[m.Groups.Count - 3].Value.ToLower(); string destination = m.Groups[m.Groups.Count - 1].Value.ToLower().Replace("\\\\", "\\"); string backupPos = m.Groups[m.Groups.Count - 2].Value; // Remove unnecessary numbers movingPiece = movingPiece.Replace("m1", "m").Replace("l1", "l").Replace("p1", "p"); destination = destination.Replace("m1", "m").Replace("l1", "l").Replace("p1", "p"); // Add missing color indicator if (movingPiece == "b1" || movingPiece == "b2" || !(movingPiece.StartsWith("b") || movingPiece.StartsWith("w"))) { movingPiece = (whiteTurn ? "w" : "b") + movingPiece; } // Fix missing destination if (destination == ".") { if (moveList.Count == 0) { destination = ""; } else { destination = backupPositions[backupPos].Peek(); } } // Remove move that wasn't commited if (!lastMoveCompleted) { moveList.RemoveAt(moveList.Count - 1); } moveList.Add(string.Format("{0} {1}", movingPiece, destination)); foreach (Stack <string> stack in backupPositions.Values) { if (stack.Count > 0 && stack.Peek() == movingPiece) { stack.Pop(); break; } } if (!backupPositions.ContainsKey(backupPos)) { backupPositions.Add(backupPos, new Stack <string>()); } backupPositions[backupPos].Push(movingPiece); lastMoveCompleted = false; } else if ((m = Regex.Match(line, @"P(0|1)\[[0-9]+ pass\]")).Success) { moveList.Add(NotationUtils.BoardSpacePass); lastMoveCompleted = false; } else if ((m = Regex.Match(line, @"P(0|1)\[[0-9]+ done\]")).Success) { lastMoveCompleted = true; whiteTurn = !whiteTurn; } else if ((m = Regex.Match(line, @"P(0|1)\[[0-9]+ resign\]")).Success) { rawResult = m.Groups[1].Value == "0" ? BoardState.BlackWins.ToString() : BoardState.WhiteWins.ToString(); } } } } GameBoard gameBoard = new GameBoard(metadata.GameType); foreach (string moveString in moveList) { Move move = null; try { move = NotationUtils.ParseMoveString(gameBoard, moveString); } catch (Exception ex) { throw new Exception(string.Format("Unable to parse '{0}'.", moveString), ex); } gameBoard.TrustedPlay(move, NotationUtils.NormalizeBoardSpaceMoveString(moveString)); } // Set result if (rawResult.Contains(metadata.White)) { metadata.SetTag("Result", BoardState.WhiteWins.ToString()); } else if (rawResult.Contains(metadata.Black)) { metadata.SetTag("Result", BoardState.BlackWins.ToString()); } else if (rawResult == "The game is a draw") { metadata.SetTag("Result", BoardState.Draw.ToString()); } else if (Enum.TryParse(rawResult, out BoardState parsed)) { metadata.SetTag("Result", parsed.ToString()); } else { metadata.SetTag("Result", gameBoard.BoardState.ToString()); } return(new GameRecording(gameBoard, GameRecordingSource.SGF, metadata) { FileName = fileName?.Trim() }); }
public ObservableBoardHistory(BoardHistory boardHistory, BoardHistory activeBoardHistory = null, Action <int> moveNumberChangedCallback = null) { _boardHistory = boardHistory ?? throw new ArgumentNullException(nameof(boardHistory)); _activeBoardHistory = activeBoardHistory ?? boardHistory; _moveNumberChangedCallback = moveNumberChangedCallback; if (_activeBoardHistory.Count > _boardHistory.Count) { throw new ArgumentException("Active history has more moves than history."); } int countWidth = _boardHistory.Count.ToString().Length; for (int i = 0; i < _boardHistory.Count; i++) { BoardHistoryItem item = _boardHistory[i]; string countString = (i + 1).ToString().PadLeft(countWidth) + ". "; string moveString = AppVM.ViewerConfig.NotationType == NotationType.BoardSpace ? NotationUtils.NormalizeBoardSpaceMoveString(item.MoveString) : item.ToString(); bool isActive = i < _activeBoardHistory.Count; bool isLastMove = i + 1 == _activeBoardHistory.Count; Items.Add(new ObservableBoardHistoryItem(countString + moveString, isActive, isLastMove)); } }