Command Line Interface to Run Games Locally (#25)
* Initial Attempts at Cobra Command * Update to the README * Fixes for Linter Thanks @LenPayne for all your hard work getting this started!
This commit is contained in:
parent
e01a1bf505
commit
6c1e0d9547
8 changed files with 1035 additions and 7 deletions
145
cli/README.md
Normal file
145
cli/README.md
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# Battlesnake CLI
|
||||
|
||||
This tool allows running a Battlesnake game locally. There are several command-
|
||||
line options for the play verb, including the ability to send snakes requests
|
||||
sequentially or all at the same time, and also to set a timeout limit.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Use the CLI to configure and play a game of Battlesnake against
|
||||
multiple snakes, with multiple rulesets.
|
||||
|
||||
Usage:
|
||||
battlesnake play [flags]
|
||||
|
||||
Flags:
|
||||
-g, --gametype string Type of Game Rules (default "standard")
|
||||
-H, --height int32 Height of Board (default 11)
|
||||
-h, --help help for play
|
||||
-n, --name stringArray Name of Snake
|
||||
-s, --sequential Use Sequential Processing
|
||||
-S, --squad stringArray Squad of Snake
|
||||
-t, --timeout int32 Request Timeout (default 500)
|
||||
-u, --url stringArray URL of Snake
|
||||
-v, --viewmap View the Map Each Turn
|
||||
-W, --width int32 Width of Board (default 11)
|
||||
|
||||
Global Flags:
|
||||
--config string config file (default is $HOME/.battlesnake.yaml)
|
||||
```
|
||||
|
||||
Names and URLs will be paired together in sequence, so in the following example
|
||||
it effectively makes:
|
||||
|
||||
* Snake1: http://snake1-url-whatever:port
|
||||
* Snake2: http://snake2-url-whatever:port
|
||||
|
||||
Names are optional, but definitely way easier to read than UUIDs. URLs are
|
||||
optional too, but your snake will lose if the server is only sending move
|
||||
requests to http://example.com.
|
||||
|
||||
|
||||
```
|
||||
battlesnake play --width 7 --height 7 --name Snake1 --url http://snake1-url-whatever:port --name Snake2 --url http://snake2-url-whatever:port
|
||||
```
|
||||
|
||||
### Sample Output
|
||||
```
|
||||
$ battlesnake play --width 3 --height 3 --url http://redacted:4567/ --url http://redacted:4568/ --name Bob --name Sue
|
||||
2020/10/31 22:05:56 [1]: State: &{3 3 [{1 0}] [{e74892ba-9f0c-4e96-9bde-1a9efaff0ab4 [{0 1} {0 2} {0 2} {0 2}] 100 } {89e20d26-7da7-4964-b0ae-148c8f60f7ee [{2 1} {2 2} {2 2} {2 2}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:05:56 [2]: State: &{3 3 [{1 0}] [{e74892ba-9f0c-4e96-9bde-1a9efaff0ab4 [{0 0} {0 1} {0 2} {0 2}] 99 } {89e20d26-7da7-4964-b0ae-148c8f60f7ee [{2 0} {2 1} {2 2} {2 2}] 99 }]} OutOfBounds: []
|
||||
2020/10/31 22:05:56 [3]: State: &{3 3 [{1 2}] [{e74892ba-9f0c-4e96-9bde-1a9efaff0ab4 [{1 0} {0 0} {0 1} {0 2} {0 2}] 100 head-collision 89e20d26-7da7-4964-b0ae-148c8f60f7ee} {89e20d26-7da7-4964-b0ae-148c8f60f7ee [{1 0} {2 0} {2 1} {2 2} {2 2}] 100 head-collision e74892ba-9f0c-4e96-9bde-1a9efaff0ab4}]} OutOfBounds: []
|
||||
2020/10/31 22:05:56 [DONE]: Game completed after 3 turns. It was a draw.
|
||||
```
|
||||
|
||||
### Sample Map Output
|
||||
```
|
||||
$ battlesnake play --url http://redacted:4567/ --url http://redacted:4567/ --url http://redacted:4567/ --url http://redacted:4567/ --url http://redacted:4567/ --url http://redacted:4567/ --url http://redacted:4567/ --url http://redacted:4567/ --name Snake1 --name Snake2 --name Snake3 --name Snake4 --name Snake5 --name Snake6 --name Snake7 --name Snake8 --width 13 --height 13 --timeout 1000 --viewmap
|
||||
2020/11/01 21:56:50 [1]
|
||||
Hazards ░: []
|
||||
Food ⚕: [{12 10} {8 4} {10 10} {9 11} {8 2} {9 6} {1 11} {9 12}]
|
||||
Snake1 ■: {cca4652d-26b5-4c09-9d05-dbe01d24626c [{0 3} {0 2} {0 2}] 99 }
|
||||
Snake2 ⌀: {aff9c973-fc49-4b1e-b219-1a4d2023d76b [{8 1} {8 0} {8 0}] 99 }
|
||||
Snake3 ●: {03c90cd1-62dc-4393-8c1c-185601cfe00a [{8 11} {7 11} {7 11}] 99 }
|
||||
Snake4 ⍟: {c112965a-0b5a-45f6-b4de-88a68f3373e3 [{3 2} {3 1} {3 1}] 99 }
|
||||
Snake5 ◘: {f4810018-cd5e-44bd-b871-3f6afd84250f [{5 2} {5 1} {5 1}] 99 }
|
||||
Snake6 ☺: {50c2933a-c4e4-4727-bc2e-e54778129308 [{7 4} {6 4} {6 4}] 99 }
|
||||
Snake7 □: {f760d89c-e503-45c4-9453-0284ed172120 [{1 12} {2 12} {2 12}] 99 }
|
||||
Snake8 ☻: {8e42531e-bd55-4d76-8d3a-e0eda0578812 [{4 7} {4 6} {4 6}] 99 }
|
||||
◦□□◦◦◦◦◦◦⚕◦◦◦
|
||||
◦⚕◦◦◦◦◦●●⚕◦◦◦
|
||||
◦◦◦◦◦◦◦◦◦◦⚕◦⚕
|
||||
◦◦◦◦◦◦◦◦◦◦◦◦◦
|
||||
◦◦◦◦◦◦◦◦◦◦◦◦◦
|
||||
◦◦◦◦☻◦◦◦◦◦◦◦◦
|
||||
◦◦◦◦☻◦◦◦◦⚕◦◦◦
|
||||
◦◦◦◦◦◦◦◦◦◦◦◦◦
|
||||
◦◦◦◦◦◦☺☺⚕◦◦◦◦
|
||||
■◦◦◦◦◦◦◦◦◦◦◦◦
|
||||
■◦◦⍟◦◘◦◦⚕◦◦◦◦
|
||||
◦◦◦⍟◦◘◦◦⌀◦◦◦◦
|
||||
◦◦◦◦◦◦◦◦⌀◦◦◦◦
|
||||
```
|
||||
|
||||
### Sample Solo Game
|
||||
```
|
||||
$ battlesnake play --url http://redacted:4567/ --name Bob --width 3 --height 3 --timeout 500 --gametype solo
|
||||
2020/10/31 22:02:58 [1]: State: &{3 3 [{2 2}] [{cc8831e8-d517-4216-a8d8-a64243decada [{1 2} {0 2} {0 2}] 99 }]} OutOfBounds: []
|
||||
2020/10/31 22:02:58 [2]: State: &{3 3 [{2 1}] [{cc8831e8-d517-4216-a8d8-a64243decada [{2 2} {1 2} {0 2} {0 2}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:02:59 [3]: State: &{3 3 [{0 1}] [{cc8831e8-d517-4216-a8d8-a64243decada [{2 1} {2 2} {1 2} {0 2} {0 2}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:02:59 [4]: State: &{3 3 [{0 1}] [{cc8831e8-d517-4216-a8d8-a64243decada [{1 1} {2 1} {2 2} {1 2} {0 2}] 99 }]} OutOfBounds: []
|
||||
2020/10/31 22:02:59 [5]: State: &{3 3 [{0 2}] [{cc8831e8-d517-4216-a8d8-a64243decada [{0 1} {1 1} {2 1} {2 2} {1 2} {1 2}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:02:59 [6]: State: &{3 3 [{2 0}] [{cc8831e8-d517-4216-a8d8-a64243decada [{0 2} {0 1} {1 1} {2 1} {2 2} {1 2} {1 2}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:02:59 [7]: State: &{3 3 [{2 0} {0 0}] [{cc8831e8-d517-4216-a8d8-a64243decada [{0 1} {0 2} {0 1} {1 1} {2 1} {2 2} {1 2}] 99 snake-self-collision cc8831e8-d517-4216-a8d8-a64243decada}]} OutOfBounds: []
|
||||
2020/10/31 22:02:59 [DONE]: Game completed after 7 turns. It was a draw.
|
||||
```
|
||||
|
||||
### Sample Squad Game
|
||||
```
|
||||
-$ battlesnake play --url http://redacted:4567/ --name Bob --squad A --url http://redacted:4567/ --name Sue --squad A --url http://redacted:4567/ --name Jim --squad B --url http://redacted:4567/ --name Francine --squad B --width 5 --height 5 --gametype squad
|
||||
2020/10/31 22:14:27 [1]: State: &{5 5 [{2 4} {4 1} {4 3} {1 4} {0 2}] [{92a1bd60-8f8d-4adb-8468-e8eb1028b7f0 [{3 0} {4 0} {4 0}] 99 } {25c5607c-a2da-421e-84c3-e2a040cffae5 [{1 2} {1 1} {1 1}] 99 } {9dc22d73-3631-43cc-9472-a2ff074bc4a1 [{3 2} {4 2} {4 2}] 99 } {54157a58-2e07-4f84-b035-6d6df73d751a [{3 4} {4 4} {4 4}] 99 }]} OutOfBounds: []
|
||||
2020/10/31 22:14:28 [2]: State: &{5 5 [{4 1} {4 3} {1 4}] [{92a1bd60-8f8d-4adb-8468-e8eb1028b7f0 [{2 0} {3 0} {4 0} {4 0}] 100 } {25c5607c-a2da-421e-84c3-e2a040cffae5 [{0 2} {1 2} {1 1} {1 1}] 100 } {9dc22d73-3631-43cc-9472-a2ff074bc4a1 [{3 3} {3 2} {4 2} {4 2}] 100 } {54157a58-2e07-4f84-b035-6d6df73d751a [{2 4} {3 4} {4 4} {4 4}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:14:28 [3]: State: &{5 5 [{4 1}] [{92a1bd60-8f8d-4adb-8468-e8eb1028b7f0 [{2 1} {2 0} {3 0} {4 0}] 99 } {25c5607c-a2da-421e-84c3-e2a040cffae5 [{0 3} {0 2} {1 2} {1 1}] 99 } {9dc22d73-3631-43cc-9472-a2ff074bc4a1 [{4 3} {3 3} {3 2} {4 2} {4 2}] 100 } {54157a58-2e07-4f84-b035-6d6df73d751a [{1 4} {2 4} {3 4} {4 4} {4 4}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:14:28 [4]: State: &{5 5 [{4 1}] [{92a1bd60-8f8d-4adb-8468-e8eb1028b7f0 [{3 1} {2 1} {2 0} {3 0}] 98 } {25c5607c-a2da-421e-84c3-e2a040cffae5 [{0 4} {0 3} {0 2} {1 2}] 98 } {9dc22d73-3631-43cc-9472-a2ff074bc4a1 [{4 4} {4 3} {3 3} {3 2} {4 2}] 99 } {54157a58-2e07-4f84-b035-6d6df73d751a [{1 3} {1 4} {2 4} {3 4} {4 4}] 99 }]} OutOfBounds: []
|
||||
2020/10/31 22:14:28 [5]: State: &{5 5 [{1 0}] [{92a1bd60-8f8d-4adb-8468-e8eb1028b7f0 [{4 1} {3 1} {2 1} {2 0} {2 0}] 100 squad-eliminated } {25c5607c-a2da-421e-84c3-e2a040cffae5 [{0 3} {0 4} {0 3} {0 2}] 97 snake-self-collision 25c5607c-a2da-421e-84c3-e2a040cffae5} {9dc22d73-3631-43cc-9472-a2ff074bc4a1 [{3 4} {4 4} {4 3} {3 3} {3 2}] 98 } {54157a58-2e07-4f84-b035-6d6df73d751a [{2 3} {1 3} {1 4} {2 4} {3 4}] 98 }]} OutOfBounds: []
|
||||
2020/10/31 22:14:28 [DONE]: Game completed after 5 turns. Francine is the winner.
|
||||
```
|
||||
|
||||
### Sample Royale Game
|
||||
```
|
||||
$ battlesnake play --url http://redacted:4567/ --url http://redacted:4567/ --name Bob --name Sue --width 7 --height 7 --timeout 800 --gametype royale
|
||||
2020/10/31 22:16:44 [1]: State: &{7 7 [{4 0} {0 0} {3 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{4 1} {5 1} {5 1}] 99 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{0 1} {1 1} {1 1}] 99 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:44 [2]: State: &{7 7 [{3 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{4 0} {4 1} {5 1} {5 1}] 100 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{0 0} {0 1} {1 1} {1 1}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:45 [3]: State: &{7 7 [{3 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 0} {4 0} {4 1} {5 1}] 99 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{1 0} {0 0} {0 1} {1 1}] 99 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:45 [4]: State: &{7 7 [{3 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 1} {3 0} {4 0} {4 1}] 98 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{1 1} {1 0} {0 0} {0 1}] 98 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:45 [5]: State: &{7 7 [{3 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 2} {3 1} {3 0} {4 0}] 97 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{1 2} {1 1} {1 0} {0 0}] 97 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:45 [6]: State: &{7 7 [{0 4}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 3} {3 2} {3 1} {3 0} {3 0}] 100 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{1 3} {1 2} {1 1} {1 0}] 96 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:45 [7]: State: &{7 7 [{0 4}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 3} {3 3} {3 2} {3 1} {3 0}] 99 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{1 4} {1 3} {1 2} {1 1}] 95 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:45 [8]: State: &{7 7 [{1 1}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 4} {2 3} {3 3} {3 2} {3 1}] 98 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{0 4} {1 4} {1 3} {1 2} {1 2}] 100 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:45 [9]: State: &{7 7 [{1 1}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 5} {2 4} {2 3} {3 3} {3 2}] 97 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{0 3} {0 4} {1 4} {1 3} {1 2}] 99 }]} OutOfBounds: []
|
||||
2020/10/31 22:16:45 [10]: State: &{7 7 [{1 1}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 5} {2 5} {2 4} {2 3} {3 3}] 96 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{0 2} {0 3} {0 4} {1 4} {1 3}] 98 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:45 [11]: State: &{7 7 [{1 1}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 4} {3 5} {2 5} {2 4} {2 3}] 95 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{1 2} {0 2} {0 3} {0 4} {1 4}] 97 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:45 [12]: State: &{7 7 [{1 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 3} {3 4} {3 5} {2 5} {2 4}] 94 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{1 1} {1 2} {0 2} {0 3} {0 4} {0 4}] 100 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [13]: State: &{7 7 [{1 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 3} {3 3} {3 4} {3 5} {2 5}] 93 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{2 1} {1 1} {1 2} {0 2} {0 3} {0 4}] 99 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [14]: State: &{7 7 [{2 0}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{1 3} {2 3} {3 3} {3 4} {3 5} {3 5}] 100 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{3 1} {2 1} {1 1} {1 2} {0 2} {0 3}] 98 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [15]: State: &{7 7 [{2 0}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{1 4} {1 3} {2 3} {3 3} {3 4} {3 5}] 99 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{3 0} {3 1} {2 1} {1 1} {1 2} {0 2}] 97 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [16]: State: &{7 7 [{2 0}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 4} {1 4} {1 3} {2 3} {3 3} {3 4}] 98 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{4 0} {3 0} {3 1} {2 1} {1 1} {1 2}] 96 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [17]: State: &{7 7 [{2 0}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 4} {2 4} {1 4} {1 3} {2 3} {3 3}] 97 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{4 1} {4 0} {3 0} {3 1} {2 1} {1 1}] 95 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [18]: State: &{7 7 [{2 0}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 3} {3 4} {2 4} {1 4} {1 3} {2 3}] 96 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{4 2} {4 1} {4 0} {3 0} {3 1} {2 1}] 94 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [19]: State: &{7 7 [{2 0}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 3} {3 3} {3 4} {2 4} {1 4} {1 3}] 95 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{5 2} {4 2} {4 1} {4 0} {3 0} {3 1}] 93 }]} OutOfBounds: [{6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [20]: State: &{7 7 [{2 0}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 2} {2 3} {3 3} {3 4} {2 4} {1 4}] 94 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{5 1} {5 2} {4 2} {4 1} {4 0} {3 0}] 92 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [21]: State: &{7 7 [{2 0}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 1} {2 2} {2 3} {3 3} {3 4} {2 4}] 93 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{5 0} {5 1} {5 2} {4 2} {4 1} {4 0}] 90 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:46 [22]: State: &{7 7 [{4 4}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{2 0} {2 1} {2 2} {2 3} {3 3} {3 4} {3 4}] 99 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{6 0} {5 0} {5 1} {5 2} {4 2} {4 1}] 88 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:47 [23]: State: &{7 7 [{4 4} {4 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 0} {2 0} {2 1} {2 2} {2 3} {3 3} {3 4}] 97 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{6 1} {6 0} {5 0} {5 1} {5 2} {4 2}] 86 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:47 [24]: State: &{7 7 [{4 4} {4 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 1} {3 0} {2 0} {2 1} {2 2} {2 3} {3 3}] 96 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{6 2} {6 1} {6 0} {5 0} {5 1} {5 2}] 84 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:47 [25]: State: &{7 7 [{4 4} {4 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 2} {3 1} {3 0} {2 0} {2 1} {2 2} {2 3}] 95 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{5 2} {6 2} {6 1} {6 0} {5 0} {5 1}] 83 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:47 [26]: State: &{7 7 [{4 4} {4 3}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{3 3} {3 2} {3 1} {3 0} {2 0} {2 1} {2 2}] 94 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{5 1} {5 2} {6 2} {6 1} {6 0} {5 0}] 82 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:47 [27]: State: &{7 7 [{4 4}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{4 3} {3 3} {3 2} {3 1} {3 0} {2 0} {2 1} {2 1}] 100 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{4 1} {5 1} {5 2} {6 2} {6 1} {6 0}] 81 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:47 [28]: State: &{7 7 [{3 6}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{4 4} {4 3} {3 3} {3 2} {3 1} {3 0} {2 0} {2 1} {2 1}] 100 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{4 0} {4 1} {5 1} {5 2} {6 2} {6 1}] 79 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:47 [29]: State: &{7 7 [{3 6}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{5 4} {4 4} {4 3} {3 3} {3 2} {3 1} {3 0} {2 0} {2 1}] 99 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{5 0} {4 0} {4 1} {5 1} {5 2} {6 2}] 77 }]} OutOfBounds: [{0 0} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:48 [30]: State: &{7 7 [{3 6} {4 6}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{5 3} {5 4} {4 4} {4 3} {3 3} {3 2} {3 1} {3 0} {2 0}] 98 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{6 0} {5 0} {4 0} {4 1} {5 1} {5 2}] 75 }]} OutOfBounds: [{0 0} {0 1} {0 2} {0 3} {0 4} {0 5} {0 6} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:48 [31]: State: &{7 7 [{3 6} {4 6}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{5 2} {5 3} {5 4} {4 4} {4 3} {3 3} {3 2} {3 1} {3 0}] 97 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{6 1} {6 0} {5 0} {4 0} {4 1} {5 1}] 73 }]} OutOfBounds: [{0 0} {0 1} {0 2} {0 3} {0 4} {0 5} {0 6} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:48 [32]: State: &{7 7 [{3 6} {4 6}] [{07ba7c7a-6533-4682-8769-fc2666b155c5 [{5 1} {5 2} {5 3} {5 4} {4 4} {4 3} {3 3} {3 2} {3 1}] 96 } {7b33dbd3-c9c5-461c-8d66-29ca715a9e43 [{5 1} {6 1} {6 0} {5 0} {4 0} {4 1}] 72 head-collision 07ba7c7a-6533-4682-8769-fc2666b155c5}]} OutOfBounds: [{0 0} {0 1} {0 2} {0 3} {0 4} {0 5} {0 6} {1 0} {2 0} {3 0} {4 0} {5 0} {6 0} {6 1} {6 2} {6 3} {6 4} {6 5} {6 6}]
|
||||
2020/10/31 22:16:48 [DONE]: Game completed after 32 turns. Bob is the winner.
|
||||
```
|
||||
477
cli/cmd/play.go
Normal file
477
cli/cmd/play.go
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/BattlesnakeOfficial/rules"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type InternalSnake struct {
|
||||
URL string
|
||||
Name string
|
||||
ID string
|
||||
API string
|
||||
LastMove string
|
||||
Squad string
|
||||
Character rune
|
||||
}
|
||||
|
||||
type XY struct {
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
}
|
||||
|
||||
type SnakeResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Health int32 `json:"health"`
|
||||
Body []XY `json:"body"`
|
||||
Latency int32 `json:"latency"`
|
||||
Head XY `json:"head"`
|
||||
Length int32 `json:"length"`
|
||||
Shout string `json:"shout"`
|
||||
Squad string `json:"squad"`
|
||||
}
|
||||
|
||||
type BoardResponse struct {
|
||||
Height int32 `json:"height"`
|
||||
Width int32 `json:"width"`
|
||||
Food []XY `json:"food"`
|
||||
Hazards []XY `json:"hazards"`
|
||||
Snakes []SnakeResponse `json:"snakes"`
|
||||
}
|
||||
|
||||
type GameResponse struct {
|
||||
Id string `json:"id"`
|
||||
Timeout int32 `json:"timeout"`
|
||||
}
|
||||
|
||||
type ResponsePayload struct {
|
||||
Game GameResponse `json:"game"`
|
||||
Turn int32 `json:"turn"`
|
||||
Board BoardResponse `json:"board"`
|
||||
You SnakeResponse `json:"you"`
|
||||
}
|
||||
|
||||
type PlayerResponse struct {
|
||||
Move string `json:"move"`
|
||||
Shout string `json:"shout"`
|
||||
}
|
||||
|
||||
type PingResponse struct {
|
||||
APIVersion string `json:"apiversion"`
|
||||
Author string `json:"author"`
|
||||
Color string `json:"color"`
|
||||
Head string `json:"head"`
|
||||
Tail string `json:"tail"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
var GameId string
|
||||
var Turn int32
|
||||
var InternalSnakes map[string]InternalSnake
|
||||
var HttpClient http.Client
|
||||
var Width int32
|
||||
var Height int32
|
||||
var Names []string
|
||||
var URLs []string
|
||||
var Squads []string
|
||||
var Timeout int32
|
||||
var Sequential bool
|
||||
var GameType string
|
||||
var ViewMap bool
|
||||
|
||||
var playCmd = &cobra.Command{
|
||||
Use: "play",
|
||||
Short: "Play a game of Battlesnake",
|
||||
Long: `Use the CLI to configure and play a game of Battlesnake against
|
||||
multiple snakes, with multiple rulesets.`,
|
||||
Run: run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(playCmd)
|
||||
|
||||
playCmd.Flags().Int32VarP(&Width, "width", "W", 11, "Width of Board")
|
||||
playCmd.Flags().Int32VarP(&Height, "height", "H", 11, "Height of Board")
|
||||
playCmd.Flags().StringArrayVarP(&Names, "name", "n", nil, "Name of Snake")
|
||||
playCmd.Flags().StringArrayVarP(&URLs, "url", "u", nil, "URL of Snake")
|
||||
playCmd.Flags().StringArrayVarP(&Names, "squad", "S", nil, "Squad of Snake")
|
||||
playCmd.Flags().Int32VarP(&Timeout, "timeout", "t", 500, "Request Timeout")
|
||||
playCmd.Flags().BoolVarP(&Sequential, "sequential", "s", false, "Use Sequential Processing")
|
||||
playCmd.Flags().StringVarP(&GameType, "gametype", "g", "standard", "Type of Game Rules")
|
||||
playCmd.Flags().BoolVarP(&ViewMap, "viewmap", "v", false, "View the Map Each Turn")
|
||||
}
|
||||
|
||||
var run = func(cmd *cobra.Command, args []string) {
|
||||
InternalSnakes = make(map[string]InternalSnake)
|
||||
GameId = uuid.New().String()
|
||||
Turn = 0
|
||||
|
||||
snakes := buildSnakesFromOptions()
|
||||
|
||||
seed := time.Now().UTC().UnixNano()
|
||||
|
||||
var ruleset rules.Ruleset
|
||||
var royale rules.RoyaleRuleset
|
||||
var outOfBounds []rules.Point
|
||||
ruleset, _ = getRuleset(seed, Turn, snakes)
|
||||
state := initializeBoardFromArgs(ruleset, snakes)
|
||||
for _, snake := range snakes {
|
||||
InternalSnakes[snake.ID] = snake
|
||||
}
|
||||
|
||||
for v := false; !v; v, _ = ruleset.IsGameOver(state) {
|
||||
Turn++
|
||||
ruleset, royale = getRuleset(seed, Turn, snakes)
|
||||
state, outOfBounds = createNextBoardState(ruleset, royale, state, outOfBounds, snakes)
|
||||
if ViewMap {
|
||||
printMap(state, outOfBounds, Turn)
|
||||
} else {
|
||||
log.Printf("[%v]: State: %v OutOfBounds: %v\n", Turn, state, outOfBounds)
|
||||
}
|
||||
}
|
||||
|
||||
var winner string
|
||||
isDraw := true
|
||||
for _, snake := range state.Snakes {
|
||||
if snake.EliminatedCause == rules.NotEliminated {
|
||||
isDraw = false
|
||||
winner = InternalSnakes[snake.ID].Name
|
||||
sendEndRequest(state, InternalSnakes[snake.ID])
|
||||
}
|
||||
}
|
||||
|
||||
if isDraw {
|
||||
log.Printf("[DONE]: Game completed after %v turns. It was a draw.", Turn)
|
||||
} else {
|
||||
log.Printf("[DONE]: Game completed after %v turns. %v is the winner.", Turn, winner)
|
||||
}
|
||||
}
|
||||
|
||||
func getRuleset(seed int64, gameTurn int32, snakes []InternalSnake) (rules.Ruleset, rules.RoyaleRuleset) {
|
||||
var ruleset rules.Ruleset
|
||||
var royale rules.RoyaleRuleset
|
||||
switch GameType {
|
||||
case "royale":
|
||||
royale = rules.RoyaleRuleset{
|
||||
Seed: seed,
|
||||
Turn: gameTurn,
|
||||
ShrinkEveryNTurns: 10,
|
||||
DamagePerTurn: 1,
|
||||
}
|
||||
ruleset = &royale
|
||||
case "squad":
|
||||
squadMap := map[string]string{}
|
||||
for _, snake := range snakes {
|
||||
squadMap[snake.ID] = snake.Squad
|
||||
}
|
||||
ruleset = &rules.SquadRuleset{
|
||||
SquadMap: squadMap,
|
||||
AllowBodyCollisions: true,
|
||||
SharedElimination: true,
|
||||
SharedHealth: true,
|
||||
SharedLength: true,
|
||||
}
|
||||
case "solo":
|
||||
ruleset = &rules.SoloRuleset{}
|
||||
default:
|
||||
ruleset = &rules.StandardRuleset{}
|
||||
}
|
||||
return ruleset, royale
|
||||
}
|
||||
|
||||
func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []InternalSnake) *rules.BoardState {
|
||||
if Timeout == 0 {
|
||||
Timeout = 500
|
||||
}
|
||||
HttpClient = http.Client{
|
||||
Timeout: time.Duration(Timeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
snakeIds := []string{}
|
||||
for _, snake := range snakes {
|
||||
snakeIds = append(snakeIds, snake.ID)
|
||||
}
|
||||
state, err := ruleset.CreateInitialBoardState(Width, Height, snakeIds)
|
||||
if err != nil {
|
||||
log.Panic("[PANIC]: Error Initializing Board State")
|
||||
panic(err)
|
||||
}
|
||||
for _, snake := range snakes {
|
||||
requestBody := getIndividualBoardStateForSnake(state, snake, nil)
|
||||
u, _ := url.ParseRequestURI(snake.URL)
|
||||
u.Path = path.Join(u.Path, "start")
|
||||
_, err = HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: Request to %v failed", u.String())
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func createNextBoardState(ruleset rules.Ruleset, royale rules.RoyaleRuleset, state *rules.BoardState, outOfBounds []rules.Point, snakes []InternalSnake) (*rules.BoardState, []rules.Point) {
|
||||
var moves []rules.SnakeMove
|
||||
if Sequential {
|
||||
for _, snake := range snakes {
|
||||
moves = append(moves, getMoveForSnake(state, snake, outOfBounds))
|
||||
}
|
||||
} else {
|
||||
c := make(chan rules.SnakeMove, len(snakes))
|
||||
for _, snake := range snakes {
|
||||
go getConcurrentMoveForSnake(state, snake, outOfBounds, c)
|
||||
}
|
||||
for range snakes {
|
||||
moves = append(moves, <-c)
|
||||
}
|
||||
}
|
||||
for _, move := range moves {
|
||||
snake := InternalSnakes[move.ID]
|
||||
snake.LastMove = move.Move
|
||||
InternalSnakes[move.ID] = snake
|
||||
}
|
||||
if GameType == "royale" {
|
||||
_, err := royale.CreateNextBoardState(state, moves)
|
||||
if err != nil {
|
||||
log.Panic("[PANIC]: Error Producing Next Royale Board State")
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
state, err := ruleset.CreateNextBoardState(state, moves)
|
||||
if err != nil {
|
||||
log.Panic("[PANIC]: Error Producing Next Board State")
|
||||
panic(err)
|
||||
}
|
||||
return state, royale.OutOfBounds
|
||||
}
|
||||
|
||||
func getConcurrentMoveForSnake(state *rules.BoardState, snake InternalSnake, outOfBounds []rules.Point, c chan rules.SnakeMove) {
|
||||
c <- getMoveForSnake(state, snake, outOfBounds)
|
||||
}
|
||||
|
||||
func getMoveForSnake(state *rules.BoardState, snake InternalSnake, outOfBounds []rules.Point) rules.SnakeMove {
|
||||
requestBody := getIndividualBoardStateForSnake(state, snake, outOfBounds)
|
||||
u, _ := url.ParseRequestURI(snake.URL)
|
||||
u.Path = path.Join(u.Path, "move")
|
||||
res, err := HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||
move := snake.LastMove
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: Request to %v failed\n", u.String())
|
||||
log.Printf("Body --> %v\n", string(requestBody))
|
||||
} else if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
} else {
|
||||
playerResponse := PlayerResponse{}
|
||||
jsonErr := json.Unmarshal(body, &playerResponse)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
} else {
|
||||
move = playerResponse.Move
|
||||
if snake.API == "1" && move == "up" {
|
||||
move = "down"
|
||||
} else if snake.API == "1" && move == "down" {
|
||||
move = "up"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rules.SnakeMove{ID: snake.ID, Move: move}
|
||||
}
|
||||
|
||||
func sendEndRequest(state *rules.BoardState, snake InternalSnake) {
|
||||
requestBody := getIndividualBoardStateForSnake(state, snake, nil)
|
||||
u, _ := url.ParseRequestURI(snake.URL)
|
||||
u.Path = path.Join(u.Path, "end")
|
||||
_, err := HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: Request to %v failed", u.String())
|
||||
}
|
||||
}
|
||||
|
||||
func getIndividualBoardStateForSnake(state *rules.BoardState, snake InternalSnake, outOfBounds []rules.Point) []byte {
|
||||
var youSnake rules.Snake
|
||||
for _, snk := range state.Snakes {
|
||||
if snake.ID == snk.ID {
|
||||
youSnake = snk
|
||||
break
|
||||
}
|
||||
}
|
||||
response := ResponsePayload{
|
||||
Game: GameResponse{Id: GameId, Timeout: Timeout},
|
||||
Turn: Turn,
|
||||
Board: BoardResponse{
|
||||
Height: state.Height,
|
||||
Width: state.Width,
|
||||
Food: xyFromPointArray(state.Food),
|
||||
Hazards: xyFromPointArray(outOfBounds),
|
||||
Snakes: buildSnakesResponse(state.Snakes),
|
||||
},
|
||||
You: snakeResponseFromSnake(youSnake),
|
||||
}
|
||||
responseJson, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
log.Panic("[PANIC]: Error Marshalling JSON from State")
|
||||
panic(err)
|
||||
}
|
||||
return responseJson
|
||||
}
|
||||
|
||||
func snakeResponseFromSnake(snake rules.Snake) SnakeResponse {
|
||||
return SnakeResponse{
|
||||
Id: snake.ID,
|
||||
Name: InternalSnakes[snake.ID].Name,
|
||||
Health: snake.Health,
|
||||
Body: xyFromPointArray(snake.Body),
|
||||
Latency: 0,
|
||||
Head: xyFromPoint(snake.Body[0]),
|
||||
Length: int32(len(snake.Body)),
|
||||
Shout: "",
|
||||
Squad: InternalSnakes[snake.ID].Squad,
|
||||
}
|
||||
}
|
||||
|
||||
func buildSnakesResponse(snakes []rules.Snake) []SnakeResponse {
|
||||
var a []SnakeResponse
|
||||
for _, snake := range snakes {
|
||||
a = append(a, snakeResponseFromSnake(snake))
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func xyFromPoint(pt rules.Point) XY {
|
||||
return XY{X: pt.X, Y: pt.Y}
|
||||
}
|
||||
|
||||
func xyFromPointArray(ptArray []rules.Point) []XY {
|
||||
a := make([]XY, 0)
|
||||
for _, pt := range ptArray {
|
||||
a = append(a, xyFromPoint(pt))
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func buildSnakesFromOptions() []InternalSnake {
|
||||
bodyChars := []rune{'■', '⌀', '●', '⍟', '◘', '☺', '□', '☻'}
|
||||
var numSnakes int
|
||||
var snakes []InternalSnake
|
||||
numNames := len(Names)
|
||||
numURLs := len(URLs)
|
||||
numSquads := len(Squads)
|
||||
if numNames > numURLs {
|
||||
numSnakes = numNames
|
||||
} else {
|
||||
numSnakes = numURLs
|
||||
}
|
||||
if numNames != numURLs {
|
||||
log.Println("[WARN]: Number of Names and URLs do not match: defaults will be applied to missing values")
|
||||
}
|
||||
for i := int(0); i < numSnakes; i++ {
|
||||
var snakeName string
|
||||
var snakeURL string
|
||||
var snakeSquad string
|
||||
|
||||
id := uuid.New().String()
|
||||
|
||||
if i < numNames {
|
||||
snakeName = Names[i]
|
||||
} else {
|
||||
log.Printf("[WARN]: Name for URL %v is missing: a default name will be applied\n", URLs[i])
|
||||
snakeName = id
|
||||
}
|
||||
|
||||
if i < numURLs {
|
||||
u, err := url.ParseRequestURI(URLs[i])
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: URL %v is not valid: a default will be applied\n", URLs[i])
|
||||
snakeURL = "https://example.com"
|
||||
} else {
|
||||
snakeURL = u.String()
|
||||
}
|
||||
} else {
|
||||
log.Printf("[WARN]: URL for Name %v is missing: a default URL will be applied\n", Names[i])
|
||||
snakeURL = "https://example.com"
|
||||
}
|
||||
|
||||
if GameType == "squad" {
|
||||
if i < numSquads {
|
||||
snakeSquad = Squads[i]
|
||||
} else {
|
||||
log.Printf("[WARN]: Squad for URL %v is missing: a default squad will be applied\n", URLs[i])
|
||||
snakeSquad = strconv.Itoa(i / 2)
|
||||
}
|
||||
}
|
||||
res, err := HttpClient.Get(snakeURL)
|
||||
api := "0"
|
||||
if err != nil {
|
||||
log.Printf("[WARN]: Request to %v failed", snakeURL)
|
||||
} else if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
body, readErr := ioutil.ReadAll(res.Body)
|
||||
if readErr != nil {
|
||||
log.Fatal(readErr)
|
||||
}
|
||||
|
||||
pingResponse := PingResponse{}
|
||||
jsonErr := json.Unmarshal(body, &pingResponse)
|
||||
if jsonErr != nil {
|
||||
log.Fatal(jsonErr)
|
||||
} else {
|
||||
api = pingResponse.APIVersion
|
||||
}
|
||||
}
|
||||
snake := InternalSnake{Name: snakeName, URL: snakeURL, ID: id, API: api, LastMove: "up", Character: bodyChars[i%8]}
|
||||
if GameType == "squad" {
|
||||
snake.Squad = snakeSquad
|
||||
}
|
||||
snakes = append(snakes, snake)
|
||||
}
|
||||
return snakes
|
||||
}
|
||||
|
||||
func printMap(state *rules.BoardState, outOfBounds []rules.Point, gameTurn int32) {
|
||||
var o bytes.Buffer
|
||||
o.WriteString(fmt.Sprintf("[%v]\n", gameTurn))
|
||||
board := make([][]rune, state.Width)
|
||||
for i := range board {
|
||||
board[i] = make([]rune, state.Height)
|
||||
}
|
||||
for y := int32(0); y < state.Height; y++ {
|
||||
for x := int32(0); x < state.Width; x++ {
|
||||
board[x][y] = '◦'
|
||||
}
|
||||
}
|
||||
for _, oob := range outOfBounds {
|
||||
board[oob.X][oob.Y] = '░'
|
||||
}
|
||||
o.WriteString(fmt.Sprintf("Hazards ░: %v\n", outOfBounds))
|
||||
for _, f := range state.Food {
|
||||
board[f.X][f.Y] = '⚕'
|
||||
}
|
||||
o.WriteString(fmt.Sprintf("Food ⚕: %v\n", state.Food))
|
||||
for _, s := range state.Snakes {
|
||||
for _, b := range s.Body {
|
||||
board[b.X][b.Y] = InternalSnakes[s.ID].Character
|
||||
}
|
||||
o.WriteString(fmt.Sprintf("%v %c: %v\n", InternalSnakes[s.ID].Name, InternalSnakes[s.ID].Character, s))
|
||||
}
|
||||
for y := state.Height - 1; y >= 0; y-- {
|
||||
for x := int32(0); x < state.Width; x++ {
|
||||
o.WriteRune(board[x][y])
|
||||
}
|
||||
o.WriteString("\n")
|
||||
}
|
||||
log.Print(o.String())
|
||||
}
|
||||
62
cli/cmd/root.go
Normal file
62
cli/cmd/root.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "battlesnake",
|
||||
Short: "Battlesnake Command-Line Interface",
|
||||
Long: "Tools and utilities for Battlesnake games.",
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.battlesnake.yaml)")
|
||||
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Search config in home directory with name ".battlesnake" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigName(".battlesnake")
|
||||
}
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
||||
7
cli/main.go
Normal file
7
cli/main.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
import "github.com/BattlesnakeOfficial/rules/cli/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue