바닐라 자바스크립트로 구현하는 인터랙티브 커피내기 사다리게임 웹앱

외부 프레임워크 없이 순수 HTML, CSS, JavaScript로 구현하는 커피내기 사다리게임 웹앱 제작 방법을 알아봅니다. 참여자 설정부터 사다리 자동 생성 및 애니메이션 결과 확인까지 단계별 코드를 제공합니다.

여럿이 모인 자리에서 커피나 간식을 걸고 내기를 할 때 사다리게임만큼 직관적이고 공정한 방법은 찾기 어렵습니다. 종이에 직접 선을 긋고 사다리를 그릴 필요 없이, 참여자 이름과 결과 항목만 입력하면 브라우저에서 자동으로 사다리를 만들어 결과를 확인하는 웹 애플리케이션을 직접 제작해 봅니다.

사다리게임 웹앱의 핵심 기능과 작동 원리

이번에 구현할 사다리게임은 참여자 수와 결과 항목의 개수를 동적으로 입력받아 세로줄과 가로선 (다리)을 무작위로 생성합니다. 사용자가 참여자 이름을 클릭하거나 버튼을 누르면 사다리 경로를 따라 이동하는 애니메이션이 실행되고 최종 결과가 화면에 표시됩니다.

  • 핵심 구현 단계는 다음과 같습니다.
  1. 참여자 이름과 결과 항목을 입력받는 입력 폼 구성
  2. HTML5 Canvas를 활용한 세로줄 및 무작위 가로 다리 드로잉
  3. 참여자별 경로 추적 알고리즘 및 결과 매핑
  4. 캔버스 위를 이동하는 시각적 애니메이션 처리

HTML 마크업 및 인터페이스 구조 설계

관련 글: 메뉴 선택 웹앱 제작

사용자가 참여자 목록과 결과를 쉼표나 줄바꿈으로 입력하고 게임을 시작할 수 있도록 기본 레이아웃을 구성합니다. 캔버스 엘리먼트는 사다리 선과 애니메이션이 그려질 도화지 역할을 합니다.

index.htmlHTML

<div class="ladder-container">
  <h1>커피내기 사다리게임</h1>
  <div class="input-section">
    <div class="input-group">
      <label for="players">참여자 이름 (쉼표로 구분)</label>
      <input type="text" id="players" value="김철수,이영희,박민수,정지은">
    </div>
    <div class="input-group">
      <label for="results">결과 항목 (쉼표로 구분)</label>
      <input type="text" id="results" value="당첨,꽝,꽝,꽝">
    </div>
    <button id="generate-btn">사다리 생성 및 게임 시작</button>
  </div>
  <div class="canvas-wrapper">
    <canvas id="ladder-canvas" width="800" height="500"></canvas>
  </div>
  <div id="result-display" class="result-box"></div>
</div>

스타일시트 구성과 반응형 레이아웃

깔끔하고 직관적인 사용자 경험을 위해 CSS로 입력 폼과 캔버스 영역을 정돈합니다. 모바일 화면에서도 캔버스가 깨지지 않도록 최대 너비를 조정합니다.

style.cssCSS

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background-color: #f7f9fc;
  margin: 0;
  padding: 20px;
  color: #333;
}
.ladder-container {
  max-width: 860px;
  margin: 0 auto;
  background: #ffffff;
  padding: 30px;
  border-radius: 12px;
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
}
h1 {
  text-align: center;
  margin-bottom: 25px;
  font-size: 24px;
  color: #2c3e50;
}
.input-section {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 15px;
  margin-bottom: 20px;
}
.input-group {
  display: flex;
  flex-direction: column;
}
.input-group label {
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 6px;
  color: #555;
}
.input-group input {
  padding: 10px 12px;
  border: 1px solid #dcdfe6;
  border-radius: 6px;
  font-size: 14px;
}
#generate-btn {
  grid-column: span 2;
  padding: 12px;
  background-color: #409eff;
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.2s;
}
#generate-btn:hover {
  background-color: #66b1ff;
}
.canvas-wrapper {
  width: 100%;
  overflow-x: auto;
  text-align: center;
  background: #fafbfc;
  border: 1px solid #ebeef5;
  border-radius: 8px;
  padding: 10px;
}
canvas {
  display: block;
  margin: 0 auto;
}
.result-box {
  margin-top: 20px;
  padding: 15px;
  background-color: #f0f9eb;
  border: 1px solid #e1f3d8;
  border-radius: 6px;
  color: #67c23a;
  font-weight: 600;
  text-align: center;
  min-height: 24px;
}

사다리 생성 및 경로 추적 자바스크립트 구현

참여자 배열과 결과 배열을 바탕으로 캔버스 좌표를 계산하고, 가로선 데이터를 무작위로 생성한 뒤 클릭 이벤트를 통해 사다리 타기 애니메이션을 실행하는 자바스크립트 로직입니다.

app.jsJAVASCRIPT

const canvas = document.getElementById('ladder-canvas');
const ctx = canvas.getContext('2d');
const generateBtn = document.getElementById('generate-btn');
const resultDisplay = document.getElementById('result-display');

let players = [];
let results = [];
let ladderData = [];
let colWidth = 0;
let rowHeight = 40;
let totalRows = 8;

generateBtn.addEventListener('click', initGame);

function initGame() {
  const playerInput = document.getElementById('players').value;
  const resultInput = document.getElementById('results').value;
  
  players = playerInput.split(',').map(s => s.trim()).filter(Boolean);
  results = resultInput.split(',').map(s => s.trim()).filter(Boolean);
  
  if (players.length !== results.length) {
    alert('참여자 수와 결과 항목의 개수가 일치해야 합니다.');
    return;
  }
  
  resultDisplay.textContent = '참여자를 클릭하여 결과를 확인하세요!';
  
  colWidth = canvas.width / (players.length + 1);
  
  generateLadderData();
  drawLadder();
}

function generateLadderData() {
  ladderData = [];
  for (let r = 0; r < totalRows; r++) {
    let row = [];
    for (let c = 0; c < players.length - 1; c++) {
      // 인접한 다리가 겹치지 않도록 확률 조정
      let hasBridge = Math.random() < 0.4 && (c === 0 || !row[c - 1]);
      row.push(hasBridge);
    }
    ladderData.push(row);
  }
}

function drawLadder() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 세로줄 및 텍스트 그리기
  ctx.strokeStyle = '#909399';
  ctx.lineWidth = 3;
  ctx.font = '14px sans-serif';
  ctx.fillStyle = '#303133';
  ctx.textAlign = 'center';
  
  for (let i = 0; i < players.length; i++) {
    let x = colWidth * (i + 1);
    
    // 세로선
    ctx.beginPath();
    ctx.moveTo(x, 60);
    ctx.lineTo(x, 60 + totalRows * rowHeight);
    ctx.stroke();
    
    // 참여자 이름
    ctx.fillText(players[i], x, 40);
    
    // 결과 항목
    ctx.fillText(results[i], x, 60 + totalRows * rowHeight + 25);
  }
  
  // 가로다리 그리기
  for (let r = 0; r < totalRows; r++) {
    for (let c = 0; c < players.length - 1; c++) {
      if (ladderData[r][c]) {
        let x1 = colWidth * (c + 1);
        let x2 = colWidth * (c + 2);
        let y = 60 + (r + 0.5) * rowHeight;
        
        ctx.beginPath();
        ctx.moveTo(x1, y);
        ctx.lineTo(x2, y);
        ctx.stroke();
      }
    }
  }
}

canvas.addEventListener('click', (e) => {
  const rect = canvas.getBoundingClientRect();
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;
  
  if (clickY < 20 || clickY > 80) return;
  
  let selectedCol = -1;
  for (let i = 0; i < players.length; i++) {
    let x = colWidth * (i + 1);
    if (Math.abs(clickX - x) < colWidth / 3) {
      selectedCol = i;
      break;
    }
  }
  
  if (selectedCol !== -1) {
    tracePath(selectedCol);
  }
});

function tracePath(startCol) {
  let currentCol = startCol;
  let path = [];
  
  let currX = colWidth * (currentCol + 1);
  let currY = 60;
  path.push({x: currX, y: currY});
  
  for (let r = 0; r < totalRows; r++) {
    currY += rowHeight;
    path.push({x: colWidth * (currentCol + 1), y: currY});
    
    // 오른쪽 다리 체크
    if (currentCol < players.length - 1 && ladderData[r][currentCol]) {
      currentCol++;
      path.push({x: colWidth * (currentCol + 1), y: currY});
    } 
    // 왼쪽 다리 체크
    else if (currentCol > 0 && ladderData[r][currentCol - 1]) {
      currentCol--;
      path.push({x: colWidth * (currentCol + 1), y: currY});
    }
  }
  
  drawLadder();
  animatePath(path, players[startCol], results[currentCol]);
}

function animatePath(path, playerName, finalResult) {
  let step = 0;
  ctx.strokeStyle = '#f56c6c';
  ctx.lineWidth = 4;
  
  function drawStep() {
    if (step < path.length - 1) {
      ctx.beginPath();
      ctx.moveTo(path[step].x, path[step].y);
      ctx.lineTo(path[step + 1].x, path[step + 1].y);
      ctx.stroke();
      step++;
      requestAnimationFrame(drawStep);
    } else {
      resultDisplay.textContent = `${playerName} 님의 결과: ${finalResult}`;
    }
  }
  drawStep()
}

사다리게임 실행 및 테스트 방법

작성한 세 개의 파일을 동일한 디렉토리에 저장한 뒤, 웹브라우저로 index.html 파일을 열어 동작을 확인합니다. 참여자 이름과 결과 개수가 일치하는지 확인하고 생성 버튼을 누른 후 상단의 이름을 클릭하면 빨간색 선이 이동하면서 최종 결과를 보여줍니다.

TIP

참여자 수와 결과 항목 개수가 다르면 경고창이 표시되므로 입력값이 올바른지 먼저 확인하세요.

자주 묻는 질문

참여자 인원수에 제한이 있나요?

입력창에 쉼표(,)로 구분하여 원하는 만큼 이름을 입력하면 캔버스가 자동으로 너비를 조절합니다. 다만 화면 크기에 따라 10명 이상 넘어가면 글자가 겹칠 수 있습니다.

사다리의 길이나 복잡도를 바꿀 수 있나요?

네, 자바스크립트 파일 내 totalRows 변수 값을 조절하면 사다리의 세로 길이나 행 개수를 늘리거나 줄일 수 있습니다.

완성 코드 예제

UtilLog Web Example
코드를 수정한 뒤 실행을 누르면 Result에 반영됩니다.

마무리

순수 자바스크립트와 HTML5 캔버스를 활용해 외부 라이브러리 없이 직접 커피내기 사다리게임 웹앱을 제작해 보았습니다.

입력 데이터 처리와 무작위 다리 생성 알고리즘을 응용하면 다양한 오피스 미니 게임을 직접 만들어 활용할 수 있습니다.

댓글 남기기

이메일 주소는 공개되지 않습니다.