CoOp:Anything
게임을 검색해 협동 플레이 가능한 모드·단계·품질·커뮤니티 평점을 확인합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A browser party game for friends: everyone invents a fake answer to a trivia question, then tries to spot the real one among the bluffs. Play online with friends anywhere: one person creates a room and shares the 5-character code or the invite link, and everyone else joins from their phone or computer. It also runs on your own computer for players on the same Wi-Fi. Arabic-first (RTL) interface with an English mode.
Play online: kalaktn.pages.dev
Online, the game runs on Cloudflare Workers with one Durable Object per room; locally it runs on Node.js (npm start). Both use the same game code over plain WebSockets.
Inspired by the Arabic party game Kalak and the "fibbing" genre of games such as Fibbage. This is an independent fan-made project for learning; it is not affiliated with the original game.
Screenshots from a local 3-player session (three separate browser sessions on one machine), with the room set to English questions.
If you have never played a Kalak/Fibbage-style game, here is the idea:
Scoring
| Event | Points |
|---|---|
| You vote for the real answer | +2 |
| Another player votes for your bluff | +1 per player fooled |
You can't vote for your own bluff (it is greyed out). If you type the real answer by accident, the game tells you and lets you write a fake one instead. If two players write the same bluff (ignoring case, spacing and accents/harakat), it appears once and both authors get the points.
After the last round, a podium shows the top three (players with equal scores share a place) and the host can start a new game with the same group.
Host tools
During a game, the host (👑) has a bar at the bottom of the screen:
| Button | What it does |
|---|---|
| Pause / Resume | Stops the timer for everyone. All players see "Paused by the host" until the host taps Resume. |
| Skip | Ends the current step now: picks a random topic, or ends the writing or voting time. |
| Rounds | Adds 1 or 3 more rounds (30 at most). Everyone sees the new total. |
| End | After a confirm, ends the game and shows the final scores to everyone. |
The host is also a player, so these tools never show them any answers or bluffs early. If the host leaves, the next player becomes host and gets the bar. In the lobby, the host can also remove a player.
0/O or 1/I) and an invite link (/?room=CODE) shown in the lobby with a Copy link button. Opening the link goes straight to the join form with the code filled in.The questions are a hand-written, hard-coded list in lib/questions.js (the QUESTIONS object). There is no external trivia API or database. There are 13 categories × 25 questions = 325 questions, each stored in Arabic and English (q/a and qE/aE), so the host can switch the language of the questions. Each question has exactly one clean answer per language (the one shown in the vote list); other accepted spellings go in alt / altE and are only used to detect a player typing the real answer. A question is never asked twice in the same game: when the chosen topic runs out, another enabled topic is used, and when every enabled topic is used up the game ends normally with the podium.
Categories: General, Geography, History, Science, Sports, Entertainment, Technology, Food, Islam, Arab world, Literature, Animals, Flags.
phones / browsers (public/) one of two hosts, same game code
┌──────────────────────────┐ ┌───────────────────────────────────────┐
│ index.html (screens) │ POST │ Node (server.js) Cloudflare │
│ game.js (UI + events)│ /api/rooms │ Express static (worker/index.js) │
│ net.js (WebSocket, │ ─────────────► │ ws package Worker + assets, │
│ acks, reconnect) │ WebSocket │ Map<code, room> a Durable Object │
│ style.css │ /ws?room=CODE │ per room code │
└──────────────────────────┘ ◄────────────► │ lib/room.js: one room's game │
│ lib/logic.js: rules (pure) │
│ lib/questions.js: 325 Q/A │
└───────────────────────────────────────┘
lib/room.js owns one room and knows nothing about the transport: it takes a connection plus (event, data, ack) and replies with conn.send(...). server.js keeps a Map of rooms keyed by code (dropped once empty). On Cloudflare, the Worker routes /ws?room=CODE to the Durable Object idFromName(CODE), which runs the same module. To create a room, the browser first asks POST /api/rooms for an unused code (the room holds it for a minute), then connects to it and sends room:create.
The server is the single source of truth. Clients only send intents (create/join, answer, vote...). The server validates every payload (room codes; settings are whitelisted and clamped; text is trimmed and capped at 60 characters; votes for your own bluff are refused), advances the game and pushes the new state to everyone in the room. Messages are capped at 4 KB, unknown events and malformed frames are ignored, and every handler and timer callback is wrapped so that one bad event is logged instead of crashing the room. Correct answers are never sent to clients before the reveal. Vote options are sent as { id, text, own } only, without who wrote them.
lobby ──game:start──► picking ──category chosen / 15 s──► question
▲ ▲ │ all answered / answer timer
│ │ game:next (rounds left) ▼
│ results ◄──all voted / vote timer──── voting
│ │ game:next (last round)
└──game:restart── finished
A single server-side interval timer per room drives each phase (setTimer) and broadcasts timer:update every second. A phase also ends early as soon as every connected player has answered or voted; this is re-checked whenever someone disconnects or leaves, and if the topic picker leaves, a random topic is chosen right away.
The host tools act on the same machine. Pause stops the timer where it is and sets paused in room:state; while paused, answers, votes and the topic pick are refused and nothing moves on. Resume carries on with the time that was left. Skip sends the phase and round the host saw, so a double tap only skips once. End jumps to finished (the podium says the host ended the game). Any phase change clears the pause. Only the current host can use them; this is checked in lib/room.js, so the Node server and the Worker both get it.
Every message is JSON: { "t": event, "d": data, "id": ackId } (id only when the sender wants an answer), and answers are { "ack": ackId, "d": reply }. public/net.js wraps this in a socket.io-like emit(event, data, ack) / on(event, fn) API, pings every 25 s and reconnects with backoff.
| Direction | Event | Purpose |
|---|---|---|
| client → server | room:create, room:join, room:resume, room:leave | create / join / rejoin / leave a room (with an ack callback) |
| client → server | room:settings, room:kick | host changes settings / removes a player |
| client → server | game:start, game:next, game:restart | host controls the flow |
| client → server | host:pause, host:resume, host:skip, host:addRounds, host:endGame | host tools during a game (the ack only says yes or no, with an error code) |
| client → server | game:pickCategory | current picker chooses the topic |
| client → server | game:answer, game:vote | submit a bluff / a vote (the ack says why it was refused: real, own...) |
| client → server | chat:send | lobby chat |
| server → clients | room:state | full room snapshot (players, scores, settings, host) |
| server → clients | game:pick, game:question, game:vote, game:results, game:end | phase changes |
| server → clients | timer:update, game:answered, game:voted, chat:msg | live counters, timer, chat |
| server → clients | session:replaced, kicked | the seat was taken over by another tab / the host removed you |
A bluff equal to the real answer (or one of its accepted alternatives, in either language) is refused when it is submitted. When voting starts, bluffs that are equal once case, spacing, punctuation, accents/harakat, Arabic letter variants and leading articles are ignored are merged into one option that credits every author. The real answer and the bluffs are then shuffled (Fisher–Yates) before they are sent out.
Each browser gets a random player token stored in localStorage (kalak_token), together with the room it is in (kalak_session). The token is sent when creating or joining a room and is never shown to other players (they only see a separate public player id).
room:resume { code, token }. The server re-attaches the socket to the same seat (same score, same answer/vote if already given) and re-sends the current phase (topic pick, question, vote list, results or podium) plus the timer.Requirements: Node.js 18+ (tested with Node 22).
npm install
npm start # http://localhost:3000
To try the Cloudflare version locally (the Worker and the Durable Object run in workerd):
npx wrangler dev # http://localhost:8787
The port can be changed with the PORT environment variable:
PORT=3105 npm start # macOS / Linux / Git Bash
$env:PORT=3105; npm start # Windows PowerShell
npm test
Runs Node's built-in test runner (node:test, no test framework needed):
test/logic.test.js: unit tests for the pure rules in lib/logic.js (settings validation, answer normalisation and merging, scoring, tied ranks, question picking) and a check that every question has one clean answer.test/server.test.js: end-to-end tests that start the real server on port 3609 (override with KALAK_TEST_PORT) and play through it with a small WebSocket client (test/ws-client.js): malformed, junk and oversized messages, full rounds, reload and resume, grace-period expiry, players and the host leaving mid-game, kicks, running out of questions, invalid room codes, two rooms running side by side, empty rooms being cleaned up, and the host tools (only the host can use them, pause and resume keep the right time, skip, end game, add rounds, host hand-off, and no answers reach the host early).The server listens on all network interfaces, so phones on the same Wi-Fi can join:
npm start and find the host computer's local IP (ipconfig on Windows, ip addr / ifconfig on macOS/Linux), e.g. 192.168.1.20.http://192.168.1.20:3000.You may need to allow Node.js through the computer's firewall.
The repo is ready for Cloudflare Workers, and the free plan is enough. wrangler.jsonc serves public/ as static assets and declares the Room Durable Object (SQLite-backed, as the free plan requires).
kalak, leave the build command empty and set the deploy command to npx wrangler deploy.https://kalak.<your-subdomain>.workers.dev, and every push to main redeploys it.From your own computer instead: npx wrangler login, then npx wrangler deploy. npx wrangler deploy --dry-run checks the build without uploading anything.
.
├── server.js # Node host: Express static files, POST /api/rooms, ws at /ws?room=CODE
├── worker/
│ └── index.js # Cloudflare host: Worker (assets, /api/rooms) + Room Durable Object
├── wrangler.jsonc # Cloudflare config (assets, Durable Object binding, migration)
├── lib/
│ ├── room.js # one room: seats/reconnects, game flow, message handling (no transport)
│ ├── logic.js # pure rules: settings validation, answer matching, scoring, ranking
│ └── questions.js # question bank (Arabic + English)
├── public/
│ ├── index.html # all screens (landing, profile, lobby, pick, question, vote, results, final)
│ ├── game.js # client: translations, screens, game events, reconnect, rendering
│ ├── net.js # WebSocket client: emit/on/acks, heartbeat, reconnect with backoff
│ ├── style.css # dark, mobile-first RTL/LTR theme and animations
│ └── favicon.svg
├── test/ # node:test unit tests + WebSocket end-to-end tests
├── docs/screenshots/ # README images
└── package.json
ws locally; Cloudflare Workers + Durable Objects onlinenode:test + ws© 2026 Hamza Ben Ismail. All rights reserved.