<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>kosop</title>
    <description>kosop
  
</description>
    <link>https://kosop7.github.io/</link>
    <atom:link href="https://kosop7.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 25 Feb 2026 05:42:14 +0000</pubDate>
    <lastBuildDate>Wed, 25 Feb 2026 05:42:14 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Neon Arena — Bird Survival</title>
        <description>&lt;style&gt;
body{
  margin:0;
  background:#ffffff;
  font-family: system-ui, -apple-system, Segoe UI, Roboto;
}

.wrapper{
  max-width:980px;
  margin:30px auto;
  padding:12px;
  text-align:center;
}

canvas{
  width:100%;
  background:#fff;
  border-radius:14px;
  border:1px solid #e5e5e5;
  outline:none;
}

.hud{
  display:flex;
  justify-content:center;
  gap:16px;
  margin:10px 0;
  font-weight:700;
}
&lt;/style&gt;

&lt;div class=&quot;wrapper&quot;&gt;
  &lt;h2&gt;NEON ARENA&lt;/h2&gt;
  &lt;p&gt;방향키 / WASD 이동 · 버티기 게임&lt;/p&gt;

  &lt;div class=&quot;hud&quot;&gt;
    &lt;div&gt;❤️ HP: &lt;span id=&quot;hp&quot;&gt;100&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;⭐ SCORE: &lt;span id=&quot;score&quot;&gt;0&lt;/span&gt;&lt;/div&gt;
  &lt;/div&gt;

  &lt;canvas id=&quot;game&quot; width=&quot;960&quot; height=&quot;600&quot; tabindex=&quot;0&quot;&gt;&lt;/canvas&gt;
&lt;/div&gt;

&lt;script&gt;
const canvas = document.getElementById(&quot;game&quot;);
const ctx = canvas.getContext(&quot;2d&quot;);
canvas.focus();

/* ================= INPUT ================= */
const key={l:0,r:0,u:0,d:0};
addEventListener(&quot;keydown&quot;,e=&gt;{
  if(e.key===&quot;ArrowLeft&quot;||e.key===&quot;a&quot;) key.l=1;
  if(e.key===&quot;ArrowRight&quot;||e.key===&quot;d&quot;) key.r=1;
  if(e.key===&quot;ArrowUp&quot;||e.key===&quot;w&quot;) key.u=1;
  if(e.key===&quot;ArrowDown&quot;||e.key===&quot;s&quot;) key.d=1;
});
addEventListener(&quot;keyup&quot;,e=&gt;{
  if(e.key===&quot;ArrowLeft&quot;||e.key===&quot;a&quot;) key.l=0;
  if(e.key===&quot;ArrowRight&quot;||e.key===&quot;d&quot;) key.r=0;
  if(e.key===&quot;ArrowUp&quot;||e.key===&quot;w&quot;) key.u=0;
  if(e.key===&quot;ArrowDown&quot;||e.key===&quot;s&quot;) key.d=0;
});

/* ================= PLAYER (BIRD) ================= */
const player={
  x:480,y:300,
  vx:0,vy:0,
  speed:3,
  hp:100
};

/* ================= ENEMIES ================= */
const enemies=[];
let spawnTimer=0;

/* ================= HUD ================= */
const hpEl=document.getElementById(&quot;hp&quot;);
const scoreEl=document.getElementById(&quot;score&quot;);
let score=0;

/* ================= UTILS ================= */
function clamp(v,a,b){ return Math.max(a,Math.min(b,v)); }

/* ================= GAME LOOP ================= */
function update(){
  /* player movement */
  player.vx=(key.r-key.l)*player.speed;
  player.vy=(key.d-key.u)*player.speed;
  player.x+=player.vx;
  player.y+=player.vy;

  player.x=clamp(player.x,20,940);
  player.y=clamp(player.y,20,580);

  /* enemy spawn */
  spawnTimer++;
  if(spawnTimer&gt;80){
    spawnTimer=0;
    enemies.push({
      x:Math.random()*960,
      y:-20,
      r:10,
      speed:1.5+Math.random()*1.5
    });
  }

  /* enemy move + collision */
  enemies.forEach(e=&gt;{
    const dx=player.x-e.x;
    const dy=player.y-e.y;
    const d=Math.hypot(dx,dy);
    e.x+=dx/d*e.speed;
    e.y+=dy/d*e.speed;

    if(d&lt;18){
      player.hp-=0.4;
    }
  });

  /* cleanup */
  for(let i=enemies.length-1;i&gt;=0;i--){
    if(enemies[i].x&lt;-50||enemies[i].x&gt;1010||enemies[i].y&gt;650){
      enemies.splice(i,1);
      score++;
    }
  }

  hpEl.textContent=Math.max(0,player.hp|0);
  scoreEl.textContent=score;
}

/* ================= DRAW ================= */
function draw(){
  ctx.clearRect(0,0,960,600);

  /* grid */
  ctx.strokeStyle=&quot;rgba(0,0,0,0.04)&quot;;
  for(let x=0;x&lt;960;x+=48){
    ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,600); ctx.stroke();
  }
  for(let y=0;y&lt;600;y+=48){
    ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(960,y); ctx.stroke();
  }

  /* bird (player) */
  ctx.fillStyle=&quot;#00c896&quot;;
  ctx.beginPath();
  ctx.ellipse(player.x,player.y,12,8,0,0,Math.PI*2);
  ctx.fill();

  /* wing */
  ctx.strokeStyle=&quot;#00a77a&quot;;
  ctx.beginPath();
  ctx.moveTo(player.x-6,player.y);
  ctx.lineTo(player.x-18,player.y-6);
  ctx.stroke();

  /* beak */
  ctx.fillStyle=&quot;#ffb703&quot;;
  ctx.beginPath();
  ctx.moveTo(player.x+12,player.y);
  ctx.lineTo(player.x+20,player.y-4);
  ctx.lineTo(player.x+20,player.y+4);
  ctx.fill();

  /* enemies */
  ctx.fillStyle=&quot;#ff4d4d&quot;;
  enemies.forEach(e=&gt;{
    ctx.beginPath();
    ctx.arc(e.x,e.y,e.r,0,Math.PI*2);
    ctx.fill();
  });
}

/* ================= LOOP ================= */
function loop(){
  if(player.hp&gt;0){
    update();
    draw();
    requestAnimationFrame(loop);
  }else{
    ctx.fillStyle=&quot;rgba(255,255,255,.9)&quot;;
    ctx.fillRect(0,0,960,600);
    ctx.fillStyle=&quot;#111&quot;;
    ctx.font=&quot;28px sans-serif&quot;;
    ctx.fillText(&quot;GAME OVER&quot;,400,300);
  }
}
loop();
&lt;/script&gt;

</description>
        <pubDate>Wed, 04 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://kosop7.github.io/2026/02/neon-post</link>
        <guid isPermaLink="true">https://kosop7.github.io/2026/02/neon-post</guid>
        
        
      </item>
    
      <item>
        <title>Clumsy Bird</title>
        <description>&lt;style&gt;
html, body {
  margin: 0;
  padding: 0;
  background: #ffffff;
  font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}

/* ===== 게임 영역 ===== */
.game-wrap {
  width: 100%;
  padding: 32px 0 60px;
}

canvas {
  display: block;
  margin: 0 auto;
  background: transparent;
}

.ui {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  color: #222;
  font-weight: 800;
  pointer-events: none;
  user-select: none;
}

#score { top: 14px; font-size: 34px; }
#hint { bottom: 18%; font-size: 14px; opacity: .6; }
#over {
  top: 38%;
  font-size: 26px;
  display: none;
  text-align: center;
}
#over small {
  display: block;
  margin-top: 10px;
  font-size: 14px;
  opacity: .6;
}
&lt;/style&gt;

&lt;section class=&quot;game-wrap&quot;&gt;
  &lt;div style=&quot;position:relative;&quot;&gt;
    &lt;canvas id=&quot;game&quot;&gt;&lt;/canvas&gt;
    &lt;div id=&quot;score&quot; class=&quot;ui&quot;&gt;0&lt;/div&gt;
    &lt;div id=&quot;hint&quot; class=&quot;ui&quot;&gt;탭 / 클릭 / 스페이스&lt;/div&gt;
    &lt;div id=&quot;over&quot; class=&quot;ui&quot;&gt;
      GAME OVER
      &lt;small&gt;다시 시작: 탭 / 클릭 / 스페이스&lt;/small&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/section&gt;

&lt;script&gt;
/* ======================
   CANVAS / RESIZE
====================== */
const canvas = document.getElementById(&apos;game&apos;);
const ctx = canvas.getContext(&apos;2d&apos;);
const DPR = Math.min(window.devicePixelRatio || 1, 2);

function resize() {
  const maxW = Math.min(window.innerWidth, 520);
  const h = Math.min(window.innerHeight * 0.72, 680);

  canvas.style.width = maxW + &apos;px&apos;;
  canvas.style.height = h + &apos;px&apos;;
  canvas.width = maxW * DPR;
  canvas.height = h * DPR;
  ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
}
window.addEventListener(&apos;resize&apos;, resize);
resize();

/* ======================
   GAME CONSTANTS
====================== */
const GRAVITY = 0.45;
const FLAP = -8.6;
const PIPE_GAP = 160;
const PIPE_W = 62;
const PIPE_SPEED = 2.0;
const GROUND_H = 90;

/* ======================
   GAME STATE
====================== */
let running = false;
let gameOver = false;
let score = 0;

const bird = {
  x: 120,
  y: 300,
  vy: 0,
  rot: 0
};

let pipes = [];
let spawnTimer = 0;

const scoreEl = document.getElementById(&apos;score&apos;);
const hintEl = document.getElementById(&apos;hint&apos;);
const overEl = document.getElementById(&apos;over&apos;);

/* ======================
   INPUT
====================== */
function flap() {
  if (!running || gameOver) {
    reset();
    running = true;
    hintEl.style.display = &apos;none&apos;;
    return;
  }
  bird.vy = FLAP;
}

window.addEventListener(&apos;keydown&apos;, e =&gt; {
  if (e.code === &apos;Space&apos;) flap();
});
window.addEventListener(&apos;mousedown&apos;, flap);
window.addEventListener(&apos;touchstart&apos;, e =&gt; {
  e.preventDefault();
  flap();
}, { passive:false });

/* ======================
   RESET
====================== */
function reset() {
  gameOver = false;
  score = 0;
  scoreEl.textContent = score;
  overEl.style.display = &apos;none&apos;;

  bird.y = canvas.height / DPR / 2;
  bird.vy = 0;

  pipes = [];
  spawnTimer = 0;
}

/* ======================
   PIPES
====================== */
function addPipe() {
  pipes.push({
    x: canvas.width / DPR + 40,
    gapY: Math.random() * 260 + 120,
    passed: false
  });
}

/* ======================
   UPDATE
====================== */
function update() {
  if (!running || gameOver) return;

  bird.vy += GRAVITY;
  bird.y += bird.vy;
  bird.rot = Math.max(-0.5, Math.min(1.2, bird.vy / 10));

  spawnTimer++;
  if (spawnTimer &gt; 120) {
    addPipe();
    spawnTimer = 0;
  }

  pipes.forEach(p =&gt; {
    p.x -= PIPE_SPEED;

    if (!p.passed &amp;&amp; p.x + PIPE_W &lt; bird.x) {
      p.passed = true;
      score++;
      scoreEl.textContent = score;
    }

    if (
      bird.x + 14 &gt; p.x &amp;&amp;
      bird.x - 14 &lt; p.x + PIPE_W &amp;&amp;
      (bird.y &lt; p.gapY - PIPE_GAP / 2 ||
       bird.y &gt; p.gapY + PIPE_GAP / 2)
    ) {
      gameOver = true;
    }
  });

  if (bird.y &gt; canvas.height / DPR - GROUND_H || bird.y &lt; 0) {
    gameOver = true;
  }

  if (gameOver) overEl.style.display = &apos;block&apos;;
}

/* ======================
   DRAW BIRD (새 모양)
====================== */
function drawBird() {
  ctx.save();
  ctx.translate(bird.x, bird.y);
  ctx.rotate(bird.rot);

  /* 몸통 */
  ctx.fillStyle = &apos;#ffdf3a&apos;;
  ctx.beginPath();
  ctx.ellipse(0, 0, 14, 11, 0, 0, Math.PI * 2);
  ctx.fill();

  /* 날개 */
  ctx.fillStyle = &apos;#f1c40f&apos;;
  ctx.beginPath();
  ctx.moveTo(-4, 0);
  ctx.quadraticCurveTo(-22, -6, -18, 6);
  ctx.closePath();
  ctx.fill();

  /* 부리 */
  ctx.fillStyle = &apos;#ff8c00&apos;;
  ctx.beginPath();
  ctx.moveTo(14, -2);
  ctx.lineTo(22, 2);
  ctx.lineTo(14, 6);
  ctx.closePath();
  ctx.fill();

  /* 눈 */
  ctx.fillStyle = &apos;#222&apos;;
  ctx.beginPath();
  ctx.arc(4, -4, 2, 0, Math.PI * 2);
  ctx.fill();

  ctx.restore();
}

/* ======================
   DRAW LOOP
====================== */
function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  /* 파이프 */
  ctx.fillStyle = &apos;#2ecc71&apos;;
  pipes.forEach(p =&gt; {
    ctx.fillRect(p.x, 0, PIPE_W, p.gapY - PIPE_GAP / 2);
    ctx.fillRect(p.x, p.gapY + PIPE_GAP / 2, PIPE_W, canvas.height / DPR);
  });

  drawBird();
  update();
  requestAnimationFrame(draw);
}

draw();
&lt;/script&gt;

</description>
        <pubDate>Wed, 04 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://kosop7.github.io/2026/02/clumsy-bird</link>
        <guid isPermaLink="true">https://kosop7.github.io/2026/02/clumsy-bird</guid>
        
        
      </item>
    
      <item>
        <title>A SMALL WORLD</title>
        <description>&lt;style&gt;
canvas{
  max-width:100%;
  border-radius:14px;
  touch-action:none;
}
&lt;/style&gt;

&lt;div style=&quot;display:flex;justify-content:center;margin:40px 0;&quot;&gt;
  &lt;canvas id=&quot;game&quot; width=&quot;960&quot; height=&quot;540&quot;&gt;&lt;/canvas&gt;
&lt;/div&gt;

&lt;script&gt;
/* =========================
   A SMALL WORLD – EP.1
========================= */

/* ---------- CORE ---------- */
const c = document.getElementById(&quot;game&quot;);
const ctx = c.getContext(&quot;2d&quot;);

/* ---------- MOBILE SAFETY ---------- */
document.addEventListener(&quot;touchmove&quot;,e=&gt;e.preventDefault(),{passive:false});

/* ---------- SAVE ---------- */
const SAVE_KEY=&quot;smallworld_ep1&quot;;
const save = JSON.parse(localStorage.getItem(SAVE_KEY)) || {
  talked:{},
  flags:{},
  region:&quot;village&quot;
};
function saveGame(){localStorage.setItem(SAVE_KEY,JSON.stringify(save));}

/* ---------- STATE ---------- */
let state=&quot;title&quot;;
let dialog=null;
let choiceIndex=0;

/* ---------- WORLD ---------- */
const world={w:5200,h:2600};
let region=save.region;

const regions={
  village:{ sky1:&quot;#6b88a6&quot;, sky2:&quot;#14141a&quot;, ground:&quot;#2b2926&quot;, name:&quot;THE VILLAGE&quot; },
  forest:{ sky1:&quot;#223322&quot;, sky2:&quot;#000&quot;, ground:&quot;#1a241a&quot;, name:&quot;THE FOREST&quot; },
  gate:{ sky1:&quot;#2a2a2a&quot;, sky2:&quot;#000&quot;, ground:&quot;#111&quot;, name:&quot;THE GATE&quot; }
};

/* ---------- HERO ---------- */
const hero={x:1200,y:1400,speed:2.4,dir:1};

/* ---------- CAMERA ---------- */
const cam={x:0,y:0};

/* ---------- INPUT ---------- */
const keys={};
document.addEventListener(&quot;keydown&quot;,e=&gt;{
  keys[e.key]=true;
  if(state===&quot;title&quot;) state=&quot;play&quot;;
});
document.addEventListener(&quot;keyup&quot;,e=&gt;keys[e.key]=false);

c.addEventListener(&quot;touchstart&quot;,e=&gt;{
  const t=e.touches[0];
  if(t.clientX&lt;c.width/2) keys[&quot;ArrowLeft&quot;]=true;
  else keys[&quot;ArrowRight&quot;]=true;
});
c.addEventListener(&quot;touchend&quot;,()=&gt;{keys[&quot;ArrowLeft&quot;]=keys[&quot;ArrowRight&quot;]=false});

/* ---------- NPC ---------- */
const npcs=[
 {id:&quot;old&quot;,x:900,y:1500,region:&quot;village&quot;,name:&quot;노인&quot;,
  talk:[
   {t:&quot;해가 지면, 사람은 자기 이야기를 하게 되지.&quot;,flag:&quot;old1&quot;},
   {t:&quot;길은 항상 있었어. 우리가 안 나갔을 뿐.&quot;,flag:&quot;old2&quot;}
  ]},
 {id:&quot;woman&quot;,x:1800,y:1480,region:&quot;village&quot;,name:&quot;여인&quot;,
  talk:[
   {t:&quot;숲 쪽으로 가봤어?&quot;,flag:&quot;forest_hint&quot;}
  ]},
 {id:&quot;child&quot;,x:2600,y:1450,region:&quot;village&quot;,name:&quot;아이&quot;,
  talk:[
   {t:&quot;저 멀리 반짝이는 게 있어.&quot;,flag:&quot;gate_hint&quot;}
  ]},
 {id:&quot;hunter&quot;,x:3400,y:1550,region:&quot;forest&quot;,name:&quot;사냥꾼&quot;,
  talk:[
   {t:&quot;여긴 길이 아니야. 선택이지.&quot;,flag:&quot;hunter1&quot;}
  ]},
 {id:&quot;guard&quot;,x:4800,y:1500,region:&quot;gate&quot;,name:&quot;문지기&quot;,
  talk:[
   {t:&quot;돌아갈 수도 있고, 넘어갈 수도 있다.&quot;,flag:&quot;end_ready&quot;}
  ]}
];

/* ---------- UTILS ---------- */
const clamp=(v,a,b)=&gt;Math.max(a,Math.min(b,v));

/* ---------- UPDATE ---------- */
let time=0;
function update(){
  time+=0.04;
  if(state!==&quot;play&quot;) return;

  if(!dialog){
    if(keys[&quot;ArrowLeft&quot;]){hero.x-=hero.speed;hero.dir=-1;}
    if(keys[&quot;ArrowRight&quot;]){hero.x+=hero.speed;hero.dir=1;}
    if(keys[&quot;ArrowUp&quot;]){hero.y-=hero.speed;}
    if(keys[&quot;ArrowDown&quot;]){hero.y+=hero.speed;}
  }

  hero.x=clamp(hero.x,60,world.w-60);
  hero.y=clamp(hero.y,200,world.h-60);

  cam.x+=(hero.x-cam.x-c.width/2)*0.08;
  cam.y+=(hero.y-cam.y-c.height/2)*0.08;

  checkNPC();
}

/* ---------- NPC CHECK ---------- */
function checkNPC(){
  dialog=null;
  npcs.forEach(n=&gt;{
    if(n.region!==region) return;
    if(save.talked[n.id]) return;
    if(Math.abs(hero.x-n.x)&lt;60 &amp;&amp; Math.abs(hero.y-n.y)&lt;60){
      dialog=n;
    }
  });
}

/* ---------- DRAW HERO ---------- */
function drawHero(){
  const x=hero.x-cam.x,y=hero.y-cam.y;
  const sway=Math.sin(time)*4;

  ctx.fillStyle=&quot;rgba(0,0,0,.25)&quot;;
  ctx.beginPath();ctx.ellipse(x,y+14,18,8,0,0,6.28);ctx.fill();

  ctx.fillStyle=&quot;#f0c94a&quot;;
  ctx.beginPath();
  ctx.moveTo(x-20,y-10);
  ctx.lineTo(x+10,y-10);
  ctx.lineTo(x+24,y+28);
  ctx.lineTo(x-30,y+28);
  ctx.closePath();ctx.fill();

  ctx.fillStyle=&quot;#3b3b3b&quot;;
  ctx.fillRect(x-8,y-6,16,20);

  ctx.fillStyle=&quot;#efe8db&quot;;
  ctx.beginPath();ctx.arc(x+hero.dir*2,y-22,9,0,6.28);ctx.fill();
}

/* ---------- DRAW ---------- */
function draw(){
  const r=regions[region];
  const g=ctx.createLinearGradient(0,0,0,c.height);
  g.addColorStop(0,r.sky1);g.addColorStop(1,r.sky2);
  ctx.fillStyle=g;ctx.fillRect(0,0,c.width,c.height);

  ctx.fillStyle=r.ground;
  ctx.fillRect(-cam.x,c.height-180-cam.y,world.w,260);

  npcs.forEach(n=&gt;{
    if(n.region!==region) return;
    const x=n.x-cam.x,y=n.y-cam.y;
    ctx.fillStyle=&quot;#888&quot;;
    ctx.fillRect(x-6,y-20,12,20);
    ctx.fillStyle=&quot;#eee&quot;;
    ctx.beginPath();ctx.arc(x,y-28,7,0,6.28);ctx.fill();
  });

  drawHero();

  if(dialog){
    ctx.fillStyle=&quot;rgba(0,0,0,.6)&quot;;
    ctx.fillRect(80,c.height-140,c.width-160,80);
    ctx.fillStyle=&quot;#fff&quot;;
    ctx.font=&quot;16px serif&quot;;
    ctx.fillText(dialog.name+&quot;: &quot;+dialog.talk[0].t,100,c.height-90);
  }

  if(state===&quot;title&quot;){
    ctx.fillStyle=&quot;rgba(0,0,0,.6)&quot;;
    ctx.fillRect(0,0,c.width,c.height);
    ctx.fillStyle=&quot;#fff&quot;;
    ctx.font=&quot;30px serif&quot;;
    ctx.fillText(&quot;A SMALL WORLD&quot;,c.width/2-120,c.height/2-10);
    ctx.font=&quot;14px serif&quot;;
    ctx.fillText(&quot;Tap / Press Any Key&quot;,c.width/2-80,c.height/2+20);
  }
}

/* ---------- INTERACT ---------- */
document.addEventListener(&quot;keydown&quot;,e=&gt;{
  if(e.key===&quot; &quot; &amp;&amp; dialog){
    save.talked[dialog.id]=true;
    saveGame();
    dialog=null;
    if(hero.x&gt;3000) region=&quot;forest&quot;;
    if(hero.x&gt;4400) region=&quot;gate&quot;;
    save.region=region;
  }
});

/* ---------- LOOP ---------- */
function loop(){update();draw();requestAnimationFrame(loop);}
loop();
&lt;/script&gt;

</description>
        <pubDate>Tue, 03 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://kosop7.github.io/2026/02/game1-post</link>
        <guid isPermaLink="true">https://kosop7.github.io/2026/02/game1-post</guid>
        
        
      </item>
    
      <item>
        <title>Mini Tekken</title>
        <description>&lt;style&gt;
  .tekken-wrap {
    display: flex;
    justify-content: center;
    margin: 40px 0 20px;
  }

  canvas {
    background: linear-gradient(#1a1a1a, #000);
    border: 2px solid #444;
    max-width: 100%;
    touch-action: none;
  }

  .controls {
    text-align: center;
    margin-bottom: 30px;
    user-select: none;
  }

  .controls button {
    font-size: 14px;
    padding: 10px 14px;
    margin: 4px;
  }
&lt;/style&gt;

&lt;div class=&quot;tekken-wrap&quot;&gt;
  &lt;canvas id=&quot;tekken&quot; width=&quot;820&quot; height=&quot;420&quot;&gt;&lt;/canvas&gt;
&lt;/div&gt;

&lt;div class=&quot;controls&quot;&gt;
  &lt;button ontouchstart=&quot;input.left=true;event.preventDefault()&quot; ontouchend=&quot;input.left=false&quot; onmousedown=&quot;input.left=true&quot; onmouseup=&quot;input.left=false&quot;&gt;◀&lt;/button&gt;

  &lt;button ontouchstart=&quot;input.right=true;event.preventDefault()&quot; ontouchend=&quot;input.right=false&quot; onmousedown=&quot;input.right=true&quot; onmouseup=&quot;input.right=false&quot;&gt;▶&lt;/button&gt;

  &lt;button ontouchstart=&quot;jump();event.preventDefault()&quot; onclick=&quot;jump()&quot;&gt;Q JUMP&lt;/button&gt;
  &lt;button ontouchstart=&quot;attack(&apos;punch&apos;);event.preventDefault()&quot; onclick=&quot;attack(&apos;punch&apos;)&quot;&gt;W PUNCH&lt;/button&gt;
  &lt;button ontouchstart=&quot;attack(&apos;kick&apos;);event.preventDefault()&quot; onclick=&quot;attack(&apos;kick&apos;)&quot;&gt;E KICK&lt;/button&gt;
  &lt;button ontouchstart=&quot;guard();event.preventDefault()&quot; onclick=&quot;guard()&quot;&gt;R GUARD&lt;/button&gt;
&lt;/div&gt;

&lt;script&gt;
/* =====================
   GLOBAL SAFETY
===================== */
document.addEventListener(&quot;touchmove&quot;, e =&gt; {
  e.preventDefault();
}, { passive:false });

/* =====================
   CORE
===================== */
const canvas = document.getElementById(&quot;tekken&quot;);
const ctx = canvas.getContext(&quot;2d&quot;);
const GROUND = 330;

/* =====================
   INPUT
===================== */
const input = { left:false, right:false };

document.addEventListener(&quot;keydown&quot;, e=&gt;{
  if(e.key===&quot;ArrowLeft&quot;||e.key===&quot;a&quot;) input.left=true;
  if(e.key===&quot;ArrowRight&quot;||e.key===&quot;d&quot;) input.right=true;
  if(e.key===&quot;q&quot;) jump();
  if(e.key===&quot;w&quot;) attack(&quot;punch&quot;);
  if(e.key===&quot;e&quot;) attack(&quot;kick&quot;);
  if(e.key===&quot;r&quot;) guard();
  if(e.key===&quot;Enter&quot; &amp;&amp; (gameState===&quot;win&quot;||gameState===&quot;lose&quot;)) resetGame();
});

document.addEventListener(&quot;keyup&quot;, e=&gt;{
  if(e.key===&quot;ArrowLeft&quot;||e.key===&quot;a&quot;) input.left=false;
  if(e.key===&quot;ArrowRight&quot;||e.key===&quot;d&quot;) input.right=false;
});

/* =====================
   AUDIO (SAFE)
===================== */
let audio;
document.addEventListener(&quot;pointerdown&quot;, ()=&gt;{
  if(!audio){
    audio = new (window.AudioContext||window.webkitAudioContext)();
  }
},{once:true});

function hitSound(freq=180){
  if(!audio) return;
  const o = audio.createOscillator();
  o.frequency.value = freq;
  o.connect(audio.destination);
  o.start();
  o.stop(audio.currentTime + 0.05);
}

/* =====================
   FIGHTER
===================== */
function Fighter(x,color,dir,hp=100){
  return {
    x, y:GROUND, vy:0,
    hp, maxHp:hp,
    dir, color,
    state:&quot;idle&quot;, frame:0
  };
}

/* =====================
   ENEMIES
===================== */
const enemies = [
  { color:&quot;#f44&quot;, hp:80 },
  { color:&quot;#fa4&quot;, hp:100 },
  { color:&quot;#f4f&quot;, hp:120 }
];
let enemyIndex = 0;

/* =====================
   GAME STATE
===================== */
let gameState = &quot;fight&quot;;

let p1 = Fighter(180,&quot;#4af&quot;,1,100);
let p2 = Fighter(620,enemies[0].color,-1,enemies[0].hp);

/* =====================
   ACTIONS
===================== */
function jump(){
  if(gameState!==&quot;fight&quot;) return;
  if(p1.y===GROUND) p1.vy=-13;
}

function guard(){
  if(gameState!==&quot;fight&quot;) return;
  if(p1.state===&quot;idle&quot;){
    p1.state=&quot;guard&quot;;
    p1.frame=0;
  }
}

function attack(type){
  if(gameState!==&quot;fight&quot;) return;
  if(p1.state!==&quot;idle&quot;) return;

  p1.state=type;
  p1.frame=0;

  const range = type===&quot;kick&quot;?50:35;
  if(Math.abs(p1.x-p2.x)&lt;range){
    if(p2.state!==&quot;guard&quot;){
      p2.hp -= type===&quot;kick&quot;?14:10;
      p2.state=&quot;hit&quot;;
      hitSound(type===&quot;kick&quot;?120:200);
    }
  }
}

/* =====================
   AI
===================== */
function ai(){
  if(gameState!==&quot;fight&quot;) return;
  if(p2.state!==&quot;idle&quot;) return;

  if(p2.x&gt;p1.x+45) p2.x-=2;
  if(p2.x&lt;p1.x-45) p2.x+=2;

  if(Math.random()&lt;0.02){
    p2.state=Math.random()&lt;0.5?&quot;punch&quot;:&quot;kick&quot;;
    if(Math.abs(p2.x-p1.x)&lt;40){
      if(p1.state!==&quot;guard&quot;){
        p1.hp-=12;
        p1.state=&quot;hit&quot;;
        hitSound();
      }
    }
  }
}

/* =====================
   PHYSICS
===================== */
function physics(p){
  p.vy+=0.6;
  p.y+=p.vy;
  if(p.y&gt;GROUND){ p.y=GROUND; p.vy=0; }
}

/* =====================
   DRAW
===================== */
function drawFighter(p){
  const x=p.x,y=p.y;
  ctx.strokeStyle=p.color;
  ctx.lineWidth=4;

  ctx.beginPath(); ctx.arc(x,y-80,10,0,Math.PI*2); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(x,y-70); ctx.lineTo(x,y-35); ctx.stroke();

  ctx.beginPath(); ctx.moveTo(x,y-60);
  ctx.lineTo(x+(p.state===&quot;punch&quot;?26:18)*p.dir,y-50);
  ctx.stroke();

  ctx.beginPath(); ctx.moveTo(x,y-35);
  ctx.lineTo(x+(p.state===&quot;kick&quot;?26:10)*p.dir,y);
  ctx.stroke();
}

function hpBar(p,x){
  ctx.fillStyle=&quot;#333&quot;; ctx.fillRect(x,20,200,10);
  ctx.fillStyle=&quot;#0f0&quot;;
  ctx.fillRect(x,20,(p.hp/p.maxHp)*200,10);
}

/* =====================
   ROUND
===================== */
function nextEnemy(){
  enemyIndex++;
  if(enemyIndex&gt;=enemies.length){
    gameState=&quot;win&quot;;
  }else{
    p2 = Fighter(620,enemies[enemyIndex].color,-1,enemies[enemyIndex].hp);
  }
}

function resetGame(){
  enemyIndex=0;
  p1 = Fighter(180,&quot;#4af&quot;,1,100);
  p2 = Fighter(620,enemies[0].color,-1,enemies[0].hp);
  gameState=&quot;fight&quot;;
}

/* =====================
   LOOP
===================== */
function update(){
  if(gameState===&quot;fight&quot;){
    if(input.left) p1.x-=4;
    if(input.right) p1.x+=4;

    physics(p1); physics(p2); ai();

    [p1,p2].forEach(p=&gt;{
      p.frame++;
      if(p.frame&gt;18){
        p.state=&quot;idle&quot;;
        p.frame=0;
      }
    });

    if(p2.hp&lt;=0) nextEnemy();
    if(p1.hp&lt;=0) gameState=&quot;lose&quot;;
  }
}

function draw(){
  ctx.clearRect(0,0,820,420);
  ctx.fillStyle=&quot;#555&quot;; ctx.fillRect(0,GROUND,820,5);

  drawFighter(p1);
  drawFighter(p2);
  hpBar(p1,60);
  hpBar(p2,560);

  if(gameState===&quot;win&quot;||gameState===&quot;lose&quot;){
    ctx.fillStyle=&quot;rgba(0,0,0,0.7)&quot;;
    ctx.fillRect(0,0,820,420);
    ctx.fillStyle=&quot;#fff&quot;;
    ctx.font=&quot;28px sans-serif&quot;;
    ctx.fillText(gameState===&quot;win&quot;?&quot;YOU WIN!&quot;:&quot;YOU LOSE&quot;,320,180);
    ctx.font=&quot;16px sans-serif&quot;;
    ctx.fillText(&quot;ENTER : 재시작&quot;,330,220);
  }
}

function loop(){
  update();
  draw();
  requestAnimationFrame(loop);
}
loop();
&lt;/script&gt;

</description>
        <pubDate>Tue, 03 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://kosop7.github.io/2026/02/game-post</link>
        <guid isPermaLink="true">https://kosop7.github.io/2026/02/game-post</guid>
        
        
      </item>
    
  </channel>
</rss>
