This commit is contained in:
Sewmina 2025-04-21 11:15:53 +05:30
commit 6bbf8cfaef
9 changed files with 1609 additions and 0 deletions

136
.gitignore vendored Normal file
View 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

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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"]
}