ムード値を計測するモバイルアプリケーションを作ってみた

## はじめに
初めましての人も、そうでない人も、こんにちは！

早速ですが、皆さんは長期休みの時はどう過ごしますか？ 彼氏・彼女と遊んだりとかしてますでしょうか？

最近付き合い始めた方などは、ムードがよくわからずに勢いで攻めすぎたばかりに引かれてしまい、別れてしまった経験はありますか？

しかし、なかなかムードなんて全然わからないし、難しすぎると思います。

そんな時、数字に表して評価値が高かったら攻めてもOKという、何かしらの指標があったら嬉しいですよね！ ですが、そんな指標そうそうあるわけが…あるんです！

アニメ「理系が恋に落ちたので証明してみた。」第6話にムード値を定義する話が展開されています！

今回はそのムード値を測るモバイルアプリを作って、ムードに困っている方の手助けができたらと思います！

## 今回使用する技術
・EXPO
・TypeScript

## ディレクトリ構成
```
rikekoi/
├── .expo/
├── assets/
├── node_modules/
├── .gitignore
├── app.json
├── App.tsx
├── babel.config.js
├── package.json
├── styles.ts
├── tsconfig.json
└── yarn.lock
```

## 環境構築
```
npm install -g expo-cli
expo init rikekoi
cd rikekoi
```

このコマンドをターミナルにコピペしてください！

## 計算方法
厳密に定義や公式を知りたい方は、原作の「理系が恋に落ちたので証明してみた。」第3巻もしくは、アニメ第6話を見てみてください！

そして、計算方法をプログラムにしなければなりませんが、めんどくさい（正直全くわからない）ため、インターネットから参考記事を探したところ、すでにプログラミングしているすごい方がいたので、その方を参考にしました！

https://qiita.com/mochihisa/items/3c72d71715487ac4fb5a

この記事を書いてくださりありがとうございます！

それでは準備も整ったため実際に作っていこうと思います！

## コーディング
おそらくですが先ほど環境構築をした際にapp.tsxがあると思うのでそれを使います！
```rikekoi/app.tsx
import React, { useState } from 'react';
import { View, Text, TextInput, Alert, Keyboard, TouchableWithoutFeedback, ScrollView, TouchableOpacity } from 'react-native';
import styles from './styles';

export default function App() {
  const [n, setN] = useState<string>('');
  const [ratings, setRatings] = useState<string[]>([]);
  const [x2, setX2] = useState<string>('');
  const [x3, setX3] = useState<string>('');
  const [x4, setX4] = useState<string>('');
  const [x5, setX5] = useState<string>('');
  const [M, setM] = useState<string | null>(null);

  const handleNChange = (text: string) => {
    setN(text);
    const nValue = text === '' ? 0 : parseInt(text);
    setRatings(new Array(nValue).fill(''));
  };

  const handleRatingChange = (index: number, text: string) => {
    const updatedRatings = [...ratings];
    updatedRatings[index] = text;
    setRatings(updatedRatings);
  };

  const validateRatings = (ratingValues: number[]): boolean => {
    for (let i = 0; i < ratingValues.length; i++) {
      if (ratingValues[i] < 0 || ratingValues[i] > 100) {
        Alert.alert('エラー', `${i + 1}人目の評価値は0から100の範囲内で入力してください`);
        return false;
      }
    }
    return true;
  };

  const calculateMoodValue = () => {
    const nValue = parseInt(n) || 0;
    const x2Value = parseFloat(x2) || 0;
    const x3Value = parseFloat(x3) || 0;
    const x4Value = parseFloat(x4) || 0;
    const x5Value = parseFloat(x5) || 0;

    if (nValue <= 0 || ratings.length !== nValue) {
      Alert.alert('エラー', '評価者の人数を正しく入力してください');
      return;
    }

    const ratingValues = ratings.map(r => parseFloat(r) || 0);
    
    if (!validateRatings(ratingValues)) {
      return;
    }
    //ムード値の計算
    const P1 = ratingValues.reduce((a, b) => a + b, 0) / 5 / nValue;

    let P2: number;
    if (x2Value === 0) {
      P2 = 20;
    } else {
      P2 = -10000 / x2Value;
    }

    const P3 = (104 - 2 * (x3Value / 20 + 20 / x3Value)) / 5;

    let P4: number;
    if (x4Value >= 0 && x4Value < 20) {
      P4 = 30 - 1000 / 100;
    } else if (x4Value >= 20 && x4Value < 70) {
      P4 = 30 - 1000 / (100 - 2 * (x4Value - 20));
    } else {
      setM('-∞');
      return;
    }

    let P5: number;
    if (x5Value > 0 && x5Value < 30) {
      P5 = 100 * x5Value / 30 / 5;
    } else if (x5Value >= 30 && x5Value <= 60) {
      P5 = 100 / 5;
    } else {
      P5 = (100 - 5 * (x5Value - 60)) / 5;
    }
    //合計
    const finalM = P1 + P2 + P3 + P4 + P5;
    setM(finalM.toFixed(2) + ' md');
  };

  const renderInput = (label: string, value: string, onChangeText: (text: string) => void) => (
    <View style={styles.inputContainer}>
      <Text style={styles.label}>{label}</Text>
      <TextInput
        style={styles.input}
        keyboardType="numeric"
        value={value}
        onChangeText={onChangeText}
      />
    </View>
  );

  return (
    <TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
      <ScrollView contentContainerStyle={styles.container}>
        <View style={styles.innerContainer}>
          <Text style={styles.header}>ムード値診断ツール</Text>

          {renderInput('評価者の人数', n, handleNChange)}

          {ratings.map((rating, i) => (
            <View key={i} style={styles.inputContainer}>
              <Text style={styles.label}>{i + 1}人目の評価:</Text>
              <TextInput
                style={styles.input}
                keyboardType="numeric"
                value={rating}
                onChangeText={text => handleRatingChange(i, text)}
              />
            </View>
          ))}

          {renderInput('こちらに注目する人間の数', x2, setX2)}
          {renderInput('その場の照度(lux)', x3, setX3)}
          {renderInput('その場の騒音値(db)', x4, setX4)}
          {renderInput('沈黙見つめ合い秒数', x5, setX5)}

          <View style={styles.buttonWrapper}>
            <TouchableOpacity style={styles.button} onPress={calculateMoodValue}>
              <Text style={styles.buttonText}>ムード値を計算する</Text>
            </TouchableOpacity>
          </View>

          {M !== null && (
            <View style={styles.resultContainer}>
              <Text style={styles.resultText}>ムード値: {M}</Text>
            </View>
          )}
        </View>
      </ScrollView>
    </TouchableWithoutFeedback>
  );
}
```

そしてデザインをより良くします！
rikekoiフォルダの中にstyle.tsを作成して以下のコードをコピペしてください
```rikekoi/style.ts
import { StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
    backgroundColor: '#F0F4F8',
    padding: 20,
  },
  innerContainer: {
    backgroundColor: 'white',
    borderRadius: 20,
    padding: 20,
    shadowColor: "#000",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#4A5568',
    textAlign: 'center',
    marginBottom: 20,
  },
  inputContainer: {
    marginBottom: 15,
  },
  label: {
    fontSize: 16,
    color: '#4A5568',
    marginBottom: 5,
  },
  input: {
    borderWidth: 1,
    borderColor: '#CBD5E0',
    borderRadius: 8,
    padding: 10,
    fontSize: 16,
    color: '#2D3748',
  },
  buttonWrapper: {
    marginTop: 20,
  },
  button: {
    backgroundColor: '#4299E1',
    padding: 15,
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonText: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold',
  },
  resultContainer: {
    marginTop: 20,
    padding: 15,
    backgroundColor: '#EBF8FF',
    borderRadius: 8,
  },
  resultText: {
    fontSize: 18,
    color: '#2B6CB0',
    textAlign: 'center',
  },
});

export default styles;
```

## 実行してみる
以下のコマンドをターミナルに入力してください！
```
npm start
```
![IMG_4042.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3631203/325d98d3-01ff-b4b8-411c-9cf34c6b1284.jpeg)

無事に実行されました！

## ムード値を測ってみる！

評価者の人数：2
1人目の評価：100
2人目の評価：20
こちらに注目する人間の数：0
その場の照度：38
その場の雑音値：50
沈黙見つめ合い秒数：10

この条件でムード値を測ってみたいと思います！

![IMG_4043.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3631203/31103881-d7fc-9062-c891-ac62fe672871.jpeg)

結果は63.50md見たいです！

参考記事と大体似たような値になっているため正解していそうです！

## 唐突に始めるQ&Aコーナー
Q: 評価者は誰でどのくらい必要ですか？
A: 適当でいいんじゃないんですか？ なるべく第三者視点で見てもらえる方がいいんじゃないんでしょうか？

Q: 照度や雑音値はどうやって測ればいいですか？
A: ECサイトなどで計測器を買ってください！ 適当にChatGPTに相場を聞いてみると、どうやら照度計は4,000円〜、雑音計は10,000円〜で買えるっぽいですよ。ChatGPTの回答なので、信用性は低いですが。

Q: ムード値をいちいち測っていた方が引かれませんか？
A: はい！ なんで、感覚的にムードを察した方が成功率が高いです！

Q: このアプリを使った結果ムード値が高いのを確認したあと告白しましたが振られました。責任とってください！
A: 知りません！一切の責任を負いかねます。

## おわりに
いかがだったでしょうか？
先にムード値についてプログラミングしてくれた方がいて、本当に助かりました。

皆さんもムードには気をつけて、デートを楽しみましょう！

ちなみに、自分は彼氏も彼女も全くいません！ いつかはできる予定ですが…。

ということで、今回の記事はいかがだったでしょうか？ また、どこかの記事でお会いしましょう！

## GithubURL
https://github.com/FrohleinYoshie/mood_checker.git