{"id":20418866,"url":"https://github.com/sehugg/starthinker","last_synced_at":"2025-04-12T17:40:29.542Z","repository":{"id":10083286,"uuid":"12140866","full_name":"sehugg/Starthinker","owner":"sehugg","description":"This is a C library for analysis and automatic play of sequential turn-based games such as chess, go, poker, etc.","archived":false,"fork":false,"pushed_at":"2024-03-11T03:54:50.000Z","size":67,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-26T12:01:39.962Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sehugg.png","metadata":{"files":{"readme":"README","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-08-15T18:37:23.000Z","updated_at":"2024-03-11T03:54:54.000Z","dependencies_parsed_at":"2024-11-15T06:35:09.035Z","dependency_job_id":null,"html_url":"https://github.com/sehugg/Starthinker","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sehugg%2FStarthinker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sehugg%2FStarthinker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sehugg%2FStarthinker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sehugg%2FStarthinker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sehugg","download_url":"https://codeload.github.com/sehugg/Starthinker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248607345,"owners_count":21132512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T06:35:04.335Z","updated_at":"2025-04-12T17:40:29.510Z","avatar_url":"https://github.com/sehugg.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\nStarthinker\n===========\n\nThis is a library for analysis and automatic play of sequential turn-based\ngames such as chess, go, poker, etc.\n\nGoals:\n\n* Simple API\n* Backtracking (i.e. no need to make/unmake moves, just make)\n* Support games with chance and/or hidden information\n* Fast evaluation\n* Tunable settings\n* Repeatable play sessions (i.e. deterministic)\n* Supports analysis of game properties\n\nThe AI engine currently uses a depth-first search with alpha-beta cutoff,\ntransposition tables and an optional Monte Carlo search on the lowest nodes.\n\nTo initialize the library:\n\n  AIEngineParams defaults = {};\n  defaults.num_players = 2;\n  defaults.max_search_level = 7;\n  ai_init(\u0026defaults);\n\nThis tells the library to initialize with two players and a max search depth\nof 7. This means the AI will search all moves up to 7 choices ahead. (We\nthink in terms of choices, not moves, for reasons which we'll explain\nbelow).\n\nYour game should have a state object which encapsulates game state --\nwhatever is used to represent playing pieces and derived data used to make\ndecisions about the game. For example, tic-tac-toe might be represented like\nthis:\n\n  typedef struct\n  {\n    int board[9]; // 3 x 3 = 9\n  } GameState;\n\nFor clarity in this example, we represent the board as a one-dimensional\narray -- you can use bitmasks or whatever else you want.\n\nNote that the current player taking the turn and score for each player is\nnot included the game state. These are built-in features of the library, so\nyou can leave them out of your game state.\n\nIn tic-tac-toe, there's only one choice a player can make during their turn:\nwhich space to place a piece. So we can define this in a make_move()\nfunction:\n\n  int make_move(const void* pstate, int index)\n  {\n    const GameState* state = pstate;\n\n    // put player's piece on board\n    int player = ai_current_player();\n    SET(state-\u003eboard[index], player+1);\n\n    // did we win?\n    int winner = player_won(state);\n    if (winner \u003e= 0)\n    {\n      ai_set_player_score(ai_current_player(), MAX_SCORE);\n      ai_game_over();\n      return 1;\n    }\n\n    // next player\n    if (ai_next_player())\n    {\n      play_turn(state);\n    }\n    return 1;\n  }\n\nThis function is called a \"choice function\" and will be repeatedly called by\nthe AI library.  Choice functions take two parameters: a state pointer and\nan integer parameter. Here the integer parameter indicates the position on\nthe 3x3 board to make our move.\n\nChoice functions return a positive value if the move was successful, or zero\nif the move failed for some reason (returning failure is neccessary in some\ngames where it's expensive to compute if the move will succeed beforehand,\nlike in chess when going in and out of check).\n\nNote that the state object is passed as const. This is intentional;\nmutations to the state object must use the library's mutation macros.\nInstead of writing:\n\n    state-\u003eboard[index] = player+1;\n\nWe write:\n\n    SET(state-\u003eboard[index], player+1);\n\nIt's important to use the mutation macros because they support backtracking\nin the AI library.  Use SET for setting variables in your state object\n(the macro assumes this a pointer named 'state' in the calling function) and\nSETGLOBAL for setting variables outside of your state object.\n\nMost games have either scoring or winning conditions. Here we have written a\nfunction called player_won() which checks to see if anyone has won yet, and\nif so we call ai_set_player_score() to set the winning player's score to\nWINNING_SCORE (defined in the library). You can set a player's score at any\ntime -- it acts as the evaluation function as well as the player score.\n\nThe last thing the move function does is to pass control to the next player\nto take the next turn:\n\n    // next player\n    if (ai_next_player())\n    {\n      play_turn(state);\n    }\n\nThe ai_next_player() function returns true while the AI library is searching\nfor the best move, so in this case we want to call play_turn() to take the\nnext turn. This causes the call tree to recurse and supports the depth-first\nsearch.\n\nNow that we have a function that implements a player's move, let's define\nplay_turn() and call this function:\n\n  bool play_turn(const GameState* state)\n  {\n    BoardMask mask = get_unoccupied_squares();\n    if (mask == 0)\n      return false; // no squares left, draw\n    else\n      return ai_choice(state, sizeof(GameState), make_move, 0, mask) != 0;\n  }\n\nHere we have written a get_unoccupied_squares() function which identifies\nvalid squares to make our next move.  If there are valid moves, we call the\nai_choice() function which calls our move function. Here are the parameters:\n\n- a pointer to our state object\n- size of our state object (not always needed, but we'll explain why later)\n- our move function\n- the lower bound on the parameter passed to the move function\n- a 64-bit mask which indicates valid values to pass to the move function\n\nThe ai_choice() function is the heart of the AI library. How it works\ndepends on what mode the library is in:\n\nIn Search mode, ai_choice() will call the move function multiple times,\npassing successive parameter values based on the lower bound and bitmask.\nThis will generally recurse down the stack, playing the game, switching\nplayer turns and backtracking state as neccessary.\n\nOnce the search completes with a valid move, we switch to Play mode. In this\nmode ai_choice() will call the move function only once with the parameter\ncorresponding to the best move as discovered in its game tree search.\n\nOnce you've defined these functions and initialized the startup state (in\nthis case it would just be an empty board) you can define your main loop. \nFor example, your main loop might look like this:\n\n  void play_game(const GameState* state)\n  {\n    while (player_won(state) \u003c 0 \u0026\u0026 play_turn(state))\n    {\n      print_board(state);\n    }\n  }\n\nThis loop plays a game turn with play_turn() until there's a draw or a\nplayer wins (assume player_won() returns -1 until someone wins).\n\n\nOPTIONS\n=======\n\nEach executable in games/ plays a game against itself, typically two-players\n(with the exception of the Freecell solver). Some common options:\n\n-v\tAdds verbosity.\n-s\tPrint tree search stats.\n-d n\tSets max tree search depth to n.\n-H n\tSets memoization hash table size to 1\u003c\u003cn.\n-r n\tSets random seed to n.\n-i n\tSets iterative deepening depth increment (not yet working?)\n-F\tDisables alpha/beta cutoff (full search).\n\nNote: When we say \"search depth\" we actually mean \"choice depth\". The number\nof choices per player turn depends on the game (i.e. for chess it's 2:\nsource square and destination square). So to extend the search tree by 1 ply\nyou need to add a depth of 2.\n\n\nTODO\n====\n\n* Address all //TODOs in the source code.\n* Look more critically at the search algorithm, esp. the integration of AB + Monte Carlo.\n* Better support for games with imperfect information (e.g. Stratego)\n* Iterative deepening\n* Interactive game play\n* Tests\n* More insightful game tree statistics\n* Don't convert to C++ (yet)\n* Complete rewrite by someone who actually knows what they're doing, or by myself\n\n\nDISCLAIMER\n==========\n\nThis is an experimental proof-of-concept and should not be relied as part of\na self-aware military supercomputer or anything that will monitor a crew on\nthe way to Jupiter. In fact, it's probably not yet ready to be used in a real\ngame.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsehugg%2Fstarthinker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsehugg%2Fstarthinker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsehugg%2Fstarthinker/lists"}