guandan.dev

guandan.dev

https://git.tonybtw.com/guandan.dev.git git://git.tonybtw.com/guandan.dev.git
7,325 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
}