├── docs ├── .gitignore ├── ai_visualisations.rst ├── inspecting_trials_and_states.rst ├── ai_tips_and_tricks.rst ├── resources │ ├── ludii-logo-32x32.ico │ └── ludii-logo-100x100.png ├── lud_format_advanced_features.rst ├── tutorial_advanced.rst ├── contact_info.rst ├── acknowledgements.rst ├── Makefile ├── make.bat ├── installing_ludii.rst ├── installing_this_repo.rst ├── ludii_terminology.rst ├── index.rst ├── lud_format_basics.rst ├── loading_games.rst ├── conf.py ├── running_trials.rst ├── basic_ai_api.rst ├── cheat_sheet.rst └── tutorial_amazons.rst ├── libs ├── junit-4.12.jar └── hamcrest-all-1.3.jar ├── travis └── download_ludii.sh ├── resources ├── ludii-logo-64x64.png ├── LOGO_ERC-FLAG_EU_.jpg └── luds │ └── walkthrough_amazons │ ├── Step1.lud │ ├── Step2.lud │ ├── Step3.lud │ ├── Step4.lud │ └── Step5.lud ├── .travis.yml ├── .gitignore ├── LICENSE ├── src └── ludii_tutorials │ ├── GameLoading.java │ ├── RunningTrials.java │ └── ListLudiiGames.java ├── test ├── running_trials │ └── TestRunningTrials.java └── game_loading │ └── TestGameLoading.java └── README.md /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build -------------------------------------------------------------------------------- /libs/junit-4.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ludeme/LudiiTutorials/HEAD/libs/junit-4.12.jar -------------------------------------------------------------------------------- /docs/ai_visualisations.rst: -------------------------------------------------------------------------------- 1 | Visualising AI Behaviour in Ludii 2 | ================================= 3 | -------------------------------------------------------------------------------- /docs/inspecting_trials_and_states.rst: -------------------------------------------------------------------------------- 1 | Inspecting Trials and States 2 | ============================ 3 | -------------------------------------------------------------------------------- /docs/ai_tips_and_tricks.rst: -------------------------------------------------------------------------------- 1 | Tips and Tricks for AI Development 2 | ================================== 3 | -------------------------------------------------------------------------------- /libs/hamcrest-all-1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ludeme/LudiiTutorials/HEAD/libs/hamcrest-all-1.3.jar -------------------------------------------------------------------------------- /travis/download_ludii.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ev 3 | wget -P ./libs https://ludii.games/downloads/Ludii.jar -------------------------------------------------------------------------------- /resources/ludii-logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ludeme/LudiiTutorials/HEAD/resources/ludii-logo-64x64.png -------------------------------------------------------------------------------- /resources/LOGO_ERC-FLAG_EU_.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ludeme/LudiiTutorials/HEAD/resources/LOGO_ERC-FLAG_EU_.jpg -------------------------------------------------------------------------------- /docs/resources/ludii-logo-32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ludeme/LudiiTutorials/HEAD/docs/resources/ludii-logo-32x32.ico -------------------------------------------------------------------------------- /docs/resources/ludii-logo-100x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ludeme/LudiiTutorials/HEAD/docs/resources/ludii-logo-100x100.png -------------------------------------------------------------------------------- /docs/lud_format_advanced_features.rst: -------------------------------------------------------------------------------- 1 | Writing .lud Descriptions -- Advanced Features 2 | ============================================== 3 | -------------------------------------------------------------------------------- /docs/tutorial_advanced.rst: -------------------------------------------------------------------------------- 1 | Writing in .lud Format 2 | ======================================================== 3 | -------------------------------------------------------------------------------- /docs/contact_info.rst: -------------------------------------------------------------------------------- 1 | Contact Info 2 | ============ 3 | 4 | 1. For questions or suggestions directly related to these tutorial pages, please 5 | `create an issue on GitHub `_. 6 | 2. For other questions, suggestions or remarks, it is preferred to use the 7 | `Ludii Forums `_. 8 | 3. Alternatively, we may be contacted via ``ludii.games@gmail.com``. 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | version: ~> 1.0 3 | 4 | branches: 5 | only: 6 | - master 7 | 8 | before_script: 9 | - sudo apt-get install ant-optional 10 | - sudo chown -R travis ./travis/download_ludii.sh 11 | - sudo chmod +x ./travis/download_ludii.sh 12 | 13 | script: 14 | - ./travis/download_ludii.sh 15 | - travis_wait ant clean test 16 | 17 | notifications: 18 | email: 19 | recipients: 20 | - d.soemers@gmail.com -------------------------------------------------------------------------------- /docs/acknowledgements.rst: -------------------------------------------------------------------------------- 1 | Acknowledgements 2 | ================ 3 | 4 | This repository is part of the European Research Council-funded Digital Ludeme 5 | Project (ERC Consolidator Grant #771292), being run by Cameron Browne at 6 | Maastricht University's Department of Advanced Computing Sciences. 7 | 8 | .. image:: ../resources/LOGO_ERC-FLAG_EU_.jpg 9 | :height: 384px 10 | :alt: European Research Council Logo 11 | :target: https://erc.europa.eu/ 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | /bin/ 24 | 25 | # Eclipse files 26 | .classpath 27 | .project 28 | 29 | # The Ludii.jar file and files it creates 30 | /libs/Ludii.jar 31 | /libs/ludii_preferences.json 32 | /libs/ludii.trl 33 | /libs/Ludii-javadoc.jar -------------------------------------------------------------------------------- /resources/luds/walkthrough_amazons/Step1.lud: -------------------------------------------------------------------------------- 1 | // Step 1 in the tutorial for writing Amazons in .lud format from scratch 2 | // 3 | // This is a minimal legal game description, with some basic equipment, 4 | // playing rules, and ending rules. 5 | 6 | (game "Amazons" 7 | (players 2) 8 | (equipment 9 | { 10 | (board (square 10)) 11 | } 12 | ) 13 | (rules 14 | (play 15 | (forEach Piece) 16 | ) 17 | 18 | (end 19 | (if 20 | (no Moves Next) 21 | (result Mover Win) 22 | ) 23 | ) 24 | ) 25 | ) -------------------------------------------------------------------------------- /resources/luds/walkthrough_amazons/Step2.lud: -------------------------------------------------------------------------------- 1 | // Step 2 in the tutorial for writing Amazons in .lud format from scratch 2 | // 3 | // In this step we specify the piece types used to play Amazons. 4 | 5 | (game "Amazons" 6 | (players 2) 7 | (equipment 8 | { 9 | (board (square 10)) 10 | (piece "Queen" Each) 11 | (piece "Dot" Neutral) 12 | } 13 | ) 14 | (rules 15 | (play 16 | (forEach Piece) 17 | ) 18 | 19 | (end 20 | (if 21 | (no Moves Next) 22 | (result Mover Win) 23 | ) 24 | ) 25 | ) 26 | ) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = LudiiTutorials 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /resources/luds/walkthrough_amazons/Step3.lud: -------------------------------------------------------------------------------- 1 | // Step 3 in the tutorial for writing Amazons in .lud format from scratch 2 | // 3 | // In this file we introduce the start rules to place the initial pieces on the board. 4 | 5 | (game "Amazons" 6 | (players 2) 7 | (equipment 8 | { 9 | (board (square 10)) 10 | (piece "Queen" Each) 11 | (piece "Dot" Neutral) 12 | } 13 | ) 14 | (rules 15 | (start 16 | { 17 | (place "Queen1" {"A4" "D1" "G1" "J4"}) 18 | (place "Queen2" {"A7" "D10" "G10" "J7"}) 19 | } 20 | ) 21 | (play 22 | (forEach Piece) 23 | ) 24 | 25 | (end 26 | (if 27 | (no Moves Next) 28 | (result Mover Win) 29 | ) 30 | ) 31 | ) 32 | ) -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=LudiiTutorials 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /resources/luds/walkthrough_amazons/Step4.lud: -------------------------------------------------------------------------------- 1 | // Step 4 in the tutorial for writing Amazons in .lud format from scratch 2 | // 3 | // In this step we add the final rules for a correct game of Amazons. 4 | 5 | (game "Amazons" 6 | (players 2) 7 | (equipment 8 | { 9 | (board (square 10)) 10 | (piece "Queen" Each (move Slide (then (moveAgain)))) 11 | (piece "Dot" Neutral) 12 | } 13 | ) 14 | (rules 15 | (start 16 | { 17 | (place "Queen1" {"A4" "D1" "G1" "J4"}) 18 | (place "Queen2" {"A7" "D10" "G10" "J7"}) 19 | } 20 | ) 21 | (play 22 | (if (is Even (count Moves)) 23 | (forEach Piece) 24 | (move Shoot (piece "Dot0")) 25 | ) 26 | ) 27 | 28 | (end 29 | (if 30 | (no Moves Next) 31 | (result Mover Win) 32 | ) 33 | ) 34 | ) 35 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Digital Ludeme Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /resources/luds/walkthrough_amazons/Step5.lud: -------------------------------------------------------------------------------- 1 | // Step 5 in the tutorial for writing Amazons in .lud format from scratch 2 | // 3 | // This file improves how Amazons is displayed graphically in Ludii, but is 4 | // otherwise identical to the game description of Step 4. 5 | 6 | (game "Amazons" 7 | (players 2) 8 | (equipment 9 | { 10 | (board (square 10)) 11 | (piece "Queen" Each (move Slide (then (moveAgain)))) 12 | (piece "Dot" Neutral) 13 | } 14 | ) 15 | (rules 16 | (start 17 | { 18 | (place "Queen1" {"A4" "D1" "G1" "J4"}) 19 | (place "Queen2" {"A7" "D10" "G10" "J7"}) 20 | } 21 | ) 22 | 23 | (play 24 | (if (is Even (count Moves)) 25 | (forEach Piece) 26 | (move Shoot (piece "Dot0")) 27 | ) 28 | ) 29 | 30 | (end 31 | (if 32 | (no Moves Next) 33 | (result Mover Win) 34 | ) 35 | ) 36 | ) 37 | ) 38 | 39 | (metadata 40 | (graphics 41 | { 42 | (piece Scale "Dot" 0.333) 43 | (board Style Chess) 44 | } 45 | ) 46 | ) -------------------------------------------------------------------------------- /src/ludii_tutorials/GameLoading.java: -------------------------------------------------------------------------------- 1 | package ludii_tutorials; 2 | 3 | import java.io.File; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import game.Game; 8 | import other.GameLoader; 9 | 10 | /** 11 | * Example class showing how we can load games. 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public class GameLoading 16 | { 17 | 18 | /** 19 | * Main method 20 | * @param args Command-line arguments. 21 | */ 22 | public static void main(final String[] args) 23 | { 24 | // This will load Tic-Tac-Toe: 25 | final Game ticTacToe = GameLoader.loadGameFromName("Tic-Tac-Toe.lud"); 26 | System.out.println("Loaded game: " + ticTacToe.name()); 27 | 28 | // This will load Chess: 29 | final Game chess = GameLoader.loadGameFromName("/Chess.lud"); 30 | System.out.println("Loaded game: " + chess.name()); 31 | 32 | // This will load the external (not built-in) version of Amazons 33 | // that we wrote in the tutorial for writing Amazons in .lud format: 34 | final Game ourOwnAmazons = GameLoader.loadGameFromFile(new File("resources/luds/walkthrough_amazons/Step7.lud")); 35 | System.out.println("Loaded game: " + ourOwnAmazons.name()); 36 | 37 | // This will load Hex on a 19x19 board, with Misere end rule: 38 | final List options = Arrays.asList("Board Size/19x19", "End Rules/Misere"); 39 | final Game hex = GameLoader.loadGameFromName("Hex.lud", options); 40 | System.out.println("Loaded game: " + hex.name()); 41 | System.out.println("Num sites on board = " + hex.board().numSites()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /docs/installing_ludii.rst: -------------------------------------------------------------------------------- 1 | Installing Ludii 2 | ================ 3 | 4 | Prerequisites 5 | ------------- 6 | 7 | Ludii requires Java version 8 or higher to be installed on your computer. 8 | Java can be downloaded from: ``_. 9 | Ludii should run correctly on any major operating system. It has been verified 10 | to run correctly on the following operating systems: 11 | 12 | * Windows 10. 13 | * OS X El Capitan 10.11.6, OS X Mojave 10.14.3. 14 | * Ubuntu 16.04, Ubuntu 18.04, Ubuntu 19.04. 15 | 16 | Download and Installation 17 | ------------------------- 18 | 19 | The latest version of Ludii may always be downloaded from 20 | `Ludii's downloads page `_. This page also 21 | contains an archive of older versions of Ludii, and various extra downloads 22 | (such as documentation). 23 | 24 | No additional installation steps are required -- after downloading Ludii, it can 25 | be used directly. However, because Ludii occasionally writes files in the directory 26 | that it is run from, it may be convenient to place it in a directory of its own somewhere. 27 | 28 | Running Ludii 29 | ------------- 30 | 31 | The easiest way to launch Ludii is to double-click the downloaded ``Ludii.jar`` file. 32 | Alternatively, it may be launched by navigating to the directory containing the 33 | ``Ludii.jar`` file in a command prompt, and entering: 34 | 35 | .. code-block:: bash 36 | 37 | java -jar Ludii.jar 38 | 39 | The Ludii downloads page also contains `an extensive user guide `_, 40 | which explains how to use Ludii. 41 | -------------------------------------------------------------------------------- /test/running_trials/TestRunningTrials.java: -------------------------------------------------------------------------------- 1 | package running_trials; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import org.junit.Test; 10 | 11 | import game.Game; 12 | import other.AI; 13 | import other.GameLoader; 14 | import other.context.Context; 15 | import other.model.Model; 16 | import other.trial.Trial; 17 | import utils.RandomAI; 18 | 19 | /** 20 | * Unit tests to make sure that our trials running code runs as expected for 21 | * the Running Trials tutorial. 22 | * 23 | * @author Dennis Soemers 24 | */ 25 | public class TestRunningTrials 26 | { 27 | 28 | @Test 29 | public void testRandomTrial() 30 | { 31 | // Our trials running tutorial runs some random trials in Hex; 32 | // so we'll just do that again here to make sure we can do so 33 | // without crashing. 34 | final Game game = GameLoader.loadGameFromName("Hex.lud"); 35 | 36 | final Trial trial = new Trial(game); 37 | final Context context = new Context(game, trial); 38 | 39 | final List ais = new ArrayList(); 40 | ais.add(null); 41 | for (int p = 1; p <= game.players().count(); ++p) 42 | { 43 | ais.add(new RandomAI()); 44 | } 45 | 46 | game.start(context); 47 | 48 | for (int p = 1; p <= game.players().count(); ++p) 49 | { 50 | ais.get(p).initAI(game, p); 51 | } 52 | 53 | final Model model = context.model(); 54 | 55 | while (!trial.over()) 56 | { 57 | model.startNewStep(context, ais, 1.0); 58 | } 59 | 60 | final double[] ranking = trial.ranking(); 61 | assertNotNull(ranking); 62 | 63 | // Hex cannot have ties; must always have one player 64 | // with rank 1.0, and one with rank 2.0 65 | assertTrue(ranking[1] == 1.0 || ranking[2] == 1.0); 66 | assertTrue(ranking[1] == 2.0 || ranking[2] == 2.0); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /docs/installing_this_repo.rst: -------------------------------------------------------------------------------- 1 | Installing the Ludii Tutorials Repository 2 | ========================================= 3 | 4 | .. note:: 5 | 6 | Following these instructions for installing the Ludii Tutorials Repository 7 | is only required if you are interested in running some of the code examples 8 | from various programming tutorials. This is irrelevant for non-programming 9 | use cases of Ludii, such as game design. 10 | 11 | The `Ludii Tutorials repository `_ on 12 | GitHub provides various code examples to go along with some of the tutorials on 13 | these pages. This page lists the steps required to run these code examples locally: 14 | 15 | 1. Clone the repository from: ``_. 16 | 2. Create a ``Java`` project in your favourite IDE, using the source code 17 | in the cloned repository. 18 | 3. Suppose that the repository was cloned in the directory ``/LudiiTutorials``, 19 | which already contains ``src`` and ``docs`` directories. Create a new directory 20 | ``/LudiiTutorials/libs`` alongside them, and place the ``Ludii.jar`` file 21 | (downloaded from `Ludii's downloads page `_) 22 | in it. 23 | 4. Set up the project in your IDE to use the ``Ludii.jar`` file as a library. 24 | Most of the code requires this as a dependency. 25 | 5. Also set the project to use the other two ``.jar`` files that are already 26 | included in ``/LudiiTutorials/libs`` as libraries; these are 27 | only required for the unit tests in this repository. 28 | 6. The code examples for various programming tutorials can all be found in 29 | the ``/LudiiTutorials/src/ludii_tutorials`` package. Each 30 | of these ``.java`` files has a main method, which means that it can be 31 | run directly to see that tutorial's code in action. 32 | -------------------------------------------------------------------------------- /docs/ludii_terminology.rst: -------------------------------------------------------------------------------- 1 | .. _ludii_programming_terminology: 2 | 3 | Ludii Programming Terminology 4 | ============================= 5 | 6 | This page describes some of the Ludii terminology and core concepts 7 | relevant for programmatic users of Ludii: 8 | 9 | Game 10 | In Ludii, the ``Game`` type refers to a type of object that contains all the 11 | rules, equipment, functions etc. required to play. A single object of this 12 | type is instantiated when Ludii compiles the contents of a ``.lud`` file. 13 | Trial 14 | A ``Trial`` in Ludii corresponds to a record of a game played at a particular 15 | time (i.e., where a ``Game`` object would be "the game of *Chess*", a ``Trial`` 16 | object would be "a game of *Chess* as played by these persons at this time". 17 | Trials in Ludii store the full history of moves applied throughout the trial, 18 | as well as any already-determined player rankings. 19 | State 20 | A ``State`` stores all the relevant properties of a game state (minus the 21 | history of moves, which is contained in the Trial as described above). 22 | Context 23 | A ``Context`` object in Ludii describes the context of a current trial being 24 | played, and is generally the most convenient object to pass around through 25 | methods. It provides pointers to the "higher-level" ``Game`` object, as well 26 | as the "lower-level" ``Trial`` and ``State`` objects. 27 | Action 28 | ``Action`` objects in Ludii are atomic objects that, when applied to a game 29 | state, modify a single property of it. Note that these do **not** correspond 30 | directly to the decisions that players can make during gameplay. Users of 31 | Ludii will generally not need to interact with these low-level objects directly. 32 | Move 33 | ``Move`` objects are wrappers around one or more ``Action`` objects. Sometimes 34 | they may even contain references to additional rules that should be executed 35 | to compute additional Actions to apply after the Actions that it directly 36 | contains have been applied to a game state. Moves correspond to the decisions 37 | that players can actually directly make when playing. 38 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Ludii's Tutorials! 2 | ============================= 3 | 4 | Ludii is a general game system designed to play, evaluate and design a wide range of games, including board games, card games, dice games, mathematical games, 5 | and so on. These pages provide tutorials for designing games in Ludii, and various programmatic use cases of Ludii (implementing, testing and evaluating 6 | Artificial Intelligence in Ludii, running games for game evaluation or other purposes, etc.). 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | :caption: Getting Started 11 | 12 | installing_ludii 13 | installing_this_repo 14 | 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | :caption: Designing Games for Ludii 19 | 20 | lud_format_basics 21 | tutorial_amazons 22 | 23 | .. lud_format_advanced_features 24 | .. tutorial_advanced 25 | 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | :caption: Programming with Ludii 30 | 31 | ludii_terminology 32 | loading_games 33 | running_trials 34 | cheat_sheet 35 | 36 | .. inspecting_trials_and_states 37 | 38 | 39 | .. toctree:: 40 | :maxdepth: 2 41 | :caption: Implementing AIs for Ludii 42 | 43 | basic_ai_api 44 | 45 | .. ai_tips_and_tricks 46 | .. ai_visualisations 47 | 48 | 49 | .. toctree:: 50 | :maxdepth: 2 51 | :caption: Other Information 52 | 53 | contact_info 54 | acknowledgements 55 | 56 | 57 | Citing Ludii 58 | ------------ 59 | 60 | The following ``.bib`` entry may be used for citing the use of Ludii in papers:: 61 | 62 | @inproceedings{Piette2020Ludii, 63 | author = "{\'E}. Piette and D. J. N. J. Soemers and M. Stephenson and C. F. Sironi and M. H. M. Winands and C. Browne", 64 | booktitle = "Proceedings of the 24th European Conference on Artificial Intelligence (ECAI 2020)", 65 | title = "Ludii -- The Ludemic General Game System", 66 | pages = "411-418", 67 | year = "2020", 68 | editor = "G. De Giacomo and A. Catala and B. Dilkina and M. Milano and S. Barro and A. Bugarín and J. Lang", 69 | series = "Frontiers in Artificial Intelligence and Applications", 70 | volume = "325", 71 | publisher = "IOS Press" 72 | } 73 | -------------------------------------------------------------------------------- /test/game_loading/TestGameLoading.java: -------------------------------------------------------------------------------- 1 | package game_loading; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.io.File; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import org.junit.Test; 11 | 12 | import game.Game; 13 | import other.GameLoader; 14 | 15 | /** 16 | * Unit tests to make sure that our game loading code runs as expected for 17 | * the Game Loading tutorial. 18 | * 19 | * @author Dennis Soemers 20 | */ 21 | public class TestGameLoading 22 | { 23 | 24 | @Test 25 | public void testBuiltInGameLoading() 26 | { 27 | // The following built-in games are loaded by the Game Loading tutorial; test 28 | // that they all successfully compile and have the expected name 29 | final Game ticTacToe = GameLoader.loadGameFromName("Tic-Tac-Toe.lud"); 30 | assertNotNull(ticTacToe); 31 | assertEquals(ticTacToe.name(), "Tic-Tac-Toe"); 32 | 33 | final Game chess = GameLoader.loadGameFromName("/Chess.lud"); 34 | assertNotNull(chess); 35 | assertEquals(chess.name(), "Chess"); 36 | 37 | final List options = Arrays.asList("Board Size/19x19", "End Rules/Misere"); 38 | final Game hex = GameLoader.loadGameFromName("Hex.lud", options); 39 | assertNotNull(hex); 40 | assertEquals(hex.name(), "Hex"); 41 | 42 | // For Hex with the 19x19 board, also make sure that we have 361 sites in the board 43 | assertEquals(hex.board().numSites(), 361); 44 | } 45 | 46 | @Test 47 | public void testAmazonsTutorialLudLoading() 48 | { 49 | // Test that all of the .lud files for the separate steps of the tutorial 50 | // to implement Amazons in .lud format from scratch compile correctly 51 | assertNotNull(GameLoader.loadGameFromFile(new File("resources/luds/walkthrough_amazons/Step1.lud"))); 52 | assertNotNull(GameLoader.loadGameFromFile(new File("resources/luds/walkthrough_amazons/Step2.lud"))); 53 | assertNotNull(GameLoader.loadGameFromFile(new File("resources/luds/walkthrough_amazons/Step3.lud"))); 54 | assertNotNull(GameLoader.loadGameFromFile(new File("resources/luds/walkthrough_amazons/Step4.lud"))); 55 | assertNotNull(GameLoader.loadGameFromFile(new File("resources/luds/walkthrough_amazons/Step5.lud"))); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Welcome to Ludii's Tutorials! 4 | 5 | [![Documentation Status](https://readthedocs.org/projects/ludiitutorials/badge/?version=latest)](https://ludiitutorials.readthedocs.io/en/latest/?badge=latest) 6 | [![Build Status](https://travis-ci.org/Ludeme/LudiiTutorials.svg?branch=master)](https://travis-ci.org/Ludeme/LudiiTutorials) 7 | [![license](https://img.shields.io/github/license/Ludeme/LudiiTutorials)](LICENSE) 8 | ![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg) 9 | [![twitter](https://img.shields.io/twitter/follow/ludiigames?style=social)](https://twitter.com/intent/follow?screen_name=ludiigames) 10 | 11 | Ludii is a general game system designed to play, evaluate and design a wide range of games, including board games, card games, dice games, mathematical games, 12 | and so on. This repository contains example code and tutorials for designing games in Ludii, and various programmatic use cases of Ludii (implementing, testing and evaluating Artificial Intelligence in Ludii, running games for game evaluation or other purposes, etc.). 13 | 14 | ## Tutorials 15 | 16 | All of the tutorials may be viewed on [ReadTheDocs](https://ludiitutorials.readthedocs.io/). 17 | 18 | ## Other Useful Links 19 | 20 | - The [main Ludii Games page](https://ludii.games/). 21 | - The [Ludii Downloads page](https://ludii.games/download.php). 22 | - The [Ludii source code repository](https://github.com/Ludeme/Ludii/). 23 | - The [Ludii User Guide](https://ludii.games/downloads/LudiiUserGuide.pdf). 24 | - The [Ludii Language Reference](https://ludii.games/downloads/LudiiLanguageReference.pdf). 25 | - The [main Digital Ludeme Project page](http://www.ludeme.eu/). 26 | 27 | ## Citing Ludii 28 | 29 | The following `.bib` entry may be used for citing the use of Ludii in papers:: 30 | 31 | 32 | @inproceedings{Piette2020Ludii, 33 | author = "{\'E}. Piette and D. J. N. J. Soemers and M. Stephenson and C. F. Sironi and M. H. M. Winands and C. Browne", 34 | booktitle = "Proceedings of the 24th European Conference on Artificial Intelligence (ECAI 2020)", 35 | title = "Ludii -- The Ludemic General Game System", 36 | pages = "411-418", 37 | year = "2020", 38 | editor = "G. De Giacomo and A. Catala and B. Dilkina and M. Milano and S. Barro and A. Bugarín and J. Lang", 39 | series = "Frontiers in Artificial Intelligence and Applications", 40 | volume = "325", 41 | publisher = "IOS Press" 42 | } 43 | 44 | ## Acknowledgements 45 | 46 | This repository is part of the European Research Council-funded Digital Ludeme Project (ERC Consolidator Grant \#771292), being run by Cameron Browne at Maastricht University's Department of Data Science and Knowledge Engineering. 47 | 48 | European Research Council Logo 49 | -------------------------------------------------------------------------------- /docs/lud_format_basics.rst: -------------------------------------------------------------------------------- 1 | Writing .lud Descriptions -- Basics 2 | =================================== 3 | 4 | The .lud Format 5 | --------------- 6 | 7 | Game descriptions for Ludii are written in text files with a ``.lud`` extension. 8 | The language used to describe games for Ludii is defined by a `class grammar approach `_; 9 | it is automatically derived from the *ludeme* classes available in Ludii's ``.jar`` file. 10 | 11 | .. note:: 12 | A full, detailed Ludii Language Reference may be downloaded from `Ludii's downloads page `_. 13 | 14 | The basic premise of the language is that ludemes are described as their name, 15 | followed by a whitespace-separated list of arguments, all wrapped up in a pair of parentheses: 16 | 17 | .. code:: 18 | 19 | (ludemeName arg1 arg2 arg3 ...) 20 | 21 | Generally, the "outer" ludeme (the first one that is visible in a game description file) 22 | will be of the type ``(game ...)``. Arguments may be of any of the following types: 23 | 24 | 1. *Ludemes*: many ludemes can be used as arguments of other ludemes, which ultimately results in games 25 | being described as **trees of ludemes**. 26 | 2. *Strings*: typically used to provide meaningful names to games, pieces, regions, etc. 27 | Strings are always written in a pair of double quotes, for example: ``"Pawn"``. By convention, 28 | names usually start with an uppercase symbol. 29 | 3. *Booleans*: the boolean constants ``true`` and ``false`` may be used for any boolean 30 | (function) parameters. 31 | 4. *Integers*: integer constants can simply be written directly in any ``.lud`` descriptions, 32 | without requiring any special syntax: ``1``, ``-1``, ``100``, etc. 33 | 5. *Floats*: any number containing a dot will be interpreted as a float constant. 34 | For example: ``0.5``, ``-1.2``, ``5.5``, etc. In ludemes that expect floats as argument, 35 | numbers without dots (such as just ``1``) cannot be used, and the same number should be 36 | written to include a decimal component instead (e.g., ``1.0``). 37 | 38 | As a first example, the following code shows the full game description for *Tic-Tac-Toe*: 39 | 40 | .. code:: 41 | 42 | (game "Tic-Tac-Toe" 43 | (players 2) 44 | (equipment 45 | { 46 | (board (square 3)) 47 | (piece "Disc" P1) 48 | (piece "Cross" P2) 49 | } 50 | ) 51 | (rules 52 | (play (move Add (to (sites Empty)))) 53 | (end (if (is Line 3) (result Mover Win))) 54 | ) 55 | ) 56 | 57 | Viewing Ludii's Built-in Game Files 58 | ----------------------------------- 59 | 60 | The ``Ludii.jar`` file is not only a runnable program, but also an archive 61 | containing files. It can be extracted or opened like any regular ``.zip`` archive, 62 | which allows for the individual files inside it to be inspected. One of the 63 | top-level directories inside it is the ``/lud/`` directory. Under this directory, 64 | all of the ``.lud`` files for all the built-in Ludii games can be found. They 65 | may all serve as examples for game designers. 66 | -------------------------------------------------------------------------------- /src/ludii_tutorials/RunningTrials.java: -------------------------------------------------------------------------------- 1 | package ludii_tutorials; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import game.Game; 7 | import other.AI; 8 | import other.GameLoader; 9 | import other.context.Context; 10 | import other.model.Model; 11 | import other.trial.Trial; 12 | import utils.RandomAI; 13 | 14 | /** 15 | * Example class showing how we can run trials in Ludii 16 | * 17 | * @author Dennis Soemers 18 | */ 19 | public class RunningTrials 20 | { 21 | 22 | /** The number of trials that we'd like to run */ 23 | private static final int NUM_TRIALS = 10; 24 | 25 | /** 26 | * Main method 27 | * @param args Command-line arguments. 28 | */ 29 | public static void main(final String[] args) 30 | { 31 | // Load our game -- we only need to do this once, and can use it for many trials 32 | final Game game = GameLoader.loadGameFromName("Hex.lud"); 33 | 34 | // Prepare Context and Trial objects; these are also re-usable by resetting them, 35 | // but we'd have to copy them if we wanted to preserve all of the different objects 36 | // corresponding to different trials 37 | final Trial trial = new Trial(game); 38 | final Context context = new Context(game, trial); 39 | 40 | // Create AI objects that we'd like to use to play our Trials 41 | // Here we just use Ludii's built in Random AI, because it's fast 42 | // Ludii uses 1-based indexing for players, so we insert a null in the list first 43 | final List ais = new ArrayList(); 44 | ais.add(null); 45 | for (int p = 1; p <= game.players().count(); ++p) 46 | { 47 | ais.add(new RandomAI()); 48 | } 49 | 50 | // Now we play through multiple trials 51 | for (int i = 0; i < NUM_TRIALS; ++i) 52 | { 53 | // This starts a new trial (resetting the Context and Trial objects if necessary) 54 | game.start(context); 55 | System.out.println("Starting a new trial!"); 56 | 57 | // Random AI technically doesn't require initialisation, but it's good practice to do so 58 | // for all AIs at the start of every new trial 59 | for (int p = 1; p <= game.players().count(); ++p) 60 | { 61 | ais.get(p).initAI(game, p); 62 | } 63 | 64 | // This "model" object lets us go through a trial step-by-step using a single API 65 | // that works correctly for alternating-move as well as simultaneous-move games 66 | final Model model = context.model(); 67 | 68 | // We keep looping for as long as the trial is not over 69 | while (!trial.over()) 70 | { 71 | // This call simply takes a single "step" in the game, using the list of AIs we give it, 72 | // and 1.0 second of "thinking time" per move. 73 | // 74 | // A step is a single move in an alternating-move game (by a single player), or a set of 75 | // moves (one per active player) in a simultaneous-move game. 76 | model.startNewStep(context, ais, 1.0); 77 | } 78 | 79 | // When we reach this code, we know that the trial is over and we can see what ranks the 80 | // different players achieved 81 | final double[] ranking = trial.ranking(); 82 | 83 | for (int p = 1; p <= game.players().count(); ++p) 84 | { 85 | // Here we print the rankings as achieved by every agent, where 86 | // the "agent indices" correspond to the order of agents prior 87 | // to the game's start. This order will usually still be the same 88 | // at the end of a trial, but may be different if any swaps happened. 89 | // 90 | // ranking[p] tells you which rank was achieved by the player 91 | // who controlled the p'th "colour" at the end of a trial, and 92 | // trial.state().playerToAgent(p) tells you which agent (in the list 93 | // of AI objects) controls that colour at the end of the trial. 94 | System.out.println("Agent " + context.state().playerToAgent(p) + " achieved rank: " + ranking[p]); 95 | } 96 | System.out.println(); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /docs/loading_games.rst: -------------------------------------------------------------------------------- 1 | .. _loading_games_tutorial: 2 | 3 | Programmatically Loading Games 4 | ============================== 5 | 6 | Loading a Game by Name 7 | ---------------------- 8 | 9 | Ludii's ``player.utils.loading.GameLoader`` class provides static helper methods that 10 | may be used to programmatically load games. The simplest such method only takes 11 | a single argument; a ``String`` representing the name of a game. This argument 12 | should always include a ``.lud`` extension, and at least the filename of 13 | the game to load. Note that this can only be used to load games that are built 14 | into the ``Ludii.jar`` file, and not for loading games from external ``.lud`` files. 15 | It may be called as follows: 16 | 17 | .. code-block:: java 18 | 19 | final Game ticTacToe = GameLoader.loadGameFromName("Tic-Tac-Toe.lud"); 20 | final Game chess = GameLoader.loadGameFromName("/Chess.lud"); 21 | 22 | It is also allowed to prepend any part of the "folder structure" under which the 23 | ``.lud`` file is stored inside ``Ludii.jar``, starting from the top-level ``/lud/`` 24 | folder. Normally Ludii should be smart enough to know which game you wish 25 | to load as long as the full filename (without folders) is provided, so this 26 | should normally not be necessary. For example, it knows that ``Chess.lud`` refers 27 | to the game of *Chess*, even though that name could also be a match for other 28 | games such as *Double Chess.lud*. However, to avoid any risk of ambiguities, it 29 | can be useful to include a part of the folder structure (or even just a single 30 | ``/``, as in the second line of the example code above) in the provided name. 31 | 32 | Listing all Built-in Ludii Games 33 | -------------------------------- 34 | 35 | A list of names for **all** built-in games in your copy of ``Ludii.jar``, all 36 | of which may be used in ``GameLoader.loadGameFromName(...)`` calls, can be 37 | obtained using the following code: 38 | 39 | .. code-block:: java 40 | 41 | final String[] allGameNames = FileHandling.listGames(); 42 | 43 | This produces an array of Strings that looks as follows:: 44 | 45 | /lud/board/hunt/Adugo.lud 46 | /lud/board/hunt/Baghchal.lud 47 | /lud/board/hunt/Cercar La Liebre.lud 48 | ... 49 | 50 | .. note:: 51 | 52 | On some operating systems, the very first symbol in every String in this 53 | array may be a backslash instead of a forward slash. They may be freely 54 | replaced by forward slashes in game loading calls, and they should still 55 | load correctly. 56 | 57 | More advanced code to filter this list of games based on their properties is 58 | provided in :download:`ListLudiiGames.java <../src/ludii_tutorials/ListLudiiGames.java>`. 59 | 60 | Loading a Game from File 61 | ------------------------ 62 | 63 | The ``GameLoader.loadGameFromName()`` method can only be used to load built-in 64 | games that ship with Ludii. Programmatically loading games from other files 65 | (such as any games you may have implemented yourself!) can be loaded using a 66 | similar ``GameLoader.loadGameFromFile()`` method, which takes a ``File`` object 67 | as argument instead of a ``String``. An example, which loads the ``.lud`` file 68 | that we created at the end of :ref:`walkthrough-amazons`, is provided by the 69 | following code: 70 | 71 | .. code-block:: java 72 | 73 | final Game ourOwnAmazons = GameLoader.loadGameFromFile(new File("resources/luds/walkthrough_amazons/Step7.lud")); 74 | 75 | Loading Games with Options 76 | -------------------------- 77 | 78 | All of the examples discussed above load the default variants of the respective 79 | games. For each of the ``GameLoader`` methods described above, there is also a 80 | version that additionally takes a ``List`` object as second argument. 81 | Whenever an empty list is provided, such a call will be identical to the calls 82 | without this argument, simply causing a game with its default *Options* to be 83 | loaded. If the list is not empty, Ludii will try to interpret each of the 84 | provided Strings as a description of an *Option* to be loaded (instead of the 85 | default option). 86 | 87 | .. note:: 88 | 89 | If you try to load a game with options that are not defined for that game, 90 | Ludii will throw an exception. 91 | 92 | By default, *Hex* in Ludii is played on an ``11x11`` board. The following code 93 | shows how to load a different variant of *Hex*, by using two non-default options; 94 | we play on a ``19x19`` board, and we invert the winning condition by selecting 95 | the "Misere" end rule: 96 | 97 | .. code-block:: java 98 | 99 | final List options = Arrays.asList("Board Size/19x19", "End Rules/Misere"); 100 | final Game hex = GameLoader.loadGameFromName("Hex.lud", options); 101 | System.out.println("Num sites on board = " + hex.board().numSites()); 102 | 103 | In this code, the last line is used to verify that we did indeed correctly load 104 | a board of size ``19x19`` instead of the default ``11x11`` board; it prints that 105 | we have ``361`` sites on the board, which is correct! The ``11x11`` board would 106 | only have ``121`` sites. 107 | 108 | .. note:: 109 | 110 | This tutorial uses example code from the following source files: 111 | 112 | * `GameLoading.java `_. 113 | * `ListLudiiGames.java `_. 114 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # This file is execfile()d with the current directory set to its 5 | # containing dir. 6 | # 7 | # Note that not all possible configuration values are present in this 8 | # autogenerated file. 9 | # 10 | # All configuration values have a default; values that are commented out 11 | # serve to show the default. 12 | 13 | # If extensions (or modules to document with autodoc) are in another directory, 14 | # add these directories to sys.path here. If the directory is relative to the 15 | # documentation root, use os.path.abspath to make it absolute, like shown here. 16 | # 17 | # import os 18 | # import sys 19 | # sys.path.insert(0, os.path.abspath('.')) 20 | 21 | 22 | # -- General configuration ------------------------------------------------ 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | # 26 | # needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix(es) of source filenames. 37 | # You can specify multiple suffix as a list of string: 38 | # 39 | # source_suffix = ['.rst', '.md'] 40 | source_suffix = '.rst' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = 'Ludii Tutorials' 47 | copyright = '2020, The Ludii Team (https://ludii.games/contact.php)' 48 | author = 'The Ludii Team (https://ludii.games/contact.php)' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | version = '1.0.0' 56 | # The full version, including alpha/beta/rc tags. 57 | release = '1.0.0' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | # 62 | # This is also used if you do content translation via gettext catalogs. 63 | # Usually you set "language" from the command line for these cases. 64 | language = None 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | # This patterns also effect to html_static_path and html_extra_path 69 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 70 | 71 | # The name of the Pygments (syntax highlighting) style to use. 72 | pygments_style = 'default' 73 | 74 | # If true, `todo` and `todoList` produce output, else they produce nothing. 75 | todo_include_todos = False 76 | 77 | 78 | # -- Options for HTML output ---------------------------------------------- 79 | 80 | # The theme to use for HTML and HTML Help pages. See the documentation for 81 | # a list of builtin themes. 82 | # 83 | html_theme = 'sphinx_rtd_theme' 84 | 85 | # Theme options are theme-specific and customize the look and feel of a theme 86 | # further. For a list of options available for each theme, see the 87 | # documentation. 88 | # 89 | # See https://sphinx-rtd-theme.readthedocs.io/en/stable/configuring.html 90 | html_theme_options = { 91 | 'collapse_navigation': False, 92 | } 93 | 94 | html_logo = 'resources/ludii-logo-100x100.png' 95 | html_favicon = 'resources/ludii-logo-32x32.ico' 96 | 97 | # Add any paths that contain custom static files (such as style sheets) here, 98 | # relative to this directory. They are copied after the builtin static files, 99 | # so a file named "default.css" will overwrite the builtin "default.css". 100 | html_static_path = ['_static'] 101 | 102 | 103 | # -- Options for HTMLHelp output ------------------------------------------ 104 | 105 | # Output file base name for HTML help builder. 106 | htmlhelp_basename = 'LudiiTutorialsdoc' 107 | 108 | 109 | # -- Options for LaTeX output --------------------------------------------- 110 | 111 | latex_elements = { 112 | # The paper size ('letterpaper' or 'a4paper'). 113 | # 114 | # 'papersize': 'letterpaper', 115 | 116 | # The font size ('10pt', '11pt' or '12pt'). 117 | # 118 | # 'pointsize': '10pt', 119 | 120 | # Additional stuff for the LaTeX preamble. 121 | # 122 | # 'preamble': '', 123 | 124 | # Latex figure (float) alignment 125 | # 126 | # 'figure_align': 'htbp', 127 | } 128 | 129 | # Grouping the document tree into LaTeX files. List of tuples 130 | # (source start file, target name, title, 131 | # author, documentclass [howto, manual, or own class]). 132 | latex_documents = [ 133 | (master_doc, 'LudiiTutorials.tex', 'Ludii Tutorials Documentation', 134 | 'The Ludii Team', 'manual'), 135 | ] 136 | 137 | 138 | # -- Options for manual page output --------------------------------------- 139 | 140 | # One entry per manual page. List of tuples 141 | # (source start file, name, description, authors, manual section). 142 | man_pages = [ 143 | (master_doc, 'ludiitutorials', 'Ludii Tutorials Documentation', 144 | [author], 1) 145 | ] 146 | 147 | 148 | # -- Options for Texinfo output ------------------------------------------- 149 | 150 | # Grouping the document tree into Texinfo files. List of tuples 151 | # (source start file, target name, title, author, 152 | # dir menu entry, description, category) 153 | texinfo_documents = [ 154 | (master_doc, 'LudiiTutorials', 'Ludii Tutorials Documentation', 155 | author, 'LudiiTutorials', 'This repository contains various tutorials for programming with the Ludii general game system.', 156 | 'Miscellaneous'), 157 | ] 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /docs/running_trials.rst: -------------------------------------------------------------------------------- 1 | .. _running_trials: 2 | 3 | Running Trials 4 | ============== 5 | 6 | In this tutorial, we look at how to run Ludii trials programmatically. This is 7 | one of the core parts of Ludii that will be of interest to practically any 8 | programmatic user of Ludii. 9 | 10 | .. note:: 11 | 12 | This tutorial uses example code from the following source file: 13 | 14 | * `RunningTrials.java `_. 15 | 16 | In this tutorial, we'll run trials for the game of *Hex*. So, let's load that 17 | game first, based on the tutorial on :ref:`loading_games_tutorial`. We only 18 | need to do this a single time, and can re-use the resulting ``Game`` object 19 | for multiple trials (assuming we want to play multiple trials of the same game 20 | of course): 21 | 22 | .. code-block:: java 23 | 24 | final Game game = GameLoader.loadGameFromName("Hex.lud"); 25 | 26 | Now we'll construct ``Trial`` and ``Context`` objects (refer back to 27 | :ref:`ludii_programming_terminology` for what these mean). For this tutorial, 28 | it is sufficient to only instantiate one of each, because we *re-use* them 29 | by resetting their data whenever we're finished with one trial and ready to 30 | start the next one. 31 | 32 | .. code-block:: java 33 | 34 | final Trial trial = new Trial(game); 35 | final Context context = new Context(game, trial); 36 | 37 | Running trials also requires ``AI`` objects, which select moves during the 38 | trials. In this tutorial, we use ``RandomAI`` objects because they are very 39 | fast. Ludii uses 1-based indexing for anything related to players. Therefore, 40 | we first insert a ``null`` entry in the list of ``AI`` objects that we create: 41 | 42 | .. code-block:: java 43 | 44 | final List ais = new ArrayList(); 45 | ais.add(null); 46 | for (int p = 1; p <= game.players().count(); ++p) 47 | { 48 | ais.add(new RandomAI()); 49 | } 50 | 51 | Finally, we implement the main loop that executes multiple trials (played by our 52 | random AIs), and inspects the rankings achieved at the end of every trial: 53 | 54 | .. code-block:: java 55 | :linenos: 56 | 57 | for (int i = 0; i < NUM_TRIALS; ++i) 58 | { 59 | game.start(context); 60 | 61 | for (int p = 1; p <= game.players().count(); ++p) 62 | { 63 | ais.get(p).initAI(game, p); 64 | } 65 | 66 | final Model model = context.model(); 67 | 68 | while (!trial.over()) 69 | { 70 | model.startNewStep(context, ais, 1.0); 71 | } 72 | 73 | final double[] ranking = trial.ranking(); 74 | for (int p = 1; p <= game.players().count(); ++p) 75 | { 76 | System.out.println("Agent " + context.state().playerToAgent(p) + " achieved rank: " + ranking[p]); 77 | } 78 | } 79 | 80 | In line 3, we start the new trial. This call resets any data from any previous 81 | trials in the ``Context`` and ``Trial`` objects, and should always be called 82 | before starting a new trial. 83 | 84 | In lines 5-8, we allow our ``AI`` objects to perform any initialisation for the 85 | game. In this tutorial this would technically not be necessary, because Ludii's 86 | built-in ``RandomAI`` does not actually require any initialisation. But it is 87 | good practice to run this code before starting any new trial, because some 88 | algorithms may require initialisation. 89 | 90 | In line 10, we obtain a ``Model`` to play this trial. This may be understood as 91 | an object that handles the "control flow" of a trial for us; it has different 92 | implementations for alternating-move games than for simultaneous-move games. 93 | By using this object, it is possible to run trials of either of those types of 94 | games using the same code. 95 | 96 | In line 12, we keep looping until the trial is over (i.e. until a terminal game 97 | state has been reached). 98 | 99 | Line 14 performs most of the work involved in running a trial. It checks which 100 | player(s) is/are to move, requests the corresponding ``AI`` objects to select 101 | their moves, and applies them to the game. In an alternating-move game, this 102 | call applies a single move to the game (selected by the current mover). In a 103 | simultaneous-move game, this call requests moves from all active players, and 104 | applies them as one large "combined move". The code used in this tutorial is 105 | the simplest version of the ``startNewStep()`` method. The final ``1.0`` 106 | argument denotes the amount of "thinking time" for AIs, in seconds. There are 107 | also more complex versions of the method that allow the user to assign iteration 108 | or search depth limits to AIs, or even control whether this method should return 109 | immediately and run in a background thread. By default, it blocks and only 110 | returns when any moves have been applied. 111 | 112 | Finally, line 17 obtains the rankings of all the players, and lines 18-21 prints 113 | them. Note that rankings returned by the call in line 17 are indexed by "player 114 | indices", which refer to the "colours" of players in a game. In most games these 115 | indices will also continue to correspond to the indices for the list of ``AI`` 116 | objects, but in games that use the "Swap rule" this may not be the case. Before 117 | swapping, the default colours in *Hex* are red for Player 1, and blue for Player 118 | 2, which are controlled by the ``AI`` objects at indices 1 and 2, respectively. 119 | After swapping, the "player indices" remain unchanged. This means that even after 120 | swapping, Player 1 will still be red, and if the red player won, ``ranking[1]`` 121 | will return ``1.0`` (for the first rank). However, *Player 1* will after a swap 122 | be controlled by *Agent 2*, and the correct index to use in arrays such as the 123 | ``ranking`` array can be obtained using ``context.state().playerToAgent(p)``. 124 | -------------------------------------------------------------------------------- /docs/basic_ai_api.rst: -------------------------------------------------------------------------------- 1 | Basic API for AI Development 2 | ============================ 3 | 4 | .. note:: 5 | 6 | This tutorial expects AIs for Ludii to be implemented in Java. For 7 | experimental support for Python-based implementations, see 8 | https://github.com/Ludeme/LudiiPythonAI. 9 | 10 | Ludii expects custom AIs to be written in Java, and extend the abstract 11 | ``util.AI`` class. This tutorial describes the basic functions that are likely 12 | to be useful to override. AIs implemented according to this tutorial can be 13 | loaded and used to play games in the Ludii app the following 14 | `instructions from the Ludii Example AI repository `_. 15 | 16 | Selecting Actions 17 | ----------------- 18 | 19 | The most important method for custom AIs, which must always be overridden, has 20 | the following signature: 21 | 22 | .. code-block:: java 23 | 24 | public abstract Move selectAction 25 | ( 26 | final Game game, 27 | final Context context, 28 | final double maxSeconds, 29 | final int maxIterations, 30 | final int maxDepth 31 | ); 32 | 33 | This method takes the following parameters: 34 | 35 | * ``game``: A reference to the game we're playing. 36 | * ``context``: A copy of the ``Context`` that we're currently in (see 37 | :ref:`ludii_programming_terminology` for what a ``Context`` is). This also 38 | contains the game state in which we're expected to make a move. 39 | * ``maxSeconds``: The maximum number of seconds, after which the AI is expected 40 | to return a selected move. Ludii does not generally enforce this limit, though 41 | it will of course be enforced in competition settings. 42 | * ``maxIterations``: The maximum number of "iterations" that the AI is allowed 43 | to use, before it should return its moves. Here, we do not have a strict 44 | definition of what "iterations" should mean. Ludii does not ever enforce 45 | this limit. It will mostly be of interest for AI researchers. For example, 46 | we use this ourselves in some research papers, where we restrict multiple 47 | different MCTS agents to a fixed MCTS iteration count, rather than a time limit. 48 | * ``maxDepth``: The maximum depth that an AI is allowed to search, before it 49 | should return its move. Here, we do not have a strict 50 | definition of what "iterations" should mean. Ludii does not ever enforce 51 | this limit. It will mostly be of interest for AI researchers. 52 | 53 | The method should be implemented to return a ``Move`` object that the agent 54 | wishes to be applied. A full example of how this method is implemented by the 55 | `Example Random AI `_ 56 | is shown below: 57 | 58 | .. code-block:: java 59 | 60 | @Override 61 | public Move selectAction 62 | ( 63 | final Game game, 64 | final Context context, 65 | final double maxSeconds, 66 | final int maxIterations, 67 | final int maxDepth 68 | ) 69 | { 70 | FastArrayList legalMoves = game.moves(context).moves(); 71 | 72 | if (legalMoves.isEmpty()) 73 | return Game.createPassMove(context); 74 | 75 | // If we're playing a simultaneous-move game, some of the legal moves may be 76 | // for different players. Extract only the ones that we can choose. 77 | if (!game.isAlternatingMoveGame()) 78 | legalMoves = AIUtils.extractMovesForMover(legalMoves, player); 79 | 80 | final int r = ThreadLocalRandom.current().nextInt(legalMoves.size()); 81 | return legalMoves.get(r); 82 | } 83 | 84 | Initialisation and Cleanup 85 | -------------------------- 86 | 87 | Ludii's abstract ``AI`` class has two methods, with default empty implementations, 88 | to perform initialisation and cleanup. These may be overwritten for agents if it 89 | is necessary to perform initialisation steps before starting to play (for instance 90 | to load data from files), or to perform cleanup after finishing a game: 91 | 92 | .. code-block:: java 93 | 94 | public void initAI(final Game game, final int playerID){} 95 | public void closeAI(){} 96 | 97 | The ``initAI()`` method also tells the AI which player it is expected to start 98 | playing as in the upcoming trial. This is generally not important for AIs for 99 | alternating move-games -- since they can always figure out who the current mover 100 | is directly from the state for which they're asked to make a move -- but it is 101 | important for AIs that support simultaneous-move games. They can memorise this 102 | argument and know that that is the player for which they should return moves. 103 | This is why the 104 | `Example Random AI `_ 105 | has the following implementation: 106 | 107 | .. code-block:: java 108 | 109 | @Override 110 | public void initAI(final Game game, final int playerID) 111 | { 112 | this.player = playerID; 113 | } 114 | 115 | For AIs loaded inside the Ludii app, it is always guaranteed that ``initAI()`` 116 | will be called at least once before an AI is requested to make a move in a given 117 | trial. Note that it is possible that the method will be called much more 118 | frequently than that (for instance if the user starts jumping back and forth 119 | through a trial). For programmers implementing their own experiments, it is 120 | important that they remember to call this method themselves, as shown in 121 | :ref:`running_trials`. Similarly, Ludii will try to call ``closeAI()`` to allow 122 | for cleanup when possible, but AIs should not rely on this for them to function 123 | correctly. 124 | 125 | .. note:: 126 | 127 | Examples of full AI implementations can be found in the 128 | `Ludii Example AI repository on GitHub `_. 129 | -------------------------------------------------------------------------------- /docs/cheat_sheet.rst: -------------------------------------------------------------------------------- 1 | .. _cheat_sheet: 2 | 3 | Ludii Programming Cheat Sheet 4 | ============================= 5 | 6 | This page provides a cheat sheet of methods in Ludii that programmatic users 7 | (such as AI developers) are likely to require. On this page, we assume that 8 | you will at least have access to a ``context`` object of the type ``Context``. 9 | Such an object is typically passed around as a wrapper around the 10 | "current game state", or can be instantiated by yourself as described on the 11 | :ref:`running_trials` page. 12 | 13 | By convention, we describe methods that should be called on ``Context`` objects 14 | as ``context.method()``, methods that should be called on ``Game`` objects as 15 | ``game.method()``, methods that should be called on ``State`` objects as 16 | ``state.method()``, and methods that should be called on ``Trial`` objects as 17 | ``trial.method()``. Note that references to ``Game``, ``State``, or ``Trial`` 18 | objects can always be obtained through ``Context`` objects. 19 | 20 | .. contents:: :local: 21 | 22 | Game methods 23 | ------------ 24 | 25 | ``game.start(final Context context)`` 26 | Resets given ``Context`` object and starts it (applying any start rules to generate 27 | an initial game state). 28 | 29 | ``game.moves(final Context context).moves()`` 30 | Returns the list of legal moves for the current game state in the given ``Context`` 31 | object. 32 | 33 | * Will have to compute them on first call, but will immediately return the list 34 | on subsequent calls (until a move is applied to modify the game state, after 35 | which it will be necessary to re-compute). 36 | * **Warning**: do **not** modify the returned list! Copy it first. 37 | 38 | ``game.apply(final Context context, final Move move)`` 39 | Applies the given move to the current game state, causing a transition into a new state. 40 | 41 | ``game.players().count()`` 42 | Returns the number of players for this game. 43 | 44 | Context methods 45 | --------------- 46 | 47 | ``context.game()`` 48 | Returns a reference to a ``Game`` object. 49 | 50 | * The ``Game`` object encapsulates the rules of a game, i.e. *how* a game 51 | is played (and with what equipment). For example, this may represent 52 | "the game of Chess," as opposed to "this particular game of Chess played 53 | by Eric and Matthew." 54 | * A single reference can safely be used across many trials running in parallel. 55 | 56 | ``context.trial()`` 57 | Returns a reference to a ``Trial`` object. 58 | 59 | * Wrapper around the history of moves that have been played so far, from initial 60 | till current game state. 61 | 62 | ``context.state()`` 63 | Returns a reference to a ``State`` object. 64 | 65 | * Represents the current game state. 66 | 67 | ``new Context(final Context other)`` 68 | Returns a deep copy of the given ``other`` object. 69 | 70 | * Copy will have a different internal state for Random Number Generation, so any 71 | future stochastic events may play out differently for the copy than the original. 72 | 73 | ``new TempContext(final Context other)`` 74 | Returns a copy of the given ``other`` object for temporary use. 75 | 76 | * Modifications to the copy will not leak back into the original. 77 | * Modifications to the original **can** leak back into previously instantiated 78 | ``TempContext`` objects, or even corrupt them. 79 | * Can be more efficient than proper ``Context`` copies, when only temporarily 80 | required and discarded after use (before any new moves are applied to the original). 81 | 82 | ``context.active(final int who)`` 83 | Returns whether the given player is still active. 84 | 85 | * Did not already win or lose or tie or otherwise stop playing. 86 | 87 | Trial methods 88 | ------------- 89 | 90 | ``trial.over()`` 91 | Returns whether or not the trial is over (i.e., a terminal game state reached with no active players). 92 | 93 | ``trial.ranking()`` 94 | Returns an array of rankings, with one value per player. 95 | 96 | * First valid index is ``1``, for the first player. 97 | * A rank value of ``1.0`` is the best possible value, and a rank value of ``K`` is the worst possible value 98 | (for a game with ``K`` players). 99 | * Entries for players that are still active are always ``0.0``. 100 | 101 | ``trial.reverseMoveIterator()`` 102 | Returns an iterator that allows iteration through all the moves that have been applied, in reverse order. 103 | 104 | ``trial.getMove(final int idx)`` 105 | Returns the move at the given index. 106 | 107 | * If all the last ``X`` moves are required, using the reverse move iterator can be significantly more efficient. 108 | 109 | ``trial.moveNumber()`` 110 | The number of moves that have been played (excluding moves applied by game's start rules to generate 111 | initial game state). 112 | 113 | ``trial.numMoves()`` 114 | Total number of moves applied (including moves applied by game's start rules to generate 115 | initial game state). 116 | 117 | ``trial.numInitPlacement()`` 118 | The number of moves applied by the game's start rules to generate initial game state. 119 | 120 | State methods 121 | ------------- 122 | 123 | ``state.mover()`` 124 | Returns the current player to move (only correct for alternating-move games). 125 | 126 | ``state.playerToAgent(final int playerIdx)`` 127 | For a given player index (corresponding to a "colour"), returns the index of the "agent" 128 | (human or AI) who is currently in control of that player index. 129 | 130 | * Usually just returns ``playerIdx`` again, but can be different in game states where 131 | players have swapped colours during gameplay (commonly used in games such as Hex). 132 | 133 | ``state.owned()`` 134 | Returns an object of type ``Owned``, which is a data structure that stores which positions 135 | are occupied by any pieces for any player. 136 | 137 | ``state.stateHash()`` 138 | Returns a (Zobrist) hash code for the state that only accounts for a limited number of 139 | state variables (intuitively: only for elements that can be visibly seen on the board, 140 | i.e. which pieces are where). 141 | 142 | ``state.fullHash()`` 143 | Returns a (Zobrist) hash code for the state that accounts for (almost) all possibly-relevant 144 | state variables. 145 | -------------------------------------------------------------------------------- /src/ludii_tutorials/ListLudiiGames.java: -------------------------------------------------------------------------------- 1 | package ludii_tutorials; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.PrintWriter; 5 | import java.io.UnsupportedEncodingException; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.regex.Pattern; 9 | 10 | import game.Game; 11 | import main.CommandLineArgParse; 12 | import main.CommandLineArgParse.ArgOption; 13 | import main.CommandLineArgParse.OptionTypes; 14 | import main.FileHandling; 15 | import other.GameLoader; 16 | 17 | /** 18 | * Example class which lists built-in Ludii games (optionally only ones 19 | * that satisfy certain criteria). 20 | * 21 | * Run this file with -h or --help as command-line argument for more info 22 | * on command-line arguments. 23 | * 24 | * @author Dennis Soemers 25 | */ 26 | public class ListLudiiGames 27 | { 28 | 29 | /** 30 | * Main method 31 | * @param args Command-line arguments. 32 | */ 33 | @SuppressWarnings("unchecked") 34 | public static void main(final String[] args) 35 | { 36 | // Define our command-line options 37 | final CommandLineArgParse argParse = 38 | new CommandLineArgParse 39 | ( 40 | true, 41 | "Provides a list of all built-in Ludii games -- optionally only" 42 | + " those that satisfy certain criteria. If criteria are specified," 43 | + " they will only be checked on the default options of a game!" 44 | ); 45 | 46 | argParse.addOption(new ArgOption() 47 | .withNames("--no-alternating-moves") 48 | .help("Exclude all alternating-move games.") 49 | .withNumVals(0) 50 | .withType(OptionTypes.Boolean)); 51 | 52 | argParse.addOption(new ArgOption() 53 | .withNames("--no-simultaneous-moves") 54 | .help("Exclude all simultaneous-move games.") 55 | .withNumVals(0) 56 | .withType(OptionTypes.Boolean)); 57 | 58 | argParse.addOption(new ArgOption() 59 | .withNames("--no-deterministic-games") 60 | .help("Exclude all deterministic games.") 61 | .withNumVals(0) 62 | .withType(OptionTypes.Boolean)); 63 | 64 | argParse.addOption(new ArgOption() 65 | .withNames("--no-stochastic-games") 66 | .help("Exclude all stochastic (non-deterministic) games.") 67 | .withNumVals(0) 68 | .withType(OptionTypes.Boolean)); 69 | 70 | argParse.addOption(new ArgOption() 71 | .withNames("--no-imperfect-information") 72 | .help("Exclude all games with imperfect information.") 73 | .withNumVals(0) 74 | .withType(OptionTypes.Boolean)); 75 | 76 | argParse.addOption(new ArgOption() 77 | .withNames("--no-perfect-information") 78 | .help("Exclude all games with perfect information.") 79 | .withNumVals(0) 80 | .withType(OptionTypes.Boolean)); 81 | 82 | argParse.addOption(new ArgOption() 83 | .withNames("--no-stacking-games") 84 | .help("Exclude all games that involve stacking pieces.") 85 | .withNumVals(0) 86 | .withType(OptionTypes.Boolean)); 87 | 88 | argParse.addOption(new ArgOption() 89 | .withNames("--no-deduction-puzzles") 90 | .help("Exclude all deduction puzzles.") 91 | .withNumVals(0) 92 | .withType(OptionTypes.Boolean)); 93 | 94 | argParse.addOption(new ArgOption() 95 | .withNames("--num-players") 96 | .help("Only games where the number of players is included in the provided list" 97 | + " will be included.") 98 | .withNumVals("*") 99 | .withType(OptionTypes.Int)); 100 | 101 | argParse.addOption(new ArgOption() 102 | .withNames("--out-file") 103 | .help("Filepath to write list of games to. If not provided, will simply write to standard out stream.") 104 | .withNumVals("1") 105 | .withType(OptionTypes.String)); 106 | 107 | // Parse any command-line args 108 | if (!argParse.parseArguments(args)) 109 | return; 110 | 111 | // This collects ALL the built-in games 112 | final String[] allGameNames = FileHandling.listGames(); 113 | 114 | // Collect only those games for which all the criteria are satisfied 115 | final List namesToInclude = new ArrayList(); 116 | 117 | final boolean allowAlternatingMoves = !argParse.getValueBool("--no-alternating-moves"); 118 | final boolean allowSimultaneousMoves = !argParse.getValueBool("--no-simultaneous-moves"); 119 | final boolean allowDeterministicGames = !argParse.getValueBool("--no-deterministic-games"); 120 | final boolean allowStochasticGames = !argParse.getValueBool("--no-stochastic-games"); 121 | final boolean allowImperfectInformation = !argParse.getValueBool("--no-imperfect-information"); 122 | final boolean allowPerfectInformation = !argParse.getValueBool("--no-perfect-information"); 123 | final boolean allowStackingGames = !argParse.getValueBool("--no-stacking-games"); 124 | final boolean allowDeductionPuzzles = !argParse.getValueBool("--no-deduction-puzzles"); 125 | final List allowedNumPlayers = (List) argParse.getValue("--num-players"); 126 | 127 | for (final String gameName : allGameNames) 128 | { 129 | // Some of our criteria require compiling the game to check it, so we'll just do that here 130 | final Game game = GameLoader.loadGameFromName(gameName); 131 | 132 | // Check all our criteria 133 | if (game.isAlternatingMoveGame()) 134 | { 135 | if (!allowAlternatingMoves) 136 | continue; 137 | } 138 | else 139 | { 140 | if (!allowSimultaneousMoves) 141 | continue; 142 | } 143 | 144 | if (game.isStochasticGame()) 145 | { 146 | if (!allowStochasticGames) 147 | continue; 148 | } 149 | else 150 | { 151 | if (!allowDeterministicGames) 152 | continue; 153 | } 154 | 155 | if (game.hiddenInformation()) 156 | { 157 | if (!allowImperfectInformation) 158 | continue; 159 | } 160 | else 161 | { 162 | if (!allowPerfectInformation) 163 | continue; 164 | } 165 | 166 | if (game.isStacking() && !allowStackingGames) 167 | continue; 168 | 169 | if (game.isDeductionPuzzle() && !allowDeductionPuzzles) 170 | continue; 171 | 172 | if (allowedNumPlayers != null && !allowedNumPlayers.contains(Integer.valueOf(game.players().count()))) 173 | continue; 174 | 175 | // All checks passed, so we'll include it 176 | // We'll also remove the annoying backslash we may get on some OSes 177 | namesToInclude.add(gameName.replaceAll(Pattern.quote("\\"), "/")); 178 | } 179 | 180 | // Write our output 181 | final String outFilepath = argParse.getValueString("--out-file"); 182 | 183 | if (outFilepath != null) 184 | { 185 | // We want to write to a file 186 | try (final PrintWriter writer = new PrintWriter(outFilepath, "UTF-8")) 187 | { 188 | for (final String name : namesToInclude) 189 | { 190 | writer.println(name); 191 | } 192 | } 193 | catch (final FileNotFoundException | UnsupportedEncodingException e) 194 | { 195 | e.printStackTrace(); 196 | } 197 | } 198 | else 199 | { 200 | // We just write to standard out stream 201 | for (final String name : namesToInclude) 202 | { 203 | System.out.println(name); 204 | } 205 | } 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /docs/tutorial_amazons.rst: -------------------------------------------------------------------------------- 1 | .. _walkthrough-amazons: 2 | 3 | Writing Amazons in .lud Format 4 | ============================== 5 | 6 | This tutorial provides a step-by-step walkthrough of how to implement the game 7 | *Amazons*, from scratch, in the ``.lud`` format. 8 | 9 | *Amazons* is played on a ``10x10`` board. Each player has four amazons (chess queens), 10 | with other pieces used as arrows. Every turn consists of two moves. First, a 11 | player moves one of their amazons like a Chess queen, without crossing or 12 | entering a space occupied by another amazon or arrow. Second, it shoots an arrow 13 | to any space on the board that is along the unobstructed path of a queen's move 14 | from that place. The last player able to make a move wins. 15 | 16 | .. note:: 17 | 18 | For each of the following steps, the `Ludii Tutorials GitHub repository `_ 19 | contains a ``.lud`` file with the contents written in that step. They can all 20 | be loaded in Ludii and "played", although some of them may not be particularly 21 | interesting to play! 22 | 23 | Step 1: A Minimum Legal Game Description 24 | ---------------------------------------- 25 | 26 | We start out with the minimum description that results in a legal game description that 27 | may be loaded in Ludii by defining the number of players, the board by its shape and 28 | its size, the most used playing rules, and a basic ending rule. 29 | 30 | .. code-block:: python 31 | :linenos: 32 | 33 | (game "Amazons" 34 | (players 2) 35 | (equipment 36 | { 37 | (board (square 10)) 38 | } 39 | ) 40 | (rules 41 | (play 42 | (forEach Piece) 43 | ) 44 | 45 | (end 46 | (if 47 | (no Moves Next) 48 | (result Mover Win) 49 | ) 50 | ) 51 | ) 52 | ) 53 | 54 | Line 2 defines that we wish to play a two-player game, where it is implied by default 55 | to be an alternating-move game. Line 3 defines the equipment, and is used to list all 56 | the items used in the game. Line 5 defines that we wish to use a square board of size 57 | 10. By default, the square board is tiled by squares. Line 8 is used to define the 58 | rules of the game; the minimum rules to compile are the playing and the ending rules. 59 | Lines 9-11 describe the playing rules by using one of the simplest ``play`` rules 60 | available in Ludii; ``(forEach Piece)``, which simply defines that Ludii should 61 | loop through all pieces owned by a player, and extract legal moves from the piece types 62 | to generate the list of legal moves for a mover. Finally, lines 13-18 describe the ending 63 | rules. Here we want the player who last made a move to win the game whenever the next 64 | player has no move. 65 | 66 | Step 2: Defining the Pieces 67 | ------------------------------------------ 68 | 69 | In this step, we add the pieces to the equipment. 70 | 71 | .. code-block:: python 72 | :linenos: 73 | :emphasize-lines: 6,7 74 | 75 | (game "Amazons" 76 | (players 2) 77 | (equipment 78 | { 79 | (board (square 10)) 80 | (piece "Queen" Each) 81 | (piece "Dot" Neutral) 82 | } 83 | ) 84 | (rules 85 | (play 86 | (forEach Piece) 87 | ) 88 | 89 | (end 90 | (if 91 | (no Moves Next) 92 | (result Mover Win) 93 | ) 94 | ) 95 | ) 96 | ) 97 | 98 | Line 6 defines that each player should have a piece type labelled ``"Queen"``. 99 | Ludii will automatically label these as ``"Queen1"`` and ``"Queen2"`` for players 100 | 1 and 2, respectively. Additionally, in line 7 we define a ``Dot'' piece type, 101 | which is not owned by any player. This is the piece type that we will use in 102 | locations that players block by shooting their arrows. 103 | 104 | Step 3: Defining the Starting Rules 105 | ----------------------------------- 106 | 107 | We extend the game description listed above by adding ``start`` rules to place the pieces on the board: 108 | 109 | .. code-block:: python 110 | :linenos: 111 | :emphasize-lines: 11-16 112 | 113 | (game "Amazons" 114 | (players 2) 115 | (equipment 116 | { 117 | (board (square 10)) 118 | (piece "Queen" Each) 119 | (piece "Dot" Neutral) 120 | } 121 | ) 122 | (rules 123 | (start 124 | { 125 | (place "Queen1" {"A4" "D1" "G1" "J4"}) 126 | (place "Queen2" {"A7" "D10" "G10" "J7"}) 127 | } 128 | ) 129 | (play 130 | (forEach Piece) 131 | ) 132 | 133 | (end 134 | (if 135 | (no Moves Next) 136 | (result Mover Win) 137 | ) 138 | ) 139 | ) 140 | ) 141 | 142 | Lines 11-16 ensure that any game is started by placing objects of the two 143 | different types of queens in the correct starting locations. The labels 144 | used to specify these locations can be seen in Ludii by enabling 145 | "Show Coordinates" in Ludii's *View* menu. 146 | 147 | Step 4: Step 4: Adding the Final Rules for *Amazons* 148 | ---------------------------------------------------- 149 | 150 | To complete the game of *Amazons*, we need to allow players to move 151 | their queens and to shoot an arrow after moving a queen. This is implemented 152 | in the following game description: 153 | 154 | .. code-block:: python 155 | :linenos: 156 | :emphasize-lines: 6,7,17-22 157 | 158 | (game "Amazons" 159 | (players 2) 160 | (equipment 161 | { 162 | (board (square 10)) 163 | (piece "Queen" Each (move Slide (then (moveAgain)))) 164 | (piece "Dot" Neutral) 165 | } 166 | ) 167 | (rules 168 | (start 169 | { 170 | (place "Queen1" {"A4" "D1" "G1" "J4"}) 171 | (place "Queen2" {"A7" "D10" "G10" "J7"}) 172 | } 173 | ) 174 | (play 175 | (if (is Even (count Moves)) 176 | (forEach Piece) 177 | (move Shoot (piece "Dot0")) 178 | ) 179 | ) 180 | 181 | (end 182 | (if 183 | (no Moves Next) 184 | (result Mover Win) 185 | ) 186 | ) 187 | ) 188 | ) 189 | 190 | To make the queens able to move, inside the queen pieces, we have added the 191 | following: ``(move Slide (then (moveAgain))))``. By default, the ``(move Slide)`` 192 | ludeme defines that the piece is permitted to slide along any axis of the used 193 | board, as long as we keep moving through locations that are empty. No additional 194 | restrictions -- in terms of direction or distance, for example -- are required for 195 | queen moves. We have appended ``(then (moveAgain))`` in the queen moves. This means 196 | that, after any queen move, the same player gets to make another move. 197 | 198 | In lines 18-21, the ``play`` rules have been changed to no longer exclusively extract 199 | their moves from the pieces. Only at even move counts (0, 2, 4, etc.) do we still make 200 | a queen move (using ``(forEach Piece)``. At odd move counts, the moves are defined by 201 | ``(move Shoot (piece "Dot0"))``. This rule lets us shoot a piece of type ``"Dot0"`` into 202 | any empty position, starting from the location that we last moved to -- this is the location 203 | that our last queen move ended up in. This game description implements the full game of *Amazons* 204 | for Ludii. 205 | 206 | Once pieces are defined, their names are internally appended with the index of the owning player. 207 | For example, the above description defines a "Queen" piece for players 1 and 2, then the subsequent 208 | description refers to "Queen1" for "Queen" pieces belonging to Player 1 and "Queen2" for "Queen" 209 | pieces belonging to Player 2. The "Dot" piece is referred to as "Dot0", indicating that this is a 210 | neutral piece not owned by any player. Note that pieces can also be referred to by their undecorated 211 | names in the game description, e.g. "Queen" or "Dot", in which case the reference applies to all 212 | pieces with that name belonging to any player. 213 | 214 | Step 5: Improving Graphics 215 | ----------------------------------- 216 | 217 | The game description above plays correctly, but does not look appealing because it uses Ludii's 218 | default colours for the board. This can be easily improved by adding graphics metadata: 219 | 220 | .. code-block:: python 221 | :linenos: 222 | :emphasize-lines: 34-41 223 | 224 | (game "Amazons" 225 | (players 2) 226 | (equipment 227 | { 228 | (board (square 10)) 229 | (piece "Queen" Each (move Slide (then (moveAgain)))) 230 | (piece "Dot" Neutral) 231 | } 232 | ) 233 | (rules 234 | (start 235 | { 236 | (place "Queen1" {"A4" "D1" "G1" "J4"}) 237 | (place "Queen2" {"A7" "D10" "G10" "J7"}) 238 | } 239 | ) 240 | 241 | (play 242 | (if (is Even (count Moves)) 243 | (forEach Piece) 244 | (move Shoot (piece "Dot0")) 245 | ) 246 | ) 247 | 248 | (end 249 | (if 250 | (no Moves Next) 251 | (result Mover Win) 252 | ) 253 | ) 254 | ) 255 | ) 256 | 257 | (metadata 258 | (graphics 259 | { 260 | (piece Scale "Dot" 0.333) 261 | (board Style Chess) 262 | } 263 | ) 264 | ) 265 | 266 | Line 37 makes the "Dot" pieces smaller, and line 38 applies a Chess style to the board. 267 | --------------------------------------------------------------------------------