diff --git a/.github/workflows/cov.yml b/.github/workflows/cov.yml
index b6696c6e..b40b9a9c 100644
--- a/.github/workflows/cov.yml
+++ b/.github/workflows/cov.yml
@@ -3,7 +3,13 @@ name: Coverage
on:
push:
branches: [ dev ]
- pull_request:
+ paths:
+ - 'ai/**'
+ - 'server/**'
+ - 'gui/**'
+ - 'Makefile'
+ - 'tests/**'
+ - '.github/workflows/cov.yml'
jobs:
coverage:
@@ -16,22 +22,62 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install -y lcov gcovr make gcc
+ sudo apt-get install -y lcov gcovr make gcc g++ python3 python3-pip
+ pip install coverage pytest pytest-cov
- - name: Build with coverage flags
+ - name: Clean build artifacts
+ run: make mrproper
+
+ # === Server (C) ===
+ - name: Build server with coverage flags
run: |
- make mrproper
make tests_server
- - name: Run tests
- run: ./tests_server
+ - name: Run server tests
+ run: make tests_run_server
+
+ - name: Generate server coverage
+ run: |
+ gcovr --root . --filter 'server/' --xml-pretty -o coverage-server.xml --exclude 'tests/server/'
+
+ # === GUI (C++) ===
+ - name: Build GUI with coverage flags
+ run: make tests_gui
+
+ - name: Run GUI tests
+ run: make tests_run_gui
+
+ - name: Generate GUI coverage
+ run: |
+ gcovr --root . --filter 'GUI/' --xml-pretty -o coverage-gui.xml --exclude 'tests/gui/'
+
+ # === AI (Python) ===
+ - name: Run AI tests and generate coverage
+ run: |
+ make tests_run_ai
+ coverage xml -o ../coverage-ai.xml
+
+ # === Upload All Reports to Codecov ===
+ - name: Upload coverage to Codecov (server)
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: coverage-server.xml
+ flags: server
+ fail_ci_if_error: true
- - name: Generate coverage report
- run: gcovr --root . --xml-pretty -o coverage.xml
+ - name: Upload coverage to Codecov (GUI)
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: coverage-gui.xml
+ flags: gui
+ fail_ci_if_error: true
- - name: Upload coverage to Codecov
+ - name: Upload coverage to Codecov (AI)
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
- files: coverage.xml
+ files: coverage-ai.xml
+ flags: ai
fail_ci_if_error: true
diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml
index 6e8cd399..ea2c971f 100644
--- a/.github/workflows/doc.yml
+++ b/.github/workflows/doc.yml
@@ -4,25 +4,24 @@ on:
push:
branches: [ dev ]
+permissions:
+ contents: write
+
jobs:
doc:
runs-on: ubuntu-latest
steps:
+ - name: Install Nix
+ uses: DeterminateSystems/nix-installer-action@v4
+
- name: Checkout code
uses: actions/checkout@v4
- - name: Install dependencies
- run: |
- git submodule update --init
- sudo apt-get install -y doxygen graphviz
-
- - name: Build doc
- run: doxygen Doxyfile
+ - name: Build documentation
+ run: nix build .#doc
- - name: Upload doc to GitHub Pages
- uses: peaceiris/actions-gh-pages@v3
+ - name: Upload GitHub Pages artifact
+ uses: actions/upload-pages-artifact@v3
with:
- force_orphan: true
- deploy_key: ${{ secrets.GH_SSH_PRIVATE_KEY }}
- publish_dir: ./.doc/html
+ path: ./result
diff --git a/.gitignore b/.gitignore
index e96cfe67..16bfd1af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ __pycache__
*.[aod]
*.so
*.egg-info
+.coverage
# Editors
.idea
@@ -34,3 +35,6 @@ a.out
# Documentation
.doc
+docs/build
+docs/doxygen
+docs/source/api
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index d8da1e47..00000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "doxygen-awesome-css"]
- path = doxygen-awesome-css
- url = https://github.com/jothepro/doxygen-awesome-css.git
diff --git a/Doxyfile b/Doxyfile
index c97e6ee3..681a0116 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,68 +1,26 @@
-#-------------------- Project Information --------------------
-PROJECT_NAME = "ZAPPY"
-PROJECT_BRIEF = "A Tribute to Zaphod Beeblebrox"
-OUTPUT_DIRECTORY = .doc
+PROJECT_NAME = Zappy
+INPUT = server gui
-#-------------------- Input --------------------
-INPUT = server/ gui/ ai/ README.md
-FILE_PATTERNS = *.c *.cpp *.h *.hpp *.py
-RECURSIVE = YES
-USE_MDFILE_AS_MAINPAGE = README.md
+FILE_PATTERNS = *.c *.cpp *.h *.hpp
+RECURSIVE = YES
-#-------------------- Build Options --------------------
-CREATE_SUBDIRS = YES
-CREATE_SUBDIRS_LEVEL = 8
-MARKDOWN_SUPPORT = YES
-MARKDOWN_ID_STYLE = DOXYGEN
-TOC_INCLUDE_HEADINGS = 5
-INPUT_ENCODING = UTF-8
-PYTHON_DOCSTRING = YES
-EXTRACT_LOCAL_CLASSES = YES
-SHOW_NAMESPACES = YES
-SOURCE_BROWSER = YES
-INLINE_SOURCES = YES
-STRIP_CODE_COMMENTS = YES
-REFERENCES_LINK_SOURCE = YES
-SOURCE_TOOLTIPS = YES
-WARNINGS = YES
-WARN_IF_UNDOCUMENTED = YES
-WARN_IF_DOC_ERROR = YES
-WARN_IF_INCOMPLETE_DOC = YES
-GENERATE_TODOLIST = YES
-GENERATE_TESTLIST = YES
-GENERATE_BUGLIST = YES
-GENERATE_DEPRECATEDLIST= YES
-SHOW_FILES = NO
-SHOW_HEADERFILE = NO
-SHOW_INCLUDE_FILES = NO
-SHOW_USED_FILES = NO
+OUTPUT_DIRECTORY = .build/doxygen
-#-------------------- HTML Output --------------------
-GENERATE_HTML = YES
-HTML_OUTPUT = html
-HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css \
- doxygen-awesome-css/doxygen-awesome-sidebar-only.css
-HTML_COLORSTYLE = AUTO_LIGHT
-HTML_DYNAMIC_MENUS = YES
-HTML_CODE_FOLDING = YES
-GENERATE_TREEVIEW = YES
+GENERATE_LATEX = NO
+GENERATE_XML = YES
+XML_OUTPUT = xml
-#-------------------- Graphs --------------------
-HAVE_DOT = YES
-CLASS_GRAPH = YES
-COLLABORATION_GRAPH = YES
-GROUP_GRAPHS = YES
-INCLUDE_GRAPH = YES
-INCLUDED_BY_GRAPH = YES
-GRAPHICAL_HIERARCHY = YES
-DIRECTORY_GRAPH = YES
-DOT_IMAGE_FORMAT = svg
-DOT_PATH = /usr/bin/env
+HAVE_DOT = yes # enables graphviz diagrams in Doxygen
+DOT_PATH = dot
-#-------------------- Miscellaneous --------------------
-OBFUSCATE_EMAILS = YES
+EXTRACT_ALL = YES
+EXTRACT_STATIC = YES
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_PRIVATE = YES
+EXTRACT_ANON_NSPACES = YES
-#-------------------- Exclude/Filter (if needed) --------------------
-FILTER_SOURCE_FILES = YES
+ALIASES = "todo=TODO" "bug=BUG" "warning=WARNING"
-#-------------------- End of file --------------------
+EXCLUDE_SYMBOLS = main
+BUILTIN_STL_SUPPORT = YES
+PREDEFINED += DOXYGEN_SHOULD_SKIP_THIS
diff --git a/Doxygen.vim b/Doxygen.vim
new file mode 100644
index 00000000..2a5f185e
--- /dev/null
+++ b/Doxygen.vim
@@ -0,0 +1,30 @@
+if expand('%:f') !=# 'Doxyfile'
+ finish
+endif
+
+" Comments
+syntax match doxyComment "#.*$"
+highlight link doxyComment Comment
+
+" Keys (must come first, but match only the key part)
+syntax match doxyKey "^\s*\zs[A-Z_]\+\ze\s*\(+=\|=\)"
+highlight link doxyKey Keyword
+
+" Operators
+syntax match doxyOperator "\(+=\|=\)"
+highlight link doxyOperator Operator
+
+" Booleans
+syntax match doxyBool "\<\(YES\|NO\)\>"
+highlight link doxyBool Constant
+
+" Strings in quotes
+syntax region doxyString start=/"/ skip=/\\"/ end=/"/ keepend
+highlight link doxyString String
+
+" Patterns like *.c or *.h
+syntax match doxyPattern "\*\.[ch]\(pp\)\?"
+highlight link doxyPattern Type
+
+let b:current_syntax = "doxygenconf"
+
diff --git a/Makefile b/Makefile
index 25d0ed3a..8ba162fb 100644
--- a/Makefile
+++ b/Makefile
@@ -7,6 +7,8 @@ LD := $(CC)
AR ?= ar
RM ?= rm --force
+CFLAGS := -std=c2x
+
CXXFLAGS := -std=c++20
CXXFLAGS += -iquote $/libs -iquote $/include
@@ -22,6 +24,8 @@ CXXFLAGS_debug := -g3 -fsanitize=address,leak,undefined -DDEBUG_MODE=1
CFLAGS_cov := --coverage -g3
CXXFLAGS_cov := --coverage -g3
+CFLAGS_tests := --coverage -g3
+CXXFLAGS_tests := --coverage -g3
LDLIBS :=
LDFLAGS :=
@@ -39,11 +43,15 @@ GENERIC_FLAGS_CPP := CXX
NAME_server_release := zappy_server
NAME_server_debug := debug_server
-NAME_server_cov := tests_server
+NAME_server_tests := tests_server
NAME_gui_release := zappy_gui
NAME_gui_debug := debug_gui
-NAME_gui_cov := tests_gui
+NAME_gui_tests := tests_gui
+
+EXTRA_SRC_gui_tests != find tests/gui -name "*.cpp"
+
+EXTRA_SRC_server_tests != find tests/server -type f -name "*.c"
# call mk-bin, bin-name, profile, lang
# DOES THIS MAKE COFFEE NOW ??
@@ -61,7 +69,8 @@ $(BUILD)/$(strip $2)/%.o: %.$(GENERIC_SUFFIX_$(strip $3))
out_$(strip $1)_$(strip $2) := $(NAME_$(strip $1)_$(strip $2))
src_$(strip $1)_$(strip $2) != \
- find $(strip $1) -type f -name "*.$(GENERIC_SUFFIX_$(strip $3))"
+ find $(strip $1) -type f -name "*.$(GENERIC_SUFFIX_$(strip $3))"
+src_$(strip $1)_$(strip $2) += $(EXTRA_SRC_$(strip $1)_$(strip $2))
objs_$(strip $1)_$(strip $2) := \
$$(src_$(strip $1)_$(strip $2):%.$(GENERIC_SUFFIX_$(strip \
@@ -84,15 +93,14 @@ LANG_server := C
LANG_gui := CPP
$(foreach target, server gui, \
-$(foreach build-mode, release debug cov, \
- $(eval $(call generic-o-builder, $(build-mode), $(LANG_$(target)))) \
+$(foreach build-mode, release debug tests, \
$(eval $(call mk-bin, $(target), $(build-mode), $(LANG_$(target)))) \
))
ifeq ($(V),2)
$(foreach target, server gui, \
-$(foreach build-mode, release debug cov, \
- $(eval $(call mk-bin, $(target), $(build-mode), $(LANG_$(target)))) \
+$(foreach build-mode, release debug cov tests, \
+ $(info $(call mk-bin, $(target), $(build-mode), $(LANG_$(target)))) \
))
endif
@@ -102,6 +110,7 @@ all: zappy_server zappy_gui zappy_ai
ifneq ($(auto-complete),)
# auto complete for dumb terminals
+
debug_gui:
debug_server:
zappy_ai:
@@ -109,6 +118,8 @@ zappy_gui:
zappy_server:
tests_gui:
tests_server:
+tests_run_server:
+tests_run_gui:
endif
venv:
@@ -122,6 +133,11 @@ zappy_ai: venv $(shell find ai -type f -name "*.py")
$Q cp venv/bin/zappy_ai $@
@ $(LOG_TIME) "CP $(C_GREEN)$@ $(C_RESET)"
+html-doc: #? html-doc: Build the static html documentation
+ doxygen Doxyfile
+ $(MAKE) -C docs html
+ @ $(LOG_TIME) "DO $(C_YELLOW)$@ $(C_RESET)"
+
.PHONY: help
help: #? help: Show this help message
@ grep -P "#[?] " $(MAKEFILE_LIST) \
@@ -136,11 +152,34 @@ fclean: clean
$(RM) $(every_out) zappy_ai
mrproper: fclean
- $(RM) -rf .build compile_commands.json
+ $(RM) -rf venv $(BUILD)
+ $(RM) -rf $(BUILD)
+ $(RM) -rf docs/source/api docs/doxygen
+ $(RM) -rf compile_commands.json
.NOTPARALLEL: re
re: fclean all
+tests_run_%: tests_%
+ ./$^
+
+tests_run_ai: venv
+ pytest . --cov=ai --no-summary
+
+.PHONY: cov cov_ai cov_server cov_gui
+
+cov_ai: tests_run_ai
+ coverage report -m
+
+cov_gui: tests_run_gui
+ gcovr $(BUILD)/tests/gui --exclude=tests
+
+cov_server: tests_run_server
+ gcovr $(BUILD)/tests/server --exclude=tests
+
+.NOTPARALLEL: cov
+cov: cov_ai cov_gui cov_server
+
V ?= 0
ifneq ($(V),0)
Q :=
diff --git a/doc.md b/doc.md
deleted file mode 100644
index eb726bca..00000000
--- a/doc.md
+++ /dev/null
@@ -1,139 +0,0 @@
-# Setting Up Documentation for ZAPPY Project
-
-This guide explains how to install the necessary tools and dependencies to generate comprehensive documentation for the **ZAPPY** project using **Doxygen** with the **Doxygen Awesome CSS** theme.
-
----
-
-## 1. Required Software and Dependencies
-
-### 1.1 Doxygen
-
-- **Purpose:**
- Doxygen is the main tool used to generate documentation from annotated C, C++, and Python source code. It extracts comments and produces nicely formatted HTML pages (and other formats if needed).
-
-- **Installation command:**
-
-```bash
-sudo apt install doxygen
-```
-
----
-
-### 1.2 Graphviz
-
-- **Purpose:**
- Graphviz generates graphical diagrams such as class inheritance trees, call graphs, and collaboration diagrams. Doxygen uses it to create visual representations of your code structure.
-
-- **Installation command:**
-
-```bash
-sudo apt install graphviz
-```
-
----
-
-### 1.3 (Optional) LaTeX (texlive)
-
-- **Purpose:**
- If you want PDF output of your documentation, Doxygen can produce LaTeX files which are compiled into PDFs. For this, you need a LaTeX distribution like texlive.
-
-- **Installation command:**
-
-```bash
-sudo apt install texlive-latex-base
-```
-
----
-
-## 2. Doxygen Awesome CSS Theme
-
-- **Purpose:**
- Enhances the look and feel of the HTML documentation generated by Doxygen with a modern, clean, and responsive design.
-
-- **Installation:**
- Download the [`doxygen-awesome.css`](https://github.com/jothepro/doxygen-awesome-css) file from the repository and include it in your documentation folder (e.g., `.doc/`).
- Then, configure your `Doxyfile` to use it as an extra stylesheet.
-
----
-
-## 3. Summary of Installation Commands
-
-Run the following commands in your terminal:
-
-```bash
-sudo apt update
-sudo apt install doxygen graphviz
-# Optional for PDF output:
-sudo apt install texlive-latex-base
-```
-
----
-
-## 4. Configuring Doxygen to Output to `.doc/` Folder
-
-1. Generate a base configuration file:
-
- ```bash
- doxygen -g Doxyfile
- ```
-
-2. Edit `Doxyfile` with your favorite editor and modify or add these lines:
-
- ```ini
- # Set the output directory for all generated files:
- OUTPUT_DIRECTORY = .doc
-
- # Project info
- PROJECT_NAME = "ZAPPY"
- PROJECT_BRIEF = "A Tribute to Zaphod Beeblebrox"
-
- # Input sources (adjust paths accordingly)
- INPUT = ./src ./include ./ai
- FILE_PATTERNS = *.c *.cpp *.h *.py
- RECURSIVE = YES
-
- # Output formats
- GENERATE_HTML = YES
- GENERATE_LATEX = NO
-
- # Source browsing
- SOURCE_BROWSER = YES
- INLINE_SOURCES = YES
-
- # Graphviz settings
- HAVE_DOT = YES
- DOT_IMAGE_FORMAT = svg
- DOT_PATH = /usr/bin/dot
-
- # Extra stylesheet for Doxygen Awesome CSS
- HTML_EXTRA_STYLESHEET = doxygen-awesome.css
-
- # Enable Markdown support
- MARKDOWN_SUPPORT = YES
-
- # Warnings for undocumented members
- WARN_IF_UNDOCUMENTED = YES
- ```
-
-3. Place the `doxygen-awesome.css` file inside the `.doc/` folder (or the folder you set as `OUTPUT_DIRECTORY`).
-
----
-
-## 5. Generating Documentation
-
-Run:
-
-\```bash
-doxygen Doxyfile
-\```
-
-All documentation files (HTML, diagrams, etc.) will be generated inside the `.doc/` folder.
-
----
-
-## 6. Viewing the Documentation
-
-Open the main HTML page in your browser:
-
-```bash
-xdg-open .doc/html/index.html
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000..eedd89b4
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1 @@
+api
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..40098da9
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = ../.build/doc
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/README.md b/docs/README.md
similarity index 87%
rename from README.md
rename to docs/README.md
index a5e62dca..bcdaaee2 100644
--- a/README.md
+++ b/docs/README.md
@@ -8,7 +8,9 @@



-[](https://codecov.io/gh/Sigmapitech/zappy?branch=dev)
+[](https://codecov.io/gh/Sigmapitech/zappy?branch=dev)
+[](https://codecov.io/gh/Sigmapitech/zappy?branch=dev)
+[](https://codecov.io/gh/Sigmapitech/zappy?branch=dev)



@@ -150,5 +152,5 @@ Run the server shell with:
|---|---|---|---|---|
|
|
|
|
|
|
-For more details, see [assignment.pdf](assignment.pdf).
+For more details, see assignment.pdf.
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 00000000..7af4f928
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,39 @@
+import os
+import sys
+import pathlib
+
+sys.path.insert(0, os.path.abspath('../'))
+
+project = 'ZAPPY'
+author = 'Team Hermitcraft'
+release = '1.0'
+extensions = [
+ 'breathe',
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.viewcode',
+ "myst_parser",
+ "exhale",
+]
+html_theme = 'furo'
+
+ZAPPY_ROOT = pathlib.Path(__file__).parent.parent
+breathe_projects = {"Zappy": f"{ZAPPY_ROOT}/.build/doxygen/xml"}
+breathe_default_project = "Zappy"
+
+exclude_patterns = []
+templates_path = ['_templates']
+html_static_path = ['_static']
+html_extra_path = ['assets']
+source_suffix = {
+ '.rst': 'restructuredtext',
+ '.md': 'markdown',
+}
+
+exhale_args = {
+ "containmentFolder": "./api",
+ "rootFileName": "api.rst",
+ "rootFileTitle": "API Documentation",
+ "doxygenStripFromPath": "..",
+ "createTreeView": True,
+}
diff --git a/docs/developer/architecture.rst b/docs/developer/architecture.rst
new file mode 100644
index 00000000..0b49abf9
--- /dev/null
+++ b/docs/developer/architecture.rst
@@ -0,0 +1,10 @@
+Architecture Overview
+=====================
+
+The Zappy project consists of three main components:
+
+- **Server** (`zappy_server`) - written in C, handles game logic and networking.
+- **AI Client** (`zappy_ai`) - written in Python, acts as a bot.
+- **GUI Client** (`zappy_gui`) - written in C++, visualizes the world.
+
+Each of these components interacts via TCP/IP using a defined protocol.
diff --git a/docs/developer/contribution.rst b/docs/developer/contribution.rst
new file mode 100644
index 00000000..46493562
--- /dev/null
+++ b/docs/developer/contribution.rst
@@ -0,0 +1,21 @@
+Contribution Guidelines
+=======================
+
+Code Style
+----------
+
+- **C/C++**: ClangFormat (`.clang-format`)
+- **Python**: PEP8, use `black`
+
+Conventions
+-----------
+
+- Use descriptive commit messages
+- Follow naming conventions from codebase
+- Always run `make re && make test`
+
+Pull Requests
+-------------
+
+- Branch from `dev`
+- Open PR against `main` only after full testing
diff --git a/docs/developer/systems.rst b/docs/developer/systems.rst
new file mode 100644
index 00000000..00a0943a
--- /dev/null
+++ b/docs/developer/systems.rst
@@ -0,0 +1,22 @@
+Systems Overview
+================
+
+Game Loop
+---------
+
+The server uses a single-threaded `poll()` loop to manage sockets and timed events.
+
+Resource Management
+-------------------
+
+Resources are respawned every 20 time units based on board density.
+
+Elevation Ritual
+----------------
+
+Players must gather resources and allies to level up.
+
+AI Logic
+--------
+
+The AI parses commands and responds autonomously using state-driven behavior trees.
diff --git a/docs/developer/usage_ai.rst b/docs/developer/usage_ai.rst
new file mode 100644
index 00000000..cf738697
--- /dev/null
+++ b/docs/developer/usage_ai.rst
@@ -0,0 +1,11 @@
+AI Development Guide
+====================
+
+Getting Started
+---------------
+
+Run an AI bot with:
+
+.. code-block:: shell
+
+ ./zappy_ai -p 4242 -n "TeamName" -h localhost
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..1fc1af99
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,15 @@
+```{include} ./README.md
+```
+
+```{toctree}
+:maxdepth: 2
+:caption: Contents:
+
+api/api
+developer/architecture
+developer/systems
+developer/contribution
+developer/usage_ai
+protocol/protocol
+modules
+```
diff --git a/docs/modules.rst b/docs/modules.rst
new file mode 100644
index 00000000..5a4d60dc
--- /dev/null
+++ b/docs/modules.rst
@@ -0,0 +1,8 @@
+Modules
+=======
+
+This section will document the various modules of the Zappy project.
+
+Currently under construction.
+
+---
diff --git a/docs/protocol/protocol.rst b/docs/protocol/protocol.rst
new file mode 100644
index 00000000..45db8f13
--- /dev/null
+++ b/docs/protocol/protocol.rst
@@ -0,0 +1,96 @@
+Zappy Protocols
+===============
+
+This document describes the communication protocols used in the Zappy project.
+
+---
+
+GUI Protocol
+------------
+
+The GUI protocol defines messages exchanged between the server and the GUI client.
+
+### Symbols and Meanings
+
+- **X**: width or horizontal position
+- **Y**: height or vertical position
+- **q0** to **q6**: quantities of resources
+ - q0: food
+ - q1: linemate
+ - q2: deraumere
+ - q3: sibur
+ - q4: mendiane
+ - q5: phiras
+ - q6: thystame
+- **n**: player number
+- **O**: orientation (1=N, 2=E, 3=S, 4=W)
+- **L**: player or incantation level
+- **e**: egg number
+- **T**: time unit
+- **N**: name of the team
+- **R**: incantation result
+- **M**: message
+- **i**: resource number
+
+### Server to Client Messages
+
++--------------+------------------------------------+----------------------------------+
+| Command | Description | Example |
++==============+====================================+==================================+
+| msz X Y | Map size | msz 10 10 |
++--------------+------------------------------------+----------------------------------+
+| bct X Y q0 q1 q2 q3 q4 q5 q6 | Content of a tile | bct 2 3 1 0 0 0 0 0 0 |
++--------------+------------------------------------+----------------------------------+
+| mct | Content of the entire map | mct |
++--------------+------------------------------------+----------------------------------+
+| tna N | Name of a team | tna TeamName |
++--------------+------------------------------------+----------------------------------+
+| pnw #n X Y O L N | New player connection | pnw #5 4 4 1 2 TeamName |
++--------------+------------------------------------+----------------------------------+
+| ppo #n X Y O | Player position | ppo #5 4 4 1 |
++--------------+------------------------------------+----------------------------------+
+| plv #n L | Player level | plv #5 3 |
++--------------+------------------------------------+----------------------------------+
+| pin #n X Y q0 q1 q2 q3 q4 q5 q6 | Player inventory | pin #5 4 4 1 0 0 0 0 0 0 |
++--------------+------------------------------------+----------------------------------+
+| pex #n | Expulsion | pex #5 |
++--------------+------------------------------------+----------------------------------+
+| pbc #n M | Broadcast message | pbc #5 Hello team! |
++--------------+------------------------------------+----------------------------------+
+| pic X Y L #n ... | Start of an incantation | pic 2 3 2 #5 #6 |
++--------------+------------------------------------+----------------------------------+
+| pie X Y R | End of an incantation | pie 2 3 1 |
++--------------+------------------------------------+----------------------------------+
+| pfk #n | Egg laying | pfk #5 |
++--------------+------------------------------------+----------------------------------+
+| pdr #n i | Resource dropping | pdr #5 0 |
++--------------+------------------------------------+----------------------------------+
+| pgt #n i | Resource collecting | pgt #5 0 |
++--------------+------------------------------------+----------------------------------+
+| pdi #n | Player death | pdi #5 |
++--------------+------------------------------------+----------------------------------+
+| enw #e #n X Y | Egg laid by a player | enw #10 #5 4 4 |
++--------------+------------------------------------+----------------------------------+
+| ebo #e | Player connection for egg | ebo #10 |
++--------------+------------------------------------+----------------------------------+
+| edi #e | Death of an egg | edi #10 |
++--------------+------------------------------------+----------------------------------+
+| sgt T | Time unit request | sgt 100 |
++--------------+------------------------------------+----------------------------------+
+| sst T | Time unit modification | sst 50 |
++--------------+------------------------------------+----------------------------------+
+| seg N | End of game | seg TeamName |
++--------------+------------------------------------+----------------------------------+
+| smg M | Message from the server | smg Welcome to Zappy! |
++--------------+------------------------------------+----------------------------------+
+| suc | Unknown command | suc |
++--------------+------------------------------------+----------------------------------+
+| sbp | Command parameter | sbp |
++--------------+------------------------------------+----------------------------------+
+
+---
+
+For the AI client protocol, please refer to the `developer/usage_ai.rst`.
+
+---
+
diff --git a/doxygen-awesome-css b/doxygen-awesome-css
deleted file mode 160000
index 9760c300..00000000
--- a/doxygen-awesome-css
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 9760c30014131f4eacb8e96f15f3869c7bc5dd8c
diff --git a/flake.nix b/flake.nix
index 99b6fd37..9a76cf56 100644
--- a/flake.nix
+++ b/flake.nix
@@ -19,10 +19,11 @@
in {
devShells = forAllSystems (pkgs: {
default = let
- cpp-fmt = pkgs.writeShellScriptBin "cpp-fmt" ''
- find . -type f -name "*.cpp" -or -name "*.hpp" \
- | xargs clang-format -i --verbose
- '';
+ pyenv = pkgs.python313.withPackages (p:
+ with p; [
+ pytest
+ pytest-cov
+ ]);
in
pkgs.mkShell {
inherit (self.checks.${pkgs.system}.pre-commit-check) shellHook;
@@ -30,51 +31,77 @@
env.MAKEFLAGS = "-j";
hardeningDisable = ["fortify"];
inputsFrom = with self.packages.${pkgs.system}; [
- zappy_ai
- zappy_gui
- zappy_server
+ ai
+ gui
+ server
+ doc
];
- packages = with pkgs; [
- clang-tools
- compiledb
- gcovr
- hl-log-viewer
- cpp-fmt
- doxygen
- graphviz
- ];
+ packages =
+ (with pkgs; [
+ clang-tools
+ compiledb
+ gcovr
+ hl-log-viewer
+ doxygen
+ graphviz
+ pyenv
+ ])
+ ++ (with self.packages.${pkgs.system}; [
+ cpp-fmt
+ ]);
};
});
formatter = forAllSystems (pkgs: pkgs.alejandra);
checks = forAllSystems (
- pkgs:
- import ./nix/pre-commit-hooks.nix {
- inherit self pkgs pre-commit-hooks;
- }
+ pkgs: {
+ pre-commit-check = pre-commit-hooks.lib.${pkgs.system}.run {
+ hooks = import ./nix/pre-commit-hooks.nix {inherit self pkgs;};
+ src = ./.;
+ };
+ }
);
packages = forAllSystems (
pkgs: let
- pypkgs = pkgs.python3.pkgs;
- in {
- zappy_ai = pypkgs.callPackage ./nix/zappy_ai.nix {};
+ pypkgs = pkgs.python313.pkgs;
+ pkgs' = self.packages.${pkgs.system};
+ in
+ {
+ ai = pypkgs.callPackage ./nix/zappy-ai.nix {};
- zappy_gui = pypkgs.callPackage ./nix/zappy_gui.nix {};
+ gui = pypkgs.callPackage ./nix/zappy-gui.nix {};
- zappy_server = pypkgs.callPackage ./nix/zappy_server.nix {};
+ server = pypkgs.callPackage ./nix/zappy-server.nix {};
- default = pkgs.symlinkJoin {
- name = "zappy";
- paths = with self.packages.${pkgs.system}; [
- zappy_server
- zappy_gui
- zappy_ai
- ];
- };
- }
+ default = pkgs.symlinkJoin {
+ name = "zappy";
+ paths = with pkgs'; [
+ server
+ gui
+ ai
+ ];
+ };
+ }
+ // {
+ ref-gui = pkgs.callPackage ./nix/ref-gui.nix {};
+
+ ref-server = pkgs.callPackage ./nix/ref-server.nix {};
+ }
+ // {
+ cpp-fmt = pkgs.writeShellScriptBin "cpp-fmt" ''
+ find . -type f -name "*.cpp" -or -name "*.hpp" \
+ | xargs clang-format -i --verbose
+ '';
+
+ exhale = pypkgs.callPackage ./nix/exhale.nix {};
+
+ doc = pkgs.callPackage ./nix/doc.nix {
+ inherit (pkgs') exhale;
+ };
+ }
);
};
}
diff --git a/gui/main.cpp b/gui/main.cpp
index 783bbe44..0460e871 100644
--- a/gui/main.cpp
+++ b/gui/main.cpp
@@ -1,3 +1,7 @@
#include
-auto main() -> int { std::cout << "Hello, client!\n"; }
+[[gnu::weak]]
+auto main() -> int
+{
+ std::cout << "Hello, client!\n";
+}
diff --git a/nix/doc.nix b/nix/doc.nix
new file mode 100644
index 00000000..3852958c
--- /dev/null
+++ b/nix/doc.nix
@@ -0,0 +1,45 @@
+{
+ stdenv,
+ doxygen,
+ python313,
+ exhale,
+ lib,
+ graphviz,
+ writableTmpDirAsHomeHook,
+ ncurses,
+}:
+stdenv.mkDerivation {
+ name = "doc";
+ src = ../.;
+
+ buildInputs = [doxygen];
+ nativeBuildInputs = with python313.pkgs; [
+ linkify-it-py
+ myst-parser
+ sphinx
+ furo
+ sphinx-copybutton
+ sphinx-design
+ sphinx-notfound-page
+ sphinx-sitemap
+ breathe
+ exhale
+ writableTmpDirAsHomeHook
+ ncurses
+ ];
+
+ preBuild = ''
+ mkdir -p .build
+ substituteInPlace Doxyfile \
+ --replace-fail "DOT_PATH = dot" "DOT_PATH = ${lib.getExe' graphviz "dot"}"
+ '';
+
+ makeFlags = ["html-doc"];
+
+ installPhase = ''
+ mkdir -p $out/
+ cp -R .build/doc/html/* $out/
+ '';
+
+ meta.maintainers = with lib.maintainers; [sigmanificient];
+}
diff --git a/nix/exhale.nix b/nix/exhale.nix
new file mode 100644
index 00000000..0197f61e
--- /dev/null
+++ b/nix/exhale.nix
@@ -0,0 +1,42 @@
+{
+ buildPythonPackage,
+ fetchFromGitHub,
+ setuptools,
+ breathe,
+ sphinx,
+ beautifulsoup4,
+ lxml,
+ six,
+ lib,
+}:
+buildPythonPackage rec {
+ pname = "exhale";
+ version = "0.3.7";
+ pyproject = true;
+
+ src = fetchFromGitHub {
+ owner = "svenevs";
+ repo = "exhale";
+ tag = "v${version}";
+ hash = "sha256-I7Q2vKLT/h35xX87FugyvxSTESnO3+LFLUX9kZOPI0I=";
+ };
+
+ build-system = [setuptools];
+
+ dependencies = [
+ breathe
+ sphinx
+ beautifulsoup4
+ lxml
+ six
+ ];
+
+ pythonImportsCheck = ["exhale"];
+
+ doCheck = false; # no great
+
+ meta = {
+ description = "Automatic C++ library api documentation generation";
+ license = lib.licenses.bsd3;
+ };
+}
diff --git a/nix/pre-commit-hooks.nix b/nix/pre-commit-hooks.nix
index 8a065d1d..d8a13a03 100644
--- a/nix/pre-commit-hooks.nix
+++ b/nix/pre-commit-hooks.nix
@@ -1,7 +1,6 @@
{
self,
pkgs,
- pre-commit-hooks,
}: let
inherit (pkgs) lib;
@@ -11,42 +10,34 @@
align-slashes = python-script "align-slashes" ../scripts/align_columns.py;
discard-headers = python-script "discard-headers" ../scripts/discard_headers.py;
+in {
+ alejandra.enable = true;
+ clang-format = {
+ enable = true;
+ name = "format the code";
+ entry = lib.getExe self.packages.${pkgs.system}.cpp-fmt;
+ };
- hooks = {
- alejandra.enable = true;
- clang-format = {
- enable = true;
- types_or = lib.mkForce [
- "c++"
- ];
- };
-
- align-slashes = {
- enable = true;
- name = "align blackslashes";
- entry = lib.getExe align-slashes;
- };
+ align-slashes = {
+ enable = true;
+ name = "align blackslashes";
+ entry = lib.getExe align-slashes;
+ };
- discard-headers = {
- enable = true;
- name = "discard headers";
- entry = lib.getExe discard-headers;
- };
+ discard-headers = {
+ enable = true;
+ name = "discard headers";
+ entry = lib.getExe discard-headers;
+ };
- trim-trailing-whitespace.enable = true;
+ trim-trailing-whitespace.enable = true;
- commit-name = {
- enable = true;
- name = "commit name";
- stages = ["commit-msg"];
- entry = ''
- ${pkgs.python310.interpreter} ${../scripts/check_commit_message.py}
- '';
- };
- };
-in {
- pre-commit-check = pre-commit-hooks.lib.${pkgs.system}.run {
- inherit hooks;
- src = ./.;
+ commit-name = {
+ enable = true;
+ name = "commit name";
+ stages = ["commit-msg"];
+ entry = ''
+ ${pkgs.python310.interpreter} ${../scripts/check_commit_message.py}
+ '';
};
}
diff --git a/nix/ref-gui.nix b/nix/ref-gui.nix
new file mode 100644
index 00000000..ab8fc47f
--- /dev/null
+++ b/nix/ref-gui.nix
@@ -0,0 +1,30 @@
+{
+ appimageTools,
+ nix-update-script,
+ stdenvNoCC,
+}: let
+ version = "3.0.0";
+
+ src = stdenvNoCC.mkDerivation {
+ pname = "ref-gui-src";
+ inherit version;
+
+ src = ../zappy_ref.tgz;
+ sourceRoot = "linux";
+
+ dontBuild = true;
+
+ postInstall = ''
+ install -Dm 644 ./zappy_gui.AppImage $out
+ '';
+ };
+in
+ appimageTools.wrapType2 {
+ pname = "ref-gui";
+
+ inherit version src;
+
+ passthru.updateScript = nix-update-script {
+ extraArgs = ["--version-regex=v([\\d.]+)"];
+ };
+ }
diff --git a/nix/ref-server.nix b/nix/ref-server.nix
new file mode 100644
index 00000000..5ad19a3c
--- /dev/null
+++ b/nix/ref-server.nix
@@ -0,0 +1,17 @@
+{stdenvNoCC}:
+stdenvNoCC.mkDerivation {
+ pname = "ref-server-src";
+ version = "3.0.1";
+
+ src = ../zappy_ref.tgz;
+ sourceRoot = "linux";
+
+ dontBuild = true;
+
+ postInstall = ''
+ mkdir -p $out/bin
+ install -Dm 577 ./zappy_server $out/bin/zappy_server
+ '';
+
+ meta.mainProgram = "zappy_server";
+}
diff --git a/nix/zappy_ai.nix b/nix/zappy-ai.nix
similarity index 94%
rename from nix/zappy_ai.nix
rename to nix/zappy-ai.nix
index fe54a0d9..5667ccea 100644
--- a/nix/zappy_ai.nix
+++ b/nix/zappy-ai.nix
@@ -4,7 +4,7 @@
setuptools,
}:
buildPythonPackage {
- name = "zappy_ai";
+ name = "zappy-ai";
version = "0.0.1";
pyproject = true;
diff --git a/nix/zappy_gui.nix b/nix/zappy-gui.nix
similarity index 94%
rename from nix/zappy_gui.nix
rename to nix/zappy-gui.nix
index d84f0530..a4ad94d8 100644
--- a/nix/zappy_gui.nix
+++ b/nix/zappy-gui.nix
@@ -4,7 +4,7 @@
ncurses,
}:
stdenv.mkDerivation (finalAttrs: {
- pname = "zappy_gui";
+ pname = "zappy-gui";
version = "0.0.1";
src = ../.;
diff --git a/nix/zappy_server.nix b/nix/zappy-server.nix
similarity index 94%
rename from nix/zappy_server.nix
rename to nix/zappy-server.nix
index 867d56b5..8a252f9d 100644
--- a/nix/zappy_server.nix
+++ b/nix/zappy-server.nix
@@ -4,7 +4,7 @@
ncurses,
}:
stdenv.mkDerivation (finalAttrs: {
- pname = "zappy_server";
+ pname = "zappy-server";
version = "0.0.1";
src = ../.;
diff --git a/scripts/align_columns.py b/scripts/align_columns.py
index d7de1c22..361bb1a1 100644
--- a/scripts/align_columns.py
+++ b/scripts/align_columns.py
@@ -34,12 +34,15 @@ def align_slash_in_file(file_path):
def main():
- with open(".gitmodules") as f:
- submodules = [
- line.split("=", 1)[1].strip()
- for line in f
- if line.strip().startswith("path = ")
- ]
+ submodules = []
+
+ if os.path.exists(".gitmodules"):
+ with open(".gitmodules") as f:
+ submodules = [
+ line.split("=", 1)[1].strip()
+ for line in f
+ if line.strip().startswith("path = ")
+ ]
collected_files = (
os.path.join(root, fname)
for root, _, files in os.walk(ROOT_DIR)
diff --git a/server/main.c b/server/main.c
index 0794124d..f9f0501c 100644
--- a/server/main.c
+++ b/server/main.c
@@ -1,5 +1,6 @@
#include
+[[gnu::weak]]
int main(void)
{
printf("Hello, server!\n");
diff --git a/tests/ai/__init__.py b/tests/ai/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/ai/test_base.py b/tests/ai/test_base.py
new file mode 100644
index 00000000..b953a0f5
--- /dev/null
+++ b/tests/ai/test_base.py
@@ -0,0 +1,2 @@
+def test_pass():
+ assert True
diff --git a/tests/gui/compass.cpp b/tests/gui/compass.cpp
new file mode 120000
index 00000000..b7bec5c2
--- /dev/null
+++ b/tests/gui/compass.cpp
@@ -0,0 +1 @@
+../server/compass.c
\ No newline at end of file
diff --git a/tests/gui/compass.h b/tests/gui/compass.h
new file mode 120000
index 00000000..d3beb7f3
--- /dev/null
+++ b/tests/gui/compass.h
@@ -0,0 +1 @@
+../server/compass.h
\ No newline at end of file
diff --git a/tests/server/compass.c b/tests/server/compass.c
new file mode 100644
index 00000000..fa92a522
--- /dev/null
+++ b/tests/server/compass.c
@@ -0,0 +1,116 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "compass.h"
+
+static
+const char NAME[] = "\033[38;5;189mCom\033[3mpass\033[23m\033[38;5;103m";
+
+static bool IS_LAST_TEST = false;
+
+extern callback TEST_SECTION_START;
+extern callback TEST_SECTION_STOP;
+
+enum test_state_e {
+ T_FORK_FAILURE = 1,
+ T_IS_CHILD = 2,
+ T_FAILURE = 4,
+ T_SUCCESS = 8
+};
+
+static
+enum test_state_e print_test_results(int status)
+{
+ if (status) {
+ fprintf(stderr,
+ "\033[48;5;210m\033[38;5;232mCRASHED" RESET
+ " with status\033[38;5;248m: " YELLOW
+ "%d" RESET "\n\n", status);
+ return T_FAILURE;
+ }
+ printf("\n");
+ return T_SUCCESS;
+}
+
+static
+enum test_state_e run_test(callback *f)
+{
+ int status;
+ pid_t pid = fork();
+
+ if (pid < 0)
+ return T_FORK_FAILURE;
+ if (!pid) {
+ f->func();
+ IS_LAST_TEST = true;
+ assert_debug("reach end of test", "", __FILE__, __LINE__);
+ return T_IS_CHILD;
+ }
+ wait(&status);
+ return print_test_results(status);
+}
+
+int main(void)
+{
+ int status = T_SUCCESS;
+
+ fprintf(stderr, "\033[38;5;103m"
+ "┌────────────────────────────┐\n"
+ "│ %s │\n"
+ "└────────────────────────────┘\n\n", NAME);
+ for (callback *f = &TEST_SECTION_START; f != &TEST_SECTION_STOP; f++) {
+ if (f->func == NULL)
+ continue;
+ fprintf(stderr,
+ "\033[38;5;103m╤══ \033[38;5;75m%s" RESET ":\n", f->name);
+ status |= run_test(f);
+ if (status & T_IS_CHILD)
+ break;
+ if (status & T_FORK_FAILURE)
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+}
+
+void assert_debug(
+ char const *msg,
+ char const *expr,
+ char const *file,
+ int line)
+{
+ char const *filename = strrchr(file, '/');
+
+ if (filename == NULL)
+ filename = file;
+ else
+ filename++;
+ fprintf(
+ stderr,
+ "\033[38;5;103m%s" BLUE "\033[4m%s" RESET
+ "\033[38;5;248m:" PURPLE "%d" RESET " %s",
+ (IS_LAST_TEST ? "└── " : "├ "), filename, line, msg);
+ if (*expr != '\0')
+ fprintf(stderr, "\033[3m\033[38;5;103m // %s", expr);
+ fprintf(stderr, RESET "\n");
+}
+
+#define $ // fix nested function false positive report
+void assert_impl(bool res) $
+{
+ if (!res)
+ fprintf(
+ stderr,
+ "\033[38;5;103m│ └ \033[38;5;197m"
+ "🯀 Assertion failed\033[0m\n"
+ );
+}
+
+Test(compass, is_properly_setup)
+{
+ assert("this test should pass", true);
+}
diff --git a/tests/server/compass.h b/tests/server/compass.h
new file mode 100644
index 00000000..7fca1d3a
--- /dev/null
+++ b/tests/server/compass.h
@@ -0,0 +1,54 @@
+#ifndef TEST_H
+ #define TEST_H
+
+ #define COL_FMT(n) "\033[" #n "m"
+
+ #define RESET COL_FMT(0)
+ #define BOLD COL_FMT(1)
+
+ #define RED COL_FMT(31)
+ #define GREEN COL_FMT(32)
+ #define YELLOW COL_FMT(33)
+ #define BLUE COL_FMT(34)
+ #define PURPLE COL_FMT(35)
+ #define CYAN COL_FMT(36)
+
+ #include
+ #include
+
+typedef struct {
+ char const *name;
+ void (*func)(void);
+} callback;
+
+ #define TEST_SECTION_START __start_compass_test
+ #define TEST_SECTION_STOP __stop_compass_test
+
+ #define TEST_PROTO(func_name) static void (func_name)(void)
+
+void assert_impl(bool res);
+void assert_debug(
+ char const *msg,
+ char const *expr,
+ char const *file,
+ int line);
+
+ #define Test(test_suite, test_case) \
+TEST_PROTO(test_suite ## __ ## test_case); \
+ \
+[[gnu::section("compass_test"), gnu::used]] \
+static const callback info_ ## test_suite ## __ ## test_case = { \
+ .name = "\033[38;5;81m" #test_suite \
+ "\033[38;5;103m -> \033[38;5;117m" #test_case, \
+ .func = &(test_suite ## __ ## test_case) \
+}; \
+ \
+TEST_PROTO(test_suite ## __ ## test_case)
+
+ #define assert(msg, expr) \
+do { \
+ assert_debug(msg, (#expr), __FILE__, __LINE__); \
+ assert_impl((expr)); \
+} while (0)
+
+#endif
diff --git a/zappy_ref-v3.0.0.tgz b/zappy_ref.tgz
similarity index 54%
rename from zappy_ref-v3.0.0.tgz
rename to zappy_ref.tgz
index 3701fe5f..91cd3083 100644
Binary files a/zappy_ref-v3.0.0.tgz and b/zappy_ref.tgz differ