OhMyGame
OhMyGame은 AI로 게임을 만들 수 있는 오픈소스 스튜디오로, 디자인·구축·플레이테스트·출판을 하나의 앱에서 모델 키와 함께 수행합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Enter the bans and picks from a Mobile Legends draft (10 bans, 5 enemy picks, 5 team picks) and get a recommended last pick or two. The analysis is done by DeepSeek on the server, using the current patch data in data/context.json. The API key never reaches the browser.
npm install
cp .env.example .env.local # then put your real DeepSeek key in DEEPSEEK_API_KEY
npm run dev
Until the key is replaced, the app loads but the analysis returns "The DeepSeek API key is not set on the server."
DEEPSEEK_API_KEY with the real key. Add APP_PASSCODE if you want the page limited to your team.When the account settings below are all present, the AI coach needs Google sign-in. Without them the site works as before (open, with the optional passcode).
app/api/auth/*). Only verified Google emails are accepted. The session is an httpOnly, signed cookie that lasts 30 days.OWNER_EMAIL) is always free and unlimited; emails on the free list are free within the fair-use limits; accounts with an active Coach plan; otherwise each account gets 2 free analyses (reserved atomically, and given back if an analysis fails). After that the API answers 402 with the current price.POST /api/picked)./account): plan, free analyses left, "Download my data" (JSON of the profile, drafts, picks, feedback and payments) and "Delete my account" (type DELETE; the profile goes, drafts stay only as anonymous statistics without notes, payment amounts stay without the email)./admin, owner only, 404 for everyone else): profit by month (revenue − payment fees − AI cost − fixed costs), the Coach price (changes always start on the 1st of next month, Malaysia time), DeepSeek balance and usage, the AI on/off switch, the monthly AI budget and fair-use limits, the free-email list, accounts (with "record a payment" for bank transfers until online payments are connected) and picks data.| Variable | What it is |
|---|---|
DATABASE_URL | Postgres connection string. Set automatically when you connect a Neon database in Vercel > Storage. Tables are created on first use. |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | From a Google Cloud OAuth client (Web application) with the redirect URI https://<your-site>/api/auth/callback/google. |
AUTH_SECRET | A random string of at least 32 characters that signs the session cookies. |
OWNER_EMAIL | The only account that can open /admin. |
APP_URL | The public site address, e.g. https://mlbb-draft-coach-six.vercel.app. |
If some of these are set but not all (or AUTH_SECRET is shorter than 32 characters), the AI coach refuses to run instead of opening to everyone, and the server log says what is missing.
All are environment variables, listed with defaults in .env.example: DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL (default deepseek-flash), APP_PASSCODE, RATE_LIMIT_PER_10MIN.
The app uses one fast model with DeepSeek's "thinking" mode turned off, because draft decisions are made in seconds. An answer usually takes about 4 seconds.
data/meta.json holds pro league stats that drive the suggestions. A GitHub Action (.github/workflows/update-meta.yml) rebuilds it every Tuesday at 08:17 Malaysia/Philippine time, after the weekend matches, and whenever the pipeline or its settings change:
scripts/update-meta.mjs reads the MPL Indonesia, Philippines and Malaysia Season 18 pages through the Liquipedia API (identifying User-Agent, gzip, several pages per request, at least 2.5 seconds between requests). Each game gives both teams' picks, bans, sides and the winner. The three leagues are pooled for the hero stats.scripts/meta/build.mjs infers it: every lineup must fill all five positions once, and each hero's likely positions are learned across all lineups, starting from its roles. Each lineup gets a confidence; corrections go in data/position-overrides.json.public/teams/<team id>.png and remembers the source file in data/team-logos.json, so a logo is downloaded again only when the team changes it. A team without a logo shows its initials.After each run, scripts/check-health.mjs looks for things a person has to fix, and the job emails the owner when the run failed or a check found something:
data/context.json is more than 7 days old,teamPages in data/meta-config.json, or leave the team out of the picker with hideTeams).Liquipedia asks API clients to put contact details in their User-Agent. Add a repository secret LIQUIPEDIA_CONTACT (for example an email address) and the job includes it; without it the job identifies itself by the repository link only. Logos are looked up only for teams that don't have one yet, and for every team in the first week of each month.
Email needs three repository secrets (Settings > Secrets and variables > Actions): SMTP_USER (the Gmail address that sends), SMTP_PASSWORD (a Gmail app password for it) and ALERT_EMAIL (where alerts go). Without them the run shows a warning instead.
Settings (leagues and their pages, half-life, team page overrides) are in data/meta-config.json. When a new season starts, change each league's name and pages. The inferred lineups are in data/pro-games.json. Data from Liquipedia is licensed CC BY-SA 3.0 and credited on the results page. Team logos belong to their teams and are shown only to identify them.
Above the bans, players can pick a pro team in two dropdowns: first the league (Malaysia, Indonesia or Philippines, each with its flag), then one of its teams (with its logo and record). The choice is optional, remembered in that browser, and can be set in a link with ?team=<team id>, e.g. ?team=ph-teamliquidph. Without a team, no team gets extra weight.
With a team chosen, 30% of each candidate's score comes from that team (team.weight in data/scoring.json): mostly how often it plays the hero in that position compared with its most played hero there, and partly its record with it. The other four parts share the remaining 70%. The prompt tells the model which team the player drafts like, to prefer that team's heroes when candidates are close, and not to pass over a clear counter just because the team hasn't played it. Each option's evidence shows the team's record with the hero, and heroes the team played there at least twice get a badge with its logo.
For each open position the server scores every available hero that pros play there (0 to 100) and keeps the best 12, with your coach's counter rules always included and listed first. The score, set in data/scoring.json, is:
| Part | Weight | What it measures |
|---|---|---|
| Popularity | 35% | How much pros in MPL ID, PH and MY play the hero in that position, recent games counting more |
| Win rate | 20% | Its pro win rate there, pulled towards 50% when the sample is small |
| Counter | 30% | How its team did in pro games against the heroes the enemy has picked now |
| Synergy | 15% | How it did alongside your current picks |
Every team's games count the same. When the player picks a pro team (see above), that team's part takes 30% and these four share the other 70%. DeepSeek ranks 3 options per open position from that shortlist, starting from the top, so a player can choose the one they play best. Each option shows its score and pro evidence. When pro data is missing, the shortlist falls back to the tier list.
POST /api/analyze checks the passcode and rate limit, then validates the draft: bans and enemyPicks are lists, teamPicks is an object keyed by position (roam, marksman, jungler, mage, fighter); every hero must be in the roster and appear once.lib/prompt.ts builds the prompt from the patch notes, roster with tiers, pro draft signals and the draft itself.lib/deepseek.ts calls DeepSeek in JSON mode.lib/draft.ts checks every option against the roster, the draft and that position's shortlist, and expects 3 options per open position. If an option is invented, banned, already picked, off the shortlist, or a position has fewer than 3, the server asks once more with the problems listed, then drops anything still invalid..github/workflows/ci.yml: typecheck, build, the pro-data pipeline tests, the end-to-end tests (stand-in DeepSeek) and the sign-in/trials/payments tests (a Postgres service and stand-in Google). Nothing real is called..github/workflows/uptime.yml opens the site and /api/health every hour and emails the owner (same SMTP secrets as the data job) when either is down or the AI coach is paused. With the same random HEALTH_TOKEN set in Vercel and as a repository secret, it also emails once a day (09:41 Malaysia time) while this month's AI spend is past 50% of the budget or on pace to pass it; admin > API usage shows the same as a bar with the month-end projection. /api/health checks the AI key, sign-in settings and database, and never calls the AI. Set the SITE_URL repository variable if the address changes.DEEPSEEK_API_KEY repository secret). It replays recent pro games with one of the winning team's picks hidden and reports how often the coach's 3 options include what the pros picked, with and without drafting like that team. Locally: APP_URL=http://127.0.0.1:3000 node scripts/eval-ai.mjs --cases 20 [--with-team] against an app started with a real key. The games are also in the coach's pro data, so compare runs rather than reading the rates as absolute accuracy.The site is a progressive web app: people can install it from the "Install app" button (Android, Chrome and Edge on desktop) or with Share > Add to Home Screen on iPhone and iPad, and it opens full screen with its own icon.
app/manifest.ts: name, colours, start page and icons (public/icons/, made from the shield in app/icon.svg).public/sw.js: the service worker. Pages always come from the network (they show who is signed in) with public/offline.html as the fallback; build files, icons, team logos and fonts are cached; /api/ and /admin are never cached. Change VERSION in it to clear old caches.components/InstallApp.tsx: registers the service worker and shows the install button.The page fills its boxes from the link, so you only click Analyze draft:
https://mlbb-draft-coach-six.vercel.app/?bans=Hirara,Freya,Kaja&enemy=Tigreal,Gloo,Clint&marksman=Hanabi&jungler=Fanny&mage=Faramis&fighter=Yu%20Zhong
bans (up to 10) and enemy (up to 5) are comma-separated hero names.roam, marksman, jungler, mage, fighter, one hero each. Leave a position out to mark it open.team (optional) chooses the pro team to draft like, by its id (e.g. ph-teamliquidph).To build and open that link for you (needs Node, nothing to install):
node scripts/open-draft.mjs tests/drafts/gloo-faramis-last-pick.json # a saved draft
node scripts/open-draft.mjs --bans "Hirara,Freya" --enemy "Gloo,Clint" --marksman Hanabi --jungler Fanny --mage Faramis --fighter "Yu Zhong"
node scripts/open-draft.mjs --print tests/drafts/gloo-faramis-last-pick.json # only print the link
Our picks have five fixed positions: Roam, Marksman (gold lane), Jungler, Mage (mid lane) and Fighter (EXP lane). Empty positions are the ones the analysis fills: one hero per open position, or the two that matter most when three or more are open.
A hero fits a position when pros play it there (at least 3 games or 15% of its pro picks). Heroes with fewer than 3 pro picks fall back to their lanes in data/context.json, then to role (Roam: Tank or Support; Marksman: Marksman; Mage: Mage; Fighter: Fighter or Tank; Jungler: Assassin, Fighter or Tank). Hero roles come from mlbbhub.com.
data/draft-rules.json holds draft knowledge the model must respect. Edit it on GitHub and the site redeploys in about 30 seconds.
{
"counters": [
{ "enemy": "Gloo", "picks": ["Vexana", "Faramis"], "why": "" }
],
"principles": [
"Always secure a frontline before the last pick."
]
}
enemy, the prompt tells the model to pick one of picks (skipping any that are banned or taken, and skipping the rule if your team already has one). If the model's picks ignore every applicable rule, the server asks once more. why is optional and is passed to the model as the reason.Hero names must match the roster (case and spacing do not matter). The results page shows which rules applied.
data/context.json is a snapshot (patch 2.2.16, Season 42, as of 2026-10-31). The page warns when it is more than 7 days old. getContext() in lib/context.ts is the single place to change when the data comes from a daily refresh job instead of this file.
node scripts/mock-deepseek.mjs & # stand-in for the DeepSeek API
DEEPSEEK_API_KEY=test-key DEEPSEEK_BASE_URL=http://127.0.0.1:4010 RATE_LIMIT_PER_10MIN=60 npm start -- -p 3100 &
npm test # runs scripts/smoke.mjs against it
Accounts (needs a local Postgres on port 5544):
node scripts/mock-google.mjs & # stand-in for Google sign-in
DATABASE_URL=postgres://[email protected]:5544/postgres \
GOOGLE_CLIENT_ID=test-google-client GOOGLE_CLIENT_SECRET=test-google-secret \
AUTH_SECRET=test-secret-0123456789-abcdefghijklmnop-xyz [email protected] APP_URL=http://127.0.0.1:3200 \
GOOGLE_AUTH_URL=http://127.0.0.1:4020/auth GOOGLE_TOKEN_URL=http://127.0.0.1:4020/token GOOGLE_USERINFO_URL=http://127.0.0.1:4020/userinfo \
DEEPSEEK_API_KEY=test-key DEEPSEEK_BASE_URL=http://127.0.0.1:4010 RATE_LIMIT_PER_10MIN=500 npm start -- -p 3200 &
npm run test:accounts # wipes the test database tables first