CoOp:Anything
게임을 검색해 협동 플레이 가능한 모드·단계·품질·커뮤니티 평점을 확인합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Two-player cribbage to 121 against a strong computer opponent, with discard hints and every count explained.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Two-player cribbage to 121 against a computer opponent that actually plays well: it searches every discard, weighs the crib, and thinks about what you might peg back.
▶ Open the live app · works on phone and laptop
_online kit, on since 2026-10-27).Plain HTML, CSS and JavaScript with no build step, hosted on GitHub Pages; the rules engine and AI are tested with plain Node scripts. Online play is written for Firebase (free plan).
The important idea is that one pure engine runs behind two transports.
js/engine.js is the rules — it never touches the DOM, the network, a timer,
or Math.random except through an injected rng. Everything else plugs into
it:
┌── local.js → ai.js playing the computer:
│ the engine runs in your tab
engine.js ───────┤
(pure rules) └── online.js → Firestore playing a person: the engine
▲ runs in the HOST's tab
│
app.js / ui.js identical for both
Both transports publish the same game:state event and accept the same
submit(action) call, so js/app.js renders an online game and a solo game
with exactly the same code and has no idea which it is driving.
| File | What it is |
|---|---|
js/score.js | Scoring. Fifteens, pairs, runs, flushes, nobs, pegging. Pure, and Node-requireable. |
js/engine.js | The game state machine: deal → discard → cut → play → show → 121. |
js/ai.js | The computer opponent. Exact discard search, one-ply pegging risk model. |
js/crib-ev.js | Generated. Expected crib value for all 169 possible throws. |
js/local.js | Transport: vs the computer. |
js/online.js | Transport: vs a person, over Firestore. |
js/ui.js / js/app.js | Rendering, and turning clicks into actions. |
firestore.rules | The entire security boundary — there is no server. |
tools/build-crib-ev.js | Regenerates the crib table. Only needed if the model changes. |
It is static. Open index.html, or serve the folder:
python -m http.server 8777
node test/score.test.js # scoring: the 29 hand, runs, the crib flush rule
node test/engine.test.js # pegging edge cases, go/last-card, stopping at 121
node test/ai.test.js # the AI, plus a 900-game tournament between levels
No framework — they are plain Node scripts that print what they checked and exit non-zero on failure. The engine test plays 300 complete games with random legal moves to prove the pegging phase can't deadlock; the AI test plays 900 more to prove the difficulty levels are actually different (hard beats easy about 78% of the time, and medium about 56%).
The security rules have their own suite, which needs the Firestore emulator:
cd test && npm install && npm test
Known problem: the emulator currently refuses to start on this machine — it exits immediately, silently, with code
-1, both here and in the sibling Secret Hitler project. This looks like the long-standing incompatibility between the bundled emulator jar and Java 21; the usual fix is a JDK 17JAVA_HOME, orfirebase setup:emulators:firestoreto pull a newer jar.test/rules.test.jsis written and complete but has not been executed.
Two decisions, handled quite differently.
The discard is searched exactly. For each of the 15 ways to break up six cards it evaluates the kept hand against every one of the 46 starters that could still be cut, then adds what the throw is worth in the crib — plus when the crib is yours, minus when it isn't. That second term is why the same six cards produce opposite decisions depending on who deals: holding 5-5-6-7-8-9, it throws itself the two fives as dealer, and throws 8-9 as non-dealer.
The crib figure comes from js/crib-ev.js, sampled once by
tools/build-crib-ev.js (30,000 deals per entry) rather than computed live,
because doing it honestly at decision time is ~700,000 hand evaluations and a
visible freeze. The numbers line up with published cribbage tables — 5-5 is the
best throw at 8.83, K-10 the worst at 3.16.
The pegging is a one-ply risk model: for each legal card, what it scores now, minus the average of what every card the opponent could still be holding would score in reply. Nobody wrote down "don't lead a five" — it falls out of sixteen ten-cards answering it for two.
Difficulty is how much of that the opponent uses, not a handicap:
| discard | pegging | |
|---|---|---|
| Easy | judges the kept hand as it stands, ignores the starter and the crib, and wavers | takes what's in front of it |
| Medium | exact hand search, crib ignored | avoids the obvious traps |
| Hard | hand and crib | full risk model, plays for the win when it's in reach |
2026-10-27: this section describes the old, never-switched-on design. Live online play now uses the shared
_onlinekit (js/game-online.js+js/crib-online.js+js/net.js, Firestoreth-board-games, friend rooms only). See PROGRESS.md.
Matchmaking, room codes, and live play with no server at all.
Finding a stranger. You put yourself in a public queue and look for someone else in it. The race — two people searching at the same instant, each finding the other, each starting a game — is settled by a Firestore transaction that claims the other player's queue entry and your own atomically. Whoever commits first becomes the host; the loser's retry finds both entries taken and simply waits to be collected.
Playing a friend. The same flow with the search removed: a six-character
room code (from an alphabet with no O/0 or I/1, because these get read
aloud) stands in for the queue entry.
During the game the host's browser is the referee. It runs the engine,
writes the public state to the match document, writes each player's cards to a
document only that player can read, and drains a queue of submitted moves.
Illegal moves are dropped rather than applied. If the host reloads, the full
state is in matches/{id}/host/state and the game resumes.
A determined host can see your cards. Somebody's browser has to hold the
deck, and on Firebase's free plan there is no server to be that somebody —
Cloud Functions need the paid plan, and this project does not use them. The
security rules make sure nothing is ever served to the wrong player, and
test/rules.test.js exists to keep them honest, but the host's own tab
necessarily holds the whole deal and devtools will show it.
For playing friends this is fine. It is also why engine.js is a pure module
with no dependencies: an authoritative referee can be dropped in later without
rewriting the game.
Until js/firebase-config.js has real values, Online.ready is false, the
menu says so, and everything else still works. To turn it on:
nam5
matches the sibling projects.timothyhadfield.github.io.firebase apps:sdkconfig WEB --project <your-project-id>
into js/firebase-config.js, replacing const FIREBASE_CONFIG = null;.firebase deploy --only firestore:rules --project <your-project-id>
No indexes are needed. The queue is read with a single orderBy and filtered
client-side precisely so that no composite index has to be created.
Spark gives 50,000 document reads and 20,000 writes a day. A complete game is roughly 200 writes and 400 reads across both players, so the free tier covers something like fifty games a day without noticing.
The ones that are usually got wrong:
length × the number of ways to make it, not as named cases.