Connect 4 - You vs AI Buddy
by SwiftPanda95182 lines10.3 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connect 4 - You vs AI Buddy</title>
<script src="https://sdk.minepi.com/pi-sdk.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Roboto, sans-serif; }
body { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #f0f4ff, #e6e9ff); padding: 20px; }
.auth-bar { width: 100%; max-width: 550px; display: flex; justify-content: flex-end; margin-bottom: 15px; }
.auth-btn, .user-info { padding: 8px 16px; border-radius: 12px; border: none; font-weight: 600; cursor: pointer; transition: all 0.2s; }
.auth-btn { background: #6366f1; color: white; }
.auth-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.auth-btn:hover:not(:disabled) { background: #4f46e5; transform: translateY(-1px); }
.user-info { background: white; color: #6366f1; border: 2px solid #6366f1; display: none; }
.game-container { background: white; border-radius: 24px; padding: 30px 20px; box-shadow: 0 10px 40px rgba(100,100,255,0.15); text-align: center; max-width: 550px; width: 100%; }
h1 { font-size: 2.8rem; background: linear-gradient(90deg, #ff5e99, #ff9a56); -webkit-background-clip: text; color: transparent; margin-bottom: 8px; }
.subtitle { color: #888; font-size: 1.1rem; margin-bottom: 20px; }
.turn-bar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 25px; padding: 12px 20px; background: #f8f9ff; border-radius: 16px; }
.player-tag { display: flex; align-items: center; gap: 8px; font-weight: 600; color: #555; }
.dot { width: 24px; height: 24px; border-radius: 50%; }
.dot.you { background: #ff5252; }
.dot.buddy { background: #ffc107; }
.turn-text { font-size: 1rem; color: #555; font-weight: 500; }
.board { display: grid; grid-template-columns: repeat(7,1fr); gap:10px; background: linear-gradient(180deg,#6366f1,#8b5cf6); padding:18px; border-radius:16px; margin:0 auto 25px; max-width:480px; pointer-events: auto; }
.cell { aspect-ratio:1; background:rgba(255,255,255,0.2); border-radius:50%; position:relative; cursor:pointer; transition:background 0.2s; pointer-events: auto; }
.cell:hover { background:rgba(255,255,255,0.35); }
.piece { position:absolute; width:85%; height:85%; left:7.5%; top:7.5%; border-radius:50%; box-shadow:inset 0 3px 6px rgba(0,0,0,0.2); animation:drop 0.4s ease-out forwards; }
.piece.player { background:radial-gradient(circle at 30% 30%,#ff7b7b,#ff3838); }
.piece.ai { background:radial-gradient(circle at 30% 30%,#ffdd70,#ffc107); }
@keyframes drop { 0%{transform:translateY(-500px);opacity:0} 100%{transform:translateY(0);opacity:1} }
.message { font-size:1.3rem; font-weight:600; color:#6366f1; margin-bottom:20px; min-height:2rem; }
#restartBtn { padding:14px 36px; font-size:1.1rem; font-weight:600; border:none; border-radius:12px; background:linear-gradient(90deg,#ff7eb3,#ff758c); color:white; cursor:pointer; transition:transform 0.2s, box-shadow 0.2s; pointer-events: auto; }
#restartBtn:hover { transform:translateY(-2px); box-shadow:0 6px 20px rgba(255,117,140,0.4); }
@media (max-width:480px) { h1{font-size:2rem} .board{gap:6px;padding:12px} .turn-bar{padding:10px;font-size:0.9rem} }
</style>
</head>
<body>
<div class="auth-bar">
<button id="signInBtn" class="auth-btn">Sign in with Pi</button>
<div id="userDisplay" class="user-info"></div>
</div>
<div class="game-container">
<h1>Connect 4</h1>
<p class="subtitle">You vs AI Buddy 🤝</p>
<div class="turn-bar">
<div class="player-tag"><div class="dot you"></div><span>You</span></div>
<div class="turn-text" id="turnText">Your turn! Tap a column to play 😊</div>
<div class="player-tag"><span>AI Buddy 🤖</span><div class="dot buddy"></div></div>
</div>
<div class="board" id="board"></div>
<div class="message" id="message"></div>
<button id="restartBtn">Restart Game</button>
</div>
<script>
// Game Settings
const ROWS = 6, COLS = 7;
let board = [], currentPlayer = 'player', gameOver = false, currentUser = null;
const boardEl = document.getElementById('board'), turnText = document.getElementById('turnText'), messageEl = document.getElementById('message');
const restartBtn = document.getElementById('restartBtn'), signInBtn = document.getElementById('signInBtn'), userDisplay = document.getElementById('userDisplay');
// Check if running on Pi Browser
function isPiBrowser() {
return /PiBrowser|Pi Browser/i.test(navigator.userAgent) && typeof window.Pi !== 'undefined';
}
// Pi Auth - only run when user clicks button
async function piAuth() {
if (!isPiBrowser()) {
alert("ℹ️ This feature works only in Pi Browser! You can still play without signing in.");
return;
}
try {
signInBtn.disabled = true;
signInBtn.textContent = "Signing in...";
await Pi.init({ version: "2.0" });
const auth = await Pi.authenticate(['username'], handleIncompletePayment);
await validateToken(auth.accessToken);
currentUser = auth.user;
updateAuthUI();
console.log("Signed in:", currentUser.username);
} catch (e) {
console.log("Auth skipped/failed:", e.message);
alert("ℹ️ You can play without signing in!");
} finally {
signInBtn.disabled = false;
signInBtn.textContent = "Sign in with Pi";
}
}
function handleIncompletePayment(payment) { console.warn("Incomplete payment:", payment.identifier); }
async function validateToken(token) {
try {
const res = await fetch("https://api.minepi.com/v2/me", {
headers: { "Authorization": `Bearer ${token}` }
});
if (!res.ok) throw new Error("Token invalid");
const data = await res.json();
console.log("Token verified:", data.username);
} catch (e) {
console.warn("Token validation skipped:", e.message);
}
}
function updateAuthUI() {
if (currentUser) { signInBtn.style.display = "none"; userDisplay.textContent = `👤 ${currentUser.username}`; userDisplay.style.display = "block"; }
else { signInBtn.style.display = "block"; userDisplay.style.display = "none"; }
}
// Game Logic - always work regardless auth
function initGame() {
board = Array(ROWS).fill().map(() => Array(COLS).fill(null));
currentPlayer = 'player'; gameOver = false; messageEl.textContent = '';
turnText.textContent = 'Your turn! Tap a column to play 😊';
renderBoard();
}
function renderBoard() {
boardEl.innerHTML = '';
for (let r=0; r<ROWS; r++) {
for (let c=0; c<COLS; c++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.col = c;
if (board[r][c]) { const p = document.createElement('div'); p.classList.add('piece', board[r][c]); cell.appendChild(p); }
// Always attach click event
cell.addEventListener('click', () => handleClick(c));
boardEl.appendChild(cell);
}
}
}
function handleClick(col) {
if (gameOver || currentPlayer!=='player') return;
const row = getEmptyRow(col); if (row===-1) return;
placePiece(row, col, 'player');
if (checkWin(row,col,'player')) { endGame('🎉 You win! AI Buddy is sad 😢'); return; }
if (board[0].every(c=>c!==null)) { endGame('🤝 It\'s a draw!'); return; }
currentPlayer = 'ai'; turnText.textContent = 'AI Buddy is thinking... 🤔'; setTimeout(aiTurn, 700);
}
function getEmptyRow(col) { for (let r=ROWS-1; r>=0; r--) if (!board[r][col]) return r; return -1; }
function placePiece(r,c,p) { board[r][c]=p; renderBoard(); }
function checkWin(r,c,p) {
const dirs = [[0,1],[1,0],[1,1],[1,-1]];
for (let [dr,dc] of dirs) {
let cnt=1;
for (let i=1;i<4;i++) { const nr=r+dr*i, nc=c+dc*i; if (nr>=0&&nr<ROWS&&nc>=0&&nc<COLS&&board[nr][nc]===p) cnt++; else break; }
for (let i=1;i<4;i++) { const nr=r-dr*i, nc=c-dc*i; if (nr>=0&&nr<ROWS&&nc>=0&&nc<COLS&&board[nr][nc]===p) cnt++; else break; }
if (cnt>=4) return true;
}
return false;
}
function endGame(t) { gameOver=true; messageEl.textContent=t; turnText.textContent='Game Over!'; }
function aiTurn() {
let move = findWin('ai') || findWin('player') || pickBestCol();
const row = getEmptyRow(move); placePiece(row, move, 'ai');
if (checkWin(row,move,'ai')) { endGame('😜 AI Buddy wins! Try again?'); return; }
if (board[0].every(c=>c!==null)) { endGame('🤝 It\'s a draw!'); return; }
currentPlayer='player'; turnText.textContent='Your turn! Tap a column to play 😊';
}
function findWin(p) {
for (let c=0;c<COLS;c++) {
const r=getEmptyRow(c); if (r===-1) continue;
board[r][c]=p; const w=checkWin(r,c,p); board[r][c]=null;
if (w) return c;
}
return null;
}
function pickBestCol() { const order=[3,2,4,1,5,0,6]; return order.find(c=>getEmptyRow(c)!==-1)||0; }
// Events
signInBtn.addEventListener("click", piAuth);
restartBtn.addEventListener('click', initGame);
// Start game immediately when page loads
document.addEventListener("DOMContentLoaded", () => {
initGame();
updateAuthUI();
});
</script>
</body>
</html>Game Source: Connect 4 - You vs AI Buddy
Creator: SwiftPanda95
Libraries: none
Complexity: moderate (182 lines, 10.3 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: connect-4-you-vs-ai-buddy-swiftpanda95-mrbhck4f" to link back to the original. Then publish at arcadelab.ai/publish.