);\r\n }\r\n return result;\r\n}\r\n\r\nconst StyledBallCount = styled.div`\r\n color:#fff;\r\n font-weight:600;\r\n .counters {\r\n display: flex;\r\n margin-bottom:0.5rem;\r\n & > div {\r\n font-size: 1.5rem;\r\n margin-right: 0.5rem;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n line-height: 1.3rem;\r\n }\r\n & > div:not(:first-child) {\r\n border-radius: 50%;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n }\r\n }\r\n .strike{\r\n & > div:not(:first-child) {\r\n background-color:yellow;\r\n }\r\n }\r\n .ball{\r\n & > div:not(:first-child) {\r\n background-color:green;\r\n }\r\n }\r\n .out{\r\n & > div:not(:first-child) {\r\n background-color:red;\r\n }\r\n }\r\n`;\r\n\r\nexport default BallCount;","import React from 'react';\r\nimport styled from 'styled-components';\r\nconst Inning = ({ inning: { turn, round }, isHome }) => {\r\n return
{round}회{turn ? '초' : '말'} {(isHome && turn) || (!isHome && !turn) ? '공격' : '수비'}\r\n}\r\n\r\nconst StyledInning = styled.div`\r\n font-size: 1.5rem;\r\n font-weight: 600;\r\n color: #fff;\r\n text-align: right;\r\n`;\r\n\r\nexport default Inning;","import { useEffect, useState } from 'react';\r\n\r\n/*\r\n한루 한루 주자가 뛰는 것을 애니메이션 넣어주기 위해서 한번에 계산하기 보다는\r\nfor문으로 한번씩 나눠서 실행 \r\n*/\r\n\r\nconst useScoreNBase = ({\r\n score: initialScore = { home: [0], away: [] },\r\n base: initialBase = { 1: false, 2: false, 3: false },\r\n}) => {\r\n const [score, setScore] = useState(initialScore);\r\n const [base, setBase] = useState(initialBase);\r\n\r\n const getNewScore = (score, team) => {\r\n if (!base[3]) return score;\r\n const newScore = { ...score };\r\n newScore[team][newScore[team].length - 1] += 1;\r\n return newScore;\r\n };\r\n const getNewBase = (base, runFirstBase) => {\r\n const newBase = { ...base };\r\n const baseStatus = Object.values(newBase);\r\n const newBaseStatus = runFirstBase\r\n ? [true, ...baseStatus.slice(0, -1)]\r\n : [false, ...baseStatus.slice(0, -1)];\r\n newBaseStatus.forEach((status, idx) => (newBase[idx + 1] = status));\r\n return newBase;\r\n };\r\n const safetyDispatch = ({ type, turn, runFirstBase }) => {\r\n const team = turn ? 'home' : 'away';\r\n switch (type) {\r\n case 'clear':\r\n const newScore = { ...score };\r\n newScore[team].push(0);\r\n setScore(newScore);\r\n setBase({ 1: false, 2: false, 3: false });\r\n return;\r\n case 'onBase':\r\n setBase(getNewBase(base, runFirstBase));\r\n setScore(getNewScore(score, team));\r\n return;\r\n default:\r\n throw Error('잘못된 safetyDispatch 타입입니다.');\r\n }\r\n };\r\n\r\n return { score, base, safetyDispatch };\r\n};\r\n\r\nexport default useScoreNBase;\r\n","import React, { useState, useContext, useEffect, useRef } from 'react';\r\nimport styled from 'styled-components';\r\nimport useScoreNBase from '../../../hooks/useScoreNBase';\r\nimport { ScoreNBaseContext } from '../GamePlay';\r\n\r\nconst Screen = ({\r\n handleStrike,\r\n handleBall,\r\n handleSafety,\r\n ballCount,\r\n turn,\r\n teamName,\r\n selectTeam,\r\n}) => {\r\n const { base, safetyDispatch } = useContext(ScoreNBaseContext);\r\n const [isTransition, setIsTransition] = useState(false);\r\n const [runFirstBase, setRunFirstBase] = useState(false);\r\n const [currentPower, setCurrentPower] = useState(0);\r\n\r\n const playerRunType = [\r\n 'translateX(19.6rem)',\r\n 'translateY(-19.6rem)',\r\n 'translateX(-19.6rem)',\r\n 'translateY(19.6rem)',\r\n ];\r\n\r\n const handleTransitionEnd = async () => {\r\n if (currentPower === 0) return;\r\n setRunFirstBase(false);\r\n setCurrentPower(currentPower - 1);\r\n safetyDispatch({ type: 'onBase', turn, runFirstBase });\r\n setIsTransition(false);\r\n };\r\n\r\n const handlePitchClick = () => {\r\n const randomNum = Math.ceil(Math.random() * 100);\r\n if (randomNum <= 55) {\r\n //스트라이크\r\n handleStrike();\r\n } else if (randomNum <= 80) {\r\n //볼\r\n if (ballCount.ball === 3) {\r\n setCurrentPower(1);\r\n setIsTransition(true);\r\n setRunFirstBase(true);\r\n }\r\n handleBall();\r\n } else {\r\n //안타\r\n setIsTransition(true);\r\n setRunFirstBase(true);\r\n handleSafety();\r\n if (randomNum <= 100) {\r\n //1루타\r\n setCurrentPower(1);\r\n } else if (randomNum <= 6) {\r\n //2루타\r\n setCurrentPower(2);\r\n } else if (randomNum <= 3) {\r\n //3루타\r\n setCurrentPower(3);\r\n } else {\r\n //홈런\r\n setCurrentPower(4);\r\n }\r\n }\r\n };\r\n\r\n const baseList = Object.entries(base).map(([baseNum, status], idx) => (\r\n
\r\n ));\r\n\r\n const isPitch = (turn && teamName.home != selectTeam) || (!turn && teamName.away != selectTeam);\r\n\r\n const savedCallback = useRef();\r\n\r\n // function callback() {\r\n // handlePitchClick();\r\n // }\r\n\r\n // useEffect(() => {\r\n // savedCallback.current = callback;\r\n // });\r\n\r\n // useEffect(() => {\r\n // function tick() {\r\n // savedCallback.current();\r\n // }\r\n // if (!isPitch) {\r\n // let id = setInterval(tick, 1000);\r\n // return () => clearInterval(id);\r\n // } else {\r\n // return null;\r\n // }\r\n // }, []);\r\n\r\n // useEffect(() => {\r\n // let interval = setInterval(() => handlePitchClick(), 1000);\r\n // clearInterval(interval);\r\n // if(!isPitch) {\r\n // interval = setInterval(() => handlePitchClick(), 1000);\r\n // } else {\r\n // clearInterval(interval);\r\n // }\r\n // return () => clearInterval(interval);\r\n // }, [turn])\r\n\r\n return (\r\n
\r\n PITCH\r\n \r\n \r\n {baseList}\r\n \r\n \r\n );\r\n};\r\n\r\nconst StyledScreen = styled.div`\r\n position: relative;\r\n align-self: center;\r\n`;\r\nconst StyledPitch = styled.button`\r\n position: absolute;\r\n top: calc(50% - 1.5rem);\r\n left: calc(50% - 5rem);\r\n display: inline;\r\n width: 10rem;\r\n height: 3rem;\r\n font-size: 2rem;\r\n font-weight: 600;\r\n border: 2px solid #fff;\r\n border-radius: 0.5rem;\r\n background: transparent;\r\n color: #fff;\r\n cursor: pointer;\r\n transition: all 0.1s linear;\r\n z-index: 1;\r\n &:hover {\r\n background-color: #fff;\r\n color: #000;\r\n }\r\n`;\r\n\r\nconst StyledGround = styled.div`\r\n position: relative;\r\n border: 3px solid #fff;\r\n width: 20rem;\r\n height: 20rem;\r\n margin: auto;\r\n transform: rotate(-45deg);\r\n .runner {\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n background-color: red;\r\n transition: ${({ isTransition }) => (isTransition ? 'all 0.5s' : '')};\r\n }\r\n\r\n .home {\r\n z-index: 4;\r\n }\r\n .base {\r\n position: absolute;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background-color: #fff;\r\n width: 2rem;\r\n height: 2rem;\r\n &:nth-child(1) {\r\n bottom: -1rem;\r\n left: -1rem;\r\n .runner {\r\n background-color: ${({ runFirstBase }) => (runFirstBase ? 'red' : 'rgba(0,0,0,0)')};\r\n transform: ${({ playerRunType, currentPower, runFirstBase }) =>\r\n runFirstBase && currentPower > 0 ? `${playerRunType[0]} rotate(45deg)` : `rotate(45deg)`};\r\n }\r\n &:before {\r\n content: '';\r\n background-color: #fff;\r\n width: 2.8284712rem;\r\n height: 4rem;\r\n position: absolute;\r\n bottom: -2.45rem;\r\n left: -1.88rem;\r\n transform: rotate(45deg);\r\n }\r\n }\r\n &:nth-child(2) {\r\n z-index: 3;\r\n bottom: -1rem;\r\n right: -1rem;\r\n .runner {\r\n background-color: ${({ base }) => (base[1] ? 'red' : 'rgba(0,0,0,0)')};\r\n transform: ${({ playerRunType, currentPower }) =>\r\n currentPower > 0 ? `${playerRunType[1]} rotate(45deg)` : `rotate(45deg)`};\r\n }\r\n }\r\n &:nth-child(3) {\r\n z-index: 2;\r\n top: -1rem;\r\n right: -1rem;\r\n .runner {\r\n background-color: ${({ base }) => (base[2] ? 'red' : 'rgba(0,0,0,0)')};\r\n transform: ${({ playerRunType, currentPower }) =>\r\n currentPower > 0 ? `${playerRunType[2]} rotate(45deg)` : `rotate(45deg)`};\r\n }\r\n }\r\n &:nth-child(4) {\r\n z-index: 1;\r\n top: -1rem;\r\n left: -1rem;\r\n .runner {\r\n background-color: ${({ base }) => (base[3] ? 'red' : 'rgba(0,0,0,0)')};\r\n transform: ${({ playerRunType, currentPower }) =>\r\n currentPower > 0 ? `${playerRunType[3]} rotate(45deg)` : `rotate(45deg)`};\r\n }\r\n }\r\n }\r\n`;\r\n\r\nexport default Screen;\r\n","const fetchPUT = async (url, data) => {\r\n const result = await fetch(url, {\r\n method: 'PUT',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify(data),\r\n });\r\n console.log(result);\r\n};\r\n\r\nconst fetchGET = async (url) => {\r\n const result = await fetch(url);\r\n const data = await result.json();\r\n return data;\r\n};\r\n\r\nconst fetchPOST = async (url, data) => {\r\n console.log(data);\r\n const result = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify(data),\r\n });\r\n console.log(result);\r\n};\r\n\r\nexport { fetchPUT, fetchGET, fetchPOST };\r\n","import styled from 'styled-components';\r\nimport { useState, useReducer, useContext, useEffect } from 'react';\r\nimport BallCount from './BallCount';\r\nimport Inning from './Inning';\r\nimport Screen from './Screen';\r\nimport { GameIdContext, ScoreNBaseContext } from '../GamePlay';\r\nimport { fetchPUT, fetchPOST } from '../../../util/api.js';\r\nimport useFetch from '../../../hooks/useFetch';\r\n\r\nconst URL = 'http://52.78.184.142';\r\n\r\nconst ballCountReducer = (state, action) => {\r\n let newState = { ...state };\r\n switch (action.type) {\r\n case 'strike':\r\n newState.strike++;\r\n break;\r\n case 'ball':\r\n newState.ball++;\r\n break;\r\n case 'safety':\r\n newState = { strike: 0, ball: 0, out: newState.out };\r\n break;\r\n case 'clear':\r\n newState = { strike: 0, ball: 0, out: 0 };\r\n break;\r\n case 'out':\r\n newState.ball = 0;\r\n newState.strike = 0;\r\n newState.out++;\r\n break;\r\n default:\r\n throw Error('잘못된 ball-count reducer');\r\n }\r\n return newState;\r\n};\r\n\r\nconst Board = ({\r\n memberListDispatch,\r\n inning,\r\n setInning,\r\n logListDispatch,\r\n game_id,\r\n teamName,\r\n teamId,\r\n selectTeam,\r\n memberList,\r\n}) => {\r\n const [ballCount, ballCountDispatch] = useReducer(ballCountReducer, {\r\n strike: 0,\r\n ball: 0,\r\n out: 0,\r\n });\r\n const { score, base, safetyDispatch } = useContext(ScoreNBaseContext);\r\n const { gameID } = useContext(GameIdContext);\r\n\r\n const getPlayerId = () => memberList[inning.turn ? 'home' : 'away'].find((v) => v.status)?.id;\r\n\r\n const currentRoundScore = {\r\n game_id: gameID,\r\n home: score.home[inning.round - 1] + inning.turn,\r\n away: !inning.turn ? score.away[inning.round - 1] + 1 : null,\r\n round: inning.round,\r\n };\r\n\r\n const handleStrike = () => {\r\n if (ballCount.strike === 2) {\r\n handleOut();\r\n } else {\r\n ballCountDispatch({ type: 'strike' });\r\n logListDispatch({ type: 'strike', ...ballCount, strike: ballCount.strike + 1 });\r\n }\r\n };\r\n const handleBall = () => {\r\n if (ballCount.ball === 3) {\r\n if (base[3]) {\r\n //4번 API {\"game_id\": 7,\"home\": 1,\"away\": 2, \"round\": 3} 모든정보 컨텍스트로\r\n fetchPUT(`${URL}/games/${gameID}`, currentRoundScore);\r\n }\r\n const playerId = getPlayerId();\r\n fetchPUT(`${URL}/players/${playerId}`, { game_id: gameID, at_bat: true });\r\n ballCountDispatch({ type: 'safety' });\r\n logListDispatch({ type: '4ball', end: true });\r\n memberListDispatch({ type: 'safety', turn: inning.turn, game_id });\r\n } else {\r\n ballCountDispatch({ type: 'ball' });\r\n logListDispatch({ type: 'ball', ...ballCount, ball: ballCount.ball + 1 });\r\n }\r\n };\r\n const handleOut = () => {\r\n const playerId = getPlayerId();\r\n if (ballCount.out === 2) {\r\n //7번 API { \"game_id\": 6, \"team_id\":1, \"round\":3, \"at_bat\": 3 } ??타자를 번호를 보내는거네???\r\n // 여기에 공수교대\r\n //\r\n fetchPOST(`${URL}/games/${gameID}`, {\r\n game_id: gameID,\r\n round: inning.round,\r\n player_id: playerId,\r\n team_id: inning.turn ? teamId.home : teamId.away,\r\n });\r\n ballCountDispatch({ type: 'clear' });\r\n if (inning.turn) setInning({ ...inning, turn: !inning.turn });\r\n else setInning({ ...inning, round: inning.round + 1, turn: !inning.turn });\r\n safetyDispatch({ type: 'clear', turn: !inning.turn });\r\n logListDispatch({ type: 'clear' });\r\n } else {\r\n fetchPUT(`${URL}/players/${playerId}`, { game_id: gameID, at_bat: false });\r\n logListDispatch({ type: 'out', end: true });\r\n ballCountDispatch({ type: 'out' });\r\n }\r\n // 멤버 아웃 1, 타석 1 증가\r\n memberListDispatch({ type: 'out', turn: inning.turn, game_id });\r\n };\r\n const handleSafety = () => {\r\n if (base[3]) {\r\n //4번 API {\"game_id\": 7,\"home\": 1,\"away\": 2, \"round\": 3} 모든정보 컨텍스트로\r\n fetchPUT(`${URL}/games/${gameID}`, currentRoundScore);\r\n }\r\n const playerId = getPlayerId();\r\n fetchPUT(`${URL}/players/${playerId}`, { game_id: gameID, at_bat: true });\r\n ballCountDispatch({ type: 'safety' });\r\n // 멤버 안타 1, 타석 1 증가\r\n memberListDispatch({ type: 'safety', turn: inning.turn, game_id });\r\n logListDispatch({ type: 'safety', end: true });\r\n };\r\n\r\n // useEffect(() => {\r\n // return () => fetchPUT(inning);\r\n // }, [inning]);\r\n\r\n return (\r\n
\r\n \r\n \r\n \r\n \r\n );\r\n};\r\n\r\nconst StyledBoard = styled.div`\r\n display: grid;\r\n grid-template-columns: 10rem 1fr 10rem;\r\n`;\r\n\r\nexport default Board;\r\n\r\nconst isHome = true;\r\n","import styled from 'styled-components';\r\n\r\nconst TYPE = {\r\n strike: '스트라이크',\r\n ball: '볼',\r\n safety: '안타',\r\n '4ball': '볼넷',\r\n out: '아웃',\r\n};\r\n\r\nconst Log = ({ logList }) => {\r\n const logs = logList.map((member, i) => (\r\n
\r\n
\r\n {member.index}번 타자 {member.name}\r\n
\r\n
\r\n {member.history.map((v, i) =>\r\n v.end ? (\r\n
\r\n
\r\n
{TYPE[v.type]}!
\r\n
\r\n
\r\n ) : (\r\n
\r\n
{i + 1}
\r\n
{TYPE[v.type]}
\r\n
\r\n S{v.strike} B{v.ball}\r\n
\r\n
\r\n )\r\n )}\r\n
\r\n
\r\n ));\r\n return (\r\n
\r\n {logs}
\r\n \r\n );\r\n};\r\n\r\nconst StyledLog = styled.div`\r\n .logs {\r\n display: flex;\r\n flex-flow: column-reverse;\r\n .log:not(:first-child) {\r\n margin-bottom: 2.5rem;\r\n }\r\n font-size: 1.125rem;\r\n }\r\n .contents {\r\n display: flex;\r\n flex-flow: column-reverse;\r\n }\r\n .contents > div {\r\n margin-bottom: 0.75rem;\r\n }\r\n .title {\r\n color: #cff0f7;\r\n font-weight: 600;\r\n margin-bottom: 1rem;\r\n }\r\n .content {\r\n display: grid;\r\n grid-template-columns: 1.25rem 3fr 1fr;\r\n text-align: center;\r\n margin-bottom: 0.5rem;\r\n *:nth-child(1) {\r\n color: #000;\r\n background-color: #f1f1f1;\r\n border-radius: 50%;\r\n }\r\n *:nth-child(2) {\r\n color: #f1f1f1;\r\n font-weight: 600;\r\n }\r\n *:nth-child(3) {\r\n color: #aaa;\r\n font-weight: 600;\r\n }\r\n }\r\n .content.result {\r\n *:nth-child(1) {\r\n background-color: transparent;\r\n }\r\n *:nth-child(2) {\r\n color: #79b5ce;\r\n }\r\n }\r\n .active {\r\n .title {\r\n color: #ff7878;\r\n }\r\n .content.result > * {\r\n color: #ff4545;\r\n }\r\n }\r\n`;\r\n\r\nexport default Log;\r\n","import React from 'react';\r\nimport styled from 'styled-components';\r\nimport { IoBaseballOutline } from 'react-icons/io5';\r\n\r\nconst ScoreList = ({ team, dataType, data, isPlayer }) => {\r\n const isInRound = dataType === 'round';\r\n const scoreList = new Array(12).fill('');\r\n\r\n data.forEach((v, idx) => (scoreList[idx] = v));\r\n isInRound ? scoreList.push('R') : scoreList.push(data.reduce((acc, cur) => acc + cur, 0));\r\n\r\n const scoreDivList = scoreList.map((score, idx, arr) => (\r\n
\r\n {score}\r\n
\r\n ));\r\n\r\n return (\r\n
\r\n \r\n
\r\n {isPlayer &&
}\r\n
{team || ''}
\r\n
\r\n {isPlayer &&
player
}\r\n
\r\n {scoreDivList}
\r\n \r\n );\r\n};\r\n\r\nconst StyledScoreList = styled.div`\r\n display: flex;\r\n\r\n .team-wrapper {\r\n margin-right: 2rem;\r\n .team {\r\n position: relative;\r\n width: 8rem;\r\n font-size: 1.1rem;\r\n font-weight: 700;\r\n .baseball {\r\n position: absolute;\r\n top: 0.2rem;\r\n left: -1.7rem;\r\n margin-right: 0.2rem;\r\n }\r\n span {\r\n text-align: center;\r\n }\r\n }\r\n .turn {\r\n color: #ff4545;\r\n font-weight: 600;\r\n text-align: center;\r\n }\r\n }\r\n\r\n .score-list {\r\n border-bottom: ${({ isInRound }) => isInRound && '3px solid #fff'};\r\n margin-bottom: ${({ isInRound }) => (isInRound ? '1rem' : '0.3rem')};\r\n }\r\n .score {\r\n display: flex;\r\n justify-content: center;\r\n font-size: 1.5rem;\r\n font-weight: 700;\r\n width: 3rem;\r\n height: ${({ isInRound }) => (isInRound ? '2.7rem' : '3rem')};\r\n margin-right: 1rem;\r\n }\r\n .last-score {\r\n color: #ff4545;\r\n }\r\n .score-list {\r\n display: flex;\r\n }\r\n`;\r\n\r\nexport default ScoreList;\r\n","import React from 'react';\r\nimport styled from 'styled-components';\r\nimport useFetch from '../../../../hooks/useFetch';\r\nimport ScoreList from './ScoreList';\r\n\r\nconst Score = ({ score, teamName, gameID }) => {\r\n const SCORE_URL = `http://52.78.184.142/games/${gameID}/detail-score`;\r\n //props로 받아올 아이들 1. team 2. player(home or away)\r\n const ROUND = new Array(12).fill().map((_, idx) => idx + 1);\r\n\r\n // const { data: score } = useFetch(SCORE_URL, 'get');\r\n return (\r\n score &&\r\n score.home && (\r\n
\r\n \r\n \r\n \r\n \r\n )\r\n );\r\n};\r\n\r\nconst StyledScore = styled.div`\r\n width: 70rem;\r\n min-width: 63rem;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 2rem 0 0.8rem 0;\r\n margin: 3rem auto;\r\n color: #fff;\r\n border: 3px solid #fff;\r\n`;\r\n\r\nconst data = {\r\n home: [1, 0, 0, 0],\r\n away: [1, 2, 2],\r\n};\r\n\r\nexport default Score;\r\n","import React from 'react';\r\n// const { atBat, plate_appearance, outCountCount } = member;\r\n\r\nconst RosterPlayer = ({ name, atBat, plate_appearance, outCount, player_name }) => {\r\n \r\n const getAverage = (atBat, plate_appearance) => (plate_appearance / atBat) ? (plate_appearance / atBat).toFixed(3) : 0;\r\n const class_name = 'roster--member' + (name === player_name ? ' active' : '');\r\n return (\r\n
\r\n {name}
\r\n {atBat}
\r\n {plate_appearance}
\r\n {outCount}
\r\n {getAverage(atBat, plate_appearance)}
\r\n \r\n );\r\n};\r\n\r\nexport default RosterPlayer;","import styled from 'styled-components';\r\nimport RosterPlayer from './RosterPlayer';\r\n\r\nconst TeamRoster = ({ memberList, teamName, selectTeam }) => {\r\n const player_name = 'abc';\r\n const HEADS = ['타자', '타석', '안타', '아웃', '평균'];\r\n const TOTALTEXT = 'Totals';\r\n const heads = HEADS.map((head, idx) =>
{head}
);\r\n let [totalAtBat, totalSafety, totalOut] = [0, 0, 0];\r\n \r\n const members = memberList.map((member, idx) => {\r\n const { atBat, plate_appearance, outCount } = member;\r\n totalAtBat += atBat;\r\n totalSafety += plate_appearance;\r\n totalOut += outCount;\r\n return
\r\n });\r\n\r\n return (\r\n
\r\n {teamName} {teamName === selectTeam &&
Player
}
\r\n \r\n - \r\n {heads}\r\n
\r\n {members}\r\n - \r\n
{TOTALTEXT}
\r\n {totalAtBat}
\r\n {totalSafety}
\r\n {totalOut}
\r\n \r\n \r\n
\r\n \r\n );\r\n}\r\n\r\nconst StyledTeamRoster = styled.div`\r\n .title {\r\n font-size: 1.5rem;\r\n font-weight: 600;\r\n color: #fff;\r\n border-bottom: 3px solid #fff;\r\n text-align: center;\r\n padding-top: 0.5rem;\r\n padding-bottom: 1rem;\r\n .player {\r\n display: inline-block;\r\n color: #ff4545;\r\n font-size: 0.75rem;\r\n font-weight: 600;\r\n position: relative;\r\n top: -0.25rem;\r\n }\r\n }\r\n ul {\r\n padding: 0;\r\n margin: 0;\r\n }\r\n .roster {\r\n li {\r\n display: grid;\r\n grid-template-columns: 2fr 1fr 1fr 1fr 1.5fr;\r\n color: #d2d2d2;\r\n font-size: 1.25rem;\r\n text-align: center;\r\n padding: 0.625rem;\r\n &:not(:last-child) {\r\n border-bottom: 1px solid #555;\r\n }\r\n }\r\n li.roster--head {\r\n border-bottom: 2px solid #777;\r\n color: #aaa;\r\n }\r\n li.roster--total {\r\n color: #fff;\r\n font-weight: 600;\r\n }\r\n li.roster--member {\r\n }\r\n .roster--member.active {\r\n color: #ff4545;\r\n }\r\n }\r\n`;\r\n\r\nexport default TeamRoster;","import styled from 'styled-components';\r\nimport TeamRoster from './TeamRoster';\r\nconst Roster = ({ memberList, teamName, selectTeam }) => {\r\n return (\r\n
\r\n \r\n \r\n \r\n );\r\n}\r\n\r\n// 90vw는 돼야하는데 그러면 이상함...;\r\nconst StyledRoster = styled.div`\r\n margin: 0 auto;\r\n max-width: 60vw;\r\n margin-top: 5rem;\r\n border: 3px solid #fff;\r\n padding: 0.5rem 1rem;\r\n display: grid;\r\n grid-template-columns: 1fr 1fr;\r\n & > *:first-child {\r\n border-right: 1.5px solid #fff;\r\n }\r\n & > *:last-child {\r\n border-left: 1.5px solid #fff;\r\n }\r\n`;\r\n\r\nexport default Roster;\r\n","import React, {useState} from 'react';\r\nimport styled, { keyframes } from 'styled-components';\r\n\r\nconst PopUp = ({ children, position, emptyText = '' }) => {\r\n const [active, setActive] = useState(false);\r\n const handleClick = ({target}) => (target.className === 'childrenWrapper' || !target.closest('.childrenWrapper')) && setActive(false);\r\n return active ?
\r\n \r\n {children}\r\n
\r\n :
setActive(true)}>{emptyText};\r\n};\r\n\r\nconst toBottom = keyframes`\r\n0% {\r\n margin-top: -40rem;\r\n}\r\n100% {\r\n margin-top: 0rem;\r\n}\r\n`;\r\n\r\nconst toTop = keyframes`\r\n0% {\r\n transform: translateY(30rem);\r\n}\r\n100% {\r\n transform: translateY(0rem);\r\n}\r\n`;\r\n\r\nconst StyledPopUp = styled.div`\r\n position: absolute;\r\n top: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n z-index: 2;\r\n text-align: center;\r\n &:before {\r\n background-color: rgba(0, 0, 0, 0.9);\r\n content: '';\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n z-index: -1;\r\n }\r\n &.active > * {\r\n animation: ${props => props.position === 'top' ? toBottom : toTop } 0.5s cubic-bezier(0.33, 1, 0.68, 1);\r\n }\r\n`;\r\n\r\nconst StyledEmpty = styled.div`\r\n width: 10vw;\r\n height: 3rem;\r\n position: absolute;\r\n right: 30vw;\r\n cursor: pointer;\r\n color: #eee;\r\n border-radius: ${props => props.position === 'top' ? '0 0 1rem 1rem' : '1rem 1rem 0 0' };\r\n background-color: rgba(0, 0, 0, 0.5);\r\n top: ${props => props.position === 'top' && '0px'};\r\n bottom: ${props => props.position === 'bottom' && '0px'};\r\n text-align: center;\r\n font-size: 1.25rem;\r\n padding: 0.5rem 0;\r\n &:hover {\r\n background-color: rgba(0, 0, 0, 0.8);\r\n }\r\n`\r\n\r\n\r\nexport default PopUp;\r\n\r\n// const PopUp = ({ children }) => {\r\n// return
{children};\r\n// };\r\n\r\n// const StyledPopUp = styled.div`\r\n// position: fixed;\r\n// width: 100vw;\r\n// height: 100vh;\r\n// background-color: rgba(0, 0, 0, 0.4);\r\n// `;\r\n","import { useEffect, useReducer, createContext, useState, useRef } from 'react';\r\nimport styled from 'styled-components';\r\nimport Score from './score/Score';\r\nimport Player from './player/Player';\r\nimport Board from './board/Board';\r\nimport Log from './log/Log';\r\nimport PopUpScore from './popup/score/Score';\r\nimport PopUpRoster from './popup/roster/Roster';\r\nimport PopUp from '../ui/PopUp';\r\nimport useScoreNBase from '../../hooks/useScoreNBase';\r\nimport { fetchPUT } from '../../util/api.js';\r\nimport useFetch from '../../hooks/useFetch';\r\n\r\nexport const ScoreNBaseContext = createContext();\r\nexport const GameIdContext = createContext();\r\nconst MemberListContext = createContext();\r\n\r\nconst DATA_PLAYER_URL = 'http://52.78.184.142';\r\n\r\nconst memberListReducer = (state, action) => {\r\n if (action.type === 'init') return action.value;\r\n let next = 0;\r\n const team = action.turn ? 'home' : 'away';\r\n const newTeam = [...state[team]].map((member, idx, arr) => {\r\n let { plate_appearance, atBat, outCount, status } = member;\r\n if (member.status) {\r\n if (action.type === 'out') outCount++;\r\n else plate_appearance++;\r\n atBat++;\r\n next = idx + 1 === arr.length ? 0 : idx + 1;\r\n return { ...member, plate_appearance, atBat, outCount, status: !status };\r\n } else {\r\n return { ...member };\r\n }\r\n });\r\n newTeam[next].status = true;\r\n return { ...state, [team]: newTeam };\r\n};\r\n\r\nconst logListReducer = (state, action) => {\r\n let newState = [...state];\r\n const target = newState.length > 0 && { ...newState[newState.length - 1] };\r\n switch (action.type) {\r\n case 'next':\r\n newState.push({ ...action.value, index: action.index, history: [] });\r\n break;\r\n case 'strike':\r\n case 'ball':\r\n target.history = [...target.history, { ...action }];\r\n newState[newState.length - 1] = target;\r\n break;\r\n case '4ball':\r\n case 'safety':\r\n case 'out':\r\n target.history = [...target.history, { type: action.type, end: true }];\r\n target.status = false;\r\n newState[newState.length - 1] = target;\r\n break;\r\n case 'clear':\r\n newState = [];\r\n break;\r\n }\r\n return newState;\r\n};\r\n\r\nconst GamePlay = ({ home, away, game_id }) => {\r\n const path = window.location.pathname;\r\n const gameID = +path.slice(7);\r\n const selectTeam = localStorage.getItem('select');\r\n localStorage.setItem('game_id', gameID);\r\n const GAME_PLAY_URL = DATA_PLAYER_URL + path;\r\n const { data: gamePlayData } = useFetch(GAME_PLAY_URL, 'get');\r\n const [inning, setInning] = useState({\r\n turn: true,\r\n round: 1,\r\n });\r\n const [logList, logListDispatch] = useReducer(logListReducer, []);\r\n const { score, base, safetyDispatch } = useScoreNBase({\r\n score: { home: [0], away: [] },\r\n base: undefined,\r\n });\r\n const [memberList, memberListDispatch] = useReducer(memberListReducer, null);\r\n const teamId = {\r\n home: gamePlayData?.home?.teamId,\r\n away: gamePlayData?.away?.teamId,\r\n };\r\n const teamName = {\r\n home: gamePlayData?.home?.name,\r\n away: gamePlayData?.away?.name,\r\n };\r\n const pitchers = {\r\n home: gamePlayData?.home?.pitcherId,\r\n away: gamePlayData?.away?.pitcherId,\r\n };\r\n\r\n useEffect(() => {\r\n const memberListData = {\r\n home: gamePlayData?.home?.member_list,\r\n away: gamePlayData?.away?.member_list,\r\n };\r\n setInning({ turn: gamePlayData?.turn || true, round: gamePlayData?.round || 1 });\r\n memberListDispatch({ type: 'init', value: memberListData });\r\n }, [gamePlayData]);\r\n\r\n useEffect(() => {\r\n if (memberList && memberList.home) {\r\n memberList[inning.turn ? 'home' : 'away'].forEach((member, index) => {\r\n if (member.status) {\r\n logListDispatch({ value: { ...member }, type: 'next', index: index + 1 });\r\n }\r\n });\r\n }\r\n }, [memberList]);\r\n\r\n return (\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n};\r\n\r\nconst StyledGamePlay = styled.div``;\r\n\r\nconst StyledGamePlayGrid = styled.div`\r\n display: grid;\r\n grid-template-columns: 3fr minmax(19.5rem, 1fr);\r\n grid-template-rows: minmax(14.5rem, 25vh) 75vh;\r\n & > div:nth-child(1),\r\n & > div:nth-child(3) {\r\n padding: 1.5rem 1.5rem 0 1.5rem;\r\n }\r\n & > div:nth-child(2),\r\n & > div:nth-child(4) {\r\n border-left: 3px solid #bbb;\r\n padding: 1.5rem 2rem 0rem 2rem;\r\n }\r\n & > div:nth-child(1),\r\n & > div:nth-child(2) {\r\n border-bottom: 3px solid #bbb;\r\n padding: 1.5rem 2rem;\r\n }\r\n & > div:nth-child(4) {\r\n overflow-y: scroll;\r\n &::-webkit-scrollbar {\r\n width: 0.875rem;\r\n }\r\n &::-webkit-scrollbar-thumb {\r\n background-color: #999;\r\n border-radius: 0.375rem;\r\n &:hover {\r\n background-color: #555;\r\n }\r\n }\r\n &::-webkit-scrollbar-track {\r\n background-color: transparent;\r\n }\r\n }\r\n`;\r\n\r\nexport default GamePlay;\r\n","export default __webpack_public_path__ + \"static/media/background.9127295c.png\";","import styled from 'styled-components';\r\nimport GameList from './components/game-list/GameList';\r\nimport GamePlay from './components/game-play/GamePlay';\r\nimport background from './images/background.png';\r\nimport { BrowserRouter as Router, Route, Link } from 'react-router-dom';\r\n\r\nfunction App() {\r\n return (\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\nconst StyledBackground = styled.div`\r\n background-image: url(${(props) => props.src});\r\n background-size: cover;\r\n position: absolute;\r\n top: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n z-index: -1;\r\n &:before {\r\n background-color: rgba(0, 0, 0, 0.8);\r\n content: '';\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n z-index: -1;\r\n }\r\n`;\r\n\r\nexport default App;\r\n\r\nconst DATA_LIST_URL = 'http://52.78.184.142/games';\r\n","import React from \"react\";\r\nimport ReactDOM from \"react-dom\";\r\nimport \"./index.css\";\r\nimport \"normalize.css\";\r\nimport App from \"./App\";\r\n\r\nReactDOM.render(\r\n
\r\n \r\n ,\r\n document.getElementById(\"root\")\r\n);\r\n"],"sourceRoot":""}
\ No newline at end of file
diff --git a/fe/build/static/js/runtime-main.8ce5e419.js b/fe/build/static/js/runtime-main.8ce5e419.js
new file mode 100644
index 000000000..ddda25819
--- /dev/null
+++ b/fe/build/static/js/runtime-main.8ce5e419.js
@@ -0,0 +1,2 @@
+!function(e){function r(r){for(var n,l,a=r[0],f=r[1],i=r[2],c=0,s=[];c
-
- {/* */}
-
- >
+
+
+
+
+
+
+
+
+
);
}
-const StyleBackground = styled.div`
+const StyledBackground = styled.div`
background-image: url(${(props) => props.src});
background-size: cover;
position: absolute;
@@ -34,3 +39,5 @@ const StyleBackground = styled.div`
`;
export default App;
+
+const DATA_LIST_URL = 'http://52.78.184.142/games';
diff --git a/fe/src/components/game-list/GameList.jsx b/fe/src/components/game-list/GameList.jsx
index 57e0552e9..368dd2689 100644
--- a/fe/src/components/game-list/GameList.jsx
+++ b/fe/src/components/game-list/GameList.jsx
@@ -1,29 +1,38 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import styled from 'styled-components';
+import useFetch from '../../hooks/useFetch';
import GameListItem from './GameListItem';
+import { Link } from 'react-router-dom';
-const GameList = (props) => {
+const DATA_LIST_URL = 'http://52.78.184.142/games';
+
+const GameList = () => {
+ const game_id = localStorage.getItem('game_id');
+ if(game_id) {
+ window.location.href = 'games/' + game_id;
+ }
+ const { data: gameListData } = useFetch(DATA_LIST_URL, 'get');
const TITLE = 'BASEBALL GAME ONLINE';
const DESCRIPTION = '참가할 게임을 선택하세요!';
- const gameList = game_list.map(({ home, away, game_id }, idx) => (
-
+ const gameList = gameListData?.game_list.map(({ home, away, id }, idx) => (
+
));
-
- return (
-
- {TITLE}
- {DESCRIPTION}
- {gameList}
-
+
+ return game_id ? null : (
+
+ {TITLE}
+ {DESCRIPTION}
+ {gameList}
+
);
};
-const StyleGameList = styled.div`
+const StyledGameList = styled.div`
padding: 0 15vw;
margin-top: 2.5rem;
`;
-const StyleTitle = styled.div`
+const StyledTitle = styled.div`
margin-bottom: 5rem;
font-size: 3rem;
font-weight: 600;
@@ -31,14 +40,14 @@ const StyleTitle = styled.div`
text-align: center;
`;
-const StyleDescription = styled.div`
+const StyledDescription = styled.div`
margin-bottom: 2rem;
font-size: 2rem;
color: #fff;
text-align: center;
`;
-const StyleList = styled.div`
+const StyledList = styled.div`
max-width: 50rem;
margin: 0 auto;
padding-left: 1.0625rem;
@@ -67,37 +76,4 @@ const StyleList = styled.div`
}
`;
-const game_list = [
- {
- home: '베리베리 스트로베리',
- away: '엄마는 외계인',
- game_id: 0,
- },
- {
- home: 'twins',
- away: 'tigers',
- game_id: 1,
- },
- {
- home: 'rockets',
- away: 'dodgers',
- game_id: 2,
- },
- {
- home: 'rockets',
- away: 'dodgers',
- game_id: 2,
- },
- {
- home: 'rockets',
- away: 'dodgers',
- game_id: 2,
- },
- {
- home: 'rockets',
- away: 'dodgers',
- game_id: 2,
- },
-];
-
export default GameList;
diff --git a/fe/src/components/game-list/GameListItem.jsx b/fe/src/components/game-list/GameListItem.jsx
index bfc6f812b..4333e45ea 100644
--- a/fe/src/components/game-list/GameListItem.jsx
+++ b/fe/src/components/game-list/GameListItem.jsx
@@ -1,25 +1,35 @@
import React from 'react';
import styled from 'styled-components';
-const GameListItem = ({ home, away, game_id, idx }) => {
+import { Link } from 'react-router-dom';
+
+const GameListItem = ({ home, away, id, idx }) => {
+ const path = `games/${id}`;
+ const onClick = (e) => {
+ localStorage.setItem('select', e.target.textContent);
+ localStorage.setItem('teams', JSON.stringify({ home, away }));
+ };
return (
-
+
GAME {idx + 1}
-
{home}
+
+
{home}
+
vs
-
{away}
+
+
{away}
+
-
+
);
};
-const StyleGameItem = styled.div`
+const StyledGameItem = styled.div`
margin: 0 auto;
margin-bottom: 1.5rem;
padding: 1rem 0;
border-radius: 1rem;
- opacity: 0.95;
- background-color: #d2d2d2;
+ background-color: rgba(210, 210, 210, 0.95);
.title {
color: #ff4545;
text-align: center;
@@ -32,7 +42,7 @@ const StyleGameItem = styled.div`
text-align: center;
align-items: center;
padding: 1rem 0;
- .teams-name {
+ &-name {
font-size: 2rem;
font-weight: 600;
cursor: pointer;
@@ -40,12 +50,19 @@ const StyleGameItem = styled.div`
color: #ff4545;
}
}
- .teams-vs {
+ &-vs {
padding-bottom: 0.5rem;
font-size: 2.5rem;
font-weight: 600;
color: #777;
}
+ a {
+ text-decoration: none;
+ color: #000;
+ }
+ a:visited {
+ color: #000;
+ }
}
`;
diff --git a/fe/src/components/game-play/GamePlay.jsx b/fe/src/components/game-play/GamePlay.jsx
index 1fcb145c0..9ecf9d26a 100644
--- a/fe/src/components/game-play/GamePlay.jsx
+++ b/fe/src/components/game-play/GamePlay.jsx
@@ -7,62 +7,153 @@ import Log from './log/Log';
import PopUpScore from './popup/score/Score';
import PopUpRoster from './popup/roster/Roster';
import PopUp from '../ui/PopUp';
+import useScoreNBase from '../../hooks/useScoreNBase';
+import { fetchPUT } from '../../util/api.js';
+import useFetch from '../../hooks/useFetch';
-const ScoreContext = createContext();
+export const ScoreNBaseContext = createContext();
+export const GameIdContext = createContext();
const MemberListContext = createContext();
-const LogContext = createContext();
+
+const DATA_PLAYER_URL = 'http://52.78.184.142';
const memberListReducer = (state, action) => {
+ if (action.type === 'init') return action.value;
let next = 0;
- const newState = state.map((member, idx) => {
- let { safety, at_bat, out, state } = member;
- if(action.type === 'out') out++
- else safety++
- at_bat++
- if(action.id === member.id) {
- next = idx + 1 === member.length ? 0 : idx + 1;
- return {...member, safety, at_bat, out, state: !state};
+ const team = action.turn ? 'home' : 'away';
+ const newTeam = [...state[team]].map((member, idx, arr) => {
+ let { plate_appearance, atBat, outCount, status } = member;
+ if (member.status) {
+ if (action.type === 'out') outCount++;
+ else plate_appearance++;
+ atBat++;
+ next = idx + 1 === arr.length ? 0 : idx + 1;
+ return { ...member, plate_appearance, atBat, outCount, status: !status };
+ } else {
+ return { ...member };
}
});
- newState[next].state = true;
+ newTeam[next].status = true;
+ return { ...state, [team]: newTeam };
+};
+
+const logListReducer = (state, action) => {
+ let newState = [...state];
+ const target = newState.length > 0 && { ...newState[newState.length - 1] };
+ switch (action.type) {
+ case 'next':
+ newState.push({ ...action.value, index: action.index, history: [] });
+ break;
+ case 'strike':
+ case 'ball':
+ target.history = [...target.history, { ...action }];
+ newState[newState.length - 1] = target;
+ break;
+ case '4ball':
+ case 'safety':
+ case 'out':
+ target.history = [...target.history, { type: action.type, end: true }];
+ target.status = false;
+ newState[newState.length - 1] = target;
+ break;
+ case 'clear':
+ newState = [];
+ break;
+ }
return newState;
-}
+};
const GamePlay = ({ home, away, game_id }) => {
-
- const [turn, round, member_list] = [null, null, null];
+ const path = window.location.pathname;
+ const gameID = +path.slice(7);
+ const selectTeam = localStorage.getItem('select');
+ localStorage.setItem('game_id', gameID);
+ const GAME_PLAY_URL = DATA_PLAYER_URL + path;
+ const { data: gamePlayData } = useFetch(GAME_PLAY_URL, 'get');
const [inning, setInning] = useState({
- turn,
- round
+ turn: true,
+ round: 1,
+ });
+ const [logList, logListDispatch] = useReducer(logListReducer, []);
+ const { score, base, safetyDispatch } = useScoreNBase({
+ score: { home: [0], away: [] },
+ base: undefined,
});
- // const [score, setScore] = useState(null);
- // const [memberList, memberListDispatch] = useReducer(memberListReducer, member_list); //member_list fetch해서 받아올아이
- /*
- data,
+ const [memberList, memberListDispatch] = useReducer(memberListReducer, null);
+ const teamId = {
+ home: gamePlayData?.home?.teamId,
+ away: gamePlayData?.away?.teamId,
+ };
+ const teamName = {
+ home: gamePlayData?.home?.name,
+ away: gamePlayData?.away?.name,
+ };
+ const pitchers = {
+ home: gamePlayData?.home?.pitcherId,
+ away: gamePlayData?.away?.pitcherId,
+ };
+
+ useEffect(() => {
+ const memberListData = {
+ home: gamePlayData?.home?.member_list,
+ away: gamePlayData?.away?.member_list,
+ };
+ setInning({ turn: gamePlayData?.turn || true, round: gamePlayData?.round || 1 });
+ memberListDispatch({ type: 'init', value: memberListData });
+ }, [gamePlayData]);
+
+ useEffect(() => {
+ if (memberList && memberList.home) {
+ memberList[inning.turn ? 'home' : 'away'].forEach((member, index) => {
+ if (member.status) {
+ logListDispatch({ value: { ...member }, type: 'next', index: index + 1 });
+ }
+ });
+ }
+ }, [memberList]);
- */
- const score = { home: data.home.score, away: data.away.score };
- const memberList = { home: data.home.member_list, away: data.home.member_list };
- const pitchers = { home: data.home.pitcher, away: data.home.pitcher };
return (
-
-
-
-
- {/* {/* */}
-
- {/* */}
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
-const StyleGamePlay = styled.div``;
+const StyledGamePlay = styled.div``;
-const StyleGamePlayGrid = styled.div`
+const StyledGamePlayGrid = styled.div`
display: grid;
grid-template-columns: 3fr minmax(19.5rem, 1fr);
grid-template-rows: minmax(14.5rem, 25vh) 75vh;
@@ -98,188 +189,4 @@ const StyleGamePlayGrid = styled.div`
}
`;
-//team 이름 props에서 받아온다고 가정
-const teamName = {
- home: 'captain',
- away: 'marvel',
- game_id: 0,
-};
-
-const data = {
- round: 4, // 게임 시작시는 round,turn X
- turn: true, //(false : 말)
- home: {
- member_list: [
- {
- name: '김광진',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 0, // 번호
- state: false,
- },
- {
- name: '추신수',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 1, // 번호
- state: false,
- },
- {
- name: '이대호',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 2, // 번호
- state: false,
- },
- {
- name: '마르코',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 3, // 번호
- state: true,
- },
- {
- name: '스타브',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 4, // 번호
- state: false,
- },
- {
- name: '카일',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 5, // 번호
- state: false,
- },
- {
- name: '제이슨',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 6, // 번호
- state: false,
- },
- {
- name: '크롱',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 7, // 번호
- state: false,
- },
- {
- name: '호눅스',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 8, // 번호
- state: false,
- },
- {
- name: '제이케이',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 9, // 번호
- state: false,
- },
- ],
- pitcher: 3,
- score: 0, // 재접속 시에도 유지할 수 있도록 팀 별 점수를 받을 수 있어야 합니다!
- },
- away: {
- member_list: [
- {
- name: '고양이',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 10, // 번호
- state: false,
- },
- {
- name: '강아지',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 11, // 번호
- state: false,
- },
- {
- name: '코끼리',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 12, // 번호
- state: false,
- },
- {
- name: '얼룩말',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 13, // 번호
- state: false,
- },
- {
- name: '코뿔소',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 14, // 번호
- state: false,
- },
- {
- name: '수달',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 15, // 번호
- state: false,
- },
- {
- name: '프레리독',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 16, // 번호
- state: true,
- },
- {
- name: '하이에나',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 17, // 번호
- state: false,
- },
- {
- name: '기린',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 18, // 번호
- state: false,
- },
- {
- name: '물개',
- at_bat: 0, // 타석
- safety: 0, // 안타
- out: 0, // 아웃
- id: 19, // 번호
- state: false,
- },
- ],
- pitcher: 19,
- score: 2, // 재접속 시에도 유지할 수 있도록 팀 별 점수를 받을 수 있어야 합니다!
- },
-};
-
export default GamePlay;
diff --git a/fe/src/components/game-play/board/BallCount.jsx b/fe/src/components/game-play/board/BallCount.jsx
index e15f89dda..2c31fce8f 100644
--- a/fe/src/components/game-play/board/BallCount.jsx
+++ b/fe/src/components/game-play/board/BallCount.jsx
@@ -3,35 +3,35 @@ import styled from 'styled-components';
const BallCount = ({ ballCount: { strike, ball, out } }) => {
return (
-
-
+
+
S
{createBallCountTag(strike)}
-
+
B
{createBallCountTag(ball)}
-
+
O
{createBallCountTag(out)}
-
+
)
}
function createBallCountTag(count) {
const result = [];
for(let i = 0; i < count; i++) {
- result.push(
);
+ result.push(
);
}
return result;
}
-const StyleBallCount = styled.div`
+const StyledBallCount = styled.div`
color:#fff;
font-weight:600;
- .couters {
+ .counters {
display: flex;
margin-bottom:0.5rem;
& > div {
diff --git a/fe/src/components/game-play/board/Board.jsx b/fe/src/components/game-play/board/Board.jsx
index 72764c3c5..a7c4cf270 100644
--- a/fe/src/components/game-play/board/Board.jsx
+++ b/fe/src/components/game-play/board/Board.jsx
@@ -1,15 +1,145 @@
import styled from 'styled-components';
+import { useState, useReducer, useContext, useEffect } from 'react';
import BallCount from './BallCount';
import Inning from './Inning';
import Screen from './Screen';
+import { GameIdContext, ScoreNBaseContext } from '../GamePlay';
+import { fetchPUT, fetchPOST } from '../../../util/api.js';
+import useFetch from '../../../hooks/useFetch';
-const Board = (props) => (
-
-
-
-
-
-);
+const URL = 'http://52.78.184.142';
+
+const ballCountReducer = (state, action) => {
+ let newState = { ...state };
+ switch (action.type) {
+ case 'strike':
+ newState.strike++;
+ break;
+ case 'ball':
+ newState.ball++;
+ break;
+ case 'safety':
+ newState = { strike: 0, ball: 0, out: newState.out };
+ break;
+ case 'clear':
+ newState = { strike: 0, ball: 0, out: 0 };
+ break;
+ case 'out':
+ newState.ball = 0;
+ newState.strike = 0;
+ newState.out++;
+ break;
+ default:
+ throw Error('잘못된 ball-count reducer');
+ }
+ return newState;
+};
+
+const Board = ({
+ memberListDispatch,
+ inning,
+ setInning,
+ logListDispatch,
+ game_id,
+ teamName,
+ teamId,
+ selectTeam,
+ memberList,
+}) => {
+ const [ballCount, ballCountDispatch] = useReducer(ballCountReducer, {
+ strike: 0,
+ ball: 0,
+ out: 0,
+ });
+ const { score, base, safetyDispatch } = useContext(ScoreNBaseContext);
+ const { gameID } = useContext(GameIdContext);
+
+ const getPlayerId = () => memberList[inning.turn ? 'home' : 'away'].find((v) => v.status)?.id;
+
+ const currentRoundScore = {
+ game_id: gameID,
+ home: score.home[inning.round - 1] + inning.turn,
+ away: !inning.turn ? score.away[inning.round - 1] + 1 : null,
+ round: inning.round,
+ };
+
+ const handleStrike = () => {
+ if (ballCount.strike === 2) {
+ handleOut();
+ } else {
+ ballCountDispatch({ type: 'strike' });
+ logListDispatch({ type: 'strike', ...ballCount, strike: ballCount.strike + 1 });
+ }
+ };
+ const handleBall = () => {
+ if (ballCount.ball === 3) {
+ if (base[3]) {
+ //4번 API {"game_id": 7,"home": 1,"away": 2, "round": 3} 모든정보 컨텍스트로
+ fetchPUT(`${URL}/games/${gameID}`, currentRoundScore);
+ }
+ const playerId = getPlayerId();
+ fetchPUT(`${URL}/players/${playerId}`, { game_id: gameID, at_bat: true });
+ ballCountDispatch({ type: 'safety' });
+ logListDispatch({ type: '4ball', end: true });
+ memberListDispatch({ type: 'safety', turn: inning.turn, game_id });
+ } else {
+ ballCountDispatch({ type: 'ball' });
+ logListDispatch({ type: 'ball', ...ballCount, ball: ballCount.ball + 1 });
+ }
+ };
+ const handleOut = () => {
+ const playerId = getPlayerId();
+ if (ballCount.out === 2) {
+ //7번 API { "game_id": 6, "team_id":1, "round":3, "at_bat": 3 } ??타자를 번호를 보내는거네???
+ // 여기에 공수교대
+ //
+ fetchPOST(`${URL}/games/${gameID}`, {
+ game_id: gameID,
+ round: inning.round,
+ player_id: playerId,
+ team_id: inning.turn ? teamId.home : teamId.away,
+ });
+ ballCountDispatch({ type: 'clear' });
+ if (inning.turn) setInning({ ...inning, turn: !inning.turn });
+ else setInning({ ...inning, round: inning.round + 1, turn: !inning.turn });
+ safetyDispatch({ type: 'clear', turn: !inning.turn });
+ logListDispatch({ type: 'clear' });
+ } else {
+ fetchPUT(`${URL}/players/${playerId}`, { game_id: gameID, at_bat: false });
+ logListDispatch({ type: 'out', end: true });
+ ballCountDispatch({ type: 'out' });
+ }
+ // 멤버 아웃 1, 타석 1 증가
+ memberListDispatch({ type: 'out', turn: inning.turn, game_id });
+ };
+ const handleSafety = () => {
+ if (base[3]) {
+ //4번 API {"game_id": 7,"home": 1,"away": 2, "round": 3} 모든정보 컨텍스트로
+ fetchPUT(`${URL}/games/${gameID}`, currentRoundScore);
+ }
+ const playerId = getPlayerId();
+ fetchPUT(`${URL}/players/${playerId}`, { game_id: gameID, at_bat: true });
+ ballCountDispatch({ type: 'safety' });
+ // 멤버 안타 1, 타석 1 증가
+ memberListDispatch({ type: 'safety', turn: inning.turn, game_id });
+ logListDispatch({ type: 'safety', end: true });
+ };
+
+ // useEffect(() => {
+ // return () => fetchPUT(inning);
+ // }, [inning]);
+
+ return (
+
+
+
+
+
+ );
+};
const StyledBoard = styled.div`
display: grid;
@@ -19,15 +149,3 @@ const StyledBoard = styled.div`
export default Board;
const isHome = true;
-
-const inning = {
- turn: true,
- round: 4
-}
-
-const ballCount = {
- strike: 2,
- ball: 3,
- out: 2
-};
-
diff --git a/fe/src/components/game-play/board/Inning.jsx b/fe/src/components/game-play/board/Inning.jsx
index bba297793..f08bc62cf 100644
--- a/fe/src/components/game-play/board/Inning.jsx
+++ b/fe/src/components/game-play/board/Inning.jsx
@@ -1,10 +1,10 @@
import React from 'react';
import styled from 'styled-components';
const Inning = ({ inning: { turn, round }, isHome }) => {
- return
{round}회{turn ? '초' : '말'} {(isHome && turn) || (!isHome && !turn) ? '공격' : '수비'}
+ return
{round}회{turn ? '초' : '말'} {(isHome && turn) || (!isHome && !turn) ? '공격' : '수비'}
}
-const StyleInning = styled.div`
+const StyledInning = styled.div`
font-size: 1.5rem;
font-weight: 600;
color: #fff;
diff --git a/fe/src/components/game-play/board/Screen.jsx b/fe/src/components/game-play/board/Screen.jsx
index c84f1476c..f5e8ce6a7 100644
--- a/fe/src/components/game-play/board/Screen.jsx
+++ b/fe/src/components/game-play/board/Screen.jsx
@@ -1,27 +1,137 @@
-import React from 'react';
+import React, { useState, useContext, useEffect, useRef } from 'react';
import styled from 'styled-components';
+import useScoreNBase from '../../../hooks/useScoreNBase';
+import { ScoreNBaseContext } from '../GamePlay';
+const Screen = ({
+ handleStrike,
+ handleBall,
+ handleSafety,
+ ballCount,
+ turn,
+ teamName,
+ selectTeam,
+}) => {
+ const { base, safetyDispatch } = useContext(ScoreNBaseContext);
+ const [isTransition, setIsTransition] = useState(false);
+ const [runFirstBase, setRunFirstBase] = useState(false);
+ const [currentPower, setCurrentPower] = useState(0);
+
+ const playerRunType = [
+ 'translateX(19.6rem)',
+ 'translateY(-19.6rem)',
+ 'translateX(-19.6rem)',
+ 'translateY(19.6rem)',
+ ];
+
+ const handleTransitionEnd = async () => {
+ if (currentPower === 0) return;
+ setRunFirstBase(false);
+ setCurrentPower(currentPower - 1);
+ safetyDispatch({ type: 'onBase', turn, runFirstBase });
+ setIsTransition(false);
+ };
+
+ const handlePitchClick = () => {
+ const randomNum = Math.ceil(Math.random() * 100);
+ if (randomNum <= 55) {
+ //스트라이크
+ handleStrike();
+ } else if (randomNum <= 80) {
+ //볼
+ if (ballCount.ball === 3) {
+ setCurrentPower(1);
+ setIsTransition(true);
+ setRunFirstBase(true);
+ }
+ handleBall();
+ } else {
+ //안타
+ setIsTransition(true);
+ setRunFirstBase(true);
+ handleSafety();
+ if (randomNum <= 100) {
+ //1루타
+ setCurrentPower(1);
+ } else if (randomNum <= 6) {
+ //2루타
+ setCurrentPower(2);
+ } else if (randomNum <= 3) {
+ //3루타
+ setCurrentPower(3);
+ } else {
+ //홈런
+ setCurrentPower(4);
+ }
+ }
+ };
+
+ const baseList = Object.entries(base).map(([baseNum, status], idx) => (
+
+ ));
+
+ const isPitch = (turn && teamName.home != selectTeam) || (!turn && teamName.away != selectTeam);
+
+ const savedCallback = useRef();
+
+ // function callback() {
+ // handlePitchClick();
+ // }
+
+ // useEffect(() => {
+ // savedCallback.current = callback;
+ // });
+
+ // useEffect(() => {
+ // function tick() {
+ // savedCallback.current();
+ // }
+ // if (!isPitch) {
+ // let id = setInterval(tick, 1000);
+ // return () => clearInterval(id);
+ // } else {
+ // return null;
+ // }
+ // }, []);
+
+ // useEffect(() => {
+ // let interval = setInterval(() => handlePitchClick(), 1000);
+ // clearInterval(interval);
+ // if(!isPitch) {
+ // interval = setInterval(() => handlePitchClick(), 1000);
+ // } else {
+ // clearInterval(interval);
+ // }
+ // return () => clearInterval(interval);
+ // }, [turn])
-const Screen = (props) => {
return (
-
- PITCH
-
-
+
+ PITCH
+
+
-
-
-
-
-
+ {baseList}
+
+
);
-}
+};
-const StyleScreen = styled.div`
- position:relative;
+const StyledScreen = styled.div`
+ position: relative;
align-self: center;
`;
-const StylePitch = styled.button`
+const StyledPitch = styled.button`
position: absolute;
top: calc(50% - 1.5rem);
left: calc(50% - 5rem);
@@ -39,27 +149,45 @@ const StylePitch = styled.button`
z-index: 1;
&:hover {
background-color: #fff;
- color:#000;
+ color: #000;
}
-`
+`;
-const StyleGround = styled.div`
+const StyledGround = styled.div`
+ position: relative;
border: 3px solid #fff;
width: 20rem;
height: 20rem;
margin: auto;
- position: relative;
transform: rotate(-45deg);
+ .runner {
+ width: 1.5rem;
+ height: 1.5rem;
+ background-color: red;
+ transition: ${({ isTransition }) => (isTransition ? 'all 0.5s' : '')};
+ }
+
+ .home {
+ z-index: 4;
+ }
.base {
position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: center;
background-color: #fff;
width: 2rem;
height: 2rem;
&:nth-child(1) {
bottom: -1rem;
left: -1rem;
+ .runner {
+ background-color: ${({ runFirstBase }) => (runFirstBase ? 'red' : 'rgba(0,0,0,0)')};
+ transform: ${({ playerRunType, currentPower, runFirstBase }) =>
+ runFirstBase && currentPower > 0 ? `${playerRunType[0]} rotate(45deg)` : `rotate(45deg)`};
+ }
&:before {
- content: "";
+ content: '';
background-color: #fff;
width: 2.8284712rem;
height: 4rem;
@@ -70,18 +198,36 @@ const StyleGround = styled.div`
}
}
&:nth-child(2) {
+ z-index: 3;
bottom: -1rem;
right: -1rem;
+ .runner {
+ background-color: ${({ base }) => (base[1] ? 'red' : 'rgba(0,0,0,0)')};
+ transform: ${({ playerRunType, currentPower }) =>
+ currentPower > 0 ? `${playerRunType[1]} rotate(45deg)` : `rotate(45deg)`};
+ }
}
&:nth-child(3) {
+ z-index: 2;
top: -1rem;
right: -1rem;
+ .runner {
+ background-color: ${({ base }) => (base[2] ? 'red' : 'rgba(0,0,0,0)')};
+ transform: ${({ playerRunType, currentPower }) =>
+ currentPower > 0 ? `${playerRunType[2]} rotate(45deg)` : `rotate(45deg)`};
+ }
}
&:nth-child(4) {
- top:-1rem;;
- left:-1rem;
+ z-index: 1;
+ top: -1rem;
+ left: -1rem;
+ .runner {
+ background-color: ${({ base }) => (base[3] ? 'red' : 'rgba(0,0,0,0)')};
+ transform: ${({ playerRunType, currentPower }) =>
+ currentPower > 0 ? `${playerRunType[3]} rotate(45deg)` : `rotate(45deg)`};
+ }
}
}
-`
+`;
-export default Screen;
\ No newline at end of file
+export default Screen;
diff --git a/fe/src/components/game-play/log/Log.jsx b/fe/src/components/game-play/log/Log.jsx
index 925f417f5..3891328be 100644
--- a/fe/src/components/game-play/log/Log.jsx
+++ b/fe/src/components/game-play/log/Log.jsx
@@ -1,45 +1,48 @@
import styled from 'styled-components';
-const Log = (props) => {
- const team = props.data.home;
- const { member_list } = team;
- const log_list = member_list.map((member, i) => (
-
-
{i + 1}번 타자 {member.name}
-
-
-
-
-
+const TYPE = {
+ strike: '스트라이크',
+ ball: '볼',
+ safety: '안타',
+ '4ball': '볼넷',
+ out: '아웃',
+};
+
+const Log = ({ logList }) => {
+ const logs = logList.map((member, i) => (
+
+
+ {member.index}번 타자 {member.name}
+
+
+ {member.history.map((v, i) =>
+ v.end ? (
+
+ ) : (
+
+
{i + 1}
+
{TYPE[v.type]}
+
+ S{v.strike} B{v.ball}
+
+
+ )
+ )}
));
return (
-
-
- {log_list}
-
-
+
+ {logs}
+
);
};
-const StyleLog = styled.div`
+const StyledLog = styled.div`
.logs {
display: flex;
flex-flow: column-reverse;
@@ -95,7 +98,6 @@ const StyleLog = styled.div`
color: #ff4545;
}
}
-
`;
-export default Log;
\ No newline at end of file
+export default Log;
diff --git a/fe/src/components/game-play/player/Player.jsx b/fe/src/components/game-play/player/Player.jsx
index 4e5a2a663..6215f9937 100644
--- a/fe/src/components/game-play/player/Player.jsx
+++ b/fe/src/components/game-play/player/Player.jsx
@@ -3,50 +3,43 @@ import styled from 'styled-components';
const Player = ({ memberList, turn, pitchers }) => {
const HOME = 'home';
const AWAY = 'away';
-
- const getMember = (id, members) => {
- for (const member of members) {
- if (member.id === id) return member;
+ const getPlayer = () => {
+ if (!memberList || !memberList[HOME] || !memberList[AWAY]) return;
+ const result = {};
+ const batterTeam = turn ? HOME : AWAY;
+ const pitcherTeam = turn ? AWAY : HOME;
+ for (const member of memberList[batterTeam]) {
+ if (member.status) result.batter = member;
}
- return null;
- };
-
- const getBatter = (members) => {
- for (const member of members) {
- if (member.state) return member;
+ for (const member of memberList[pitcherTeam]) {
+ if (member.id === pitchers[pitcherTeam]) result.pitcher = member;
}
- return null;
+ return result;
};
-
- const pitcher = turn
- ? getMember(pitchers[HOME], memberList[HOME])
- : getMember(pitchers[AWAY], memberList[AWAY]);
-
- const batter = turn ? getBatter(memberList[HOME]) : getBatter(memberList[AWAY]);
-
+ const player = getPlayer();
return (
-
+
투수
-
{pitcher.name}
-
#{pitcher.id}
+
{player?.pitcher.name}
+
#{player?.pitcher.id}
타자
-
{batter.name}
+
{player?.batter.name}
- {batter.at_bat}타석 {batter.safety}안타
+ {player?.batter.atBat || 0}타석 {player?.batter.plate_appearance || 0}안타
-
+
);
};
-const StylePlayer = styled.div`
+const StyledPlayer = styled.div`
height: 100%;
padding-bottom: 1.5rem;
display: flex;
diff --git a/fe/src/components/game-play/popup/roster/Roster.jsx b/fe/src/components/game-play/popup/roster/Roster.jsx
index 7d8315458..d343efa70 100644
--- a/fe/src/components/game-play/popup/roster/Roster.jsx
+++ b/fe/src/components/game-play/popup/roster/Roster.jsx
@@ -1,17 +1,16 @@
import styled from 'styled-components';
import TeamRoster from './TeamRoster';
-const Roster = () => {
- // team_name 필요
+const Roster = ({ memberList, teamName, selectTeam }) => {
return (
-
-
-
-
+
+
+
+
);
}
// 90vw는 돼야하는데 그러면 이상함...;
-const StyleRoster = styled.div`
+const StyledRoster = styled.div`
margin: 0 auto;
max-width: 60vw;
margin-top: 5rem;
@@ -27,127 +26,4 @@ const StyleRoster = styled.div`
}
`;
-const game_roster = {
- // 평균은 fe에서
- 'home': {
- 'member_list' : [
- {
- 'name': '김광진',
- 'at_bat': 7, // 타석
- 'safety': 3, // 안타
- 'out': 4, // 아웃
- },
- {
- 'name': '추신수',
- 'at_bat': 7, // 타석
- 'safety': 5, // 안타
- 'out': 2, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 11, // 타석
- 'safety': 4, // 안타
- 'out': 7, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 10, // 타석
- 'safety': 2, // 안타
- 'out': 8, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 3, // 타석
- 'safety': 2, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 4, // 타석
- 'safety': 1, // 안타
- 'out': 3, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 6, // 타석
- 'safety': 2, // 안타
- 'out': 4, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 5, // 타석
- 'safety': 4, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 7, // 타석
- 'safety': 7, // 안타
- 'out': 0, // 아웃
- },
- ],
- 'player_name' : '추신수'
- },
- 'away':{
- 'member_list' : [
- {
- 'name': '김광진',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '이대호',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- {
- 'name': '김광진',
- 'at_bat': 2, // 타석
- 'safety': 1, // 안타
- 'out': 1, // 아웃
- },
- ],
- 'player_name' : '이대호'
- }
-}
-
-
export default Roster;
diff --git a/fe/src/components/game-play/popup/roster/RosterPlayer.jsx b/fe/src/components/game-play/popup/roster/RosterPlayer.jsx
new file mode 100644
index 000000000..e3fbc2de4
--- /dev/null
+++ b/fe/src/components/game-play/popup/roster/RosterPlayer.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+// const { atBat, plate_appearance, outCountCount } = member;
+
+const RosterPlayer = ({ name, atBat, plate_appearance, outCount, player_name }) => {
+
+ const getAverage = (atBat, plate_appearance) => (plate_appearance / atBat) ? (plate_appearance / atBat).toFixed(3) : 0;
+ const class_name = 'roster--member' + (name === player_name ? ' active' : '');
+ return (
+
+ {name}
+ {atBat}
+ {plate_appearance}
+ {outCount}
+ {getAverage(atBat, plate_appearance)}
+
+ );
+};
+
+export default RosterPlayer;
\ No newline at end of file
diff --git a/fe/src/components/game-play/popup/roster/TeamRoster.jsx b/fe/src/components/game-play/popup/roster/TeamRoster.jsx
index 30cf3ec1d..e406e3357 100644
--- a/fe/src/components/game-play/popup/roster/TeamRoster.jsx
+++ b/fe/src/components/game-play/popup/roster/TeamRoster.jsx
@@ -1,32 +1,24 @@
import styled from 'styled-components';
+import RosterPlayer from './RosterPlayer';
-const TeamRoster = ({ member_list, player_name, team_name = '베리베리 스트로베리', player = false }) => {
+const TeamRoster = ({ memberList, teamName, selectTeam }) => {
+ const player_name = 'abc';
const HEADS = ['타자', '타석', '안타', '아웃', '평균'];
const TOTALTEXT = 'Totals';
const heads = HEADS.map((head, idx) =>
{head}
);
- const getAverage = (at_bat, safety) => (safety / at_bat).toFixed(3);
let [totalAtBat, totalSafety, totalOut] = [0, 0, 0];
- const members = member_list.map(({ name, at_bat, safety, out }, idx) => {
- totalAtBat += at_bat;
- totalSafety += safety;
- totalOut += out;
- const class_name = 'roster--member' + (name === player_name ? ' active' : '');
- return (
-
- {name}
- {at_bat}
- {safety}
- {out}
- {getAverage(at_bat, safety)}
-
- );
- }
- );
+ const members = memberList.map((member, idx) => {
+ const { atBat, plate_appearance, outCount } = member;
+ totalAtBat += atBat;
+ totalSafety += plate_appearance;
+ totalOut += outCount;
+ return
+ });
return (
-
- {team_name} {player &&
Player
}
+
+ {teamName} {teamName === selectTeam &&
Player
}
-
{heads}
@@ -40,11 +32,11 @@ const TeamRoster = ({ member_list, player_name, team_name = '베리베리 스트
-
+
);
}
-const StyleTeamRoster = styled.div`
+const StyledTeamRoster = styled.div`
.title {
font-size: 1.5rem;
font-weight: 600;
diff --git a/fe/src/components/game-play/popup/score/Score.jsx b/fe/src/components/game-play/popup/score/Score.jsx
index 0d4e6267d..ffa1dcaa7 100644
--- a/fe/src/components/game-play/popup/score/Score.jsx
+++ b/fe/src/components/game-play/popup/score/Score.jsx
@@ -1,21 +1,27 @@
import React from 'react';
import styled from 'styled-components';
+import useFetch from '../../../../hooks/useFetch';
import ScoreList from './ScoreList';
-const Score = (props) => {
+const Score = ({ score, teamName, gameID }) => {
+ const SCORE_URL = `http://52.78.184.142/games/${gameID}/detail-score`;
//props로 받아올 아이들 1. team 2. player(home or away)
const ROUND = new Array(12).fill().map((_, idx) => idx + 1);
+ // const { data: score } = useFetch(SCORE_URL, 'get');
return (
-
-
-
-
-
+ score &&
+ score.home && (
+
+
+
+
+
+ )
);
};
-const StyleScore = styled.div`
+const StyledScore = styled.div`
width: 70rem;
min-width: 63rem;
display: flex;
diff --git a/fe/src/components/game-play/popup/score/ScoreList.jsx b/fe/src/components/game-play/popup/score/ScoreList.jsx
index 679762fd3..b32443e7d 100644
--- a/fe/src/components/game-play/popup/score/ScoreList.jsx
+++ b/fe/src/components/game-play/popup/score/ScoreList.jsx
@@ -3,45 +3,44 @@ import styled from 'styled-components';
import { IoBaseballOutline } from 'react-icons/io5';
const ScoreList = ({ team, dataType, data, isPlayer }) => {
- const isRound = dataType === 'round';
-
+ const isInRound = dataType === 'round';
const scoreList = new Array(12).fill('');
data.forEach((v, idx) => (scoreList[idx] = v));
- isRound ? scoreList.push('R') : scoreList.push(data.reduce((acc, cur) => acc + cur));
+ isInRound ? scoreList.push('R') : scoreList.push(data.reduce((acc, cur) => acc + cur, 0));
const scoreDivList = scoreList.map((score, idx, arr) => (
{score}
));
return (
-
+
{isPlayer &&
}
-
{team ? team : ''}
+
{team || ''}
{isPlayer &&
player
}
{scoreDivList}
-
+
);
};
-const StyleScoreList = styled.div`
+const StyledScoreList = styled.div`
display: flex;
.team-wrapper {
margin-right: 2rem;
.team {
position: relative;
- width: 5rem;
- font-size: 1.5rem;
+ width: 8rem;
+ font-size: 1.1rem;
font-weight: 700;
.baseball {
position: absolute;
@@ -61,8 +60,8 @@ const StyleScoreList = styled.div`
}
.score-list {
- border-bottom: ${({ isRound }) => isRound && '3px solid #fff'};
- margin-bottom: ${({ isRound }) => (isRound ? '1rem' : '0.3rem')};
+ border-bottom: ${({ isInRound }) => isInRound && '3px solid #fff'};
+ margin-bottom: ${({ isInRound }) => (isInRound ? '1rem' : '0.3rem')};
}
.score {
display: flex;
@@ -70,7 +69,7 @@ const StyleScoreList = styled.div`
font-size: 1.5rem;
font-weight: 700;
width: 3rem;
- height: ${({ isRound }) => (isRound ? '2.7rem' : '3rem')};
+ height: ${({ isInRound }) => (isInRound ? '2.7rem' : '3rem')};
margin-right: 1rem;
}
.last-score {
diff --git a/fe/src/components/game-play/score/Score.jsx b/fe/src/components/game-play/score/Score.jsx
index d3043fc5b..ef8f45b0d 100644
--- a/fe/src/components/game-play/score/Score.jsx
+++ b/fe/src/components/game-play/score/Score.jsx
@@ -1,36 +1,42 @@
+import { useContext } from 'react';
import styled from 'styled-components';
+import { ScoreNBaseContext } from '../GamePlay';
-const Score = ({ teamName, score, turn }) => {
+const Score = ({ teamName, selectTeam }) => {
+ const { score } = useContext(ScoreNBaseContext);
const TITLE = 'BASEBALL GAME ONLINE';
const TURN = 'Player';
+ const homeScore = score.home.reduce((acc, cur) => acc + cur, 0);
+ const awayScore = score.away.reduce((acc, cur) => acc + cur, 0);
+
return (
-
+
{TITLE}
{teamName.home}
- {turn &&
{TURN}
}
+ {teamName.home == selectTeam &&
{TURN}
}
-
{score.home}
+
{homeScore}
vs
-
{score.away}
+
{awayScore}
{teamName.away}
- {!turn &&
{TURN}
}
+ {teamName.away == selectTeam &&
{TURN}
}
-
+
);
};
-const StyleScore = styled.div`
+const StyledScore = styled.div`
.title {
- font-size: 3rem;
+ font-size: 2.5rem;
font-weight: 600;
color: #fff;
text-align: center;
@@ -46,15 +52,15 @@ const StyleScore = styled.div`
.home,
.away {
display: flex;
- justify-content: center;
align-items: center;
- font-size: 5rem;
+ font-size: 3rem;
}
.teams-vs {
font-size: 4rem;
color: #777;
}
.teams-name {
+ font-size: 3rem;
position: relative;
}
.turn {
@@ -66,13 +72,15 @@ const StyleScore = styled.div`
}
}
.home {
+ justify-content: flex-end;
.teams-score {
- margin-left: 3rem;
+ margin: 0 3rem;
}
}
.away {
+ justify-content: flex-start;
.teams-score {
- margin-right: 3rem;
+ margin: 0 3rem;
}
}
`;
diff --git a/fe/src/components/ui/PopUp.jsx b/fe/src/components/ui/PopUp.jsx
index bbee37ffa..01765bc6a 100644
--- a/fe/src/components/ui/PopUp.jsx
+++ b/fe/src/components/ui/PopUp.jsx
@@ -1,14 +1,14 @@
import React, {useState} from 'react';
import styled, { keyframes } from 'styled-components';
-const PopUp = ({ children, position }) => {
+const PopUp = ({ children, position, emptyText = '' }) => {
const [active, setActive] = useState(false);
- const handleClick = ({target}) => !target.closest('.childrenWrapper') && setActive(false);
- return active ?
+ const handleClick = ({target}) => (target.className === 'childrenWrapper' || !target.closest('.childrenWrapper')) && setActive(false);
+ return active ?
{children}
- :
setActive(true)} />;
+ : setActive(true)}>{emptyText};
};
const toBottom = keyframes`
@@ -29,12 +29,13 @@ const toTop = keyframes`
}
`;
-const StylePopUp = styled.div`
+const StyledPopUp = styled.div`
position: absolute;
top: 0;
width: 100vw;
height: 100vh;
z-index: 2;
+ text-align: center;
&:before {
background-color: rgba(0, 0, 0, 0.9);
content: '';
@@ -50,22 +51,33 @@ const StylePopUp = styled.div`
}
`;
-const StyleEmpty = styled.div`
- width:100vw;
- height:5rem;
+const StyledEmpty = styled.div`
+ width: 10vw;
+ height: 3rem;
position: absolute;
+ right: 30vw;
+ cursor: pointer;
+ color: #eee;
+ border-radius: ${props => props.position === 'top' ? '0 0 1rem 1rem' : '1rem 1rem 0 0' };
+ background-color: rgba(0, 0, 0, 0.5);
top: ${props => props.position === 'top' && '0px'};
bottom: ${props => props.position === 'bottom' && '0px'};
+ text-align: center;
+ font-size: 1.25rem;
+ padding: 0.5rem 0;
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.8);
+ }
`
export default PopUp;
// const PopUp = ({ children }) => {
-// return {children};
+// return {children};
// };
-// const StylePopUp = styled.div`
+// const StyledPopUp = styled.div`
// position: fixed;
// width: 100vw;
// height: 100vh;
diff --git a/fe/src/hooks/useFetch.js b/fe/src/hooks/useFetch.js
new file mode 100644
index 000000000..9fe7395a3
--- /dev/null
+++ b/fe/src/hooks/useFetch.js
@@ -0,0 +1,47 @@
+import { useEffect, useState } from 'react';
+
+const useFetch = (url, method, value = '') => {
+ const [data, setData] = useState(null);
+
+ const fetchData = async (url, method, value) => {
+ try {
+ if (method === 'get') {
+ const res = await fetch(url);
+ const data = await res.json();
+ setData(data);
+ } else if (method === 'post') {
+ console.log('post', value);
+ const res = await fetch(url, {
+ method: 'post',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(value),
+ });
+ const data = await res.json();
+ console.log(res, data);
+ } else if (method === 'put') {
+ console.log('put', value);
+ const res = await fetch(url, {
+ method: 'put',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(value),
+ });
+ const data = await res.json();
+ console.log(res, data);
+ }
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ useEffect(() => {
+ fetchData(url, method, value);
+ }, []);
+
+ return { data };
+};
+
+export default useFetch;
diff --git a/fe/src/hooks/useScoreNBase.js b/fe/src/hooks/useScoreNBase.js
new file mode 100644
index 000000000..ab82a98e5
--- /dev/null
+++ b/fe/src/hooks/useScoreNBase.js
@@ -0,0 +1,51 @@
+import { useEffect, useState } from 'react';
+
+/*
+한루 한루 주자가 뛰는 것을 애니메이션 넣어주기 위해서 한번에 계산하기 보다는
+for문으로 한번씩 나눠서 실행
+*/
+
+const useScoreNBase = ({
+ score: initialScore = { home: [0], away: [] },
+ base: initialBase = { 1: false, 2: false, 3: false },
+}) => {
+ const [score, setScore] = useState(initialScore);
+ const [base, setBase] = useState(initialBase);
+
+ const getNewScore = (score, team) => {
+ if (!base[3]) return score;
+ const newScore = { ...score };
+ newScore[team][newScore[team].length - 1] += 1;
+ return newScore;
+ };
+ const getNewBase = (base, runFirstBase) => {
+ const newBase = { ...base };
+ const baseStatus = Object.values(newBase);
+ const newBaseStatus = runFirstBase
+ ? [true, ...baseStatus.slice(0, -1)]
+ : [false, ...baseStatus.slice(0, -1)];
+ newBaseStatus.forEach((status, idx) => (newBase[idx + 1] = status));
+ return newBase;
+ };
+ const safetyDispatch = ({ type, turn, runFirstBase }) => {
+ const team = turn ? 'home' : 'away';
+ switch (type) {
+ case 'clear':
+ const newScore = { ...score };
+ newScore[team].push(0);
+ setScore(newScore);
+ setBase({ 1: false, 2: false, 3: false });
+ return;
+ case 'onBase':
+ setBase(getNewBase(base, runFirstBase));
+ setScore(getNewScore(score, team));
+ return;
+ default:
+ throw Error('잘못된 safetyDispatch 타입입니다.');
+ }
+ };
+
+ return { score, base, safetyDispatch };
+};
+
+export default useScoreNBase;
diff --git a/fe/src/index.css b/fe/src/index.css
index 740956d38..e2f8999a1 100644
--- a/fe/src/index.css
+++ b/fe/src/index.css
@@ -11,8 +11,3 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/fe/src/util/api.js b/fe/src/util/api.js
new file mode 100644
index 000000000..18a1973d9
--- /dev/null
+++ b/fe/src/util/api.js
@@ -0,0 +1,30 @@
+const fetchPUT = async (url, data) => {
+ const result = await fetch(url, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ });
+ console.log(result);
+};
+
+const fetchGET = async (url) => {
+ const result = await fetch(url);
+ const data = await result.json();
+ return data;
+};
+
+const fetchPOST = async (url, data) => {
+ console.log(data);
+ const result = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ });
+ console.log(result);
+};
+
+export { fetchPUT, fetchGET, fetchPOST };