diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4020bcb..c94b934 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,4 +1,5 @@ module.exports = { + root: true, env: { browser: true, es2020: true }, extends: [ 'eslint:recommended', @@ -10,5 +11,6 @@ module.exports = { plugins: ['react-refresh'], rules: { 'react-refresh/only-export-components': 'warn', + 'react-hooks/exhaustive-deps': 'off', }, -} +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf64146..96b3af9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,39 +8,38 @@ on: jobs: eslint: - name: Run eslint scanning - runs-on: ubuntu-latest + name: Run eslint scanning + runs-on: ubuntu-latest steps: - - name: Checkout code #コードを持ってくる + - name: Checkout code #コードを持ってくる uses: actions/checkout@v3 - - name: Install ESLint #ESlintをインストールする + - name: Install ESLint #ESlintをインストールする run: | npm install eslint@8.47.0 npm install @microsoft/eslint-formatter-sarif@2.1.7 - - name: Run ESLint #ESLintを実行する - run: - npx eslint . --ext .js,.jsx,.ts,.tsx --format @microsoft/eslint-formatter-sarif + - name: Run ESLint #ESLintを実行する + run: npx eslint ./src --ext .js,.jsx,.ts,.tsx --format @microsoft/eslint-formatter-sarif --output-file eslint-results.sarif continue-on-error: true - - name: Upload analysis results to GitHub #githubに解析結果をアップロード + - name: Upload analysis results to GitHub #githubに解析結果をアップロード uses: github/codeql-action/upload-sarif@v2 with: sarif_file: eslint-results.sarif wait-for-processing: true rome: - name: Run rome scanning + name: Run rome scanning runs-on: ubuntu-latest steps: - - name: Checkout code #コードを持ってくる + - name: Checkout code #コードを持ってくる uses: actions/checkout@v3 - - name: Install Rome #romeをインストールする + - name: Install Rome #romeをインストールする run: | npm install rome@12.0.0 - - name: Run Rome #romeを実行 - run: npx rome check ./src \ No newline at end of file + - name: Run Rome #romeを実行 + run: npx rome check ./src diff --git a/doc/battle.md b/doc/battle.md new file mode 100644 index 0000000..aba7d56 --- /dev/null +++ b/doc/battle.md @@ -0,0 +1,5 @@ +バトルする +勝つ +ツイートできるようにする +負ける +励ましの言葉をヤギにかける \ No newline at end of file diff --git a/functions/lib/index.js b/functions/lib/index.js index 715d6a2..7481a6c 100644 --- a/functions/lib/index.js +++ b/functions/lib/index.js @@ -96,4 +96,48 @@ ${reqText}というワードがどのような場面で利用されるかを"Joy return; } }); +exports.gptwrapper = (0, https_1.onRequest)({ cors: [/firebase\.com$/, 'flutter.com'] }, async (req, res) => { + var _a; + /** + * 本番環境ではコメントアウトする + */ + res.setHeader('Access-Control-Allow-Origin', '*'); + //reqText + const reqText = req.query.text; + if (!reqText) { + res.status(400).send('NoText'); + return; + } + //context + const requestContext = reqText; + try { + //config + if (!apiKey) { + res.status(500).send('NoApiKey'); + return; + } + const config = new openai_1.Configuration({ + apiKey: apiKey, + }); + //params + const params = { + model: 'gpt-3.5-turbo', + messages: [{ role: 'user', content: requestContext }], + }; + //OpenAI + const aiInstance = new openai_1.OpenAIApi(config); + const openAiRes = await aiInstance.createChatCompletion(params); + const aiResText = (_a = openAiRes.data.choices[0].message) === null || _a === void 0 ? void 0 : _a.content; + if (!aiResText) { + res.status(500).send('GPTError'); + return; + } + res.status(200).send(openAiRes.data); + return; + } + catch (e) { + res.status(500).send(e); + return; + } +}); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/functions/lib/index.js.map b/functions/lib/index.js.map index 36c050e..97acc4d 100644 --- a/functions/lib/index.js.map +++ b/functions/lib/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/src/index.ts b/functions/src/index.ts index 10361b8..2735cb5 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -112,3 +112,53 @@ ${reqText}というワードがどのような場面で利用されるかを"Joy return; } }); + +exports.gptwrapper = onRequest({ cors: [/firebase\.com$/, 'flutter.com'] }, async (req, res) => { + /** + * 本番環境ではコメントアウトする + */ + res.setHeader('Access-Control-Allow-Origin', '*'); + + //reqText + const reqText = req.query.text; + if (!reqText) { + res.status(400).send('NoText'); + return; + } + + //context + const requestContext = reqText as string; + + try { + //config + if (!apiKey) { + res.status(500).send('NoApiKey'); + return; + } + const config = new Configuration({ + apiKey: apiKey, + }); + + //params + const params: CreateChatCompletionRequest = { + model: 'gpt-3.5-turbo', + messages: [{ role: 'user', content: requestContext }], + }; + + //OpenAI + const aiInstance = new OpenAIApi(config); + const openAiRes = await aiInstance.createChatCompletion(params); + const aiResText = openAiRes.data.choices[0].message?.content; + + if (!aiResText) { + res.status(500).send('GPTError'); + return; + } + + res.status(200).send(openAiRes.data); + return; + } catch (e) { + res.status(500).send(e); + return; + } +}); diff --git a/package-lock.json b/package-lock.json index c0e76b0..7dceef5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.6", + "@swc/core": "^1.3.76", "axios": "^1.4.0", "dayjs": "^1.11.8", "firebase": "^9.23.0", @@ -1509,10 +1510,9 @@ ] }, "node_modules/@swc/core": { - "version": "1.3.66", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.66.tgz", - "integrity": "sha512-Hpf91kH5ly7fHkWnApwryTQryT+TO4kMMPH3WyciUSQOWLE3UuQz1PtETHQQk7PZ/b1QF0qQurJrgfBr5bSKUA==", - "dev": true, + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.76.tgz", + "integrity": "sha512-aYYTA2aVYkwJAZepQXtPnkUthhOfn8qd6rsh+lrJxonFrjmpI7RHt2tMDVTXP6XDX7fvnvrVtT1bwZfmBFPh0Q==", "hasInstallScript": true, "engines": { "node": ">=10" @@ -1522,16 +1522,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.66", - "@swc/core-darwin-x64": "1.3.66", - "@swc/core-linux-arm-gnueabihf": "1.3.66", - "@swc/core-linux-arm64-gnu": "1.3.66", - "@swc/core-linux-arm64-musl": "1.3.66", - "@swc/core-linux-x64-gnu": "1.3.66", - "@swc/core-linux-x64-musl": "1.3.66", - "@swc/core-win32-arm64-msvc": "1.3.66", - "@swc/core-win32-ia32-msvc": "1.3.66", - "@swc/core-win32-x64-msvc": "1.3.66" + "@swc/core-darwin-arm64": "1.3.76", + "@swc/core-darwin-x64": "1.3.76", + "@swc/core-linux-arm-gnueabihf": "1.3.76", + "@swc/core-linux-arm64-gnu": "1.3.76", + "@swc/core-linux-arm64-musl": "1.3.76", + "@swc/core-linux-x64-gnu": "1.3.76", + "@swc/core-linux-x64-musl": "1.3.76", + "@swc/core-win32-arm64-msvc": "1.3.76", + "@swc/core-win32-ia32-msvc": "1.3.76", + "@swc/core-win32-x64-msvc": "1.3.76" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -1543,13 +1543,12 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.66", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.66.tgz", - "integrity": "sha512-UijJsvuLy73vxeVYEy7urIHksXS+3BdvJ9s9AY+bRMSQW483NO7RLp8g4FdTyJbRaN0BH15SQnY0dcjQBkVuHw==", + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.76.tgz", + "integrity": "sha512-ovviEhZ/1E81Z9OGrO0ivLWk4VCa3I3ZzM+cd3gugglRRwVwtlIaoIYqY5S3KiCAupDd1+UCl5X7Vbio7a/V8g==", "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -1558,6 +1557,141 @@ "node": ">=10" } }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.76.tgz", + "integrity": "sha512-tcySTDqs0SHCebtW35sCdcLWsmTEo7bEwx0gNL/spetqVT9fpFi6qU8qcnt7i2KaZHbeNl9g1aadu+Yrni+GzA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.76.tgz", + "integrity": "sha512-apgzpGWy1AwoMF4urAAASsAjE7rEzZFIF+p6utuxhS7cNHzE0AyEVDYJbo+pzBdlZ8orBdzzsHtFwoEgKOjebA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.76.tgz", + "integrity": "sha512-c3c0zz6S0eludqidDpuqbadE0WT3OZczyQxe9Vw8lFFXES85mvNGtwYzyGK2o7TICpsuHrndwDIoYpmpWk879g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.76.tgz", + "integrity": "sha512-Is3bpq7F2qtlnkzEeOD6HIZJPpOmu3q6c82lKww90Q0NnrlSluVMozTHJgwVoFZyizH7uLnk0LuNcEAWLnmJIw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.76.tgz", + "integrity": "sha512-iwCeRzd9oSvUzqt7nU6p/ztceAWfnO9XVxBn502R5gs6QCBbE1HCKrWHDO77aKPK7ss+0NcIGHvXTd9L8/wRzw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.76.tgz", + "integrity": "sha512-a671g4tW8kyFeuICsgq4uB9ukQfiIyXJT4V6YSnmqhCTz5mazWuDxZ5wKnx/1g5nXTl+U5cWH2TZaCJatp4GKA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.76.tgz", + "integrity": "sha512-+swEFtjdMezS0vKUhJC3psdSDtOJGY5pEOt4e8XOPvn7aQpKQ9LfF49XVtIwDSk5SGuWtVoLFzkSY3reWUJCyg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.76.tgz", + "integrity": "sha512-5CqwAykpGBJ3PqGLOlWGLGIPpBAG1IwWVDUfro3hhjQ7XJxV5Z1aQf5V5OJ90HJVtrEAVx2xx59UV/Dh081LOg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.76", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.76.tgz", + "integrity": "sha512-CiMpWLLlR3Cew9067E7XxaLBwYYJ90r9EhGSO6V1pvYSWj7ET/Ppmtj1ZhzPJMqRXAP6xflfl5R5o4ee1m4WLA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", diff --git a/package.json b/package.json index 63cf384..ac0bb88 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.6", + "@swc/core": "^1.3.76", "axios": "^1.4.0", "dayjs": "^1.11.8", "firebase": "^9.23.0", diff --git a/src/App.tsx b/src/App.tsx index 4ecf23d..23f37a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,13 @@ +import { CssBaseline } from '@mui/material'; import { Router } from './routes/Routers'; const App: React.FC = () => { - return ; + return ( + <> + + + + ); }; export default App; diff --git a/src/api/addWordEmotions.ts b/src/api/addWordEmotions.ts new file mode 100644 index 0000000..3d01470 --- /dev/null +++ b/src/api/addWordEmotions.ts @@ -0,0 +1,28 @@ +import { addDoc, collection } from 'firebase/firestore'; +import { EmotionDataType } from '../types/EmotionDataType'; +import { db } from '../lib/firebase'; +import { COLLECTIONNAME, EmotionDataDBType } from '../lib/firestore'; + +type Props = { + text: string; + emotionData: EmotionDataType; +}; + +/** + * 文字列に対応する感情をfireStoreに追加する関数 + * + * @param text 文字列 + * @param emotionData 対応するEmotionData + * + * @see + * https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja + */ +export const addWordEmotions = async ({ text, emotionData }: Props) => { + const collectionRef = collection(db, COLLECTIONNAME); + const docData: EmotionDataDBType = { + documentWord: text, + emotionData: emotionData, + }; + + await addDoc(collectionRef, docData); +}; diff --git a/src/api/getEmotionApi.ts b/src/api/getEmotionApi.ts new file mode 100644 index 0000000..0e2c960 --- /dev/null +++ b/src/api/getEmotionApi.ts @@ -0,0 +1,69 @@ +import axios from 'axios'; +import { EmotionDataType } from '../types/EmotionDataType'; +import { addWordEmotions } from './addWordEmotions'; +import { getWordEmotions } from './getWordEmotions'; + +type emotionDataType = EmotionDataType; +type addWordObjectType = { + text: string; + emotionData: EmotionDataType; +}; + +export const getEmotionApi = async (text: string, emotionData: emotionDataType) => { + let newEmotionData = Object.assign({}, emotionData); + let maxEmotion = ''; + let maxScore = 0; + + const fetchAPI = async () => { + const res = await axios.get(`https://callgpt-f6bkalktuq-uc.a.run.app?text=${text}`); + const fetchEmotionData = res.data as emotionDataType; + + newEmotionData = { + Joy: Number(fetchEmotionData.Joy), + Anger: Number(fetchEmotionData.Anger), + Sorrow: Number(fetchEmotionData.Sorrow), + Enjoyable: Number(fetchEmotionData.Enjoyable), + emoId: 0, + }; + }; + + newEmotionData = await getWordEmotions(text); + if (newEmotionData.emoId === undefined) { + await fetchAPI(); + for (const emotion in newEmotionData) { + if (newEmotionData[emotion as keyof emotionDataType] > maxScore) { + maxEmotion = emotion; + maxScore = newEmotionData[emotion as keyof emotionDataType]; + } + } + switch (maxEmotion) { + case 'Joy': + newEmotionData.emoId = 1; + break; + case 'Anger': + newEmotionData.emoId = 2; + break; + case 'Sorrow': + newEmotionData.emoId = 3; + break; + case 'Enjoyable': + newEmotionData.emoId = 4; + break; + } + const addWord: addWordObjectType = { + text: text, + emotionData: newEmotionData, + }; + addWordEmotions(addWord); + } + + const updateEmotionData = { + Joy: (emotionData.Joy += newEmotionData.Joy), + Anger: (emotionData.Anger += newEmotionData.Anger), + Sorrow: (emotionData.Sorrow += newEmotionData.Sorrow), + Enjoyable: (emotionData.Enjoyable += newEmotionData.Enjoyable), + emoId: newEmotionData.emoId, + }; + + return updateEmotionData; +}; diff --git a/src/api/getWordEmotions.ts b/src/api/getWordEmotions.ts new file mode 100644 index 0000000..49066dc --- /dev/null +++ b/src/api/getWordEmotions.ts @@ -0,0 +1,28 @@ +import { collection, getDocs, query, where } from 'firebase/firestore'; +import { EmotionDataType } from '../types/EmotionDataType'; +import { db } from '../lib/firebase'; +import { COLLECTIONNAME, FIELDNAME } from '../lib/firestore'; + +/** + * 文字列・対応する感情をfireStoreから取得する関数 + * + * @param text 文字列 + * + * @returns emotionData 対応するEmotionData | 文字列が存在しない場合には空のEmotionData + * + * @see + * https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja + */ +export const getWordEmotions = async (text: string): Promise => { + const collectionRef = collection(db, COLLECTIONNAME); + const queryResult = query(collectionRef, where(FIELDNAME, '==', text)); + + const docList = await getDocs(queryResult); + + /** + * 文字列が存在しない場合には空のEmotionDataを返す + */ + if (docList.size === 0) return {} as EmotionDataType; + + return docList.docs[0].data().emotionData as EmotionDataType; +}; diff --git a/src/assets/mori.png b/src/assets/mori.png new file mode 100644 index 0000000..c7eb9ce Binary files /dev/null and b/src/assets/mori.png differ diff --git a/src/assets/night.png b/src/assets/night.png new file mode 100644 index 0000000..3d2cba5 Binary files /dev/null and b/src/assets/night.png differ diff --git a/src/assets/noon.png b/src/assets/noon.png new file mode 100644 index 0000000..4d5a159 Binary files /dev/null and b/src/assets/noon.png differ diff --git a/src/assets/sougen.png b/src/assets/sougen.png new file mode 100644 index 0000000..e1e8c6a Binary files /dev/null and b/src/assets/sougen.png differ diff --git a/src/assets/umi.png b/src/assets/umi.png new file mode 100644 index 0000000..5f3ae61 Binary files /dev/null and b/src/assets/umi.png differ diff --git a/src/components/Branch.tsx b/src/components/Branch.tsx index e88808b..676acce 100644 --- a/src/components/Branch.tsx +++ b/src/components/Branch.tsx @@ -1,4 +1,3 @@ -import { useState, FC } from 'react'; import { EmotionDataType } from '../types/EmotionDataType'; type Props = { diff --git a/src/components/Eat.tsx b/src/components/Eat.tsx index e26475d..ecef396 100644 --- a/src/components/Eat.tsx +++ b/src/components/Eat.tsx @@ -17,15 +17,25 @@ type Props = { eat: boolean; showImage: boolean; randomNum: number; + containerSize: { + width: number; + height: number; + }; }; -const Eat: FC = (props) => { - const emoId = props.emotionData.emoId; - const random = props.randomNum; +const Eat: FC = ({ emotionData, eat, showImage, randomNum, containerSize }) => { + const emoId = emotionData.emoId; + const random = randomNum; + const position = { + x: (containerSize.width / 5) * 3, + y: (containerSize.height / 12) * 11, + }; + const grassTop = position.y + 68; + const grassLeft = position.x - 10; return (
- {props.eat + {eat ? (() => { switch (emoId) { //嬉 @@ -39,7 +49,10 @@ const Eat: FC = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - zIndex: 1, + zIndex: 2, + position: 'absolute', + left: `${grassLeft}px`, + top: `${grassTop}px`, }} /> ) : ( @@ -51,7 +64,10 @@ const Eat: FC = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - zIndex: 1, + zIndex: 2, + position: 'absolute', + left: `${grassLeft}px`, + top: `${grassTop}px`, }} /> ); @@ -66,7 +82,10 @@ const Eat: FC = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - zIndex: 1, + zIndex: 2, + position: 'absolute', + left: `${grassLeft}px`, + top: `${grassTop}px`, }} /> ) : ( @@ -78,7 +97,10 @@ const Eat: FC = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - zIndex: 1, + zIndex: 2, + position: 'absolute', + left: `${grassLeft}px`, + top: `${grassTop}px`, }} /> ); @@ -93,7 +115,10 @@ const Eat: FC = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - zIndex: 1, + zIndex: 2, + position: 'absolute', + left: `${grassLeft}px`, + top: `${grassTop}px`, }} /> ) : ( @@ -105,7 +130,10 @@ const Eat: FC = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - zIndex: 1, + zIndex: 2, + position: 'absolute', + left: `${grassLeft}px`, + top: `${grassTop}px`, }} /> ); @@ -120,7 +148,10 @@ const Eat: FC = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - zIndex: 1, + zIndex: 2, + position: 'absolute', + left: `${grassLeft}px`, + top: `${grassTop}px`, }} /> ) : ( @@ -132,7 +163,10 @@ const Eat: FC = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - zIndex: 1, + zIndex: 2, + position: 'absolute', + left: `${grassLeft}px`, + top: `${grassTop}px`, }} /> ); @@ -141,8 +175,8 @@ const Eat: FC = (props) => { } })() : null} - {props.eat ? ( - props.showImage ? ( + {eat ? ( + showImage ? ( = (props) => { backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: 'center', - marginTop: '-130px', - marginLeft: '25px', - zIndex: -1, + left: `${position.x}px`, + top: `${position.y}px`, + zIndex: 1, }} /> ) : ( = (props) => { - const isDispPop = props.pop && props.evoPop; +const EvolutionPopup: FC = ({ eatCount, pop, evolution, evoPop }) => { + const isDispPop = pop && evoPop; return (
{isDispPop ? (
- {props.eatCount > EATLIMIT ? ( + {eatCount > EATLIMIT ? ( = (props) => { + {isDisableTextField ? ( + + + + ) : ( diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx index 0fafa4b..8b1e2fd 100644 --- a/src/components/NavBar.tsx +++ b/src/components/NavBar.tsx @@ -1,21 +1,24 @@ import { BottomNavigation, BottomNavigationAction, Box } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; import { FC, useRef, useState } from 'react'; import HelpIcon from '@mui/icons-material/Help'; import SearchIcon from '@mui/icons-material/Search'; import MusicNoteIcon from '@mui/icons-material/MusicNote'; import MusicOffIcon from '@mui/icons-material/MusicOff'; +import CoronavirusIcon from '@mui/icons-material/Coronavirus'; import bgm from '../Audio/Bgm.mp3'; type Props = { handleTutorialChange: React.MouseEventHandler | undefined; + handleBattleChange: React.MouseEventHandler | undefined; }; -export const NavBar: FC = ({ handleTutorialChange }) => { +export const NavBar: FC = ({ handleTutorialChange, handleBattleChange }) => { const audioRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); + const navigate = useNavigate(); const toggleBGM = () => { - console.log('toggleBGM'); if (audioRef.current) { if (isPlaying) { audioRef.current.pause(); @@ -41,6 +44,14 @@ export const NavBar: FC = ({ handleTutorialChange }) => { onClick={handleTutorialChange} sx={{ color: 'white' }} /> + } + onClick={() => { + navigate('/Battle'); + }} + sx={{ color: 'white' }} + /> } sx={{ color: 'white' }} /> void; + handleBattleModalOpen: () => void; +}; + +export const NavBarCon: FC = ({ handleTutorialModalOpen, handleBattleModalOpen }) => { + return ( + + + + ); +}; diff --git a/src/components/NormalWalk.tsx b/src/components/NormalWalk.tsx index 9607d77..624a8ad 100644 --- a/src/components/NormalWalk.tsx +++ b/src/components/NormalWalk.tsx @@ -1,47 +1,31 @@ -import React, { useRef, useEffect, useState } from 'react'; +import { useRef, useEffect, useState, FC } from 'react'; import yagi_left from '../assets/yagi_left.png'; import yagi_right from '../assets/yagi_right.png'; import yagi_efect from '../Audio/やぎの鳴き声.mp3'; import { Button } from '@mui/material'; -const NormalWalk: React.FC = () => { - const walkerRef = useRef(null); - const [position, setPosition] = useState({ x: 120, y: -50 }); - const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); - useEffect(() => { - const walkerElement = walkerRef.current; - if (!walkerElement) return; - - const containerElement = walkerElement.parentElement; - if (!containerElement) return; - - const updateContainerSize = () => { - const containerWidth = containerElement.clientWidth; - const containerHeight = containerElement.clientHeight; - setContainerSize({ width: containerWidth, height: containerHeight }); - }; - - updateContainerSize(); // 初回描画時にコンテナサイズを更新 - - const resizeObserver = new ResizeObserver(updateContainerSize); - resizeObserver.observe(containerElement); - - return () => { - resizeObserver.disconnect(); - }; - }, []); +type Props = { + containerSize: { + width: number; + height: number; + }; +}; +const NormalWalk: FC = ({ containerSize }) => { + const walkerRef = useRef(null); + const [position, setPosition] = useState({ + x: containerSize.width / 3, + y: (containerSize.height / 10) * 5, + }); useEffect(() => { - if (!walkerRef.current) return; // walkerRef.currentがnullの場合、処理を終了 - - const startX = containerSize.width / 2 - 150; // 開始位置のx座標 - const startY = containerSize.height / 2 - 150; // 開始位置のy座標 - const endX = containerSize.width / 2 + 100; // 終了位置のx座標 - const endY = containerSize.height / 2 + 30; // 終了位置のy座標 + const minX = -50; // 開始位置のx座標 + const minY = (containerSize.height / 10) * 4; // 開始位置のy座標 + const maxX = (containerSize.width / 10) * 5; // 終了位置のx座標 + const maxY = (containerSize.height / 10) * 6; // 終了位置のy座標 const updatePosition = () => { - const newX = startX + Math.random() * (endX - startX); - const newY = startY + Math.random() * (endY - startY); + const newX = Math.floor(Math.random() * (maxX - minX + 1)) + minX; + const newY = Math.floor(Math.random() * (maxY - minY + 1)) + minY; setPosition({ x: newX, y: newY }); }; @@ -53,7 +37,7 @@ const NormalWalk: React.FC = () => { }; }, [containerSize]); - const backgroundImage = position.x > containerSize.width / 2 ? yagi_right : yagi_left; + const backgroundImage = position.x > containerSize.width / 3 ? yagi_right : yagi_left; const [play, isPlaying] = useState(true); const yagiAudio = () => { diff --git a/src/components/PageContainer.tsx b/src/components/PageContainer.tsx new file mode 100644 index 0000000..2ed1cb7 --- /dev/null +++ b/src/components/PageContainer.tsx @@ -0,0 +1,51 @@ +import { useRef, useLayoutEffect, FC, useCallback } from 'react'; +import { Container } from '@mui/material'; + +type Props = { + updatePageSize: ( + newContainerSize: React.SetStateAction<{ + width: number; + height: number; + }> + ) => void; +}; + +export const PageContainer: FC = ({ updatePageSize }) => { + const walkerRef = useRef(null); + + const updateContainerSize = useCallback(() => { + const walkerElement = walkerRef.current; + if (!walkerElement) return; + + const containerElement = walkerElement.parentElement; + if (!containerElement) return; + + const containerWidth = containerElement.clientWidth; + const containerHeight = containerElement.clientHeight; + updatePageSize({ width: containerWidth, height: containerHeight }); + }, []); + + useLayoutEffect(() => { + updateContainerSize(); // 初回描画時にコンテナサイズを更新 + + const walkerElement = walkerRef.current; + if (!walkerElement) return; + + const containerElement = walkerElement.parentElement; + if (!containerElement) return; + + const resizeObserver = new ResizeObserver(updateContainerSize); + resizeObserver.observe(containerElement); + + return () => { + resizeObserver.disconnect(); + }; + }, [updateContainerSize]); + + return ( + + ); +}; diff --git a/src/components/Pulse.tsx b/src/components/Pulse.tsx index dc10e69..9ad4792 100644 --- a/src/components/Pulse.tsx +++ b/src/components/Pulse.tsx @@ -1,6 +1,5 @@ -import React, { useState, useEffect } from 'react'; -import { Box, Button, Typography } from '@mui/material'; -import { FC } from 'react'; +import { useState, useEffect, FC } from 'react'; +import { Box } from '@mui/material'; import yagi_left from '../assets/yagi_left.png'; import yagi_ikari from '../assets/yagi_iakri.png'; import yagi_tanosii from '../assets/yagi_tanosii.png'; @@ -10,11 +9,18 @@ import yagi_yorokobi from '../assets/yagi_yorokobi.png'; type Props = { typeId: number; walkEvo: () => void; + containerSize: { + width: number; + height: number; + }; }; -const Pulse: FC = ({ typeId, walkEvo }) => { +const Pulse: FC = ({ typeId, walkEvo, containerSize }) => { const [isDisplayLeft, setIsDisplayLeft] = useState(true); - + const position = { + x: (containerSize.width / 5) * 3, + y: (containerSize.height / 12) * 11, + }; useEffect(() => { //let intervalId: NodeJS.Timeout; @@ -34,7 +40,7 @@ const Pulse: FC = ({ typeId, walkEvo }) => { return () => { clearInterval(intervalId); }; - }, []); + }, [walkEvo]); const getImagePath = (typeId: number) => { switch (typeId) { @@ -62,7 +68,8 @@ const Pulse: FC = ({ typeId, walkEvo }) => { height: '130px', backgroundImage: `url(${isDisplayLeft ? yagi_left : backgroundImage})`, backgroundSize: 'cover', - left: '240px', + left: `${position.x}px`, + top: `${position.y}px`, }} />
diff --git a/src/components/Tutorial.tsx b/src/components/Tutorial.tsx index e613c22..0292e68 100644 --- a/src/components/Tutorial.tsx +++ b/src/components/Tutorial.tsx @@ -3,45 +3,30 @@ import Backdrop from '@mui/material/Backdrop'; import Modal from '@mui/material/Modal'; import Fade from '@mui/material/Fade'; import Typography from '@mui/material/Typography'; -import backgroundImage from '../assets/tutorial.png'; import { Button, Stack } from '@mui/material'; +import { modalStyle } from '../styles/modalStyle'; -const style = { - position: 'absolute' as const, - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: 300, - color: '#000', - - backgroundImage: `url(${backgroundImage})`, - backgroundSize: 'cover', - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center', - border: '1.5px solid #FFF', - borderRadius: '10px', - boxShadow: 24, - p: 4, - outline: 'none', -}; - -const labelList = ['あそびかた', 'ことばをたべさせて', 'やぎをそだてよう!']; +const labelList = new Map([ + [0, 'ことばをたべさせて'], + [1, 'ヤギをそだてよう!'], + [2, 'ことばによって '], + [3, 'ヤギのようすがかわるよ'], +]); type Props = { open: boolean; - openclick: React.MouseEventHandler | undefined; - closeclick: React.MouseEventHandler | undefined; + closeClick: React.MouseEventHandler | undefined; }; -const Tutorial: FC = ({ open, openclick, closeclick }) => { +const Tutorial: FC = ({ open, closeClick }) => { return (
- {/* + {/* */} = ({ open, openclick, closeclick }) => { }} > - + = ({ open, openclick, closeclick }) => { あそびかた - {labelList.map((label) => ( - - {label} + {[...labelList].map(([key]) => ( + + {labelList.get(key)} ))} - - ことばをたべさせて -
- やぎをそだてよう! -
- - ことばによって
- ヤギのようすがかわるよ -
- diff --git a/src/components/battle/Battle.tsx b/src/components/battle/Battle.tsx new file mode 100644 index 0000000..e67ff4e --- /dev/null +++ b/src/components/battle/Battle.tsx @@ -0,0 +1,96 @@ +import { FC, useState, useEffect } from 'react'; +import Backdrop from '@mui/material/Backdrop'; +import Modal from '@mui/material/Modal'; +import Fade from '@mui/material/Fade'; +import Typography from '@mui/material/Typography'; +import { Button, Stack } from '@mui/material'; +import { EATLIMIT } from '../../const/eatLimit'; +import { modalStyle } from '../../styles/modalStyle'; + +type Props = { + eatCount: number; + monster: number; + open: boolean; + closeClick: React.MouseEventHandler | undefined; +}; + +const Battle: FC = ({ open, monster, closeClick, eatCount }) => { + const [monsterlabel1, setmonsterlabel1] = useState(''); + const [monsterlabel2, setmonsterlabel2] = useState(''); + + const labelList = [ + 'このモンスターは', + monsterlabel1, + 'よわいようだ!', + 'ことばをたべさせて', + monsterlabel2, + 'やぎをそだてよう', + ]; + + useEffect(() => { + switch (monster) { + case 1: + setmonsterlabel1('よろこびのかんじょうに'); + setmonsterlabel2('やぎをよろこびのかんじょうに'); + break; + case 2: + setmonsterlabel1('いかりのかんじょうに'); + setmonsterlabel2('やぎをいかりのかんじょうに'); + break; + case 3: + setmonsterlabel1('かなしいのかんじょうに'); + setmonsterlabel2('やぎをかなしいのかんじょうに'); + break; + case 4: + setmonsterlabel1('たのしいのかんじょうに'); + setmonsterlabel2('やぎをたのしいかんじょうに'); + break; + } + }, []); + + return ( +
+ {eatCount > EATLIMIT ? null : ( + + + + + モンスターがあらわれた!! + + + {labelList.map((label) => ( + + {label} + + ))} + + + + + + + + )} +
+ ); +}; +export default Battle; diff --git a/src/components/battle/BattleAction.tsx b/src/components/battle/BattleAction.tsx new file mode 100644 index 0000000..429a979 --- /dev/null +++ b/src/components/battle/BattleAction.tsx @@ -0,0 +1,109 @@ +import { FC, useState, useEffect } from 'react'; +import Backdrop from '@mui/material/Backdrop'; +import Modal from '@mui/material/Modal'; +import Fade from '@mui/material/Fade'; +import { Stack } from '@mui/material'; +import { EATLIMIT } from '../../const/eatLimit'; +import { EmotionDataType } from '../../types/EmotionDataType'; + +import yagi_yorokobi from '../assets/yagi_yorokobi.png'; +import yagi_ikari from '../assets/yagi_iakri.png'; +import yagi_kanasimi from '../assets/yagi_kanasimi.png'; +import yagi_tanosii from '../assets/yagi_tanosii.png'; +import yagi_yorokobi_right from '../assets/yagi_yorokobi_right.png'; +import yagi_ikari_right from '../assets/yagi_iakri_right.png'; +import yagi_kanasimi_right from '../assets/yagi_kanasimi_right.png'; +import yagi_tanosii_right from '../assets/yagi_tanosii_right.png'; +import { modalStyle } from '../../styles/modalStyle'; + +type Props = { + monster: number; + eatCount: number; + open: boolean; + emotionData: EmotionDataType; + closeClick: React.MouseEventHandler | undefined; +}; + +export const BattleAction: FC = ({ monster, eatCount, open, emotionData, closeClick }) => { + const [monsterimg, setmonsterimg] = useState(''); + const [yagiimg, setyagiimg] = useState(''); + const emoId = emotionData.emoId; + useEffect(() => { + switch (monster) { + case 1: + setmonsterimg(yagi_yorokobi_right); + break; + case 2: + setmonsterimg(yagi_ikari_right); + break; + case 3: + setmonsterimg(yagi_kanasimi_right); + break; + case 4: + setmonsterimg(yagi_tanosii_right); + break; + } + + switch (emoId) { + case 1: + setyagiimg(yagi_yorokobi); + break; + case 2: + setyagiimg(yagi_ikari); + break; + case 3: + setyagiimg(yagi_kanasimi); + break; + case 4: + setyagiimg(yagi_tanosii); + break; + } + }, []); + return ( +
+ {eatCount > EATLIMIT ? ( + + + + + {/* + */} + + + + + ) : null} +
+ ); +}; diff --git a/src/components/battle/BattleResult.tsx b/src/components/battle/BattleResult.tsx new file mode 100644 index 0000000..0139e7b --- /dev/null +++ b/src/components/battle/BattleResult.tsx @@ -0,0 +1,73 @@ +import { FC, useState, useEffect } from 'react'; +import Backdrop from '@mui/material/Backdrop'; +import Modal from '@mui/material/Modal'; +import Fade from '@mui/material/Fade'; +import Typography from '@mui/material/Typography'; +import { Button, Stack } from '@mui/material'; +import { EATLIMIT } from '../../const/eatLimit'; +import { EmotionDataType } from '../../types/EmotionDataType'; +import { modalStyle } from '../../styles/modalStyle'; + +type Props = { + monster: number; + eatCount: number; + open: boolean; + emotionData: EmotionDataType; + closeClick: React.MouseEventHandler | undefined; +}; + +const BattleResult: FC = ({ monster, eatCount, open, emotionData, closeClick }) => { + const [label, setLabel] = useState(''); + const emoId = emotionData.emoId; + useEffect(() => { + if (monster === emoId) { + setLabel('ヤギはたたかいにかったようだ!やぎをほめよう!'); + } else { + setLabel('ヤギはたたかいにまけてしまった!やぎをはげまそう!'); + } + }, []); + + return ( +
+ {eatCount > EATLIMIT ? ( + + + + + ヤギはモンスターとたたかった! + + + + {label} + + + + + + + + + ) : null} +
+ ); +}; +export default BattleResult; diff --git a/src/components/common/CircleProgressCon.tsx b/src/components/common/CircleProgressCon.tsx new file mode 100644 index 0000000..1aa9899 --- /dev/null +++ b/src/components/common/CircleProgressCon.tsx @@ -0,0 +1,27 @@ +import { CircularProgress, Container, Stack } from '@mui/material'; +import { FC } from 'react'; + +type Props = { + isOpen: boolean; +}; + +export const CircleProgressCon: FC = ({ isOpen }) => { + return ( + <> + {isOpen ? ( + + + + + + ) : null} + + ); +}; diff --git a/src/components/getEmotionApi.ts b/src/components/getEmotionApi.ts deleted file mode 100644 index 8e26232..0000000 --- a/src/components/getEmotionApi.ts +++ /dev/null @@ -1,52 +0,0 @@ -import axios from 'axios'; -import { EmotionDataType } from '../types/EmotionDataType'; - -type emotionDataType = EmotionDataType; - -export const getEmotionApi = async (text: string, emotionData: emotionDataType) => { - let ans = {} as emotionDataType; - let updateEmotionData = {} as emotionDataType; - let maxEmotion = ''; - let maxScore = -Infinity; - updateEmotionData = emotionData; - const fetchAPI = async () => { - const res = await axios.get(`https://callgpt-f6bkalktuq-uc.a.run.app?text=${text}`); - const fetchEmotionData = res.data as emotionDataType; - ans = { - Joy: Number(fetchEmotionData.Joy), - Anger: Number(fetchEmotionData.Anger), - Sorrow: Number(fetchEmotionData.Sorrow), - Enjoyable: Number(fetchEmotionData.Enjoyable), - emoId: 0, - }; - }; - await fetchAPI(); - - for (const emotion in ans) { - if (ans[emotion as keyof emotionDataType] > maxScore) { - maxEmotion = emotion; - maxScore = ans[emotion as keyof emotionDataType]; - } - } - switch (maxEmotion) { - case 'Joy': - updateEmotionData.emoId = 1; - break; - case 'Anger': - updateEmotionData.emoId = 2; - break; - case 'Sorrow': - updateEmotionData.emoId = 3; - break; - case 'Enjoyable': - updateEmotionData.emoId = 4; - break; - } - - updateEmotionData.Joy += ans.Joy; - updateEmotionData.Anger += ans.Anger; - updateEmotionData.Sorrow += ans.Sorrow; - updateEmotionData.Enjoyable += ans.Enjoyable; - - return updateEmotionData; -}; diff --git a/src/hooks/useDiscloser.ts b/src/hooks/useDiscloser.ts index 64ad325..2e48267 100644 --- a/src/hooks/useDiscloser.ts +++ b/src/hooks/useDiscloser.ts @@ -1,12 +1,12 @@ import { useState } from 'react'; -export const useDiscloser = (initOpen: boolean) => { - const [isTutorialModalOpen, setisTutorialModalOpen] = useState(initOpen); +type UseDiscloserType = [boolean, () => void, () => void]; - const handleTutorialModalOpen = () => setisTutorialModalOpen(true); - const handleTutorialModalClose = () => setisTutorialModalOpen(false); +export const useDiscloser = (initOpen: boolean): UseDiscloserType => { + const [isTutorialModalOpen, setIsTutorialModalOpen] = useState(initOpen); - return { isTutorialModalOpen, handleTutorialModalOpen, handleTutorialModalClose }; -}; + const handleTutorialModalOpen = () => setIsTutorialModalOpen(true); + const handleTutorialModalClose = () => setIsTutorialModalOpen(false); -// useHooks + return [isTutorialModalOpen, handleTutorialModalOpen, handleTutorialModalClose]; +}; diff --git a/src/hooks/useInput.ts b/src/hooks/useInput.ts new file mode 100644 index 0000000..8c6f7fe --- /dev/null +++ b/src/hooks/useInput.ts @@ -0,0 +1,23 @@ +import { useCallback, useState } from 'react'; + +type useInputType = [ + string, + React.Dispatch>, + (e: React.ChangeEvent) => void +]; + +/** + * 入力欄をハンドリングするカスタムフック + * + * @param initialValue 初期の文字列 + * @returns [value, setValue, handleChange] + */ +export const useInput = (initialValue: string): useInputType => { + const [value, setValue] = useState(initialValue); + + const handleChange = useCallback((e: React.ChangeEvent) => { + setValue(e.target.value); + }, []); + + return [value, setValue, handleChange]; +}; diff --git a/src/lib/firebase.ts b/src/lib/firebase.ts new file mode 100644 index 0000000..a1ea288 --- /dev/null +++ b/src/lib/firebase.ts @@ -0,0 +1,14 @@ +import { initializeApp } from 'firebase/app'; +import { getFirestore } from 'firebase/firestore'; + +const firebaseConfig = { + apiKey: 'AIzaSyAyrDxvJU48ETweFaMIsHxOrPY_K-P9A6Q', + authDomain: 'yagi5108.firebaseapp.com', + projectId: 'yagi5108', + storageBucket: 'yagi5108.appspot.com', + messagingSenderId: '916610444207', + appId: '1:916610444207:web:2d8ba10c93e12b2540dd5a', +}; + +const app = initializeApp(firebaseConfig); +export const db = getFirestore(app); diff --git a/src/lib/firestore.ts b/src/lib/firestore.ts new file mode 100644 index 0000000..db2ba84 --- /dev/null +++ b/src/lib/firestore.ts @@ -0,0 +1,23 @@ +import { EmotionDataType } from '../types/EmotionDataType'; + +/** + * FireStore DB設計 + * + * コレクション名: words + * ドキュメント: 自動設定の文字列 + * + * フィールド名: documentWord: string 対象の文字列 + * フィールド名: emotionData: EmotionDataType 対応する感情 + */ + +// DBの型定義 +export type EmotionDataDBType = { + documentWord: string; + emotionData: EmotionDataType; +}; + +// コレクションの名前 +export const COLLECTIONNAME = 'words'; + +// 文字列を格納するフィールド名 +export const FIELDNAME = 'documentWord'; diff --git a/src/pages/AppView.tsx b/src/pages/AppView.tsx index 8dbc6ac..cc3a96e 100644 --- a/src/pages/AppView.tsx +++ b/src/pages/AppView.tsx @@ -1,89 +1,98 @@ -import React, { useEffect, useState } from 'react'; -import { Container, Stack, Grid, Box, ThemeProvider, CircularProgress } from '@mui/material'; +import React, { FC, useEffect, useState } from 'react'; +import { Container, Stack, Grid, Box, ThemeProvider } from '@mui/material'; import Tutorial from '../components/Tutorial'; import Form from '../components/Form'; import Eat from '../components/Eat'; -import bgImage from '../assets/backGround.png'; import NormalWalk from '../components/NormalWalk'; -import { getEmotionApi } from '../components/getEmotionApi'; +import { getEmotionApi } from '../api/getEmotionApi'; import { Branch } from '../components/Branch'; import FlowerPopup from '../components/FlowerPopup'; import EvolutionPopup from '../components/EvolutionPopup'; import EvolutionWalk from '../components/EvolutionWalk'; import { theme } from '../theme/theme'; -import { NavBar } from '../components/NavBar'; import Pulse from '../components/Pulse'; import { useDiscloser } from '../hooks/useDiscloser'; import { EmotionDataType } from '../types/EmotionDataType'; import { EATLIMIT } from '../const/eatLimit'; +import Battle from '../components/battle/Battle'; +import BattleResult from '../components/battle/BattleResult'; +import noon from '../assets/noon.png'; +// import night from '../assets/night.png'; +// import sougen from '../assets/sougen.png'; +// import umi from '../assets/umi.png'; +import mori from '../assets/mori.png'; +import { PageContainer } from '../components/PageContainer'; +import { useInput } from '../hooks/useInput'; +import { CircleProgressCon } from '../components/common/CircleProgressCon'; +import { NavBarCon } from '../components/NavBarCon'; -export const AppView: React.FC = () => { - const [pop, handlepop] = useState(true); //生成された草のポップアップの表示 - const [eat, handleeat] = useState(false); //食事するヤギの表示 +type RandomType = 0 | 1 | null; + +const emotionInitialData = { + Joy: 0, + Anger: 0, + Sorrow: 0, + Enjoyable: 0, + emoId: 0, +}; + +export const AppView: FC = () => { + const [pop, handlePop] = useState(true); //生成された草のポップアップの表示 + const [eat, handleEat] = useState(false); //食事するヤギの表示 const [showImage, setShowImage] = useState(false); //生成された草の表示(これいらんかもしれん) - const [inputText, setInputText] = useState(''); //フォームに入力された文字を管理 + const [inputText, setInputText, handleInputText] = useInput(''); //フォームに入力された文字を管理 const [eatCount, setEatCount] = useState(1); //食べた回数と進化先の変数の追加(eatCount,typeId) const [typeId, setTypeId] = useState(-1); //進化先のIDを格納 const [EvoPopup, setEvoPopup] = useState(false); //進化するタイミングで表示されるポップアップの表示 const [dispWalker, setDispWalker] = useState(true); //通常状態の移動するヤギの表示 const [random, setRandom] = useState(null); //草か果実かを定める - const [evoPop, setEvopop] = useState(true); //進化時のポップアップの表示 - const [evoWalk, setevoWalk] = useState(false); //進化したヤギの表示 + const [evoPop, setEvoPop] = useState(true); //進化時のポップアップの表示 + const [evoWalk, setEvoWalk] = useState(false); //進化したヤギの表示 const [dispCircle, setDispCircle] = useState(false); //ロード画面の表示 const [EmotionMax, setMax] = useState(0); const [Emotion, setEmotion] = useState([0, 0, 0, 0]); const [overlap, setOverlap] = useState(false); - - const handleChange = (event: React.ChangeEvent) => { - setInputText(event.target.value); - }; - - const { isTutorialModalOpen, handleTutorialModalOpen, handleTutorialModalClose } = + const [monster, setMonster] = useState(0); + const [containerSize, setContainerSize] = useState({ width: 260, height: 600 }); + const [emotionData, setEmotionData] = useState(emotionInitialData); + const [isTutorialModalOpen, handleTutorialModalOpen, handleTutorialModalClose] = useDiscloser(true); - - type RandomType = 0 | 1 | null; - const changeRnadom = () => { + const [isBattleModalOpen, handleBattleModalOpen, handleBattleModalClose] = useDiscloser(false); + const changeRandome = () => { const setItem = random === 0 ? 1 : 0; setRandom(setItem); }; - type emotionDataType = EmotionDataType; - - const emotionInitialData = { - Joy: 0, - Anger: 0, - Sorrow: 0, - Enjoyable: 0, - emoId: 0, - }; - const [emotionData, setEmotionData] = useState(emotionInitialData); + useEffect(() => { + setMonster(Math.floor(Math.random() * (4 - 1 + 1)) + 1); + }, [monster]); const handleSubmit = async () => { setDispCircle(true); setInputText(''); setEmotionData(await getEmotionApi(inputText, emotionData)); setDispCircle(false); - handlepop(false); - changeRnadom(); + handlePop(false); + changeRandome(); handleGrass(); }; const popSubmit = () => { - handlepop(true); - handleeat(true); + handlePop(true); + handleEat(true); setDispWalker(false); setTimeout(() => { setDispWalker(true); }, 2000); }; const walking = () => { - handleeat(false); + handleEat(false); }; const evolution = () => { setEvoPopup(true); - setEvopop(false); + setEvoPop(false); }; const WalkEvo = () => { - setevoWalk(true); + setEvoWalk(true); setEvoPopup(false); }; //草生成用のハンドルを追加(食事回数と条件達成で進化先の分析) @@ -117,132 +126,118 @@ export const AppView: React.FC = () => { // eatがfalseの場合はタイマーをキャンセルする setShowImage(true); } - }, [eat]); + }, [eat, showImage]); const isDisableTextField = () => { return eatCount > EATLIMIT; }; + const updatePageSize = ( + newContainerSize: React.SetStateAction<{ width: number; height: number }> + ) => { + setContainerSize(newContainerSize); + }; + return (
+ - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - {EvoPopup ? : null} - - - - + + {/* */} + + + + {EvoPopup ? ( + + ) : null} + + + - - - - - - - - {dispWalker && evoPop ? : null} - {evoWalk ? : null} - - - - + + + + + + {dispWalker && evoPop ? : null} + {evoWalk ? ( + + ) : null} + + + + + + - - - - {dispCircle ? ( - - - - - - ) : null} + + {/* ナビゲーションバー */} + + + {/* ロード画面 */} +
); diff --git a/src/pages/BattleView.tsx b/src/pages/BattleView.tsx new file mode 100644 index 0000000..8d7941c --- /dev/null +++ b/src/pages/BattleView.tsx @@ -0,0 +1,244 @@ +import React, { FC, useEffect, useState } from 'react'; +import { Container, Stack, Grid, Box, ThemeProvider } from '@mui/material'; +import Tutorial from '../components/Tutorial'; +import Form from '../components/Form'; +import Eat from '../components/Eat'; +import NormalWalk from '../components/NormalWalk'; +import { getEmotionApi } from '../api/getEmotionApi'; +import { Branch } from '../components/Branch'; +import FlowerPopup from '../components/FlowerPopup'; +import EvolutionPopup from '../components/EvolutionPopup'; +import EvolutionWalk from '../components/EvolutionWalk'; +import { theme } from '../theme/theme'; +import Pulse from '../components/Pulse'; +import { useDiscloser } from '../hooks/useDiscloser'; +import { EmotionDataType } from '../types/EmotionDataType'; +import { EATLIMIT } from '../const/eatLimit'; +import Battle from '../components/battle/Battle'; +import BattleResult from '../components/battle/BattleResult'; +//import noon from '../assets/noon.png'; +import night from '../assets/night.png'; +// import sougen from '../assets/sougen.png'; +// import umi from '../assets/umi.png'; +import mori from '../assets/mori.png'; +import { PageContainer } from '../components/PageContainer'; +import { useInput } from '../hooks/useInput'; +import { CircleProgressCon } from '../components/common/CircleProgressCon'; +import { NavBarCon } from '../components/NavBarCon'; + +type RandomType = 0 | 1 | null; + +const emotionInitialData = { + Joy: 0, + Anger: 0, + Sorrow: 0, + Enjoyable: 0, + emoId: 0, +}; + +export const BattleView: FC = () => { + const [pop, handlePop] = useState(true); //生成された草のポップアップの表示 + const [eat, handleEat] = useState(false); //食事するヤギの表示 + const [showImage, setShowImage] = useState(false); //生成された草の表示(これいらんかもしれん) + const [inputText, setInputText, handleInputText] = useInput(''); //フォームに入力された文字を管理 + const [eatCount, setEatCount] = useState(1); //食べた回数と進化先の変数の追加(eatCount,typeId) + const [typeId, setTypeId] = useState(-1); //進化先のIDを格納 + const [EvoPopup, setEvoPopup] = useState(false); //進化するタイミングで表示されるポップアップの表示 + const [dispWalker, setDispWalker] = useState(true); //通常状態の移動するヤギの表示 + const [random, setRandom] = useState(null); //草か果実かを定める + const [evoPop, setEvoPop] = useState(true); //進化時のポップアップの表示 + const [evoWalk, setEvoWalk] = useState(false); //進化したヤギの表示 + const [dispCircle, setDispCircle] = useState(false); //ロード画面の表示 + const [EmotionMax, setMax] = useState(0); + const [Emotion, setEmotion] = useState([0, 0, 0, 0]); + const [overlap, setOverlap] = useState(false); + const [monster, setMonster] = useState(0); + const [containerSize, setContainerSize] = useState({ width: 260, height: 600 }); + const [emotionData, setEmotionData] = useState(emotionInitialData); + const [isTutorialModalOpen, handleTutorialModalOpen, handleTutorialModalClose] = + useDiscloser(true); + const [isBattleModalOpen, handleBattleModalOpen, handleBattleModalClose] = useDiscloser(false); + const changeRandome = () => { + const setItem = random === 0 ? 1 : 0; + setRandom(setItem); + }; + + useEffect(() => { + setMonster(Math.floor(Math.random() * (4 - 1 + 1)) + 1); + }, [monster]); + + const handleSubmit = async () => { + setDispCircle(true); + setInputText(''); + setEmotionData(await getEmotionApi(inputText, emotionData)); + setDispCircle(false); + handlePop(false); + changeRandome(); + handleGrass(); + }; + const popSubmit = () => { + handlePop(true); + handleEat(true); + setDispWalker(false); + setTimeout(() => { + setDispWalker(true); + }, 2000); + }; + const walking = () => { + handleEat(false); + }; + const evolution = () => { + setEvoPopup(true); + setEvoPop(false); + }; + const WalkEvo = () => { + setEvoWalk(true); + setEvoPopup(false); + }; + //草生成用のハンドルを追加(食事回数と条件達成で進化先の分析) + const handleGrass = () => { + setEatCount(eatCount + 1); + + // setTypeId(Branch(emotionData,EmotionMax={EmotionMax},setMax={setMax},Emotion,setEmotion,overlap,setOverlap)); + setTypeId( + Branch({ + emotionData, + EmotionMax, + setMax, + Emotion, + setEmotion, + overlap, + setOverlap, + }) + ); + }; + + useEffect(() => { + if (eat) { + const timer = setTimeout(() => { + setShowImage(false); + walking(); + }, 2000); + + // クリーンアップ関数を返すことで、タイマーがキャンセルされる + return () => clearTimeout(timer); + } else { + // eatがfalseの場合はタイマーをキャンセルする + setShowImage(true); + } + }, [eat, showImage]); + + const isDisableTextField = () => { + return eatCount > EATLIMIT; + }; + + const updatePageSize = ( + newContainerSize: React.SetStateAction<{ width: number; height: number }> + ) => { + setContainerSize(newContainerSize); + }; + + return ( +
+ + + + + + + {/* */} + + + + {EvoPopup ? ( + + ) : null} + + + + + + + + + {dispWalker && evoPop ? : null} + {evoWalk ? ( + + ) : null} + + + + + + + + + + {/* ナビゲーションバー */} + + + {/* ロード画面 */} + + +
+ ); +}; diff --git a/src/routes/Routers.tsx b/src/routes/Routers.tsx index 68a9efc..ac80f5d 100644 --- a/src/routes/Routers.tsx +++ b/src/routes/Routers.tsx @@ -1,11 +1,13 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { AppView } from '../pages/AppView'; +import { BattleView } from '../pages/BattleView'; export const Router = () => { return ( } /> + } /> ); diff --git a/src/styles/modalStyle.ts b/src/styles/modalStyle.ts new file mode 100644 index 0000000..b00ac8c --- /dev/null +++ b/src/styles/modalStyle.ts @@ -0,0 +1,20 @@ +import backgroundImage from '../assets/tutorial.png'; + +export const modalStyle = { + position: 'absolute' as const, + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 300, + color: '#000', + + backgroundImage: `url(${backgroundImage})`, + backgroundSize: 'cover', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center', + border: '1.5px solid #FFF', + borderRadius: '10px', + boxShadow: 24, + p: 4, + outline: 'none', +};