لأن العمل المشترك لمصلحتي
- إنه يوحد.
Matroskin
رمي الحصى في الماء ، انظر إلى الدوائر التي تشكلها ؛ وإلا فإن هذا الرمي سيكون متعة فارغة.
كوزما بروتكوف "خواطر وامثال".
مؤخرًا ، يوم الجمعة الماضي ، قررنا تنويع حياتنا اليومية بشكل طفيف من خلال تنظيم دورة برمجة. لم يتم تحديد جدول الأعمال على الفور. كانت هناك أفكار حول معالجة البيانات التحليلية ، والتعلم الآلي ، ولكن في النهاية ، استقروا على ألعاب الطاولة. أردنا إدخال عنصر المنافسة في الحدث ، ولكن ما الذي يجعل هذا الأمر سهلاً ، إن لم يكن الألعاب؟

لذلك ، كان الفريق الذي يرغب في المشاركة في المسابقة متاحًا ، وقد توصلوا أيضًا إلى تحديد صندوق الجائزة - ويبقى اتخاذ قرار بشأن اللعبة. اقترحت "أتاري جو" وكان لدي أكثر الأسباب إلحاحًا لذلك.
, '' ''?
- —
- — , « »
- « » , " "
- , , , "-"
- , ,
أتوقع اعتراضات على النقطة الأخيرة. نعم ، في الواقع ، تمت كتابة العديد من برامج الروبوت لـ Go وإيجاد تطبيق يمكن الوصول إليه ليس مشكلة على الإطلاق ، ولكن Atari Go هي لعبة مختلفة. لا يعتبر فقدان الأحجار الفردية في Go كارثة - فالأهداف في اللعبة مختلفة تمامًا. في Atari Go ، خسارة حتى حجر واحد هو هزيمة فورية.
نظرًا لأننا لم نرغب في ربط المشاركين بأي لغة برمجة واحدة ، فقد تقرر تطوير خدمة ويب توفر واجهة برمجة تطبيقات RESTلتسجيل تحركات المشاركين في البطولة. بعد ذلك ، بررت هذه الفكرة نفسها تمامًا. بالإضافة إلى Java ، استخدم المنافسون C ++ و Kotlin وحتى Lua كلغات تطوير. لاستبعاد التأثير المحتمل للأداء المختلف لأجهزة الكمبيوتر التي تم التخطيط لتشغيل الروبوتات عليها ، تم شراء مجموعتين من نفس النوع من أجهزة الكمبيوتر المصغرة واختبارها في البداية ، حيث تم تثبيت الإصدار 20 من نظام التشغيل Ubuntu Linux.

تم تطوير خدمة تتبع اللعبة في Node.js باستخدام إطار عمل Nest ، لكن هذا كان نصف المعركة فقط. الحقيقة هي أن الخادم قد تم تصميمه كحل عالمي لا يعتمد على تفاصيل أي من الألعاب. وتتمثل مهمتها في تسجيل تحركات اللاعبين في قاعدة البيانات والتحكم في الوقت ، لكنها لا تتحقق من صحة الحركات نفسها. مهمة Arbiter هي التحقق من صحة الحركات وتحديد الفائز ، وهو تطبيق JavaScript صغير يتصل بالخادم باستخدام مكتبة jQuery .
مزيد من التفاصيل الفنية
— , . PostgreSQL. « » , , :
user- token-, ( JWT-). games ( « » ). game_sessions. , ( ) user_games. game_moves.
, (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), , ( , , , ). , ( ). , ( ).

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), , ( , , , ). , ( ). , ( ).
يعد تطوير برامج الروبوت ، حتى بالنسبة لـ Atari Go ، أمرًا صعبًا. كانت الأيام الثلاثة المخصصة للمتسابقين للتحضير كافية فقط لكي تعمل الروبوتات ببساطة. بالإضافة إلى ذلك ، تبين أن أجهزة الكمبيوتر المصغرة التي أقيمت عليها المنافسة أقل إنتاجية بكثير من محطات العمل التي تم إجراء التصحيح عليها. كل هذا أدى إلى حقيقة أن الروبوتات ، خلال البطولة ، لم تتألق بذكاء خاص ، لكن ما زالت تحدث لحظات مضحكة.

هذا مثال على مركز نهائي في إحدى مباريات البطولة. كانت معركة الروبوتات مثيرة للاهتمام وشرسة. في النهاية ، حاول White الإمساك بالخصم في shichho ، لكنه لم يلاحظ أنه في الحركة التالية ، وضعه Black في وضع atari . أخطأ بوت وايت في محاولته مواصلة "السلم". استفاد الأسود على الفور من هذا - أخذ حجرًا وأنهى اللعبة.
كل هذا يوضح بشكل جيد طبيعة الأخطاء التي ارتكبها المشاركون في البطولة.
, , , . , , , . :
"" — . « », , «E6», . , , , — , «» , . «», , . .
, , : "", "" "". , , , . , , , . , , , .
, , , . , «».
"" — . « », , «E6», . , , , — , «» , . «», , . .
, , : "", "" "". , , , . , , , . , , , .
,
« », , . , :
, , 5x5. , , (, «» ). , . , 90, 180 270 , . . .
, . , heuristic. , . , « », , , , .
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. , . , « », , , , .
, , , . , «».
ومع ذلك ، فإن المرحلة التأهيلية للبطولة ، والتي لعب فيها كل مشارك مباراتين مع جميع المتقدمين (أبيض وأسود) ، سارت على ما يرام وحددنا اثنين من المتأهلين للتصفيات النهائية بناءً على عدد الانتصارات.

علاوة على ذلك ، استمرت الألعاب حتى ثلاثة انتصارات ، مع تناوب ترتيب الخطوة الأولى. بعد فوزه بنتيجة 3: 1 ، حصل فائز سعيد (ولا ينام لمدة ثلاث ليالٍ) على جائزته:

فلنصفق له!