より良いエンジニアを目指して

1日1つ。良くなる!上手くなる!

ストップウォッチで正確に計測するということ

仕事で使っているシステムには時間を計測する機能があるのですが、ふと以下のようなゲームを思い出しました。

www.itmedia.co.jp

昔、ギリギリ10秒に近づけるゲームってやりましたね。G-SHOCKとか使ってやってました。

後述しますがやってみると5秒の方がゲームのテンポが早くて正解だと思います。

でも、正確に計測するって難しくない? 100m走とか先生、ストップウォッチ止めてるけど、ずれたりしてない? って。

Google検索すると以下のような記事がヒットしました。

https://www.jstage.jst.go.jp/article/jjtehpe/18/0/18_27/_pdf/-char/ja

詳しいことは論文の内容を読んでいただくとして、手動でストップウォッチで計測すると実際より速い値になってしまうようです。

概ね-0.2秒。悪いと-0.45秒。

遅いのであれば、わかります。通り過ぎてから押したから。ですが、ある程度、人は予測してストップウォッチを止めているということでしょう。

Let's hack !!!

最近、技術的なことを書くことが減っていてヤバいので、10秒で止めるゲームアプリケーションをWPFで書いてみます。

久しぶりにWPFのViewModelを書こうとしてどうすればいいのか焦りました。

ReactivePropertyを使います。

MainWindow.xaml

<Window x:Class="StopwatchApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StopwatchApplication"
        mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:StopwatchViewModel}"
        Title="MainWindow" Height="250" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0"  Background="{Binding BackgroundColor.Value}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" Text="{Binding TimeText.Value}" FontSize="32"/>
                <TextBlock Grid.Column="1" Text="{Binding ReviewText.Value}" FontSize="32"/>
        </Grid>
        <Button Grid.Row="1" Command="{Binding ActCommand, Mode=OneWay}" Content="{Binding ButtonName.Value}" Height="40"/>
    </Grid>
</Window>

MainWindow.xaml.cs

#region

using System.Windows;

#endregion

namespace StopwatchApplication
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new StopwatchViewModel();
        }
    }
}

StopwatchViewModel.cs

#region

using System;
using System.Diagnostics;
using System.Windows.Media;
using Reactive.Bindings;

#endregion

namespace StopwatchApplication
{
    public class StopwatchViewModel
    {
        /// <summary>
        /// 時間計測オブジェクト
        /// </summary>
        private readonly Stopwatch _stopwatch = new Stopwatch();

        /// <summary>
        /// <see cref="StopwatchViewModel"/>のコンストラクタです。
        /// </summary>
        public StopwatchViewModel()
        {
            ActCommand = new ReactiveCommand();
            ActCommand.Subscribe(_ => ExecuteAction());
            SetButtonName();
            BackgroundColor.Value = new SolidColorBrush(Colors.Transparent);
        }

        /// <summary>
        /// 時間
        /// </summary>
        public ReactiveProperty<string> TimeText { get; } = new ReactiveProperty<string>();

        /// <summary>
        /// 評価
        /// </summary>
        public ReactiveProperty<string> ReviewText { get; } = new ReactiveProperty<string>();

        /// <summary>
        /// ボタン名
        /// </summary>
        public ReactiveProperty<string> ButtonName { get; } = new ReactiveProperty<string>();

        /// <summary>
        /// ボタン処理
        /// </summary>
        public ReactiveCommand ActCommand { get; }

        /// <summary>
        /// 評価
        /// </summary>
        public ReactiveProperty<SolidColorBrush> BackgroundColor { get; } = new ReactiveProperty<SolidColorBrush>();

        /// <summary>
        /// ボタン名を設定
        /// </summary>
        private void SetButtonName()
        {
            ButtonName.Value = _stopwatch.IsRunning ? "Stop" : "Start";
        }

        /// <summary>
        /// ボタン処理を実行します。
        /// </summary>
        private void ExecuteAction()
        {
            if (_stopwatch.IsRunning)
            {
                _stopwatch.Stop();
                TimeText.Value = _stopwatch.Elapsed.TotalSeconds + "s";
                if (_stopwatch.Elapsed.TotalSeconds >= 10d)
                {
                    ReviewText.Value = "Over...";
                    BackgroundColor.Value = new SolidColorBrush(Colors.DeepPink);
                }
                else if (_stopwatch.Elapsed.TotalSeconds >= 9.75d)
                {
                    ReviewText.Value = "Great!!!";
                    BackgroundColor.Value = new SolidColorBrush(Colors.DeepSkyBlue);
                }
                else if (_stopwatch.Elapsed.TotalSeconds >= 9.5d)
                {
                    ReviewText.Value = "Nice!";
                    BackgroundColor.Value = new SolidColorBrush(Colors.YellowGreen);
                }
                else if (_stopwatch.Elapsed.TotalSeconds >= 9.0d)
                {
                    ReviewText.Value = "Good!";
                    BackgroundColor.Value = new SolidColorBrush(Colors.DarkOrange);
                }
                else
                {
                    ReviewText.Value = "Bad...";
                    BackgroundColor.Value = new SolidColorBrush(Colors.Yellow);
                }
            }
            else
            {
                TimeText.Value = "Purpose:10s";
                ReviewText.Value = "Starting...";
                BackgroundColor.Value = new SolidColorBrush(Colors.Transparent);
                _stopwatch.Reset();
                _stopwatch.Start();
            }
        }
    }
}

実行例

9.75s〜10sだとGreatが出るのですが作った本人、一度も出せませんでした。

101,102,103と100をつけて計測するといいよという記事を読んだのですが、するとオーバーを連発。

年をとって体内時計が遅くなったかっ!と思いました。(ただ、この100をつける数え方は単純に数を数えるときに有用だったりします。私、数を数えるといつの間にか飛んでいたりするので)

1,2,3,4と普通に数えると7.8sとかBad連発...。

f:id:rimever:20200705142946p:plain

f:id:rimever:20200705142808p:plain

IsHighResolution

StopWatch.IsHighResolutionはTrueでした。

dobon.net