Goでのフラッシュゲームのリバースエンジニアリングとボットの作成

画像 この記事では、フラッシュアプ​​リケーションを逆コンパイルし、リクエスト署名アルゴリズムをゲームサーバーに抽出する方法を説明します。 そして、この情報に基づいて、 Goでゲームボットを作成する方法を説明します。

それはすべて、Androidスマートフォンで遊ぶための戦略を探していたという事実から始まりました。 「玉座ラッシュ」と呼ばれる良いゲームを見つけました。 その後、ブラウザクライアントもあることが判明しました。これにより、いくつかのアクションがより便利になります。 それでも、ゲームプレイには明らかに自動化が必要でした。 後で説明する優れたツールJPEXS Free Flash Decompilerを使用しました。



githubのプロジェクトのソースコード。



そのため、タスクはネイティブクライアントの作業をエミュレートすることです。

Firebug'om監視通信フラッシュアプ​​リケーション
POST https://epicwar-facebook.progrestar.net/rpc/ Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding gzip, deflate Accept-Language uk Connection keep-alive Cookie __utma=252078920.1702705582.1400051769.1400051769.1400051769.1; __utmz=252078920.1400051769.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none) Host epicwar-facebook.progrestar.net User-Agent Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0 Content-Length 1913 Content-Type application/json; charset=UTF-8 X-Auth-Application-Id 1424411677784893 X-Auth-Network-Ident facebook X-Auth-Session-Id 0n5nkrp20i8sf9 X-Auth-Session-Init 1 X-Auth-Signature 57d0320e91c26cd8e56152d3aad1a809 X-Auth-Token 6514a97ae525f196b8060337380e0cbb X-Auth-User-Id 675063875 X-Env-Library-Version 0 X-Request-Id 1 X-Requested-With XMLHttpRequest X-Server-Time 1400219523 {"calls":[{"name":"registration","args":{"user":{"locale":"en","id":"675063875","birthday":"1970-1-1","referrer":{"type":"bookmark"},"lastName":"","city":null,"country":null,"firstName":"","sex":"female"},"friendIds":[]},"ident":"registration"},{"name":"boostGetAll","args":{},"ident":"boostGetAll"},{"name":"getTime","args":{},"ident":"getTime"},{"name":"getSelfInfo","args":{},"ident":"getSelfInfo"},{"name":"getDynamicParams","args":{},"ident":"getDynamicParams"},{"name":"getArmyQueue","args":{},"ident":"getArmyQueue"},{"name":"getBuildings","args":{},"ident":"getBuildings"},{"name":"heroesGetList","args":{},"ident":"heroesGetList"},{"name":"getResearchQueue","args":{},"ident":"getResearchQueue"},{"name":"getMissions","args":{},"ident":"getMissions"},{"name":"getQuests","args":{},"ident":"getQuests"},{"name":"getProtections","args":{},"ident":"getProtections"},{"name":"getInvitedBy","args":{},"ident":"getInvitedBy"},{"name":"getInvitedUsers","args":{},"ident":"getInvitedUsers"},{"name":"getBonusCrystals","args":{},"ident":"getBonusCrystals"},{"name":"getSettings","args":{},"ident":"getSettings"},{"name":"promoGetHalfBilling","args":{},"ident":"promoGetHalfBilling"},{"name":"giftGetAvailable","args":{},"ident":"giftGetAvailable"},{"name":"giftGetReceivers","args":{},"ident":"giftGetReceivers"},{"name":"cloverGetAll","args":{},"ident":"cloverGetAll"},{"name":"paymentsCount","args":{},"ident":"paymentsCount"},{"name":"cemeteryGet","args":{},"ident":"cemeteryGet"},{"name":"getNotices","args":{},"ident":"getNotices"},{"name":"allianceGetMessages","args":{},"ident":"allianceGetMessages"},{"name":"getGlobalNews","args":{},"ident":"getGlobalNews"},{"name":"battleGetActive","args":{},"ident":"battleGetActive"},{"name":"spellList","args":{},"ident":"spellList"},{"name":"spellProductionQueue","args":{},"ident":"spellProductionQueue"},{"name":"state","args":{},"ident":"state"}],"session":null}
      
      







これはX-Auth-Signatureの問題です。 どこで入手できるかわかりませんでした。 明らかに、これは何らかのハッシュです。 しかし、何と何が不明です。 「FFDec」はこれを助けてくれました。 Javaで作成されたオープンソースプログラムはLinuxで実行されます。 この仕事に最適です。

htmlコードは、どのファイルがロードされているかを示しています。



 <object id="flash-app" width="100%" height="100%" data="https://epicwar-a.akamaihd.net/facebook/static/assets/Start.swf?v=13" type="application/x-shockwave-flash">
      
      





しかし、実際には、これは必要なものではありません。 flashvarsを探します。 そこで「preloader = https%3A%2F%2Fepicwar-a.akamaihd.net%2Ffacebook%2Fv042%2FFbLoader.swf%3Fv%3D2」。 しかし、これはそうではありません。 そして、必要なコードは、私がそれをどのように理解したかを正確に覚えていません。実際、 https://epicwar-a.akamaihd.net/facebook/v042/EpicWar.swfです。



逆コンパイル中です。

プログラムウィンドウ




実際、エクスポートがあります。

これが逆コンパイルされたコードです
 protected function createHeaders(param1:RpcEntryBase) : Object { var _loc5_:String = null; var _loc2_:String = SocialAdapter.instance.flashVars["session_key"]; var _loc3_:Object = { "Content-Type":"application/json; charset=UTF-8", "X-Request-Id":++this.unionRequestID, "X-Auth-Network-Ident":Env.NETWORK, "X-Auth-Application-Id":SocialAdapter.instance.app_id, "X-Auth-User-Id":SocialAdapter.instance.getPlayer().id, "X-Auth-Session-Id":Env.sessionKey }; if(_loc2_ != null) { _loc3_["X-Auth-Session-Key"] = _loc2_; } var _loc4_:Object = param1.headers; if(_loc4_ != null) { for(_loc5_ in _loc4_) { _loc3_[_loc5_] = _loc4_[_loc5_]; } } return _loc3_; } protected function createAuthSignature(param1:Object, param2:RpcEntryBase) : ByteArray { var _loc5_:ByteArray = null; var _loc3_:Object = param2.request.getFormattedData(); var _loc4_:ByteArray = new ByteArray(); _loc4_.writeUTFBytes(param1["X-Request-Id"]); _loc4_.writeUTFBytes(":"); _loc4_.writeUTFBytes(SocialAdapter.instance.authentication_key); _loc4_.writeUTFBytes(":"); _loc4_.writeUTFBytes(param1["X-Auth-Session-Id"]); _loc4_.writeUTFBytes(":"); if(_loc3_ is ByteArray) { _loc5_ = _loc3_ as ByteArray; _loc5_.position = 0; _loc4_.writeBytes(_loc5_,0,_loc5_.length); } else if(_loc3_ is String) { _loc4_.writeUTFBytes(_loc3_ as String); } _loc4_.writeUTFBytes(":"); _loc4_.writeUTFBytes(this.createFingerprint(param1)); return _loc4_; } private function createFingerprint(param1:Object) : String { var _loc4_:String = null; var _loc5_:* = 0; var _loc6_:* = 0; var _loc7_:String = null; var _loc2_:Array = []; var _loc3_:* = ""; for(_loc4_ in param1) { if(_loc4_.indexOf("X-Env") != -1) { _loc7_ = _loc4_.substr(6); _loc2_.push( { "key":_loc7_.toUpperCase(), "value":param1[_loc4_] }); } } _loc2_.sortOn("key"); _loc5_ = _loc2_.length; _loc6_ = 0; while(_loc6_ < _loc5_) { _loc3_ = _loc3_ + (_loc2_[_loc6_].key + "=" + _loc2_[_loc6_].value); _loc6_++; } return _loc3_; } protected function addHeaders(param1:URLRequest, param2:RpcEntryBase) : void { var _loc4_:String = null; var _loc3_:Object = this.createHeaders(param2); var _loc5_:ByteArray = this.createAuthSignature(_loc3_,param2); _loc3_["X-Auth-Signature"] = MD5.hashBytes(_loc5_); for(_loc4_ in _loc3_) { param1.requestHeaders.push(new URLRequestHeader(_loc4_,_loc3_[_loc4_].toString())); } }
      
      







すべてがシンプルです。 通常のmd5。 以下はGoのこのコードです。

ネットワーク
 package network import ( "net/http" "strings" "bytes" "crypto/md5" "io" "encoding/hex" "strconv" "io/ioutil" "log" ) const SERVER_URL = "https://epicwar-facebook.progrestar.net/rpc/" const APP_ID = "1424411677784893" const AUTH_KEY = "6514a97ae525f196b8060337380e0cbb" const NETWORK = "facebook" var _unionRequestID int var _uid string var _sid string func createFingerprint(headers map[string]string) string { var fingerprint bytes.Buffer preparedHeaders := []Pair{} for header, _ := range headers{ if(strings.Index(header, "X-Env") != -1){ preparedHeaders = append(preparedHeaders, Pair{ key : strings.ToUpper(header[6:len(header)]), value : headers[header]}) } } sortByKey(preparedHeaders) count := len(preparedHeaders); i := 0; for i < count { fingerprint.WriteString(preparedHeaders[i].key) fingerprint.WriteString("=") fingerprint.WriteString(preparedHeaders[i].value) i++; } res := fingerprint.String() return res } func createAuthSignature (headers map[string]string, postData string) string { h := md5.New() io.WriteString(h, headers["X-Request-Id"]) io.WriteString(h, ":") io.WriteString(h, AUTH_KEY); io.WriteString(h, ":"); io.WriteString(h, headers["X-Auth-Session-Id"]); io.WriteString(h, ":"); io.WriteString(h, postData); io.WriteString(h, ":"); io.WriteString(h, createFingerprint(headers)); return hex.EncodeToString(h.Sum(nil)) } func createHeaders() (map[string]string) { _unionRequestID = _unionRequestID + 1 headers := map[string]string{ "X-Request-Id":strconv.Itoa(_unionRequestID), "X-Auth-Network-Ident":NETWORK, "X-Auth-Application-Id":APP_ID, "X-Auth-User-Id":_uid, "X-Auth-Session-Id":_sid, "X-Env-Library-Version": "0"} return headers } func addHeaders(req *http.Request, postData string) { headers := createHeaders(); headers["X-Auth-Signature"] = createAuthSignature(headers, postData); for index, header := range headers { req.Header.Add(index, header) } } func Post(postData []byte) []byte { client := &http.Client{} req, err := http.NewRequest("POST", SERVER_URL, bytes.NewReader(postData)) addHeaders(req, string(postData)) resp, err := client.Do(req) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if(err != nil){ log.Fatal(err) } return body } func Init(Uid string, Sid string) { _unionRequestID = 0 _uid = Uid _sid = Sid }
      
      







どうやら、私は余分な見出しを送信しません。 アクションスクリプトコードでは、RpcClientBaseクラスはRpcClientクラスによって「com.progrestar.game.server.rpc」から継承され、さらにヘッダーが追加されます。 たとえば、そこから取った「X-Env-Library-Version」。 彼なしでは機能しません。



クエリ生成関数
 package bot import ( "log" "encoding/json" ) func getFormattedData(calls Calls) []byte { data, err := json.Marshal(Request{Calls:calls, Session: nil}) if(err != nil){ log.Fatal(err) } return data } func getStartBoard() []byte { return getFormattedData(Calls{ Call{Name:"getSelfInfo",Args:struct{}{},Ident:"getSelfInfo"}, Call{Name:"getBuildings",Args:struct{}{},Ident:"getBuildings"}}) } func collectResource(building uint64) []byte { return getFormattedData(Calls{ Call{ Name:"collectResource", Args:struct{BuildingId uint64 `json:"buildingId"`}{BuildingId:building}, Ident:"group_0_body"}, Call{Name:"state", Args: struct{}{}, Ident:"group_1_body"}}) } func upgradeBuilding(building uint64) []byte { return getFormattedData(Calls{ Call{ Name:"upgradeBuilding", Args:struct{BuildingId uint64 `json:"buildingId"`}{BuildingId:building}, Ident:"group_0_body"}, Call{Name:"state", Args: struct{}{}, Ident:"group_1_body"}}) }
      
      







データ構造
 package bot type Unit struct { Id uint64 `json:"id"` Amount uint64 `json:"amount"` } type Building struct { Id uint64 `json:"id"` TypeId uint `json:"typeId"` Flip bool `json:"flip"` Level uint `json:"level"` X uint `json:"x"` Y uint `json:"y"` Completed bool `json:"completed"` Volume uint `json:"volume"` StateTimestamp uint64 `json:"stateTimestamp"` Hitpoints uint64 `json:"hitpoints"` CompleteTime uint64 `json:"completeTime"` } type Player struct { Units []Unit Buildings []Building Stars uint //interlan game currency Level uint Builders int //Builder house lvl GoldCapacity uint32 FoodCapacity uint32 Food uint32 Gold uint32 CastleLvl uint } type Result struct { Ident string `json:"ident"` Result map[string]interface{} `json:"result"` } type Error struct { Name string `json:"name"` Description string `json:"description"` Call `json:"call"` } type Response struct{ Date float64 `json:"date"` Results []Result `json:"results"` Error Error `json:"error"` } type BuildingDependency struct { CastleLvl uint Cost uint32 } type BuildingDependencies []BuildingDependency type Buildings struct { Wall BuildingDependencies } type Call struct { Ident string `json:"ident"` Args interface {} `json:"args"` Name string `json:"name"` } type Calls []Call type Request struct { Calls Calls `json:"calls"` Session interface{} `json:"session"` }
      
      







リバースエンジニアリングの一部は終了しました。 ボットのロジックの一部になりました。 ネイティブクライアントでプレイする機会を残しました。 これを行うには、ブラウザーでhttp:// localhost:8080 / originalに移動します。

モジュールコード
 package main import ( "net/http" "log" "./utils" ) type Pair struct { Key string Value string } func original(Uid string) { flashvars := []Pair{ Pair{"fb_source", "bookmark"}, Pair{"ref", "bookmarks"}, Pair{"count", "0"}, Pair{"fb_bmpos", "2_0"}, Pair{"code", "AQA9KrPoSxjyTjNoG9B1mUHoZ8ooUxusmPWV6Aa17SfgHIkSVubBwLxCKC5EO7fkfIiC9LvnrDOY35pzlPwyasKVe6q1dcOZzvyQeTmrlf-rhjjMH0FZGh0PWwMp0k3IqZTg1tKunkFFGMALgo4Vf8vSzGFG2r8DiJq5-N7K-5MIg7j3VnR0A0-EzaM5kATvrM5FmI1XWmxHbHFmHpS52rKuTvSuH27Ipwt4p2V2DGayvPDjnvvfs6d5-hdaCtoxoOvBJDfecDakToecSzr3kAU6zF4QiMCIC1MtihaH_3C7a9BeLdVqMhr4w4q33WqEso0"}, Pair{"sys_id", "4"}, Pair{"network", "facebook"}, Pair{"uid", Uid}, Pair{"app_id", "1424411677784893"}, Pair{"interface_lang", "uk"}, Pair{"access_token", "CAAUPfrARez0BAKDb5H1uds5kLg3794HyPAbTYRZAA1H2i43NPl8sSjpxl77gIqDapYZB4QxWrZAK1H6VQUVAFbWuTWr4VYbXagirvciMba7FhyYKSUboICrvSJKYgBndShSZA0n4ZA5JRZBqigVbMRdCsHrjl8AQEmcWfbJqkHflqmv8XEBarKEVJRfLp56ksLZCO7TBzkfVQZDZD"}, Pair{"auth_key", "6514a97ae525f196b8060337380e0cbb"}, Pair{"requestLoadingInfoTimeout", "3000"}, Pair{"ref", "bookmark"}, Pair{"rpc_url", "https%3A%2F%2Fepicwar-facebook.progrestar.net%2Frpc%2F"}, Pair{"preloader_asset", "preloader%2Fpreloader_dwarf.swf"}, Pair{"browser", "chrome"}, Pair{"country_code", "UA"}, Pair{"geoip_city", "Kiev"}, Pair{"index_version", "1398240421"}, Pair{"static_url", "https%3A%2F%2Fepicwar-a.akamaihd.net%2F"}, Pair{"preloader", "https%3A%2F%2Fepicwar-a.akamaihd.net%2Ffacebook%2Fv042%2FFbLoader.swf%3Fv%3D2"}, Pair{"rpc_url", "https%3A%2F%2Fepicwar-facebook.progrestar.net%2Frpc%2F"}, Pair{"stat_url", "https://stat.progrestar.net/collector/client/"}, Pair{"error_url", "https://error.progrestar.net/client/"}, } t := utils.Template("original") http.HandleFunc("/original", func (w http.ResponseWriter, r *http.Request) { err := t.Execute(w, struct{Flashvars []Pair}{Flashvars: flashvars}) if err != nil { log.Fatal("There was an error:", err) } }) }
      
      



-rhjjMH0FZGh0PWwMp0k3IqZTg1tKunkFFGMALgo4Vf8vSzGFG2r8DiJq5-N7K-5MIg7j3VnR0A0-EzaM5kATvrM5FmI1XWmxHbHFmHpS52rKuTvSuH27Ipwt4p2V2DGayvPDjnvvfs6d5-hdaCtoxoOvBJDfecDakToecSzr3kAU6zF4QiMCIC1MtihaH_3C7a9BeLdVqMhr4w4q33WqEso0"}、 package main import ( "net/http" "log" "./utils" ) type Pair struct { Key string Value string } func original(Uid string) { flashvars := []Pair{ Pair{"fb_source", "bookmark"}, Pair{"ref", "bookmarks"}, Pair{"count", "0"}, Pair{"fb_bmpos", "2_0"}, Pair{"code", "AQA9KrPoSxjyTjNoG9B1mUHoZ8ooUxusmPWV6Aa17SfgHIkSVubBwLxCKC5EO7fkfIiC9LvnrDOY35pzlPwyasKVe6q1dcOZzvyQeTmrlf-rhjjMH0FZGh0PWwMp0k3IqZTg1tKunkFFGMALgo4Vf8vSzGFG2r8DiJq5-N7K-5MIg7j3VnR0A0-EzaM5kATvrM5FmI1XWmxHbHFmHpS52rKuTvSuH27Ipwt4p2V2DGayvPDjnvvfs6d5-hdaCtoxoOvBJDfecDakToecSzr3kAU6zF4QiMCIC1MtihaH_3C7a9BeLdVqMhr4w4q33WqEso0"}, Pair{"sys_id", "4"}, Pair{"network", "facebook"}, Pair{"uid", Uid}, Pair{"app_id", "1424411677784893"}, Pair{"interface_lang", "uk"}, Pair{"access_token", "CAAUPfrARez0BAKDb5H1uds5kLg3794HyPAbTYRZAA1H2i43NPl8sSjpxl77gIqDapYZB4QxWrZAK1H6VQUVAFbWuTWr4VYbXagirvciMba7FhyYKSUboICrvSJKYgBndShSZA0n4ZA5JRZBqigVbMRdCsHrjl8AQEmcWfbJqkHflqmv8XEBarKEVJRfLp56ksLZCO7TBzkfVQZDZD"}, Pair{"auth_key", "6514a97ae525f196b8060337380e0cbb"}, Pair{"requestLoadingInfoTimeout", "3000"}, Pair{"ref", "bookmark"}, Pair{"rpc_url", "https%3A%2F%2Fepicwar-facebook.progrestar.net%2Frpc%2F"}, Pair{"preloader_asset", "preloader%2Fpreloader_dwarf.swf"}, Pair{"browser", "chrome"}, Pair{"country_code", "UA"}, Pair{"geoip_city", "Kiev"}, Pair{"index_version", "1398240421"}, Pair{"static_url", "https%3A%2F%2Fepicwar-a.akamaihd.net%2F"}, Pair{"preloader", "https%3A%2F%2Fepicwar-a.akamaihd.net%2Ffacebook%2Fv042%2FFbLoader.swf%3Fv%3D2"}, Pair{"rpc_url", "https%3A%2F%2Fepicwar-facebook.progrestar.net%2Frpc%2F"}, Pair{"stat_url", "https://stat.progrestar.net/collector/client/"}, Pair{"error_url", "https://error.progrestar.net/client/"}, } t := utils.Template("original") http.HandleFunc("/original", func (w http.ResponseWriter, r *http.Request) { err := t.Execute(w, struct{Flashvars []Pair}{Flashvars: flashvars}) if err != nil { log.Fatal("There was an error:", err) } }) }



"}、 package main import ( "net/http" "log" "./utils" ) type Pair struct { Key string Value string } func original(Uid string) { flashvars := []Pair{ Pair{"fb_source", "bookmark"}, Pair{"ref", "bookmarks"}, Pair{"count", "0"}, Pair{"fb_bmpos", "2_0"}, Pair{"code", "AQA9KrPoSxjyTjNoG9B1mUHoZ8ooUxusmPWV6Aa17SfgHIkSVubBwLxCKC5EO7fkfIiC9LvnrDOY35pzlPwyasKVe6q1dcOZzvyQeTmrlf-rhjjMH0FZGh0PWwMp0k3IqZTg1tKunkFFGMALgo4Vf8vSzGFG2r8DiJq5-N7K-5MIg7j3VnR0A0-EzaM5kATvrM5FmI1XWmxHbHFmHpS52rKuTvSuH27Ipwt4p2V2DGayvPDjnvvfs6d5-hdaCtoxoOvBJDfecDakToecSzr3kAU6zF4QiMCIC1MtihaH_3C7a9BeLdVqMhr4w4q33WqEso0"}, Pair{"sys_id", "4"}, Pair{"network", "facebook"}, Pair{"uid", Uid}, Pair{"app_id", "1424411677784893"}, Pair{"interface_lang", "uk"}, Pair{"access_token", "CAAUPfrARez0BAKDb5H1uds5kLg3794HyPAbTYRZAA1H2i43NPl8sSjpxl77gIqDapYZB4QxWrZAK1H6VQUVAFbWuTWr4VYbXagirvciMba7FhyYKSUboICrvSJKYgBndShSZA0n4ZA5JRZBqigVbMRdCsHrjl8AQEmcWfbJqkHflqmv8XEBarKEVJRfLp56ksLZCO7TBzkfVQZDZD"}, Pair{"auth_key", "6514a97ae525f196b8060337380e0cbb"}, Pair{"requestLoadingInfoTimeout", "3000"}, Pair{"ref", "bookmark"}, Pair{"rpc_url", "https%3A%2F%2Fepicwar-facebook.progrestar.net%2Frpc%2F"}, Pair{"preloader_asset", "preloader%2Fpreloader_dwarf.swf"}, Pair{"browser", "chrome"}, Pair{"country_code", "UA"}, Pair{"geoip_city", "Kiev"}, Pair{"index_version", "1398240421"}, Pair{"static_url", "https%3A%2F%2Fepicwar-a.akamaihd.net%2F"}, Pair{"preloader", "https%3A%2F%2Fepicwar-a.akamaihd.net%2Ffacebook%2Fv042%2FFbLoader.swf%3Fv%3D2"}, Pair{"rpc_url", "https%3A%2F%2Fepicwar-facebook.progrestar.net%2Frpc%2F"}, Pair{"stat_url", "https://stat.progrestar.net/collector/client/"}, Pair{"error_url", "https://error.progrestar.net/client/"}, } t := utils.Template("original") http.HandleFunc("/original", func (w http.ResponseWriter, r *http.Request) { err := t.Execute(w, struct{Flashvars []Pair}{Flashvars: flashvars}) if err != nil { log.Fatal("There was an error:", err) } }) }







一部のフラッシュバーは必要ないと思いますが、すべてのフラッシュバーを残しました。



通信プロトコルは非常に簡単です。 POSTリクエストと1つのURLのみ。 Grepolisの場合のように、GET / POSTリクエストとパラメーターは豊富ではありません。

次に、ボットはゲームカードを個別に受け取り、 http:// localhost:8080 / botで表示できます。 これは、美と等尺性のない単なる地図です。 しかし、一方で、どのような建物がどこにあるかがはっきりと見えるため、敵が攻撃を仕掛けることができる都市の真ん中に空いている場所が出現する可能性が低くなります。 私はこれをよく自分でやった。 また、ネイティブクライアントでは、角度を変更できない不快な等角投影は必ずしも便利ではありません。



リソースを収集することから始めます。 とても簡単です。 可能な場合はリソースを収集し、スリープ状態になる別のゴルーチンが作成されます。 答えは、建物とリソース、いわゆる状態にあります。



リソースコレクターのソースコードは次のとおりです。
 func processCollectRequest(player *Player, resp *Response){ for _, result := range resp.Results { if(result.Ident == "group_1_body"){ parseResources(player, result.Result["resource"].([]interface{})) parseBuildings(player, result.Result["building"].([]interface{})) } } } func collectFood(player *Player) *Response{ var resp *Response for _, building := range player.Buildings { if(building.TypeId == MILL_ID){ resp = decodeJson(network.Post(collectResource(building.Id))) } } return resp } func collectGold(player *Player) *Response{ var resp *Response for _, building := range player.Buildings { if(building.TypeId == MINE_ID){ resp = decodeJson(network.Post(collectResource(building.Id))) } } return resp } func resourcesCollector(playerChan chan Player) { var resp *Response var playerStruct Player var player *Player resp = nil playerStruct = <- playerChan player = &playerStruct if(player.FoodCapacity > player.Food){ log.Print("Collect Food") resp = collectFood(player) } if(player.GoldCapacity > player.Gold){ log.Print("Collect Gold") resp = collectGold(player) } if(resp != nil){ processCollectRequest(player, resp) resp = nil } playerChan <- playerStruct time.Sleep(time.Minute * 10) go resourcesCollector(playerChan) }
      
      







別のファイル定数。
 package bot const CASTLE_ID = 1 const MINE_ID = 2 const TREASURY_ID = 3 const MILL_ID = 4 const BARN_ID = 5 const BARRACKS_ID = 6 const STAFF_ID = 7 const BUILDER_HUT_ID = 8 const FORGE_ID = 9 const BALLISTA_ID = 10 const WALL_ID = 11 const ARCHER_TOWER_ID = 12 const CANNON_ID = 13 const THUNDER_TOWER_ID = 14 const ICE_TOWER_ID = 15 const FIRE_TOWER_ID = 16 const CLAN_HOUSE_ID = 17 const DARK_TOWER_ID = 18 const TAVERN_ID = 19 const ALCHEMIST_ID = 20 const GOLD_RESOURCE_ID = 1 const FOOD_RESOURCE_ID = 2 var CAPACITIES = [12]uint32 {0, 5000, 15000, 35000, 75000, 150000, 300000, 600000, 1000000, 2000000, 3000000, 4000000} var BUILDINGS = Buildings{ Wall: BuildingDependencies{ BuildingDependency{CastleLvl: 2, Cost: 250}, BuildingDependency{CastleLvl: 2, Cost: 500}, BuildingDependency{CastleLvl: 3, Cost: 1000}, BuildingDependency{CastleLvl: 4, Cost: 3000}, BuildingDependency{CastleLvl: 5, Cost: 10000}, BuildingDependency{CastleLvl: 6, Cost: 25000}, BuildingDependency{CastleLvl: 7, Cost: 60000}, BuildingDependency{CastleLvl: 8, Cost: 150000}, BuildingDependency{CastleLvl: 9, Cost: 400000}, BuildingDependency{CastleLvl: 10, Cost: 1000000}, BuildingDependency{CastleLvl: 11, Cost: 2000000}}}
      
      







そしてパーサー
 package bot import "log" func parseResources (player *Player, resources []interface{}){ for _, resource := range resources { var Id uint var Amount uint32 Id = uint(resource.(map[string]interface{})["id"].(float64)) Amount = uint32(resource.(map[string]interface{})["amount"].(float64)) if(Id == GOLD_RESOURCE_ID){ player.Gold = Amount log.Print("Gold - ", Amount) } if(Id == FOOD_RESOURCE_ID){ player.Food = Amount log.Print("Food - ", Amount) } } } func parseUnits (player *Player, units []interface{}){ player.Units = []Unit{} for _, unit := range units { player.Units = append( player.Units, Unit{ Id: uint64(unit.(map[string]interface{})["id"].(float64)), Amount: uint64(unit.(map[string]interface{})["amount"].(float64))}) } } func parseBuildings(player *Player, buildings []interface{}){ player.Buildings = []Building{} for _, building := range buildings { var typeId uint var level uint var completed bool typeId = uint(building.(map[string]interface{})["typeId"].(float64)) level = uint(building.(map[string]interface{})["level"].(float64)) completed = building.(map[string]interface{})["completed"].(bool) if(typeId == BARN_ID) { player.FoodCapacity += CAPACITIES[level] } if(typeId == TREASURY_ID) { player.GoldCapacity += CAPACITIES[level] } if(typeId == CASTLE_ID){ player.CastleLvl = level if(completed == false){ player.CastleLvl-- } } player.Buildings = append( player.Buildings, Building{ Id: uint64(building.(map[string]interface{})["id"].(float64)), TypeId: typeId, Flip: building.(map[string]interface{})["flip"].(bool), Level: level, X: uint(building.(map[string]interface{})["x"].(float64)), Y: uint(building.(map[string]interface{})["y"].(float64)), Completed: completed, Volume: uint(building.(map[string]interface{})["volume"].(float64)), StateTimestamp:uint64(building.(map[string]interface{})["stateTimestamp"].(float64)), Hitpoints: uint64(building.(map[string]interface{})["hitpoints"].(float64)), CompleteTime: uint64(building.(map[string]interface{})["completeTime"].(float64))}) } }
      
      







多くの建物があり、個々のブロックをクリックするのは非常に不便であるため、壁の建物のみを自動改善しました。 それに応じて、同じ状態になります。

ビルダー
 func builder(playerChan chan Player){ var playerStruct Player var player *Player var resp *Response var isBuild bool = false playerStruct = <- playerChan player = &playerStruct for _, building := range player.Buildings { if( building.TypeId == WALL_ID && len(BUILDINGS.Wall) > int(building.Level) && player.CastleLvl >= BUILDINGS.Wall[building.Level].CastleLvl && player.Gold >= BUILDINGS.Wall[building.Level].Cost){ log.Print("Upgrade Wall. Level ", building.Level) resp = decodeJson(network.Post(upgradeBuilding(building.Id))) if(resp != nil){ processCollectRequest(player, resp) isBuild = true } break } } playerChan <- playerStruct if(isBuild){ time.Sleep(time.Second) }else{ time.Sleep(time.Hour) } go builder(playerChan) }
      
      







また、参照によって構造をゴルーチンに単純に転送することもできます。 しかし、これを行うと、ガベージコレクターはしばらくしてメモリをクリアし、nullPointer例外をスローしました。 したがって、チャネルを使用します。

メイン
 func decodeJson(encoded_json []byte) *Response { var resp *Response err := json.Unmarshal(encoded_json, &resp) if(err != nil){ log.Fatal("decodeJson", err) } if(resp.Error != Error{}){ resp = nil } return resp } func initGame(playerChan chan Player) { var playerStruct Player var player *Player playerStruct = Player{} player = &playerStruct resp := decodeJson(network.Post(getStartBoard())) for _, result := range resp.Results { if(result.Ident == "getSelfInfo"){ user := result.Result["user"].(map[string]interface{}) level, _ := strconv.Atoi(user["level"].(string)) player.Level = uint(level) player.Stars = uint(user["starmoney"].(float64)) parseUnits(player, user["unit"].([]interface{})) parseResources(player, user["resource"].([]interface{})) } player.GoldCapacity = 0 player.FoodCapacity = 0 if(result.Ident == "getBuildings"){ parseBuildings(player, result.Result["building"].([]interface{})) } } playerChan <- playerStruct } func Main(){ var player = make(chan Player, 1) initGame(player) go resourcesCollector(player) go builder(player) http.HandleFunc("/bot", func (w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("static/bot.html") if err != nil { log.Fatal("There was an error:", err) } err = t.Execute(w, nil) if err != nil { log.Fatal("There was an error:", err) } }) http.HandleFunc("/bot/map", func (w http.ResponseWriter, r *http.Request) { var playerStruct Player = <- player player <- playerStruct json, err := json.Marshal(playerStruct) if(err != nil){ log.Fatal(err) } io.WriteString(w, string(json)) }) }
      
      







ボットのクライアント部分も代表します。 最も困難なことは、建物がない場所を最適に表示することでしたが、そこにも着陸を落とすことはできません。 残りは非常に簡単です。

ボットクライアントコードを気にする人
 <!doctype html> <html> <head> <title>Throne Rush Bot</title> <style> #main{ display:block; margin:0 auto; background-color:#00FF33; } body{ background-color:#66CC00; height: 100%; margin: 0; padding: 0; } html{ height: 100%; } </style> </head> <body> <canvas id="main"></canvas> <script> var STYLES = { 1: { name: "Castle", size: 5, fillColor: '#996600' }, 2: { name: "Mine", size: 2, fillColor: '#996600' }, 3: { name: "Treasury", size: 3, fillColor: '#996600' }, 4: { name: "Mill", size: 2, fillColor: '#996600' }, 5: { name: "Barn", size: 3, fillColor: '#996600' }, 6: { name: "Barracs", size: 3, fillColor: '#996600' }, 7: { name: "Staff", size: 2, fillColor: '#996600' }, 8: { name: "Bldrs", size: 2, fillColor: '#996600' }, 9: { name: "Forge", size: 2, fillColor: '#996600' }, 10: { name: "Ballista", size: 2, fillColor: '#996600' }, 11: { name: "", size: 1, fillColor: '#660000' }, 12: { name: "Archrs", size: 2, fillColor: '#996600' }, 13: { name: "Cannon", size: 2, fillColor: '#996600' }, 14: { name: "Thunder", size: 2, fillColor: '#996600' }, 15: { name: "Ice", size: 2, fillColor: '#996600' }, 16: { name: "Fire Tower", size: 2, fillColor: '#996600' }, 17: { name: "Clan house", size: 3, fillColor: '#996600' }, 18: { name: "Dark Tower", size: 2, fillColor: '#996600' }, 19: { name: "Tavern", size: 3, fillColor: '#996600' }, 20: { name: "Alchemist", size: 3, fillColor: '#996600' } }; var MAP_SIZE = 36; var NOT_DESANT_COLOR = "#FF0033"; var height = document.body.clientHeight; var canvas = document.getElementById('main'); var ctx = canvas.getContext('2d'); var step = height / 40; canvas.width = height; canvas.height = height; canvas.style.width = height + 'px'; canvas.style.height = height + 'px'; ctx.strokeStyle="#FF0033"; ctx.textAlign="center"; ctx.textBaseline="middle"; function pointFree(buildings, point){ for(var i in buildings){ var building = buildings[i]; if( point[0] >= building.x && point[0] < building.x + STYLES[building.typeId].size && point[1] >= building.y && point[1] < building.y + STYLES[building.typeId].size ){ return false; } } return true; } function drawMap(player){ var redPoints = []; player.Buildings .forEach(function(building){ for(var i = -1; i < STYLES[building.typeId].size + 1; i++){ var point = [building.x + i, building.y - 1].join(','); if(redPoints.indexOf(point) === -1){ redPoints.push(point); } } for(var i = -1; i < STYLES[building.typeId].size + 1; i++){ var point = [building.x + i, building.y + STYLES[building.typeId].size].join(','); if(redPoints.indexOf(point) === -1){ redPoints.push(point); } } for(var i = -1; i < STYLES[building.typeId].size + 1; i++){ var point = [building.x - 1, building.y + i].join(','); if(redPoints.indexOf(point) === -1){ redPoints.push(point); } } for(var i = -1; i < STYLES[building.typeId].size + 1; i++){ var point = [building.x + STYLES[building.typeId].size, building.y + i].join(','); if(redPoints.indexOf(point) === -1){ redPoints.push(point); } } ctx.fillStyle=STYLES[building.typeId].fillColor; ctx.fillRect( step * building.x, step * building.y, step * STYLES[building.typeId].size, step * STYLES[building.typeId].size); ctx.stroke(); ctx.strokeText( STYLES[building.typeId].name+'('+building.level+')', step * building.x + step * STYLES[building.typeId].size / 2, step * building.y + step * STYLES[building.typeId].size / 2); }); while(redPoints.length > 0){ var point = redPoints.pop().split(','); if(pointFree(player.Buildings, point)){ ctx.fillStyle=NOT_DESANT_COLOR; ctx.fillRect( step * point[0], step * point[1], step, step); ctx.stroke(); } } } var xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', '/bot/map', true); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState === 4) { drawMap(JSON.parse(xmlhttp.responseText)) } }; xmlhttp.send(null); </script> </body> </html>
      
      







だから彼は見える





All Articles