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)