← 목록으로

킹니갓비 게임 '팀버본'으로 데이터베이스를 만들 수 있다고? 실화냐? 🤯

2026. 3. 30.

킹니갓비 게임 '팀버본'으로 데이터베이스를 만들 수 있다고? 실화냐? 🤯

안녕하세요, 10년 차 개발자이자 테크 블로거, 블록체인 비버입니다. 요즘 제가 푹 빠져 있는 게임 중 하나인 팀버본(Timberborn)이 드디어 1.0 정식 버전을 출시하며 얼리 액세스를 졸업했습니다.

혹시 이 게임을 잘 모르시는 분들을 위해 간단히 설명하자면, 팀버본은 비버들이 문명을 건설하는 도시 건설 전략 시뮬레이션 게임입니다. 인류(게임 내에서는 '호만즈'라고 불리죠)가 사라진 지구를 비버들이 점령하고, 플레이어는 비버 정착지의 지도자가 되어 자원을 모으고, 비버들의 다양한 니즈를 충족시키며, 복잡한 수력 발전망과 공장, 운송 시스템 등을 구축해야 합니다.

...아니, 제 경우에는 '데이터베이스'를 구축해야 했습니다. 😅

이 게임은 3D 물리학 엔진을 기반으로 물이 흐르고, 가뭄으로 물을 아껴야 하는 건조한 계절이나 오염된 물이 유입되어 비버들에게 해로운 '나쁜 조수(bad tides)' 등 다양한 계절 변화가 특징입니다. 2021년 초기 출시 이후 정말 많은 기능이 추가되었죠.

그리고 가장 최근 업데이트는 저에게 게임을 '약간 망가뜨리는' 수준의 충격을 주었습니다. 바로 자동화 기능 때문입니다! 이 자동화 기능에는 나쁜 조수를 감지하거나 물의 흐름을 파악하는 다양한 센서들이 포함되어 있습니다. 심지어 논리 게이트(Logic Gates)와 HTTP 레버까지 추가된 거죠.

Oh No...

오, 제발. 이건 그냥 지나칠 수 없었습니다. 무조건 가지고 놀아봐야죠! 게임 내에서 HTTP 레버가 어떻게 생겼는지 한 번 보시죠.

팀버본의 HTTP 레버

보시다시피, 레버마다 이름(이 경우 "HTTP Lever 1")과 "Switch-on URL", "Switch-off URL"이 있습니다.

원래 이 기능의 의도는 스트리머들이 트위치 웹훅(Twitch webhooks) 같은 다른 서비스와 연동하여 사용하는 것입니다. 예를 들어, 시청자가 '좋아요'를 누르면 게임 내에서 불꽃놀이가 터진다거나 하는 식으로 말이죠.

그런데 말입니다: 누가 이 레버를 하나만 쓰라고 했죠?

팀버본의 1000개에 달하는 HTTP 레버

제가 무슨 생각을 하는지 감이 오시나요?

Oh No! (정말로!)

네, 맞습니다. 이 게임은 약 1,000개 정도의 HTTP 레버를 동시에 운용할 수 있습니다. 제가 이걸 가지고 실험하는 동안 윈도우즈가 "시스템에 문제가 발생했다"는 경고를 두 번이나 띄웠으니, 애초에 실용적이지는 않다는 걸 알 수 있죠. 하지만 실용적일 필요는 없습니다. 재미있으면 그만이니까요!

각 레버에는 레버를 켜는 엔드포인트와 끄는 엔드포인트가 하나씩 있습니다. 아쉽게도 배치(batch) 처리는 불가능하지만, 어쩌면 모드가 있을지도 모릅니다. 또 다른 엔드포인트는 현재 맵에 있는 모든 레버의 상태를 반환합니다. 데이터는 대략 이런 형태입니다:

[
  {
    "name": "HTTP Lever 829",
    "state": false,
    "springReturn": false
  },
  {
    "name": "HTTP Lever 154",
    "state": true,
    "springReturn": false
  },
  {
    "name": "HTTP Lever 839",
    "state": false,
    "springReturn": false
  },
  {
    "name": "HTTP Lever 164",
    "state": true,
    "springReturn": false
  }
]

두 가지 상태를 가진 HTTP 레버는 단일 비트(single bit)로 생각할 수 있습니다. 결국 필요한 것은 이 비트를 읽고 쓰는 방법뿐입니다.

팀버본 웹 인터페이스: 동작 원리

기본 아이디어는 간단합니다. 사용자 입력을 JSON으로 변환하고, 이를 ASCII 인코딩을 거쳐 바이너리로 만듭니다. 그리고 각 비트(레버)를 하나씩 전환하는 거죠. 데이터를 읽을 때는 모든 레버 상태를 한 번에 가져와 비트 시퀀스로 재정렬한 다음, 8비트 청크로 분할하고 다시 문자로 디코딩하여 결과 JSON을 파싱합니다. 짜잔! 이렇게 읽기/쓰기가 가능한 데이터 스토리지가 탄생합니다.

먼저 간단한 HTML부터 시작해 봅시다.

<form method="POST" id="form">
  <div>
    <input type="text" id="title">
  </div>
  <div>
    <textarea id="text"></textarea>
  </div>
  <button type="submit">Store in Timberborn</button>
</form>

<button type="button" id="load">
  Load data from Timberborn
</button>

이제부터 재미있는 부분입니다. 기본적인 자바스크립트 스캐폴딩으로 시작합니다.

const title = document.querySelector('#title')
const text = document.querySelector('#text')
const form = document.querySelector('#form')
const load = document.querySelector('#load')
const chunkSize = 8 // 나중에 비트를 분할하는 데 필요합니다.
const numberOfLevers = 1000 // 게임 내 레버 수와 정확히 일치해야 안정적으로 작동합니다.

다음으로, 폼 제출 이벤트를 수신하고 두 입력 필드에서 JSON 문자열을 만듭니다.

form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const data = {
    title: title.value,
    text: text.value,
  }

  const json = JSON.stringify(data)

  // ...
})

JSON 문자열을 얻었으니, 이제 이걸 바이너리 문자열의 연속으로 변환할 수 있습니다.

form.addEventListener('submit', async (event) => {
  // ...

  const json = JSON.stringify(data)

  const asciiEncoded = json.split('')
    .map(c => c.charCodeAt(0)) // 각 문자를 ASCII 코드로 변환

  const binary = asciiEncoded.map(
    num => num.toString(2).padStart(chunkSize, '0') // ASCII 코드를 2진수로, 8비트 길이로 만듦
  )
  
  // ...
})

이 코드를 실행하면 0과 1로 이루어진 8비트 길이의 문자열 배열이 생성됩니다.

HTML 폼과 바이너리 데이터의 console.log를 보여주는 브라우저 창

다음으로 이 비트들을 연결하여 최종 비트 문자열을 만듭니다. 그리고 이 비트들을 불리언(boolean) 값으로, 다시 API URL로 변환하여 fetch를 이용해 호출합니다.

form.addEventListener('submit', async (event) => {
  // ...

  const bits = binary.join('')
    // 현재 데이터 끝에 잔여 데이터가 남지 않도록
    .padEnd(numberOfLevers, '0') // 총 레버 수만큼 '0'으로 채워넣습니다.
    .split('')
    .map(b => b === '1') // '1'이면 true, '0'이면 false로 변환

  const allUrls = bits.map((bit, key) => 
    `http://localhost:8080/api/switch-${bit ? 'on' : 'off'}/HTTP Lever ${key + 1}`
  )
      
  await Promise.all(allUrls.map(url => fetch(url))) // 모든 URL에 동시에 요청 보냄

  console.log('done!')
})

데이터를 저장할 때 이 과정은 게임의 엔드포인트로 총 1,000개의 HTTP 요청을 발생시킵니다. 시스템이 좋아할 리 없죠. 제가 실무에서 대용량 데이터를 다룰 때도 이렇게 순차적인 fetch 요청을 동시에 쏘는 일은 극히 드물지만, 이 실험에서는 그 덕분에 시스템 부하가 바로 체감될 정도로 재미있는 상황이 연출되었습니다.

하지만: 작동합니다!

텍스트 저장 시 팀버본 HTTP 레버가 업데이트되는 GIF

(GIF가 로드되는 데 몇 초 정도 걸릴 수 있습니다...)

팀버본에서 데이터 읽기

다음으로 데이터를 읽어야 합니다. 예시에서 보셨듯이 레버 상태는 한 번에 가져올 수 있지만, 순서가 뒤죽박죽입니다. 이를 해결하기 위해 모든 데이터를 로드한 다음 정렬합니다. 그리고 모든 것을 다시 비트 문자열로 변환하고, 이를 청크로 나누어 ASCII로 디코딩합니다.

load.addEventListener('click', async () => {
  const response = await fetch('http://localhost:8080/api/levers')
  const json = await response.json()

  // 레버 이름을 기반으로 정렬하여 올바른 순서로 만듭니다.
  const sorted = json.sort((a, b) => {
    const aNumber = Number(a.name.replace('HTTP Lever ', ''))
    const bNumber = Number(b.name.replace('HTTP Lever ', ''))

    return aNumber - bNumber
  })

  const bitString = sorted.map(l => l.state ? '1' : '0') // 레버 상태를 '1' 또는 '0' 비트로 변환

  const chunks = []
  // 비트 문자열을 8비트 청크로 나눕니다.
  for (let i = 0; i < bitString.length; i += chunkSize) {
    chunks.push(bitString.slice(i, i + chunkSize).join(''))
  }

  const numbers = chunks.map(c => Number.parseInt(c, 2)) // 각 청크를 10진수로 변환
    // 0으로만 이루어진 데이터는 쓰레기 데이터일 가능성이 높으므로 필터링합니다.
    .filter(n => n > 0)
  const letters = numbers.map(n => String.fromCharCode(n)) // 10진수를 문자로 변환
  const data = JSON.parse(letters.join('')) // 문자들을 합쳐 JSON으로 파싱

  title.value = data.title
  text.value = data.text
})

이것으로 끝입니다!

기술적으로 보면 이제 이것은 클라우드 스토리지라고 할 수 있습니다. 스팀(Steam)이 세이브 파일을 클라우드에 업로드하고, 팀버본은 HTTP 레버의 상태를 저장하기 때문(즉, 게임 저장 시 레버 상태도 함께 저장됨)에 모든 데이터가 영구적으로 유지됩니다.

혹시 이 보잘것없는 킬로비트 이상의 데이터를 팀버본에 저장하는 데 성공하신 분이 있다면, 저에게 꼭 연락 주세요. 분명 어떻게든 Drupal 데이터베이스 어댑터를 만들 수 있을 것이라고 확신합니다. 개인적으로 이런 기상천외한 방식으로 데이터를 저장하고 관리하는 접근 방식은 단순한 실험을 넘어, 특정 제약 조건 하에서 창의적인 문제 해결 능력을 보여주는 좋은 예시라고 생각합니다.

(추신: AI 이미지 사용한 점, 죄송하지만 죄송하지 않습니다. 언젠가는 시도해봐야 했거든요!)

(재추신: 이 포스팅은 협찬을 받지 않았습니다. 하지만 팀버본 개발자님들, 혹시 이 글을 읽으신다면, 자동화 기능을 이용한 훨씬 더 기상천외한 아이디어들을 시도해볼 의향이 있습니다! :D)


이 글을 쓰는 동안 제가 즐거웠던 만큼, 여러분도 즐겁게 읽으셨기를 바랍니다! 마음에 드셨다면 ❤️를 남겨주세요! 저는 여가 시간에 테크 글을 쓰고 가끔 커피를 마시는 것을 좋아합니다.

제 노력을 응원하고 싶으시다면, 커피 한 잔 사주실 수 있습니다 ☕! 페이팔(Paypal)을 통해 직접 후원해주셔도 좋습니다! 아니면 블루스카이 🦋에서 저를 팔로우해주세요!

Buy me a coffee button


원문: https://dev.to/thormeier/how-to-use-timberborn-yes-the-beaver-city-building-game-as-a-database-489c 수집일: 2026-03-30 06:17:56