【C#】日時の計算（DateTime, TimeSpan）おまけ：放置ゲームもどき

メモ帳苦行開発。その2。
使うかどうかわからない備忘録。

.NET Framework には、時間を表す構造体が2種類あるそうな。（めんｄ、、）

 - 特定の日時を表す `DateTime構造体（System名前空間）`
 - 時間間隔を表す `TimeSpan構造体（System名前空間）`

これら2つを使って現在時刻を取得したり、2つの日時の差異を計算できる。


【参考】
■[C# File] テキストファイルの読み込みと書き込み
https://yaspage.com/prog/csharp/cs-sample-fileio/

■日時や時間間隔の加減算を行うには？
https://www.atmarkit.co.jp/ait/articles/0502/25/news125.html

■タイマにより一定時間間隔で処理を行うには？（Windowsタイマ編）
https://www.atmarkit.co.jp/ait/articles/0511/11/news117.html

■Microsoft .NET入門 > 6.塗りつぶす
http://wisdom.sakura.ne.jp/system/msnet/msnet_win6.html

# 現在の日時を取得
現在の日時を取得するには、DateTime.Now でできる。
DateTime型の変数にぶち込む。

```c#:
DateTime todayData = DateTime.Now;
Console.WriteLine(todayData); //出力 yyyy/MM/dd hh:mm:ss
```

こうして得た日時を、テキストファイルなんかに記録して、次回起動したときに読み込みんでその時の日時と比較すれば差異を得られる。

テキストファイルに書き込むには`StreamWriterクラス(System.IO名前空間)`の`WriteLine()メソッド`か、`File.WriteAllText()メソッド(System.IO名前空間)`を使う。

StreamWriter だと、`Close()` しないといけなかったり、ちょっとめんどいなー、という印象。
まぁ、とにかく使ってみる。

```c#:
//文字コードを指定
Encoding enc = Encoding.GetEncoding("utf-8");

//書き込むファイルを開く。falseは上書きtrueは追記
StreamWriter writer = new StreamWriter(@"<ファイルのパス>", false, enc);

//テキストを書き込む。
writer.WriteLine(todayData.ToString("yyyy/MM/dd HH:mm:ss"));

//書き込むファイルを閉じる
writer.Close();
```

これで、`<ファイルのパス>`に指定したファイルにテキストとして保存される。

書き込んだ場合は、`StreamReaderクラス`の`ReadToEnd()メソッド`か`File.ReadAllLines()メソッド`を使う。
StreamReader は同じく`Close()`が必要。

```c#:
//前回日時を書き込んだファイルを読み込む。
StreamReader sr = new StreamReader(@"<ファイルのパス>", enc);

DateTime oldData = DateTime.Parse(sr.ReadToEnd());

//ファイル読み込む終了。
sr.Close();

```

現在の日時と比較するために、DateTime構造体にパースしている。
（parseとかキャストとかToStringとかややこしいね）

DateTime 型にさえなっちゃえば、単純に引き算できる。
計算結果の「時間間隔」については`TimeSpam型`じゃないとダメらしい。（ややこしい）


```c#:
//今日の日付 - 前の日付 = 何時間ぶりか
TimeSpan diffTime = todayData - oldData;
```

時間、分、秒を取り出すにはそれぞれ`TotalHours`、`Minutes`、`Seconds`などのプロパティから取り出す。
`TotalHours`のみは、`(int)`キャストしないと、0.XXXXXXXXXXXXみたいな数値になる。

# 前回の実行から何時間ぶりかを計算するプログラム

上記を利用して、前回の実行から何時間ぶりかを表示するだけのプログラムを作った。
たぶん、日をまたいだらその分も時間加算してくれるはず。

```c#:C#TimeSpanCalc.cs
//前回開いた時間から何時間ぶりかを計算するプログラム。
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

class Program
{
  //メイン関数
  static void Main()
  {
    //インスタンス作成。
    Form1 f = new Form1();
    f.Text = "前回の実行からの間隔時間を計算するプログラム";
    f.Bounds = new Rectangle(100, 100, 500, 200);

    Application.Run(f);
  }//Main()
}

public class Form1 : Form
{
  //文字コードを指定
  Encoding enc = Encoding.GetEncoding("utf-8");
    
  //現在の日時を取得
  DateTime todayData = DateTime.Now;

  //部品の宣言はコンストラクタの外側のスコープに置く
  public Label lb1 = new Label();
  Label lb2 = new Label();
  Label lb3 = new Label();

  //コンストラクタ、プロパティはこの中で設定
  //メソッドとか使う時もこの中じゃないと「無効なトークン"("」が出てしまう。
  public Form1()
  {
    //前回日時を書き込んだファイルを読み込む。
    StreamReader sr = new StreamReader(@"C#TimeSpanCalc_OldDayData.txt", enc);

    DateTime oldData = DateTime.Parse(sr.ReadToEnd());

    //ファイル読み込む終了。
    sr.Close();

    //今日の日付 - 前の日付 = 何時間ぶりか
    TimeSpan diffTime = todayData - oldData;

    //書き込むファイルを開く。falseは上書きtrueは追記
    StreamWriter writer = new StreamWriter(@"C#TimeSpanCalc_OldDayData.txt", false, enc);

    //テキストを書き込む。
    writer.WriteLine(todayData.ToString("yyyy/MM/dd HH:mm:ss"));

    //書き込むファイルを閉じる
    writer.Close();

    //部品の内容、位置を定義しフォームに表示
    lb1.Text = "現在の日時：" + todayData;
    lb2.Text = "前回の日時：" + oldData;
    lb3.Text = "前回の実行から"
               + (int)diffTime.TotalHours + "時間"
               + diffTime.Minutes + "分" 
               + diffTime.Seconds + "秒ぶりの実行ですね！！！";

    //ラベルの位置を指定
    lb1.Bounds = new Rectangle(10, 10 + (0 * 20), 400, 20);
    lb2.Bounds = new Rectangle(10, 10 + (1 * 20), 400, 20);
    lb3.Bounds = new Rectangle(10, 10 + (2 * 20), 400, 20);

    //コントロールに追加。これでフォームに表示される
    Controls.Add(lb1);
    Controls.Add(lb2);
    Controls.Add(lb3);
  }
}//Form1
```

```txt:C#TimeSpanCalc_OldDayData.txt
2021/07/05 00:17:28
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494508/1802201d-7f93-4551-5601-09e2057cc115.png)

なんか秒単位で返信を催促するメンヘラみたいなメッセージになっちまったがまぁ、これで完成。

# 開いてない時間も計算が必要なゲーム

と、いえばたとえば放置ゲームとか。

アプリケーションを開いている間は時間経過で成長し、アプリケーションを開いていない間についても、上記の「前回開いた時間から何時間ぶりかを計算するプログラム」を利用し、経過時間を計算し、成長に反映する。

実際に作ってみた。
例によってコメントとかそのまま。
まぁ、自分用だし、、、、、、（言い訳）

```c#:C#放置ゲーム.cs
using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

class WinMain : Form
{
  DateTime todayData = DateTime.Now;  //現在の日時を取得。
  private bool addX, addY;
  string filePath = @"C#放置ゲーム_Date.txt";
  int counter;  //繰り返しをカウントする用の変数
  Label lb = new Label();

  //体の位置、大きさの情報　ここをいじくって動かす
  Rectangle rect;// = new Rectangle(50, 50, 50, 50);

  //イベントメソッド , timer.Tick に追加する。
  //フォーム内で縦横増減させ、画像を移動させる。
  private void Move_Tick(object sender, EventArgs e)
  {
    //カウンター、これの値によりいろいろ処理
    counter++;
    //移動速度処理 float扱えるんかな？わからん
    if(counter % 3 == 0)
    {
      rect.X += addX ? 1 : -2; //横方向移動
      rect.Y += addY ? 2 : -1; //縦方向移動
    }
    //成長速度調整。
    if(counter >= 100)
    {
      rect.Width += 1;  //幅を増やす
      rect.Height += 1; //高さを増やす
      //現在の大きさを保存
      File.WriteAllText(filePath, todayData.ToString() + "\n" +rect.Width.ToString());
      counter = 0;
    }

    //フォームの外枠超えようとすると、移動方向反転
    if(rect.X + rect.Width >= ClientSize.Width)addX = false;
    else if(rect.X <= 0)addX = true;

    if(rect.Y + rect.Height >= ClientSize.Height)addY = false;
    else if(rect.Y <= 0)addY = true;

    //現在の大きさを表示
    lb.Text = rect.Width.ToString();
    //再描画
    Invalidate();
  }

  //画像を描画する処理
  protected override void OnPaint(PaintEventArgs e)
  {
    //base.OnPaint(e); //なにこれ。なんであるの？
    Graphics g = e.Graphics;
    SolidBrush whiteBrush = new SolidBrush(Color.FromArgb(0xff, 0xff, 0xff));

    //左目の位置とか大きさとか決める。
    Rectangle eyeLeft = new Rectangle(
      rect.X + rect.Width / 2 - rect.Width / 5, 
      rect.Y + 15, 
      rect.Width / 7, 
      rect.Height / 7
    );

    //右目も
    Rectangle eyeRight = new Rectangle(
      rect.X + rect.Width / 2 + rect.Width / 5, 
      rect.Y + 15, 
      rect.Width / 7, 
      rect.Height / 7
    );

    //体を描画する実際の処理。
    g.FillEllipse(
      new SolidBrush(Color.FromArgb(0x00, 0xff, 0xff)),
      rect
    );

    //左目描画
    g.FillEllipse(
      whiteBrush,
      eyeLeft
    );

    //右目
    g.FillEllipse(
      whiteBrush,
      eyeRight
    );
  }

  //コンストラクタ
  WinMain()
  {
    //https://dobon.net/vb/dotnet/form/preventmaximize.html
    //フォームを最大化、最小化できないようにする
    //MaximizeBox = false;
    //MinimizeBox = false;

    //前回の記録を読み込み、開いてなかった時間を計算
    string[] txtArr = File.ReadAllLines(filePath);
    TimeSpan diffTime = todayData - DateTime.Parse(txtArr[0]);

    //上記で出た時間 = 大きさとして、差異をサイズに取り込む。
    int diffSize = (int)diffTime.TotalHours + int.Parse(txtArr[1]);
    rect = new Rectangle(10, 10, diffSize, diffSize);

    //大きさを表すラベルの位置を調整、コントロールに追加
    lb.Bounds = new Rectangle(10, 10, 200, 20);
    Controls.Add(lb);

    //ダブルバッファ処理
    //この塊はワンセットとしとけばたぶんOK
    SetStyle(
      ControlStyles.DoubleBuffer |
      ControlStyles.UserPaint |
      ControlStyles.AllPaintingInWmPaint, true
    );

    //突然出てくるタイマー、時間経過でイベント発生させる
    Timer timer = new Timer();
    timer.Interval = 10;
    timer.Tick += new EventHandler(Move_Tick);
    timer.Start();
  }

  //メイン関数 アプリケーションラン実行のみ
  static void Main()
  {
    WinMain win = new WinMain();
    win.Text = "放置ゲーム";
    Application.Run(win);
  }
}
```

```txt:C#放置ゲーム_Date.txt
2021/07/05 0:30:18
88
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494508/f673da02-bc76-49ca-577a-2129f336fa7b.png)

数分後。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494508/2da84868-c42f-44c9-947f-97ee95b1794e.png)
丸っこいなにかがしっかり大きく成長しました！
（時間調整がばがばなので超速度で成長しちゃう）

たぶんC#の基本的な機能ばかりなんだろうけど、なーんにもわからんことが多くて、勉強になったかな。
（これに打ち込んで休日がつぶれた、、、、。）