feat(cd): add Matrix notification on deploy failure

Sends a message to a Matrix room when a deploy fails, including
the failing services, commit, deployer, and a link to the logs.

Requires two GitHub Actions secrets:
- DEPLOY_NOTIFY_ROOM_ID: Matrix room ID
- DEPLOY_NOTIFY_BOT_TOKEN: Matrix bot access token

Skips silently if secrets are not configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-20 19:47:53 +01:00
parent 8c2aa261e8
commit 8511c2ca4c
22 changed files with 2684 additions and 0 deletions

View file

@ -48,6 +48,8 @@ env:
PROJECT_DIR: /Users/mana/projects/manacore-monorepo
COMPOSE_FILE: docker-compose.macmini.yml
ENV_FILE: .env.macmini
DEPLOY_NOTIFY_ROOM_ID: ${{ secrets.DEPLOY_NOTIFY_ROOM_ID }}
DEPLOY_NOTIFY_BOT_TOKEN: ${{ secrets.DEPLOY_NOTIFY_BOT_TOKEN }}
PATH: /usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin
jobs:
@ -411,6 +413,33 @@ jobs:
push_deploy_metrics "$STATUS" "$DURATION" "$BRANCH" 2>/dev/null || true
echo "Deploy tracking recorded: status=$STATUS duration=${DURATION}s"
- name: Notify on failure
if: failure()
run: |
cd "${{ env.PROJECT_DIR }}"
SERVICES="${{ steps.services.outputs.services }}"
[ "${{ steps.services.outputs.deploy-all }}" == "true" ] && SERVICES="all"
COMMIT_MSG=$(git log -1 --pretty=%s 2>/dev/null | head -c 100)
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
MSG="⚠️ **Deploy failed**\n\n**Services:** ${SERVICES}\n**Commit:** ${COMMIT_MSG}\n**By:** ${{ github.actor }}\n**[View logs](${RUN_URL})**"
# Send to Matrix deploy-notifications room via Synapse API
ROOM_ID="${DEPLOY_NOTIFY_ROOM_ID:-}"
BOT_TOKEN="${DEPLOY_NOTIFY_BOT_TOKEN:-}"
if [ -n "$ROOM_ID" ] && [ -n "$BOT_TOKEN" ]; then
TXN_ID="deploy-$(date +%s)"
curl -s -X PUT \
"http://localhost:8008/_matrix/client/v3/rooms/${ROOM_ID}/send/m.room.message/${TXN_ID}" \
-H "Authorization: Bearer ${BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"msgtype\":\"m.text\",\"body\":\"Deploy failed: ${SERVICES}\",\"format\":\"org.matrix.custom.html\",\"formatted_body\":\"$(echo -e "$MSG" | sed 's/"/\\"/g')\"}" \
|| true
echo "Matrix notification sent"
else
echo "Matrix notification skipped (DEPLOY_NOTIFY_ROOM_ID or DEPLOY_NOTIFY_BOT_TOKEN not set)"
fi
- name: Cleanup old images
if: always()
run: |

27
games/whopixels/.gitignore vendored Normal file
View file

@ -0,0 +1,27 @@
# Umgebungsvariablen
.env
# Node.js
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
package-lock.json
# Logs
logs
*.log
# Betriebssystem-Dateien
.DS_Store
Thumbs.db
# IDE und Editor Dateien
.idea/
.vscode/
*.swp
*.swo
# Build-Verzeichnisse
dist/
build/

74
games/whopixels/README.md Normal file
View file

@ -0,0 +1,74 @@
# WhoPixels
Ein webbasiertes Pixel-Spiel, entwickelt mit Phaser.js.
Projekt Starten:
node server.js
## Über das Projekt
WhoPixels ist ein einfaches Pixel-Art-Editor-Spiel, in dem du deine eigenen Pixel-Kunstwerke erstellen kannst. Das Projekt verwendet Phaser.js, eine leistungsstarke HTML5-Spieleentwicklungsbibliothek.
## Funktionen
- Interaktives Pixel-Art-Editor-Interface
- Farbpalette mit 8 Grundfarben
- Einfache und intuitive Benutzeroberfläche
- Responsive Design
## Erste Schritte
Um das Spiel lokal zu starten, benötigst du einen lokalen Webserver. Du kannst einen einfachen Server mit Python oder Node.js starten.
### Mit Python:
```bash
# Python 3
python -m http.server
# Python 2
python -m SimpleHTTPServer
```
### Mit Node.js:
Installiere zuerst das `http-server`-Paket:
```bash
npm install -g http-server
```
Dann starte den Server:
```bash
http-server
```
## Projektstruktur
```
whopixels/
├── assets/ # Spielressourcen (Bilder, Sounds, etc.)
├── css/ # CSS-Stylesheets
├── js/ # JavaScript-Dateien
│ ├── scenes/ # Phaser-Szenen
│ │ ├── BootScene.js
│ │ ├── MainMenuScene.js
│ │ └── GameScene.js
│ └── main.js # Hauptspieldatei
└── index.html # Haupt-HTML-Datei
```
## Weiterentwicklung
Hier sind einige Ideen für zukünftige Erweiterungen:
- Speichern und Laden von Pixel-Art
- Mehr Werkzeuge (Pinsel, Radierer, Füllen, etc.)
- Animation-Editor
- Teilen von Kunstwerken
- Mehrere Ebenen für komplexere Designs
## Lizenz
Dieses Projekt ist Open Source und steht unter der MIT-Lizenz.

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Background Image</title>
<style>
body { margin: 0; background: #222233; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Fill background
ctx.fillStyle = '#222233';
ctx.fillRect(0, 0, 800, 600);
// Add some pattern
ctx.fillStyle = '#1a1a2a';
for (let i = 0; i < 100; i++) {
const x = Math.random() * 800;
const y = Math.random() * 600;
const size = Math.random() * 5 + 2;
ctx.fillRect(x, y, size, size);
}
// Instructions
ctx.fillStyle = '#ffffff';
ctx.font = '20px Arial';
ctx.fillText('Right-click and save this image as background.png', 200, 300);
</script>
</body>
</html>

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<title>Create Placeholder Images</title>
</head>
<body>
<h1>Creating placeholder images...</h1>
<canvas id="backgroundCanvas" width="800" height="600" style="display: none;"></canvas>
<canvas id="playerCanvas" width="32" height="32" style="display: none;"></canvas>
<canvas id="tileCanvas" width="32" height="32" style="display: none;"></canvas>
<div id="downloadLinks"></div>
<script>
// Create background image
const bgCanvas = document.getElementById('backgroundCanvas');
const bgCtx = bgCanvas.getContext('2d');
bgCtx.fillStyle = '#222233';
bgCtx.fillRect(0, 0, 800, 600);
// Add some pattern to background
bgCtx.fillStyle = '#1a1a2a';
for (let i = 0; i < 100; i++) {
const x = Math.random() * 800;
const y = Math.random() * 600;
const size = Math.random() * 5 + 2;
bgCtx.fillRect(x, y, size, size);
}
// Create player image
const playerCanvas = document.getElementById('playerCanvas');
const playerCtx = playerCanvas.getContext('2d');
playerCtx.fillStyle = '#ff0000';
playerCtx.fillRect(0, 0, 32, 32);
playerCtx.fillStyle = '#ff5555';
playerCtx.fillRect(8, 8, 16, 16);
// Create tile image
const tileCanvas = document.getElementById('tileCanvas');
const tileCtx = tileCanvas.getContext('2d');
tileCtx.fillStyle = '#ffffff';
tileCtx.fillRect(0, 0, 32, 32);
tileCtx.strokeStyle = '#cccccc';
tileCtx.lineWidth = 1;
tileCtx.strokeRect(0.5, 0.5, 31, 31);
// Create download links
const downloadDiv = document.getElementById('downloadLinks');
function createDownloadLink(canvas, filename) {
const link = document.createElement('a');
link.download = filename;
link.href = canvas.toDataURL('image/png');
link.textContent = `Download ${filename}`;
link.style.display = 'block';
link.style.margin = '10px';
downloadDiv.appendChild(link);
// Auto-click to download
setTimeout(() => link.click(), 500);
}
createDownloadLink(bgCanvas, 'background.png');
createDownloadLink(playerCanvas, 'player.png');
createDownloadLink(tileCanvas, 'tile.png');
</script>
</body>
</html>

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>Player Image</title>
<style>
body { margin: 0; background: #333; display: flex; justify-content: center; align-items: center; height: 100vh; }
canvas { display: block; border: 1px solid #fff; background: #000; }
</style>
</head>
<body>
<canvas id="canvas" width="32" height="32"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Draw player
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, 32, 32);
ctx.fillStyle = '#ff5555';
ctx.fillRect(8, 8, 16, 16);
// Instructions (shown in console)
console.log('Right-click and save this image as player.png');
</script>
</body>
</html>

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,54 @@
// Simple script to create basic placeholder images
// Just open this in a browser and it will create data URLs you can copy
document.body.innerHTML = `
<h1>Placeholder Images for WhoPixels</h1>
<div>
<h2>Background (800x600)</h2>
<canvas id="bg" width="800" height="600" style="border:1px solid #000; max-width: 100%;"></canvas>
<p id="bgData"></p>
</div>
<div>
<h2>Player (32x32)</h2>
<canvas id="player" width="32" height="32" style="border:1px solid #000;"></canvas>
<p id="playerData"></p>
</div>
<div>
<h2>Tile (32x32)</h2>
<canvas id="tile" width="32" height="32" style="border:1px solid #000;"></canvas>
<p id="tileData"></p>
</div>
`;
// Draw background
const bgCanvas = document.getElementById('bg');
const bgCtx = bgCanvas.getContext('2d');
bgCtx.fillStyle = '#222233';
bgCtx.fillRect(0, 0, 800, 600);
for (let i = 0; i < 100; i++) {
bgCtx.fillStyle = '#1a1a2a';
const x = Math.random() * 800;
const y = Math.random() * 600;
const size = Math.random() * 5 + 2;
bgCtx.fillRect(x, y, size, size);
}
document.getElementById('bgData').textContent = 'Save this image as background.png';
// Draw player
const playerCanvas = document.getElementById('player');
const playerCtx = playerCanvas.getContext('2d');
playerCtx.fillStyle = '#ff0000';
playerCtx.fillRect(0, 0, 32, 32);
playerCtx.fillStyle = '#ff5555';
playerCtx.fillRect(8, 8, 16, 16);
document.getElementById('playerData').textContent = 'Save this image as player.png';
// Draw tile
const tileCanvas = document.getElementById('tile');
const tileCtx = tileCanvas.getContext('2d');
tileCtx.fillStyle = '#ffffff';
tileCtx.fillRect(0, 0, 32, 32);
tileCtx.strokeStyle = '#cccccc';
tileCtx.lineWidth = 1;
tileCtx.strokeRect(0.5, 0.5, 31, 31);
document.getElementById('tileData').textContent = 'Save this image as tile.png';

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>Tile Image</title>
<style>
body { margin: 0; background: #333; display: flex; justify-content: center; align-items: center; height: 100vh; }
canvas { display: block; border: 1px solid #fff; background: #000; }
</style>
</head>
<body>
<canvas id="canvas" width="32" height="32"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Draw tile
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, 32, 32);
ctx.strokeStyle = '#cccccc';
ctx.lineWidth = 1;
ctx.strokeRect(0.5, 0.5, 31, 31);
// Instructions (shown in console)
console.log('Right-click and save this image as tile.png');
</script>
</body>
</html>

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,14 @@
body {
margin: 0;
padding: 0;
background-color: #000;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: 'Arial', sans-serif;
}
#game-container {
box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
}

View file

@ -0,0 +1,83 @@
// Liste der NPC-Charaktere mit Namen und Persönlichkeiten
// NPC-Charaktere: Berühmte Erfinder durch die Historie
const npcCharacters = [
{
id: 1,
name: 'Leonardo da Vinci',
personality:
'Ein vielseitiger Universalgelehrter der Renaissance, bekannt für seine Kunst und Erfindungen. Er spricht nachdenklich und philosophisch, oft mit Metaphern über Natur und Kunst. Er ist neugierig und beobachtet alles genau.',
hint: 'Meine Skizzenbücher enthalten Flugmaschinen und anatomische Studien, die ihrer Zeit weit voraus waren.',
},
{
id: 2,
name: 'Nikola Tesla',
personality:
'Ein exzentrischer Elektroingenieur mit visionären Ideen. Er spricht leidenschaftlich über Elektrizität und drahtlose Energieübertragung. Er ist brillant, aber auch etwas eigenartig und distanziert.',
hint: 'Meine Arbeiten mit Wechselstrom revolutionierten die Art, wie wir Energie nutzen.',
},
{
id: 3,
name: 'Marie Curie',
personality:
'Eine entschlossene und präzise Wissenschaftlerin, die für ihre Entdeckungen im Bereich der Radioaktivität bekannt ist. Sie spricht klar und methodisch, mit einem starken Fokus auf wissenschaftliche Genauigkeit.',
hint: 'Meine Forschung zu radioaktiven Elementen brachte mir zwei Nobelpreise ein, obwohl sie letztendlich meine Gesundheit beeinträchtigte.',
},
{
id: 4,
name: 'Thomas Edison',
personality:
'Ein pragmatischer und geschäftstüchtiger Erfinder mit über 1.000 Patenten. Er spricht direkt und selbstbewusst, oft mit praktischen Beispielen. Er betont harte Arbeit und Ausdauer über Inspiration.',
hint: 'Meine Erfindung brachte Licht in die Dunkelheit und veränderte die Art, wie Menschen nach Sonnenuntergang leben.',
},
{
id: 5,
name: 'Ada Lovelace',
personality:
'Eine visionäre Mathematikerin des 19. Jahrhunderts mit einer einzigartigen Verbindung von Logik und Kreativität. Sie spricht eloquent und präzise, mit einer Mischung aus poetischer und mathematischer Sprache.',
hint: 'Ich schrieb den ersten Algorithmus für eine Maschine, lange bevor Computer existierten.',
},
{
id: 6,
name: 'Archimedes',
personality:
'Ein genialer antiker Mathematiker und Erfinder aus Syrakus. Er ist von mathematischen Problemen fasziniert und kann sich darin verlieren. Er spricht mit Begeisterung über Geometrie und physikalische Prinzipien.',
hint: "Mein berühmtester Ausruf war 'Heureka!' als ich das Prinzip des Auftriebs in der Badewanne entdeckte.",
},
{
id: 7,
name: 'Johannes Gutenberg',
personality:
'Ein geduldiger und präziser Handwerker, der die Druckkunst revolutionierte. Er spricht bescheiden über seine Erfindung, aber mit Stolz über deren Auswirkungen auf die Verbreitung von Wissen.',
hint: 'Meine Erfindung machte Bücher für die Massen zugänglich und veränderte die Verbreitung von Wissen für immer.',
},
{
id: 8,
name: 'Grace Hopper',
personality:
'Eine pragmatische und humorvolle Computerpionierin und Marineoffizierin. Sie erklärt komplexe Konzepte mit einfachen Analogien und hat einen trockenen Humor. Sie ist direkt und lösungsorientiert.',
hint: "Ich entwickelte den ersten Compiler und fand einmal einen echten 'Bug' im Computer - eine Motte, die einen Fehler verursachte.",
},
{
id: 9,
name: 'Alexander Graham Bell',
personality:
'Ein einfallsreicher und geduldiger Erfinder, der sich für Kommunikation und Gehörlose engagierte. Er spricht deutlich und artikuliert, mit einem schottischen Akzent. Er ist enthusiastisch, wenn er über seine Erfindungen spricht.',
hint: 'Meine Erfindung ermöglichte es Menschen, über große Entfernungen miteinander zu sprechen.',
},
{
id: 10,
name: 'Hedy Lamarr',
personality:
'Eine glamouröse Hollywoodschauspielerin mit einem brillanten technischen Verstand. Sie spricht charmant und selbstbewusst, mit einer Mischung aus Eleganz und technischem Scharfsinn. Sie ist kreativ und unkonventionell.',
hint: 'Meine Erfindung der Frequenzsprungverfahren bildet die Grundlage für moderne WLAN- und Bluetooth-Technologien, obwohl viele mich nur als Filmstar kennen.',
},
];
// Mache die Charaktere sowohl im Browser als auch in Node.js verfügbar
if (typeof window !== 'undefined') {
// Browser-Umgebung
window.npcCharacters = npcCharacters;
} else if (typeof module !== 'undefined') {
// Node.js-Umgebung
module.exports = npcCharacters;
}

View file

@ -0,0 +1,47 @@
// This script uses Node.js to generate placeholder images for our game
const fs = require('fs');
const { createCanvas } = require('canvas');
// Create background image (800x600)
const bgCanvas = createCanvas(800, 600);
const bgCtx = bgCanvas.getContext('2d');
bgCtx.fillStyle = '#222233';
bgCtx.fillRect(0, 0, 800, 600);
// Add some pattern to background
bgCtx.fillStyle = '#1a1a2a';
for (let i = 0; i < 100; i++) {
const x = Math.random() * 800;
const y = Math.random() * 600;
const size = Math.random() * 5 + 2;
bgCtx.fillRect(x, y, size, size);
}
// Create player image (32x32)
const playerCanvas = createCanvas(32, 32);
const playerCtx = playerCanvas.getContext('2d');
playerCtx.fillStyle = '#ff0000';
playerCtx.fillRect(0, 0, 32, 32);
playerCtx.fillStyle = '#ff5555';
playerCtx.fillRect(8, 8, 16, 16);
// Create tile image (32x32)
const tileCanvas = createCanvas(32, 32);
const tileCtx = tileCanvas.getContext('2d');
tileCtx.fillStyle = '#ffffff';
tileCtx.fillRect(0, 0, 32, 32);
tileCtx.strokeStyle = '#cccccc';
tileCtx.lineWidth = 1;
tileCtx.strokeRect(0.5, 0.5, 31, 31);
// Save images
const bgBuffer = bgCanvas.toBuffer('image/png');
fs.writeFileSync('./assets/background.png', bgBuffer);
const playerBuffer = playerCanvas.toBuffer('image/png');
fs.writeFileSync('./assets/player.png', playerBuffer);
const tileBuffer = tileCanvas.toBuffer('image/png');
fs.writeFileSync('./assets/tile.png', tileBuffer);
console.log('All placeholder images have been generated!');

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WhoPixels - Pixel Game</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="game-container"></div>
<!-- Phaser Library -->
<script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.min.js"></script>
<!-- Game Data -->
<script src="data/npc_characters.js"></script>
<!-- Game Scripts -->
<script src="js/scenes/BootScene.js"></script>
<script src="js/scenes/MainMenuScene.js"></script>
<script src="js/scenes/GameScene.js"></script>
<script src="js/scenes/RPGScene.js"></script>
<script src="js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,18 @@
// Game configuration
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
pixelArt: true,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false,
},
},
scene: [BootScene, MainMenuScene, GameScene, RPGScene],
};
// Create and start the game
const game = new Phaser.Game(config);

View file

@ -0,0 +1,445 @@
class BootScene extends Phaser.Scene {
constructor() {
super({ key: 'BootScene' });
}
preload() {
// Loading screen
this.graphics = this.add.graphics();
this.newGraphics = this.add.graphics();
const progressBar = new Phaser.Geom.Rectangle(200, 300, 400, 50);
const progressBarFill = new Phaser.Geom.Rectangle(205, 305, 290, 40);
this.graphics.fillStyle(0xffffff, 1);
this.graphics.fillRectShape(progressBar);
this.newGraphics.fillStyle(0x3587e2, 1);
this.newGraphics.fillRectShape(progressBarFill);
const loadingText = this.add.text(250, 260, 'Loading: ', { fontSize: '32px', fill: '#FFF' });
// Update as load progresses
this.load.on('progress', (percent) => {
loadingText.setText('Loading: ' + parseInt(percent * 100) + '%');
progressBarFill.width = 390 * percent;
this.newGraphics.clear();
this.newGraphics.fillStyle(0x3587e2, 1);
this.newGraphics.fillRectShape(progressBarFill);
});
this.load.on('complete', () => {
loadingText.destroy();
this.graphics.destroy();
this.newGraphics.destroy();
});
// We'll create graphics objects instead of loading images
}
create() {
// Create a texture for background
const bgGraphics = this.make.graphics({ x: 0, y: 0 });
bgGraphics.fillStyle(0x222233);
bgGraphics.fillRect(0, 0, 800, 600);
// Add some pattern to background
bgGraphics.fillStyle(0x1a1a2a);
for (let i = 0; i < 100; i++) {
const x = Math.random() * 800;
const y = Math.random() * 600;
const size = Math.random() * 5 + 2;
bgGraphics.fillRect(x, y, size, size);
}
// Erstelle ein Partikel-Sprite für Spezialeffekte
const particleGraphics = this.make.graphics({ x: 0, y: 0 });
particleGraphics.fillStyle(0xffffff);
particleGraphics.fillCircle(4, 4, 4);
particleGraphics.generateTexture('particle', 8, 8);
bgGraphics.generateTexture('background', 800, 600);
// Create a texture for player (pixel editor)
const playerGraphics = this.make.graphics({ x: 0, y: 0 });
playerGraphics.fillStyle(0xff0000);
playerGraphics.fillRect(0, 0, 32, 32);
playerGraphics.fillStyle(0xff5555);
playerGraphics.fillRect(8, 8, 16, 16);
playerGraphics.generateTexture('player', 32, 32);
// Erstelle Texturen für verschiedene Tile-Typen (8x8 Pixel Tiles, skaliert auf 32x32)
this.createTileTextures();
// Erstelle NPC-Texturen
this.createNPCTextures();
// Create a texture for basic tile
const tileGraphics = this.make.graphics({ x: 0, y: 0 });
tileGraphics.fillStyle(0xffffff);
tileGraphics.fillRect(0, 0, 32, 32);
tileGraphics.lineStyle(1, 0xcccccc);
tileGraphics.strokeRect(0, 0, 32, 32);
tileGraphics.generateTexture('tile', 32, 32);
// Create player walk animation frames (4 directions)
this.createPlayerWalkAnimations();
this.scene.start('MainMenuScene');
}
createPlayerWalkAnimations() {
// Erstelle eine Spritesheet-Textur für den Spieler im RPG
const frameWidth = 32;
const frameHeight = 32;
// Farbpalette für den Spieler
const colors = {
body: 0x3366cc, // Blauer Körper
face: 0xffcc99, // Hautfarbe für Gesicht
hair: 0x663300, // Braune Haare
shirt: 0x339933, // Grünes Hemd
pants: 0x333366, // Dunkelblaue Hose
shoes: 0x663300, // Braune Schuhe
outline: 0x000000, // Schwarze Umrisse
};
// Gemeinsame Funktion zum Zeichnen der Grundform des Spielers
const drawPlayerBase = (graphics) => {
// Umriss
graphics.lineStyle(1, colors.outline);
// Körper (Torso)
graphics.fillStyle(colors.shirt);
graphics.fillRect(10, 12, 12, 10);
graphics.strokeRect(10, 12, 12, 10);
// Kopf
graphics.fillStyle(colors.face);
graphics.fillRect(10, 4, 12, 8);
graphics.strokeRect(10, 4, 12, 8);
// Haare
graphics.fillStyle(colors.hair);
graphics.fillRect(10, 4, 12, 3);
graphics.strokeRect(10, 4, 12, 3);
// Hose
graphics.fillStyle(colors.pants);
graphics.fillRect(10, 22, 12, 6);
graphics.strokeRect(10, 22, 12, 6);
};
// Nach unten (0)
const downGraphics = this.make.graphics({ x: 0, y: 0 });
drawPlayerBase(downGraphics);
// Gesicht nach unten
downGraphics.fillStyle(colors.outline);
downGraphics.fillRect(14, 8, 1, 1); // Linkes Auge
downGraphics.fillRect(17, 8, 1, 1); // Rechtes Auge
downGraphics.fillRect(15, 10, 2, 1); // Mund
// Beine und Schuhe nach unten
downGraphics.fillStyle(colors.pants);
downGraphics.fillRect(12, 28, 3, 2); // Linkes Bein
downGraphics.fillRect(17, 28, 3, 2); // Rechtes Bein
downGraphics.fillStyle(colors.shoes);
downGraphics.fillRect(12, 30, 3, 2); // Linker Schuh
downGraphics.fillRect(17, 30, 3, 2); // Rechter Schuh
downGraphics.lineStyle(1, colors.outline);
downGraphics.strokeRect(12, 28, 3, 4); // Linkes Bein Umriss
downGraphics.strokeRect(17, 28, 3, 4); // Rechtes Bein Umriss
downGraphics.generateTexture('player_down', frameWidth, frameHeight);
// Nach oben (1)
const upGraphics = this.make.graphics({ x: 0, y: 0 });
drawPlayerBase(upGraphics);
// Rücken der Haare
upGraphics.fillStyle(colors.hair);
upGraphics.fillRect(10, 2, 12, 2);
upGraphics.lineStyle(1, colors.outline);
upGraphics.strokeRect(10, 2, 12, 2);
// Beine und Schuhe nach oben
upGraphics.fillStyle(colors.pants);
upGraphics.fillRect(12, 28, 3, 2); // Linkes Bein
upGraphics.fillRect(17, 28, 3, 2); // Rechtes Bein
upGraphics.fillStyle(colors.shoes);
upGraphics.fillRect(12, 30, 3, 2); // Linker Schuh
upGraphics.fillRect(17, 30, 3, 2); // Rechter Schuh
upGraphics.lineStyle(1, colors.outline);
upGraphics.strokeRect(12, 28, 3, 4); // Linkes Bein Umriss
upGraphics.strokeRect(17, 28, 3, 4); // Rechtes Bein Umriss
upGraphics.generateTexture('player_up', frameWidth, frameHeight);
// Nach links (2)
const leftGraphics = this.make.graphics({ x: 0, y: 0 });
drawPlayerBase(leftGraphics);
// Gesicht nach links
leftGraphics.fillStyle(colors.outline);
leftGraphics.fillRect(12, 8, 1, 1); // Auge
leftGraphics.fillRect(11, 10, 2, 1); // Mund
// Arm nach links
leftGraphics.fillStyle(colors.shirt);
leftGraphics.fillRect(6, 14, 4, 3);
leftGraphics.lineStyle(1, colors.outline);
leftGraphics.strokeRect(6, 14, 4, 3);
// Beine und Schuhe nach links
leftGraphics.fillStyle(colors.pants);
leftGraphics.fillRect(12, 28, 3, 2); // Linkes Bein
leftGraphics.fillRect(15, 28, 3, 2); // Rechtes Bein
leftGraphics.fillStyle(colors.shoes);
leftGraphics.fillRect(9, 30, 6, 2); // Schuhe
leftGraphics.lineStyle(1, colors.outline);
leftGraphics.strokeRect(9, 28, 9, 4); // Beine Umriss
leftGraphics.generateTexture('player_left', frameWidth, frameHeight);
// Nach rechts (3)
const rightGraphics = this.make.graphics({ x: 0, y: 0 });
drawPlayerBase(rightGraphics);
// Gesicht nach rechts
rightGraphics.fillStyle(colors.outline);
rightGraphics.fillRect(19, 8, 1, 1); // Auge
rightGraphics.fillRect(19, 10, 2, 1); // Mund
// Arm nach rechts
rightGraphics.fillStyle(colors.shirt);
rightGraphics.fillRect(22, 14, 4, 3);
rightGraphics.lineStyle(1, colors.outline);
rightGraphics.strokeRect(22, 14, 4, 3);
// Beine und Schuhe nach rechts
rightGraphics.fillStyle(colors.pants);
rightGraphics.fillRect(14, 28, 3, 2); // Linkes Bein
rightGraphics.fillRect(17, 28, 3, 2); // Rechtes Bein
rightGraphics.fillStyle(colors.shoes);
rightGraphics.fillRect(17, 30, 6, 2); // Schuhe
rightGraphics.lineStyle(1, colors.outline);
rightGraphics.strokeRect(14, 28, 9, 4); // Beine Umriss
rightGraphics.generateTexture('player_right', frameWidth, frameHeight);
}
createTileTextures() {
const tileSize = 32; // Größe jedes Tiles
// 1. Gras
const grassGraphics = this.make.graphics({ x: 0, y: 0 });
grassGraphics.fillStyle(0x88aa44); // Grün für Gras
grassGraphics.fillRect(0, 0, tileSize, tileSize);
// Kleine Texturen für Gras
grassGraphics.fillStyle(0x779933);
for (let i = 0; i < 8; i++) {
const x = Math.random() * tileSize;
const y = Math.random() * tileSize;
grassGraphics.fillRect(x, y, 2, 2);
}
grassGraphics.generateTexture('tile_grass', tileSize, tileSize);
// 2. Gras mit Blumen
const grassFlowerGraphics = this.make.graphics({ x: 0, y: 0 });
grassFlowerGraphics.fillStyle(0x88aa44); // Grün für Gras
grassFlowerGraphics.fillRect(0, 0, tileSize, tileSize);
// Kleine Texturen für Gras
grassFlowerGraphics.fillStyle(0x779933);
for (let i = 0; i < 5; i++) {
const x = Math.random() * tileSize;
const y = Math.random() * tileSize;
grassFlowerGraphics.fillRect(x, y, 2, 2);
}
// Blumen hinzufügen
grassFlowerGraphics.fillStyle(0xffff00); // Gelb für Blumen
for (let i = 0; i < 3; i++) {
const x = 5 + Math.random() * (tileSize - 10);
const y = 5 + Math.random() * (tileSize - 10);
grassFlowerGraphics.fillRect(x, y, 3, 3);
}
grassFlowerGraphics.fillStyle(0xff5555); // Rot für Blumen
for (let i = 0; i < 2; i++) {
const x = 5 + Math.random() * (tileSize - 10);
const y = 5 + Math.random() * (tileSize - 10);
grassFlowerGraphics.fillRect(x, y, 3, 3);
}
grassFlowerGraphics.generateTexture('tile_grass_flower', tileSize, tileSize);
// 3. Erde
const dirtGraphics = this.make.graphics({ x: 0, y: 0 });
dirtGraphics.fillStyle(0x8b4513); // Braun für Erde
dirtGraphics.fillRect(0, 0, tileSize, tileSize);
// Kleine Texturen für Erde
dirtGraphics.fillStyle(0x6b3304);
for (let i = 0; i < 10; i++) {
const x = Math.random() * tileSize;
const y = Math.random() * tileSize;
dirtGraphics.fillRect(x, y, 2, 2);
}
dirtGraphics.generateTexture('tile_dirt', tileSize, tileSize);
// 4. Erde mit Steinen
const dirtStoneGraphics = this.make.graphics({ x: 0, y: 0 });
dirtStoneGraphics.fillStyle(0x8b4513); // Braun für Erde
dirtStoneGraphics.fillRect(0, 0, tileSize, tileSize);
// Kleine Texturen für Erde
dirtStoneGraphics.fillStyle(0x6b3304);
for (let i = 0; i < 6; i++) {
const x = Math.random() * tileSize;
const y = Math.random() * tileSize;
dirtStoneGraphics.fillRect(x, y, 2, 2);
}
// Steine hinzufügen
dirtStoneGraphics.fillStyle(0x888888); // Grau für Steine
for (let i = 0; i < 4; i++) {
const size = 3 + Math.random() * 4;
const x = Math.random() * (tileSize - size);
const y = Math.random() * (tileSize - size);
dirtStoneGraphics.fillRect(x, y, size, size);
}
dirtStoneGraphics.generateTexture('tile_dirt_stone', tileSize, tileSize);
// 5. Steinwand
const stoneWallGraphics = this.make.graphics({ x: 0, y: 0 });
stoneWallGraphics.fillStyle(0x777777); // Grau für Steinwand
stoneWallGraphics.fillRect(0, 0, tileSize, tileSize);
// Steinmuster für die Wand
stoneWallGraphics.fillStyle(0x555555);
const brickHeight = 8;
const brickWidth = 16;
for (let y = 0; y < tileSize; y += brickHeight) {
const offset = y % (brickHeight * 2) === 0 ? 0 : brickWidth / 2;
for (let x = offset; x < tileSize; x += brickWidth) {
stoneWallGraphics.fillRect(x, y, brickWidth - 1, brickHeight - 1);
}
}
stoneWallGraphics.generateTexture('tile_stone_wall', tileSize, tileSize);
// 6. Steinwand mit Blumen/Moos
const stoneWallFlowerGraphics = this.make.graphics({ x: 0, y: 0 });
stoneWallFlowerGraphics.fillStyle(0x777777); // Grau für Steinwand
stoneWallFlowerGraphics.fillRect(0, 0, tileSize, tileSize);
// Steinmuster für die Wand
stoneWallFlowerGraphics.fillStyle(0x555555);
for (let y = 0; y < tileSize; y += brickHeight) {
const offset = y % (brickHeight * 2) === 0 ? 0 : brickWidth / 2;
for (let x = offset; x < tileSize; x += brickWidth) {
stoneWallFlowerGraphics.fillRect(x, y, brickWidth - 1, brickHeight - 1);
}
}
// Moos/Blumen an der Wand
stoneWallFlowerGraphics.fillStyle(0x55aa55); // Grün für Moos
for (let i = 0; i < 6; i++) {
const x = Math.random() * tileSize;
const y = Math.random() * tileSize;
const size = 2 + Math.random() * 3;
stoneWallFlowerGraphics.fillRect(x, y, size, size);
}
stoneWallFlowerGraphics.fillStyle(0xffff00); // Gelb für Blumen
for (let i = 0; i < 2; i++) {
const x = 5 + Math.random() * (tileSize - 10);
const y = 5 + Math.random() * (tileSize - 10);
stoneWallFlowerGraphics.fillRect(x, y, 3, 3);
}
stoneWallFlowerGraphics.generateTexture('tile_stone_wall_flower', tileSize, tileSize);
}
createNPCTextures() {
const frameWidth = 32;
const frameHeight = 32;
// Farbpalette für den NPC
const colors = {
body: 0xcc6633, // Bräunlicher Körper
face: 0xffcc99, // Hautfarbe für Gesicht
hair: 0x996600, // Blonde Haare
shirt: 0xcc3333, // Rotes Hemd
pants: 0x333333, // Schwarze Hose
shoes: 0x663300, // Braune Schuhe
outline: 0x000000, // Schwarze Umrisse
};
// Gemeinsame Funktion zum Zeichnen der Grundform des NPCs
const drawNPCBase = (graphics) => {
// Umriss
graphics.lineStyle(1, colors.outline);
// Körper (Torso)
graphics.fillStyle(colors.shirt);
graphics.fillRect(10, 12, 12, 10);
graphics.strokeRect(10, 12, 12, 10);
// Kopf
graphics.fillStyle(colors.face);
graphics.fillRect(10, 4, 12, 8);
graphics.strokeRect(10, 4, 12, 8);
// Haare
graphics.fillStyle(colors.hair);
graphics.fillRect(10, 4, 12, 3);
graphics.strokeRect(10, 4, 12, 3);
// Hose
graphics.fillStyle(colors.pants);
graphics.fillRect(10, 22, 12, 6);
graphics.strokeRect(10, 22, 12, 6);
};
// NPC nach unten schauend
const npcDownGraphics = this.make.graphics({ x: 0, y: 0 });
drawNPCBase(npcDownGraphics);
// Gesicht nach unten
npcDownGraphics.fillStyle(colors.outline);
npcDownGraphics.fillRect(14, 8, 1, 1); // Linkes Auge
npcDownGraphics.fillRect(17, 8, 1, 1); // Rechtes Auge
npcDownGraphics.fillRect(15, 10, 2, 1); // Mund
// Beine und Schuhe nach unten
npcDownGraphics.fillStyle(colors.pants);
npcDownGraphics.fillRect(12, 28, 3, 2); // Linkes Bein
npcDownGraphics.fillRect(17, 28, 3, 2); // Rechtes Bein
npcDownGraphics.fillStyle(colors.shoes);
npcDownGraphics.fillRect(12, 30, 3, 2); // Linker Schuh
npcDownGraphics.fillRect(17, 30, 3, 2); // Rechter Schuh
npcDownGraphics.lineStyle(1, colors.outline);
npcDownGraphics.strokeRect(12, 28, 3, 4); // Linkes Bein Umriss
npcDownGraphics.strokeRect(17, 28, 3, 4); // Rechtes Bein Umriss
npcDownGraphics.generateTexture('npc_down', frameWidth, frameHeight);
// NPC nach oben schauend
const npcUpGraphics = this.make.graphics({ x: 0, y: 0 });
drawNPCBase(npcUpGraphics);
// Rücken der Haare
npcUpGraphics.fillStyle(colors.hair);
npcUpGraphics.fillRect(10, 2, 12, 2);
npcUpGraphics.lineStyle(1, colors.outline);
npcUpGraphics.strokeRect(10, 2, 12, 2);
// Beine und Schuhe nach oben
npcUpGraphics.fillStyle(colors.pants);
npcUpGraphics.fillRect(12, 28, 3, 2); // Linkes Bein
npcUpGraphics.fillRect(17, 28, 3, 2); // Rechtes Bein
npcUpGraphics.fillStyle(colors.shoes);
npcUpGraphics.fillRect(12, 30, 3, 2); // Linker Schuh
npcUpGraphics.fillRect(17, 30, 3, 2); // Rechter Schuh
npcUpGraphics.lineStyle(1, colors.outline);
npcUpGraphics.strokeRect(12, 28, 3, 4); // Linkes Bein Umriss
npcUpGraphics.strokeRect(17, 28, 3, 4); // Rechtes Bein Umriss
npcUpGraphics.generateTexture('npc_up', frameWidth, frameHeight);
}
}

View file

@ -0,0 +1,113 @@
class GameScene extends Phaser.Scene {
constructor() {
super({ key: 'GameScene' });
}
create() {
// Add background
this.add.image(400, 300, 'background');
// Create grid for pixel art (16x16 grid of 32x32 pixel tiles)
this.grid = [];
this.tileSize = 32;
this.gridWidth = 16;
this.gridHeight = 16;
this.gridStartX = (800 - this.gridWidth * this.tileSize) / 2;
this.gridStartY = (600 - this.gridHeight * this.tileSize) / 2;
// Create grid of tiles
for (let y = 0; y < this.gridHeight; y++) {
this.grid[y] = [];
for (let x = 0; x < this.gridWidth; x++) {
const tile = this.add.image(
this.gridStartX + x * this.tileSize + this.tileSize / 2,
this.gridStartY + y * this.tileSize + this.tileSize / 2,
'tile'
);
tile.setTint(0xffffff); // Default white color
tile.setInteractive();
tile.on('pointerdown', () => {
this.paintTile(x, y);
});
this.grid[y][x] = tile;
}
}
// Current selected color (default: black)
this.currentColor = 0x000000;
// Create color palette
this.createColorPalette();
// Add UI text
this.add
.text(400, 50, 'Pixel Editor', {
fontSize: '32px',
fill: '#fff',
})
.setOrigin(0.5);
// Add back button
const backButton = this.add
.text(100, 50, 'Zurück', {
fontSize: '24px',
fill: '#fff',
backgroundColor: '#4a4a4a',
padding: { x: 10, y: 5 },
})
.setOrigin(0.5)
.setInteractive();
backButton.on('pointerover', () => {
backButton.setStyle({ fill: '#ff0' });
});
backButton.on('pointerout', () => {
backButton.setStyle({ fill: '#fff' });
});
backButton.on('pointerdown', () => {
this.scene.start('MainMenuScene');
});
}
createColorPalette() {
const colors = [
0x000000, // Black
0xffffff, // White
0xff0000, // Red
0x00ff00, // Green
0x0000ff, // Blue
0xffff00, // Yellow
0xff00ff, // Magenta
0x00ffff, // Cyan
];
const paletteX = 700;
const paletteY = 150;
const paletteSize = 30;
const paletteGap = 10;
colors.forEach((color, index) => {
const colorButton = this.add.rectangle(
paletteX,
paletteY + index * (paletteSize + paletteGap),
paletteSize,
paletteSize,
color
);
colorButton.setInteractive();
colorButton.on('pointerdown', () => {
this.currentColor = color;
});
// Add stroke around the button
colorButton.setStrokeStyle(2, 0xffffff);
});
}
paintTile(x, y) {
this.grid[y][x].setTint(this.currentColor);
}
}

View file

@ -0,0 +1,98 @@
class MainMenuScene extends Phaser.Scene {
constructor() {
super({ key: 'MainMenuScene' });
}
create() {
// Add background
this.add.image(400, 300, 'background');
// Add title
this.add
.text(400, 120, 'WhoPixels', {
fontSize: '64px',
fill: '#fff',
fontStyle: 'bold',
})
.setOrigin(0.5);
// Add subtitle
this.add
.text(400, 180, 'Ein Pixel-Abenteuer', {
fontSize: '32px',
fill: '#fff',
})
.setOrigin(0.5);
// Create buttons
const startButton = this.add
.text(400, 280, 'RPG Spiel starten', {
fontSize: '32px',
fill: '#fff',
backgroundColor: '#4a4a4a',
padding: { x: 20, y: 10 },
})
.setOrigin(0.5)
.setInteractive();
const editorButton = this.add
.text(400, 350, 'Pixel Editor', {
fontSize: '32px',
fill: '#fff',
backgroundColor: '#4a4a4a',
padding: { x: 20, y: 10 },
})
.setOrigin(0.5)
.setInteractive();
const optionsButton = this.add
.text(400, 420, 'Optionen', {
fontSize: '32px',
fill: '#fff',
backgroundColor: '#4a4a4a',
padding: { x: 20, y: 10 },
})
.setOrigin(0.5)
.setInteractive();
// Button interactions - RPG Game
startButton.on('pointerover', () => {
startButton.setStyle({ fill: '#ff0' });
});
startButton.on('pointerout', () => {
startButton.setStyle({ fill: '#fff' });
});
startButton.on('pointerdown', () => {
this.scene.start('RPGScene');
});
// Button interactions - Pixel Editor
editorButton.on('pointerover', () => {
editorButton.setStyle({ fill: '#ff0' });
});
editorButton.on('pointerout', () => {
editorButton.setStyle({ fill: '#fff' });
});
editorButton.on('pointerdown', () => {
this.scene.start('GameScene');
});
// Button interactions - Options
optionsButton.on('pointerover', () => {
optionsButton.setStyle({ fill: '#ff0' });
});
optionsButton.on('pointerout', () => {
optionsButton.setStyle({ fill: '#fff' });
});
optionsButton.on('pointerdown', () => {
// Options functionality would go here
console.log('Options button clicked');
});
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
{
"dependencies": {
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0"
}
}

282
games/whopixels/server.js Normal file
View file

@ -0,0 +1,282 @@
const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
// Lade Umgebungsvariablen aus .env-Datei
require('dotenv').config();
// Für die Verarbeitung von POST-Anfragen
const { parse } = require('querystring');
// Konfiguration
const PORT = 3000;
// Azure OpenAI API Konfiguration aus Umgebungsvariablen
const AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;
const AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT;
const AZURE_OPENAI_DEPLOYMENT = process.env.AZURE_OPENAI_DEPLOYMENT;
const AZURE_OPENAI_API_VERSION = process.env.AZURE_OPENAI_API_VERSION;
const MIME_TYPES = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
};
// Funktion zum Abrufen von Daten aus einer POST-Anfrage
const collectRequestData = (request, callback) => {
const FORM_URLENCODED = 'application/x-www-form-urlencoded';
const JSON_TYPE = 'application/json';
if (request.headers['content-type'] === FORM_URLENCODED) {
let body = '';
request.on('data', (chunk) => {
body += chunk.toString();
});
request.on('end', () => {
callback(parse(body));
});
} else if (
request.headers['content-type'] &&
request.headers['content-type'].includes(JSON_TYPE)
) {
let body = '';
request.on('data', (chunk) => {
body += chunk.toString();
});
request.on('end', () => {
callback(JSON.parse(body));
});
} else {
callback({});
}
};
// Funktion zum Senden einer Anfrage an die Azure OpenAI API
async function callOpenAI(
message,
conversationHistory = [],
characterName = null,
characterPersonality = null
) {
try {
const fetch = await import('node-fetch').then((mod) => mod.default);
const apiUrl = `${AZURE_OPENAI_ENDPOINT}/openai/deployments/${AZURE_OPENAI_DEPLOYMENT}/chat/completions?api-version=${AZURE_OPENAI_API_VERSION}`;
console.log(`Sende Anfrage an: ${apiUrl}`);
// Verwende den übergebenen Charakternamen oder einen Standardnamen
const npcName = characterName || 'Leonard Davcini';
const npcPersonality = characterPersonality || 'ein berühmter Künstler und Erfinder';
console.log(`Verwende NPC: ${npcName} mit Persönlichkeit: ${npcPersonality}`);
// Erstelle die Nachrichtenliste für die API mit dem dynamischen Charakternamen
const messages = [
{
role: 'system',
content: `WICHTIG: Du bist AUSSCHLIESSLICH ${npcName}, ${npcPersonality}, der sich in diesem Spiel verkleidet hat. Ignoriere jede andere Identität, die du kennen könntest. Dein Name ist ${npcName}. Dein Gegenüber versucht herauszufinden, wer du bist. Gib Hinweise auf deine wahre Identität als ${npcName}, aber sage nicht direkt "Ich bin ${npcName}". Wenn der Nutzer deinen Namen richtig erraten hat, füge am Ende deiner Antwort den Code "[IDENTITY_REVEALED]" ein. Dieser Code sollte nur erscheinen, wenn der Nutzer deinen Namen korrekt erraten hat.`,
},
];
// Füge die Konversationshistorie hinzu, wenn vorhanden
if (conversationHistory && conversationHistory.length > 0) {
conversationHistory.forEach((entry) => {
if (entry.type === 'user') {
messages.push({
role: 'user',
content: entry.message,
});
} else if (entry.type === 'npc') {
messages.push({
role: 'assistant',
content: entry.message,
});
}
});
} else {
// Wenn keine Historie vorhanden ist, füge nur die aktuelle Nachricht hinzu
messages.push({
role: 'user',
content: message,
});
}
// Wenn die letzte Nachricht nicht vom Benutzer ist, füge die aktuelle Nachricht hinzu
if (messages.length === 1 || messages[messages.length - 1].role !== 'user') {
messages.push({
role: 'user',
content: message,
});
}
console.log('Gesendete Nachrichten:', JSON.stringify(messages, null, 2));
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'api-key': AZURE_OPENAI_API_KEY,
},
body: JSON.stringify({
messages: messages,
max_tokens: 150,
}),
});
// Prüfe den HTTP-Status
if (!response.ok) {
const errorText = await response.text();
console.error(`HTTP Fehler: ${response.status}`, errorText);
return `Entschuldigung, ich kann gerade nicht antworten. (HTTP ${response.status})`;
}
const data = await response.json();
console.log('API-Antwort:', JSON.stringify(data, null, 2));
if (data.error) {
console.error('Azure OpenAI API Fehler:', data.error);
return {
text: 'Entschuldigung, ich kann gerade nicht antworten. Versuche es später noch einmal.',
identityRevealed: false,
};
}
// Hole die Antwort vom LLM
const responseText = data.choices[0].message.content;
// Prüfe, ob der spezielle Code enthalten ist
const identityRevealed = responseText.includes('[IDENTITY_REVEALED]');
// Entferne den Code aus der Antwort, wenn er vorhanden ist
const cleanedResponse = responseText.replace('[IDENTITY_REVEALED]', '').trim();
console.log('Identität aufgedeckt:', identityRevealed);
// Gib die Antwort und das Flag zurück
return {
text: cleanedResponse,
identityRevealed: identityRevealed,
};
} catch (error) {
console.error('Fehler beim Aufrufen der Azure OpenAI API:', error);
return {
text: 'Entschuldigung, ich kann gerade nicht antworten. Versuche es später noch einmal.',
identityRevealed: false,
};
}
}
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`);
// CORS-Header hinzufügen für Cross-Origin-Anfragen
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// OPTIONS-Anfragen für CORS-Preflight behandeln
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// API-Endpunkt für OpenAI-Anfragen
if (req.method === 'POST' && req.url === '/api/chat') {
collectRequestData(req, async (data) => {
try {
if (!data.message) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Nachricht fehlt' }));
return;
}
// Verwende die Konversationshistorie, wenn vorhanden
const conversationHistory = data.conversationHistory || [];
console.log(
'Erhaltene Konversationshistorie:',
JSON.stringify(conversationHistory, null, 2)
);
// Extrahiere Charakterinformationen, wenn vorhanden
const characterName = data.characterName;
const characterPersonality = data.characterPersonality;
if (characterName) {
console.log(`NPC-Charakter in der Anfrage: ${characterName}`);
}
const response = await callOpenAI(
data.message,
conversationHistory,
characterName,
characterPersonality
);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
response: response.text,
identityRevealed: response.identityRevealed,
})
);
} catch (error) {
console.error('Fehler bei der Verarbeitung der Chat-Anfrage:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Interner Serverfehler' }));
}
});
return;
}
// Statische Dateien behandeln
let filePath = '.' + req.url;
if (filePath === './') {
filePath = './index.html';
}
// Get the file extension
const extname = path.extname(filePath);
const contentType = MIME_TYPES[extname] || 'application/octet-stream';
// Read the file
fs.readFile(filePath, (error, content) => {
if (error) {
if (error.code === 'ENOENT') {
// Page not found
fs.readFile('./index.html', (err, content) => {
if (err) {
res.writeHead(500);
res.end('Error loading index.html');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(content, 'utf-8');
}
});
} else {
// Server error
res.writeHead(500);
res.end(`Server Error: ${error.code}`);
}
} else {
// Success
res.writeHead(200, { 'Content-Type': contentType });
res.end(content, 'utf-8');
}
});
});
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
console.log('Press Ctrl+C to stop the server');
console.log('Azure OpenAI API ist konfiguriert und bereit!');
});