How we played the tournament

Because joint work, for my benefit

- it unites.



Matroskin



Throwing pebbles into the water, look at the circles they form; otherwise such throwing will be empty fun.



Kozma Prutkov "Thoughts and Aphorisms".




Recently, last Friday, we decided to slightly diversify our everyday life by holding a programming tournament. The agenda was not immediately determined. There were thoughts about analytical data processing, machine learning, but in the end, they settled on board games. We wanted to introduce an element of competition into the event, but what, if not games, makes this easy?





So, the team wishing to take part in the competition was available, they also figured out the prize fund - it remains to decide on the game. I proposed "Atari Go" and I had the most compelling reasons for that.



, '' ''?
— , , . , . (), , (). — 19x19. , , . .



. , , . , , «». , , ( ). , , . , :







. , . , . , , :







(), , . , « » . , , . , , . , « », , .



« » — , . — 9x9 . . , — . , , — , . « » . !



  • — , « »
  • « » , " "
  • , , , "-"
  • , ,


I foresee objections to the last point. Yes, indeed, many bots are written for Go and finding an accessible implementation is not at all a problem, but Atari Go is a different game. The loss of individual stones in Go is not considered a disaster - the goals in the game are completely different. In Atari Go, the loss of even one stone is immediate defeat.



Since we did not want to bind the participants with any one programming language, it was decided to develop a Web service that provides a REST APIto register the moves of the tournament participants. Subsequently, this idea fully justified itself. In addition to Java, the competitors used C ++, Kotlin, and even Lua as development languages. To exclude the possible impact of different performance of computers on which the bots were planned to run, two sets of the same type of mini PC were purchased and initially tested , on which the Ubuntu Linux OS 20th version was installed.







The game tracking service was developed in Node.js using the Nest framework , but that was only half the battle. The fact is that the server was conceived as a universal solution that does not depend on the specifics of any of the games. Its task is to record the players' moves in the database and control the time, but it does not check the moves themselves for correctness. Checking the correctness of the moves, as well as determining the winner, is the task of the Arbiter , a small JavaScript application that connects to the server using the jQuery library .



More technical details
— , . PostgreSQL. « » , , :





user- token-, ( JWT-). games ( « » ). game_sessions. , ( ) user_games. game_moves.



API
{
   "openapi":"3.0.0",
   "info":{
      "title":"Dagaz Server",
      "description":"Dagaz Server API description",
      "version":"0.0.1",
      "contact":{

      }
   },
   "tags":[
      {
         "name":"dagaz",
         "description":""
      }
   ],
   "servers":[

   ],
   "components":{
      "schemas":{
         "User":{
            "type":"object",
            "properties":{
               "id":{
                  "type":"number"
               },
               "is_admin":{
                  "type":"number"
               },
               "name":{
                  "type":"string"
               },
               "username":{
                  "type":"string"
               },
               "password":{
                  "type":"string"
               },
               "email":{
                  "type":"string"
               },
               "created":{
                  "format":"date-time",
                  "type":"string"
               },
               "deleted":{
                  "format":"date-time",
                  "type":"string"
               },
               "last_actived":{
                  "format":"date-time",
                  "type":"string"
               }
            },
            "required":[
               "id",
               "name",
               "username",
               "created",
               "last_actived"
            ]
         },
         "Pref":{
            "type":"object",
            "properties":{
               "id":{
                  "type":"number"
               },
               "user_id":{
                  "type":"number"
               },
               "game_id":{
                  "type":"number"
               },
               "created":{
                  "format":"date-time",
                  "type":"string"
               }
            },
            "required":[
               "game_id"
            ]
         },
         "Sess":{
            "type":"object",
            "properties":{
               "id":{
                  "type":"number"
               },
               "status":{
                  "type":"number"
               },
               "game_id":{
                  "type":"number"
               },
               "game":{
                  "type":"string"
               },
               "filename":{
                  "type":"string"
               },
               "created":{
                  "format":"date-time",
                  "type":"string"
               },
               "creator":{
                  "type":"string"
               },
               "changed":{
                  "format":"date-time",
                  "type":"string"
               },
               "closed":{
                  "format":"date-time",
                  "type":"string"
               },
               "players_total":{
                  "type":"number"
               },
               "winner":{
                  "type":"number"
               },
               "loser":{
                  "type":"number"
               },
               "score":{
                  "type":"number"
               },
               "last_setup":{
                  "type":"string"
               }
            },
            "required":[
               "game_id"
            ]
         },
         "Challenge":{
            "type":"object",
            "properties":{
               "id":{
                  "type":"number"
               },
               "session_id":{
                  "type":"number"
               },
               "user_id":{
                  "type":"number"
               },
               "user":{
                  "type":"string"
               },
               "player_num":{
                  "type":"number"
               }
            },
            "required":[
               "session_id"
            ]
         },
         "Join":{
            "type":"object",
            "properties":{
               "id":{
                  "type":"number"
               },
               "user_id":{
                  "type":"number"
               },
               "user":{
                  "type":"string"
               },
               "session_id":{
                  "type":"number"
               },
               "player_num":{
                  "type":"number"
               },
               "is_ai":{
                  "type":"number"
               }
            },
            "required":[
               "session_id"
            ]
         },
         "Move":{
            "type":"object",
            "properties":{
               "id":{
                  "type":"number"
               },
               "session_id":{
                  "type":"number"
               },
               "user_id":{
                  "type":"number"
               },
               "turn_num":{
                  "type":"number"
               },
               "move_str":{
                  "type":"string"
               },
               "setup_str":{
                  "type":"string"
               },
               "note":{
                  "type":"string"
               },
               "time_delta":{
                  "type":"number"
               },
               "time_limit":{
                  "type":"number"
               },
               "additional_time":{
                  "type":"number"
               }
            },
            "required":[
               "session_id",
               "user_id",
               "move_str"
            ]
         },
         "Result":{
            "type":"object",
            "properties":{
               "id":{
                  "type":"number"
               },
               "session_id":{
                  "type":"number"
               },
               "user_id":{
                  "type":"number"
               },
               "result_id":{
                  "type":"number"
               },
               "score":{
                  "type":"number"
               }
            },
            "required":[
               "session_id",
               "result_id"
            ]
         }
      }
   },
   "paths":{
      "/api/auth/login":{
         "post":{
            "operationId":"AppController_login",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/User"
                        }
                     }
                  }
               }
            },
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               }
            },
            "security":[
               {
                  "basic":[

                  ]
               }
            ]
         }
      },
      "/api/auth/refresh":{
         "get":{
            "operationId":"AppController_refresh",
            "parameters":[

            ],
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               }
            },
            "security":[
               {
                  "basic":[

                  ]
               }
            ]
         }
      },
      "/api/users":{
         "get":{
            "operationId":"UsersController_findAll",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         },
         "post":{
            "operationId":"UsersController_update",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/User"
                        }
                     }
                  }
               }
            },
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/users/{id}":{
         "get":{
            "operationId":"UsersController_findUsers",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         },
         "delete":{
            "operationId":"UsersController_delete",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "403":{
                  "description":"Forbidden."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/preferences":{
         "get":{
            "operationId":"PreferencesController_findAll",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         },
         "post":{
            "operationId":"PreferencesController_create",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/Pref"
                        }
                     }
                  }
               }
            },
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/preferences/{id}":{
         "delete":{
            "operationId":"PreferencesController_delete",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/session":{
         "get":{
            "operationId":"SessionController_findAll",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "403":{
                  "description":"Forbidden."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         },
         "post":{
            "operationId":"SessionController_create",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/Sess"
                        }
                     }
                  }
               }
            },
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/session/{id}":{
         "get":{
            "operationId":"SessionController_getSession",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/session/close":{
         "post":{
            "operationId":"SessionController_close",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/Sess"
                        }
                     }
                  }
               }
            },
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "403":{
                  "description":"Forbidden."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/challenge":{
         "get":{
            "operationId":"ChallengeController_findAll",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         },
         "post":{
            "operationId":"ChallengeController_create",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/Challenge"
                        }
                     }
                  }
               }
            },
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/challenge/{id}":{
         "delete":{
            "operationId":"ChallengeController_delete",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/join/{id}":{
         "get":{
            "operationId":"JoinController_findJoined",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/join":{
         "post":{
            "operationId":"JoinController_join",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/Join"
                        }
                     }
                  }
               }
            },
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/move/all/{id}":{
         "get":{
            "operationId":"MoveController_getMoves",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "403":{
                  "description":"Forbidden."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/move/unconfirmed/{id}":{
         "get":{
            "operationId":"MoveController_getUnconfirmedMove",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "403":{
                  "description":"Forbidden."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/move/confirmed/{id}":{
         "get":{
            "operationId":"MoveController_getConfirmedMove",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/move":{
         "post":{
            "operationId":"MoveController_update",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/Move"
                        }
                     }
                  }
               }
            },
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/move/confirm":{
         "post":{
            "operationId":"MoveController_confirm",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/Move"
                        }
                     }
                  }
               }
            },
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "403":{
                  "description":"Forbidden."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/result/{id}":{
         "get":{
            "operationId":"ResultController_getMoves",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/result":{
         "post":{
            "operationId":"ResultController_join",
            "parameters":[

            ],
            "requestBody":{
               "required":true,
               "content":{
                  "application/json":{
                     "schema":{
                        "type":"array",
                        "items":{
                           "$ref":"#/components/schemas/Result"
                        }
                     }
                  }
               }
            },
            "responses":{
               "201":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "404":{
                  "description":"Not Found."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      },
      "/api/game":{
         "get":{
            "operationId":"GameController_allGames",
            "parameters":[

            ],
            "responses":{
               "200":{
                  "description":"Successfully."
               },
               "401":{
                  "description":"Unauthorized."
               },
               "500":{
                  "description":"Internal Server error."
               }
            },
            "security":[
               {
                  "bearer":[

                  ]
               }
            ]
         }
      }
   }
}




, (POST api/session), (POST api/challenge) (POST api/move). (GET api/challenge) (POST api/join). , , , (GET api/move/confirmed/:id, id — ) (POST api/move).



, , , . , (games.main_time), (games.additional_time), . ( ), . . , , , .



( ), — , . — , (setup_str), , ( , , , ). , ( ). , ( ).



Developing bots, even for Atari Go, is difficult. The three days allocated to the contestants for preparation was enough only for the bots to simply work. In addition, the mini-PCs on which the competition was held turned out to be significantly less productive than the workplaces on which debugging was performed. All this led to the fact that the bots, during the tournament, did not shine with special intelligence, but funny moments still happened.





This is an example of a final position in one of the tournament games. The bots' fight was interesting and fierce. In the end, White tried to catch the opponent in the shichho , but did not notice that on the next move Black put him in the atari position . White's bot made a mistake trying to continue the “ladder”. Black immediately took advantage of this - took a stone and ended the game.



All this well illustrates the nature of the mistakes made by the tournament participants.
, , , . , , , . :







"" — . « », , «E6», . , , , — , «» , . «», , . .







, , : "", "" "". , , , . , , , . , , , .



,
« », , . , :



1000    ;  
-----
?????
??B??
?B.??
?????
?????


, , 5x5. , , (, «» ). , . , 90, 180 270 , . . .



Dagaz.AI.Patterns.push({re: /.{7}B.{3}B0.{12}/, price: 1000});
Dagaz.AI.Patterns.push({re: /.{11}B0.{4}B.{7}/, price: 1000});
Dagaz.AI.Patterns.push({re: /.{12}0B.{3}B.{7}/, price: 1000});
Dagaz.AI.Patterns.push({re: /.{7}B.{4}0B.{11}/, price: 1000});


, . , heuristic. , . , « », , , , .



, , , . , «».



Nevertheless, the qualifying stage of the tournament, in which each of the participants played two games with all the applicants (white and black), went well, and by the number of victories we determined two finalists.







Further, the games continued until three victories, with the alternation of the order of the first move. Having won with a final score of 3: 1, the contented (and had not slept for three nights) winner took his prize:





Let's applaud him!




All Articles