add: absorb uno-online as regular subdirectory

UNO card game web app (Node.js/React) with Miku bot integration.
Previously an independent git repo (fork of mizanxali/uno-online).
Removed .git/ and absorbed into main repo for unified tracking.

Includes bot integration code: botActionExecutor, cardParser,
gameStateBuilder, and server-side bot action support.
37 files, node_modules excluded via local .gitignore.
This commit is contained in:
2026-03-04 00:21:38 +02:00
parent c708770266
commit 34b184a05a
37 changed files with 26885 additions and 0 deletions

158
uno-online/server.js Normal file
View File

@@ -0,0 +1,158 @@
const express = require('express')
const socketio = require('socket.io')
const http = require('http')
const cors = require('cors')
const { addUser, removeUser, getUser, getUsersInRoom } = require('./users')
const path = require('path')
const PORT = process.env.PORT || 5000
const app = express()
const server = http.createServer(app)
const io = socketio(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
})
app.use(cors())
// Store latest game states by room code for bot API access
const gameStates = new Map()
// HTTP endpoint for bot to get game state
app.get('/api/game/:roomCode/state', (req, res) => {
const roomCode = req.params.roomCode
const state = gameStates.get(roomCode)
if (state) {
res.json({
success: true,
gameState: state,
timestamp: new Date().toISOString()
})
} else {
res.status(404).json({
success: false,
error: 'Game room not found or no state available'
})
}
})
// HTTP endpoint for bot to submit action
app.post('/api/game/:roomCode/action', express.json(), (req, res) => {
const roomCode = req.params.roomCode
const action = req.body
// minimal log for incoming bot action
console.log(`[Bot HTTP Action] Room: ${roomCode}`)
// Emit action to the game room via socket
io.to(roomCode).emit('botActionReceived', action)
res.json({
success: true,
message: 'Action received and forwarded to game'
})
})
io.on('connection', socket => {
socket.on('join', (payload, callback) => {
let numberOfUsersInRoom = getUsersInRoom(payload.room).length
const { error, newUser} = addUser({
id: socket.id,
name: numberOfUsersInRoom===0 ? 'Player 1' : 'Player 2',
room: payload.room
})
if(error)
return callback(error)
socket.join(newUser.room)
io.to(newUser.room).emit('roomData', {room: newUser.room, users: getUsersInRoom(newUser.room)})
socket.emit('currentUserData', {name: newUser.name})
callback()
})
socket.on('initGameState', gameState => {
const user = getUser(socket.id)
if(user)
io.to(user.room).emit('initGameState', gameState)
})
socket.on('updateGameState', gameState => {
const user = getUser(socket.id)
if(user) {
io.to(user.room).emit('updateGameState', gameState)
// Also update stored game state for bot REST API access
const currentState = gameStates.get(user.room) || {}
gameStates.set(user.room, {
...currentState,
...gameState,
room: user.room,
lastUpdate: new Date().toISOString()
})
}
})
socket.on('sendMessage', (payload, callback) => {
const user = getUser(socket.id)
io.to(user.room).emit('message', {user: user.name, text: payload.message})
callback()
})
// Bot integration: receive game state from Player 2 (bot)
socket.on('botGameState', gameState => {
const user = getUser(socket.id)
if(user && user.name === 'Player 2') {
// Store latest game state for bot access (do not log full JSON to avoid noise)
gameStates.set(user.room, {
...gameState,
room: user.room,
lastUpdate: new Date().toISOString()
})
}
})
// Bot integration: receive bot action (play card or draw)
socket.on('botAction', (action, callback) => {
const user = getUser(socket.id)
if(user && user.name === 'Player 2') {
// Forward action to game logic. Client will handle via regular game state updates
callback && callback({ success: true })
}
})
// Bot integration: request current game state
socket.on('requestGameState', (callback) => {
const user = getUser(socket.id)
if(user) {
// Request game state from room
socket.to(user.room).emit('gameStateRequested')
callback && callback({ success: true })
}
})
socket.on('disconnect', () => {
const user = removeUser(socket.id)
if(user)
io.to(user.room).emit('roomData', {room: user.room, users: getUsersInRoom(user.room)})
})
})
//serve static assets in production
if(process.env.NODE_ENV === 'production') {
//set static folder
app.use(express.static('client/build'))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'))
})
}
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})