dev.to

I Was Overwhelmed, So I Turned My GitHub Profile Into a Roguelike Dungeon

할 일이 막막해 GitHub 프로필을 로그라이크 던전으로 만들었습니다

C#/.NET 개발자가 GitHub 기여 기록과 저장소 정보를 로그라이크 던전 형태의 프로필로 구현했습니다. SVG 조각을 README에 쌓고 GitHub Actions로 매일 갱신하며, 캐시 문제와 GraphQL 데이터 불일치도 해결한 과정을 소개합니다.

AI 요약

할 일과 이슈가 쌓여 어디서 시작할지 막막했던 글쓴이는 다른 개발자의 사이버펑크풍 GitHub 프로필에서 영감을 얻었습니다. C#/.NET 게임 개발자인 글쓴이는 도시 대신 던전을 만들기로 했고, 프로필 제작을 시작하면서 미뤄 둔 일에도 다시 손을 댈 수 있었다고 설명합니다. 원작 아이디어는 여러 SVG 이미지를 이어 하나의 콘솔처럼 보이게 만들고 GitHub Actions로 매일 다시 그리는 방식입니다. 글쓴이는 이를 로그라이크 테마와 .NET 생성기로 구현했습니다.

프로필 구성과 SVG 조각

프로필은 번호가 붙은 여러 구역을 하나의 복고풍 터미널처럼 배치합니다. 시작 화면에는 타이핑 애니메이션이 나오고, 최근 365일 기여 기록은 돌벽과 빛나는 바닥 타일, 보물 상자 등으로 표현합니다. 현재 연속 기여 기록을 나타내는 작은 영웅도 던전을 걷습니다. 밤하늘에는 별과 달, 성, 던전 입구를 그렸습니다.

README 안의 이미지는 마우스 입력에 반응하지 않으므로, 던전 이미지를 누르면 GitHub Pages의 대화형 페이지로 이동하게 했습니다. 그곳에서는 타일에 마우스를 올려 해당 날짜의 기여 수를 확인합니다. 프로그래밍 언어와 저장소 통계는 배낭과 캐릭터 능력치 창으로, 고정 저장소는 상태를 표시하는 퀘스트 카드로 보여줍니다. 다른 사람의 오픈소스 저장소에 기여한 경우에는 CONTRIBUTOR 배지와 역할, 병합된 PR 수를 카드에 넣습니다. 소개와 학습 중인 내용, 연락처 링크도 RPG 캐릭터 시트와 버튼 형태로 구성했습니다.

GitHub README는 JavaScript와 대부분의 CSS를 제거하지만, <img> 태그로 불러오는 SVG 안에는 CSS 애니메이션을 넣을 수 있습니다. 글쓴이는 이를 이용해 커서 깜박임, 버튼 효과, 걷는 영웅을 구현했습니다. 여러 이미지를 하나의 화면처럼 보이게 하려면 각 조각의 폭을 960픽셀로 통일하고 높이는 40픽셀의 배수로 맞춰야 합니다. 그래야 배경 격자가 조각 사이에서 이어집니다. 좌우 테두리도 같은 x 좌표에 그립니다. 퀘스트 카드와 버튼은 README에서 이미지 전체에만 링크를 걸 수 있어 각각 별도 이미지로 만들었습니다.

.NET 생성기와 데이터 처리

생성기는 tools/DungeonGen에 둔 .NET 10 콘솔 앱입니다. GraphQL과 기여 달력을 읽는 GitHub.cs, 던전 지도를 그리는 DungeonRenderer.cs, 밤하늘과 인벤토리, 퀘스트 카드, 캐릭터 시트를 만드는 렌더러 등으로 나눴습니다. 각 렌더러는 별도 라이브러리 없이 C# 문자열을 조합해 SVG를 생성합니다. 소개 문구는 about.json에서 관리하므로 코드를 수정하지 않고도 프로필 내용을 바꿀 수 있습니다.

기여 달력의 하루는 14픽셀 타일 하나로 나타냅니다. 기여가 없으면 돌벽을, 기여가 늘면 더 밝은 바닥을 표시하고, 가장 바쁜 날에는 보물 상자를 그립니다. 최고 기록에는 금색 테두리를 씌웁니다. GitHub Actions가 매일 생성기를 실행하고 결과가 바뀐 경우에만 커밋합니다. workflow_dispatch로 수동 실행도 지원하며, 생성기 파일이 바뀌었을 때도 실행하도록 설정했습니다.

구현 과정에서 고친 문제

GitHub GraphQL API는 토큰이 비공개 기여 기록을 볼 수 있을 때만 그 기록을 집계합니다. 프로필 그래프에 비공개 기여 표시를 켰을 때 보이는 수치와 생성기 결과가 달라질 수 있어, 생성기는 프로필 페이지와 같은 기여 달력을 우선 읽고 API를 대체 경로로 사용하게 바꿨습니다.

언어 순위는 저장소별 코드 바이트 수가 아니라 해당 연도 커밋 수를 기준으로 매깁니다. 코드가 큰 저장소 하나가 순위를 독차지하는 문제를 피하기 위해서입니다. 소유하지 않은 오픈소스 저장소의 기여도 보여주려고 별칭을 지정한 GraphQL 검색으로 PR 수와 병합 수를 집계합니다. 조직 한 곳에서 토큰 접근을 막았을 때는 GraphQL이 오류와 함께 일부 데이터를 반환할 수 있다는 점을 반영했습니다. 오류를 경고로 기록하고 받은 데이터로 계속 진행합니다.

SVG의 세로선을 빛 효과 필터와 함께 <line>으로 그렸더니 선의 경계 상자 폭이 0이 되어 필터 영역이 비어 버렸습니다. 선을 폭 3픽셀의 <rect>로 바꾸자 문제가 해결됐습니다. README 이미지가 갱신되지 않는 문제는 캐시 때문이었습니다. 상대 경로 이미지는 GitHub에서 raw.githubusercontent.com으로 리디렉션되고, 응답의 Cache-Control 값은 max-age=300입니다. 브라우저도 이전 이미지를 더 오래 보여줄 수 있습니다. 생성기는 각 SVG의 해시를 URL에 쿼리 매개변수로 붙입니다. 내용이 바뀌면 URL도 바뀌어 새 이미지를 가져오고, 바뀌지 않으면 해시가 유지되어 불필요한 커밋을 만들지 않습니다.

글쓴이는 첫 SVG 조각을 완성한 뒤 작업을 이어 갈 추진력을 얻었다고 말합니다. 이 프로젝트는 GitHub 프로필을 개인 취향에 맞게 꾸미는 구현 사례이면서, SVG와 GitHub Actions, GraphQL을 조합해 데이터를 자동으로 시각화한 과정도 담고 있습니다.

dev.to 반응

  • @georgekobaidze — 정말 멋집니다! 언급해 줘서 고마워요. 제 아이디어가 이런 멋진 작업에 영감을 줬다니 정말 기쁩니다! 🤩
    • @g00ds0ul — 고마워요. 솔직히 당신 글이 딱 좋은 시기에 올라왔어요. 여러 조각을 쌓는 아이디어는 정말 기발했어요. 계속 멋진 작업을 해 주세요. 🙌🏽
  • @kyisaiah47 — SVG URL을 그대로 둔 채 매일 이미지를 다시 만들면 캐시가 어떻게 동작하던가요?
    • @g00ds0ul — URL이 그대로일 때 저장소에는 새 SVG가 올라가도 강력 새로고침을 하기 전까지 프로필에는 예전 이미지가 보였어요. 프로필 README의 상대 경로 이미지는 github.com///raw/main/...에서 제공된 뒤 raw.githubusercontent.com으로 리디렉션됩니다. 응답에 Cache-Control: max-age=300이 붙어서 CDN과 브라우저가 모두 예전 이미지를 재사용할 수 있어요. 페이지에 다시 돌아왔을 때 브라우저가 이미지를 더 오래 유지하기도 하고요. 그래서 렌더링 뒤 SVG마다 해시를 계산해 README의 주소를 dungeon.svg?v=... 형태로 고칩니다. 내용이 바뀌면 URL도 달라져 브라우저가 새 이미지를 가져옵니다. 내용이 그대로면 해시도 같고 workflow가 커밋을 만들지 않아 불필요한 커밋이 생기지 않아요.
  • @digitalgh0st — 🖤🖤
    • @g00ds0ul — 🖤🗡️

원문: dev.to / 번역·요약: Trawling