mirror of
https://codeberg.org/ashley/poke
synced 2025-05-30 02:59:43 +00:00
674 lines
23 KiB
Plaintext
674 lines
23 KiB
Plaintext
<!--
|
||
This Source Code Form is subject to the terms of the GNU General Public License:
|
||
|
||
Copyright (C) 2021-2025 PokeTube (https://codeberg.org/Ashley/poketube)
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation, either version 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with this program. If not, see https://www.gnu.org/licenses/.
|
||
--><!--//--><![CDATA[//><!--
|
||
/**
|
||
* @licstart The following is the entire license notice for the JavaScript
|
||
* code in this page.
|
||
*
|
||
* Copyright (C) 2021-2024 POKETUBE (https://codeberg.org/Ashley/poketube)
|
||
*
|
||
* The JavaScript code in this page is free software: you can redistribute
|
||
* it and/or modify it under the terms of the GNU General Public License
|
||
* (GNU GPL) as published by the Free Software Foundation, either version 3
|
||
* of the License, or (at your option) any later version. The code is
|
||
* distributed WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU GPL
|
||
* for more details.
|
||
*
|
||
* As additional permission under GNU GPL version 3 section 7, you may
|
||
* distribute non-source (e.g., minimized or compacted) forms of that code
|
||
* without the copy of the GNU GPL normally required by section 4, provided
|
||
* you include this license notice and a URL through which recipients can
|
||
* access the Corresponding Source.
|
||
*
|
||
* @licend The above is the entire license notice for the JavaScript code
|
||
* in this page.
|
||
*/
|
||
|
||
//--><!]]>
|
||
<% if (!game) { %>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link href="/css/yt-ukraine.svg?v=4" rel="icon">
|
||
<link rel="manifest" href="/manifest.json">
|
||
<meta property="og:title" content="▶▶ Poke Games Hub">
|
||
<meta property="twitter:description" content="Free software gaming on poke!">
|
||
<meta property="og:image" content="https://cdn.glitch.global/.../hub-image.png">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Poke! Games Hub</title>
|
||
<style>
|
||
:root {
|
||
--bg-start: #1a1a2e;
|
||
--bg-end: #16213e;
|
||
--accent: #e94560;
|
||
--font-main: 'PokeTube Flex', sans-serif;
|
||
--card-bg: rgba(255,255,255,0.1);
|
||
--card-border: rgba(255,255,255,0.5);
|
||
--card-hover: rgba(255,255,255,0.2);
|
||
--shadow: rgba(0,0,0,0.4);
|
||
}
|
||
* { box-sizing: border-box; margin:0; padding:0; }
|
||
body {
|
||
font-family: var(--font-main);
|
||
background: linear-gradient(135deg, var(--bg-start), var(--bg-end));
|
||
color: #fff;
|
||
overflow: hidden;
|
||
}
|
||
@font-face {
|
||
font-family: var(--font-main);
|
||
src: url("https://p.poketube.fun/.../robotoflex.ttf");
|
||
font-display: swap;
|
||
}
|
||
|
||
/* FULL-PAGE EMOJI GRID */
|
||
.emoji-bg {
|
||
position: fixed; top: 0; left: 0;
|
||
width: 100vw; height: 100vh;
|
||
display: grid;
|
||
/* make a responsive grid of ~25 columns */
|
||
grid-template-columns: repeat(auto-fill, minmax(3rem, 1fr));
|
||
grid-auto-rows: 3rem;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
font-size: 2.5rem;
|
||
}
|
||
.emoji-bg span {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0.08;
|
||
animation: float var(--dur) ease-in-out infinite alternate;
|
||
}
|
||
/* randomize durations a bit */
|
||
.emoji-bg span:nth-child(odd) { --dur: 6s; }
|
||
.emoji-bg span:nth-child(even) { --dur: 9s; }
|
||
@keyframes float {
|
||
to { transform: translateY(-20px) rotate(15deg); }
|
||
}
|
||
|
||
/* Main container */
|
||
.wrapper {
|
||
position: relative; z-index: 1;
|
||
max-width: 1000px; margin: 3rem auto;
|
||
padding: 2rem;
|
||
background: var(--card-bg);
|
||
border: 2px solid var(--card-border);
|
||
border-radius: 20px;
|
||
box-shadow: 0 8px 32px var(--shadow);
|
||
backdrop-filter: blur(12px);
|
||
animation: fadeIn 1s ease forwards;
|
||
opacity: 0;
|
||
}
|
||
@keyframes fadeIn { to { opacity: 1; } }
|
||
h1 {
|
||
font-size: 3.5rem;
|
||
text-align: center;
|
||
margin-bottom: 2rem;
|
||
background: linear-gradient(90deg, #ff2e63, #08d9d6);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
.game-container {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||
gap: 1.5rem;
|
||
}
|
||
.game {
|
||
position: relative;
|
||
background: var(--card-bg);
|
||
border: 2px solid var(--card-border);
|
||
border-radius: 16px;
|
||
padding: 1.5rem;
|
||
text-align: center;
|
||
color: #fff;
|
||
text-decoration: none;
|
||
overflow: hidden;
|
||
transition: transform 0.3s, box-shadow 0.3s, background 0.3s;
|
||
}
|
||
.game::before {
|
||
content: '';
|
||
position: absolute; top:0; left:0; width:100%; height:100%;
|
||
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.2), transparent 70%);
|
||
opacity: 0; transition: opacity 0.3s;
|
||
}
|
||
.game:hover {
|
||
transform: translateY(-8px) rotate(-1deg);
|
||
box-shadow: 0 12px 48px var(--shadow);
|
||
background: var(--card-hover);
|
||
}
|
||
.game:hover::before { opacity: 1; }
|
||
|
||
.game .icon {
|
||
font-size: 3rem;
|
||
margin-bottom: 0.5rem;
|
||
animation: popIn 0.5s ease forwards;
|
||
opacity: 0;
|
||
}
|
||
@keyframes popIn { to { opacity:1; transform: scale(1); } }
|
||
|
||
.game h2 {
|
||
font-size: 1.4rem;
|
||
margin-bottom: 0.3rem;
|
||
text-shadow: 1px 1px 4px var(--shadow);
|
||
}
|
||
/* hide game UIs until selected */
|
||
canvas, .board { display: none; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- empty grid; JS will populate -->
|
||
<div class="emoji-bg"></div>
|
||
|
||
<div class="wrapper">
|
||
<h1>Poke! Games Hub</h1>
|
||
<div class="game-container">
|
||
<a href="?game=snake" class="game">
|
||
<div class="icon">🐍</div>
|
||
<h2>Snake</h2>
|
||
</a>
|
||
<a href="?game=tic-tac-toe" class="game">
|
||
<div class="icon">❌⭕</div>
|
||
<h2>Tic-Tac-Toe</h2>
|
||
</a>
|
||
<a href="?game=sudoku" class="game">
|
||
<div class="icon">🧮</div>
|
||
<h2>Sudoku</h2>
|
||
</a>
|
||
<a href="?game=pong" class="game">
|
||
<div class="icon">🏓</div>
|
||
<h2>Ping-Pong</h2>
|
||
</a>
|
||
<a href="?game=minesweeper" class="game">
|
||
<div class="icon">💣</div>
|
||
<h2>Minesweeper</h2>
|
||
</a>
|
||
<a href="?game=breakout" class="game">
|
||
<div class="icon">🧱</div>
|
||
<h2>Breakout</h2>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/static/data-mobile.js?v=6000"></script>
|
||
<script>
|
||
const emojis = ['🎮','🕹️','👾','🎧','🖥️','🎲','🏆','🎯','🔥','💥','🧩','⭐','⚔️','🛡️','🚀','🎉','🌟','⚡','💣'];
|
||
const bg = document.querySelector('.emoji-bg');
|
||
for (let i = 0; i < 400; i++) {
|
||
const span = document.createElement('span');
|
||
span.textContent = emojis[i % emojis.length];
|
||
// randomize animation speed slightly
|
||
span.style.setProperty('--dur', `${6 + Math.random()*4}s`);
|
||
bg.appendChild(span);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
<% } %>
|
||
|
||
|
||
|
||
|
||
|
||
<% if (game === "snake") { %>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link href="/css/yt-ukraine.svg?v=4" rel="icon">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Snake</title>
|
||
<style>
|
||
html,body{margin:0;padding:0;background:linear-gradient(135deg,#2c3e50,#34495e);overflow:hidden;}
|
||
#snakeCanvas{display:block;border:1px solid #000;position:absolute;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<canvas id="snakeCanvas"></canvas>
|
||
<script>
|
||
// GPL header omitted—see top of file
|
||
const canvas = document.getElementById('snakeCanvas');
|
||
const ctx = canvas.getContext('2d');
|
||
const scale = 20;
|
||
let snake = [{x:10,y:10}], dir={x:0,y:1}, food=randomFood();
|
||
function resize(){
|
||
canvas.width=Math.floor(window.innerWidth/scale)*scale;
|
||
canvas.height=Math.floor(window.innerHeight/scale)*scale;
|
||
}
|
||
window.addEventListener('resize',resize);
|
||
resize();
|
||
function randomFood(){ return { x:Math.floor(Math.random()*(canvas.width/scale)), y:Math.floor(Math.random()*(canvas.height/scale)) }; }
|
||
function draw(){
|
||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||
ctx.fillStyle='#f00'; ctx.fillRect(food.x*scale,food.y*scale,scale,scale);
|
||
ctx.fillStyle='#00f'; snake.forEach(s=>ctx.fillRect(s.x*scale,s.y*scale,scale,scale));
|
||
}
|
||
function update(){
|
||
const head={x:snake[0].x+dir.x,y:snake[0].y+dir.y};
|
||
head.x=(head.x+(canvas.width/scale))%(canvas.width/scale);
|
||
head.y=(head.y+(canvas.height/scale))%(canvas.height/scale);
|
||
if(snake.some((seg,i)=>i&&seg.x===head.x&&seg.y===head.y)) return reset();
|
||
snake.unshift(head);
|
||
if(head.x===food.x&&head.y===food.y) food=randomFood(); else snake.pop();
|
||
}
|
||
function reset(){ snake=[{x:10,y:10}]; dir={x:0,y:1}; food=randomFood(); }
|
||
document.addEventListener('keydown',e=>{
|
||
if(e.key.startsWith('Arrow')){
|
||
const map={Up:[0,-1],Down:[0,1],Left:[-1,0],Right:[1,0]};
|
||
const [dx,dy]=map[e.key.replace('Arrow','')];
|
||
if(dx!==-dir.x&&dy!==-dir.y) dir={x:dx,y:dy};
|
||
}
|
||
});
|
||
setInterval(()=>{update();draw();},100);
|
||
</script>
|
||
</body>
|
||
</html>
|
||
<% } %>
|
||
|
||
|
||
<% if (game === "tic-tac-toe") { %>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link href="/css/yt-ukraine.svg?v=4" rel="icon">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Tic-Tac-Toe</title>
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
font-family: 'PokeTube Flex', sans-serif;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
background: linear-gradient(135deg, #2c3e50, #34495e);
|
||
color: #fff;
|
||
}
|
||
#board {
|
||
display: grid;
|
||
grid-template: repeat(3, 100px) / repeat(3, 100px);
|
||
gap: 5px;
|
||
}
|
||
.cell {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 2em;
|
||
background: rgba(255,255,255,0.2);
|
||
cursor: pointer;
|
||
}
|
||
#message {
|
||
text-align: center;
|
||
margin-bottom: 1rem;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div>
|
||
<div id="message">Player X’s turn :3</div>
|
||
<div id="board"></div>
|
||
</div>
|
||
<script>
|
||
const boardElement = document.getElementById("board");
|
||
const messageElement = document.getElementById("message");
|
||
let currentPlayer = "X";
|
||
let board = ["", "", "", "", "", "", "", "", ""];
|
||
|
||
function checkWinner() {
|
||
const winningCombinations = [
|
||
[0, 1, 2], [3, 4, 5], [6, 7, 8], // Rows
|
||
[0, 3, 6], [1, 4, 7], [2, 5, 8], // Columns
|
||
[0, 4, 8], [2, 4, 6] // Diagonals
|
||
];
|
||
|
||
for (const combination of winningCombinations) {
|
||
const [a, b, c] = combination;
|
||
if (board[a] && board[a] === board[b] && board[a] === board[c]) {
|
||
return board[a];
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
function checkDraw() {
|
||
return !board.includes("");
|
||
}
|
||
|
||
function handleClick(index) {
|
||
if (board[index] === "" && !checkWinner() && !checkDraw()) {
|
||
board[index] = currentPlayer;
|
||
renderBoard();
|
||
const winner = checkWinner();
|
||
if (winner) {
|
||
messageElement.textContent = `Player ${winner} won!!!!!! woaah`;
|
||
} else if (checkDraw()) {
|
||
messageElement.textContent = "It's a draw! oh welp >~<";
|
||
} else {
|
||
currentPlayer = currentPlayer === "X" ? "O" : "X";
|
||
messageElement.textContent = `Player ${currentPlayer}'s turn :3`;
|
||
if (currentPlayer === "O") {
|
||
setTimeout(makeComputerMove, 500); // AI waits for 0.5 second
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function makeComputerMove() {
|
||
// Look for a winning move, then look to block player, otherwise, choose a random move
|
||
const winningMove = findWinningMove();
|
||
const blockingMove = findBlockingMove();
|
||
|
||
if (winningMove !== null) {
|
||
board[winningMove] = currentPlayer;
|
||
} else if (blockingMove !== null) {
|
||
board[blockingMove] = currentPlayer;
|
||
} else {
|
||
// Randomly choose an empty cell for the computer's move
|
||
const emptyCells = board.reduce((acc, value, index) => {
|
||
if (value === "") {
|
||
acc.push(index);
|
||
}
|
||
return acc;
|
||
}, []);
|
||
|
||
if (emptyCells.length > 0) {
|
||
const randomIndex = Math.floor(Math.random() * emptyCells.length);
|
||
const computerMove = emptyCells[randomIndex];
|
||
board[computerMove] = currentPlayer;
|
||
}
|
||
}
|
||
|
||
renderBoard();
|
||
const winner = checkWinner();
|
||
if (winner) {
|
||
messageElement.textContent = `Player ${winner} won!!!!!! woaah`;
|
||
} else if (checkDraw()) {
|
||
messageElement.textContent = "It's a draw! oh welp >~<";
|
||
} else {
|
||
currentPlayer = currentPlayer === "X" ? "O" : "X";
|
||
messageElement.textContent = `Player ${currentPlayer}'s turn :3`;
|
||
}
|
||
}
|
||
|
||
function findWinningMove() {
|
||
for (let i = 0; i < board.length; i++) {
|
||
if (board[i] === "") {
|
||
board[i] = currentPlayer;
|
||
if (checkWinner() === currentPlayer) {
|
||
board[i] = ""; // Reset the move
|
||
return i;
|
||
}
|
||
board[i] = ""; // Reset the move
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function findBlockingMove() {
|
||
const opponent = currentPlayer === "X" ? "O" : "X";
|
||
for (let i = 0; i < board.length; i++) {
|
||
if (board[i] === "") {
|
||
board[i] = opponent;
|
||
if (checkWinner() === opponent) {
|
||
board[i] = ""; // Reset the move
|
||
return i;
|
||
}
|
||
board[i] = ""; // Reset the move
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function renderBoard() {
|
||
boardElement.innerHTML = "";
|
||
board.forEach((value, index) => {
|
||
const cell = document.createElement("div");
|
||
cell.classList.add("cell");
|
||
cell.textContent = value;
|
||
cell.addEventListener("click", () => handleClick(index));
|
||
boardElement.appendChild(cell);
|
||
});
|
||
}
|
||
|
||
function resetGame() {
|
||
currentPlayer = "X";
|
||
board = ["", "", "", "", "", "", "", "", ""];
|
||
messageElement.textContent = `Player ${currentPlayer}'s turn :3`;
|
||
renderBoard();
|
||
|
||
// If AI is the starting player, make the first move
|
||
if (currentPlayer === "O") {
|
||
setTimeout(makeComputerMove, 500); // AI waits for 0.5 second
|
||
}
|
||
}
|
||
|
||
// Initial setup
|
||
resetGame();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
<% } %>
|
||
|
||
|
||
<% if (game === "sudoku") { %>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link href="/css/yt-ukraine.svg?v=4" rel="icon">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Sudoku</title>
|
||
<style>
|
||
body{margin:0;display:flex;align-items:center;justify-content:center;height:100vh;background:linear-gradient(135deg,#2c3e50,#34495e);font-family:Arial;}
|
||
#sudokuBoard{display:grid;grid-template:repeat(9,40px)/repeat(9,40px);gap:1px;}
|
||
.cell{display:flex;align-items:center;justify-content:center;background:#ddd;font-weight:bold;cursor:pointer;user-select:none;}
|
||
.given{background:#ccc;} .error{background:#fbb;}
|
||
#overlay, #popup{display:none;position:fixed;width:100%;height:100%;}
|
||
#overlay{background:rgba(0,0,0,0.5);z-index:1;}
|
||
#popup{z-index:2;background:#fff;padding:2rem;border-radius:1em;top:50%;left:50%;transform:translate(-50%,-50%);}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="overlay"></div>
|
||
<div id="popup"><h2>U did a incorrect move :sob:</h2><button onclick="closePopup()">Oki</button></div>
|
||
<div id="sudokuBoard"></div>
|
||
<script>
|
||
// GPL header omitted—see top of file
|
||
const base=[[5,3,0,0,7,0,0,0,0],[6,0,0,1,9,5,0,0,0],[0,9,8,0,0,0,0,6,0],[8,0,0,0,6,0,0,0,3],[4,0,0,8,0,3,0,0,1],[7,0,0,0,2,0,0,0,6],[0,6,0,0,0,0,2,8,0],[0,0,0,4,1,9,0,0,5],[0,0,0,0,8,0,0,7,9]];
|
||
let board=JSON.parse(JSON.stringify(base));
|
||
const cont=document.getElementById('sudokuBoard'), pop=document.getElementById('popup'), ov=document.getElementById('overlay');
|
||
function closePopup(){pop.style.display=ov.style.display='none';}
|
||
function render(){
|
||
cont.innerHTML='';
|
||
board.forEach((r,ri)=>r.forEach((v,ci)=>{
|
||
const c=document.createElement('div');
|
||
c.className='cell'+(base[ri][ci]?' given':'');
|
||
c.textContent=v||'';
|
||
if(!base[ri][ci])c.onclick=()=>clickCell(ri,ci);
|
||
cont.append(c);
|
||
}));
|
||
}
|
||
function clickCell(r,c){
|
||
const n=parseInt(prompt('Enter 1-9:'),10);
|
||
if(n>=1&&n<=9){board[r][c]=n; if(!valid()){showErr();board[r][c]=0;} render();}
|
||
}
|
||
function valid(){
|
||
for(let i=0;i<9;i++){
|
||
let rs=new Set(), cs=new Set();
|
||
for(let j=0;j<9;j++){
|
||
if(board[i][j]&&rs.has(board[i][j]))return false;
|
||
rs.add(board[i][j]);
|
||
if(board[j][i]&&cs.has(board[j][i]))return false;
|
||
cs.add(board[j][i]);
|
||
}
|
||
}
|
||
for(let br=0;br<9;br+=3)for(let bc=0;bc<9;bc+=3){
|
||
let bs=new Set();
|
||
for(let r=br;r<br+3;r++)for(let c=bc;c<bc+3;c++){
|
||
if(board[r][c]&&bs.has(board[r][c]))return false;
|
||
bs.add(board[r][c]);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
function showErr(){pop.style.display=ov.style.display='block';setTimeout(closePopup,1000);}
|
||
render();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
<% } %>
|
||
|
||
|
||
<% if (game === "pong") { %>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link href="/css/yt-ukraine.svg?v=4" rel="icon">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Pong</title>
|
||
<style>
|
||
body{margin:0;font-family:Arial;background:linear-gradient(135deg,#2c3e50,#34495e);color:#fff;display:flex;justify-content:center;align-items:center;height:100vh;}
|
||
#pongCanvas{border:1px solid #fff;}
|
||
#score{position:absolute;top:10px;left:50%;transform:translateX(-50%);font-size:1.2em;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="score">0 : 0</div>
|
||
<canvas id="pongCanvas" width="800" height="400"></canvas>
|
||
<script>
|
||
// GPL header omitted—see top of file
|
||
const c=document.getElementById('pongCanvas'),ctx=c.getContext('2d');
|
||
let L=0,R=0,ball={x:400,y:200,vx:5,vy:3},p1=170,p2=170,P={w:10,h:60},max=5;
|
||
function draw(){ctx.clearRect(0,0,800,400);ctx.fillStyle='#fff';ctx.fillRect(0,p1,P.w,P.h);ctx.fillRect(800-P.w,p2,P.w,P.h);ctx.beginPath();ctx.arc(ball.x,ball.y,8,0,2*Math.PI);ctx.fill();document.getElementById('score').textContent=`${L} : ${R}`;}
|
||
function update(){
|
||
ball.x+=ball.vx;ball.y+=ball.vy;
|
||
if(ball.y<0||ball.y>400)ball.vy*=-1;
|
||
if(ball.x<P.w&&ball.y>p1&&ball.y<p1+P.h)ball.vx*=-1;
|
||
if(ball.x>800-P.w&&ball.y>p2&&ball.y<p2+P.h)ball.vx*=-1;
|
||
if(ball.x<0){R++;reset();}
|
||
if(ball.x>800){L++;reset();}
|
||
p1+=(ball.y-(p1+P.h/2))*0.02;
|
||
}
|
||
function reset(){ball={x:400,y:200,vx:5*(Math.random()>0.5?1:-1),vy:3*(Math.random()>0.5?1:-1)};if(L>=max||R>=max){L=R=0;}}
|
||
window.addEventListener('keydown',e=>{if(e.key==='w'&&p2>0)p2-=20;if(e.key==='s'&&p2<340)p2+=20;});
|
||
setInterval(()=>{update();draw();},1000/60);
|
||
</script>
|
||
</body>
|
||
</html>
|
||
<% } %>
|
||
|
||
|
||
<% if (game === "minesweeper") { %>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link href="/css/yt-ukraine.svg?v=4" rel="icon">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Minesweeper</title>
|
||
<style>
|
||
body{margin:0;display:flex;justify-content:center;align-items:center;height:100vh;background:linear-gradient(135deg,#2c3e50,#34495e);font-family:Arial;color:#fff;}
|
||
#board{display:grid;grid-template:repeat(10,30px)/repeat(10,30px);gap:2px;}
|
||
.cell{width:30px;height:30px;background:#aaa;display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none;}
|
||
.revealed{background:#ddd;cursor:default;} .flag{background:#f22;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="board"></div>
|
||
<script>
|
||
// GPL header omitted—see top of file
|
||
const R=10,C=10,M=15,boardEl=document.getElementById('board');
|
||
let cells=[],mines=new Set();
|
||
function init(){
|
||
while(mines.size<M)mines.add(Math.floor(Math.random()*R*C));
|
||
for(let i=0;i<R*C;i++){
|
||
const d=document.createElement('div');d.className='cell';d.dataset.i=i;
|
||
d.oncontextmenu=e=>{e.preventDefault();d.classList.toggle('flag');};
|
||
d.onclick=()=>reveal(d);boardEl.append(d);cells.push(d);
|
||
}
|
||
}
|
||
function nbr(i){const a=[];const x=i%C,y=Math.floor(i/C);
|
||
for(let dy=-1;dy<=1;dy++)for(let dx=-1;dx<=1;dx++){
|
||
const nx=x+dx,ny=y+dy;
|
||
if(nx>=0&&nx<C&&ny>=0&&ny<R&&(dx||dy))a.push(ny*C+nx);
|
||
}return a;
|
||
}
|
||
function reveal(d){
|
||
const i=+d.dataset.i; if(d.classList.contains('flag')||d.classList.contains('revealed'))return;
|
||
d.classList.add('revealed');
|
||
if(mines.has(i)){d.textContent='💣';alert('Game Over!');reset();return;}
|
||
const cnt=nbr(i).filter(n=>mines.has(n)).length;
|
||
if(cnt)d.textContent=cnt; else nbr(i).forEach(n=>reveal(cells[n]));
|
||
if(cells.filter(c=>!c.classList.contains('revealed')).length===M){alert('You Win!');reset();}
|
||
}
|
||
function reset(){boardEl.innerHTML='';cells=[];mines.clear();init();}
|
||
init();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
<% } %>
|
||
|
||
|
||
<% if (game === "breakout") { %>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link href="/css/yt-ukraine.svg?v=4" rel="icon">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Breakout</title>
|
||
<style>
|
||
body{margin:0;display:flex;justify-content:center;align-items:center;height:100vh;background:linear-gradient(135deg,#2c3e50,#34495e);font-family:Arial;color:#fff;}
|
||
#breakoutCanvas{border:1px solid #fff;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<canvas id="breakoutCanvas" width="800" height="400"></canvas>
|
||
<script>
|
||
// GPL header omitted—see top of file
|
||
const c=document.getElementById('breakoutCanvas'),ctx=c.getContext('2d');
|
||
const P={w:100,h:10,x:350,y:380},B={r:8,dx:4,dy:-4},br=[];let score=0;
|
||
for(let r=0;r<5;r++)for(let co=0;co<8;co++)br.push({x:co*100+35,y:r*30+30,alive:true});
|
||
document.addEventListener('mousemove',e=>P.x=Math.min(c.width-P.w,Math.max(0,e.clientX-c.offsetLeft-P.w/2)));
|
||
function draw(){
|
||
ctx.clearRect(0,0,c.width,c.height);
|
||
ctx.fillStyle='#fff';ctx.fillRect(P.x,P.y,P.w,P.h);
|
||
ctx.beginPath();ctx.arc(B.x,B.y,B.r,0,2*Math.PI);ctx.fill();
|
||
br.forEach(b=>b.alive&&(ctx.fillStyle='#09f',ctx.fillRect(b.x,b.y,90,20)));
|
||
ctx.fillText(`Score: ${score}`,10,c.height-10);
|
||
}
|
||
function update(){
|
||
B.x+=B.dx;B.y+=B.dy;
|
||
if(B.x<B.r||B.x>c.width-B.r)B.dx*=-1;
|
||
if(B.y<B.r)B.dy*=-1;
|
||
if(B.y>c.height)return alert('Game Over'),location.reload();
|
||
if(B.y+ B.r>P.y&&B.x>P.x&&B.x<P.x+P.w)B.dy*=-1;
|
||
br.forEach(b=>b.alive&&B.x>b.x&&B.x<b.x+90&&B.y>b.y&&B.y<b.y+20&&(B.dy*=-1,b.alive=false,score++));
|
||
}
|
||
function loop(){update();draw();requestAnimationFrame(loop);}
|
||
loop();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
<% } %>
|