Skip to content

Instantly share code, notes, and snippets.

@ohga
Last active October 6, 2017 22:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ohga/857783a36b0f2928d06e9991c8951151 to your computer and use it in GitHub Desktop.
Save ohga/857783a36b0f2928d06e9991c8951151 to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"strconv"
"strings"
)
type WaffleWrapper struct {
Engine string `json:"engine"`
FadeoutCommand string `json:"fadeout_command"`
LogFile string `json:"logfile"`
TimeThreshold int `json:"time_threshold"`
TimeToOverwrite int `json:"time_to_overwrite"`
MaxScore int `json:"max_score"`
ChallengeChangeScore int `json:"challenge_change_score"`
Distance int `json:"distance"`
TimeToOverwrite2 int `json:"time_to_overwrite2"`
CancelScoreUpperLimit int `json:"cancel_score_upperlimit"`
CancelScoreLowerLimit int `json:"cancel_score_lowerlimit"`
Score int
Depth int
Nodes int
OldScore int
OldDepth int
OldNodes int
OldScore2 int
OldDepth2 int
OldNodes2 int
IsBlack bool
IsFadeoutExecuted bool
HasPonder bool
Rollback bool
Btime int
Wtime int
Binc int
Winc int
Byoyomi int
}
func (param *WaffleWrapper) Clear() {
param.Score = 0
param.Depth = 0
param.Nodes = 0
param.OldScore = 0
param.OldDepth = 0
param.OldNodes = 0
param.OldScore2 = 0
param.OldDepth2 = 0
param.OldNodes2 = 0
param.IsBlack = false
param.IsFadeoutExecuted = false
param.HasPonder = false
param.Rollback = false
param.Btime = 0
param.Wtime = 0
param.Binc = 0
param.Winc = 0
param.Byoyomi = 0
}
func (param *WaffleWrapper) Load(filename string) {
raw, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
json.Unmarshal(raw, param)
}
func (param *WaffleWrapper) Build() {
// 持ち時間が無い or mate が出てる。
if (param.IsBlack && param.Btime < param.TimeToOverwrite2) ||
(!param.IsBlack && param.Wtime < param.TimeToOverwrite2) ||
param.IsFadeoutExecuted {
log.Printf("cancel: Time < %d or fadeout executed .\n", param.TimeToOverwrite2)
return
}
// スコアが上限を超えてるならどうでもいい。
if param.Score > param.MaxScore {
log.Printf("cancel: Score(%d) > %d.\n", param.Score, param.MaxScore)
return
}
// 考えどころの評価値。ただし局面数や深さが一手前より増えてるならいいや。
if (param.Score < param.CancelScoreUpperLimit && param.Score > param.CancelScoreLowerLimit) &&
(param.Nodes < param.OldNodes || param.Depth < param.OldDepth) {
log.Printf("cancel: Score %d < %d < %d .\n", param.CancelScoreLowerLimit, param.Score, param.CancelScoreUpperLimit)
log.Printf(" : Depth %d < %d .\n", param.Depth, param.OldDepth)
log.Printf(" : Nodes %d < %d .\n", param.Nodes, param.OldNodes)
return
}
// 一手前との比較。増加量が閾値を超えてるなら時間攻め
tmp := param.Score - param.OldScore
if tmp > param.ChallengeChangeScore {
log.Printf("attack: (%d - %d) > %d.\n", param.Score, param.OldScore, param.ChallengeChangeScore)
if param.IsBlack {
param.Btime = param.TimeToOverwrite
} else {
param.Wtime = param.TimeToOverwrite
}
return
}
// https://github.com/32hiko/HoneyWaffleWCSC27/commit/31c39879f4e89af1e1b2326062b71200c72f6770#diff-8b54bb62d2105fd50238d34fba61c236
if param.IsBlack {
if (param.Btime > param.Wtime) && (param.TimeThreshold > param.Wtime) {
param.Btime = param.TimeToOverwrite
}
if (param.Wtime - param.Btime) > param.Distance {
param.Btime = param.TimeToOverwrite2
}
} else {
if (param.Wtime > param.Btime) && (param.TimeThreshold > param.Btime) {
param.Wtime = param.TimeToOverwrite
}
if (param.Btime - param.Wtime) > param.Distance {
param.Wtime = param.TimeToOverwrite2
}
}
}
func (param *WaffleWrapper) SetGoCommand(text string) {
param.HasPonder = false
param.Btime = 0
param.Wtime = 0
param.Binc = -1
param.Winc = -1
param.Byoyomi = -1
tmp := ""
arr := strings.Split(text, " ")
for _, row := range arr {
if row == "ponder" {
param.HasPonder = true
continue
}
nn, err := strconv.Atoi(row)
if err != nil {
tmp = row
}
if nn == 0 {
continue
}
switch tmp {
case "btime":
param.Btime = nn
case "wtime":
param.Wtime = nn
case "binc":
param.Binc = nn
case "winc":
param.Winc = nn
case "byoyomi":
param.Byoyomi = nn
}
nn = 0
}
}
func (param *WaffleWrapper) SetGoCommandFromInfo(text string) {
tmp := ""
arr := strings.Split(text, " ")
for _, row := range arr {
nn, err := strconv.Atoi(row)
if err != nil {
tmp = row
}
if nn == 0 {
continue
}
switch tmp {
case "cp":
param.Score = nn
case "depth":
param.Depth = nn
case "nodes":
param.Nodes = nn
case "mate":
log.Printf(" >>: mate %d\n", nn)
if !param.IsFadeoutExecuted {
param.IsFadeoutExecuted = true
go param.ExecuteFadeoutCommand()
}
}
nn = 0
}
}
func (param *WaffleWrapper) SplitCommandString(text string) []string {
var cmds []string
var tmp string
var quote bool = false
var escape bool = false
for _, chr := range text {
switch chr {
case '\\':
if escape {
escape = false
tmp = tmp + string(chr)
continue
}
escape = true
case '"':
if escape {
escape = false
tmp = tmp + string(chr)
continue
}
quote = !quote
case ' ':
if quote {
tmp = tmp + string(chr)
continue
}
cmds = append(cmds, tmp)
tmp = ""
default:
tmp = tmp + string(chr)
}
}
if len(tmp) != 0 {
cmds = append(cmds, tmp)
}
return cmds
}
func (param *WaffleWrapper) ExecuteFadeoutCommand() {
log.Printf(" >>: exec %s\n", param.FadeoutCommand)
// cmds := strings.Split(param.FadeoutCommand, " ")
cmds := param.SplitCommandString(param.FadeoutCommand)
var cmd *exec.Cmd
if len(cmds) == 1 {
cmd = exec.Command(cmds[0])
} else {
cmd = exec.Command(cmds[0], cmds[1:]...)
}
if err := cmd.Start(); err != nil {
log.Println(err)
}
}
func (param *WaffleWrapper) GetGoCommand() string {
var rtn = "go"
if param.HasPonder {
rtn = fmt.Sprintf("%s ponder", rtn)
}
if param.Btime >= 0 {
rtn = fmt.Sprintf("%s btime %d", rtn, param.Btime)
}
if param.Wtime >= 0 {
rtn = fmt.Sprintf("%s wtime %d", rtn, param.Wtime)
}
if param.Byoyomi >= 0 {
rtn = fmt.Sprintf("%s byoyomi %d", rtn, param.Byoyomi)
}
if param.Binc >= 0 {
rtn = fmt.Sprintf("%s binc %d", rtn, param.Binc)
}
if param.Winc >= 0 {
rtn = fmt.Sprintf("%s winc %d", rtn, param.Winc)
}
return rtn
}
func (param *WaffleWrapper) FromCommandToStdout(scanner *bufio.Scanner) {
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "info") {
param.SetGoCommandFromInfo(line)
//log.Printf("<-----: info cp %d(%d) depth %d nodes %d.\n", param.Score, param.OldScore, param.Depth, param.Nodes)
} else if strings.HasPrefix(line, "bestmove") {
log.Printf("<-----: %s.\n", line)
if param.Rollback {
log.Printf("----->: rollback.\n")
param.Score = param.OldScore
param.Depth = param.OldDepth
param.Nodes = param.OldNodes
param.OldScore = param.OldScore2
param.OldDepth = param.OldDepth2
param.OldNodes = param.OldNodes2
}
} else {
log.Printf("<-----: %s.\n", line)
}
fmt.Printf("%s\n", line)
}
}
func (param *WaffleWrapper) FromQueToCommand(ch chan string, stdin io.WriteCloser) {
for {
text, ok := <-ch
if !ok {
return
}
if !strings.HasPrefix(text, "position") {
log.Printf("----->: %s.\n", text)
} else {
arr := strings.Split(text, " ")
if len(arr) > 4 {
log.Printf("----->: position (%d) .. %s %s.\n", len(arr)-4, arr[len(arr)-2], arr[len(arr)-1])
}
}
if strings.HasPrefix(text, "go") {
param.SetGoCommand(text)
param.Rollback = false
param.Build()
text = param.GetGoCommand()
log.Printf(" >>: %s.(%d .. %d)\n", text, param.OldScore, param.Score)
param.OldScore2 = param.OldScore
param.OldDepth2 = param.OldDepth
param.OldNodes2 = param.OldNodes
param.OldScore = param.Score
param.OldDepth = param.Depth
param.OldNodes = param.Nodes
} else if strings.HasPrefix(text, "stop") {
param.Rollback = true
}
fmt.Fprintf(stdin, "%s\n", text)
if text == "quit" {
break
}
}
log.Printf("end wrapper.\n")
os.Exit(0)
}
func (param *WaffleWrapper) FromStdinToQue(reader *bufio.Reader, ch chan string) {
var isNoColor = true
for {
text, _, err := reader.ReadLine()
if err == io.EOF {
break
} else if err != nil {
panic(err)
}
if strings.HasPrefix(string(text), "wf_fadeout") {
go param.ExecuteFadeoutCommand()
continue
}
if strings.HasPrefix(string(text), "isready") {
isNoColor = true
}
if isNoColor {
param.Clear()
if strings.HasPrefix(string(text), "position") {
// 先手なら"position startpos"だけ
tmpArr := strings.Split(string(text), " ")
if len(tmpArr) == 2 {
param.IsBlack = true
log.Printf("!!! black.\n")
} else {
param.IsBlack = false
log.Printf("!!! white.\n")
}
isNoColor = false
}
}
ch <- string(text)
}
}
func main() {
param := WaffleWrapper{}
param.Load("settings.json")
if len(param.Engine) == 0 {
fmt.Println("load error(settings.json)")
os.Exit(1)
}
if len(param.LogFile) == 0 {
log.SetFlags(0)
log.SetOutput(ioutil.Discard)
} else {
logfile, err := os.OpenFile(param.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
panic(err)
}
log.SetFlags(log.Ldate | log.Ltime)
log.SetOutput(logfile)
}
log.Printf("start wrapper.\n")
cmds := param.SplitCommandString(param.Engine)
var subProcess *exec.Cmd
if len(cmds) == 1 {
subProcess = exec.Command(cmds[0])
} else {
subProcess = exec.Command(cmds[0], cmds[1:]...)
}
subProcess.Stderr = os.Stderr
stdin, err := subProcess.StdinPipe()
if err != nil {
panic(err)
}
defer stdin.Close()
stdout, err := subProcess.StdoutPipe()
if err != nil {
panic(err)
}
defer stdout.Close()
if err = subProcess.Start(); err != nil {
panic(err)
}
ch := make(chan string)
scanner := bufio.NewScanner(stdout)
reader := bufio.NewReader(os.Stdin)
go param.FromCommandToStdout(scanner)
go param.FromQueToCommand(ch, stdin)
param.FromStdinToQue(reader, ch)
log.Printf("end wrapper.\n")
}
/* example settings.json {{{
{
"engine": "YaneuraOu-by-gcc",
"fadeout_command": "cmd.exe /c start getwild.mp3",
"logfile": "wf_debug.log",
"time_threshold": 60000,
"time_to_overwrite": 8000,
"max_score": 1000,
"challenge_change_score": 500,
"distance": 120000,
"time_to_overwrite2": 15000,
"cancel_score_upperlimit": 100,
"cancel_score_lowerlimit": -500,
"_comment": [
"engine // USI Engine",
"fadeout_command // mate を受けた時に実行するコマンド。(省略時はなにもしない)",
"logfile // ログファイル(省略時はoff)",
"// 時間攻めの設定",
"time_threshold = 60 * 1000 // 時間攻めを開始する相手の残り持ち時間(msec)",
"time_to_overwrite = 8 * 1000 // 時間攻め応手にかける時間(msec)",
"max_score = 1000 // 発動する評価値上限、これ以上の評価値がついたら無理に時間攻めはしない",
"challenge_change_score = 500 // 前回の指し手の評価値との増加量を見て、無条件で時間攻めをする評価値の下限",
"// なるべく相手との持ち時間が離されないようにする為の設定",
"distance = 120 * 1000 // 持ち時間の差(msec)",
"time_to_overwrite2 = 15 * 1000 // 応手にかける時間(msec)",
"// 考えどころの評価値の範囲の設定。(のつもり)",
"// 時間攻めや、なるべく離されないようにする、の持ち時間操作をキャンセルする。",
"// ただし、前回の指し手の局面数や深さが増えていたらキャンセルされない。(十分考えている、はず?)",
"cancel_score_upperlimit = -100 // 評価値範囲上限",
"cancel_score_lowerlimit = -500 // 評価値範囲下限"
]
}
}}} */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment