poke/html/gamehub.ejs
2025-04-26 23:16:33 +00:00

430 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
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: #2c3e50;
--bg-end: #34495e;
--accent: #ffabcc;
--card-bg: rgba(255,255,255,0.1);
--card-hover: rgba(255,255,255,0.2);
--font-main: 'PokeTube Flex', sans-serif;
}
* { 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;
display:flex; align-items:center; justify-content:center;
min-height:100vh;
}
h1 {
font-weight:900; font-stretch:ultra-expanded; font-style:italic;
color: var(--accent); text-align:center; margin-bottom:1rem;
}
.game-container {
display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
gap:1rem; width:90%; max-width:1000px;
}
.game {
background: var(--card-bg);
border:2px solid #fff; border-radius:10px;
text-decoration:none; color:#fff; padding:1rem;
text-align:center; transition:background .3s;
}
.game:hover { background: var(--card-hover); }
.game h2 { margin-bottom: .5rem; }
canvas, .board { display:none; }
@font-face {
font-family: var(--font-main);
src: url("https://p.poketube.fun/.../robotoflex.ttf");
font-display: swap;
}
</style>
</head>
<body>
<div>
<h1>Poke! Games Hub</h1>
<div class="game-container">
<a href="?game=snake" class="game"><h2>Snake</h2><canvas id="snakeCanvas"></canvas></a>
<a href="?game=tic-tac-toe" class="game"><h2>Tic-Tac-Toe</h2><div id="message"></div><div class="board" id="board"></div></a>
<a href="?game=sudoku" class="game"><h2>Sudoku</h2><div class="board" id="sudokuBoard"></div></a>
<a href="?game=pong" class="game"><h2>Ping-Pong</h2><canvas id="pongCanvas"></canvas></a>
<a href="?game=minesweeper" class="game"><h2>Minesweeper</h2><div class="board" id="minesweeperBoard"></div></a>
<a href="?game=breakout" class="game"><h2>Breakout</h2><canvas id="breakoutCanvas"></canvas></a>
</div>
</div>
<script src="/static/data-mobile.js?v=6000"></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';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:#fff2;cursor:pointer;}
#message{text-align:center;margin-bottom:1rem;}
</style>
</head>
<body>
<div>
<div id="message">Player Xs turn :3</div>
<div id="board"></div>
</div>
<script>
// GPL header omitted—see top of file
const boardEl=document.getElementById('board'), msgEl=document.getElementById('message');
let board=Array(9).fill(''), player='X';
function render(){
boardEl.innerHTML='';
board.forEach((v,i)=>{
const c=document.createElement('div');
c.className='cell'; c.textContent=v;
c.onclick=()=>move(i);
boardEl.append(c);
});
}
function move(i){
if(board[i]||win()||draw())return;
board[i]=player;
if(win()){ msgEl.textContent=`Player ${player} won!!!!!! woaah`; }
else if(draw()){ msgEl.textContent="It's a draw! oh welp >~<"; }
else{ player=player==='X'?'O':'X'; msgEl.textContent=`Player ${player}s turn :3`; if(player==='O')setTimeout(ai,300); }
render();
}
function win(){ return [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]].some(l=>l.every(i=>board[i]&&board[i]===board[l[0]])); }
function draw(){ return board.every(v=>v); }
function ai(){
const tryMv=p=>board.findIndex((v,i)=>!v&&(board[i]=p,win())&&(board[i]='',true));
let idx=tryMv('O')||tryMv('X');
if(idx<0) idx=board.map((v,i)=>v?'':i).filter(i=>i)[0];
move(idx);
}
render();
</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>
<% } %>