Initial commit

This commit is contained in:
Allen
2026-04-30 02:14:25 +00:00
commit 7090e6f01d
243 changed files with 115122 additions and 0 deletions
+557
View File
@@ -0,0 +1,557 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Connect Four</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #1a237e; touch-action: none; font-family: Arial, sans-serif; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = null;
let musicEnabled = true;
let sfxEnabled = true;
let musicPlaying = false;
function initAudio() {
if (!audioCtx) audioCtx = new AudioContext();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
// Fun board game melody
const melody = [
{n: 523, d: 0.25}, {n: 587, d: 0.25}, {n: 659, d: 0.25}, {n: 698, d: 0.5},
{n: 659, d: 0.25}, {n: 587, d: 0.25}, {n: 523, d: 0.5},
{n: 440, d: 0.25}, {n: 494, d: 0.25}, {n: 523, d: 0.25}, {n: 587, d: 0.5},
{n: 523, d: 0.25}, {n: 494, d: 0.25}, {n: 440, d: 0.5}, {n: 0, d: 0.25}
];
const bass = [
{n: 131, d: 0.5}, {n: 165, d: 0.5}, {n: 131, d: 0.5}, {n: 110, d: 0.5},
{n: 110, d: 0.5}, {n: 131, d: 0.5}, {n: 110, d: 0.5}, {n: 98, d: 0.5}
];
let melodyIdx = 0, bassIdx = 0, nextMelody = 0, nextBass = 0;
function playTone(freq, dur, type = 'square', vol = 0.08) {
if (!audioCtx || !musicEnabled || freq === 0) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.value = freq;
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + dur * 0.9);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + dur);
}
function playSfx(type) {
if (!audioCtx || !sfxEnabled) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
if (type === 'drop') {
osc.frequency.setValueAtTime(600, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
} else if (type === 'win') {
osc.frequency.setValueAtTime(523, audioCtx.currentTime);
osc.frequency.setValueAtTime(659, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(784, audioCtx.currentTime + 0.3);
osc.frequency.setValueAtTime(1047, audioCtx.currentTime + 0.45);
gain.gain.setValueAtTime(0.25, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.6);
} else if (type === 'lose') {
osc.frequency.setValueAtTime(400, audioCtx.currentTime);
osc.frequency.setValueAtTime(300, audioCtx.currentTime + 0.15);
osc.frequency.setValueAtTime(200, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.45);
} else if (type === 'draw') {
osc.frequency.value = 350;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
}
osc.type = 'square';
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.7);
}
function updateMusic() {
if (!audioCtx || !musicEnabled || !musicPlaying) return;
const now = audioCtx.currentTime;
if (now >= nextMelody) {
const note = melody[melodyIdx];
playTone(note.n, note.d, 'square', 0.06);
nextMelody = now + note.d;
melodyIdx = (melodyIdx + 1) % melody.length;
}
if (now >= nextBass) {
const note = bass[bassIdx];
playTone(note.n, note.d, 'triangle', 0.1);
nextBass = now + note.d;
bassIdx = (bassIdx + 1) % bass.length;
}
}
function startMusic() {
if (!musicPlaying && musicEnabled) {
initAudio();
musicPlaying = true;
nextMelody = audioCtx.currentTime;
nextBass = audioCtx.currentTime;
}
}
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let W, H, safeTop, safeBottom;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
safeTop = H * 0.1;
safeBottom = H * 0.9;
}
resize();
window.addEventListener('resize', resize);
const COLS = 7;
const ROWS = 6;
let board = [];
let currentPlayer = 1; // 1 = player (red), 2 = CPU (yellow)
let gameOver = false;
let winner = 0;
let gameRunning = false;
let hoverCol = -1;
let cpuThinking = false;
let winningCells = [];
let aiDifficulty = 0.5; // Start easy, increase over time
function createBoard() {
board = [];
for (let r = 0; r < ROWS; r++) {
board.push(new Array(COLS).fill(0));
}
}
function init() {
createBoard();
currentPlayer = 1;
gameOver = false;
winner = 0;
winningCells = [];
cpuThinking = false;
aiDifficulty = 0.3; // Easy start
gameRunning = true;
startMusic();
}
function getLowestRow(col) {
for (let r = ROWS - 1; r >= 0; r--) {
if (board[r][col] === 0) return r;
}
return -1;
}
function dropPiece(col, player) {
const row = getLowestRow(col);
if (row === -1) return false;
board[row][col] = player;
playSfx('drop');
return true;
}
function checkWin(player) {
// Horizontal
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c <= COLS - 4; c++) {
if (board[r][c] === player && board[r][c+1] === player &&
board[r][c+2] === player && board[r][c+3] === player) {
winningCells = [[r,c], [r,c+1], [r,c+2], [r,c+3]];
return true;
}
}
}
// Vertical
for (let r = 0; r <= ROWS - 4; r++) {
for (let c = 0; c < COLS; c++) {
if (board[r][c] === player && board[r+1][c] === player &&
board[r+2][c] === player && board[r+3][c] === player) {
winningCells = [[r,c], [r+1,c], [r+2,c], [r+3,c]];
return true;
}
}
}
// Diagonal down-right
for (let r = 0; r <= ROWS - 4; r++) {
for (let c = 0; c <= COLS - 4; c++) {
if (board[r][c] === player && board[r+1][c+1] === player &&
board[r+2][c+2] === player && board[r+3][c+3] === player) {
winningCells = [[r,c], [r+1,c+1], [r+2,c+2], [r+3,c+3]];
return true;
}
}
}
// Diagonal up-right
for (let r = 3; r < ROWS; r++) {
for (let c = 0; c <= COLS - 4; c++) {
if (board[r][c] === player && board[r-1][c+1] === player &&
board[r-2][c+2] === player && board[r-3][c+3] === player) {
winningCells = [[r,c], [r-1,c+1], [r-2,c+2], [r-3,c+3]];
return true;
}
}
}
return false;
}
function isBoardFull() {
return board[0].every(cell => cell !== 0);
}
function evaluateWindow(window, player) {
const opp = player === 1 ? 2 : 1;
const playerCount = window.filter(c => c === player).length;
const oppCount = window.filter(c => c === opp).length;
const emptyCount = window.filter(c => c === 0).length;
if (playerCount === 4) return 100;
if (playerCount === 3 && emptyCount === 1) return 5;
if (playerCount === 2 && emptyCount === 2) return 2;
if (oppCount === 3 && emptyCount === 1) return -4;
return 0;
}
function scorePosition(player) {
let score = 0;
// Center column preference
const centerCol = board.map(row => row[3]).filter(c => c === player).length;
score += centerCol * 3;
// Horizontal
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c <= COLS - 4; c++) {
const window = [board[r][c], board[r][c+1], board[r][c+2], board[r][c+3]];
score += evaluateWindow(window, player);
}
}
// Vertical
for (let c = 0; c < COLS; c++) {
for (let r = 0; r <= ROWS - 4; r++) {
const window = [board[r][c], board[r+1][c], board[r+2][c], board[r+3][c]];
score += evaluateWindow(window, player);
}
}
// Diagonals
for (let r = 0; r <= ROWS - 4; r++) {
for (let c = 0; c <= COLS - 4; c++) {
const window = [board[r][c], board[r+1][c+1], board[r+2][c+2], board[r+3][c+3]];
score += evaluateWindow(window, player);
}
}
for (let r = 3; r < ROWS; r++) {
for (let c = 0; c <= COLS - 4; c++) {
const window = [board[r][c], board[r-1][c+1], board[r-2][c+2], board[r-3][c+3]];
score += evaluateWindow(window, player);
}
}
return score;
}
function cpuMove() {
if (gameOver || currentPlayer !== 2) return;
cpuThinking = true;
setTimeout(() => {
// Sometimes make random move (easier for kids)
if (Math.random() > aiDifficulty) {
const validCols = [];
for (let c = 0; c < COLS; c++) {
if (getLowestRow(c) !== -1) validCols.push(c);
}
const col = validCols[Math.floor(Math.random() * validCols.length)];
dropPiece(col, 2);
} else {
// Smart move
let bestScore = -Infinity;
let bestCol = 3;
for (let c = 0; c < COLS; c++) {
const row = getLowestRow(c);
if (row === -1) continue;
board[row][c] = 2;
// Check for winning move
if (checkWin(2)) {
board[row][c] = 0;
winningCells = [];
bestCol = c;
break;
}
// Check if blocking player win
board[row][c] = 1;
if (checkWin(1)) {
board[row][c] = 0;
winningCells = [];
bestCol = c;
break;
}
board[row][c] = 2;
const score = scorePosition(2);
board[row][c] = 0;
if (score > bestScore) {
bestScore = score;
bestCol = c;
}
}
winningCells = [];
dropPiece(bestCol, 2);
}
cpuThinking = false;
if (checkWin(2)) {
gameOver = true;
winner = 2;
gameRunning = false;
playSfx('lose');
} else if (isBoardFull()) {
gameOver = true;
winner = 0;
gameRunning = false;
playSfx('draw');
} else {
currentPlayer = 1;
}
// Gradually increase difficulty
aiDifficulty = Math.min(0.8, aiDifficulty + 0.02);
}, 800);
}
function playerMove(col) {
if (gameOver || currentPlayer !== 1 || cpuThinking) return;
if (getLowestRow(col) === -1) return;
dropPiece(col, 1);
if (checkWin(1)) {
gameOver = true;
winner = 1;
gameRunning = false;
playSfx('win');
} else if (isBoardFull()) {
gameOver = true;
winner = 0;
gameRunning = false;
playSfx('draw');
} else {
currentPlayer = 2;
cpuMove();
}
}
canvas.addEventListener('mousemove', e => {
const availH = safeBottom - safeTop - 60;
const availW = W - 40;
const cellSize = Math.min(availW / COLS, availH / ROWS);
const boardW = cellSize * COLS;
const boardX = (W - boardW) / 2;
hoverCol = Math.floor((e.clientX - boardX) / cellSize);
if (hoverCol < 0 || hoverCol >= COLS) hoverCol = -1;
});
canvas.addEventListener('click', e => {
initAudio();
if (gameOver) { init(); return; }
const availH = safeBottom - safeTop - 60;
const availW = W - 40;
const cellSize = Math.min(availW / COLS, availH / ROWS);
const boardW = cellSize * COLS;
const boardX = (W - boardW) / 2;
const col = Math.floor((e.clientX - boardX) / cellSize);
if (col >= 0 && col < COLS) playerMove(col);
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
initAudio();
if (gameOver) { init(); return; }
const availH = safeBottom - safeTop - 60;
const availW = W - 40;
const cellSize = Math.min(availW / COLS, availH / ROWS);
const boardW = cellSize * COLS;
const boardX = (W - boardW) / 2;
const col = Math.floor((e.touches[0].clientX - boardX) / cellSize);
if (col >= 0 && col < COLS) playerMove(col);
});
document.addEventListener('keydown', e => {
initAudio();
if ((e.key === ' ' || e.key === 'Enter') && gameOver) init();
if (e.key >= '1' && e.key <= '7' && !gameOver && currentPlayer === 1) {
playerMove(parseInt(e.key) - 1);
}
});
function update() {
updateMusic();
}
function draw() {
// Background
ctx.fillStyle = '#1a237e';
ctx.fillRect(0, 0, W, H);
// Safe zones
ctx.fillStyle = '#0d1657';
ctx.fillRect(0, 0, W, safeTop);
ctx.fillRect(0, safeBottom, W, H - safeBottom);
// Header
ctx.fillStyle = '#fff';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'center';
ctx.fillText('CONNECT FOUR', W / 2, 40);
const turnText = gameOver ? '' : (currentPlayer === 1 ? 'Your Turn (Red)' : 'CPU Thinking...');
ctx.font = '18px Arial';
ctx.fillStyle = currentPlayer === 1 ? '#ef5350' : '#ffca28';
ctx.fillText(turnText, W / 2, 65);
// Calculate board
const availH = safeBottom - safeTop - 80;
const availW = W - 40;
const cellSize = Math.min(availW / COLS, availH / ROWS);
const boardW = cellSize * COLS;
const boardH = cellSize * ROWS;
const boardX = (W - boardW) / 2;
const boardY = safeTop + 80 + (availH - boardH) / 2;
// Board background
ctx.fillStyle = '#1565c0';
ctx.beginPath();
ctx.roundRect(boardX - 10, boardY - 10, boardW + 20, boardH + 20, 10);
ctx.fill();
// Hover indicator
if (hoverCol >= 0 && hoverCol < COLS && currentPlayer === 1 && !gameOver && !cpuThinking) {
ctx.fillStyle = 'rgba(239, 83, 80, 0.5)';
ctx.beginPath();
ctx.arc(boardX + hoverCol * cellSize + cellSize / 2, boardY - 25, cellSize / 3, 0, Math.PI * 2);
ctx.fill();
}
// Cells
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const x = boardX + c * cellSize + cellSize / 2;
const y = boardY + r * cellSize + cellSize / 2;
const radius = cellSize * 0.4;
// Hole
ctx.fillStyle = '#0d47a1';
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
// Piece
if (board[r][c] !== 0) {
const isWinning = winningCells.some(cell => cell[0] === r && cell[1] === c);
if (board[r][c] === 1) {
ctx.fillStyle = isWinning ? '#ff8a80' : '#ef5350';
} else {
ctx.fillStyle = isWinning ? '#fff59d' : '#ffca28';
}
ctx.beginPath();
ctx.arc(x, y, radius - 2, 0, Math.PI * 2);
ctx.fill();
// Highlight
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.beginPath();
ctx.arc(x - radius * 0.2, y - radius * 0.2, radius * 0.3, 0, Math.PI * 2);
ctx.fill();
}
}
}
// Controls hint
ctx.fillStyle = '#90caf9';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('Tap column or press 1-7 to drop', W / 2, safeBottom + 25);
// Game over
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.font = 'bold 36px Arial';
ctx.textAlign = 'center';
if (winner === 1) {
ctx.fillStyle = '#4caf50';
ctx.fillText('YOU WIN!', W / 2, H / 2 - 30);
} else if (winner === 2) {
ctx.fillStyle = '#f44336';
ctx.fillText('CPU WINS!', W / 2, H / 2 - 30);
} else {
ctx.fillStyle = '#ffca28';
ctx.fillText("IT'S A DRAW!", W / 2, H / 2 - 30);
}
ctx.fillStyle = '#1565c0';
ctx.beginPath();
ctx.roundRect(W / 2 - 100, H / 2 + 20, 200, 50, 10);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px Arial';
ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 52);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
window.gameScore = 0;
Object.defineProperty(window, 'gameScore', { get: () => winner === 1 ? 100 : 0 });
window.pauseGame = () => { gameRunning = false; musicPlaying = false; };
window.resumeGame = () => { if (!gameOver) { gameRunning = true; startMusic(); } };
window.setMusicEnabled = (v) => { musicEnabled = v; if (!v) musicPlaying = false; };
window.setSfxEnabled = (v) => { sfxEnabled = v; };
init();
gameLoop();
</script>
</body>
</html>