Every year, Atomic Object hosts the Atomic Games — a hackathon where college students develop game agents for two-player games, ranging from real-time strategy (RTS) to classic board games. The competitive environment challenges participants to create bots that interact with both the game server and their opponents’ bots. The runtime of each game involves three key processes: Player 1’s bot, Player 2’s bot, and the game server.
To enhance the development experience and facilitate rapid testing and iteration of these game agents, implementing a robust CI/CD pipeline is essential. Here, I’ll walk you through setting up a GitHub Actions workflow that automates building your bot, running the server and both bots using Docker Compose, and publishing the game results as pipeline outcomes.
Why Automate with GitHub Actions and Docker Compose?
Manual testing can be time-consuming and error-prone, especially when dealing with multiple components like game servers and bots. Automating this process ensures consistency, saves time, and provides immediate feedback on the performance and compatibility of your bots within the game environment. An advantage of using Docker Compose is that it will allow you to use the same process to test locally as used in Github. If you opt not to use Docker Compose, you may want to look at Act for testing Github Action workflows locally.
Step-by-Step Guide to Setting Up the Workflow
1. Repository Setup
Ensure your project repository is structured to include separate directories for the server and each bot. For example:
/project-root
│
├── server/
└── sdks/
│
├── ruby/
└── python/
2. Create a Docker Compose File
Docker Compose simplifies the orchestration of multiple containers. Create a docker-compose.yml
file at the root of your repository:
version: '3.8'
version: '3.8'
services:
p1:
build:
context: sdks/ruby
dockerfile: Dockerfile
networks:
- game-network
p2:
build:
context: sdks/ruby
dockerfile: Dockerfile
depends_on:
- p1
networks:
- game-network
server:
build:
context: server
dockerfile: Dockerfile
ports:
- "9090:9090"
depends_on:
- p2
networks:
- game-network
networks:
game-network:
driver: bridge
This configuration builds and runs the server and both bots as separate services.
3. Define the GitHub Actions Workflow
Create a workflow file at .github/workflows/ci.yml
:
name: CI Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Docker Compose Services
run: docker compose build
- name: Run Docker Compose
run: docker compose up server
- name: Collect Test Results
uses: actions/upload-artifact@v3
with:
name: game-results
path: ./server/results/
- name: Tear Down Docker Compose
if: always()
run: docker compose down
4. Building the Bots and Server
Ensure each service (server
, bot1
, bot2
) has a corresponding Dockerfile
that defines how to build the image. For example, in sdks/python/Dockerfile
:
FROM python:3
WORKDIR /usr/src/app
COPY . .
EXPOSE 9090
CMD [ "python", "./client.py", "9090" ]
Repeat similar setups for the server and bot2. Important note, while using the Docker Compose network, the server will refer to the players base IP:Port based on the service name, i.e.:
CMD [ "python", "./server.py", "9090" "-p1ip", "p1", "-p1port", "9090", "-p2ip", "p2", "-p2port", "9090" ]
This example is particularly simple, but if you start producing more complicated Docker images spending time optimizing Docker Layers may benefit your application.
5. Running the Server and Bots
The docker compose up server
command starts all services because the server
depends on p2
, and p2
depends on p1
. The server is coded to terminate with the outcome of the game as a result.
6. Publishing Results
The actions/upload-artifact
step uploads the game results, making them accessible via the GitHub Actions interface.
7. Cleaning Up
The docker compose down
command stops and removes all containers, networks, and volumes created by docker compose up
. Using if: always()
ensures that cleanup occurs regardless of the previous steps’ success or failure.
Integrating GitHub Actions with Docker Compose
Integrating GitHub Actions with Docker Compose provides a streamlined and automated approach to developing, testing, and iterating on game agents for Atomic Games. This setup not only enhances efficiency but also ensures that your bots are consistently evaluated in a controlled environment, leading to more robust and competitive entries in the hackathon. Implementing such workflows empowers participants to focus on refining their strategies and improving their bots’ performance, ultimately contributing to the success of Atomic Games.