]> git.localhorst.tv Git - alttp.git/blob - resources/js/pages/GuessingGameMonitor.js
add simple guessing game browser source
[alttp.git] / resources / js / pages / GuessingGameMonitor.js
1 import axios from 'axios';
2 import moment from 'moment';
3 import React from 'react';
4 import { Container } from 'react-bootstrap';
5 import { Helmet } from 'react-helmet';
6 import { useParams } from 'react-router-dom';
7
8 import {
9         hasActiveGuessing,
10         isAcceptingGuesses,
11         patchGuess,
12         patchWinner,
13 } from '../helpers/Channel';
14 import ErrorBoundary from '../components/common/ErrorBoundary';
15 import Icon from '../components/common/Icon';
16 import Slider from '../components/common/Slider';
17
18 export const Component = () => {
19         const [channel, setChannel] = React.useState({});
20         const [guesses, setGuesses] = React.useState([]);
21         const [winnerExpiry, setWinnerExpiry] = React.useState(moment().subtract(15, 'second'));
22         const [winners, setWinners] = React.useState([]);
23
24         const params = useParams();
25         const { key } = params;
26
27         React.useEffect(() => {
28                 if (!key) return;
29                 axios.get(`/api/guessing-game-monitor/${key}`)
30                         .then(res => {
31                                 setChannel(res.data.channel);
32                                 res.data.guesses.forEach(g => {
33                                         setGuesses(gs => patchGuess(gs, g));
34                                 });
35                                 res.data.winners.forEach(w => {
36                                         setWinners(ws => patchGuess(ws, w));
37                                 });
38                         });
39                 window.Echo.channel(`ChannelKey.${key}`)
40                         .listen('.GuessingGuessCreated', (e) => {
41                                 setGuesses(gs => patchGuess(gs, e.model));
42                         })
43                         .listen('.GuessingWinnerCreated', (e) => {
44                                 setWinners(ws => patchWinner(ws, e.model));
45                         })
46                         .listen('.ChannelUpdated', (e) => {
47                                 setChannel(c => ({ ...c, ...e.model }));
48                         });
49                 return () => {
50                         window.Echo.leave(`ChannelKey.${key}`);
51                 };
52         }, [key]);
53
54         React.useEffect(() => {
55                 if (isAcceptingGuesses(channel)) {
56                         setGuesses(gs => gs.filter(g => g.created_at >= channel.guessing_start));
57                         setWinners([]);
58                 }
59         }, [channel]);
60
61         React.useEffect(() => {
62                 const interval = setInterval(() => {
63                         setWinnerExpiry(moment().subtract(15, 'second'));
64                 }, 1000);
65                 return () => {
66                         clearInterval(interval);
67                 };
68         }, []);
69
70         const guessingStats = React.useMemo(() => {
71                 const stats = {
72                         counts: [],
73                         lastWin: null,
74                         max: 0,
75                         wins: [],
76                         winners: [],
77                 };
78                 for (let i = 0; i < 22; ++i) {
79                         stats.counts.push(0);
80                         stats.wins.push(false);
81                 }
82                 const seen = [];
83                 guesses.forEach(guess => {
84                         if (seen[guess.uid]) {
85                                 --stats.counts[parseInt(seen[guess.uid].guess, 10) - 1];
86                         }
87                         ++stats.counts[parseInt(guess.guess, 10) - 1];
88                         seen[guess.uid] = guess;
89                 });
90                 winners.forEach(winner => {
91                         if (winner.score) {
92                                 stats.wins[parseInt(winner.guess, 10) - 1] = true;
93                                 stats.winners.push(winner.uname);
94                         }
95                         if (!stats.lastWin || stats.lastWin < winner.created_at) {
96                                 stats.lastWin = winner.created_at;
97                         }
98                 });
99                 for (let i = 0; i < 22; ++i) {
100                         if (stats.counts[i] > stats.max) {
101                                 stats.max = stats.counts[i];
102                         }
103                 }
104                 return stats;
105         }, [guesses, winners]);
106
107         const getNumberHeight = React.useCallback((number) => {
108                 if (!guessingStats || !guessingStats.max) return 3;
109                 if (!number) return 3;
110                 return Math.max(0.05, number / guessingStats.max) * 100;
111         }, [guessingStats]);
112
113         const getStatClass = React.useCallback((index) => {
114                 const names = ['guessing-stat'];
115                 if (guessingStats.wins[index]) {
116                         names.push('has-won');
117                 }
118                 return names.join(' ');
119         }, [guessingStats]);
120
121         const showOpen = React.useMemo(() => {
122                 return isAcceptingGuesses(channel);
123         }, [channel]);
124
125         const showClosed = React.useMemo(() => {
126                 return hasActiveGuessing(channel) && !isAcceptingGuesses(channel);
127         }, [channel]);
128
129         const showWinners = React.useMemo(() => {
130                 return !hasActiveGuessing(channel) && (
131                         guessingStats?.lastWin &&
132                         moment(guessingStats.lastWin).isAfter(winnerExpiry));
133         }, [channel, guessingStats, winnerExpiry]);
134
135         return <ErrorBoundary>
136                 <Helmet>
137                         <title>Guessing Game</title>
138                 </Helmet>
139                 <Container className="guessing-game-monitor" fluid>
140                 {showOpen || showClosed || showWinners ?
141                         <div className="message-box">
142                                 {showOpen ?
143                                         <div className="message-title accepting-guesses">
144                                                 <Icon.WARNING className="message-icon" />
145                                                 <div className="message-text">
146                                                         <Slider duration={3500}>
147                                                                 <Slider.Slide>GT Big Key Guessing Game</Slider.Slide>
148                                                                 <Slider.Slide>Zahlen von 1 bis 22 in den Chat!</Slider.Slide>
149                                                         </Slider>
150                                                 </div>
151                                                 <Icon.WARNING className="message-icon" />
152                                         </div>
153                                 : null}
154                                 {showClosed ?
155                                         <div className="message-title guessing-closed">
156                                                 <div className="message-text">
157                                                         Anmeldung geschlossen
158                                                 </div>
159                                         </div>
160                                 : null}
161                                 {showWinners ?
162                                         <div className="message-title guessing-winners">
163                                                 <div className="message-text">
164                                                         {guessingStats.winners.length ?
165                                                                 <Slider duration={2500}>
166                                                                         <Slider.Slide>Herzlichen Glückwunsch!</Slider.Slide>
167                                                                         {guessingStats.winners.map(winner =>
168                                                                                 <Slider.Slide key={winner}>{winner}</Slider.Slide>
169                                                                         )}
170                                                                 </Slider>
171                                                         :
172                                                                 'Leider keiner richtig'
173                                                         }
174                                                 </div>
175                                         </div>
176                                 : null}
177                                 <div className="guessing-stats">
178                                         {guessingStats.counts.map((number, index) =>
179                                                 <div className={getStatClass(index)} key={index}>
180                                                         <div className="guessing-box">
181                                                                 <div
182                                                                         className="guessing-box-bar"
183                                                                         style={{ height: `${getNumberHeight(number)}%` }}
184                                                                 />
185                                                         </div>
186                                                         <div className="guessing-number">{index + 1}</div>
187                                                 </div>
188                                         )}
189                                 </div>
190                         </div>
191                 : null}
192                 </Container>
193         </ErrorBoundary>;
194 };