「 コンピューター囲碁を作るかだぜ☆
↓ググれば なんでも載ってるだろ……☆」
「 コンピューター囲碁のポート番号は 9696
だったかだぜ☆?」
C:\Users\むずでょ\source\repos\kifuwarabe-air2019
「 ↑近年 Rustは仕様が変わったみたいだし見ておくかだぜ☆」
「 ↑Rustのインストールには rustup
を使えと書いてあるぜ☆
わたしは既に古いのが入っているから アップグレードだな☆」
「 ↑Visual Studio Code には Terminal も付いている☆ Powershell でやってしまおうぜ☆」
rustc --version
cargo --version
cargo new kifuwarabe-air2019
「 ↑ Cargo.toml
の内容は何もいじらなくても書かれていたぜ☆ 完璧だぜ☆」
cargo build
cargo run
「 ↑ しかし ソース・レポジトリのコードを GitHub の ローカル・レポジトリにコピーするのは めんどくさいよな☆」
go-to-git.ps1
# +
# | UTF-8 with BOM (Powershell)
# |
# | Folder copy.
# +
# +
# | フォルダをゴミ箱に移動する
# +
function Remove-FolderToRecycleBin($dir) {
if (Test-Path $dir -PathType Container) {
$fullpath = (Get-Item $dir).FullName
# +
# | こんな変なパスぜったい間違う☆(^~^)チェックだぜ☆(^~^)
# +
if ($fullpath -cmatch ".*\\kifuwarabe-air2019\\kifuwarabe-air2019") {
Write-Host "Remove | [$fullpath] directory."
Remove-Item $fullpath -Recurse
}
else {
Write-Host "Ignore | [$fullpath] directory."
}
}
else {
Write-Host "Ignore | [$dir] is not directory or not found."
}
}
$sr = "C:\Users\むずでょ\source\repos\kifuwarabe-air2019\kifuwarabe-air2019"
$ds = "C:\Users\むずでょ\Documents\GitHub\kifuwarabe-air2019\kifuwarabe-air2019"
Remove-FolderToRecycleBin($ds)
Write-Host "Copy | [$sr] --to--> [$ds]."
Copy-Item $sr -destination $ds -recurse
「 powershell ./go-to-git.ps1
と打鍵するのが うんこ じゃないか☆?」
「 Visual studio code の Explorer で右クリックして File explorer 開いて .ps1 ファイルをダブルクリックするからOK☆」
「 ↑ あとは Git hub desktop でなんとかする☆」
cargo check
「 ↑ コーディングのチェックをしてくれたりするのだろうか☆? あとで使おう☆」
cargo build --release
「 ↑ デバッグを外して .exe を作るには --release
を付ければいいらしい☆ これ1つ忘れるだけで大会では クソとノーマルの差が分かれる☆」
「 ロギングできるかどうかで プログラマーは クソとノーマルに大きく分かれるのに 公式のマニュアルに書いてないな☆」
Rust:logでログ出力を行う
Rustでのロギング
Crate log
Configure Logging
「 ここらへんの連中は ログをファイルに書き込まないのだろうか☆?
stderr
へのストリーム出力から ファイルへリダイレクトが基本かだぜ☆?」
「 遅~いロガーを掴んだら コンピューターは弱くなるわよ?」
「 じゃあ env_logger
一択な気がするな……☆ 使ってみるかだぜ☆?」
「 こんな風に 真似して Cargo.toml
を書いていけばいい☆
1990年代のジャパンなら 友だちの友だちをたどっていけば パソコンオタクの眼鏡のお兄さんがどこかに居て
家までやってきて パソコンのコマンドの叩き方とか教えてくれたが、
2019年頃のジャパンでは 学校のパソコン部に入ったのに何もせずに卒業して ブログのエントリを書いて
いいね! が付くのが流行りみたいだぜ☆」
「 何も いいね! ではないわよね。 何が いいの かしら?」
main.rs
#[macro_use]
extern crate log;
extern crate env_logger;
fn main() {
env_logger::init();
println!("Hello, println!");
trace!("Hello, trace!");
debug!("Hello, debug!");
info!("Hello, info!");
warn!("Hello, warn!");
error!("Hello, error!");
}
「 ↑環境変数を使って これだけ使い分けれれば十分だろう……☆
なんか Hello, println!
の出るタイミングがバラバラだな……、非同期か☆」
「 で、 PowerShell って どうやって ストリームをリダイレクトするんだぜ☆?」
command 2>&1 >> ./kifuwarabe-air2019.log
「 ↑コマンドの一覧が ログ・ファイルに書き出された……☆ ということは☆」
cargo run 2>&1 >> ./kifuwarabe-air2019.log
「 一応 >>
が利いたのか ファイルに 追加書き込みされているが、標準出力に何もでなくなったぜ☆」
cargo run 2>> ./kifuwarabe-air2019.log
「 だったら単に 標準エラーの2 を追加書き込みのリダイレクト >>
でファイルに送る指定だけする☆
これで平和だぜ☆」
「 リダイレクト先を2つに増やすことはできないんじゃないか☆?
とりあえず ログを取るときは しばらくこれで☆」
/*
# - Log level.
#$env:RUST_LOG = "trace"
#$env:RUST_LOG = "debug"
$env:RUST_LOG = "info"
#$env:RUST_LOG = "warn"
#$env:RUST_LOG = "error"
# - Log redirect.
cargo run 2>> ./kifuwarabe-air2019.log
*/
「 main.rs の冒頭にコメントを書いておいて これを貼り付ければいいだろ……☆」
Rust のお試しコードを実行する: cargo run --example
「 ↑rust には サンプル・プログラムの書き方がある☆」
「 examples
ディレクトリの下にあるファイルは cargo run --example
を使って実行できる☆」
Rust で TCP/IP ソケット通信をする際のモデル
Rustにっき/8日目・TCPサーバ
### Linux
lsof -i:3000
### Windows
netstat -aon | findstr 3000
「 接続される側で 待ち受けているサーバーが無いのだから、接続できるわけないんじゃないの?」
「 だったら そういうサンプルだと説明が欲しいぜ☆
サーバー書くか……☆」
examples/server.rs
/*
# Current directory
# cd ./kifuwarabe-air2019
cd C:\Users\むずでょ\source\repos\kifuwarabe-air2019\kifuwarabe-air2019
cargo run --example server
*/
use std::net::{TcpListener, TcpStream, ToSocketAddrs};
use std::io::{BufRead, BufReader, BufWriter, Error, Read, Write};
use std::thread;
/**
* See: [Rustにっき/8日目・TCPサーバ](https://cha-shu00.hatenablog.com/entry/2019/03/02/174532)
*/
fn main(){
let host = "localhost";
let port = 9696;
let url = format!("{}:{}", host, port);
let mut addrs = url.to_socket_addrs().unwrap();
// Change to ip v4.
if let Some(addr) = addrs.find(|x| (*x).is_ipv4()) {
// Success addr:127.0.0.1:9696
println!("Success sever-addr:{}", addr);
// Wait for connection.
let listener = TcpListener::bind(addr).expect("Error. failed to bind.");
for streams in listener.incoming() {
match streams {
Err(e) => { eprintln!("error: {}", e)},
Ok(stream) => {
println!("Create the thread.")
/*
// TODO: Create the thread.
thread::spawn(move || {
handler(stream).unwrap_or_else(|error| eprintln!("{:?}", error));
});
*/
}
}
}
} else {
eprintln!("Invalid Host:Port Number");
}
}
examples/client.rs
/*
# Current directory
# cd ./kifuwarabe-air2019
cd C:\Users\むずでょ\source\repos\kifuwarabe-air2019\kifuwarabe-air2019
cargo run --example client
*/
use std::net::{TcpStream, ToSocketAddrs};
use std::io::{BufRead, BufReader, BufWriter, Write};
/**
* See: [Rust で TCP/IP ソケット通信をする際のモデル](https://qiita.com/7ma7X/items/479ad9025a3368c2348f)
*/
fn main(){
let host = "localhost";
let port = 9696;
let url = format!("{}:{}", host, port);
let mut addrs = url.to_socket_addrs().unwrap();
// Change to ip v4.
if let Some(addr) = addrs.find(|x| (*x).is_ipv4()) {
// Success addr:127.0.0.1:3000
println!("Success client-addr:{}", addr);
match TcpStream::connect(addr) {
Err(_) => {
println!("Connection NG.");
}
Ok(stream) => {
println!("Connection Ok.");
// Buffering.
let mut reader = BufReader::new(&stream);
let mut writer = BufWriter::new(&stream);
read_something(&mut reader);
write_something(&mut writer, "hoge");
}
}
} else {
eprintln!("Invalid Host:Port Number");
}
}
fn read_something (reader: &mut BufReader<&TcpStream>) {
let mut msg = String::new();
reader.read_line(&mut msg).expect("RECEIVE FAILURE!!!");
// read_line は改行文字まで読む。
// 他のread系のメソッドもある (https://doc.rust-lang.org/std/io/trait.BufRead.html)
println!("{}", msg);
}
fn write_something (writer: &mut BufWriter<&TcpStream>, comment: &str) {
let msg = format!("MESSAGE: {}\n", comment);
// 送る側もたぶん改行文字を付けたほうがよいでしょう。
writer.write(msg.as_bytes()).expect("SEND FAILURE!!!");
writer.flush().unwrap();
}
「 で、使うには server.rs を実行して待機させておいてから、 client.rs で接続する☆
Visual studio code は Terminal ビューを1つしか出せないかもしれないので、
Visual studio code の2つ目を起動して Terminal ビューを使う☆」
「 コンピューター囲碁の通信プログラムって どんなもんだぜ☆?」
通信対局規約
19. The Go Text Protocol
GTP - Go Text Protocol
Go Text Protocol
「 Go text protocol だろう☆ 大会で使うコマンドは6つしかない☆」
CgfGoBan and Nngs_try
ASCIIコード表
「 ↑例えば Cgf Go Ban の cgf_pipe.cpp というファイルに実装が書いてあるぜ☆
ASCIIコード表は助けになるだろう☆」
\n
(Asciiコード 10)で終了。\n
を除く 32未満のAsciiコードはすべて無視。例えば \r
(Asciiコード 13) などを無視。\t
(水平タブ; Asciiコード 9) を 半角スペース (Asciiコード 32) に置換しているコードがあるが、これは働いてないと思う。#
ならコメント行だから無視。まだコマンドは送られてくる。\n
だけの行)は無視。まだコマンドは送られてくる。
「 あと、プロトコルの通信ログを見ないと分からないところだが☆、」
virihaure 462% ./gnugo --mode gtp
1 boardsize 7
=1
2 clear_board
=2
3 play black D5
=3
4 genmove white
=4 C3
5 play black C3
?5 illegal move
6 play black E3
=6
7 showboard
=7
A B C D E F G
7 . . . . . . . 7
6 . . . . . . . 6
5 . . + X + . . 5
4 . . . + . . . 4
3 . . O . X . . 3
2 . . . . . . . 2 WHITE (O) has captured 0 stones
1 . . . . . . . 1 BLACK (X) has captured 0 stones
A B C D E F G
8 quit
=8
「 サーバーは イコールを付けて ひと桁の数を返してくる☆
イコールと数の2文字だけが飛んで来たら無視しろだぜ☆ 3文字以上なら何か命令かもしれない☆」
=4 C3
なら C3
が move。pass
。 GnuGoは"PASS"と送ってくるから大文字・小文字を区別しないようにする。resign
。 SGMPは非対応。nngsは相手が落ちるかも?I
は無いから詰めること。
「 Go text protocol は いろいろコマンドがあるんだが 大会では 5つのコマンドしか送んな、ということだし
それだけ 実装すればいいんだが、一応 Cgp Go Ban にあるコマンドを見ておこう☆」
boardsize %d\n
- ban_size 何路盤。clear_board\n
- 盤面を初期化komi %.1f\n
- komi, - コミを設定name\n
- 名前を要求。version\n
- バージョンを要求。genmove black\n
- GnuGo に黒番打たせる。genmove white\n
- GnuGo に白番打たせる。play black %s\n
- GnuGo の盤面に黒石を置く。符号は Q4
みたいなやつ。play white %s\n
- GnuGo の盤面に白石を置く。final_status %s\n
- GnuGo に死活判定を聞く。符号は Q4
みたいなやつ。結果は次の6つ。
alive
dead
seki
white_territory
black_territory
dame
「 こっちから 通信で送るのは cgf_win.cpp の SendUpdateSetKifu
関数から辿っていけば 見つかるだろう……☆」
「 cgf_term.cpp にいろいろ書いてあるが、 SGMP
使ってそう☆」
「 大会は NNGS 1.1.22 を拡張したプロトコル
だから、 NNGS 1.1.22 が何を使ってるか調べるか……☆」
「 cgf_wsk.cpp
に、nngs への接続プログラムが書かれているぜ☆」
Send:
telnet nngs.computer-go.jp 9696
Send:
playera
Receive:
No Name Go Server (NNGS) version 1.1.14
「 次はこんな行が送られてくるから、
No Name Go Server (NNGS) version
という文字列を当てに行く☆ 当たれば☆、」
Send:
set client TRUE\n
「 こんな文字列を 有無を言わさず送る☆
大会では "set client FALSE"
と書いてあって どうすればいいのか☆」
Receive:
Set client to be True
「 よく分からんが client モードが True になった、と受信すれば……☆」
match %s B %d %d 0\n",nngs_player_name[1-fTurn],ban_size,nngs_minutes
「 matchコマンドで黒番に対局を申し込むのか☆ わたしは白番か☆」
Match [19x19] in 40 minutes requested with playera as Black.
「 ↑みたいな文字列を受け取る☆
"Match [%dx%d]",ban_size,ban_size
みたいな感じで当てに行く☆」
match playerb B 19 40 0
「 そして
"match %s W %d %d 0\n",nngs_player_name[1-fTurn],ban_size,nngs_minutes
みたいな感じの文字列を 有無を言わさず送る☆
わたしのプレイヤー名は playerb
、 黒番で 19路、 持ち時間は 40分とか そんな感じだろうか☆」
Received:
accepted.
「 黒番のときは accepted.
を受信するとのことだぜ☆ この直後から初手を送れるとのことだぜ☆」
Illegal
「 エラーがあった場合、 illegal
という文字が含まれているらしいぜ☆ こうなりゃ終了☆」
sprintf(tmp,"(%s): ",stone_str[1-fTurn]);
Pass
「 これを受け取ったら、相手はパス☆
送るのは pass
なのに、受け取るのは Pass
なのか☆」
T10
「 よく分からんが 指し手 は そのまんま飛んでくるのか? よく分からん☆」
14 |. # # O O # # . # O # # O O O # O O #| 14
13 |# # O # # # . # O O O # # # O O O . O| 13 Last Move: J1
12 |# O O # O . . # # O . O O O O . O O .| 12 #216 O (White)
「 なんで そんなところに……☆ よく誤検知しないものだぜ☆ A1 とか B10 とか☆」
Received:
You can check your score
「 自分が pass
したあとに相手が pass
すると、 Pass
は受信せず上のメッセージを受信するみたいだぜ☆」
「 これは プロトコル なのかだぜ☆?!」
Send:
done
「 地を計算するんだが めんどくさいんで done\n
を送る☆ すると☆、」
Received:
resigns.
「 というメッセージを受信して 通信完了だぜ☆ 棋譜はサーバーに保存されているはずだぜ☆」
Received:
9 {Game 1: test2 vs test1 : Black resigns. W 10.5 B 6.0}
「 対局が終わると 上記のようなメッセージを受信するので……☆」
strstr(str,"9 {Game") && strstr(str,"resigns.")
「 9 {Game
と resigns.
を含む行を当てに行き……☆」
sprintf(tmp,"%s vs %s",nngs_player_name[1],nngs_player_name[0]);
「 playera vs playerb
みたいな文字列を作って また当てに行き、当たったら 対局終了だぜ☆」
Received:
{%s has disconnected}
「 対戦相手が接続を切ったときは 上のメッセージが飛んでくる☆ %s
は相手プレイヤー名だぜ☆」
Received:
forfeits on time
「 どちらかの持ち時間が切れた場合は、上のメッセージと同じ行に 両プレイヤー名が載っているはずだぜ☆」
「 もっと古い時代に NNGS を使っていた Wing のサイトも見てみるかだぜ☆」
「 パスワードを変える方法はあっても、パスワードを入力する説明は見当たらないな☆」
「 CgfGoBan でパスワードを入れたことは無いだろ☆ パスワード無しでやろうぜ☆」
telnet nngs.computer-go.jp 9696
playera
No Name Go Server (NNGS) version
set client TRUE
Set client to be True
match %s B %d %d 0\n",nngs_player_name[1-fTurn],ban_size,nngs_minutes
Match [19x19]
match %s W %d %d 0\n",nngs_player_name[1-fTurn],ban_size,nngs_minutes
accepted.
Illegal
Pass
T10
pass
You can check your score
done
9 {Game 1: test2 vs test1 : Black resigns. W 10.5 B 6.0}
{%s has disconnected}
forfeits on time
Send:
telnet nngs.computer-go.jp 9696
「 ログインは 通信プロトコルと関係ないよな☆ 接続を確立するまでは TCP/IP だぜ☆」
「 ここから先は とにかく2人以上が サーバーにぶら下がっているという前提みたいだぜ☆」
Send:
match playerb B 19 40 0
「 相手のプレイヤー名も知っているという前提だな☆ 大会でなら知ってるしな☆」
Send (Opponent):
match playera W 19 40 0
「 サーバー側で シェイクハンド を監視しないといけないな☆」
Send:
d3
Send (Self/Opponent):
pass
Send (Opponent/Self):
pass
「 これは棋譜の上で2者が続けて pass
を送信するということだな☆」
Send (Self/Opponent):
done
Send (Opponent/Self):
done
「 これは本当は 郵便囲碁でお互いが 地計算して どれが生き石、どれが死に石 と言い合うところなんだが、
大会では めんどくさいんで スキップしようぜ、という意味合いだぜ☆」
「 しかし、白番が先にログインして 黒番は後から入れ、とか 何か しきたり がなかったかだぜ☆?」
「 2人しか接続してこないという前提で プログラム書けば 早くできるんじゃないの?」
// Create the thread.
thread::spawn(move || {
handler(stream).unwrap_or_else(|error| eprintln!("{:?}", error));
});
「 Rust でどう書けばいいのか分からん☆ とりあえず quit
を送ったらループから抜けるようにするかだぜ☆」
Struct std::io::BufStream
bufstream 0.1.4
「 ↑Read も Write もしたいバッファー・ストリームは どう書けばいいんだぜ☆?」
bufstream = "0.1.4"
extern crate bufstream;
// Buffering.
let mut reader = BufReader::new(&stream);
let mut writer = BufWriter::new(&stream);
「 ↑いや、そんなことをしなくても これでいけたのか……☆?」
「 ↑ \n
を末尾に付けておかないと read_line
が いつまで経っても終わらないぜ☆」
「 そうか、クライアントのスレッド と サーバーのスレッド との間は グローバル変数でやりとりするのではなく、
通信して やりとりするのかだぜ☆」
「 Rust で Postgresql データベースを使えたりしないの?」
<書きかけ>
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント