SC CODE: // Copyright 2024. Civilware. All rights reserved.
// TELA Decentralized Web Document (TELA-DOC-1)
Function InitializePrivate() Uint64
10 IF init() == 0 THEN GOTO 30
20 RETURN 1
30 STORE("nameHdr", "game.js")
31 STORE("descrHdr", "JS file")
32 STORE("iconURLHdr", "")
33 STORE("dURL", "game.js")
34 STORE("docType", "TELA-JS-1")
35 STORE("subDir", "")
36 STORE("fileCheckC", "237c618d0515738fdde0738a09db857038bbeffbea11fb24570fd150b802270b")
37 STORE("fileCheckS", "1c0b9511c358438b5b8c4a424424009288737a668220ae1c7465f4515d6d3cd2")
100 RETURN 0
End Function
Function init() Uint64
10 IF EXISTS("owner") == 0 THEN GOTO 30
20 RETURN 1
30 STORE("owner", address())
50 STORE("docVersion", "1.0.0")
60 STORE("hash", HEX(TXID()))
70 STORE("likes", 0)
80 STORE("dislikes", 0)
100 RETURN 0
End Function
Function address() String
10 DIM s as String
20 LET s = SIGNER()
30 IF IS_ADDRESS_VALID(s) THEN GOTO 50
40 RETURN "anon"
50 RETURN ADDRESS_STRING(s)
End Function
Function Rate(r Uint64) Uint64
10 DIM addr as String
15 LET addr = address()
16 IF r < 100 && EXISTS(addr) == 0 && addr != "anon" THEN GOTO 30
20 RETURN 1
30 STORE(addr, ""+r+"_"+BLOCK_HEIGHT())
40 IF r < 50 THEN GOTO 70
50 STORE("likes", LOAD("likes")+1)
60 RETURN 0
70 STORE("dislikes", LOAD("dislikes")+1)
100 RETURN 0
End Function
/*const svg=document.getElementById('game')
const shipNode=document.getElementById('shipBody')
const flameNode=document.getElementById("shipFlame")
const asteroidsNode=document.getElementById('asteroids')
const bulletsNode=document.getElementById('bullets')
const particlesNode=document.getElementById("particles")
const WIDTH=800
const HEIGHT=600
const SAFE_RADIUS=150
const MAX_HEALTH=3
const LIVES=3
function setZoom(scale){
const g=document.getElementById("gameScale")
g.setAttribute("transform", `scale(${scale})`)
}
function autoScale(){
const baseW=800
const baseH=600
const screenW=window.innerWidth
const screenH=window.innerHeight
const scale=Math.min(
screenW / baseW,
screenH / baseH
)
const steps = [0.5,0.75,1.0,1.25,1.5,2.0,3.0]
let best = steps[0]
for(const s of steps){
if(s <= scale) best = s;
}
let w=(baseW * best)
let h=(baseH * best)
svg.setAttribute("width",w)
svg.setAttribute("height",h)
svg.setAttribute("viewBox","0 0 "+w+" "+h)
setZoom(best)
}
//matrix multiply
function matIdentity(){
return [1, 0, 0, 1, 0, 0];
}
function matTranslate(tx, ty){
return [1, 0, 0, 1, tx, ty]
}
function matRotate(angle){
const c=Math.cos(angle)
const s=Math.sin(angle)
return [c,s,-s,c,0,0]
}
function matScale(sx,sy) {
return [sx,0,0,sy,0,0]
}
function matMul(a,b){
return [
a[0] * b[0] + a[2] * b[1],
a[1] * b[0] + a[3] * b[1],
a[0] * b[2] + a[2] * b[3],
a[1] * b[2] + a[3] * b[3],
a[0] * b[4] + a[2] * b[5] + a[4],
a[1] * b[4] + a[3] * b[5] + a[5]
];
}
function matApply(m,p){
return {
x: m[0] * p.x + m[2] * p.y + m[4],
y: m[1] * p.x + m[3] * p.y + m[5]
};
}
var thrustPower = 0
const shipShape = [
{ x: 15, y: 0 },// nose
{ x: -10, y: 10 },
{ x: -10, y: -10 }
];
const state={
ship:{
x:400,
y:300,
angle:0,
vx:0,
vy:0,
health:MAX_HEALTH,
invuln:0,
shape:shipShape,
hitFlash:0
},
mode:"title",
lives:LIVES,
level:0,
keys:{},
asteroids:[],
bullets:[],
particles:[],
shards:[]
}
function applyThrust(){
const thrust=0.15
state.ship.vx += Math.cos(state.ship.angle) * thrust
state.ship.vy += Math.sin(state.ship.angle) * thrust
thrustPower = thrust
}
//input
window.addEventListener("keydown",e =>{
if(e.code==="Space"){
shoot()
}
})
window.addEventListener("keydown",e =>{
if (e.code==="Enter") {
startPressed()
}
})
window.addEventListener('keydown',e=>state.keys[e.code]=true)
window.addEventListener('keyup',e =>state.keys[e.code]=false)
function startPressed(){
if(state.mode!=="playing"&&state.mode!== "paused")startGame()
else if(state.mode!=="paused"){state.mode = "paused";hudGameOver.textContent="PAUSED"}
else{state.mode="playing";hudGameOver.textContent=""}
}
function handleInput(){
const turnSpeed = 0.06
if(state.keys['ArrowLeft'])state.ship.angle-=turnSpeed
if(state.keys['ArrowRight'])state.ship.angle+=turnSpeed
if(state.keys['ArrowUp']){
applyThrust()
}
}
function pollGamepad(){
const gp=navigator.getGamepads()[0]
if(!gp)return;
// D-pad or left stick for rotation + thrust
const left = gp.buttons[14].pressed || gp.axes[0] < -0.4;
const right = gp.buttons[15].pressed || gp.axes[0] > 0.4;
const up = gp.buttons[12].pressed || gp.axes[1] < -0.4;
const rt = gp.buttons[7].value; // 0 to 1
// Buttons
const shootBtn = gp.buttons[0].pressed;
// Start button detection
const startBtn = gp.buttons[9].pressed;
const lx = Math.abs(gp.axes[0]) > 0.3 ? gp.axes[0] : 0;
const ly = Math.abs(gp.axes[1]) > 0.3 ? gp.axes[1] : 0;
if(!gp.buttons[14].pressed && !gp.buttons[15].pressed && !gp.buttons[12].pressed){
state.ship.angle += lx * 0.08 // scale
if(rt > 0.1){
const power = rt * 0.2
state.ship.vx += Math.cos(state.ship.angle) * power
state.ship.vy += Math.sin(state.ship.angle) * power
thrustPower = power;
}else{
const power = (-ly) * 0.15; // scale
state.ship.vx += Math.cos(state.ship.angle) * power
state.ship.vy += Math.sin(state.ship.angle) * power
thrustPower = power;
}
}else{
// Rotate
if(left)state.ship.angle -= 0.08
if(right)state.ship.angle += 0.08
// Thrust
if(up) applyThrust();
}
// Shoot
if(shootBtn && !state.shootHeld){
shoot();
}
state.shootHeld = shootBtn
// Start game
if(startBtn && !state.startHeld){
startPressed()
}
state.startHeld = startBtn
}
//Rendering
function wrap(a){
if(a.x < 0) a.x += WIDTH
if(a.x > WIDTH) a.x -= WIDTH
if(a.y < 0) a.y += HEIGHT
if(a.y > HEIGHT) a.y -= HEIGHT
}
function renderShip(){
const {x,y,angle}=state.ship
let M=matIdentity()
M=matMul(M,matTranslate(x,y))
M=matMul(M,matRotate(angle))
const worldPoints=shipShape.map(p =>matApply(M,p))
shipNode.setAttribute(
"points",
worldPoints.map(p => `${p.x},${p.y}`).join(" ")
);
const flameLength=20
const flameWidth=8
const backX=state.ship.x-Math.cos(state.ship.angle)*10
const backY=state.ship.y-Math.sin(state.ship.angle)*10
const leftX = backX - Math.cos(state.ship.angle) * flameLength + Math.sin(state.ship.angle) * flameWidth
const leftY = backY - Math.sin(state.ship.angle) * flameLength - Math.cos(state.ship.angle) * flameWidth
const rightX = backX - Math.cos(state.ship.angle) * flameLength - Math.sin(state.ship.angle) * flameWidth
const rightY = backY - Math.sin(state.ship.angle) * flameLength + Math.cos(state.ship.angle) * flameWidth
flameNode.setAttribute(
"points",
`${backX},${backY} ${leftX},${leftY} ${rightX},${rightY}`
);
if(thrustPower > 0){
const flicker = 0.7 + Math.random() * 0.5
flameNode.setAttribute("opacity", thrustPower * flicker)
thrustPower = 0
}else{
flameNode.setAttribute("opacity", 0)
}
}
function updateShip(){
state.ship.x += state.ship.vx
state.ship.y += state.ship.vy
// simple friction
state.ship.vx *= 0.99
state.ship.vy *= 0.99
if(state.ship.invuln > 0){
state.ship.invuln--
}
if(state.ship.hitFlash > 0){
shipNode.setAttribute("fill","white")
state.ship.hitFlash--
}else{
shipNode.setAttribute("fill","none")
}
wrap(state.ship)
}
function shoot(){
if(state.bullets.length >= 4) return;
const bulletSpeed = 6;
const {x,y,vx,vy,angle}=state.ship
// direction vector of the ship
const dx=Math.cos(angle)
const dy=Math.sin(angle)
const bullet={
x,
y,
vx: vx + dx * bulletSpeed,
vy: vy + dy * bulletSpeed,
life: 60,
node: null
}
const node=document.createElementNS('http://www.w3.org/2000/svg','circle')
node.setAttribute('r', 2)
node.setAttribute('fill', 'white')
bulletsNode.appendChild(node)
bullet.node = node
state.bullets.push(bullet)
}
function updateBullets(){
state.bullets=state.bullets.filter(b=>{
b.x += b.vx
b.y += b.vy
wrap(b)
b.life--
if(b.life <= 0){
bulletsNode.removeChild(b.node)
return false// remove from array
}
return true// keep bullet
})
}
function renderBullets(){
for(const b of state.bullets){
b.node.setAttribute('cx',b.x)
b.node.setAttribute('cy',b.y)
}
}
function makeAsteroidShape(radius,jaggedness=0.4,points=10){
const shape=[]
for(let i = 0; i < points; i++){
const angle = (i / points) * Math.PI * 2
const r = radius * (1 - jaggedness + Math.random() * jaggedness * 2)
shape.push({x: Math.cos(angle) * r, y: Math.sin(angle) * r})
}
return shape
}
function spawnAsteroidAwayFromShip(radius){
let x,y;
while (true){
x = Math.random() * WIDTH
y = Math.random() * HEIGHT
const dx = x - state.ship.x
const dy = y - state.ship.y
const dist2 = dx*dx + dy*dy
if(dist2 > SAFE_RADIUS * SAFE_RADIUS){
break; // good spawn
}
}
spawnAsteroid(x,y,radius)
}
function renderAsteroidAt(ast,ox,oy){
let M = matIdentity()
M = matMul(M, matTranslate(ast.x + ox, ast.y + oy))
M = matMul(M, matRotate(ast.angle))
const pts = ast.shape.map(p => matApply(M, p))
const str = pts.map(p => `${p.x},${p.y}`).join(" ")
// choose main or shadow node
const node = (ox === 0 && oy === 0) ? ast.main : ast.shadow
node.setAttribute("points", str)
}
function renderAsteroidAtNode(ast,ox,oy,node) {
let M=matIdentity()
M=matMul(M,matTranslate(ast.x + ox, ast.y + oy))
M=matMul(M,matRotate(ast.angle))
const pts=ast.shape.map(p=>matApply(M,p))
node.setAttribute("points",pts.map(p=>`${p.x},${p.y}`).join(" "))
}
function spawnAsteroid(x,y,radius){
const shape = makeAsteroidShape(radius)
// group for asteroid + shadow
const group = document.createElementNS("http://www.w3.org/2000/svg","g")
// main polygon
const polyMain = document.createElementNS("http://www.w3.org/2000/svg","polygon")
polyMain.setAttribute("fill", "none")
polyMain.setAttribute("stroke", "white")
polyMain.setAttribute("stroke-width", "2")
group.appendChild(polyMain)
// shadow polygons (up to 3)
const shadows = []
for(let i = 0; i < 3; i++){
const poly = document.createElementNS("http://www.w3.org/2000/svg","polygon")
poly.setAttribute("fill","none")
poly.setAttribute("stroke","white")
poly.setAttribute("stroke-width","2")
group.appendChild(poly)
shadows.push(poly)
}
group.setAttribute("filter","url(#glow)")
asteroidsNode.appendChild(group)
const asteroid={
x, y,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
radius,
angle: Math.random() * Math.PI * 2,
spin: (Math.random() - 0.5) * 0.02,
shape,
main: polyMain,
shadows:shadows,
group
}
state.asteroids.push(asteroid)
}
function renderAsteroid(ast){
// MAIN asteroid
renderAsteroidAt(ast,0,0)
// SHADOW asteroids
const offsets=[]
if (ast.x - ast.radius < 0) offsets.push({ ox: WIDTH, oy: 0 })
if (ast.x + ast.radius > WIDTH) offsets.push({ ox: -WIDTH, oy: 0 })
if (ast.y - ast.radius < 0) offsets.push({ ox: 0, oy: HEIGHT })
if (ast.y + ast.radius > HEIGHT) offsets.push({ ox: 0, oy: -HEIGHT })
// diagonal copies
if (offsets.length === 2) {
offsets.push({
ox: offsets[0].ox,
oy: offsets[1].oy
});
}
// shadows
for(let i = 0; i < offsets.length; i++){
const off = offsets[i]
const node = ast.shadows[i]
renderAsteroidAtNode(ast, off.ox, off.oy, node)
}
// hide unused shadow nodes
for(let i = offsets.length; i < ast.shadows.length; i++){
ast.shadows[i].setAttribute("points","")
}
}
function getWrapOffset(x, y, radius){
let ox = 0
let oy = 0
if (x - radius < 0) ox = WIDTH
else if (x + radius > WIDTH) ox = -WIDTH
if (y - radius < 0) oy = HEIGHT
else if (y + radius > HEIGHT) oy = -HEIGHT
return { ox, oy }
}
function updateAsteroids(){
if(state.asteroids.length === 0){
newLevel()
}
for(const a of state.asteroids){
a.x += a.vx
a.y += a.vy
a.angle += a.spin
renderAsteroid(a)
wrap(a)
}
}
// Collision detection
function pointInPoly(pt, poly) {
let inside=false;
for(let i = 0, j = poly.length - 1; i < poly.length; j = i++){
const xi = poly[i].x, yi = poly[i].y
const xj = poly[j].x, yj = poly[j].y
const intersect =
((yi > pt.y) !== (yj > pt.y)) &&
(pt.x < (xj - xi) * (pt.y - yi) / (yj - yi) + xi)
if (intersect) inside = !inside
}
return inside
}
function collision(a, b){
// build shapeA matrix
let Ma=matIdentity()
Ma=matMul(Ma,matTranslate(a.x, a.y))
Ma=matMul(Ma,matRotate(a.angle))
// build shapeB matrix
let Mb=matIdentity()
Mb=matMul(Mb,matTranslate(b.x, b.y))
Mb=matMul(Mb,matRotate(b.angle))
const worldA=a.shape.map(p=>matApply(Ma,p))
const worldB=b.shape.map(p=>matApply(Mb,p))
// ship points inside asteroid
for(const p of worldA){
if(pointInPoly(p, worldB)) return true;
}
// asteroid points inside ship
for(const p of worldB){
if (pointInPoly(p, worldA)) return true;
}
return false
}
function handleCollisions(){
// bullets vs asteroids
for(const b of state.bullets){
for(const a of state.asteroids){
let Ma = matIdentity()
Ma = matMul(Ma, matTranslate(a.x, a.y))
Ma = matMul(Ma, matRotate(a.angle))
const asteroidWorld = a.shape.map(p => matApply(Ma, p))
if(pointInPoly(b, asteroidWorld)){
// remove bullet
bulletsNode.removeChild(b.node)
state.bullets.splice(state.bullets.indexOf(b), 1)
// split asteroid or remove
asteroidsNode.removeChild(a.group);
state.asteroids.splice(state.asteroids.indexOf(a), 1)
if(a.radius > 20){
spawnAsteroid(a.x, a.y, a.radius / 2)
spawnAsteroid(a.x, a.y, a.radius / 2)
}
//effect
let M=matIdentity()
M=matMul(M,matTranslate(a.x, a.y))
M=matMul(M,matRotate(a.angle))
const world = a.shape.map(p=>matApply(M,p))
for(let i = 0; i < world.length; i++){
const p1 = world[i]
const p2 = world[(i + 1) % world.length]
spawnShard(p1, p2, a)
}
//done
break
}
}
}
// ship vs asteroids
for(const a of state.asteroids){
if(collision(state.ship,a)){
if(state.ship.invuln === 0){
state.ship.health--
state.ship.invuln = 120
state.ship.hitFlash = 10
if(state.ship.health <= 0){
killShip()
}
}
}
}
}
function resetShip(){
state.ship.health = MAX_HEALTH
state.ship.x = WIDTH / 2
state.ship.y = HEIGHT / 2
state.ship.vx = 0
state.ship.vy = 0
state.ship.angle = 0
state.ship.invuln = 180
}
function killShip(){
explodeShip(state.ship.x, state.ship.y)
state.lives--
if(state.lives <= 0){
state.mode = "gameover"
return
}
resetShip()
}
function updateParticles(){
//ship
for(let i = state.particles.length - 1; i >= 0; i--){
const p = state.particles[i]
p.x += p.vx
p.y += p.vy
p.life--
p.node.setAttribute("x1", p.x)
p.node.setAttribute("y1", p.y)
p.node.setAttribute("x2", p.x + p.vx * 2)
p.node.setAttribute("y2", p.y + p.vy * 2)
if(p.life <= 0){
p.node.remove()
state.particles.splice(i, 1)
}
}
//rocks
state.shards = state.shards.filter(s=>{
s.x1 += s.vx
s.y1 += s.vy
s.x2 += s.vx
s.y2 += s.vy
s.life--
s.node.setAttribute("x1", s.x1)
s.node.setAttribute("y1", s.y1)
s.node.setAttribute("x2", s.x2)
s.node.setAttribute("y2", s.y2)
if(s.life <= 0){
s.node.remove()
return false
}
return true
});
}
function createParticleNode(){
const p = document.createElementNS("http://www.w3.org/2000/svg","line")
p.setAttribute("stroke","orange")
p.setAttribute("stroke-width","2")
particlesNode.appendChild(p)
return p
}
function explodeShip(x, y){
for (let i = 0; i < 6; i++){
state.particles.push({
x,
y,
vx: (Math.random() - 0.5) * 6,
vy: (Math.random() - 0.5) * 6,
life: 40,
node: createParticleNode()
});
}
}
function spawnShard(p1, p2, asteroid){
const node = document.createElementNS("http://www.w3.org/2000/svg","line")
node.setAttribute("x1", p1.x)
node.setAttribute("y1", p1.y)
node.setAttribute("x2", p2.x)
node.setAttribute("y2", p2.y)
node.setAttribute("stroke","white")
node.setAttribute("stroke-width","1")
particlesNode.appendChild(node)
const angle = Math.atan2(p1.y - asteroid.y, p1.x - asteroid.x)
const speed = 2 + Math.random() * 2
state.shards.push({
x1: p1.x, y1: p1.y,
x2: p2.x, y2: p2.y,
vx: Math.cos(angle) * speed + asteroid.vx,
vy: Math.sin(angle) * speed + asteroid.vy,
life: 30 + Math.random() * 16,
node
});
}
function newLevel(){
if(state.ship.health < MAX_HEALTH){
state.ship.health++
}
state.level++
startLevel(state.level)
}
function startLevel(level){
const asteroidCount = 4 + level
for(let i = 0; i < asteroidCount; i++){
spawnAsteroidAwayFromShip(40 + Math.random() * 30)
}
}
function updateHUD(){
hudHealth.textContent = "Health: " + state.ship.health
hudLives.textContent = "Lives: " + state.lives
hudLevel.textContent = "Level: " + state.level
if(state.mode == "gameover"){
hudGameOver.textContent="GAME OVER"
hudGameOverExtra.textContent="Press Enter or Start"
} else {
hudGameOver.textContent=""
hudGameOverExtra.textContent=""
}
}
function resetGame(){
while(asteroidsNode.firstChild){
asteroidsNode.removeChild(asteroidsNode.firstChild)
}
state.asteroids.length=0
while(bulletsNode.firstChild){
bulletsNode.removeChild(bulletsNode.firstChild)
}
state.bullets.length=0
state.level=1
state.lives=LIVES
resetShip()
startLevel(state.level)
}
function startGame(){
resetGame()
state.mode="playing"
titleText.textContent=""
}
// Game loop
function loop(){
pollGamepad()
handleInput()
if(state.mode !== "playing"){
requestAnimationFrame(loop)
return
}
updateShip()
updateBullets()
updateAsteroids()
handleCollisions()
updateParticles()
renderShip()
renderBullets()
// asteroids already rendered in updateAsteroids
updateHUD()
requestAnimationFrame(loop)
}
// initial setup
autoScale()
loop()
*/ |
| SC Arguments: [Name:SC_ACTION Type:uint64 Value:'1' Name:SC_CODE Type:string Value:'// Copyright 2024. Civilware. All rights reserved.
// TELA Decentralized Web Document (TELA-DOC-1)
Function InitializePrivate() Uint64
10 IF init() == 0 THEN GOTO 30
20 RETURN 1
30 STORE("nameHdr", "game.js")
31 STORE("descrHdr", "JS file")
32 STORE("iconURLHdr", "")
33 STORE("dURL", "game.js")
34 STORE("docType", "TELA-JS-1")
35 STORE("subDir", "")
36 STORE("fileCheckC", "237c618d0515738fdde0738a09db857038bbeffbea11fb24570fd150b802270b")
37 STORE("fileCheckS", "1c0b9511c358438b5b8c4a424424009288737a668220ae1c7465f4515d6d3cd2")
100 RETURN 0
End Function
Function init() Uint64
10 IF EXISTS("owner") == 0 THEN GOTO 30
20 RETURN 1
30 STORE("owner", address())
50 STORE("docVersion", "1.0.0")
60 STORE("hash", HEX(TXID()))
70 STORE("likes", 0)
80 STORE("dislikes", 0)
100 RETURN 0
End Function
Function address() String
10 DIM s as String
20 LET s = SIGNER()
30 IF IS_ADDRESS_VALID(s) THEN GOTO 50
40 RETURN "anon"
50 RETURN ADDRESS_STRING(s)
End Function
Function Rate(r Uint64) Uint64
10 DIM addr as String
15 LET addr = address()
16 IF r < 100 && EXISTS(addr) == 0 && addr != "anon" THEN GOTO 30
20 RETURN 1
30 STORE(addr, ""+r+"_"+BLOCK_HEIGHT())
40 IF r < 50 THEN GOTO 70
50 STORE("likes", LOAD("likes")+1)
60 RETURN 0
70 STORE("dislikes", LOAD("dislikes")+1)
100 RETURN 0
End Function
/*const svg=document.getElementById('game')
const shipNode=document.getElementById('shipBody')
const flameNode=document.getElementById("shipFlame")
const asteroidsNode=document.getElementById('asteroids')
const bulletsNode=document.getElementById('bullets')
const particlesNode=document.getElementById("particles")
const WIDTH=800
const HEIGHT=600
const SAFE_RADIUS=150
const MAX_HEALTH=3
const LIVES=3
function setZoom(scale){
const g=document.getElementById("gameScale")
g.setAttribute("transform", `scale(${scale})`)
}
function autoScale(){
const baseW=800
const baseH=600
const screenW=window.innerWidth
const screenH=window.innerHeight
const scale=Math.min(
screenW / baseW,
screenH / baseH
)
const steps = [0.5,0.75,1.0,1.25,1.5,2.0,3.0]
let best = steps[0]
for(const s of steps){
if(s <= scale) best = s;
}
let w=(baseW * best)
let h=(baseH * best)
svg.setAttribute("width",w)
svg.setAttribute("height",h)
svg.setAttribute("viewBox","0 0 "+w+" "+h)
setZoom(best)
}
//matrix multiply
function matIdentity(){
return [1, 0, 0, 1, 0, 0];
}
function matTranslate(tx, ty){
return [1, 0, 0, 1, tx, ty]
}
function matRotate(angle){
const c=Math.cos(angle)
const s=Math.sin(angle)
return [c,s,-s,c,0,0]
}
function matScale(sx,sy) {
return [sx,0,0,sy,0,0]
}
function matMul(a,b){
return [
a[0] * b[0] + a[2] * b[1],
a[1] * b[0] + a[3] * b[1],
a[0] * b[2] + a[2] * b[3],
a[1] * b[2] + a[3] * b[3],
a[0] * b[4] + a[2] * b[5] + a[4],
a[1] * b[4] + a[3] * b[5] + a[5]
];
}
function matApply(m,p){
return {
x: m[0] * p.x + m[2] * p.y + m[4],
y: m[1] * p.x + m[3] * p.y + m[5]
};
}
var thrustPower = 0
const shipShape = [
{ x: 15, y: 0 },// nose
{ x: -10, y: 10 },
{ x: -10, y: -10 }
];
const state={
ship:{
x:400,
y:300,
angle:0,
vx:0,
vy:0,
health:MAX_HEALTH,
invuln:0,
shape:shipShape,
hitFlash:0
},
mode:"title",
lives:LIVES,
level:0,
keys:{},
asteroids:[],
bullets:[],
particles:[],
shards:[]
}
function applyThrust(){
const thrust=0.15
state.ship.vx += Math.cos(state.ship.angle) * thrust
state.ship.vy += Math.sin(state.ship.angle) * thrust
thrustPower = thrust
}
//input
window.addEventListener("keydown",e =>{
if(e.code==="Space"){
shoot()
}
})
window.addEventListener("keydown",e =>{
if (e.code==="Enter") {
startPressed()
}
})
window.addEventListener('keydown',e=>state.keys[e.code]=true)
window.addEventListener('keyup',e =>state.keys[e.code]=false)
function startPressed(){
if(state.mode!=="playing"&&state.mode!== "paused")startGame()
else if(state.mode!=="paused"){state.mode = "paused";hudGameOver.textContent="PAUSED"}
else{state.mode="playing";hudGameOver.textContent=""}
}
function handleInput(){
const turnSpeed = 0.06
if(state.keys['ArrowLeft'])state.ship.angle-=turnSpeed
if(state.keys['ArrowRight'])state.ship.angle+=turnSpeed
if(state.keys['ArrowUp']){
applyThrust()
}
}
function pollGamepad(){
const gp=navigator.getGamepads()[0]
if(!gp)return;
// D-pad or left stick for rotation + thrust
const left = gp.buttons[14].pressed || gp.axes[0] < -0.4;
const right = gp.buttons[15].pressed || gp.axes[0] > 0.4;
const up = gp.buttons[12].pressed || gp.axes[1] < -0.4;
const rt = gp.buttons[7].value; // 0 to 1
// Buttons
const shootBtn = gp.buttons[0].pressed;
// Start button detection
const startBtn = gp.buttons[9].pressed;
const lx = Math.abs(gp.axes[0]) > 0.3 ? gp.axes[0] : 0;
const ly = Math.abs(gp.axes[1]) > 0.3 ? gp.axes[1] : 0;
if(!gp.buttons[14].pressed && !gp.buttons[15].pressed && !gp.buttons[12].pressed){
state.ship.angle += lx * 0.08 // scale
if(rt > 0.1){
const power = rt * 0.2
state.ship.vx += Math.cos(state.ship.angle) * power
state.ship.vy += Math.sin(state.ship.angle) * power
thrustPower = power;
}else{
const power = (-ly) * 0.15; // scale
state.ship.vx += Math.cos(state.ship.angle) * power
state.ship.vy += Math.sin(state.ship.angle) * power
thrustPower = power;
}
}else{
// Rotate
if(left)state.ship.angle -= 0.08
if(right)state.ship.angle += 0.08
// Thrust
if(up) applyThrust();
}
// Shoot
if(shootBtn && !state.shootHeld){
shoot();
}
state.shootHeld = shootBtn
// Start game
if(startBtn && !state.startHeld){
startPressed()
}
state.startHeld = startBtn
}
//Rendering
function wrap(a){
if(a.x < 0) a.x += WIDTH
if(a.x > WIDTH) a.x -= WIDTH
if(a.y < 0) a.y += HEIGHT
if(a.y > HEIGHT) a.y -= HEIGHT
}
function renderShip(){
const {x,y,angle}=state.ship
let M=matIdentity()
M=matMul(M,matTranslate(x,y))
M=matMul(M,matRotate(angle))
const worldPoints=shipShape.map(p =>matApply(M,p))
shipNode.setAttribute(
"points",
worldPoints.map(p => `${p.x},${p.y}`).join(" ")
);
const flameLength=20
const flameWidth=8
const backX=state.ship.x-Math.cos(state.ship.angle)*10
const backY=state.ship.y-Math.sin(state.ship.angle)*10
const leftX = backX - Math.cos(state.ship.angle) * flameLength + Math.sin(state.ship.angle) * flameWidth
const leftY = backY - Math.sin(state.ship.angle) * flameLength - Math.cos(state.ship.angle) * flameWidth
const rightX = backX - Math.cos(state.ship.angle) * flameLength - Math.sin(state.ship.angle) * flameWidth
const rightY = backY - Math.sin(state.ship.angle) * flameLength + Math.cos(state.ship.angle) * flameWidth
flameNode.setAttribute(
"points",
`${backX},${backY} ${leftX},${leftY} ${rightX},${rightY}`
);
if(thrustPower > 0){
const flicker = 0.7 + Math.random() * 0.5
flameNode.setAttribute("opacity", thrustPower * flicker)
thrustPower = 0
}else{
flameNode.setAttribute("opacity", 0)
}
}
function updateShip(){
state.ship.x += state.ship.vx
state.ship.y += state.ship.vy
// simple friction
state.ship.vx *= 0.99
state.ship.vy *= 0.99
if(state.ship.invuln > 0){
state.ship.invuln--
}
if(state.ship.hitFlash > 0){
shipNode.setAttribute("fill","white")
state.ship.hitFlash--
}else{
shipNode.setAttribute("fill","none")
}
wrap(state.ship)
}
function shoot(){
if(state.bullets.length >= 4) return;
const bulletSpeed = 6;
const {x,y,vx,vy,angle}=state.ship
// direction vector of the ship
const dx=Math.cos(angle)
const dy=Math.sin(angle)
const bullet={
x,
y,
vx: vx + dx * bulletSpeed,
vy: vy + dy * bulletSpeed,
life: 60,
node: null
}
const node=document.createElementNS('http://www.w3.org/2000/svg','circle')
node.setAttribute('r', 2)
node.setAttribute('fill', 'white')
bulletsNode.appendChild(node)
bullet.node = node
state.bullets.push(bullet)
}
function updateBullets(){
state.bullets=state.bullets.filter(b=>{
b.x += b.vx
b.y += b.vy
wrap(b)
b.life--
if(b.life <= 0){
bulletsNode.removeChild(b.node)
return false// remove from array
}
return true// keep bullet
})
}
function renderBullets(){
for(const b of state.bullets){
b.node.setAttribute('cx',b.x)
b.node.setAttribute('cy',b.y)
}
}
function makeAsteroidShape(radius,jaggedness=0.4,points=10){
const shape=[]
for(let i = 0; i < points; i++){
const angle = (i / points) * Math.PI * 2
const r = radius * (1 - jaggedness + Math.random() * jaggedness * 2)
shape.push({x: Math.cos(angle) * r, y: Math.sin(angle) * r})
}
return shape
}
function spawnAsteroidAwayFromShip(radius){
let x,y;
while (true){
x = Math.random() * WIDTH
y = Math.random() * HEIGHT
const dx = x - state.ship.x
const dy = y - state.ship.y
const dist2 = dx*dx + dy*dy
if(dist2 > SAFE_RADIUS * SAFE_RADIUS){
break; // good spawn
}
}
spawnAsteroid(x,y,radius)
}
function renderAsteroidAt(ast,ox,oy){
let M = matIdentity()
M = matMul(M, matTranslate(ast.x + ox, ast.y + oy))
M = matMul(M, matRotate(ast.angle))
const pts = ast.shape.map(p => matApply(M, p))
const str = pts.map(p => `${p.x},${p.y}`).join(" ")
// choose main or shadow node
const node = (ox === 0 && oy === 0) ? ast.main : ast.shadow
node.setAttribute("points", str)
}
function renderAsteroidAtNode(ast,ox,oy,node) {
let M=matIdentity()
M=matMul(M,matTranslate(ast.x + ox, ast.y + oy))
M=matMul(M,matRotate(ast.angle))
const pts=ast.shape.map(p=>matApply(M,p))
node.setAttribute("points",pts.map(p=>`${p.x},${p.y}`).join(" "))
}
function spawnAsteroid(x,y,radius){
const shape = makeAsteroidShape(radius)
// group for asteroid + shadow
const group = document.createElementNS("http://www.w3.org/2000/svg","g")
// main polygon
const polyMain = document.createElementNS("http://www.w3.org/2000/svg","polygon")
polyMain.setAttribute("fill", "none")
polyMain.setAttribute("stroke", "white")
polyMain.setAttribute("stroke-width", "2")
group.appendChild(polyMain)
// shadow polygons (up to 3)
const shadows = []
for(let i = 0; i < 3; i++){
const poly = document.createElementNS("http://www.w3.org/2000/svg","polygon")
poly.setAttribute("fill","none")
poly.setAttribute("stroke","white")
poly.setAttribute("stroke-width","2")
group.appendChild(poly)
shadows.push(poly)
}
group.setAttribute("filter","url(#glow)")
asteroidsNode.appendChild(group)
const asteroid={
x, y,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
radius,
angle: Math.random() * Math.PI * 2,
spin: (Math.random() - 0.5) * 0.02,
shape,
main: polyMain,
shadows:shadows,
group
}
state.asteroids.push(asteroid)
}
function renderAsteroid(ast){
// MAIN asteroid
renderAsteroidAt(ast,0,0)
// SHADOW asteroids
const offsets=[]
if (ast.x - ast.radius < 0) offsets.push({ ox: WIDTH, oy: 0 })
if (ast.x + ast.radius > WIDTH) offsets.push({ ox: -WIDTH, oy: 0 })
if (ast.y - ast.radius < 0) offsets.push({ ox: 0, oy: HEIGHT })
if (ast.y + ast.radius > HEIGHT) offsets.push({ ox: 0, oy: -HEIGHT })
// diagonal copies
if (offsets.length === 2) {
offsets.push({
ox: offsets[0].ox,
oy: offsets[1].oy
});
}
// shadows
for(let i = 0; i < offsets.length; i++){
const off = offsets[i]
const node = ast.shadows[i]
renderAsteroidAtNode(ast, off.ox, off.oy, node)
}
// hide unused shadow nodes
for(let i = offsets.length; i < ast.shadows.length; i++){
ast.shadows[i].setAttribute("points","")
}
}
function getWrapOffset(x, y, radius){
let ox = 0
let oy = 0
if (x - radius < 0) ox = WIDTH
else if (x + radius > WIDTH) ox = -WIDTH
if (y - radius < 0) oy = HEIGHT
else if (y + radius > HEIGHT) oy = -HEIGHT
return { ox, oy }
}
function updateAsteroids(){
if(state.asteroids.length === 0){
newLevel()
}
for(const a of state.asteroids){
a.x += a.vx
a.y += a.vy
a.angle += a.spin
renderAsteroid(a)
wrap(a)
}
}
// Collision detection
function pointInPoly(pt, poly) {
let inside=false;
for(let i = 0, j = poly.length - 1; i < poly.length; j = i++){
const xi = poly[i].x, yi = poly[i].y
const xj = poly[j].x, yj = poly[j].y
const intersect =
((yi > pt.y) !== (yj > pt.y)) &&
(pt.x < (xj - xi) * (pt.y - yi) / (yj - yi) + xi)
if (intersect) inside = !inside
}
return inside
}
function collision(a, b){
// build shapeA matrix
let Ma=matIdentity()
Ma=matMul(Ma,matTranslate(a.x, a.y))
Ma=matMul(Ma,matRotate(a.angle))
// build shapeB matrix
let Mb=matIdentity()
Mb=matMul(Mb,matTranslate(b.x, b.y))
Mb=matMul(Mb,matRotate(b.angle))
const worldA=a.shape.map(p=>matApply(Ma,p))
const worldB=b.shape.map(p=>matApply(Mb,p))
// ship points inside asteroid
for(const p of worldA){
if(pointInPoly(p, worldB)) return true;
}
// asteroid points inside ship
for(const p of worldB){
if (pointInPoly(p, worldA)) return true;
}
return false
}
function handleCollisions(){
// bullets vs asteroids
for(const b of state.bullets){
for(const a of state.asteroids){
let Ma = matIdentity()
Ma = matMul(Ma, matTranslate(a.x, a.y))
Ma = matMul(Ma, matRotate(a.angle))
const asteroidWorld = a.shape.map(p => matApply(Ma, p))
if(pointInPoly(b, asteroidWorld)){
// remove bullet
bulletsNode.removeChild(b.node)
state.bullets.splice(state.bullets.indexOf(b), 1)
// split asteroid or remove
asteroidsNode.removeChild(a.group);
state.asteroids.splice(state.asteroids.indexOf(a), 1)
if(a.radius > 20){
spawnAsteroid(a.x, a.y, a.radius / 2)
spawnAsteroid(a.x, a.y, a.radius / 2)
}
//effect
let M=matIdentity()
M=matMul(M,matTranslate(a.x, a.y))
M=matMul(M,matRotate(a.angle))
const world = a.shape.map(p=>matApply(M,p))
for(let i = 0; i < world.length; i++){
const p1 = world[i]
const p2 = world[(i + 1) % world.length]
spawnShard(p1, p2, a)
}
//done
break
}
}
}
// ship vs asteroids
for(const a of state.asteroids){
if(collision(state.ship,a)){
if(state.ship.invuln === 0){
state.ship.health--
state.ship.invuln = 120
state.ship.hitFlash = 10
if(state.ship.health <= 0){
killShip()
}
}
}
}
}
function resetShip(){
state.ship.health = MAX_HEALTH
state.ship.x = WIDTH / 2
state.ship.y = HEIGHT / 2
state.ship.vx = 0
state.ship.vy = 0
state.ship.angle = 0
state.ship.invuln = 180
}
function killShip(){
explodeShip(state.ship.x, state.ship.y)
state.lives--
if(state.lives <= 0){
state.mode = "gameover"
return
}
resetShip()
}
function updateParticles(){
//ship
for(let i = state.particles.length - 1; i >= 0; i--){
const p = state.particles[i]
p.x += p.vx
p.y += p.vy
p.life--
p.node.setAttribute("x1", p.x)
p.node.setAttribute("y1", p.y)
p.node.setAttribute("x2", p.x + p.vx * 2)
p.node.setAttribute("y2", p.y + p.vy * 2)
if(p.life <= 0){
p.node.remove()
state.particles.splice(i, 1)
}
}
//rocks
state.shards = state.shards.filter(s=>{
s.x1 += s.vx
s.y1 += s.vy
s.x2 += s.vx
s.y2 += s.vy
s.life--
s.node.setAttribute("x1", s.x1)
s.node.setAttribute("y1", s.y1)
s.node.setAttribute("x2", s.x2)
s.node.setAttribute("y2", s.y2)
if(s.life <= 0){
s.node.remove()
return false
}
return true
});
}
function createParticleNode(){
const p = document.createElementNS("http://www.w3.org/2000/svg","line")
p.setAttribute("stroke","orange")
p.setAttribute("stroke-width","2")
particlesNode.appendChild(p)
return p
}
function explodeShip(x, y){
for (let i = 0; i < 6; i++){
state.particles.push({
x,
y,
vx: (Math.random() - 0.5) * 6,
vy: (Math.random() - 0.5) * 6,
life: 40,
node: createParticleNode()
});
}
}
function spawnShard(p1, p2, asteroid){
const node = document.createElementNS("http://www.w3.org/2000/svg","line")
node.setAttribute("x1", p1.x)
node.setAttribute("y1", p1.y)
node.setAttribute("x2", p2.x)
node.setAttribute("y2", p2.y)
node.setAttribute("stroke","white")
node.setAttribute("stroke-width","1")
particlesNode.appendChild(node)
const angle = Math.atan2(p1.y - asteroid.y, p1.x - asteroid.x)
const speed = 2 + Math.random() * 2
state.shards.push({
x1: p1.x, y1: p1.y,
x2: p2.x, y2: p2.y,
vx: Math.cos(angle) * speed + asteroid.vx,
vy: Math.sin(angle) * speed + asteroid.vy,
life: 30 + Math.random() * 16,
node
});
}
function newLevel(){
if(state.ship.health < MAX_HEALTH){
state.ship.health++
}
state.level++
startLevel(state.level)
}
function startLevel(level){
const asteroidCount = 4 + level
for(let i = 0; i < asteroidCount; i++){
spawnAsteroidAwayFromShip(40 + Math.random() * 30)
}
}
function updateHUD(){
hudHealth.textContent = "Health: " + state.ship.health
hudLives.textContent = "Lives: " + state.lives
hudLevel.textContent = "Level: " + state.level
if(state.mode == "gameover"){
hudGameOver.textContent="GAME OVER"
hudGameOverExtra.textContent="Press Enter or Start"
} else {
hudGameOver.textContent=""
hudGameOverExtra.textContent=""
}
}
function resetGame(){
while(asteroidsNode.firstChild){
asteroidsNode.removeChild(asteroidsNode.firstChild)
}
state.asteroids.length=0
while(bulletsNode.firstChild){
bulletsNode.removeChild(bulletsNode.firstChild)
}
state.bullets.length=0
state.level=1
state.lives=LIVES
resetShip()
startLevel(state.level)
}
function startGame(){
resetGame()
state.mode="playing"
titleText.textContent=""
}
// Game loop
function loop(){
pollGamepad()
handleInput()
if(state.mode !== "playing"){
requestAnimationFrame(loop)
return
}
updateShip()
updateBullets()
updateAsteroids()
handleCollisions()
updateParticles()
renderShip()
renderBullets()
// asteroids already rendered in updateAsteroids
updateHUD()
requestAnimationFrame(loop)
}
// initial setup
autoScale()
loop()
*/'] |