Bharat Open World — Prototype
by FlashKoala74160 lines12.0 KB🛠️ Three.js (3D graphics)
from pathlib import Path
html = r'''<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bharat Open World — Prototype</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#101820;color:#fff;font-family:system-ui,Arial}
#hud{position:fixed;top:10px;left:10px;z-index:5;background:#07111ddd;padding:12px 14px;border:1px solid #52616b;border-radius:12px;max-width:min(390px,calc(100vw - 44px));font-size:13px;line-height:1.45}
#hud strong{font-size:16px;color:#ffd166} #msg{color:#9fe7ff}
#help{position:fixed;bottom:10px;left:10px;right:10px;text-align:center;z-index:5;background:#07111dbb;padding:8px;border-radius:10px;font-size:12px}
#cross{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);font-size:22px;color:#fff9;pointer-events:none}
button{background:#24465b;color:white;border:1px solid #6b8798;border-radius:7px;padding:5px 8px;margin:3px;cursor:pointer}
canvas{display:block}
</style>
</head>
<body>
<div id="hud">
<strong>BHARAT OPEN WORLD</strong> <span style="color:#9fe7ff">— playable 3D prototype</span><br>
Character: <b id="character">Michael</b> · Mode: <b id="mode">Walking</b><br>
Location: <b id="place">Mumbai-inspired city</b><br>
Health: <b id="health">100</b> · NPCs nearby: <b id="npcs">0</b><br>
<button id="switch">Switch character (C)</button><button id="vehicle">Enter/exit car (E)</button><button id="fire">Energy pulse (F)</button>
<div id="msg">Explore the city. Visit the Gateway landmark and India Gate landmark.</div>
</div>
<div id="cross">+</div>
<div id="help">WASD = move/drive · Mouse = look around · Click/tap = lock pointer · C = switch character · E = car · F = harmless sci-fi energy pulse · Esc = release mouse</div>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x91b7d1);
scene.fog = new THREE.Fog(0x91b7d1, 100, 430);
const camera = new THREE.PerspectiveCamera(72, innerWidth/innerHeight, 0.1, 900);
const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.7));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xd9efff,0x42513e,2.0));
const sun = new THREE.DirectionalLight(0xffefce,2.2); sun.position.set(-80,130,65); sun.castShadow=true; scene.add(sun);
sun.shadow.mapSize.set(1024,1024);
const mat=(color,roughness=0.85)=>new THREE.MeshStandardMaterial({color,roughness});
const ground = new THREE.Mesh(new THREE.PlaneGeometry(900,900),mat(0x6f8b62)); ground.rotation.x=-Math.PI/2; ground.receiveShadow=true; scene.add(ground);
const roadMat=mat(0x303941), lineMat=mat(0xf1d879), sidewalkMat=mat(0xbab8a8);
function box(w,h,d,m,x,y,z,cast=true){const o=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),m);o.position.set(x,y,z);o.castShadow=cast;o.receiveShadow=true;scene.add(o);return o;}
function road(x,z,w,d){box(w,.12,d,roadMat,x,.07,z,false); if(w>d){for(let k=-w/2+5;k<w/2;k+=10)box(3,.025,.18,lineMat,x+k,.14,z,false)}else{for(let k=-d/2+5;k<d/2;k+=10)box(.18,.025,3,lineMat,x,.14,z+k,false)}}
for(let i=-3;i<=3;i++){road(i*70,0,18,560);road(0,i*70,560,18)}
// city blocks with varied buildings
const buildingMats=[0xc6b9a5,0x9daeb5,0xd8d4c8,0xb7a08b,0x778b98,0xe0c9a3].map(c=>mat(c));
for(let gx=-3;gx<=3;gx++)for(let gz=-3;gz<=3;gz++){
if(Math.abs(gx)%1===0&&Math.abs(gz)%1===0){
for(let n=0;n<4;n++){
const x=gx*70+(n%2?18:-18), z=gz*70+(n<2?18:-18);
if(Math.abs(x)<20&&Math.abs(z)<20)continue;
const w=12+Math.random()*9,d=12+Math.random()*9,h=9+Math.random()*45;
const b=box(w,h,d,buildingMats[Math.floor(Math.random()*buildingMats.length)],x,h/2,z);
// windows on street-facing side
const win=mat(0x75b4cf,.3);
for(let yy=3;yy<h-2;yy+=4)for(let xx=-w/2+2;xx<w/2-1;xx+=3){
const pane=new THREE.Mesh(new THREE.BoxGeometry(1.2,1.5,.08),win);pane.position.set(x+xx,yy,z+d/2+.05);scene.add(pane);
}
}
}
}
// India-inspired landmarks, simplified original low-poly representations
function archLandmark(x,z, label, color){
const stone=mat(color), dark=mat(0x4a514e);
box(18,14,4,stone,x,7,z);
box(6,10,5,mat(0x263843),x,5.2,z+2.55,false);
box(22,2,5,stone,x,14,z);
box(2,16,5,stone,x-10,8,z);box(2,16,5,stone,x+10,8,z);
// decorative small top blocks
for(let xx=-8;xx<=8;xx+=4)box(1,2,4,dark,x+xx,15,z);
const canvas=document.createElement('canvas');canvas.width=512;canvas.height=128;
const ctx=canvas.getContext('2d');ctx.fillStyle='#17232c';ctx.fillRect(0,0,512,128);ctx.fillStyle='#ffffff';ctx.font='bold 42px sans-serif';ctx.textAlign='center';ctx.fillText(label,256,78);
const tex=new THREE.CanvasTexture(canvas);const sign=new THREE.Mesh(new THREE.PlaneGeometry(22,5),new THREE.MeshBasicMaterial({map:tex}));sign.position.set(x,20,z+3);scene.add(sign);
}
archLandmark(-35,-110,'GATEWAY OF INDIA',0xcab18a);
archLandmark(105,110,'INDIA GATE',0xcdbb99);
// Taj Mahal-inspired dome
const white=mat(0xf1eee2), marble=mat(0xd8d7d0);
box(24,13,18,white,130,-4,-130);
for(const x of [120,140])for(const z of [-137,-123]){
const tower=new THREE.Mesh(new THREE.CylinderGeometry(1.5,2,19,10),white);tower.position.set(x,9,z);tower.castShadow=true;scene.add(tower);
const cap=new THREE.Mesh(new THREE.SphereGeometry(2.2,12,8),marble);cap.scale.y=1.3;cap.position.set(x,19,z);scene.add(cap);
}
const dome=new THREE.Mesh(new THREE.SphereGeometry(9,20,14,0,Math.PI*2,0,Math.PI/2),white);dome.scale.y=1.2;dome.position.set(130,13,-130);scene.add(dome);
box(27,1,21,marble,130,.5,-130);
// player capsule
const player=new THREE.Group();scene.add(player);
const body=new THREE.Mesh(new THREE.CapsuleGeometry(.65,1.1,4,8),mat(0x253b56));body.position.y=1.3;body.castShadow=true;player.add(body);
const head=new THREE.Mesh(new THREE.SphereGeometry(.42,14,10),mat(0xd7a783));head.position.y=2.35;head.castShadow=true;player.add(head);
const jacket=new THREE.Mesh(new THREE.BoxGeometry(1.05,.28,.72),mat(0x101820));jacket.position.set(0,1.45,0);player.add(jacket);
player.position.set(0,0,8);
const car=new THREE.Group();scene.add(car);
const carBody=new THREE.Mesh(new THREE.BoxGeometry(2.5,.8,4.5),mat(0xc33f37));carBody.position.y=.8;carBody.castShadow=true;car.add(carBody);
const cabin=new THREE.Mesh(new THREE.BoxGeometry(1.8,.8,2),mat(0x72a6ba,.3));cabin.position.set(0,1.55,-.15);cabin.castShadow=true;car.add(cabin);
for(const x of [-1.25,1.25])for(const z of [-1.45,1.45]){const wh=new THREE.Mesh(new THREE.CylinderGeometry(.42,.42,.25,12),mat(0x17191c));wh.rotation.z=Math.PI/2;wh.position.set(x,.45,z);car.add(wh)}
car.position.set(5,0,8);car.visible=true;
let driving=false, charIndex=0, yaw=0, pitch=-.12, keys={}, pointer=false;
const chars=[{name:'Michael',color:0x253b56},{name:'Trevor',color:0x63513e},{name:'Franklin',color:0x245b48}];
function setCharacter(){document.querySelector('#character').textContent=chars[charIndex].name;body.material.color.setHex(chars[charIndex].color);document.querySelector('#msg').textContent=chars[charIndex].name+' selected. This is an original placeholder model, not the official GTA character model.'}
document.querySelector('#switch').onclick=()=>{charIndex=(charIndex+1)%3;setCharacter()};
document.querySelector('#vehicle').onclick=()=>{driving=!driving;if(driving)car.position.copy(player.position);document.querySelector('#mode').textContent=driving?'Driving':'Walking';document.querySelector('#msg').textContent=driving?'You are driving the red car.':'You are on foot.'};
const pulses=[];
function fire(){const dir=new THREE.Vector3(Math.sin(yaw),0,-Math.cos(yaw));const p=new THREE.Mesh(new THREE.SphereGeometry(.22,8,8),new THREE.MeshBasicMaterial({color:0x69f0ff}));p.position.copy(driving?car.position:player.position).add(new THREE.Vector3(0,1.5,0)).add(dir.clone().multiplyScalar(2));scene.add(p);pulses.push({mesh:p,dir,life:100});}
document.querySelector('#fire').onclick=fire;
addEventListener('keydown',e=>{keys[e.key.toLowerCase()]=true;if(e.repeat)return;if(e.key.toLowerCase()==='c'){charIndex=(charIndex+1)%3;setCharacter()}if(e.key.toLowerCase()==='e'){driving=!driving;if(driving)car.position.copy(player.position);document.querySelector('#mode').textContent=driving?'Driving':'Walking'}if(e.key.toLowerCase()==='f')fire()});
addEventListener('keyup',e=>keys[e.key.toLowerCase()]=false);
renderer.domElement.addEventListener('click',()=>{if(!pointer)renderer.domElement.requestPointerLock?.()});
document.addEventListener('pointerlockchange',()=>pointer=document.pointerLockElement===renderer.domElement);
document.addEventListener('mousemove',e=>{if(pointer){yaw-=e.movementX*.002;pitch-=e.movementY*.002;pitch=Math.max(-.8,Math.min(.45,pitch))}});
let touchStart=null;
renderer.domElement.addEventListener('touchstart',e=>{if(e.touches.length===1)touchStart={x:e.touches[0].clientX,y:e.touches[0].clientY}});
renderer.domElement.addEventListener('touchmove',e=>{if(touchStart&&e.touches.length===1){yaw-=(e.touches[0].clientX-touchStart.x)*.004;pitch-=(e.touches[0].clientY-touchStart.y)*.003;pitch=Math.max(-.8,Math.min(.45,pitch));touchStart={x:e.touches[0].clientX,y:e.touches[0].clientY}}},{passive:true});
renderer.domElement.addEventListener('touchend',()=>touchStart=null);
const npcs=[];
for(let i=0;i<45;i++){
const g=new THREE.Group();const cloth=mat([0x7b4b3a,0x2f5f65,0x7c7439,0x51476d,0x9a9a91][i%5]);
const torso=new THREE.Mesh(new THREE.CapsuleGeometry(.38,.65,3,6),cloth);torso.position.y=1;g.add(torso);
const h=new THREE.Mesh(new THREE.SphereGeometry(.27,8,6),mat(0xb98261));h.position.y=1.8;g.add(h);
g.position.set((Math.random()-.5)*360,0,(Math.random()-.5)*360);g.userData={phase:Math.random()*6,origin:g.position.clone()};scene.add(g);npcs.push(g);
}
document.querySelector('#npcs').textContent=npcs.length;
const clock=new THREE.Clock();
function animate(){
requestAnimationFrame(animate);const dt=Math.min(clock.getDelta(),.04);
const forward=new THREE.Vector3(Math.sin(yaw),0,-Math.cos(yaw));const right=new THREE.Vector3(Math.cos(yaw),0,Math.sin(yaw));
const mover=driving?car:player;let speed=driving?22:8;
let move=new THREE.Vector3();if(keys.w||keys.arrowup)move.add(forward);if(keys.s||keys.arrowdown)move.sub(forward);if(keys.a||keys.arrowleft)move.sub(right);if(keys.d||keys.arrowright)move.add(right);
if(move.lengthSq()>0){move.normalize().multiplyScalar(speed*dt);mover.position.add(move);if(driving){car.rotation.y=yaw}else player.rotation.y=yaw}
mover.position.x=THREE.MathUtils.clamp(mover.position.x,-260,260);mover.position.z=THREE.MathUtils.clamp(mover.position.z,-260,260);
if(driving)player.position.copy(car.position);
npcs.forEach((n,i)=>{n.userData.phase+=dt*(.3+i%3*.12);n.position.x=n.userData.origin.x+Math.sin(n.userData.phase)*2;n.position.z=n.userData.origin.z+Math.cos(n.userData.phase*.7)*2;n.rotation.y=Math.sin(n.userData.phase)*.4});
for(let i=pulses.length-1;i>=0;i--){const p=pulses[i];p.mesh.position.add(p.dir.clone().multiplyScalar(35*dt));p.life--;if(p.life<=0){scene.remove(p.mesh);pulses.splice(i,1)}}
const target=mover.position.clone().add(new THREE.Vector3(0,driving?1.6:1.7,0));
const camDist=driving?10:7;const desired=target.clone().add(new THREE.Vector3(Math.sin(yaw)*camDist*Math.cos(pitch),2.8-Math.sin(pitch)*camDist,Math.cos(yaw)*camDist*Math.cos(pitch)));
camera.position.lerp(desired,1-Math.exp(-5*dt));camera.lookAt(target);
let place='Mumbai-inspired city';const p=mover.position;
if(Math.hypot(p.x+35,p.z+110)<35)place='Gateway of India district';
else if(Math.hypot(p.x-105,p.z-110)<35)place='India Gate district';
else if(Math.hypot(p.x-130,p.z+130)<40)place='Taj Mahal district';
document.querySelector('#place').textContent=place;
renderer.render(scene,camera);
}
addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight)});
animate();
</script>
</body>
</html>'''
path = Path("/mnt/data/bharat_open_world_prototype.html")
path.write_text(html, encoding="utf-8")
print(f"Created {path} ({path.stat().st_size} bytes)")
Game Source: Bharat Open World — Prototype
Creator: FlashKoala74
Libraries: three
Complexity: moderate (160 lines, 12.0 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: bharat-open-world-prototype-flashkoala74" to link back to the original. Then publish at arcadelab.ai/publish.