Cricket Champions
by ShadowOwl70950 lines32.1 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Cricket Champions</title>
<style>
:root{
--gold:#ffc928; --blue:#071426; --panel:rgba(4,12,26,.9);
--green:#22c55e; --red:#ef4444;
}
*{box-sizing:border-box}
body{
margin:0; overflow:hidden; background:#020713; color:white;
font-family:Inter,system-ui,Arial,sans-serif; touch-action:manipulation;
}
canvas{display:block}
button,select,input{font:inherit}
button{
border:0; border-radius:9px; padding:10px 14px; cursor:pointer;
color:white; background:#163257; font-weight:800;
}
button:hover{filter:brightness(1.2)}
button:active{transform:scale(.97)}
.gold{background:linear-gradient(135deg,#ffb300,#ff6f00); color:#151000}
.green{background:#15803d}
.red{background:#b91c1c}
.hidden{display:none!important}
#menu,#toss,#result,#pauseMenu{
position:fixed; inset:0; z-index:20; display:flex;
align-items:center; justify-content:center; padding:18px;
background:radial-gradient(circle at top,rgba(18,62,109,.95),rgba(1,5,14,.98));
}
.card{
width:min(720px,100%); max-height:94vh; overflow:auto;
padding:24px; border-radius:20px; background:rgba(3,13,29,.94);
border:1px solid #31527a; box-shadow:0 20px 80px #000;
}
h1{font-size:clamp(34px,7vw,66px); margin:0; color:var(--gold); line-height:.95}
h2,h3{margin:8px 0}
.subtitle{color:#9fc5ed; margin:10px 0 20px}
.modeGrid,.shopGrid{display:grid; grid-template-columns:repeat(2,1fr); gap:10px}
.mode{
text-align:left; min-height:74px; background:linear-gradient(135deg,#102b4b,#08182d);
}
.mode small{display:block; color:#a8c4df; margin-top:4px}
.topline{display:flex; justify-content:space-between; gap:8px; flex-wrap:wrap}
.badge{background:#122842; padding:7px 10px; border-radius:20px; color:#d7ecff}
#hud{position:fixed; inset:0; z-index:5; pointer-events:none}
#scorebar{
pointer-events:auto; margin:10px auto; width:min(970px,calc(100% - 20px));
display:flex; justify-content:space-between; align-items:center; gap:8px;
padding:9px 12px; border-radius:12px; background:var(--panel);
border-bottom:3px solid var(--gold); box-shadow:0 5px 25px #0008;
}
#score{font-size:clamp(18px,4vw,30px); font-weight:900}
#detail{font-size:12px; color:#bcd5ed; text-align:center}
.iconbtn{padding:8px 11px}
#message{
position:absolute; top:92px; left:50%; transform:translateX(-50%);
min-width:220px; max-width:90%; padding:9px 16px; border-radius:10px;
background:#061326dc; text-align:center; font-weight:900;
transition:.25s; text-shadow:0 2px 3px #000;
}
#controls{
pointer-events:auto; position:absolute; bottom:10px; left:50%;
transform:translateX(-50%); width:min(900px,calc(100% - 16px));
padding:10px; border-radius:15px; background:var(--panel);
}
#shots{display:grid; grid-template-columns:repeat(7,1fr); gap:6px}
.shot{padding:9px 4px; font-size:12px}
#runBtn{margin-top:7px; width:100%; background:#15803d}
#bowlControls{display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; align-items:end}
label{font-size:11px; color:#b7d0e8}
label>*{display:block; width:100%; margin-top:4px}
select,input{
border:1px solid #31527a; background:#081a30; color:white;
border-radius:7px; padding:8px;
}
#bowlBtn{grid-column:1/-1; background:#b91c1c}
#ticker{
position:absolute; right:12px; top:100px; width:190px;
background:#061326dd; border-radius:10px; padding:10px; font-size:12px;
}
#balls{display:flex; gap:4px; margin-top:7px; flex-wrap:wrap}
.ballMark{
width:25px; height:25px; display:grid; place-items:center;
border-radius:50%; background:#1e3a5f; font-size:10px; font-weight:bold;
}
#settingsBox{display:grid; gap:12px; margin:15px 0}
.statline{display:flex; justify-content:space-between; padding:7px; border-bottom:1px solid #234}
#toast{
position:fixed; z-index:50; bottom:20px; left:50%; transform:translateX(-50%);
background:#111d; padding:10px 18px; border-radius:20px; opacity:0;
pointer-events:none; transition:.3s;
}
@media(max-width:650px){
#shots{grid-template-columns:repeat(4,1fr)}
#ticker{top:100px; right:7px; width:145px}
#controls{bottom:4px}
.shot{font-size:10px; padding:8px 2px}
#bowlControls{grid-template-columns:1fr 1fr}
#bowlBtn{grid-column:1/-1}
}
</style>
</head>
<body>
<div id="menu">
<div class="card">
<div class="topline">
<div>
<h1>CRICKET<br>CHAMPIONS</h1>
<div class="subtitle">Take the pitch. Build your legacy.</div>
</div>
<div>
<div class="badge">⭐ Level <span id="level">1</span></div>
<div class="badge" style="margin-top:7px">🪙 <span id="coins">0</span></div>
</div>
</div>
<h3>Select mode</h3>
<div class="modeGrid">
<button class="mode" data-mode="quick">⚡ Quick Match<small>2 overs · 3 wickets</small></button>
<button class="mode" data-mode="t20">🏆 T20 Match<small>20 overs · 10 wickets</small></button>
<button class="mode" data-mode="tournament">🥇 Tournament<small>Win matches and trophies</small></button>
<button class="mode" data-mode="practice">🎯 Practice<small>Unlimited batting</small></button>
<button class="mode" data-mode="challenge">🔥 Challenge<small>Chase 36 from 12 balls</small></button>
</div>
<h3 style="margin-top:18px">Career</h3>
<div id="career"></div>
<h3>Equipment</h3>
<div class="shopGrid">
<button id="batShop">Unlock Golden Bat · 100 🪙</button>
<button id="jerseyShop">Unlock Red Jersey · 150 🪙</button>
</div>
</div>
</div>
<div id="toss" class="hidden">
<div class="card" style="text-align:center;max-width:480px">
<h2>Match Toss</h2>
<p id="tossText">Call heads or tails.</p>
<div id="callButtons">
<button onclick="callToss('Heads')">Heads</button>
<button onclick="callToss('Tails')">Tails</button>
</div>
<div id="choiceButtons" class="hidden">
<button class="gold" onclick="chooseInnings(true)">Bat First</button>
<button onclick="chooseInnings(false)">Bowl First</button>
</div>
</div>
</div>
<div id="hud" class="hidden">
<div id="scorebar">
<div>
<div id="teamName">CHAMPIONS</div>
<div id="score">0/0</div>
</div>
<div id="detail"></div>
<button class="iconbtn" onclick="pauseGame()">Ⅱ</button>
</div>
<div id="message">Prepare for the next delivery</div>
<div id="ticker">
<b>THIS OVER</b>
<div id="balls"></div>
<hr style="border-color:#234">
<div id="matchInfo"></div>
</div>
<div id="controls">
<div id="batControls">
<div id="shots">
<button class="shot" data-shot="defensive">1 Defensive</button>
<button class="shot" data-shot="straight">2 Straight</button>
<button class="shot" data-shot="cover">3 Cover</button>
<button class="shot" data-shot="pull">4 Pull</button>
<button class="shot" data-shot="cut">5 Cut</button>
<button class="shot" data-shot="lofted">6 Lofted</button>
<button class="shot" data-shot="sweep">7 Sweep</button>
</div>
<button id="runBtn">RUN · R</button>
</div>
<div id="bowlControls" class="hidden">
<label>Delivery
<select id="bowlType">
<option>Fast</option><option>Swing</option><option>Spin</option>
<option>Yorker</option><option>Bouncer</option><option>Slower</option>
</select>
</label>
<label>Line
<input id="line" type="range" min="-100" max="100" value="0">
</label>
<label>Pace
<input id="pace" type="range" min="40" max="100" value="80">
</label>
<button id="bowlBtn">BOWL</button>
</div>
</div>
</div>
<div id="pauseMenu" class="hidden">
<div class="card" style="max-width:440px">
<h2>Paused</h2>
<div id="settingsBox">
<label>Time
<select id="timeSetting"><option value="day">Day</option><option value="night">Night</option></select>
</label>
<label>Weather
<select id="weatherSetting"><option value="clear">Clear</option><option value="rain">Rain</option></select>
</label>
<label><input id="soundSetting" type="checkbox" checked> Sound enabled</label>
</div>
<button class="green" onclick="resumeGame()">Resume</button>
<button class="red" onclick="quitGame()">Quit Match</button>
</div>
</div>
<div id="result" class="hidden">
<div class="card" style="max-width:530px;text-align:center">
<h2 id="resultTitle">Victory!</h2>
<p id="resultText"></p>
<div id="rewards"></div>
<button class="gold" onclick="restartMatch()">Play Again</button>
<button onclick="quitGame()">Main Menu</button>
</div>
</div>
<div id="toast"></div>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script>
"use strict";
/* ---------- DOM helpers ---------- */
const $ = id => document.getElementById(id);
const show = (id, yes=true) => $(id).classList.toggle("hidden", !yes);
const clamp = (v,a,b) => Math.max(a,Math.min(b,v));
const rand = (a,b) => a + Math.random()*(b-a);
const pick = a => a[Math.floor(Math.random()*a.length)];
/* ---------- Persistent career ---------- */
let career = JSON.parse(localStorage.getItem("ccCareer") || "null") || {
xp:0, coins:0, matches:0, wins:0, sixes:0, fours:0,
wickets:0, trophies:0, goldenBat:false, redJersey:false
};
function saveCareer(){
localStorage.setItem("ccCareer",JSON.stringify(career));
renderCareer();
}
function renderCareer(){
$("level").textContent = Math.floor(career.xp/100)+1;
$("coins").textContent = career.coins;
$("career").innerHTML = `
<div class="statline"><span>XP</span><b>${career.xp}</b></div>
<div class="statline"><span>Matches / Wins</span><b>${career.matches} / ${career.wins}</b></div>
<div class="statline"><span>Fours / Sixes</span><b>${career.fours} / ${career.sixes}</b></div>
<div class="statline"><span>Wickets / Trophies</span><b>${career.wickets} / ${career.trophies}</b></div>`;
$("batShop").textContent = career.goldenBat ? "✓ Golden Bat Equipped" : "Unlock Golden Bat · 100 🪙";
$("jerseyShop").textContent = career.redJersey ? "✓ Red Jersey Equipped" : "Unlock Red Jersey · 150 🪙";
}
function purchase(item,cost){
if(career[item]) return toast("Already unlocked");
if(career.coins<cost) return toast("Not enough coins");
career.coins-=cost; career[item]=true; saveCareer(); toast("Equipment unlocked!");
}
$("batShop").onclick=()=>purchase("goldenBat",100);
$("jerseyShop").onclick=()=>purchase("redJersey",150);
renderCareer();
/* ---------- Three.js scene ---------- */
let scene,camera,renderer,ball,bat,batsman,bowler;
let fielders=[], crowd=[], rain=[];
let lastTime=0, paused=false;
function init3D(){
scene=new THREE.Scene();
scene.background=new THREE.Color(0x79bced);
scene.fog=new THREE.Fog(0x8cc9ed,45,100);
camera=new THREE.PerspectiveCamera(55,innerWidth/innerHeight,.1,180);
camera.position.set(0,8,18);
camera.lookAt(0,1,0);
renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setPixelRatio(Math.min(devicePixelRatio,1.6));
renderer.setSize(innerWidth,innerHeight);
renderer.shadowMap.enabled=true;
document.body.prepend(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xd9eeff,0x28461f,2.1));
const sun=new THREE.DirectionalLight(0xffffff,2.3);
sun.position.set(-20,35,15); sun.castShadow=true; scene.add(sun);
// Oval field
const grass=new THREE.Mesh(
new THREE.CircleGeometry(43,80),
new THREE.MeshStandardMaterial({color:0x238139,roughness:.95})
);
grass.rotation.x=-Math.PI/2; grass.scale.z=.78; grass.receiveShadow=true;
scene.add(grass);
// Boundary rope
const rope=new THREE.Mesh(
new THREE.TorusGeometry(38,.16,8,100),
new THREE.MeshStandardMaterial({color:0xffffff})
);
rope.rotation.x=Math.PI/2; rope.scale.y=.78; rope.position.y=.13; scene.add(rope);
// Pitch
const pitch=new THREE.Mesh(
new THREE.BoxGeometry(3.6,.12,22),
new THREE.MeshStandardMaterial({color:0xc9a66b,roughness:1})
);
pitch.position.y=.09; pitch.receiveShadow=true; scene.add(pitch);
// Crease lines
[-8,8].forEach(z=>{
const line=new THREE.Mesh(
new THREE.BoxGeometry(4.2,.02,.1),
new THREE.MeshBasicMaterial({color:0xffffff})
);
line.position.set(0,.17,z); scene.add(line);
});
createWickets(-8); createWickets(8);
createStadium();
createPlayers();
ball=new THREE.Mesh(
new THREE.SphereGeometry(.18,18,18),
new THREE.MeshStandardMaterial({color:0xb51d28,roughness:.45})
);
ball.castShadow=true; ball.position.set(0,.35,-7); scene.add(ball);
window.addEventListener("resize",()=>{
camera.aspect=innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
});
}
function createWickets(z){
const mat=new THREE.MeshStandardMaterial({color:0xf4d78b});
[-.35,0,.35].forEach(x=>{
const stump=new THREE.Mesh(new THREE.CylinderGeometry(.045,.045,1.4,8),mat);
stump.position.set(x,.75,z); scene.add(stump);
});
}
function person(color,x,z,scale=1){
const group=new THREE.Group();
const body=new THREE.Mesh(
new THREE.CapsuleGeometry(.28*scale,.75*scale,4,8),
new THREE.MeshStandardMaterial({color})
);
body.position.y=1.05*scale; body.castShadow=true; group.add(body);
const head=new THREE.Mesh(
new THREE.SphereGeometry(.21*scale,12,12),
new THREE.MeshStandardMaterial({color:0xb87955})
);
head.position.y=1.78*scale; group.add(head);
group.position.set(x,0,z); scene.add(group); return group;
}
function createPlayers(){
batsman=person(career.redJersey?0xc51b2f:0x1557b0,0,7.5);
bowler=person(0xb91c1c,0,-7);
bat=new THREE.Mesh(
new THREE.BoxGeometry(.18,1.45,.12),
new THREE.MeshStandardMaterial({color:career.goldenBat?0xffc400:0xd7a85b})
);
bat.position.set(.55,1.05,7.35); bat.rotation.z=-.25; scene.add(bat);
[[-8,2],[8,1],[-12,-4],[12,-5],[0,-18],[-20,8],[20,9],[0,17]].forEach(p=>{
fielders.push(person(0xb91c1c,p[0],p[1],.9));
});
}
function createStadium(){
const standMat=new THREE.MeshStandardMaterial({color:0x30445b});
const stand=new THREE.Mesh(new THREE.TorusGeometry(46,5,8,80),standMat);
stand.rotation.x=Math.PI/2; stand.scale.y=.78; stand.position.y=1.8; scene.add(stand);
for(let i=0;i<150;i++){
const a=Math.random()*Math.PI*2, r=43+Math.random()*5;
const c=new THREE.Mesh(
new THREE.BoxGeometry(.22,.35,.22),
new THREE.MeshBasicMaterial({color:pick([0xffd43b,0x2585e6,0xe63d51,0xffffff])})
);
c.position.set(Math.cos(a)*r,3+Math.random()*2,Math.sin(a)*r*.78);
scene.add(c); crowd.push(c);
}
// Dressing rooms
[-1,1].forEach(s=>{
const room=new THREE.Mesh(
new THREE.BoxGeometry(12,4,5),
new THREE.MeshStandardMaterial({color:0x17273b})
);
room.position.set(s*28,2,-23); scene.add(room);
});
// Scoreboard
const board=new THREE.Mesh(
new THREE.BoxGeometry(13,6,.6),
new THREE.MeshStandardMaterial({color:0x07111f,emissive:0x061629})
);
board.position.set(0,7,-31); scene.add(board);
// Floodlights
[[-30,-24],[30,-24],[-32,23],[32,23]].forEach(([x,z])=>{
const pole=new THREE.Mesh(
new THREE.CylinderGeometry(.13,.18,18,8),
new THREE.MeshStandardMaterial({color:0x88939d})
);
pole.position.set(x,9,z); scene.add(pole);
const lamp=new THREE.PointLight(0xffffff,0,55);
lamp.position.set(x,18,z); lamp.userData.flood=true; scene.add(lamp);
});
}
/* ---------- Match state ---------- */
const modeRules={
quick:{overs:2,wickets:3},
t20:{overs:20,wickets:10},
tournament:{overs:3,wickets:5},
practice:{overs:999,wickets:999},
challenge:{overs:2,wickets:3}
};
let game={
mode:"quick", overs:2, maxWickets:3, innings:0, userBatFirst:true,
runs:0,wickets:0,balls:0,target:null,firstScore:null,
striker:0,batterRuns:[0,0],bowlerRuns:0,bowlerWickets:0,
overEvents:[], active:false, phase:"idle", delivery:null,
shotUsed:false,runIntent:false,lastResult:"", tossWon:false
};
function startMode(mode){
game.mode=mode;
Object.assign(game,modeRules[mode]);
show("menu",false);
if(mode==="practice"){
game.userBatFirst=true;
beginMatch();
}else if(mode==="challenge"){
game.userBatFirst=false;
game.innings=1;
game.firstScore=35;
game.target=36;
beginInnings(true);
}else{
show("toss",true);
$("tossText").textContent="Call heads or tails.";
show("callButtons",true); show("choiceButtons",false);
}
}
document.querySelectorAll("[data-mode]").forEach(b=>b.onclick=()=>startMode(b.dataset.mode));
function callToss(call){
const coin=Math.random()<.5?"Heads":"Tails";
game.tossWon=call===coin;
show("callButtons",false);
if(game.tossWon){
$("tossText").textContent=`${coin}! You won the toss.`;
show("choiceButtons",true);
}else{
const aiBats=Math.random()<.55;
game.userBatFirst=!aiBats;
$("tossText").textContent=`${coin}. Opponent chooses to ${aiBats?"bat":"bowl"} first.`;
setTimeout(beginMatch,1500);
}
}
function chooseInnings(bat){
game.userBatFirst=bat;
beginMatch();
}
function beginMatch(){
show("toss",false); show("result",false); show("hud",true);
game.innings=0; game.firstScore=null; game.target=null;
beginInnings(game.userBatFirst);
}
function beginInnings(userBatting){
game.runs=0; game.wickets=0; game.balls=0; game.striker=0;
game.batterRuns=[0,0]; game.bowlerRuns=0; game.bowlerWickets=0;
game.overEvents=[]; game.active=true; game.phase="idle";
game.userBatting=userBatting;
show("toss",false); show("result",false); show("hud",true);
show("batControls",userBatting); show("bowlControls",!userBatting);
resetCamera();
updateHUD();
say(userBatting ? "You are batting" : "You are bowling");
if(userBatting) setTimeout(aiDeliver,1000);
}
function resetCamera(){
camera.position.set(game.userBatting?0:0,7,game.userBatting?18:-18);
camera.lookAt(0,1,0);
}
/* ---------- Batting ---------- */
function aiDeliver(){
if(!game.active || paused || !game.userBatting || game.phase!=="idle") return;
const types=["Fast","Swing","Spin","Yorker","Bouncer","Slower"];
let type=pick(types);
// Match-aware bowling strategy
if(game.target && game.target-game.runs<10) type=pick(["Yorker","Slower","Fast"]);
game.delivery={
type,
duration:type==="Fast"||type==="Bouncer"?850:type==="Slower"?1250:1050,
start:performance.now(),
line:rand(-.7,.7),
swing:type==="Swing"?rand(-.8,.8):type==="Spin"?rand(-.45,.45):0
};
game.phase="delivery"; game.shotUsed=false; game.runIntent=false;
ball.position.set(game.delivery.line,.35,-7);
say(`${type} delivery`);
}
const shotData={
defensive:{power:.25,risk:.03,angle:0},
straight:{power:.68,risk:.10,angle:0},
cover:{power:.72,risk:.13,angle:-.65},
pull:{power:.78,risk:.17,angle:.85},
cut:{power:.69,risk:.14,angle:-1.05},
lofted:{power:1,risk:.34,angle:rand(-.4,.4)},
sweep:{power:.68,risk:.18,angle:1.15}
};
function playShot(name){
if(!game.active || !game.userBatting || game.phase!=="delivery" || game.shotUsed) return;
game.shotUsed=true;
const idealZ=6.35;
const timing=Math.abs(ball.position.z-idealZ);
const quality=clamp(1-timing/5,0,1);
swingBat(name);
if(timing>4.1){
say("Missed!");
return;
}
const d=game.delivery.type, data=shotData[name];
let suitability=1;
if(d==="Bouncer" && name==="pull") suitability=1.3;
if(d==="Spin" && name==="sweep") suitability=1.3;
if(d==="Yorker" && name==="straight") suitability=1.15;
if(d==="Bouncer" && name==="defensive") suitability=.65;
if(d==="Yorker" && name==="pull") suitability=.5;
let power=quality*data.power*suitability*rand(.88,1.12);
if(career.goldenBat) power*=1.08;
game.phase="hit";
const distance=8+power*38;
const angle=data.angle+rand(-.25,.25);
const loft=name==="lofted" ? 7+power*8 : 1+power*4;
animateHit(
new THREE.Vector3(Math.sin(angle)*distance,loft,7-Math.cos(angle)*distance),
power,name,data.risk,quality
);
}
function swingBat(name){
const old=bat.rotation.z;
bat.rotation.z=name==="sweep"?-1.6:-2.2;
setTimeout(()=>bat.rotation.z=old,240);
sound("hit");
}
function animateHit(target,power,name,risk,quality){
const start=ball.position.clone(), begun=performance.now();
const duration=950;
function move(now){
if(game.phase!=="hit") return;
const t=clamp((now-begun)/duration,0,1);
ball.position.lerpVectors(start,target,t);
ball.position.y=Math.max(.2,(1-t)*start.y+t*target.y+Math.sin(t*Math.PI)*4*power);
camera.position.x+=(ball.position.x-camera.position.x)*.025;
camera.lookAt(ball.position);
if(t<1) requestAnimationFrame(move);
else resolveHit(power,name,risk,quality);
}
requestAnimationFrame(move);
}
function resolveHit(power,name,risk,quality){
let wicket=false, runs=0, text="";
const catchChance=risk*(1-quality*.55);
if(Math.random()<catchChance){
wicket=true; text=Math.random()<.7?"CAUGHT!":"BOWLED!";
}else if(power>.83){
runs=6; text="SIX!";
}else if(power>.58){
runs=4; text="FOUR!";
}else if(power>.36){
runs=Math.random()<.45?3:2; text=`${runs} runs`;
}else if(power>.16){
runs=1; text="Single";
}else text="Dot ball";
if(game.runIntent && !wicket && runs<4){
if(Math.random()<.16+Math.max(0,.35-quality)){
wicket=true; runs=0; text="RUN OUT!";
}else{
runs=Math.min(3,runs+1); text=`Quick running: ${runs}`;
}
}
addBall(runs,wicket,false,text);
}
/* ---------- Bowling ---------- */
$("bowlBtn").onclick=()=>{
if(!game.active || game.userBatting || game.phase!=="idle") return;
const type=$("bowlType").value;
const line=+$("line").value/100;
const pace=+$("pace").value;
game.delivery={type,line,pace,start:performance.now(),duration:1450-pace*7};
game.phase="bowling";
ball.position.set(0,.4,7);
say(`${type} ball released`);
const start=ball.position.clone();
const end=new THREE.Vector3(line*1.7,.25,-7);
const begun=performance.now();
function move(now){
if(game.phase!=="bowling") return;
const t=clamp((now-begun)/game.delivery.duration,0,1);
ball.position.lerpVectors(start,end,t);
if(type==="Bouncer") ball.position.y=.3+Math.sin(t*Math.PI)*1.2;
if(type==="Swing") ball.position.x+=Math.sin(t*Math.PI)*.55;
if(type==="Spin") ball.rotation.y+=.35;
if(t<1) requestAnimationFrame(move);
else resolveAIBatting(type,line,pace);
}
requestAnimationFrame(move);
};
function resolveAIBatting(type,line,pace){
if(Math.abs(line)>.88){
addBall(1,false,true,"Wide");
return;
}
let wicketChance=.08, boundary=.20;
if(type==="Yorker" && Math.abs(line)<.25) wicketChance+=.13;
if(type==="Swing" && Math.abs(line)<.45) wicketChance+=.07;
if(type==="Slower" && pace<65) wicketChance+=.07;
if(type==="Bouncer" && pace>85) wicketChance+=.06;
if(type==="Fast" && pace>92) boundary+=.09;
// AI attacks more aggressively while chasing
if(game.target){
const needed=game.target-game.runs;
const ballsLeft=game.overs*6-game.balls;
if(needed>ballsLeft*1.5){ boundary+=.17; wicketChance+=.08; }
}
const r=Math.random();
if(r<wicketChance){
career.wickets++; saveCareer();
addBall(0,true,false,pick(["BOWLED!","CAUGHT!","LBW!"]));
}else if(r<wicketChance+boundary){
const runs=Math.random()<.72?4:6;
addBall(runs,false,false,`AI hits ${runs}!`);
}else{
const runs=pick([0,0,1,1,1,2,2,3]);
addBall(runs,false,false,runs?`${runs} run${runs>1?"s":""}`:"Dot ball");
}
}
/* ---------- Scoring and innings ---------- */
function addBall(runs,wicket,extra,text){
if(!game.active) return;
game.runs+=runs;
if(game.userBatting) game.batterRuns[game.striker]+=runs;
else game.bowlerRuns+=runs;
if(wicket){
game.wickets++;
if(!game.userBatting) game.bowlerWickets++;
}
if(!extra){
game.balls++;
game.overEvents.push(wicket?"W":String(runs));
if(runs%2===1) game.striker=1-game.striker;
if(game.balls%6===0){
game.striker=1-game.striker;
setTimeout(()=>game.overEvents=[],700);
}
}else{
game.overEvents.push("Wd");
}
if(game.userBatting){
if(runs===4) career.fours++;
if(runs===6) career.sixes++;
}
sound(wicket?"wicket":runs>=4?"cheer":"bounce");
say(text);
updateHUD();
if(checkEnd()) return;
game.phase="waiting";
setTimeout(()=>{
if(!game.active) return;
game.phase="idle";
ball.position.set(0,.35,game.userBatting?-7:7);
resetCamera();
updateHUD();
if(game.userBatting) setTimeout(aiDeliver,500);
},1150);
}
function checkEnd(){
if(game.mode==="practice") return false;
const exhausted=game.balls>=game.overs*6 || game.wickets>=game.maxWickets;
const chased=game.innings===1 && game.runs>=game.target;
if(chased){
finishMatch(game.userBatting);
return true;
}
if(!exhausted) return false;
if(game.innings===0){
game.firstScore=game.runs;
game.target=game.runs+1;
game.innings=1;
game.active=false;
say(`Innings complete · Target ${game.target}`);
setTimeout(()=>beginInnings(!game.userBatFirst),1800);
}else{
if(game.runs===game.firstScore) finishMatch(null);
else finishMatch(game.runs>game.firstScore ? game.userBatting : !game.userBatting);
}
return true;
}
function finishMatch(userWon){
game.active=false; game.phase="ended";
career.matches++;
let title,text,xp,coins;
if(userWon===null){
title="Match Tied";
text=`Both teams scored ${game.runs}.`;
xp=20; coins=10;
}else if(userWon){
title="Victory!";
text=game.userBatting
? `Target chased with ${game.overs*6-game.balls} balls remaining.`
: `You defended the total by ${game.firstScore-game.runs} runs.`;
xp=50; coins=30; career.wins++;
if(game.mode==="tournament") career.trophies++;
}else{
title="Defeat";
text=game.userBatting
? `You fell ${game.target-game.runs} runs short.`
: "The opponent completed the chase.";
xp=15; coins=8;
}
career.xp+=xp; career.coins+=coins; saveCareer();
$("resultTitle").textContent=title;
$("resultText").textContent=text;
$("rewards").innerHTML=`<h3>+${xp} XP +${coins} 🪙</h3>`;
setTimeout(()=>show("result",true),700);
}
function updateHUD(){
const overs=`${Math.floor(game.balls/6)}.${game.balls%6}`;
$("score").textContent=`${game.runs}/${game.wickets}`;
$("teamName").textContent=game.userBatting?"CHAMPIONS":"RIVALS";
let detail=`Overs ${overs} / ${game.overs}`;
if(game.userBatting)
detail+=` · Batter ${game.batterRuns[game.striker]}* · Non-striker ${game.batterRuns[1-game.striker]}`;
else
detail+=` · Bowler ${game.bowlerWickets}/${game.bowlerRuns}`;
if(game.target){
const need=Math.max(0,game.target-game.runs);
const left=Math.max(0,game.overs*6-game.balls);
const rrr=left?((need/left)*6).toFixed(2):"∞";
detail+=` · Need ${need} from ${left} · RRR ${rrr}`;
}
$("detail").textContent=detail;
$("balls").innerHTML=game.overEvents.slice(-8)
.map(x=>`<span class="ballMark">${x}</span>`).join("");
$("matchInfo").innerHTML=`
<div>Innings: ${game.innings+1}</div>
<div>Mode: ${game.mode.toUpperCase()}</div>
${game.target?`<div>Target: ${game.target}</div>`:""}
<div>Weather: ${$("weatherSetting").value}</div>`;
}
/* ---------- Input ---------- */
document.querySelectorAll("[data-shot]").forEach(b=>b.onclick=()=>playShot(b.dataset.shot));
$("runBtn").onclick=()=>{
if(game.phase==="hit" || game.phase==="delivery"){
game.runIntent=true; say("Run called!");
}
};
addEventListener("keydown",e=>{
const keys={
"1":"defensive","2":"straight","3":"cover","4":"pull",
"5":"cut","6":"lofted","7":"sweep"
};
if(keys[e.key]) playShot(keys[e.key]);
if(e.key.toLowerCase()==="r") $("runBtn").click();
if(e.key==="Escape") paused?resumeGame():pauseGame();
});
/* Swipe batting */
let touchStart=null;
addEventListener("touchstart",e=>{
const t=e.changedTouches[0]; touchStart={x:t.clientX,y:t.clientY,time:Date.now()};
},{passive:true});
addEventListener("touchend",e=>{
if(!touchStart || !game.userBatting) return;
const t=e.changedTouches[0], dx=t.clientX-touchStart.x, dy=t.clientY-touchStart.y;
if(Math.hypot(dx,dy)<35) return;
if(dy<-50) playShot("lofted");
else if(dy>50) playShot("defensive");
else if(dx>0) playShot("pull");
else playShot("cover");
},{passive:true});
/* ---------- Audio ---------- */
let audioCtx;
function sound(type){
if(!$("soundSetting").checked) return;
audioCtx ||= new (window.AudioContext||window.webkitAudioContext)();
const o=audioCtx.createOscillator(), g=audioCtx.createGain();
o.connect(g); g.connect(audioCtx.destination);
const now=audioCtx.currentTime;
const freq={hit:170,bounce:90,cheer:440,wicket:115}[type]||120;
o.frequency.setValueAtTime(freq,now);
if(type==="cheer") o.frequency.exponentialRampToValueAtTime(760,now+.45);
g.gain.setValueAtTime(.12,now);
g.gain.exponentialRampToValueAtTime(.001,now+(type==="cheer"?.65:.18));
o.start(now); o.stop(now+(type==="cheer"?.7:.2));
}
/* ---------- Settings and menus ---------- */
function pauseGame(){
if(!game.active) return;
paused=true; show("pauseMenu",true);
}
function resumeGame(){
paused=false; show("pauseMenu",false);
if(game.userBatting && game.phase==="idle") aiDeliver();
}
function quitGame(){
game.active=false; paused=false;
show("pauseMenu",false); show("result",false); show("hud",false);
show("toss",false); show("menu",true);
}
function restartMatch(){
show("result",false);
startMode(game.mode);
}
$("timeSetting").onchange=applyEnvironment;
$("weatherSetting").onchange=applyEnvironment;
function applyEnvironment(){
const night=$("timeSetting").value==="night";
scene.background.set(night?0x030a18:0x79bced);
scene.fog.color.set(night?0x081126:0x8cc9ed);
scene.traverse(o=>{
if(o.userData.flood) o.intensity=night?35:0;
});
rain.forEach(r=>scene.remove(r)); rain=[];
if($("weatherSetting").value==="rain"){
for(let i=0;i<170;i++){
const drop=new THREE.Mesh(
new THREE.BoxGeometry(.018,.35,.018),
new THREE.MeshBasicMaterial({color:0xaadfff})
);
drop.position.set(rand(-40,40),rand(2,24),rand(-35,35));
scene.add(drop); rain.push(drop);
}
}
updateHUD();
}
function say(text){
$("message").textContent=text;
}
function toast(text){
$("toast").textContent=text; $("toast").style.opacity=1;
setTimeout(()=>$("toast").style.opacity=0,1500);
}
/* ---------- Animation loop ---------- */
function animate(now){
requestAnimationFrame(animate);
const dt=Math.min(.04,(now-lastTime)/1000||0); lastTime=now;
if(!paused){
crowd.forEach((c,i)=>c.position.y+=Math.sin(now*.004+i)*.0015);
rain.forEach(r=>{
r.position.y-=20*dt;
r.position.x-=3*dt;
if(r.position.y<0) r.position.y=24;
});
fielders.forEach((f,i)=>{
f.rotation.y=Math.sin(now*.001+i)*.12;
});
if(game.phase==="delivery" && game.delivery){
const d=game.delivery;
const t=clamp((now-d.start)/d.duration,0,1);
ball.position.z=-7+t*13.8;
ball.position.x=d.line+Math.sin(t*Math.PI)*d.swing;
ball.position.y=.25;
if(d.type==="Bouncer") ball.position.y=.3+Math.sin(t*Math.PI)*1.1;
if(d.type==="Spin") ball.rotation.y+=.25;
if(t>=1){
if(!game.shotUsed){
const wicketChance=d.type==="Yorker"?.28:d.type==="Swing"?.18:.12;
addBall(0,Math.random()<wicketChance,false,
Math.random()<wicketChance?"BOWLED!":"Dot ball");
}else if(game.phase==="delivery"){
addBall(0,false,false,"Dot ball");
}
}
}
}
renderer.render(scene,camera);
}
init3D();
applyEnvironment();
requestAnimationFrame(animate);
</script>
</body>
<Game Source: Cricket Champions
Creator: ShadowOwl70
Libraries: three
Complexity: complex (950 lines, 32.1 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: cricket-champions-shadowowl70" to link back to the original. Then publish at arcadelab.ai/publish.