guandan.dev

guandan.dev

https://git.tonybtw.com/guandan.dev.git git://git.tonybtw.com/guandan.dev.git
9,529 bytes raw
1
import { useState } from 'react'
2
import { motion } from 'framer-motion'
3
import { Player_Info } from '../game/types'
4
5
interface Lobby_Props {
6
    room_id: string | null
7
    players: Player_Info[]
8
    on_create_room: (name: string) => void
9
    on_join_room: (room_id: string, name: string) => void
10
    on_fill_bots: () => void
11
}
12
13
export function Lobby({ room_id, players, on_create_room, on_join_room, on_fill_bots }: Lobby_Props) {
14
    const [name, set_name] = useState('')
15
    const [join_code, set_join_code] = useState('')
16
    const [mode, set_mode] = useState<'select' | 'create' | 'join'>('select')
17
18
    const handle_create = () => {
19
        if (name.trim()) {
20
            on_create_room(name.trim())
21
        }
22
    }
23
24
    const handle_join = () => {
25
        if (name.trim() && join_code.trim()) {
26
            on_join_room(join_code.trim(), name.trim())
27
        }
28
    }
29
30
    if (room_id) {
31
        return (
32
            <div style={styles.container}>
33
                <motion.div
34
                    initial={{ opacity: 0, y: 20 }}
35
                    animate={{ opacity: 1, y: 0 }}
36
                    style={styles.card}
37
                >
38
                    <h2 style={styles.title}>Room: {room_id}</h2>
39
                    <p style={styles.subtitle}>Waiting for players... ({players.length}/4)</p>
40
41
                    <div style={styles.players_grid}>
42
                        {[0, 1, 2, 3].map((seat) => {
43
                            const player = players.find((p) => p.seat === seat)
44
                            const team = seat % 2
45
                            return (
46
                                <motion.div
47
                                    key={seat}
48
                                    initial={{ opacity: 0, scale: 0.8 }}
49
                                    animate={{ opacity: 1, scale: 1 }}
50
                                    transition={{ delay: seat * 0.1 }}
51
                                    style={{
52
                                        ...styles.player_slot,
53
                                        backgroundColor: team === 0 ? '#e3f2fd' : '#fce4ec',
54
                                        borderColor: team === 0 ? '#2196f3' : '#e91e63',
55
                                    }}
56
                                >
57
                                    {player ? (
58
                                        <>
59
                                            <div style={styles.player_name}>{player.name}</div>
60
                                            <div style={styles.player_team}>Team {team + 1}</div>
61
                                        </>
62
                                    ) : (
63
                                        <div style={styles.empty_slot}>Empty</div>
64
                                    )}
65
                                </motion.div>
66
                            )
67
                        })}
68
                    </div>
69
70
                    <motion.button
71
                        whileHover={{ scale: 1.05 }}
72
                        whileTap={{ scale: 0.95 }}
73
                        onClick={on_fill_bots}
74
                        style={{ ...styles.button, backgroundColor: '#ff9800', marginBottom: 16 }}
75
                    >
76
                        Fill with Bots
77
                    </motion.button>
78
79
                    <p style={styles.hint}>Share room code with friends to join</p>
80
                </motion.div>
81
            </div>
82
        )
83
    }
84
85
    return (
86
        <div style={styles.container}>
87
            <motion.div
88
                initial={{ opacity: 0, y: 20 }}
89
                animate={{ opacity: 1, y: 0 }}
90
                style={styles.card}
91
            >
92
                <h1 style={styles.logo}>掼蛋</h1>
93
                <h2 style={styles.title}>Guan Dan</h2>
94
95
                {mode === 'select' && (
96
                    <div style={styles.buttons}>
97
                        <motion.button
98
                            whileHover={{ scale: 1.05 }}
99
                            whileTap={{ scale: 0.95 }}
100
                            onClick={() => set_mode('create')}
101
                            style={styles.button}
102
                        >
103
                            Create Room
104
                        </motion.button>
105
                        <motion.button
106
                            whileHover={{ scale: 1.05 }}
107
                            whileTap={{ scale: 0.95 }}
108
                            onClick={() => set_mode('join')}
109
                            style={{ ...styles.button, backgroundColor: '#28a745' }}
110
                        >
111
                            Join Room
112
                        </motion.button>
113
                    </div>
114
                )}
115
116
                {mode === 'create' && (
117
                    <div style={styles.form}>
118
                        <input
119
                            type="text"
120
                            placeholder="Your name"
121
                            value={name}
122
                            onChange={(e) => set_name(e.target.value)}
123
                            style={styles.input}
124
                        />
125
                        <div style={styles.buttons}>
126
                            <motion.button
127
                                whileHover={{ scale: 1.05 }}
128
                                whileTap={{ scale: 0.95 }}
129
                                onClick={handle_create}
130
                                style={styles.button}
131
                            >
132
                                Create
133
                            </motion.button>
134
                            <motion.button
135
                                whileHover={{ scale: 1.05 }}
136
                                whileTap={{ scale: 0.95 }}
137
                                onClick={() => set_mode('select')}
138
                                style={{ ...styles.button, backgroundColor: '#6c757d' }}
139
                            >
140
                                Back
141
                            </motion.button>
142
                        </div>
143
                    </div>
144
                )}
145
146
                {mode === 'join' && (
147
                    <div style={styles.form}>
148
                        <input
149
                            type="text"
150
                            placeholder="Your name"
151
                            value={name}
152
                            onChange={(e) => set_name(e.target.value)}
153
                            style={styles.input}
154
                        />
155
                        <input
156
                            type="text"
157
                            placeholder="Room code"
158
                            value={join_code}
159
                            onChange={(e) => set_join_code(e.target.value)}
160
                            style={styles.input}
161
                        />
162
                        <div style={styles.buttons}>
163
                            <motion.button
164
                                whileHover={{ scale: 1.05 }}
165
                                whileTap={{ scale: 0.95 }}
166
                                onClick={handle_join}
167
                                style={{ ...styles.button, backgroundColor: '#28a745' }}
168
                            >
169
                                Join
170
                            </motion.button>
171
                            <motion.button
172
                                whileHover={{ scale: 1.05 }}
173
                                whileTap={{ scale: 0.95 }}
174
                                onClick={() => set_mode('select')}
175
                                style={{ ...styles.button, backgroundColor: '#6c757d' }}
176
                            >
177
                                Back
178
                            </motion.button>
179
                        </div>
180
                    </div>
181
                )}
182
            </motion.div>
183
        </div>
184
    )
185
}
186
187
const styles: Record<string, React.CSSProperties> = {
188
    container: {
189
        display: 'flex',
190
        justifyContent: 'center',
191
        alignItems: 'center',
192
        minHeight: '100vh',
193
        backgroundColor: '#1a1a2e',
194
    },
195
    card: {
196
        backgroundColor: '#16213e',
197
        padding: 40,
198
        borderRadius: 16,
199
        textAlign: 'center',
200
        boxShadow: '0 8px 32px rgba(0,0,0,0.3)',
201
        minWidth: 360,
202
    },
203
    logo: {
204
        fontSize: 64,
205
        margin: 0,
206
        color: '#fff',
207
    },
208
    title: {
209
        color: '#fff',
210
        marginTop: 8,
211
        marginBottom: 24,
212
    },
213
    subtitle: {
214
        color: '#aaa',
215
        marginBottom: 24,
216
    },
217
    buttons: {
218
        display: 'flex',
219
        gap: 12,
220
        justifyContent: 'center',
221
    },
222
    button: {
223
        padding: '12px 24px',
224
        fontSize: 16,
225
        border: 'none',
226
        borderRadius: 8,
227
        backgroundColor: '#007bff',
228
        color: '#fff',
229
        cursor: 'pointer',
230
    },
231
    form: {
232
        display: 'flex',
233
        flexDirection: 'column',
234
        gap: 12,
235
    },
236
    input: {
237
        padding: '12px 16px',
238
        fontSize: 16,
239
        border: '2px solid #333',
240
        borderRadius: 8,
241
        backgroundColor: '#0f3460',
242
        color: '#fff',
243
        outline: 'none',
244
    },
245
    players_grid: {
246
        display: 'grid',
247
        gridTemplateColumns: '1fr 1fr',
248
        gap: 12,
249
        marginBottom: 24,
250
    },
251
    player_slot: {
252
        padding: 16,
253
        borderRadius: 8,
254
        border: '2px solid',
255
    },
256
    player_name: {
257
        fontWeight: 'bold',
258
        color: '#333',
259
    },
260
    player_team: {
261
        fontSize: 12,
262
        color: '#666',
263
    },
264
    empty_slot: {
265
        color: '#999',
266
    },
267
    hint: {
268
        color: '#666',
269
        fontSize: 12,
270
    },
271
}