anycoder-59c33955 / index.html
samirerty's picture
Upload folder using huggingface_hub
0170441 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Axeman: Timber Battle</title>
<!-- Importing a nice font for the minimal look -->
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700;900&display=swap" rel="stylesheet">
<!-- FontAwesome for Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--bg-color: #1A1A2E;
--tree-color: #8B7355;
--cut-line: #FF2E63;
--player-color: #4ECDC4;
--axe-color: #C7C7C7;
--text-color: #FFFFFF;
--score-color: #FFD166;
--ui-overlay: rgba(26, 26, 46, 0.9);
--accent: #E94560;
}
* {
box-sizing: border-box;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
padding: 0;
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Montserrat', sans-serif;
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
}
/* Header */
header {
width: 100%;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(0,0,0,0.2);
position: absolute;
top: 0;
z-index: 10;
}
h1 {
font-size: 1.2rem;
margin: 0;
font-weight: 900;
letter-spacing: 1px;
color: var(--player-color);
}
.anycoder-link {
color: var(--text-color);
text-decoration: none;
font-size: 0.8rem;
opacity: 0.7;
transition: opacity 0.3s;
}
.anycoder-link:hover { opacity: 1; text-decoration: underline; }
/* Game Container */
#game-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
canvas {
display: block;
box-shadow: 0 0 50px rgba(0,0,0,0.5);
}
/* UI Overlays */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--ui-overlay);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 20;
transition: opacity 0.3s ease;
}
.hidden {
opacity: 0;
pointer-events: none;
}
.menu-title {
font-size: 3rem;
font-weight: 900;
color: var(--cut-line);
text-transform: uppercase;
margin-bottom: 10px;
text-shadow: 2px 2px 0px var(--player-color);
text-align: center;
}
.menu-subtitle {
font-size: 1rem;
color: var(--score-color);
margin-bottom: 30px;
}
.btn {
background: var(--accent);
color: white;
border: none;
padding: 15px 40px;
font-size: 1.2rem;
font-weight: bold;
border-radius: 50px;
cursor: pointer;
margin: 10px;
transition: transform 0.1s, background 0.2s;
box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4);
font-family: inherit;
min-width: 200px;
}
.btn:hover {
transform: scale(1.05);
background: #ff5e78;
}
.btn:active {
transform: scale(0.95);
}
.mode-select {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
}
.mode-btn {
background: transparent;
border: 2px solid var(--player-color);
color: var(--player-color);
padding: 10px 20px;
}
.mode-btn.active {
background: var(--player-color);
color: var(--bg-color);
}
/* HUD */
#hud {
position: absolute;
top: 60px;
left: 0;
width: 100%;
padding: 0 20px;
display: flex;
justify-content: space-between;
pointer-events: none;
z-index: 5;
}
.hud-item {
text-align: center;
}
.score-label {
font-size: 0.8rem;
opacity: 0.8;
}
.score-value {
font-size: 2rem;
font-weight: 900;
color: var(--score-color);
}
#combo-display {
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
font-size: 2.5rem;
font-weight: 900;
color: var(--cut-line);
opacity: 0;
transition: opacity 0.2s;
text-shadow: 0 0 10px var(--cut-line);
pointer-events: none;
}
.controls-hint {
margin-top: 20px;
display: flex;
gap: 20px;
font-size: 0.9rem;
opacity: 0.7;
}
.key {
border: 1px solid rgba(255,255,255,0.3);
padding: 5px 10px;
border-radius: 5px;
}
/* Touch Controls for Mobile */
#touch-zones {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
z-index: 2;
}
.touch-zone {
flex: 1;
/* background: rgba(255,0,0,0.1); Debugging */
}
/* Responsive Adjustments */
@media (max-width: 600px) {
.menu-title { font-size: 2rem; }
.btn { padding: 12px 30px; min-width: 160px; font-size: 1rem; }
.controls-hint { display: none; } /* Hide keyboard hints on mobile */
}
</style>
</head>
<body>
<header>
<h1><i class="fas fa-tree"></i> AXEMAN</h1>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
</header>
<div id="game-container">
<canvas id="gameCanvas"></canvas>
<!-- HUD -->
<div id="hud" class="hidden">
<div class="hud-item">
<div class="score-label">SCORE</div>
<div id="score-val" class="score-value">0</div>
</div>
<div class="hud-item">
<div class="score-label">TIME</div>
<div id="time-val" class="score-value">--</div>
</div>
<div class="hud-item">
<div class="score-label">BEST</div>
<div id="best-val" class="score-value">0</div>
</div>
</div>
<div id="combo-display">COMBO x2</div>
<!-- Touch Zones (Invisible) -->
<div id="touch-zones" class="hidden">
<div class="touch-zone" id="touch-left"></div>
<div class="touch-zone" id="touch-right"></div>
</div>
<!-- Start Screen -->
<div id="start-screen" class="overlay">
<div class="menu-title">AXEMAN</div>
<div class="menu-subtitle">CUT FAST. DON'T MISTAKE.</div>
<div class="mode-select">
<button class="btn mode-btn active" data-mode="classic">CLASSIC</button>
<button class="btn mode-btn" data-mode="timeattack">TIME ATTACK (90s)</button>
<button class="btn mode-btn" data-mode="survival">SURVIVAL</button>
</div>
<button id="start-btn" class="btn">PLAY GAME</button>
<div class="controls-hint">
<span><span class="key">A</span> / <span class="key"></span> Left</span>
<span><span class="key">D</span> / <span class="key"></span> Right</span>
</div>
</div>
<!-- Game Over Screen -->
<div id="game-over-screen" class="overlay hidden">
<h1 style="color: var(--cut-line); font-size: 3rem;">GAME OVER</h1>
<div style="font-size: 1.5rem; margin: 10px;">Score: <span id="final-score" style="color:var(--score-color)">0</span></div>
<div style="font-size: 1rem; margin-bottom: 30px;">Trees Cut: <span id="final-trees">0</span></div>
<button id="restart-btn" class="btn">TRY AGAIN</button>
<button id="menu-btn" class="btn" style="background:transparent; border:1px solid white; margin-top:10px;">MAIN MENU</button>
</div>
</div>
<script>
/**
* AXEMAN GAME ENGINE
* Built with Vanilla JS, HTML5 Canvas, and Web Audio API
*/
// --- Configuration ---
const CONFIG = {
colors: {
bg: '#1A1A2E',
tree: '#8B7355',
treeLight: '#A68B6B',
cutLine: '#FF2E63',
player: '#4ECDC4',
axe: '#C7C7C7',
gold: '#FFD166',
bird: '#FFFFFF',
ui: '#FFFFFF'
},
gravity: 0.5,
chopForce: 15,
treeBaseHeight: 400, // Starting height
segmentHeight: 40,
maxComboTime: 2000 // ms to keep combo
};
// --- Audio System (Web Audio API) ---
const AudioSys = {
ctx: null,
init: function() {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
this.ctx = new AudioContext();
},
playTone: function(freq, type, duration, vol = 0.1) {
if (!this.ctx) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
gain.gain.setValueAtTime(vol, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + duration);
},
playNoise: function(duration, vol = 0.2) {
if (!this.ctx) return;
const bufferSize = this.ctx.sampleRate * duration;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = this.ctx.createBufferSource();
noise.buffer = buffer;
const gain = this.ctx.createGain();
// Lowpass filter for "thud" sound
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 1000;
gain.gain.setValueAtTime(vol, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
noise.connect(filter);
filter.connect(gain);
gain.connect(this.ctx.destination);
noise.start();
},
sfx: {
chop: () => {
AudioSys.playNoise(0.1, 0.3);
AudioSys.playTone(150, 'square', 0.1, 0.1);
},
hitBranch: () => {
AudioSys.playTone(100, 'sawtooth', 0.3, 0.3);
},
score: () => {
AudioSys.playTone(600, 'sine', 0.1, 0.1);
setTimeout(() => AudioSys.playTone(900, 'sine', 0.2, 0.1), 100);
},
combo: () => {
AudioSys.playTone(400, 'triangle', 0.1, 0.1);
setTimeout(() => AudioSys.playTone(600, 'triangle', 0.1, 0.1), 50);
setTimeout(() => AudioSys.playTone(800, 'triangle', 0.2, 0.1), 100);
},
powerup: () => {
AudioSys.playTone(300, 'sine', 0.1, 0.2);
AudioSys.playTone(600, 'sine', 0.3, 0.2);
},
gameOver: () => {
AudioSys.playTone(200, 'sawtooth', 1.0, 0.5);
setTimeout(() => AudioSys.playTone(100, 'sawtooth', 1.0, 0.5), 200);
}
}
};
// --- Game State Management ---
const Game = {
canvas: document.getElementById('gameCanvas'),
ctx: document.getElementById('gameCanvas').getContext('2d'),
width: 0,
height: 0,
state: 'MENU', // MENU, PLAYING, GAMEOVER
mode: 'classic', // classic, timeattack, survival
lastTime: 0,
score: 0,
bestScore: parseInt(localStorage.getItem('axeman_best')) || 0,
treesCut: 0,
// Mechanics
playerSide: 1, // 1 = Right, -1 = Left
lastChopSide: 0, // 0 = none, 1 = right, -1 = left
treeHP: 10,
treeMaxHP: 10,
treeWidth: 120,
timeRemaining: 0,
combo: 0,
comboTimer: 0,
// Entities
particles: [],
powerUps: [],
activePowerUp: null, // {type: 'GOLDEN_AXE', endTime: timestamp}
birds: [], // {x, y, side}
// Visual Shake
shake: 0,
init: function() {
this.resize();
window.addEventListener('resize', () => this.resize());
this.setupInputs();
this.loop(0);
// UI Updates
document.getElementById('best-val').innerText = this.bestScore;
},
resize: function() {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.canvas.width = this.width;
this.canvas.height = this.height;
},
start: function(mode) {
AudioSys.init();
this.mode = mode;
this.state = 'PLAYING';
this.score = 0;
this.treesCut = 0;
this.combo = 0;
this.comboTimer = 0;
this.activePowerUp = null;
this.birds = [];
this.powerUps = [];
// Tree Setup
this.treeHP = 10;
this.treeMaxHP = 10;
this.lastChopSide = 0; // Reset so first hit is always safe
// Mode Setup
if (mode === 'timeattack') {
this.timeRemaining = 90;
} else if (mode === 'classic') {
this.timeRemaining = 10; // Initial buffer
} else {
this.timeRemaining = 9999;
}
// UI
document.getElementById('start-screen').classList.add('hidden');
document.getElementById('game-over-screen').classList.add('hidden');
document.getElementById('hud').classList.remove('hidden');
document.getElementById('touch-zones').classList.remove('hidden');
this.updateHUD();
},
endGame: function() {
this.state = 'GAMEOVER';
AudioSys.sfx.gameOver();
if (this.score > this.bestScore) {
this.bestScore = this.score;
localStorage.setItem('axeman_best', this.bestScore);
}
document.getElementById('game-over-screen').classList.remove('hidden');
document.getElementById('hud').classList.add('hidden');
document.getElementById('touch-zones').classList.add('hidden');
document.getElementById('final-score').innerText = this.score;
document.getElementById('final-trees').innerText = this.treesCut;
},
setupInputs: function() {
// Keyboard
window.addEventListener('keydown', (e) => {
if (this.state !== 'PLAYING') return;
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') this.chop(-1);
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') this.chop(1);
});
// Touch
const leftZone = document.getElementById('touch-left');
const rightZone = document.getElementById('touch-right');
const handleTouch = (side) => (e) => {
e.preventDefault();
if (this.state === 'PLAYING') this.chop(side);
};
leftZone.addEventListener('touchstart', handleTouch(-1));
rightZone.addEventListener('touchstart', handleTouch(1));
// Mode Selection
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.mode = btn.dataset.mode;
});
});
// Menu Buttons
document.getElementById('start-btn').addEventListener('click', () => this.start(this.mode));
document.getElementById('restart-btn').addEventListener('click', () => this.start(this.mode));
document.getElementById('menu-btn').addEventListener('click', () => {
document.getElementById('game-over-screen').classList.add('hidden');
document.getElementById('start-screen').classList.remove('hidden');
this.state = 'MENU';
});
},
chop: function(side) {
// 1. Validate Rule: Don't hit same side twice
if (this.lastChopSide === side) {
this.triggerShake(20);
AudioSys.sfx.hitBranch();
this.endGame();
return;
}
this.lastChopSide = side;
this.playerSide = side;
// 2. Audio & Visuals
AudioSys.sfx.chop();
this.spawnParticles(side);
this.triggerShake(5);
// 3. Logic
let damage = 1;
if (this.activePowerUp && this.activePowerUp.type === 'GOLDEN_AXE') damage = 2;
// Check for obstacles on this segment (Bird)
// Simplified: Random chance bird spawns on current hit side
// If bird exists, damage is 0 or negative? Let's say bird needs 2 hits.
// For simplicity: Bird just adds HP to tree if present.
const birdIndex = this.birds.findIndex(b => b.side === side && Math.abs(b.y - (this.height - 150)) < 50);
if (birdIndex !== -1) {
AudioSys.playTone(800, 'square', 0.1); // Tweet
this.birds.splice(birdIndex, 1);
this.treeHP += 1; // Penalty: extra hit needed
this.shake = 10;
}
this.treeHP -= damage;
// 4. Combo System
this.combo++;
this.comboTimer = CONFIG.maxComboTime;
// 5. Score
let points = 10 + (this.combo * 2);
if (this.activePowerUp && this.activePowerUp.type === 'MAGNET') points *= 2;
this.score += points;
// 6. Tree Logic
if (this.treeHP <= 0) {
this.treeDown();
}
// 7. Time Management (Classic Mode)
if (this.mode === 'classic') {
this.timeRemaining = Math.min(this.timeRemaining + 1, 10);
}
this.updateHUD();
},
treeDown: function() {
AudioSys.sfx.score();
this.treesCut++;
// Increase difficulty
const speedBoost = Math.min(5, Math.floor(this.treesCut / 2));
this.treeMaxHP = Math.min(20, 10 + speedBoost);
this.treeHP = this.treeMaxHP;
this.lastChopSide = 0; // Reset side restriction for new tree
// Visual: Tree falls
this.triggerShake(10);
// Chance for PowerUp
if (Math.random() < 0.2) this.spawnPowerUp();
// Spawn new bird occasionally
if (Math.random() < 0.3) {
this.birds.push({
side: Math.random() > 0.5 ? 1 : -1,
y: this.height - 200 - Math.random() * 200
});
}
},
spawnPowerUp: function() {
const types = ['GOLDEN_AXE', 'SLOW_TIME', 'COMBO_LOCK', 'MAGNET'];
const type = types[Math.floor(Math.random() * types.length)];
this.powerUps.push({
x: this.width / 2 + (Math.random() * 100 - 50),
y: -50,
type: type,
active: true
});
},
activatePowerUp: function(type) {
AudioSys.sfx.powerup();
const duration = 10000; // 10s
if (type === 'SLOW_TIME') {
// Logic handled in update
}
this.activePowerUp = {
type: type,
endTime: Date.now() + duration
};
},
spawnParticles: function(side) {
const count = 10;
const originX = this.width / 2 + (side * (this.treeWidth / 2));
const originY = this.height - 180;
for (let i = 0; i < count; i++) {
this.particles.push({
x: originX,
y: originY,
vx: (side * Math.random() * 5) + (Math.random() - 0.5),
vy: -Math.random() * 10,
life: 1.0,
color: CONFIG.colors.treeLight
});
}
},
triggerShake: function(intensity) {
this.shake = intensity;
},
updateHUD: function() {
document.getElementById('score-val').innerText = this.score;
let timeDisplay = "";
if (this.mode === 'timeattack') {
timeDisplay = Math.ceil(this.timeRemaining) + "s";
} else if (this.mode