Skip to content

Instantly share code, notes, and snippets.

@secdev02
Created January 28, 2025 12:53
Show Gist options
  • Save secdev02/2a8f1c6fc0cce3d4a5d94d8637a16786 to your computer and use it in GitHub Desktop.
Save secdev02/2a8f1c6fc0cce3d4a5d94d8637a16786 to your computer and use it in GitHub Desktop.
NetworkExplorer-Nodes-Protocols-HunttheWumpusStyle
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Network Explorer</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<style>
body { margin: 0; overflow: hidden; font-family: Arial; background: #f0f0f0; }
.node {
stroke: #fff;
stroke-width: 1.5px;
cursor: pointer;
transition: all 0.3s ease;
}
.node:hover { stroke: #000; stroke-width: 2px; }
.link { stroke: #999; stroke-opacity: 0.6; }
.hidden { display: none; }
.revealed { opacity: 1; }
.unexplored { opacity: 0.3; }
.game-config {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.config-item {
margin: 15px 0;
}
.config-item label {
display: block;
margin-bottom: 5px;
}
.config-item input[type="number"] {
width: 100px;
padding: 5px;
}
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.checkbox-group label {
display: inline-block;
margin-left: 5px;
}
.power-ups {
margin-top: 15px;
padding-top: 10px;
border-top: 1px solid #ccc;
}
.power-ups button {
background: #9c27b0;
margin: 5px;
font-size: 12px;
}
.power-ups button:disabled {
background: #cccccc;
cursor: not-allowed;
}
.property-selector {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
z-index: 2000;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.property-button {
margin: 5px;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.game-controls {
position: fixed;
top: 20px;
left: 20px;
background: white;
padding: 15px;
border-radius: 8px;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.game-stats {
position: fixed;
top: 20px;
right: 20px;
background: white;
padding: 15px;
border-radius: 8px;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.game-over {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.95);
color: white;
padding: 30px;
border-radius: 15px;
text-align: center;
display: none;
z-index: 2000;
}
.node-label {
pointer-events: none;
text-shadow: -1px -1px 0 #fff,
1px -1px 0 #fff,
-1px 1px 0 #fff,
1px 1px 0 #fff;
font-weight: bold;
}
.property-tag {
font-size: 10px;
pointer-events: none;
text-shadow: -1px -1px 0 #fff,
1px -1px 0 #fff,
-1px 1px 0 #fff,
1px 1px 0 #fff;
}
button {
padding: 10px 20px;
border-radius: 6px;
border: none;
background: #4CAF50;
color: white;
cursor: pointer;
font-size: 14px;
}
button:hover { background: #45a049; }
.danger-indicator {
color: red;
font-weight: bold;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
</style>
</head>
<body>
<div class="game-config" id="gameConfig">
<h3>Network Explorer Configuration</h3>
<div class="config-item">
<label for="nodeCount">Number of Nodes (10-200):</label>
<input type="number" id="nodeCount" min="10" max="200" value="100">
</div>
<div class="config-item">
<label for="dangerCount">Dangerous Nodes (1-20):</label>
<input type="number" id="dangerCount" min="1" max="20" value="5">
</div>
<div class="config-item">
<h4>Connection Types:</h4>
<div class="checkbox-group">
<input type="checkbox" id="rdp" checked><label for="rdp">RDP</label>
<input type="checkbox" id="ssh" checked><label for="ssh">SSH</label>
<input type="checkbox" id="smb" checked><label for="smb">SMB</label>
<input type="checkbox" id="winrm" checked><label for="winrm">WinRM</label>
<input type="checkbox" id="http"><label for="http">HTTP</label>
<input type="checkbox" id="ftp"><label for="ftp">FTP</label>
</div>
</div>
<button onclick="startGame()">Start Game</button>
</div>
<div class="game-controls" style="display: none;">
<h3>Network Explorer</h3>
<button onclick="location.reload()">New Game</button>
<p>Click nodes to explore the network</p>
<p class="danger-indicator">⚠️ Warning: Dangerous nodes exist!</p>
<div class="power-ups">
<h4>Power-ups (One-time use)</h4>
<button id="revealProperty" onclick="game.showPropertySelector()">Reveal Property</button>
<button id="revealDanger" onclick="game.revealDangerousNodes()">Reveal Dangers</button>
</div>
</div>
<div id="propertySelector" class="property-selector" style="display: none;">
<h4>Select Property to Reveal</h4>
<div id="propertyButtons"></div>
<button onclick="document.getElementById('propertySelector').style.display='none'">Cancel</button>
</div>
<div class="game-stats">
<div>Explored: <span id="exploredCount">0</span></div>
<div>Connected: <span id="connectedCount">0</span></div>
<div>Danger: <span id="dangerSense">Safe</span></div>
</div>
<div class="game-over">
<h2 id="gameOverText"></h2>
<p id="gameOverStats"></p>
<button onclick="location.reload()">Play Again</button>
</div>
<script>
const prefixes = ['Captain', 'Doctor', 'Agent', 'Professor', 'Master', 'Quantum', 'Cyber', 'Digital', 'Binary', 'Tech'];
const names = ['Nexus', 'Circuit', 'Spark', 'Vector', 'Matrix', 'Cipher', 'Protocol', 'Cache', 'Proxy', 'Socket'];
function generateHeroName() {
return `${prefixes[Math.floor(Math.random() * prefixes.length)]} ${names[Math.floor(Math.random() * names.length)]}`;
}
class NetworkGame {
constructor(nodeCount = 100, dangerCount = 5, propertyTypes = ['SSH', 'RDP', 'SMB', 'WinRM']) {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.nodes = [];
this.links = [];
this.revealedNodes = new Set();
this.gameOver = false;
this.dangerousNodes = new Set();
this.nodeCount = Math.min(Math.max(nodeCount, 10), 200);
this.dangerCount = Math.min(Math.max(dangerCount, 1), 20);
this.propertyTypes = propertyTypes;
this.svg = d3.select("body")
.append("svg")
.attr("width", this.width)
.attr("height", this.height);
this.generateNetwork();
this.setupSimulation();
window.addEventListener('resize', () => {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.svg.attr('width', this.width).attr('height', this.height);
this.simulation.force('center', d3.forceCenter(this.width / 2, this.height / 2));
this.simulation.alpha(1).restart();
});
}
generateNetwork() {
// Generate nodes
for (let i = 0; i < this.nodeCount; i++) {
const numProperties = Math.floor(Math.random() * 2) + 1;
const properties = new Set();
while (properties.size < numProperties) {
properties.add(this.propertyTypes[Math.floor(Math.random() * this.propertyTypes.length)]);
}
this.nodes.push({
id: `Node${i}`,
heroName: generateHeroName(),
properties: Array.from(properties),
revealed: false,
isDangerous: false,
isHub: Math.random() < 0.15 // 15% chance of being a hub
});
}
// Set dangerous nodes
const shuffled = [...this.nodes];
for (let i = 0; i < this.dangerCount; i++) {
const randomIndex = Math.floor(Math.random() * shuffled.length);
shuffled[randomIndex].isDangerous = true;
this.dangerousNodes.add(shuffled[randomIndex].id);
}
// Generate hub-and-spoke connections with random additional connections
this.nodes.forEach((node, idx) => {
if (node.isHub) {
// Hub nodes connect to more nodes
const maxConnections = Math.floor(Math.random() * 8) + 5; // 5-12 connections
let connections = 0;
// Shuffle nodes to randomize connections
const potentialTargets = [...this.nodes]
.filter(n => n.id !== node.id)
.sort(() => Math.random() - 0.5);
potentialTargets.forEach(target => {
if (connections >= maxConnections) return;
const sharedProps = node.properties.filter(
prop => target.properties.includes(prop)
);
if (sharedProps.length > 0 && Math.random() < 0.7) {
this.links.push({
source: node.id,
target: target.id,
property: sharedProps[0]
});
connections++;
}
});
} else {
// Non-hub nodes have fewer connections
const maxConnections = Math.floor(Math.random() * 3) + 1; // 1-3 connections
let connections = 0;
// Find nodes with shared properties
const potentialTargets = [...this.nodes]
.filter(n => n.id !== node.id)
.sort(() => Math.random() - 0.5);
potentialTargets.forEach(target => {
if (connections >= maxConnections) return;
const sharedProps = node.properties.filter(
prop => target.properties.includes(prop)
);
if (sharedProps.length > 0 && Math.random() < 0.3) {
this.links.push({
source: node.id,
target: target.id,
property: sharedProps[0]
});
connections++;
}
});
}
});
// Ensure all nodes have at least one connection
this.nodes.forEach(node => {
if (!this.links.some(link => link.source.id === node.id || link.target.id === node.id)) {
// Find a suitable connection for isolated nodes
const potentialTargets = this.nodes.filter(n =>
n.id !== node.id &&
n.properties.some(prop => node.properties.includes(prop))
);
if (potentialTargets.length > 0) {
const target = potentialTargets[Math.floor(Math.random() * potentialTargets.length)];
const sharedProp = node.properties.find(prop => target.properties.includes(prop));
this.links.push({
source: node.id,
target: target.id,
property: sharedProp
});
}
}
});
// Reveal starting node
this.nodes[0].revealed = true;
this.revealedNodes.add(this.nodes[0].id);
this.updateConnectedNodes(this.nodes[0]);
}
setupSimulation() {
this.simulation = d3.forceSimulation(this.nodes)
.force("link", d3.forceLink(this.links).id(d => d.id).distance(150))
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(this.width / 2, this.height / 2))
.force("collision", d3.forceCollide().radius(50));
this.link = this.svg.append("g")
.selectAll("line")
.data(this.links)
.enter()
.append("line")
.attr("class", "link")
.attr("stroke-width", 2);
this.nodeGroup = this.svg.append("g")
.selectAll("g")
.data(this.nodes)
.enter()
.append("g")
.attr("class", "node-group");
this.nodeGroup.append("circle")
.attr("r", 15)
.attr("class", "node")
.attr("fill", d => this.getNodeColor(d))
.on("click", (event, d) => this.handleNodeClick(d));
this.nodeGroup.append("text")
.attr("class", "node-label")
.attr("dy", -20)
.attr("text-anchor", "middle")
.style("font-size", "10px")
.text(d => d.heroName);
this.nodeGroup.each((d, i, nodes) => {
d.properties.forEach((prop, index) => {
d3.select(nodes[i])
.append("text")
.attr("class", "property-tag")
.attr("dy", 25 + index * 12)
.attr("text-anchor", "middle")
.text(prop);
});
});
this.simulation.on("tick", () => {
this.link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
this.nodeGroup.attr("transform", d => `translate(${d.x},${d.y})`);
});
this.updateVisibility();
}
showPropertySelector() {
if (this.gameOver || this.powerUpsUsed.property) return;
const selector = document.getElementById('propertySelector');
const buttonContainer = document.getElementById('propertyButtons');
buttonContainer.innerHTML = '';
this.propertyTypes.forEach(prop => {
const button = document.createElement('button');
button.className = 'property-button';
button.style.backgroundColor = this.getNodeColor({ properties: [prop] });
button.style.color = 'white';
button.textContent = prop;
button.onclick = () => this.revealPropertyNodes(prop);
buttonContainer.appendChild(button);
});
selector.style.display = 'block';
}
revealPropertyNodes(property) {
if (this.gameOver || this.powerUpsUsed.property) return;
this.powerUpsUsed.property = true;
document.getElementById('revealProperty').disabled = true;
document.getElementById('propertySelector').style.display = 'none';
const propertyNodes = this.nodes.filter(node =>
node.properties.includes(property) && !node.isDangerous
);
propertyNodes.forEach(node => {
node.revealed = true;
this.revealedNodes.add(node.id);
});
this.updateVisibility();
this.updateStats();
}
revealDangerousNodes() {
if (this.gameOver || this.powerUpsUsed.danger) return;
this.powerUpsUsed.danger = true;
document.getElementById('revealDanger').disabled = true;
const dangerNodes = this.nodes.filter(node => node.isDangerous);
dangerNodes.forEach(node => {
node.temporarilyRevealed = true;
});
this.updateVisibility();
// Hide dangerous nodes after 3 seconds
setTimeout(() => {
dangerNodes.forEach(node => {
node.temporarilyRevealed = false;
});
this.updateVisibility();
}, 3000);
}
getNodeColor(node) {
if (!node.revealed && !node.temporarilyRevealed && this.isConnectedToRevealed(node)) return "#666";
if (node.isDangerous && node.revealed) return "red";
const colorMap = {
'SSH': '#2ca02c',
'RDP': '#ff7f0e',
'SMB': '#9467bd',
'WinRM': '#d62728',
'HTTP': '#17becf',
'FTP': '#1f77b4'
};
return colorMap[node.properties[0]] || '#8c564b';
}
isConnectedToRevealed(node) {
return this.links.some(link =>
(link.source.id === node.id && this.revealedNodes.has(link.target.id)) ||
(link.target.id === node.id && this.revealedNodes.has(link.source.id))
);
}
updateVisibility() {
this.nodeGroup.style("display", d =>
d.revealed || this.isConnectedToRevealed(d) ? null : "none"
);
this.nodeGroup.selectAll("circle")
.attr("fill", d => this.getNodeColor(d));
this.link.style("display", d =>
(this.revealedNodes.has(d.source.id) || this.revealedNodes.has(d.target.id))
? null : "none"
);
this.nodeGroup.selectAll("text")
.style("opacity", d => d.revealed ? 1 : 0.5);
}
handleNodeClick(node) {
if (this.gameOver || !this.isConnectedToRevealed(node)) return;
if (node.isDangerous) {
this.endGame(false);
return;
}
node.revealed = true;
this.revealedNodes.add(node.id);
this.updateConnectedNodes(node);
this.updateVisibility();
this.updateStats();
if (this.revealedNodes.size === this.nodes.length) {
this.endGame(true);
}
}
updateConnectedNodes(node) {
const connectedNodes = this.links
.filter(link => link.source.id === node.id || link.target.id === node.id)
.map(link => link.source.id === node.id ? link.target : link.source);
const hasDangerousNeighbor = connectedNodes.some(n => this.dangerousNodes.has(n.id));
document.getElementById("dangerSense").textContent = hasDangerousNeighbor ? "⚠️ DANGER!" : "Safe";
document.getElementById("dangerSense").style.color = hasDangerousNeighbor ? "red" : "green";
}
updateStats() {
document.getElementById("exploredCount").textContent = this.revealedNodes.size;
const connectedCount = this.nodes.filter(n => !n.revealed && this.isConnectedToRevealed(n)).length;
document.getElementById("connectedCount").textContent = connectedCount;
}
endGame(won) {
this.gameOver = true;
const gameOver = document.querySelector(".game-over");
const gameOverText = document.getElementById("gameOverText");
const gameOverStats = document.getElementById("gameOverStats");
gameOverText.textContent = won ?
"Victory! Network Fully Explored!" :
"Game Over! Dangerous Node Encountered!";
gameOverStats.textContent = `Nodes explored: ${this.revealedNodes.size} of ${this.nodes.length}`;
gameOver.style.display = "block";
// Reveal all dangerous nodes
this.nodes.forEach(node => {
if (node.isDangerous) node.revealed = true;
});
this.updateVisibility();
}
}
function getSelectedProperties() {
const properties = [];
const checkboxes = ['rdp', 'ssh', 'smb', 'winrm', 'http', 'ftp'];
checkboxes.forEach(id => {
if (document.getElementById(id).checked) {
properties.push(id.toUpperCase());
}
});
return properties;
}
function validateConfig() {
const properties = getSelectedProperties();
if (properties.length === 0) {
alert("Please select at least one connection type");
return false;
}
return true;
}
let game;
function startGame() {
if (!validateConfig()) return;
document.getElementById('gameConfig').style.display = 'none';
document.querySelector('.game-controls').style.display = 'block';
const nodeCount = parseInt(document.getElementById('nodeCount').value);
const dangerCount = parseInt(document.getElementById('dangerCount').value);
const properties = getSelectedProperties();
game = new NetworkGame(nodeCount, dangerCount, properties);
}
// Initialize configuration on load
window.onload = () => {
document.getElementById('nodeCount').value = 100;
document.getElementById('dangerCount').value = 5;
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment