Update 2025-04-17_20:04:08

This commit is contained in:
root
2025-04-17 20:04:09 +02:00
commit 13920ef307
2228 changed files with 406873 additions and 0 deletions

102
static/app.js Normal file
View File

@ -0,0 +1,102 @@
// /var/www/minitactix/static/app.js
let board = []
let playerSymbol = ''
let npcSymbol = ''
let playerEmoji = ''
let npcEmoji = ''
let gameOver = false
const EMOJIS = ['❌', '⭕', '🔴', '🟢', '🟡', '🟣', '⭐', '💠', '🧿', '🧩']
function startGame(symbol) {
playerSymbol = symbol
npcSymbol = symbol === 'X' ? 'O' : 'X'
const shuffled = EMOJIS.sort(() => 0.5 - Math.random())
playerEmoji = shuffled[0]
npcEmoji = shuffled[1]
document.getElementById('symbol-chooser').classList.add('hidden')
document.getElementById('board').classList.remove('hidden')
document.getElementById('restart').classList.remove('hidden')
document.getElementById('info').textContent = `You are ${playerEmoji} — NPC is ${npcEmoji}`
initBoard()
randomNPCMoves(3 + Math.floor(Math.random() * 3))
renderBoard()
}
function initBoard() {
board = Array(16).fill('')
}
function renderBoard() {
const boardDiv = document.getElementById('board')
boardDiv.innerHTML = ''
board.forEach((val, idx) => {
const tile = document.createElement('div')
tile.className = 'tile'
tile.textContent = val === playerSymbol ? playerEmoji : (val === npcSymbol ? npcEmoji : '')
tile.onclick = () => playerMove(idx)
if (val !== '' || gameOver) tile.onclick = null
boardDiv.appendChild(tile)
})
}
function playerMove(index) {
if (board[index] !== '' || gameOver) return
board[index] = playerSymbol
if (checkWin(playerSymbol)) {
endGame(`${playerEmoji} wins!`)
return
}
if (board.includes('')) {
randomNPCMoves(1)
if (checkWin(npcSymbol)) {
endGame(`${npcEmoji} wins!`)
return
}
}
if (!board.includes('')) endGame("It's a draw!")
renderBoard()
}
function randomNPCMoves(count) {
for (let i = 0; i < count; i++) {
const free = board.map((v, i) => v === '' ? i : null).filter(v => v !== null)
if (free.length === 0) return
const pick = free[Math.floor(Math.random() * free.length)]
board[pick] = npcSymbol
}
}
function checkWin(sym) {
const lines = [
[0,1,2], [1,2,3],
[4,5,6], [5,6,7],
[8,9,10], [9,10,11],
[12,13,14], [13,14,15],
[0,4,8], [4,8,12],
[1,5,9], [5,9,13],
[2,6,10], [6,10,14],
[3,7,11], [7,11,15],
[0,5,10], [1,6,11],
[4,9,14], [5,10,15],
[2,5,8], [3,6,9],
[6,9,12], [7,10,13]
]
return lines.some(line => line.every(i => board[i] === sym))
}
function endGame(msg) {
document.getElementById('status').textContent = msg
gameOver = true
renderBoard()
}
// PWA service worker registration
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('service-worker.js')
})
}

88
static/game.js Normal file
View File

@ -0,0 +1,88 @@
// /var/www/minitactix/static/game.js
let board = []
let playerSymbol = ''
let npcSymbol = ''
let gameOver = false
function startGame(symbol) {
playerSymbol = symbol
npcSymbol = symbol === 'X' ? 'O' : 'X'
document.getElementById('symbol-chooser').classList.add('hidden')
document.getElementById('board').classList.remove('hidden')
document.getElementById('restart').classList.remove('hidden')
initBoard()
randomNPCMoves(3 + Math.floor(Math.random() * 3))
renderBoard()
}
function initBoard() {
board = Array(16).fill('')
}
function renderBoard() {
const boardDiv = document.getElementById('board')
boardDiv.innerHTML = ''
board.forEach((val, idx) => {
const tile = document.createElement('div')
tile.className = 'tile'
tile.textContent = val
tile.onclick = () => playerMove(idx)
if (val !== '' || gameOver) tile.onclick = null
boardDiv.appendChild(tile)
})
}
function playerMove(index) {
if (board[index] !== '' || gameOver) return
board[index] = playerSymbol
if (checkWin(playerSymbol)) {
endGame('You win!')
return
}
if (board.includes('')) {
randomNPCMoves(1)
if (checkWin(npcSymbol)) {
endGame('NPC wins!')
return
}
}
if (!board.includes('')) endGame("It's a draw!")
renderBoard()
}
function randomNPCMoves(count) {
for (let i = 0; i < count; i++) {
const free = board.map((v, i) => v === '' ? i : null).filter(v => v !== null)
if (free.length === 0) return
const pick = free[Math.floor(Math.random() * free.length)]
board[pick] = npcSymbol
}
}
function checkWin(sym) {
const lines = [
// rows
[0,1,2], [1,2,3],
[4,5,6], [5,6,7],
[8,9,10], [9,10,11],
[12,13,14], [13,14,15],
// cols
[0,4,8], [4,8,12],
[1,5,9], [5,9,13],
[2,6,10], [6,10,14],
[3,7,11], [7,11,15],
// diagonals
[0,5,10], [1,6,11],
[4,9,14], [5,10,15],
[2,5,8], [3,6,9],
[6,9,12], [7,10,13]
]
return lines.some(line => line.every(i => board[i] === sym))
}
function endGame(msg) {
document.getElementById('status').textContent = msg
gameOver = true
renderBoard()
}

36
static/index.html Normal file
View File

@ -0,0 +1,36 @@
<!-- /var/www/minitactix/static/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MiniTactix 4x4</title>
<link rel="stylesheet" href="style.css">
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#ffffff">
</head>
<body>
<div class="container">
<h1>MiniTactix 4x4</h1>
<p id="info"></p>
<div id="symbol-chooser">
<p>Choose your symbol:</p>
<button onclick="startGame('X')">X</button>
<button onclick="startGame('O')">O</button>
</div>
<div id="board" class="grid hidden"></div>
<p id="status"></p>
<button id="restart" class="hidden" onclick="location.reload()">Play Again</button>
</div>
<script src="app.js"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('service-worker.js')
})
}
</script>
</body>
</html>

23
static/service-worker.js Normal file
View File

@ -0,0 +1,23 @@
// /var/www/minitactix/static/service-worker.js
const CACHE_NAME = "minitactix-cache-v1"
const FILES_TO_CACHE = [
"/",
"/index.html",
"/style.css",
"/app.js",
"/manifest.json"
]
self.addEventListener("install", (evt) => {
evt.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(FILES_TO_CACHE))
)
self.skipWaiting()
})
self.addEventListener("fetch", (evt) => {
evt.respondWith(
caches.match(evt.request).then((response) => response || fetch(evt.request))
)
})

68
static/style.css Normal file
View File

@ -0,0 +1,68 @@
/* /var/www/minitactix/static/style.css */
body {
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: #f9f9f9;
}
.container {
max-width: 960px;
width: 100%;
text-align: center;
}
h1 {
margin-bottom: 20px;
}
button {
padding: 10px 20px;
margin: 10px;
font-size: 18px;
cursor: pointer;
border: 1px solid #ccc;
background: white;
border-radius: 5px;
}
button:hover {
background: #eee;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 80px);
grid-gap: 5px;
justify-content: center;
margin: 20px auto;
}
.tile {
width: 80px;
height: 80px;
font-size: 36px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #888;
background: white;
cursor: pointer;
}
.tile:hover {
background: #f0f0f0;
}
.hidden {
display: none;
}
#status {
font-size: 20px;
margin-top: 10px;
}