├── src ├── Ranking.css ├── components │ ├── Menu.css │ ├── visualizer.tsx │ ├── Header.tsx │ ├── Menu.tsx │ └── Graph.tsx ├── react-app-env.d.ts ├── fruit_apple.png ├── Login.css ├── Home.css ├── setupTests.ts ├── App.css ├── ChallengeInfo.css ├── App.test.tsx ├── index.css ├── reportWebVitals.ts ├── index.tsx ├── Home.tsx ├── App.tsx ├── logo.svg ├── Login.tsx ├── Submit.tsx ├── Challenge.tsx ├── Ranking.tsx ├── Submissions.tsx ├── ChallengesList.tsx └── ChallengeInfo.tsx ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── README.md ├── firebase.json ├── .gitignore ├── tsconfig.json └── package.json /src/Ranking.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Menu.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ret2home/colledor-front/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ret2home/colledor-front/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ret2home/colledor-front/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/fruit_apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ret2home/colledor-front/HEAD/src/fruit_apple.png -------------------------------------------------------------------------------- /src/Login.css: -------------------------------------------------------------------------------- 1 | .login-form{ 2 | margin: 30px; 3 | } 4 | .login-form-in{ 5 | margin: 20px 0px; 6 | } -------------------------------------------------------------------------------- /src/components/visualizer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Visualizer(){ 4 | return (
); 5 | } 6 | export default Visualizer; -------------------------------------------------------------------------------- /src/Home.css: -------------------------------------------------------------------------------- 1 | .event-title{ 2 | background: #f0f0f0; 3 | text-align: center; 4 | padding: 30px; 5 | border-radius: 5px; 6 | margin: 10px 0px; 7 | } 8 | .top-block{ 9 | margin: 30px 0px; 10 | } -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # colledor-front 2 | 3 | パ研合宿 2021 レクリエーション フロントエンド(アーカイブ) 4 | 5 | https://colledor.web.app/ 6 | 7 | ![image](https://colledor.web.app/thumb.png) 8 | 9 | Server: https://github.com/ret2home/colledor-back 10 | 11 | Judge: https://github.com/ret2home/colledor-judge 12 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | main{ 2 | min-height: 100vh; 3 | padding: 15px 20px; 4 | max-width: 1000px; 5 | margin-left: auto; 6 | margin-right: auto; 7 | border-left: 1px solid #ccc; 8 | border-right: 1px solid #ccc; 9 | } 10 | .main-contents{ 11 | margin: 30px; 12 | } -------------------------------------------------------------------------------- /src/ChallengeInfo.css: -------------------------------------------------------------------------------- 1 | .board{ 2 | background: #ad907c; 3 | width: 610px; 4 | height: 710px; 5 | margin: 15px auto; 6 | border-radius: 10px; 7 | position: relative; 8 | z-index: 0; 9 | } 10 | .scoreboard{ 11 | text-align: center; 12 | font-size: 20px; 13 | } -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .firebase 26 | .firebaserc 27 | package-lock.json -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Header from './components/Header' 3 | import Menu from './components/Menu' 4 | 5 | import './Home.css' 6 | 7 | import { Typography } from '@mui/material'; 8 | 9 | 10 | function Home() { 11 | return ( 12 |
13 |
14 |
15 | 16 |
17 |
18 | パ研合宿 2022 レクリエーション 19 |
20 | 参加対象: パ研合宿 2022 参加者 21 | 22 |
23 | コンテスト情報 24 |
    25 |
  • 26 | コンテスト時間: 180 分 27 |
  • 28 |
29 |
30 |
31 |
32 |
33 | ) 34 | } 35 | export default Home; -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' 5 | import Home from './Home' 6 | import Login from './Login' 7 | import Submit from './Submit' 8 | import Ranking from './Ranking' 9 | import Submissions from './Submissions' 10 | import Challenge from './Challenge' 11 | import ChallengesList from './ChallengesList' 12 | import ChallengeInfo from './ChallengeInfo' 13 | 14 | function App() { 15 | return ( 16 |
17 | 18 | 19 | } /> 20 | } /> 21 | }/> 22 | }/> 23 | }/> 24 | }/> 25 | }/> 26 | }/> 27 | 28 | 29 |
30 | ); 31 | } 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colledor-front", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.8.2", 7 | "@emotion/styled": "^11.8.1", 8 | "@mui/icons-material": "^5.5.1", 9 | "@mui/material": "^5.5.1", 10 | "@testing-library/jest-dom": "^5.16.2", 11 | "@testing-library/react": "^12.1.4", 12 | "@testing-library/user-event": "^13.5.0", 13 | "@types/jest": "^27.4.1", 14 | "@types/node": "^16.11.26", 15 | "@types/react": "^17.0.40", 16 | "@types/react-chartjs-2": "^2.5.7", 17 | "@types/react-dom": "^17.0.13", 18 | "@types/react-router-dom": "^5.3.3", 19 | "axios": "^0.26.1", 20 | "chart.js": "^3.7.1", 21 | "dotenv": "^16.0.0", 22 | "dotenv-cli": "^5.0.0", 23 | "highlight.js": "^11.5.0", 24 | "react": "^17.0.2", 25 | "react-chartjs-2": "^4.3.1", 26 | "react-dom": "^17.0.2", 27 | "react-router-dom": "^6.2.2", 28 | "react-scripts": "5.0.0", 29 | "typescript": "^4.6.2", 30 | "web-vitals": "^2.1.4" 31 | }, 32 | "scripts": { 33 | "start": "dotenv -e .env.local react-scripts start", 34 | "build": "dotenv -e .env.build react-scripts build", 35 | "test": "react-scripts test", 36 | "eject": "react-scripts eject" 37 | }, 38 | "eslintConfig": { 39 | "extends": [ 40 | "react-app", 41 | "react-app/jest" 42 | ] 43 | }, 44 | "browserslist": { 45 | "production": [ 46 | ">0.2%", 47 | "not dead", 48 | "not op_mini all" 49 | ], 50 | "development": [ 51 | "last 1 chrome version", 52 | "last 1 firefox version", 53 | "last 1 safari version" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | パ研合宿 2021 レクリエーション 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { AppBar, Toolbar, Typography, Button, Box, IconButton } from '@mui/material' 3 | import HomeIcon from '@mui/icons-material/Home'; 4 | import { Navigate } from 'react-router'; 5 | 6 | function Header() { 7 | const user: string | null = localStorage.getItem('user'); 8 | const [href, sethref] = useState(); 9 | const logout = () => { 10 | localStorage.removeItem('user'); 11 | localStorage.removeItem('token'); 12 | if(window.location.pathname!="/login")sethref("/login"); 13 | } 14 | const gologin = () => { 15 | if(window.location.pathname!="/login")sethref("/login"); 16 | } 17 | return ( 18 |
19 | 20 | 21 | 22 | 23 | 31 | 32 | パ研合宿 2022 レクリエーション 33 | 34 | {user ? ( 35 | <> 36 | {user} 37 | 38 | 39 | ) : 40 | ( 41 | <> 42 | 43 | 44 | ) 45 | } 46 | 47 | 48 | 49 | {href && ( 50 | 51 | )} 52 |
53 | ) 54 | } 55 | export default Header; -------------------------------------------------------------------------------- /src/components/Menu.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import Tabs from '@mui/material/Tabs'; 4 | import Tab from '@mui/material/Tab'; 5 | import './Menu.css' 6 | import { Navigate } from 'react-router'; 7 | import { Typography } from '@mui/material'; 8 | 9 | interface Props { 10 | num: number 11 | } 12 | 13 | function two(x:string){ 14 | if(x.length==1)return "0"+x; 15 | return x; 16 | } 17 | function dat(d:number){ 18 | let x:Date=new Date(d); 19 | return String(x.getFullYear())+"/"+String(x.getMonth()+1)+"/"+String(x.getDate())+" "+two(String(x.getHours()))+":"+two(String(x.getMinutes())); 20 | } 21 | 22 | const CONTEST_START:number=Number(process.env.REACT_APP_CONTEST_START); 23 | const CONTEST_END:number=Number(process.env.REACT_APP_CONTEST_END); 24 | 25 | console.log(process.env.REACT_APP_API_URL) 26 | function Menu(props: Props) { 27 | const [value, setValue] = React.useState(props.num); 28 | const [href, sethref]=React.useState(); 29 | 30 | const handleChange = (event: React.SyntheticEvent, newValue: number) => { 31 | setValue(newValue); 32 | }; 33 | 34 | const hr=(hr: string)=>{ 35 | if(window.location.pathname!=hr)sethref(hr); 36 | }; 37 | 38 | return ( 39 | 40 | 41 | コンテスト時間: {dat(CONTEST_START*1000)} ~ {dat(CONTEST_END*1000)} ({(CONTEST_END-CONTEST_START)/60} 分) 42 | 43 | 44 | hr("/")} /> 45 | hr("/submit")} /> 46 | hr("/challenge")} /> 47 | hr("/submissions-list")} /> 48 | hr("/challenges-list")} /> 49 | hr("/ranking")} /> 50 | 51 | {href && ( 52 | 53 | )} 54 | 55 | ); 56 | } 57 | export default Menu -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Header from './components/Header' 3 | import { Navigate } from 'react-router-dom' 4 | import { Typography, TextField, Button, Dialog, DialogContent } from '@mui/material' 5 | import './Login.css' 6 | import axios from 'axios' 7 | import Menu from './components/Menu' 8 | 9 | const API_URL: string | undefined = process.env.REACT_APP_API_URL; 10 | 11 | if (axios.defaults.headers) { 12 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 13 | } 14 | 15 | function Login() { 16 | const [id, setID] = useState(''); 17 | const [pw, setPW] = useState(''); 18 | const [isLoggedin, setIsLoggedin] = useState(localStorage.getItem('token')!=null); 19 | const [isOpen, setIsOpen] = useState(false); 20 | const [message, setMessage] = useState(''); 21 | const handleID = (event: React.ChangeEvent) => { 22 | setID(event.target.value); 23 | }; 24 | const handlePW = (event: React.ChangeEvent) => { 25 | setPW(event.target.value); 26 | }; 27 | const handleAuth = () => { 28 | let data = { 29 | "id": id, 30 | "pw": pw 31 | }; 32 | setIsOpen(true); 33 | axios.post(API_URL + "/login", JSON.stringify(data)).then(res => { 34 | let resBody = JSON.parse(JSON.stringify(res.data)); 35 | localStorage.setItem("token", resBody.token); 36 | localStorage.setItem("user", id); 37 | setIsLoggedin(true); 38 | setIsOpen(false); 39 | }).catch(err => { 40 | setIsOpen(false); 41 | setMessage('ログイン失敗'); 42 | }) 43 | } 44 | return ( 45 |
46 |
47 |
48 | 49 |
50 | ログイン 51 | {message} 52 |
53 |
54 | 55 |
56 |
57 | 58 |
59 | 60 |
61 |
62 | {isLoggedin && 63 | 64 | } 65 | 66 | 処理中 67 | 68 |
69 |
70 | ) 71 | } 72 | export default Login; -------------------------------------------------------------------------------- /src/Submit.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Header from './components/Header' 3 | import Menu from './components/Menu' 4 | 5 | import axios from 'axios' 6 | import { Typography, Button, TextareaAutosize, Dialog, DialogContent } from '@mui/material'; 7 | import { Navigate } from 'react-router'; 8 | 9 | const API_URL: string | undefined = process.env.REACT_APP_API_URL; 10 | 11 | if (axios.defaults.headers) { 12 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 13 | } 14 | 15 | 16 | export default function Submit() { 17 | const [source, setSource] = useState(); 18 | const [isOpen, setIsOpen] = useState(false); 19 | const [message, setMessage] = useState(); 20 | const [href, sethref] = useState(); 21 | const onSubmit = () => { 22 | setIsOpen(true); 23 | let data = { 24 | "token": localStorage.getItem('token'), 25 | "source": source 26 | }; 27 | axios.post(API_URL + "/submit", JSON.stringify(data)).then(res => { 28 | let resBody = JSON.parse(JSON.stringify(res.data)); 29 | setIsOpen(false); 30 | sethref("/submissions-list") 31 | }).catch(err => { 32 | setIsOpen(false); 33 | setMessage('提出失敗'); 34 | }) 35 | } 36 | const onChangeSource=(event: React.ChangeEvent)=>{ 37 | setSource(event.target.value); 38 | } 39 | useEffect(()=>{ 40 | if(localStorage.getItem('token')==null)sethref("/login"); 41 | },[]) 42 | return ( 43 |
44 |
45 |
46 | 47 |
48 | 注意事項 49 |
    50 |
  • 51 | 言語は C++17 のみです。 52 |
  • 53 |
  • 54 | AC-Library が使えます。 55 |
  • 56 |
  • 57 | コンパイルが成功するかのみチェックが行われます。 58 |
  • 59 |
  • 60 | チャレンジには最後に提出されたコードが使われます。 61 |
  • 62 |
63 |
64 | {message} 65 |
66 |
67 | 68 |
69 |
70 | 71 |
72 |
73 | 74 | 処理中 75 | 76 | {href &&( 77 | 78 | )} 79 |
80 |
81 | ) 82 | } -------------------------------------------------------------------------------- /src/components/Graph.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Legend, 11 | } from 'chart.js'; 12 | import { Chart, registerables} from 'chart.js'; 13 | 14 | import { Line } from 'react-chartjs-2' 15 | import axios from 'axios' 16 | 17 | interface History { 18 | tim_num: number, 19 | user_id: string, 20 | rating: number 21 | } 22 | interface Dataset { 23 | label: string, 24 | data: Array, 25 | borderColor: string, 26 | backgroundColor: string 27 | } 28 | interface Data { 29 | labels: Array, 30 | datasets: Array 31 | } 32 | 33 | 34 | Chart.register(...registerables); 35 | 36 | const API_URL: string | undefined = process.env.REACT_APP_API_URL; 37 | 38 | if (axios.defaults.headers) { 39 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 40 | } 41 | 42 | export default function Graph() { 43 | ChartJS.register( 44 | CategoryScale, 45 | LinearScale, 46 | PointElement, 47 | LineElement, 48 | Title, 49 | Tooltip, 50 | Legend 51 | ); 52 | 53 | const options = { 54 | responsive: true, 55 | plugins: { 56 | legend: { 57 | position: 'top' as const, 58 | }, 59 | title: { 60 | display: true, 61 | text: 'Top ranker', 62 | }, 63 | }, 64 | }; 65 | const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']; 66 | const [data, setData] = useState(); 67 | 68 | const CONTEST_START: number = Number(process.env.REACT_APP_CONTEST_START); 69 | 70 | useEffect(() => { 71 | axios.get(API_URL + "/top-rating-history").then(res => { 72 | let resBody: Array = JSON.parse(JSON.stringify(res.data))["history"]; 73 | console.log(res) 74 | let ratings: Array = []; 75 | interface LooseObject { 76 | [key: string]: any 77 | } 78 | let mapping: LooseObject = {}; 79 | let nowdata: Data = { 80 | labels: [], 81 | datasets: [] 82 | }; 83 | 84 | let cur = 0; 85 | for (let t = CONTEST_START; t <= CONTEST_START + 60 * 60 * 3; t += 60 * 5) { 86 | while (cur < resBody.length && resBody[cur].tim_num <= t) { 87 | if (resBody[cur].tim_num == CONTEST_START) { 88 | let new_data: Dataset = { 89 | label: resBody[cur].user_id, 90 | data: [], 91 | borderColor: colors[cur % colors.length], 92 | backgroundColor: colors[cur % colors.length] + '80' 93 | }; 94 | nowdata.datasets.push(new_data); 95 | mapping[resBody[cur].user_id] = cur; 96 | ratings.push(resBody[cur].rating); 97 | } else { 98 | ratings[mapping[resBody[cur].user_id]] = resBody[cur].rating; 99 | } 100 | cur++; 101 | } 102 | for (let i = 0; i < ratings.length; i++)nowdata.datasets[i].data.push(ratings[i]); 103 | nowdata.labels.push(((t - CONTEST_START)/60).toString()); 104 | if(Date.now()/1000 { 108 | console.error(err); 109 | }) 110 | }, []) 111 | 112 | return ( 113 |
114 | {data && ( 115 | 116 | )} 117 |
118 | ) 119 | } -------------------------------------------------------------------------------- /src/Challenge.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Header from './components/Header' 3 | import Menu from './components/Menu' 4 | 5 | import axios from 'axios' 6 | import { Typography, Button, TextareaAutosize, Dialog, DialogContent, InputLabel, MenuItem, FormControl } from '@mui/material'; 7 | import { Navigate } from 'react-router'; 8 | 9 | import Select, { SelectChangeEvent } from '@mui/material/Select'; 10 | 11 | const API_URL: string | undefined = process.env.REACT_APP_API_URL; 12 | 13 | if (axios.defaults.headers) { 14 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 15 | } 16 | 17 | interface User{ 18 | id: string, 19 | rating: number 20 | } 21 | export default function Challenge() { 22 | const [target, setTarget] = useState(''); 23 | const [candTarget, setCandTarget] = useState>(); 24 | const [isOpen, setIsOpen] = useState(false); 25 | const [message, setMessage] = useState(); 26 | const [href, sethref] = useState(); 27 | const onSubmit = () => { 28 | setIsOpen(true); 29 | let data = { 30 | "token": localStorage.getItem('token'), 31 | "target": target 32 | }; 33 | axios.post(API_URL + "/challenge", JSON.stringify(data)).then(res => { 34 | let resBody = JSON.parse(JSON.stringify(res.data)); 35 | setIsOpen(false); 36 | sethref("/challenges-list") 37 | }).catch(err => { 38 | setIsOpen(false); 39 | setMessage('チャレンジ申込み失敗'); 40 | }) 41 | } 42 | const onChange = (event: SelectChangeEvent) => { 43 | setTarget(event.target.value); 44 | } 45 | useEffect(() => { 46 | if (localStorage.getItem('token') == null) sethref("/login"); 47 | axios.get(API_URL + "/submitted-users").then(res => { 48 | let resBody: Array = JSON.parse(JSON.stringify(res.data))["users"]; 49 | let cand: Array = []; 50 | resBody.forEach((val, i) => { 51 | let d:User={ 52 | id: val["id"], 53 | rating: val["rating"] 54 | } 55 | cand.push(d); 56 | }); 57 | setCandTarget(cand); 58 | }) 59 | }, []) 60 | return ( 61 |
62 |
63 |
64 | 65 |
66 | 注意事項 67 |
    68 |
  • 69 | レーティングが自分以上のアカウントとの対戦のみ rated です。 70 |
  • 71 |
  • 72 | 自分自身と対戦する事もできますが unrated です。 73 |
  • 74 |
  • 75 | 一度も提出していないアカウントとは対戦できません。 76 |
  • 77 |
  • 78 | 申込み者が先手になります。 79 |
  • 80 |
  • 81 | 申込み時点ではなく、ジャッジ開始時点で最後に提出されたコードが使われます。 82 |
  • 83 |
  • 84 | 対戦相手に関わらず、申し込んだチャレンジが終了するまではチャレンジできません。 85 |
  • 86 |
87 |
88 | {message} 89 |
90 |
91 | 92 | 対戦相手 93 | 103 | 104 |
105 |
106 | 107 |
108 |
109 | 110 | 処理中 111 | 112 | {href && ( 113 | 114 | )} 115 |
116 |
117 | ) 118 | } -------------------------------------------------------------------------------- /src/Ranking.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Header from './components/Header' 3 | 4 | import { Typography } from '@mui/material' 5 | import Table from '@mui/material/Table'; 6 | import TableBody from '@mui/material/TableBody'; 7 | import TableCell from '@mui/material/TableCell'; 8 | import TableContainer from '@mui/material/TableContainer'; 9 | import TableHead from '@mui/material/TableHead'; 10 | import TableRow from '@mui/material/TableRow'; 11 | import Container from '@mui/material/Container'; 12 | import Graph from './components/Graph' 13 | 14 | import './Ranking.css' 15 | 16 | import axios from 'axios' 17 | import Menu from './components/Menu' 18 | 19 | const API_URL: string | undefined = process.env.REACT_APP_API_URL; 20 | 21 | if (axios.defaults.headers) { 22 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 23 | } 24 | 25 | 26 | interface User { 27 | rank: number, 28 | id: string, 29 | rating: number 30 | } 31 | 32 | interface User2 { 33 | rank: number, 34 | id: string, 35 | stock: number 36 | } 37 | function Ranking() { 38 | const [users, setUsers] = useState>(); 39 | const [users2, setUsers2] = useState>(); 40 | useEffect(() => { 41 | axios.get(API_URL + "/users").then(res => { 42 | let resBody = JSON.parse(JSON.stringify(res.data))["users"]; 43 | let resData: Array = []; 44 | let las:number=-1,rank:number=-1; 45 | resBody.forEach((elem: any, index: any) => { 46 | if(elem.rating!=las){ 47 | rank=index+1; 48 | } 49 | las=elem.rating; 50 | let data:User={ 51 | rank: rank, 52 | id: elem.id, 53 | rating: elem.rating 54 | }; 55 | resData.push(data); 56 | }); 57 | setUsers(resData); 58 | }).catch(err => { 59 | console.error(err); 60 | }) 61 | 62 | axios.get(API_URL + "/users2").then(res => { 63 | let resBody = JSON.parse(JSON.stringify(res.data))["users"]; 64 | let resData: Array = []; 65 | let las:number=-1,rank:number=-1; 66 | resBody.forEach((elem: any, index: any) => { 67 | if(elem.stock!=las){ 68 | rank=index+1; 69 | } 70 | las=elem.stock; 71 | let data:User2={ 72 | rank: rank, 73 | id: elem.id, 74 | stock: elem.stock 75 | }; 76 | resData.push(data); 77 | }); 78 | setUsers2(resData); 79 | }).catch(err => { 80 | console.error(err); 81 | }) 82 | }, []); 83 | return ( 84 |
85 |
86 |
87 | 88 |
89 | 90 | Rating Ranking 91 | 92 | 93 | 94 | 95 | Rank 96 | User 97 | Rating 98 | 99 | 100 | 101 | {users?.map((row, index) => ( 102 | 103 | {row.rank} 104 | {row.id} 105 | {row.rating} 106 | 107 | ))} 108 | 109 |
110 |
111 | Stock Ranking 112 | 113 | 114 | 115 | 116 | 117 | Rank 118 | User 119 | Stock 120 | 121 | 122 | 123 | {users2?.map((row, index) => ( 124 | 125 | {row.rank} 126 | {row.id} 127 | {row.stock} 128 | 129 | ))} 130 | 131 |
132 |
133 |
134 |
135 |
136 | ) 137 | } 138 | export default Ranking; -------------------------------------------------------------------------------- /src/Submissions.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Header from './components/Header' 3 | 4 | import { Button, Dialog, DialogContent, FormControlLabel, Typography } from '@mui/material' 5 | import Table from '@mui/material/Table'; 6 | import TableBody from '@mui/material/TableBody'; 7 | import TableCell from '@mui/material/TableCell'; 8 | import TableContainer from '@mui/material/TableContainer'; 9 | import TableHead from '@mui/material/TableHead'; 10 | import TableRow from '@mui/material/TableRow'; 11 | import Paper from '@mui/material/Paper'; 12 | import Container from '@mui/material/Container'; 13 | 14 | import Checkbox from '@mui/material/Checkbox'; 15 | import hljs from 'highlight.js/lib/core' 16 | import cpp from 'highlight.js/lib/languages/cpp' 17 | import 'highlight.js/styles/base16/github.css' 18 | import './Ranking.css' 19 | 20 | import axios from 'axios' 21 | import Menu from './components/Menu' 22 | import { Navigate } from 'react-router'; 23 | 24 | const API_URL: string | undefined = process.env.REACT_APP_API_URL; 25 | 26 | if (axios.defaults.headers) { 27 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 28 | } 29 | 30 | 31 | interface Submission { 32 | id: number, 33 | tim: string, 34 | user: string, 35 | source: string 36 | } 37 | 38 | // 2022/3/17 13:00:00 39 | const CONTEST_END: number = Number(process.env.REACT_APP_CONTEST_END) * 1000; 40 | export default function Submissions() { 41 | const [submissions, setSubmissions] = useState>(); 42 | const currentTime = Date.now(); 43 | const user = localStorage.getItem('user'); 44 | const [isOwnOnly, setIsOwnOnly] = useState(currentTime <= CONTEST_END && user != 'admin'); 45 | const [href, sethref] = useState(); 46 | const [isOpen, setIsOpen] = useState(false); 47 | const [source, setSource] = useState(""); 48 | useEffect(() => { 49 | if (localStorage.getItem('token') == null) sethref("/login"); 50 | hljs.registerLanguage('cpp', cpp); 51 | }, []) 52 | useEffect(() => { 53 | let data = { 54 | "token": localStorage.getItem('token'), 55 | "user": (isOwnOnly ? localStorage.getItem('user') : "all") 56 | } 57 | 58 | axios.post(API_URL + "/submissions-list", JSON.stringify(data)).then(res => { 59 | let resBody = JSON.parse(JSON.stringify(res.data))["submissions"]; 60 | let resData: Array = []; 61 | resBody.forEach((elem: any, index: any) => resData.push(elem)); 62 | setSubmissions(resData); 63 | }).catch(err => { 64 | console.error(err); 65 | }) 66 | }, [isOwnOnly]); 67 | const openDetail = (id: number) => { 68 | setIsOpen(true); 69 | setSource("Loading..."); 70 | let data = { 71 | "token": localStorage.getItem('token'), 72 | "id": id 73 | } 74 | axios.post(API_URL + "/submission-info", JSON.stringify(data)).then(res => { 75 | setSource(JSON.parse(JSON.stringify(res.data))["source"]); 76 | hljs.highlightAll(); 77 | }).catch(err => { 78 | console.error(err); 79 | }) 80 | } 81 | return ( 82 |
83 |
84 |
85 | 86 |
87 | 88 | {(currentTime >= CONTEST_END || user == 'admin') && ( 89 | setIsOwnOnly(!isOwnOnly)} />} label="自分の提出のみ" /> 90 | ) 91 | } 92 | 93 | 94 | 95 | ID 96 | Time 97 | User 98 | 99 | 100 | 101 | 102 | {submissions?.map((row, index) => ( 103 | 104 | {row.id} 105 | {row.tim} 106 | {row.user} 107 | 108 | 109 | 110 | 111 | ))} 112 | 113 |
114 |
115 |
116 | {href && ( 117 | 118 | )} 119 | setIsOpen(false)} maxWidth="md"> 120 | 121 | 122 | 123 |
124 |                             
125 |                                 {source}
126 |                             
127 |                         
128 |
129 |
130 |
131 |
132 | ) 133 | } -------------------------------------------------------------------------------- /src/ChallengesList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Header from './components/Header' 3 | 4 | import { Button, Dialog, DialogContent, FormControlLabel, Typography } from '@mui/material' 5 | import Table from '@mui/material/Table'; 6 | import TableBody from '@mui/material/TableBody'; 7 | import TableCell from '@mui/material/TableCell'; 8 | import TableContainer from '@mui/material/TableContainer'; 9 | import TableHead from '@mui/material/TableHead'; 10 | import TableRow from '@mui/material/TableRow'; 11 | import Container from '@mui/material/Container'; 12 | import InputLabel from '@mui/material/InputLabel'; 13 | import MenuItem from '@mui/material/MenuItem'; 14 | import FormControl from '@mui/material/FormControl'; 15 | import Select, { SelectChangeEvent } from '@mui/material/Select'; 16 | 17 | 18 | import Checkbox from '@mui/material/Checkbox'; 19 | import 'highlight.js/styles/base16/github.css' 20 | import './Ranking.css' 21 | 22 | import axios from 'axios' 23 | import Menu from './components/Menu' 24 | import { Navigate } from 'react-router'; 25 | 26 | const API_URL: string | undefined = process.env.REACT_APP_API_URL; 27 | 28 | if (axios.defaults.headers) { 29 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 30 | } 31 | 32 | 33 | interface Challenge { 34 | id: number, 35 | rated: string, 36 | tim: string, 37 | tim_num: number, 38 | stat: string, 39 | user1: string, 40 | user2: string, 41 | user1_score: string, 42 | user2_score: string, 43 | user1_vote: number, 44 | user2_vote: number 45 | } 46 | 47 | const sign=((num:number)=>{ 48 | if(num>0){ 49 | return "+"+num.toString() 50 | }else if(num<0){ 51 | return num.toString() 52 | } 53 | return "±0" 54 | }) 55 | export default function ChallengesList() { 56 | const [challenges, setChallenges] = useState>(); 57 | const user = localStorage.getItem('user'); 58 | const [isOwnOnly, setIsOwnOnly] = useState(false); 59 | const [vote, setVote] = useState<{ [id: number]: number }>({}); 60 | const [href, sethref] = useState(); 61 | const [voteValue, setVoteValue] = useState>(Array(0)); 62 | 63 | const update = () => { 64 | axios.get(API_URL + "/challenges-list/" + (isOwnOnly ? localStorage.getItem('user') : "all")).then(res => { 65 | let resBody = JSON.parse(JSON.stringify(res.data))["challenges"]; 66 | let resData: Array = []; 67 | let hoge: Array = []; 68 | hoge.push(''); 69 | resBody.forEach((elem: any, index: any) => { 70 | let data: Challenge = { 71 | id: elem.id, 72 | rated: elem.rated ? "rated" : "unrated", 73 | tim: elem.tim, 74 | tim_num: elem.tim_num, 75 | stat: elem.stat, 76 | user1: elem.user1, 77 | user2: elem.user2, 78 | user1_score: elem.user1_score, 79 | user2_score: elem.user2_score, 80 | user1_vote: elem.user1_vote, 81 | user2_vote: elem.user2_vote 82 | }; 83 | resData.push(data); 84 | hoge.push(''); 85 | }); 86 | setChallenges(resData); 87 | 88 | axios.get(API_URL + "/vote-info/" + (localStorage.getItem('user'))).then(res => { 89 | let resBody = JSON.parse(JSON.stringify(res.data))["votes"]; 90 | let resData: { [id: number]: number } = {}; 91 | resBody.forEach((elem: any, index: any) => { 92 | resData[elem.id] = elem.vote; 93 | hoge[elem.id] = (2 * elem.id + elem.vote).toString() 94 | }); 95 | setVote(resData); 96 | 97 | console.log(hoge); 98 | 99 | setVoteValue(hoge); 100 | }).catch(err => { 101 | console.error(err); 102 | }) 103 | }).catch(err => { 104 | console.error(err); 105 | }) 106 | } 107 | useEffect(() => { 108 | if (localStorage.getItem('token') == null) sethref("/login"); 109 | }, []) 110 | useEffect(() => { 111 | update(); 112 | }, [isOwnOnly]); 113 | 114 | 115 | const handleChange = (event: SelectChangeEvent) => { 116 | if (typeof event.target.value === 'number') { 117 | let hoge = voteValue.slice() 118 | let id = Math.floor(event.target.value / 2); 119 | let vo = event.target.value % 2; 120 | hoge[id] = event.target.value as string; 121 | setVoteValue(hoge); 122 | 123 | let data = { 124 | "token": localStorage.getItem('token'), 125 | "id": id, 126 | "vote": vo 127 | }; 128 | axios.post(API_URL + "/vote", JSON.stringify(data)).then(res => { 129 | update(); 130 | }).catch(err => { 131 | console.error(err); 132 | alert("Error"); 133 | }) 134 | } 135 | }; 136 | return ( 137 |
138 |
139 |
140 | 141 |
142 | 143 | 144 | setIsOwnOnly(!isOwnOnly)} />} label="自分の対戦のみ" /> 145 | 146 | 147 | 148 | ID 149 | Time 150 | Rated 151 | Status 152 | User 1 153 | User 2 154 | Score 1 155 | Score 2 156 | Vote 157 | 158 | 159 | 160 | 161 | {challenges?.map((row, index) => { 162 | let result; 163 | if (row.stat != "FINISHED" || row.user1 == row.user2) result = "-1"; 164 | else if (row.user1_score == "TLE" || row.user1_score == "WA") result = "win2"; 165 | else if (row.user2_score == "TLE" || row.user2_score == "WA") result = "win1"; 166 | else if (Number(row.user1_score) < Number(row.user2_score)) result = "win2"; 167 | else if (Number(row.user1_score) > Number(row.user2_score)) result = "win1"; 168 | else result = "draw"; 169 | 170 | let diff1 = 0, diff2 = 0; 171 | if (result == "win1" && row.user1_vote != 0) { 172 | diff1 = Math.ceil(row.user2_vote * 50 / row.user1_vote); 173 | diff2 = -50; 174 | } 175 | if (result == "win2" && row.user2_vote != 0) { 176 | diff1 = -50; 177 | diff2 = Math.ceil(row.user1_vote * 50 / row.user2_vote); 178 | } 179 | 180 | let background = (index % 2 ? '#f2f2f2' : '#fff'); 181 | if ((user == row.user1 && result == "win1") || (user == row.user2 && result == "win2")) background = "#d5f0d5"; 182 | else if ((user == row.user1 && result == "win2") || (user == row.user2 && result == "win1")) background = "#f5e6d3"; 183 | else if ((user == row.user1 || user == row.user2) && result == "draw") background = "#e9f5ba"; 184 | 185 | return ( 186 | 187 | {row.id} 188 | {row.tim} 189 | {row.rated} 190 | {row.stat} 191 | {row.user1} 192 | {row.user2} 193 | {row.user1_score} 194 | {row.user2_score} 195 | 196 | {row.rated == "rated"&&voteValue.length!=0 ? 197 | 198 | vote.hasOwnProperty(row.id) ? ( 199 | row.stat == "FINISHED" ? ( 200 | 201 | {(vote[row.id] == 0 ? sign(diff1) : sign(diff2))+" ("+row.user1_vote.toString()+"/"+row.user2_vote.toString()+" votes)"} 202 | 203 | ) : ( 204 | 205 | {row.user1_vote.toString()+"/"+row.user2_vote.toString()+" votes"} 206 | 217 | 218 | ) 219 | ) : ( 220 | (row.tim_num + 60) * 1000)}> 221 | {row.user1_vote.toString()+"/"+row.user2_vote.toString()+" votes"} 222 | 232 | 233 | ) : ( 234 | "unrated" 235 | )} 236 | 237 | 238 | 239 | 240 | 241 | ) 242 | })} 243 | 244 |
245 |
246 |
247 | {href && ( 248 | 249 | )} 250 |
251 |
252 | ) 253 | } -------------------------------------------------------------------------------- /src/ChallengeInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, ReactElement } from 'react'; 2 | import Header from './components/Header' 3 | 4 | import { Button, Dialog, DialogContent, FormControlLabel, Typography, IconButton, Slider } from '@mui/material' 5 | import Table from '@mui/material/Table'; 6 | import TableBody from '@mui/material/TableBody'; 7 | import TableCell from '@mui/material/TableCell'; 8 | import TableContainer from '@mui/material/TableContainer'; 9 | import TableHead from '@mui/material/TableHead'; 10 | import TableRow from '@mui/material/TableRow'; 11 | import Container from '@mui/material/Container'; 12 | 13 | import Checkbox from '@mui/material/Checkbox'; 14 | 15 | import axios from 'axios' 16 | import Menu from './components/Menu' 17 | import { Navigate, useParams } from 'react-router'; 18 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; 19 | import StopIcon from '@mui/icons-material/Stop'; 20 | import AppleImage from './fruit_apple.png' 21 | import './ChallengeInfo.css' 22 | 23 | import { Chart, registerables} from 'chart.js'; 24 | 25 | import { 26 | Chart as ChartJS, 27 | CategoryScale, 28 | LinearScale, 29 | BarElement, 30 | Title, 31 | Tooltip, 32 | Legend, 33 | } from 'chart.js'; 34 | import { Bar } from 'react-chartjs-2'; 35 | 36 | 37 | 38 | Chart.register(...registerables); 39 | 40 | const API_URL: string | undefined = process.env.REACT_APP_API_URL; 41 | 42 | if (axios.defaults.headers) { 43 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 44 | } 45 | 46 | 47 | interface Challenge { 48 | id: number, 49 | rated: string, 50 | tim: number, 51 | stat: string, 52 | user1: string, 53 | user2: string, 54 | user1_score: string, 55 | user2_score: string, 56 | output: string 57 | } 58 | 59 | interface Act { 60 | type: number, 61 | x: number, 62 | y: number 63 | } 64 | interface Game { 65 | C: Array>, 66 | base: Array>, 67 | X: Array, 68 | Y: Array, 69 | score: Array, 70 | wall_used: Array, 71 | wall_hrz: Array>, 72 | wall_vert: Array>, 73 | turn: number 74 | } 75 | function newGame() { 76 | let a99 = new Array(9); 77 | for (let i = 0; i < 9; i++) { 78 | a99[i] = (new Array(9).fill(0)); 79 | } 80 | let game: Game = { 81 | C: a99.slice(), 82 | base: a99.slice(), 83 | X: [0, 8], 84 | Y: [4, 4], 85 | score: [0, 0], 86 | wall_used: [0, 0], 87 | wall_hrz: a99.slice(), 88 | wall_vert: a99.slice(), 89 | turn: 0 90 | }; 91 | return JSON.parse(JSON.stringify(game)); 92 | } 93 | let dx = [-1, 0, 1, 0]; 94 | let dy = [0, 1, 0, -1]; 95 | function applyAct(game: Game, act: Act) { 96 | let n = JSON.parse(JSON.stringify(game)); 97 | if (act.type == 0 || act.type == 1) { 98 | n.X[n.turn] = act.x; 99 | n.Y[n.turn] = act.y; 100 | if (n.C[n.X[n.turn]][n.Y[n.turn]]) { 101 | n.score[n.turn] += 5; 102 | n.C[n.X[n.turn]][n.Y[n.turn]] = false; 103 | } 104 | } else if (act.type == 2) { 105 | n.base[act.x][act.y] = n.turn + 1; 106 | n.score[n.turn] -= 3; 107 | } 108 | else if (act.type == 3) { 109 | n.wall_hrz[act.x][act.y] = n.wall_hrz[act.x][act.y + 1] = n.turn + 1; 110 | n.score[n.turn] -= 3; 111 | } else if (act.type == 4) { 112 | n.wall_vert[act.x][act.y] = n.wall_vert[act.x + 1][act.y] = n.turn + 1; 113 | n.score[n.turn] -= 3; 114 | } 115 | n.turn ^= 1; 116 | return JSON.parse(JSON.stringify(n)); 117 | } 118 | 119 | 120 | export default function ChallengeInfo() { 121 | const id = useParams()["id"]; 122 | const [challenge, setChallenge] = useState(); 123 | const [result, setResult] = useState>([]); 124 | const games = useRef>([]); 125 | const [running, setRunning] = useState(true); 126 | const [currentStep, setCurrentStep] = useState(0); 127 | const updateIntervalID = useRef(-1); 128 | const stepIntervalID = useRef(0); 129 | 130 | const update = (() => { 131 | axios.get(API_URL + "/challenge-info/" + id).then(res => { 132 | let resBody: Challenge = JSON.parse(JSON.stringify(res.data)); 133 | setChallenge(resBody); 134 | let spl = resBody.output.split('\n'); 135 | 136 | while (spl[spl.length - 1] == "") spl.pop(); 137 | if (spl[spl.length - 1] == "END") { 138 | spl.pop(); spl.pop(); 139 | } 140 | if (spl.length == 0) return; 141 | let clone = []; 142 | if (clone.length == 0) { 143 | let game: Game = newGame(); 144 | for (let i = 0; i < 9; i++)for (let j = 0; j < 9; j++) { 145 | if (spl[i][j] == '1') game.C[i][j] = 1; 146 | else game.C[i][j] = 0; 147 | } 148 | clone.push(game); 149 | } 150 | for (let i = 9; i < spl.length; i++) { 151 | let x = spl[i].split(' '); 152 | let act: Act = { 153 | type: Number(x[0]), 154 | x: Number(x[1]), 155 | y: Number(x[2]) 156 | }; 157 | clone.push(applyAct(clone[clone.length - 1], act)); 158 | } 159 | games.current = clone; 160 | 161 | if (resBody.user1_score != "") { 162 | window.clearInterval(updateIntervalID.current); 163 | } 164 | }).catch(err => { 165 | console.error(err); 166 | }) 167 | }); 168 | 169 | const stopRunning = (() => { 170 | window.clearInterval(stepIntervalID.current); 171 | setRunning(false); 172 | console.log("STOP", stepIntervalID.current); 173 | }) 174 | const onRunningChange = (() => { 175 | if (running) stopRunning(); 176 | else { 177 | stepIntervalID.current = window.setInterval(step, 500); 178 | console.log("START", stepIntervalID); 179 | setRunning(true); 180 | } 181 | }) 182 | const step = (() => { 183 | setCurrentStep(c => { 184 | if (c == games.current.length - 1 || games.current.length == 0) { 185 | if (challenge?.stat == "FINISHED") stopRunning(); 186 | return c; 187 | } 188 | return c + 1; 189 | }); 190 | }) 191 | const options = { 192 | indexAxis: 'y' as const, 193 | elements: { 194 | bar: { 195 | borderWidth: 2, 196 | }, 197 | }, 198 | responsive: true, 199 | legend: { 200 | display: false, 201 | }, 202 | aspectRatio: Math.min(1000, window.innerWidth) / 200, 203 | maintainAspectRatio: false, 204 | scales: { 205 | x: { 206 | suggestedMin: 0, 207 | suggestedMax: 30, 208 | ticks: { 209 | stepSize: 5 210 | } 211 | } 212 | } 213 | }; 214 | const handleSliderChange = (event: Event, newValue: number | number[]) => { 215 | if (typeof (newValue) == "number") { 216 | stopRunning(); 217 | setCurrentStep(newValue); 218 | } 219 | } 220 | useEffect(() => { 221 | update(); 222 | stepIntervalID.current = window.setInterval(step, 500); 223 | updateIntervalID.current = window.setInterval(update, 1000); 224 | }, []); 225 | return ( 226 |
227 |
228 |
229 | 230 |
231 | 232 | 233 | 234 | 235 | 236 | ID 237 | Time 238 | Rated 239 | Status 240 | User 1 241 | User 2 242 | User 1 Score 243 | User 2 Score 244 | 245 | 246 | 247 | {challenge && 248 | ( 249 | 250 | {challenge.id} 251 | {challenge.tim} 252 | {challenge.rated ? "rated" : "unrated"} 253 | {challenge.stat} 254 | {challenge.user1} 255 | {challenge.user2} 256 | {challenge.user1_score} 257 | {challenge.user2_score} 258 | 259 | ) 260 | } 261 | 262 |
263 |
264 | 265 |
{currentStep}/{Math.max(games.current.length - 1, 0)} Step
266 |
267 | onRunningChange()} 270 | size="large" 271 | > 272 | {running ? ( 273 | 274 | ) : 275 | ( 276 | 277 | )} 278 | 279 | 280 |
281 | 282 | {games.current.length && challenge ? ( 283 | 284 |
285 | {(currentStep == games.current.length - 1 && challenge?.stat == "FINISHED" && (challenge.user1_score == "WA" || challenge.user1_score == "TLE" || challenge.user2_score == "WA" || challenge.user2_score == "TLE")) ? ( 286 | ((challenge.user1_score == "WA" || challenge.user1_score == "TLE") ? ( 287 | {challenge.user1} : {challenge.user1_score} 288 | ) : ( 289 | {challenge.user2} : {challenge.user2_score} 290 | )) 291 | ) : ( 292 | (() => { 293 | const labels = [challenge.user1, challenge.user2]; 294 | const dat = { 295 | labels, 296 | datasets: [ 297 | { 298 | label: "Score", 299 | data: [games.current[currentStep].score[0], games.current[currentStep].score[1]], 300 | backgroundColor: ["#00000080", "#ffffff80"], 301 | borderColor: "#888", 302 | borderWidth: 2, 303 | barThickness: 20, 304 | } 305 | ] 306 | }; 307 | return 308 | })() 309 | ) 310 | } 311 |
312 | ) : null} 313 | {games.current.length ? 314 | ( 315 |
316 | { 317 | games.current[currentStep].C.map((row, i) => ( 318 | row.map((x, j) => ( 319 |
320 | 321 |

🏳

322 |

🏴

323 |
324 | )) 325 | )) 326 | } 327 | { 328 | games.current[currentStep].wall_hrz.map((row, i) => ( 329 | row.map((x, j) => { 330 | return ( 331 |
332 | ) 333 | }) 334 | )) 335 | } 336 | { 337 | games.current[currentStep].wall_vert.map((row, i) => ( 338 | row.map((x, j) => { 339 | return ( 340 |
341 | ) 342 | }) 343 | )) 344 | } 345 |
346 |
347 |
348 | ) : ( 349 |
Waiting to start...
350 | ) 351 | } 352 |
353 |
354 |
355 | ) 356 | } --------------------------------------------------------------------------------