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 @@ ![Makefile](https://img.shields.io/badge/build-Makefile-brightgreen?logo=gnu&logoColor=white) ![Linux x86_64](https://img.shields.io/badge/platform-Linux%20x86__64-lightgrey?logo=linux) ![Nix](https://img.shields.io/badge/env-Nix-5277C3?logo=nixos&logoColor=white) -[![Coverage](https://codecov.io/gh/Sigmapitech/zappy/branch/dev/graph/badge.svg?token=5fssEEelNi)](https://codecov.io/gh/Sigmapitech/zappy?branch=dev) +[![AI Coverage](https://codecov.io/gh/Sigmapitech/zappy/branch/dev/graph/badge.svg?token=5fssEEelNi&flag=ai)](https://codecov.io/gh/Sigmapitech/zappy?branch=dev) +[![GUI Coverage](https://codecov.io/gh/Sigmapitech/zappy/branch/dev/graph/badge.svg?token=5fssEEelNi&flag=gui)](https://codecov.io/gh/Sigmapitech/zappy?branch=dev) +[![Server Coverage](https://codecov.io/gh/Sigmapitech/zappy/branch/dev/graph/badge.svg?token=5fssEEelNi&flag=server)](https://codecov.io/gh/Sigmapitech/zappy?branch=dev) ![Build](https://img.shields.io/github/actions/workflow/status/Sigmapitech/zappy/ci.yml?branch=main&logo=github) ![Last Commit](https://img.shields.io/github/last-commit/Sigmapitech/zappy?logo=git) ![Issues](https://img.shields.io/github/issues/Sigmapitech/zappy?logo=github) @@ -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