- Go 1.21 or higher
- Basic understanding of JavaScript
- Familiarity with REST APIs
- Clone and build:
git clone https://github.com/frog-software/CardGames.git
cd CardGames
go mod download
go build -o cardgames- Run tests:
./test.sh- Start development server:
./cardgames serve --devThe --dev flag enables:
- SQL query logging
- Detailed error messages
- Auto-reload (with tools like Air)
All game concepts are database records:
- Rules →
game_rulesrecords - Rooms →
tablesrecords - Game State →
game_statesrecords - Actions →
game_actionsrecords
This enables:
- Complete event replay
- Easy debugging
- Natural state management
Player Action
↓
API Request
↓
Validate (JS logic)
↓
Create game_action record
↓
Update game_state
↓
Broadcast via WebSocket
↓
All Clients Updated
Create game_logics/my_game.js:
/**
* Initialize game state
*/
function initializeGame(config, playerIds) {
// Create deck, shuffle, distribute cards
return {
player_hands: {...},
deck: [...],
discard_pile: [],
current_player_turn: playerIds[0],
player_melds: {},
last_play: null,
game_specific_data: {}
};
}
/**
* Validate play action
*/
function validatePlay_cards(config, gameState, playerId, actionData) {
// Check if it's player's turn
if (gameState.current_player_turn !== playerId) {
return { valid: false, message: "Not your turn" };
}
// Validate the cards exist in hand
// ... your validation logic ...
return { valid: true, message: "Valid play" };
}
/**
* Apply play action to state
*/
function applyPlay_cards(config, gameState, playerId, actionData) {
const newState = { ...gameState };
// Remove cards from hand
// Add to discard pile
// Update turn
// ... your state update logic ...
return newState;
}
// Implement validate* and apply* for each action typeVia PocketBase Admin UI:
- Go to
http://localhost:8090/_/ - Navigate to
game_rulescollection - Create new record:
- name: "My Game"
- description: "Description of the game"
- logic_file: "my_game.js"
- config_json:
{ "meta": { "player_count": { "min": 2, "max": 4 } }, "custom_data": { "your_game_specific": "configuration" } }
- Create a table with your new game rule
- Add players
- Test actions through API calls
Every game must implement:
- Purpose: Set up initial game state
- Returns: Initial
game_stateobject - Called: When game starts
- Purpose: Validate if action is legal
- Returns:
{ valid: boolean, message: string } - Example:
validatePlay_cards,validateDraw
- Purpose: Apply action to state
- Returns: Updated
game_stateobject - Example:
applyPlay_cards,applyDraw
Action type play_cards requires:
validatePlay_cards(...)applyPlay_cards(...)
Note: Underscores in action type, camelCase after "validate"/"apply"
if (gameState.current_player_turn !== playerId) {
return { valid: false, message: "Not your turn" };
}const playerHand = gameState.player_hands[playerId];
const hasCard = playerHand.some(c =>
c.suit === card.suit && c.rank === card.rank
);const players = Object.keys(gameState.player_hands);
const currentIndex = players.indexOf(playerId);
const nextIndex = (currentIndex + 1) % players.length;
newState.current_player_turn = players[nextIndex];// Get all tables
tables, err := app.FindRecordsByFilter(
"tables",
"status = 'waiting'",
"-created",
100,
0,
)
// Get single record
table, err := app.FindRecordById("tables", tableId)
// Get with filter
rule, err := app.FindFirstRecordByFilter(
"game_rules",
"name = 'Four Color Card'",
)collection, err := app.FindCollectionByNameOrId("tables")
record := core.NewRecord(collection)
record.Set("name", "New Game")
record.Set("owner", userId)
record.Set("status", "waiting")
err := app.Save(record)table, err := app.FindRecordById("tables", tableId)
players := table.GetStringSlice("players")
players = append(players, newPlayerId)
table.Set("players", players)
err := app.Save(table)- Start server:
./cardgames serve --dev-
Create admin account at
http://localhost:8090/_/ -
Use admin UI to:
- View collections
- Create test data
- Monitor real-time updates
# Login
curl -X POST http://localhost:8090/api/collections/users/auth-with-password \
-H "Content-Type: application/json" \
-d '{"identity":"user@example.com","password":"password123"}'
# Create table
curl -X POST http://localhost:8090/api/collections/tables/records \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"name":"Test Game","rule":"RULE_ID","owner":"USER_ID","status":"waiting"}'./cardgames serve --devThis shows all SQL queries and API calls.
sqlite3 pb_data/data.db "SELECT * FROM tables"Collections not created:
- Check server logs for errors
- Verify
initializeCollectionsruns on serve
JavaScript errors:
- Check function names match action types
- Verify all required functions exist
- Use
console.login JS (appears in Go stdout)
Action validation fails:
- Check player turn
- Verify cards exist in hand
- Review game state structure
- Follow standard Go conventions
- Use
gofmtfor formatting - Keep functions focused and small
- Use descriptive variable names
- Comment complex logic
- Return explicit validation results
- Fork the repository
- Create feature branch
- Add tests if applicable
- Submit pull request
- Create an issue on GitHub
- Check existing issues for solutions
- Review the API documentation