github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/families/battleship/sawtooth_battleship/battleship_cli.py (about) 1 #!/usr/bin/env python 2 # 3 # Copyright 2016 Intel Corporation 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # ------------------------------------------------------------------------------ 17 18 from __future__ import print_function 19 20 import argparse 21 import configparser 22 import getpass 23 import json 24 import logging 25 import os 26 import traceback 27 import sys 28 29 from colorlog import ColoredFormatter 30 31 from sawtooth_signing import create_context 32 33 from sawtooth_battleship.battleship_board import BoardLayout 34 from sawtooth_battleship.battleship_board import create_nonces 35 from sawtooth_battleship.battleship_client import BattleshipClient 36 from sawtooth_battleship.battleship_exceptions import BattleshipException 37 38 39 def create_console_handler(verbose_level): 40 clog = logging.StreamHandler() 41 formatter = ColoredFormatter( 42 "%(log_color)s[%(asctime)s %(levelname)-8s%(module)s]%(reset)s " 43 "%(white)s%(message)s", 44 datefmt="%H:%M:%S", 45 reset=True, 46 log_colors={ 47 'DEBUG': 'cyan', 48 'INFO': 'green', 49 'WARNING': 'yellow', 50 'ERROR': 'red', 51 'CRITICAL': 'red', 52 }) 53 54 clog.setFormatter(formatter) 55 56 if verbose_level == 0: 57 clog.setLevel(logging.WARN) 58 elif verbose_level == 1: 59 clog.setLevel(logging.INFO) 60 else: 61 clog.setLevel(logging.DEBUG) 62 63 return clog 64 65 66 def setup_loggers(verbose_level): 67 logger = logging.getLogger() 68 logger.setLevel(logging.DEBUG) 69 logger.addHandler(create_console_handler(verbose_level)) 70 71 72 def add_create_parser(subparsers, parent_parser): 73 parser = subparsers.add_parser('create', parents=[parent_parser]) 74 75 parser.add_argument( 76 'name', 77 type=str, 78 help='an identifier for the new game') 79 80 parser.add_argument( 81 '--ships', 82 type=str, 83 help="a space delimited string of ship types: 'AAA SS BBB'" 84 ) 85 86 parser.add_argument( 87 '--wait', 88 nargs='?', 89 const=sys.maxsize, 90 type=int, 91 help='wait for game to commit, set an integer to specify a timeout') 92 93 94 def add_fire_parser(subparsers, parent_parser): 95 parser = subparsers.add_parser('fire', parents=[parent_parser]) 96 97 parser.add_argument( 98 'name', 99 type=str, 100 help='the identifier for the game') 101 102 parser.add_argument( 103 'column', 104 type=str, 105 help='the column to fire upon (A-J)') 106 107 parser.add_argument( 108 'row', 109 type=str, 110 help='the row to fire upon (1-10)') 111 112 parser.add_argument( 113 '--wait', 114 nargs='?', 115 const=sys.maxsize, 116 type=int, 117 help='wait for game to commit, set an integer to specify a timeout') 118 119 120 def add_join_parser(subparsers, parent_parser): 121 parser = subparsers.add_parser('join', parents=[parent_parser]) 122 123 parser.add_argument( 124 'name', 125 type=str, 126 help='the identifier for the game') 127 128 parser.add_argument( 129 '--wait', 130 nargs='?', 131 const=sys.maxsize, 132 type=int, 133 help='wait for game to commit, set an integer to specify a timeout') 134 135 136 def add_init_parser(subparsers, parent_parser): 137 parser = subparsers.add_parser('init', parents=[parent_parser]) 138 139 parser.add_argument( 140 '--username', 141 type=str, 142 help='the name of the player') 143 144 parser.add_argument( 145 '--url', 146 type=str, 147 help='the url of the REST API') 148 149 150 def add_list_parser(subparsers, parent_parser): 151 subparsers.add_parser('list', parents=[parent_parser]) 152 153 154 def add_show_parser(subparsers, parent_parser): 155 parser = subparsers.add_parser('show', parents=[parent_parser]) 156 157 parser.add_argument( 158 'name', 159 type=str, 160 help='the identifier for the game') 161 162 163 def add_genstats_parser(subparsers, parent_parser): 164 parser = subparsers.add_parser('genstats', parents=[parent_parser]) 165 166 parser.add_argument( 167 '--count', 168 type=int, 169 default=100000, 170 help='the number of games to create') 171 172 parser.add_argument( 173 '--size', 174 type=int, 175 default=10, 176 help='the board size') 177 178 179 def create_parent_parser(prog_name): 180 parent_parser = argparse.ArgumentParser(prog=prog_name, add_help=False) 181 parent_parser.add_argument( 182 '-v', '--verbose', 183 action='count', 184 help='enable more verbose output') 185 186 return parent_parser 187 188 189 def create_parser(prog_name): 190 parent_parser = create_parent_parser(prog_name) 191 192 parser = argparse.ArgumentParser( 193 parents=[parent_parser], 194 formatter_class=argparse.RawDescriptionHelpFormatter) 195 196 subparsers = parser.add_subparsers(title='subcommands', dest='command') 197 198 add_create_parser(subparsers, parent_parser) 199 add_fire_parser(subparsers, parent_parser) 200 add_genstats_parser(subparsers, parent_parser) 201 add_init_parser(subparsers, parent_parser) 202 add_join_parser(subparsers, parent_parser) 203 add_list_parser(subparsers, parent_parser) 204 add_show_parser(subparsers, parent_parser) 205 206 return parser 207 208 209 def do_create(args, config): 210 name = args.name 211 if args.ships is not None: 212 ships = args.ships.split(' ') 213 else: 214 ships = ["AAAAA", "BBBB", "CCC", "DD", "DD", "SSS", "SSS"] 215 216 url = config.get('DEFAULT', 'url') 217 key_file = config.get('DEFAULT', 'key_file') 218 219 client = BattleshipClient(base_url=url, keyfile=key_file, wait=args.wait) 220 client.create(name=name, ships=ships) 221 222 223 def do_init(args, config): 224 username = args.username \ 225 if args.username else config.get('DEFAULT', 'username') 226 url = args.url if args.url else config.get('DEFAULT', 'url') 227 228 config.set('DEFAULT', 'username', username) 229 config.set('DEFAULT', 'url', url) 230 231 print("set username: %s" % username) 232 print("set url: %s" % url) 233 234 save_config(config) 235 236 priv_filename = config.get('DEFAULT', 'key_file') 237 if priv_filename.endswith(".priv"): 238 public_key_filename = priv_filename[0:-len(".priv")] + ".pub" 239 else: 240 public_key_filename = priv_filename + ".pub" 241 242 if not os.path.exists(priv_filename): 243 try: 244 if not os.path.exists(os.path.dirname(priv_filename)): 245 os.makedirs(os.path.dirname(priv_filename)) 246 247 context = create_context('secp256k1') 248 private_key = context.new_random_private_key() 249 public_key = context.get_public_key(private_key) 250 251 with open(priv_filename, "w") as priv_fd: 252 print("writing file: {}".format(priv_filename)) 253 priv_fd.write(private_key.as_hex()) 254 priv_fd.write("\n") 255 256 with open(public_key_filename, "w") as public_key_fd: 257 print("writing file: {}".format(public_key_filename)) 258 public_key_fd.write(public_key.as_hex()) 259 public_key_fd.write("\n") 260 except IOError as ioe: 261 raise BattleshipException("IOError: {}".format(str(ioe))) 262 263 264 def do_fire(args, config): 265 name = args.name 266 column = args.column 267 row = args.row 268 269 url = config.get('DEFAULT', 'url') 270 key_file = config.get('DEFAULT', 'key_file') 271 272 data = load_data(config) 273 274 if name not in data['games']: 275 raise BattleshipException( 276 "no such game in local database: {}".format(name)) 277 278 client = BattleshipClient(base_url=url, keyfile=key_file, wait=args.wait) 279 state = client.list_games() 280 281 if name not in state: 282 raise BattleshipException( 283 "no such game: {}".format(name)) 284 state_game = state[name] 285 286 reveal_space = None 287 reveal_nonce = None 288 289 if 'LastFireColumn' in state_game: 290 last_col = ord(state_game['LastFireColumn']) - ord('A') 291 last_row = int(state_game['LastFireRow']) - 1 292 293 layout = BoardLayout.deserialize(data['games'][name]['layout']) 294 nonces = data['games'][name]['nonces'] 295 296 reveal_space = layout.render()[last_row][last_col] 297 reveal_nonce = nonces[last_row][last_col] 298 299 response = client.fire( 300 name=name, 301 column=column, 302 row=row, 303 reveal_space=reveal_space, 304 reveal_nonce=reveal_nonce) 305 306 print(response) 307 308 309 def do_join(args, config): 310 name = args.name 311 312 url = config.get('DEFAULT', 'url') 313 key_file = config.get('DEFAULT', 'key_file') 314 315 data = load_data(config) 316 317 client_for_state = BattleshipClient(base_url=url, keyfile=key_file) 318 state = client_for_state.list_games() 319 if name not in state: 320 raise BattleshipException( 321 "No such game: {}".format(name) 322 ) 323 game = state[name] 324 ships = game['Ships'] 325 326 if name not in data['games']: 327 new_layout = BoardLayout.generate(ships=ships) 328 data['games'][name] = {} 329 data['games'][name]['layout'] = new_layout.serialize() 330 data['games'][name]['nonces'] = create_nonces(new_layout.size) 331 332 home = os.path.expanduser("~") 333 334 username = config.get('DEFAULT', 'username') 335 336 data_file = os.path.join(home, 337 ".sawtooth", 338 "battleship-{}.data".format(username)) 339 with open(data_file + ".new", 'w') as fd: 340 json.dump(data, fd, sort_keys=True, indent=4) 341 if os.name == 'nt': 342 if os.path.exists(data_file): 343 os.remove(data_file) 344 os.rename(data_file + ".new", data_file) 345 else: 346 print("Board and nonces already defined for game, reusing...") 347 348 layout = BoardLayout.deserialize(data['games'][name]['layout']) 349 nonces = data['games'][name]['nonces'] 350 351 hashed_board = layout.render_hashed(nonces) 352 353 client = BattleshipClient(base_url=url, keyfile=key_file, wait=args.wait) 354 client.join(name=name, board=hashed_board) 355 356 357 def do_list(args, config): 358 url = config.get('DEFAULT', 'url') 359 key_file = config.get('DEFAULT', 'key_file') 360 361 client = BattleshipClient(base_url=url, keyfile=key_file) 362 state = client.list_games() 363 364 fmt = "%-15s %-15.15s %-15.15s %s" 365 print(fmt % ('GAME', 'PLAYER 1', 'PLAYER 2', 'STATE')) 366 367 keys = list(state.keys()) 368 keys.sort() 369 for name in keys: 370 if 'Player1' in state[name]: 371 player1 = state[name]['Player1'] 372 else: 373 player1 = '' 374 if 'Player2' in state[name]: 375 player2 = state[name]['Player2'] 376 else: 377 player2 = '' 378 game_state = state[name]['State'] 379 print(fmt % (name, player1, player2, game_state)) 380 381 382 def do_show(args, config): 383 name = args.name 384 385 url = config.get('DEFAULT', 'url') 386 key_file = config.get('DEFAULT', 'key_file') 387 388 data = load_data(config) 389 390 client = BattleshipClient(base_url=url, keyfile=key_file) 391 state = client.list_games() 392 393 if name not in state: 394 raise BattleshipException('no such game: {}'.format(name)) 395 396 game = state[name] 397 398 player1 = '' 399 player2 = '' 400 if 'Player1' in game: 401 player1 = game['Player1'] 402 if 'Player2' in game: 403 player2 = game['Player2'] 404 game_state = game['State'] 405 406 print("GAME: : {}".format(name)) 407 print("PLAYER 1 : {}".format(player1)) 408 print("PLAYER 2 : {}".format(player2)) 409 print("STATE : {}".format(game_state)) 410 411 # figure out the proper user's target board, given the public_key 412 priv_filename = config.get('DEFAULT', 'key_file') 413 if priv_filename.endswith(".priv"): 414 public_key_filename = priv_filename[0:-len(".priv")] + ".pub" 415 else: 416 public_key_filename = priv_filename + ".pub" 417 public_key_file = open(public_key_filename, mode='r') 418 public_key = public_key_file.readline().rstrip('\n') 419 420 if 'Player1' in game and public_key == game['Player1']: 421 target_board_name = 'TargetBoard1' 422 elif 'Player2' in game and public_key == game['Player2']: 423 target_board_name = 'TargetBoard2' 424 else: 425 raise BattleshipException("Player hasn't joined game.") 426 427 # figure out who fired last and who is calling do_show 428 # to determine which board * is diplayed on to 429 # show pending shot 430 if 'LastFireRow' in game and 'LastFireColumn' in game: 431 last_fire = (int(game['LastFireRow']) - 1, 432 int(ord(game['LastFireColumn'])) - ord('A')) 433 else: 434 last_fire = None 435 436 if game_state == 'P1-NEXT' and target_board_name == 'TargetBoard1': 437 # player 2 last shot and player 1 is looking 438 will_be_on_target_board = False 439 elif game_state == 'P1-NEXT' and target_board_name == 'TargetBoard2': 440 # player 2 last shot and player 2 is looking 441 will_be_on_target_board = True 442 elif game_state == 'P2-NEXT' and target_board_name == 'TargetBoard1': 443 # player 1 last shot and player 1 is looking 444 will_be_on_target_board = True 445 elif game_state == 'P2-NEXT' and target_board_name == 'TargetBoard2': 446 # player 1 last shot and player 2 is looking 447 will_be_on_target_board = False 448 else: 449 last_fire = None 450 will_be_on_target_board = False 451 452 if target_board_name in game: 453 target_board = game[target_board_name] 454 size = len(target_board) 455 456 print() 457 print(" Target Board") 458 print_board(target_board, size, is_target_board=True, 459 pending_on_target_board=will_be_on_target_board, 460 last_fire=last_fire) 461 462 if name in data['games']: 463 layout = BoardLayout.deserialize(data['games'][name]['layout']) 464 board = layout.render() 465 size = len(board) 466 467 print() 468 print(" Secret Board") 469 print_board(board, size, is_target_board=False, 470 pending_on_target_board=will_be_on_target_board, 471 last_fire=last_fire) 472 473 474 def print_board(board, size, is_target_board=True, 475 pending_on_target_board=False, last_fire=None): 476 print(''.join(["-"] * (size * 3 + 3))) 477 print(" ", end=' ') 478 for i in range(0, size): 479 print(" {}".format(chr(ord('A') + i)), end=' ') 480 print() 481 482 for row_idx, row in enumerate(range(0, size)): 483 print("%2d" % (row + 1), end=' ') 484 for col_idx, space in enumerate(board[row]): 485 if is_target_board: 486 if pending_on_target_board and last_fire is not None and \ 487 row_idx == last_fire[0] and col_idx == last_fire[1]: 488 489 print(" {}".format( 490 space.replace('?', '*') 491 ), end=' ') 492 else: 493 print(" {}".format( 494 space.replace('?', ' ') 495 .replace('M', '.').replace('H', 'X') 496 ), end=' ') 497 498 else: 499 if not pending_on_target_board and last_fire is not None and \ 500 row_idx == last_fire[0] and col_idx == last_fire[1]: 501 print(" {}".format( 502 '*' 503 ), end=' ') 504 else: 505 print(" {}".format( 506 space.replace('-', ' ') 507 ), end=' ') 508 print() 509 510 511 def do_genstats(args, config): 512 count = args.count 513 size = args.size 514 ships = ["AAAAA", "BBBB", "CCC", "DD", "DD", "SSS", "SSS"] 515 # Create a board which contains a count of the number of time 516 # a space was used. 517 count_board = [[0] * size for i in range(size)] 518 for i in range(0, count): 519 layout = BoardLayout.generate(size=size, ships=ships) 520 board = layout.render() 521 for row in range(0, size): 522 for col in range(0, size): 523 if board[row][col] != '-': 524 count_board[row][col] += 1 525 526 print("Percentages Board") 527 print("-----------------") 528 529 # Print the board of percentages. 530 print(" ", end=' ') 531 for i in range(0, size): 532 print(" {}".format(chr(ord('A') + i)), end=' ') 533 print() 534 535 for row in range(0, size): 536 print("%2d" % (row + 1), end=' ') 537 for space in count_board[row]: 538 print("%3.0f" % (float(space) / float(count) * 100,), end=' ') 539 print() 540 541 print() 542 print("Total Games Created: {}".format(count)) 543 544 545 def load_config(): 546 home = os.path.expanduser("~") 547 real_user = getpass.getuser() 548 549 config_file = os.path.join(home, ".sawtooth", "battleship.cfg") 550 key_dir = os.path.join(home, ".sawtooth", "keys") 551 552 config = configparser.SafeConfigParser() 553 config.set('DEFAULT', 'url', 'http://127.0.0.1:8008') 554 config.set('DEFAULT', 'key_dir', key_dir) 555 config.set('DEFAULT', 'key_file', '%(key_dir)s/%(username)s.priv') 556 config.set('DEFAULT', 'username', real_user) 557 if os.path.exists(config_file): 558 config.read(config_file) 559 560 return config 561 562 563 def save_config(config): 564 home = os.path.expanduser("~") 565 566 config_file = os.path.join(home, ".sawtooth", "battleship.cfg") 567 if not os.path.exists(os.path.dirname(config_file)): 568 os.makedirs(os.path.dirname(config_file)) 569 570 with open("{}.new".format(config_file), "w") as fd: 571 config.write(fd) 572 if os.name == 'nt': 573 if os.path.exists(config_file): 574 os.remove(config_file) 575 os.rename("{}.new".format(config_file), config_file) 576 577 578 def load_data(config): 579 home = os.path.expanduser("~") 580 581 username = config.get('DEFAULT', 'username') 582 583 data_file = os.path.join(home, 584 ".sawtooth", 585 "battleship-{}.data".format(username)) 586 if os.path.exists(data_file): 587 with open(data_file, 'r') as fd: 588 data = json.load(fd) 589 else: 590 data = {'games': {}} 591 592 return data 593 594 595 def main(prog_name=os.path.basename(sys.argv[0]), args=None): 596 if args is None: 597 args = sys.argv[1:] 598 parser = create_parser(prog_name) 599 args = parser.parse_args(args) 600 601 if args.verbose is None: 602 verbose_level = 0 603 else: 604 verbose_level = args.verbose 605 606 setup_loggers(verbose_level=verbose_level) 607 608 config = load_config() 609 610 if args.command == 'create': 611 do_create(args, config) 612 elif args.command == 'fire': 613 do_fire(args, config) 614 elif args.command == 'genstats': 615 do_genstats(args, config) 616 elif args.command == 'init': 617 do_init(args, config) 618 elif args.command == 'join': 619 do_join(args, config) 620 elif args.command == 'list': 621 do_list(args, config) 622 elif args.command == 'show': 623 do_show(args, config) 624 else: 625 raise BattleshipException("invalid command: {}".format(args.command)) 626 627 628 def main_wrapper(): 629 try: 630 main() 631 except BattleshipException as e: 632 print("Error: {}".format(e), file=sys.stderr) 633 sys.exit(1) 634 except KeyboardInterrupt: 635 pass 636 except SystemExit as e: 637 raise e 638 except BaseException: 639 traceback.print_exc(file=sys.stderr) 640 sys.exit(1)