init
This commit is contained in:
commit
6bbf8cfaef
136
.gitignore
vendored
Normal file
136
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# vitepress build output
|
||||||
|
**/.vitepress/dist
|
||||||
|
|
||||||
|
# vitepress cache directory
|
||||||
|
**/.vitepress/cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
1279
package-lock.json
generated
Normal file
1279
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
package.json
Normal file
24
package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "duelfi-game-manager",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "tsc",
|
||||||
|
"serve": "node dist/index.js"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.1",
|
||||||
|
"@types/node": "^22.14.1",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fs": "^0.0.1-security",
|
||||||
|
"tcp-port-used": "^1.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/data.ts
Normal file
18
src/data.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { Settings } from "./types";
|
||||||
|
|
||||||
|
export const settings:Settings = {
|
||||||
|
games:[
|
||||||
|
{
|
||||||
|
name:"tetris",
|
||||||
|
path:"/root/games/tetris/tetris",
|
||||||
|
timeout:250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name:"snakes",
|
||||||
|
path:"/root/games/snakes/snakes",
|
||||||
|
timeout:250
|
||||||
|
}
|
||||||
|
],
|
||||||
|
port_range_start:6000,
|
||||||
|
port_range_end:8000,
|
||||||
|
}
|
||||||
42
src/game_starter.ts
Normal file
42
src/game_starter.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { settings } from "./data";
|
||||||
|
import { Log } from "./logger";
|
||||||
|
import type { Game } from "./types";
|
||||||
|
import net from 'node:net';
|
||||||
|
|
||||||
|
const tcpPortUsed = require('tcp-port-used');
|
||||||
|
|
||||||
|
|
||||||
|
export async function StartGame(gameName:string, address:string) : Promise<Game | undefined> {
|
||||||
|
const game = settings.games.find(game => game.name === gameName);
|
||||||
|
if(!game){
|
||||||
|
Log(`Game ${gameName} not found`, 'game_starter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let port = 2;
|
||||||
|
let portInUse = true;
|
||||||
|
while(portInUse) {
|
||||||
|
port = Math.floor(Math.random() * (settings.port_range_end - settings.port_range_start + 1)) + settings.port_range_start;
|
||||||
|
//Check if port is already in use
|
||||||
|
portInUse = await tcpPortUsed.check(port, '127.0.0.1');
|
||||||
|
if(portInUse){
|
||||||
|
Log(`Port ${port} is already in use, trying another port`, 'game_starter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const gameProcess = spawn(game.path, [port.toString()]);
|
||||||
|
|
||||||
|
gameProcess.on('error', (error) => {
|
||||||
|
Log(`Error starting game ${gameName}: ${error.message}`, 'game_starter');
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: gameName,
|
||||||
|
address: address,
|
||||||
|
pid: gameProcess.pid ?? 2,
|
||||||
|
port: port,
|
||||||
|
init_time: new Date().getTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
71
src/index.ts
Normal file
71
src/index.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import express, { type Request, type Response } from 'express';
|
||||||
|
import { settings } from './data';
|
||||||
|
import type { Game } from './types';
|
||||||
|
import { StartGame } from './game_starter';
|
||||||
|
import { Log } from './logger';
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = process.env.PORT || 3035;
|
||||||
|
|
||||||
|
//dictionary of games with address as key
|
||||||
|
const games:Record<string, Game> = {};
|
||||||
|
|
||||||
|
function killGameProcess(pid: number) {
|
||||||
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
spawn('taskkill', ['/pid', pid.toString(), '/f']);
|
||||||
|
} else {
|
||||||
|
// For Linux/Unix systems
|
||||||
|
spawn('kill', ['-9', pid.toString()]);
|
||||||
|
}
|
||||||
|
Log(`Killed game process with PID ${pid}`, 'game_manager');
|
||||||
|
} catch (error) {
|
||||||
|
Log(`Error killing process ${pid}: ${error}`, 'game_manager');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/getGamePort', async (req: Request, res: Response) => {
|
||||||
|
const {gameName, address} = req.query;
|
||||||
|
|
||||||
|
if(!gameName || !address){
|
||||||
|
res.status(400).send('Missing gameName or address');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addressString = address.toString();
|
||||||
|
let game:Game | undefined;
|
||||||
|
if(!games[addressString]){
|
||||||
|
game = await StartGame(gameName.toString(), addressString);
|
||||||
|
if(game){
|
||||||
|
games[addressString] = game;
|
||||||
|
|
||||||
|
// Get the game timeout from settings
|
||||||
|
const gameSettings = settings.games.find(g => g.name === gameName);
|
||||||
|
if (gameSettings?.timeout) {
|
||||||
|
// Set timeout to kill the game
|
||||||
|
setTimeout(() => {
|
||||||
|
if (game?.pid) {
|
||||||
|
killGameProcess(game.pid);
|
||||||
|
delete games[addressString];
|
||||||
|
}
|
||||||
|
}, gameSettings.timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
game = games[addressString];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!game){
|
||||||
|
res.status(500).send('Failed to start game');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gamePort = game.port;
|
||||||
|
res.send(`${gamePort}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server running at http://localhost:${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
6
src/logger.ts
Normal file
6
src/logger.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
export function Log(message: string, title = 'log') {
|
||||||
|
console.log(`[${new Date().toISOString()}] ${title}: ${message}`);
|
||||||
|
fs.appendFileSync(`logs/${title}.txt`, `${new Date().toISOString()}: ${message}\n`);
|
||||||
|
}
|
||||||
20
src/types.ts
Normal file
20
src/types.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export interface Game{
|
||||||
|
pid:number;
|
||||||
|
address:string;
|
||||||
|
name:string;
|
||||||
|
port:number;
|
||||||
|
init_time:number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface Settings{
|
||||||
|
games:GameData[];
|
||||||
|
port_range_start:number;
|
||||||
|
port_range_end:number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameData{
|
||||||
|
name:string;
|
||||||
|
path:string;
|
||||||
|
timeout:number;
|
||||||
|
}
|
||||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user