<commit_before>﻿// Copyright (c) the authors of NanoGames. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt in the project root.

using NanoGames.Application.Ui;
using NanoGames.Engine;
using NanoGames.Games;
using NanoGames.Synchronization;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace NanoGames.Application
{
    /// <summary>
    /// A view that allows players to play singleplayer practice games.
    /// </summary>
    internal sealed class PracticeView : IView
    {
        private static readonly Color _playerColor = new Color(0.1, 0.4, 0.8);

        private readonly Action _goBack;
        private readonly Menu _selectDisciplineMenu;

        private Terminal _currentTerminal;
        private Match _match;

        private BufferedRenderer _bufferedRenderer;
        private Graphics _bufferedGraphics;
        private long _matchStartTimestamp;
        private long _matchFrameCount = 0;

        /// <summary>
        /// Initializes a new instance of the <see cref="PracticeView"/> class.
        /// </summary>
        /// <param name="goBack">The action to call when the player leaves the practice view.</param>
        public PracticeView(Action goBack)
        {
            _goBack = goBack;

            _bufferedRenderer = new BufferedRenderer();
            _bufferedGraphics = new Graphics(_bufferedRenderer);

            _selectDisciplineMenu = new Menu("SELECT GAME")
            {
                OnBack = GoBack,
                Items = DisciplineDirectory.Disciplines.Select(d => new CommandMenuItem(d.Name, () => StartMatch(d))).ToList<MenuItem>(),
            };

            _selectDisciplineMenu.Items.Insert(0, new CommandMenuItem("BACK", GoBack));
        }

        /// <inheritdoc/>
        public void Update(Terminal terminal)
        {
            if (_match != null)
            {
                if (terminal.KeyEvents.Any(k => k.Code == KeyCode.Escape))
                {
                    _match = null;
                }
                else
                {
                    UpdateMatch(terminal);
                }
            }

            if (_match == null)
            {
                _currentTerminal = terminal;
                _selectDisciplineMenu.Update(terminal);
            }
        }

        private void UpdateMatch(Terminal terminal)
        {
            var currentTimestamp = Stopwatch.GetTimestamp();

            if (_matchStartTimestamp == 0)
            {
                _matchStartTimestamp = currentTimestamp;
            }

            /* Check if the buffered frame is still valid and compute new frames as needed. */
            while (Stopwatch.GetTimestamp() - _matchStartTimestamp - _matchFrameCount * GameSpeed.FrameInterval > GameSpeed.FrameInterval)
            {
                ++_matchFrameCount;
                _bufferedRenderer.Clear();
                _match.Update(
                    new List<PlayerDescription>
                    {
                                new PlayerDescription
                                {
                                    Graphics = _bufferedGraphics,
                                    Input = terminal.Input,
                                },
                    });

                if (_match.IsCompleted)
                {
                    _match = null;
                    return;
                }
            }

            /* Render the buffered frame. */
            _bufferedRenderer.RenderTo(terminal.Renderer);
        }

        private void GoBack()
        {
            _goBack?.Invoke();
        }

        private void StartMatch(Discipline discipline)
        {
            var description = new MatchDescription
            {
                Players =
                {
                    new PlayerDescription
                    {
                        Color = _playerColor,
                    },
                },
            };

            _match = discipline.CreateMatch(description);
        }
    }
}
<commit_msg>Fix escape triggering twice when leaving a practice match<commit_after>﻿// Copyright (c) the authors of NanoGames. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt in the project root.

using NanoGames.Application.Ui;
using NanoGames.Engine;
using NanoGames.Games;
using NanoGames.Synchronization;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace NanoGames.Application
{
    /// <summary>
    /// A view that allows players to play singleplayer practice games.
    /// </summary>
    internal sealed class PracticeView : IView
    {
        private static readonly Color _playerColor = new Color(0.1, 0.4, 0.8);

        private readonly Action _goBack;
        private readonly Menu _selectDisciplineMenu;

        private Terminal _currentTerminal;
        private Match _match;

        private BufferedRenderer _bufferedRenderer;
        private Graphics _bufferedGraphics;
        private long _matchStartTimestamp;
        private long _matchFrameCount = 0;

        /// <summary>
        /// Initializes a new instance of the <see cref="PracticeView"/> class.
        /// </summary>
        /// <param name="goBack">The action to call when the player leaves the practice view.</param>
        public PracticeView(Action goBack)
        {
            _goBack = goBack;

            _bufferedRenderer = new BufferedRenderer();
            _bufferedGraphics = new Graphics(_bufferedRenderer);

            _selectDisciplineMenu = new Menu("SELECT GAME")
            {
                OnBack = GoBack,
                Items = DisciplineDirectory.Disciplines.Select(d => new CommandMenuItem(d.Name, () => StartMatch(d))).ToList<MenuItem>(),
            };

            _selectDisciplineMenu.Items.Insert(0, new CommandMenuItem("BACK", GoBack));
        }

        /// <inheritdoc/>
        public void Update(Terminal terminal)
        {
            if (_match != null)
            {
                if (terminal.KeyEvents.Any(k => k.Code == KeyCode.Escape))
                {
                    _match = null;
                    terminal.KeyEvents.Clear();
                }
                else
                {
                    UpdateMatch(terminal);
                }
            }

            if (_match == null)
            {
                _currentTerminal = terminal;
                _selectDisciplineMenu.Update(terminal);
            }
        }

        private void UpdateMatch(Terminal terminal)
        {
            var currentTimestamp = Stopwatch.GetTimestamp();

            if (_matchStartTimestamp == 0)
            {
                _matchStartTimestamp = currentTimestamp;
            }

            /* Check if the buffered frame is still valid and compute new frames as needed. */
            while (Stopwatch.GetTimestamp() - _matchStartTimestamp - _matchFrameCount * GameSpeed.FrameInterval > GameSpeed.FrameInterval)
            {
                ++_matchFrameCount;
                _bufferedRenderer.Clear();
                _match.Update(
                    new List<PlayerDescription>
                    {
                                new PlayerDescription
                                {
                                    Graphics = _bufferedGraphics,
                                    Input = terminal.Input,
                                },
                    });

                if (_match.IsCompleted)
                {
                    _match = null;
                    return;
                }
            }

            /* Render the buffered frame. */
            _bufferedRenderer.RenderTo(terminal.Renderer);
        }

        private void GoBack()
        {
            _goBack?.Invoke();
        }

        private void StartMatch(Discipline discipline)
        {
            var description = new MatchDescription
            {
                Players =
                {
                    new PlayerDescription
                    {
                        Color = _playerColor,
                    },
                },
            };

            _match = discipline.CreateMatch(description);
        }
    }
}

{'fraction_non_alphanumeric': 0.07773465922871696, 'fraction_numerical': 0.00218287654620422, 'mean_word_length': 1.3643922018348624, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4609606', 'n_tokens_mistral': 1998, 'n_tokens_neox': 1821, 'n_words': 479}