Browse Source

Added load and save game ability + code cleaning + debugging

master
Doug Le Tough 6 years ago
parent
commit
2d0cd5a93e
7 changed files with 291 additions and 211 deletions
  1. +4
    -1
      CHANGELOG.TXT
  2. +25
    -4
      README.md
  3. +2
    -2
      TODO.TXT
  4. +36
    -24
      bot.py
  5. +136
    -124
      client.py
  6. +2
    -0
      game.py
  7. +86
    -56
      ui.py

+ 4
- 1
CHANGELOG.TXT View File

@ -1,6 +1,9 @@
2014-10-11:
- Added save game ability
- Added load game from file ability
- Added replay game ability
- Added heroes color
2014-10-10:
- Added load config from file ability
- Added save config to file ability


+ 25
- 4
README.md View File

@ -35,7 +35,18 @@ http://www.vindinium.org/
- ui.py The curses U.I. source code
3 - Prerequities:
3 - Goal:
-----------------
The main goal of this project is to provide a solution to people r
unning their bots on a server and who'd like to view their
bots' activity in real-time without using the http connection
graciously provided by Vindinium.
This U.I to Vindinium is usable in a terminal and using an ssh
connection also permits reducing network latency..
4 - Prerequities:
-----------------
a - An Unix-like system (Linux/BSD/MacOSX/...)
@ -53,7 +64,7 @@ http://www.vindinium.org/
d - The python-curses modules
4 - How to make it work:
5 - How to make it work:
------------------------
The ai.py file contains the code of your bot.
@ -80,8 +91,18 @@ http://www.vindinium.org/
some basic infos to get connected to the server.
Note : The code provided here do NOT contains any AI or path-finding code
but only a random AI. However it would run fine as is but would only win by mistake :)
You may post your question about this project to doug.letough@free.fr
or to the #vindinium freenode.net IRC channel. The latter may be a
faster way to obtain answers but an email should always be privileged
as it guaranties an first-hand answer.
5 - Note :
----------
The code provided here do NOT contains any AI or path-finding code
but only a random AI (hey, This YOUR job to put it up).
However it would run fine as is but would only win by mistake :)


+ 2
- 2
TODO.TXT View File

@ -1,8 +1,8 @@
TODO
-----
- Add ability to load game
- Add ability to replay game
- Define one unique color for each player
- Add game summary at end of game
- Add currently winning player identification mark while game is running
- Add Windows support
- Allow user to browse turns while replaying game
- Allow user to increase/decrease display time while replaying game

+ 36
- 24
bot.py View File

@ -14,7 +14,7 @@ class Curses_ui_bot:
"""THis is your bot"""
def __init__(self):
self.running = True
self.state = None
self.state = {}
self.game = None
self.last_mine_count = 0
self.last_gold = 0
@ -23,20 +23,38 @@ class Curses_ui_bot:
self.hero_last_move = None
self.action = None
self.last_action = None
self.path_to_goal = None
self.decision = None
self.last_pos = None
self.path_to_goal = []
self.decision = []
self.nearest_enemy_pos = None
self.nearest_mine_pos = None
self.nearest_tavern_pos = None
self.last_nearest_enemy_pos = None
self.last_nearest_mine_pos = None
self.last_nearest_tavern_pos = None
self.last_pos = None
# The A.I, Skynet's rising !
self.ai = ai.AI()
def move(self, state):
"""Return store data provided by A.I
and return selected move"""
self.state = state
self.state = state
# Store status for later report
try:
self.hero_last_move = self.hero_move
self.last_life = self.game.hero.life
self.last_action = self.action
self.last_gold = self.game.hero.gold
self.last_mine_count = self.game.hero.mine_count
self.last_pos = self.game.hero.pos
self.last_nearest_enemy_pos = self.nearest_enemy_pos
self.last_nearest_mine_pos = self.nearest_mine_pos
self.last_nearest_tavern_pos = self.nearest_tavern_pos
except AttributeError:
# First move has no previous move
pass
self.game = Game(self.state)
################################################################
# Put your call to AI code here
################################################################
@ -54,28 +72,22 @@ class Curses_ui_bot:
# /AI
################################################################
# Store status for later report
self.hero_last_move = self.hero_move
self.last_life = self.game.hero.life
self.last_action = self.action
self.last_gold = self.game.hero.gold
self.last_mine_count = self.game.hero.mine_count
self.last_pos = self.game.hero.pos
self.last_nearest_enemy_pos = self.nearest_enemy_pos
self.last_nearest_mine_pos = self.nearest_mine_pos
self.last_nearest_tavern_pos = self.nearest_tavern_pos
return self.hero_move
def process_game(self, state):
"""Process state data (for replay mode)"""
self.state = state
try:
self.hero_last_move = self.hero_move
self.last_life = self.game.hero.life
self.last_action = self.action
self.last_gold = self.game.hero.gold
self.last_mine_count = self.game.hero.mine_count
self.last_nearest_enemy_pos = self.nearest_enemy_pos
self.last_nearest_mine_pos = self.nearest_mine_pos
self.last_nearest_tavern_pos = self.nearest_tavern_pos
except AttributeError:
# First move has no previous move and no game
pass
self.game = Game(self.state)
self.hero_last_move = self.hero_move
self.last_life = self.game.hero.life
self.last_action = self.action
self.last_gold = self.game.hero.gold
self.last_mine_count = self.game.hero.mine_count
self.last_pos = self.game.hero.pos
self.last_nearest_enemy_pos = self.nearest_enemy_pos
self.last_nearest_mine_pos = self.nearest_mine_pos
self.last_nearest_tavern_pos = self.nearest_tavern_pos

+ 136
- 124
client.py View File

@ -9,13 +9,14 @@ import select
import time
import os
import ConfigParser
import ast
TIMEOUT = 15
class Config:
def __init__(self, game_mode="training", server_url="http://vindinium.org",
number_of_games=1, number_of_turns=300, map_name="m1", key=None):
number_of_games=1, number_of_turns=300, map_name="m3", key=None):
self.game_mode = game_mode
self.number_of_games = number_of_games
self.number_of_turns = number_of_turns
@ -37,9 +38,11 @@ class Client:
self.states = []
self.delay = 0.5 # Delay in s between turns in replay mode
def _print(self, *args, **kwargs):
def pprint(self, *args, **kwargs):
"""Display args in the bot gui or
print it if no gui is available"""
print it if no gui is available
For debugging purpose consider using self.gui.append_log()
"""
printable = ""
for arg in args:
printable = printable + str(arg)+" "
@ -58,7 +61,7 @@ class Client:
# bot has a gui so we add this entries to its log panel
if self.gui.log_win:
self.gui.append_log(printable)
self.gui.log_win.refresh()
self.gui.refresh()
else:
print printable
@ -76,7 +79,7 @@ class Client:
self.config.key = config_parser.get("game", "key")
self.config.number_of_games = config_parser.getint("game", "number_of_games")
self.config.number_of_turns = config_parser.getint("game", "number_of_turns")
except Exception as e:
except (IOError, ConfigParser.Error) as e:
self.gui.quit_ui()
print "Error while loading config file", config_file_name, ":", e
quit(1)
@ -94,24 +97,39 @@ class Client:
for key, value in self.config.__dict__.items():
config_parser.set("game", key, value)
config_parser.write(config_file)
except Exception as e:
except (IOError, ConfigParser.Error) as e:
self.gui.quit_ui()
print "Error while saving config file", config_file_name, ":", e
quit(1)
def load_game(self, game_file_name):
"""Load saved game from file"""
# Reset our bot and self.states
self.states = []
try:
with open(game_file_name, "r") as game_file:
for line in game_file.readlines():
if len(line.strip(chr(0)).strip()) > 0:
self.states.append(ast.literal_eval(line))
self.state = self.states[0]
except (IOError, IndexError) as e:
self.gui.quit_ui()
print "Error while loading game file", game_file_name, ":", e
quit(1)
def save_game(self):
"""Save game to file in ~/.vindinium/save/<game ID>"""
user_home_dir = os.path.expanduser("~")
try :
try:
# Get game_id from game sate
game_id = self.state['game']["id"]
game_id = self.state['game']["id"]
except KeyError:
try:
# State has not been downloaded
# Try to get game_id from last state saved if any
game_id = self.states[0]['game']["id"]
game_id = self.states[0]['game']["id"]
except IndexError:
self._print("No states available for this game, unable to save game.")
self.pprint("No states available for this game, unable to save game.")
game_file_name = os.path.join(user_home_dir, ".vindinium", "save", game_id)
try:
if not os.path.isdir(os.path.join(user_home_dir, ".vindinium", "save")):
@ -119,12 +137,48 @@ class Client:
with open(game_file_name, "w") as game_file:
for state in self.states:
game_file.write(str(state)+"\n")
self._print("Game saved: "+game_file_name)
self.pprint("Game saved: "+game_file_name)
except IOError as e:
self.gui._print("Error while saving game file", game_file_name, ":", e)
self.gui.append_log("Error while saving game file", game_file_name, ":", e)
def download_game_file(self, game_file_url):
# I will treat no other forbidden char than space char.
game_file_url = game_file_url.replace(" ", "%20")
#
# FIXME :
#
# Game file available online at http://vindinium.org/events/<gameId>
# are not parsable by ast.literal_eval() ????
#
# self.session = requests.session()
# response = self.session.get(game_file_url, timeout=10*60)
# if response.status_code == 200:
# for line in response.text:
# line = line.strip(chr(0)).strip()
# if len(line) > 0:
# self.states.append(ast.literal_eval(line))
# try:
# print self.states[len(self.states)-1], type(self.states[len(self.states)-1])
# except:
# pass
self.gui.quit_ui()
os.system('cls' if os.name == 'nt' else 'clear')
print "********************************************************"
print "* Feature not available yet. *"
print "* Please wait for U.I restart *"
print "********************************************************"
for i in reversed(range(1, 6)):
print i
time.sleep(1)
self.start_ui()
def start_ui(self):
"""Start the curses UI"""
self.bot = Curses_ui_bot()
self.running = True
self.game_url = None
self.states = []
self.state = None
self.gui = ui.tui()
choice = self.gui.ask_main_menu()
if choice == '1':
@ -134,14 +188,17 @@ class Client:
self.play()
elif choice == '2':
# Setup game
self.config = Config()
choice = self.gui.ask_game_mode()
if choice == '1':
# Arena mode config
self.config.game_mode = "arena"
self.config.number_of_turns = 300
self.config.number_of_games = self.gui.ask_number_games()
elif choice == '2':
# Training mode config
self.config.game_mode = "training"
self.config.number_of_games = 1
self.config.number_of_turns = self.gui.ask_number_turns()
self.config.map_name = "m"+str(self.gui.ask_map())
self.config.server_url = self.gui.ask_server_url(self.config.game_mode)
@ -149,28 +206,36 @@ class Client:
if self.gui.ask_save_config():
self.save_config()
if self.gui.ask_play_game():
# Start game U.I
self.gui.draw_game_windows()
# Launch game
self.play()
else:
self.start_ui()
elif choice == '3':
# Load game from file
game_file_path = self.gui.ask_game_file_path()
self.gui.quit_ui()
print game_file_path, len(game_file_path)
exit(0)
game_file_name = self.gui.ask_game_file_path()
self.load_game(game_file_name)
self.gui.draw_game_windows()
self.replay()
elif choice == '4':
# Load game from URL
game_file_url = self.gui.ask_game_file_url()
self.gui.quit_ui()
print game_file_url, len(game_file_url)
exit(0)
self.download_game_file(game_file_url)
self.gui.draw_game_windows()
self.replay()
elif choice == '5':
# quit
self.gui.quit_ui()
exit(0)
if self.gui.running and self.gui.help_win:
key = None
while key != 'm':
key = self.gui.ask_quit()
if key == 's':
self.save_game()
elif key == 'r':
self.replay()
self.gui.clear()
self.start_ui()
def play(self):
"""Play all games"""
@ -178,67 +243,42 @@ class Client:
# start a new game
if self.bot.running:
self.start_game()
self._print("Game finished: "+str(i+1)+"/"+str(self.config.number_of_games))
if self.gui.running and self.gui.help_win:
key = None
while key != 'q':
key = self.gui.ask_quit()
if key == 's':
self.save_game()
elif key == 'r':
self.replay()
self.gui.quit_ui()
quit(0)
self.pprint("Game finished: "+str(i+1)+"/"+str(self.config.number_of_games))
def replay(self):
"""Replay last game"""
# Restart with a new bot
self.bot = Curses_ui_bot()
for i in range(self.config.number_of_games):
# start a new game
if self.bot.running:
self.restart_game()
self._print("Game finished.")
if self.gui.running and self.gui.help_win:
key = None
while key != 'q':
key = self.gui.ask_quit()
if key == 's':
self.save_game()
elif key == 'r':
self.replay()
self.gui.quit_ui()
quit(0)
self.pprint("Game finished.")
def start_game(self):
"""Starts a game with all the required parameters"""
self.running = True
# Delete prévious game states
self.states = []
# Restart game with brand new bot
self.bot = Curses_ui_bot()
# Default move is no move !
direction = "Stay"
# Create a requests session that will be used throughout the game
self.pprint('Connecting...')
self.session = requests.session()
if self.config.game_mode == 'arena':
self._print('Connecting and waiting for other players to join...')
self.pprint('Waiting for other players to join...')
try:
# Get the initial state
self.state = self.get_new_game_state()
self.states.append(self.state)
self._print("Playing at: " + self.state['viewUrl'])
except Exception as e:
self._print("Error: Please verify your settings.")
self._print("Settings:", self.config.__dict__)
self._print("Game state:", self.state)
self.pprint("Playing at: " + self.state['viewUrl'])
except (KeyError, TypeError) as e:
self.pprint("Error: Please verify your settings.")
self.pprint("Settings:", self.config.__dict__)
self.running = False
key = None
while key != 'q':
key = self.gui.ask_quit()
if key == 's':
self.save_game()
elif key == 'r':
self.replay()
self.gui.quit_ui()
quit(0)
return
for i in range(self.config.number_of_turns + 1):
if self.running:
# Choose a move
@ -248,7 +288,6 @@ class Client:
line = sys.stdin.read(1)
if line.strip() == "q":
self.running = False
self.gui.quit_ui()
self.bot.running = False
break
elif line.strip() == "p":
@ -260,22 +299,12 @@ class Client:
self.display_game()
except Exception, e:
if self.gui.log_win:
self._print("Error at client.start_game:", str(e))
self._print("If your code or your settings are not responsible of this error, please report this error to:")
self._print("doug.letough@free.fr.")
self.pprint("Error at client.start_game:", str(e))
self.pprint("If your code or your settings are not responsible of this error, please report this error to:")
self.pprint("doug.letough@free.fr.")
self.gui.pause()
self.running = False
if self.gui.help_win:
key = None
while key != 'q':
key = self.gui.ask_quit()
self.gui.append_log(key)
if key == 's':
self.save_game()
elif key == 'r':
self.replay()
self.gui.quit_ui()
quit(0)
self.running = False
return
if not self.is_game_over():
# Send the move and receive the updated game state
self.game_url = self.state['playUrl']
@ -290,21 +319,13 @@ class Client:
try:
# Get the initial state
self.state = self.states[0]
self._print("Playing at: " + self.state['viewUrl'])
self.pprint("Replaying: " + self.state['viewUrl'])
except (IndexError, KeyError) as e:
self._print("Error while trying to replay game.")
self._print("Game state:", self.state)
self.pprint("Error while trying to replay game.")
self.pprint("Game states length:", len(self.states))
self.running = False
key = None
while key != 'q':
key = self.gui.ask_quit()
if key == 's':
self.save_game()
elif key == 'r':
self.replay()
self.gui.quit_ui()
quit(0)
return
self.gui.draw_help_win()
for state in self.states:
self.state = state
if self.running:
@ -315,9 +336,7 @@ class Client:
line = sys.stdin.read(1)
if line.strip() == "q":
self.running = False
self.gui.quit_ui()
self.bot.running = False
quit(0)
break
elif line.strip() == "p":
self.gui.pause()
@ -328,22 +347,12 @@ class Client:
self.display_game()
except Exception, e:
if self.gui.log_win:
self._print("Error at client.restart_game:", str(e))
self._print("If your code or your settings are not responsible of this error, please report this error to:")
self._print("doug.letough@free.fr.")
self.pprint("Error at client.restart_game:", str(e))
self.pprint("If your code or your settings are not responsible of this error, please report this error to:")
self.pprint("doug.letough@free.fr.")
self.gui.pause()
self.running = False
if self.gui.help_win:
key = None
while key != 'q':
key = self.gui.ask_quit()
self.gui.append_log(key)
if key == 's':
self.save_game()
elif key == 'r':
self.replay()
self.gui.quit_ui()
quit(0)
self.running = False
return
if not self.is_game_over():
# Replay next turn
self.game_url = state['playUrl']
@ -362,34 +371,38 @@ class Client:
params = {'key': self.config.key}
api_endpoint = '/api/arena'
# Wait for 10 minutes
r = self.session.post(self.config.server_url + api_endpoint, params, timeout=10*60)
if r.status_code == 200:
return r.json()
else:
self._print("Error when creating the game:", str(r.status_code))
try:
r = self.session.post(self.config.server_url + api_endpoint, params, timeout=10*60)
if r.status_code == 200:
return r.json()
else:
self.pprint("Error when creating the game:", str(r.status_code))
self.running = False
self.pprint(r.text)
except requests.ConnectionError as e:
self.pprint("Error when creating the game:", e)
self.running = False
self._print(r.text)
def is_game_over(self):
"""Return True if game defined by state is over"""
try:
return self.state['game']['finished']
except Exception:
except (TypeError, KeyError):
return True
def send_move(self, direction):
"""Send a move to the server
Moves can be one of: 'Stay', 'North', 'South', 'East', 'West'"""
try:
r = self.session.post(self.game_url, {'dir': direction}, timeout=TIMEOUT)
if r.status_code == 200:
return r.json()
response = self.session.post(self.game_url, {'dir': direction}, timeout=TIMEOUT)
if response.status_code == 200:
return response.json()
else:
self._print("Error HTTP ", str(r.status_code), ": ", r.text)
self.pprint("Error HTTP ", str(response.status_code), ": ", response.text)
self.running = False
return {'game': {'finished': True}}
except requests.exceptions.RequestException as e:
self._print("Error at client.move;", str(e))
self.pprint("Error at client.move;", str(e))
self.running = False
return {'game': {'finished': True}}
@ -397,7 +410,7 @@ class Client:
"""Display game data on the U.I"""
if not self.gui.paused:
# Draw the map
self.gui.draw_map(self.bot.game.board_map, self.bot.path_to_goal)
self.gui.draw_map(self.bot.game.board_map, self.bot.path_to_goal, self.bot.game.heroes)
# Print informations about other players
self.gui.display_heroes(self.bot.game.heroes, self.bot.game.hero.user_id)
# Use the following methods to display datas
@ -424,16 +437,15 @@ class Client:
self.gui.display_last_nearest_mine(self.bot.last_nearest_mine_pos)
self.gui.display_last_nearest_hero(self.bot.last_nearest_enemy_pos)
self.gui.display_last_nearest_tavern(self.bot.last_nearest_tavern_pos)
# Print what you think is usefull to understand
# how the decision has been taken to make this move
# If too long the string will be truncated to fit
# in the display
# Print a *list of tuples* representing what you think can be usefull
# i.e an heuristic result
self.gui.display_decision(self.bot.decision)
# Print the estimated path to reach the goal if any
# Print *list of tuples* representing
# the estimated path to reach the goal if any.
# If too long the path will be truncated to fit
# in the display
self.gui.display_path(self.bot.path_to_goal)
# Move cursor along the time line
# Move cursor along the time line (cost cpu time)
cursor_pos = int(float(self.gui.TIME_W) / self.bot.game.max_turns * self.bot.game.turn)
self.gui.move_time_cursor(cursor_pos)
# Finally display selected move


+ 2
- 0
game.py View File

@ -9,9 +9,11 @@ class Hero:
# Training bots have no elo or userId
self.elo = hero['elo']
self.user_id = hero['userId']
self.bot_last_move = hero['lastDir']
except KeyError:
self.elo = 0
self.user_id = 0
self.last_move = None
self.bot_id = hero['id']
self.life = hero['life']


+ 86
- 56
ui.py View File

@ -18,47 +18,38 @@ class tui:
def __init__(self):
self.running = True
self.paused = False
self.DATA_Y = 1
self.DATA_X = 0
self.DATA_H = 29
self.DATA_W = 32
self.PLAYERS_Y = 1
self.PLAYERS_X = self.DATA_X + self.DATA_W + 2
self.PLAYERS_H = 21
self.PLAYERS_W = 66
self.MAP_Y = 1
self.MAP_X = self.PLAYERS_X + self.PLAYERS_W + 2
self.MAP_H = 0
self.MAP_W = 0
self.PATH_Y = self.PLAYERS_Y + self.PLAYERS_H + 3
self.PATH_X = self.DATA_X + self.DATA_W + 2
self.PATH_H = 5
self.PATH_W = 66
self.LOG_Y = self.DATA_Y + self.DATA_H + 2
self.LOG_X = 0
self.LOG_H = 12
self.LOG_W = self.DATA_W + self.PLAYERS_W + 2
self.HELP_Y = self.LOG_Y + self.LOG_H - 2
self.HELP_X = 1
self.HELP_H = 1
self.HELP_W = self.LOG_W - 2
self.TIME_Y = self.LOG_Y + self.LOG_H + 2
self.TIME_X = 0
self.TIME_H = 0
self.TIME_W = 0
self.MENU_Y = 0
self.MENU_X = 0
self.MENU_H = 24
self.MENU_W = 0
self.data_win = None
self.map_win = None
self.path_win = None
@ -67,21 +58,21 @@ class tui:
self.players_win = None
self.time_win = None
self.menu_win = None
self.time_win = None
self.log_entries = []
self.stdscr = curses.initscr()
curses.start_color()
# Basic color set
self.WBK = curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_WHITE)
self.ALERT = curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED)
self.RED = curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
self.YELLOW = curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK)
self.GREEN = curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK)
self.BLUE = curses.init_pair(6, curses.COLOR_WHITE, curses.COLOR_BLUE)
self.CYAN = curses.init_pair(7, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_WHITE)
curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED)
curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(6, curses.COLOR_WHITE, curses.COLOR_BLUE)
curses.init_pair(7, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
curses.init_pair(9, curses.COLOR_WHITE, curses.COLOR_GREEN)
curses.init_pair(10, curses.COLOR_WHITE, curses.COLOR_YELLOW)
# check for minimal screen size
screen_y, screen_x = self.stdscr.getmaxyx()
if screen_y < MIN_LINES or screen_x < MIN_COLS:
@ -93,7 +84,6 @@ class tui:
screen_y, "lines and", screen_x, "columns."
self.quit_ui()
quit(1)
# Screen is up
curses.noecho()
curses.cbreak()
@ -101,9 +91,29 @@ class tui:
self.stdscr.keypad(1)
# - /screen init ----
# - /init ---------------------------------------------------------------
def clear(self):
"""Refresh all windows"""
self.stdscr.erase()
if self.data_win:
self.data_win.erase()
if self.map_win:
self.map_win.erase()
if self.path_win:
self.path_win.erase()
if self.log_win:
self.log_win.erase()
if self.help_win:
self.help_win.erase()
if self.players_win:
self.players_win.erase()
if self.time_win:
self.time_win.erase()
if self.menu_win:
self.menu_win.erase()
curses.doupdate()
def refresh(self):
"""Refresh all windows"""
self.stdscr.addstr(self.DATA_Y - 1, self.DATA_X + 1, "Game", curses.A_BOLD)
@ -123,7 +133,7 @@ class tui:
if self.time_win:
self.time_win.noutrefresh()
if self.menu_win:
self.time_win.noutrefresh()
self.menu_win.noutrefresh()
curses.doupdate()
@ -133,7 +143,6 @@ class tui:
"""Draw the windows needed for the game"""
if self.menu_win:
self.menu_win.erase()
self.draw_data_win()
self.draw_path_win()
self.draw_log_win()
@ -148,7 +157,6 @@ class tui:
self.data_win.box()
self.data_pan = curses.panel.new_panel(self.data_win)
self.stdscr.addstr(self.DATA_Y - 1, self.DATA_X + 1, "Game", curses.A_BOLD)
data_lines = ["Playing",
"Bot name",
"Elo",
@ -163,14 +171,11 @@ class tui:
"Nearest hero",
"Nearest bar",
"Nearest mine"]
self.data_win.vline(1, 13, curses.ACS_VLINE, self.DATA_H)
self.data_win.addch(0, 13, curses.ACS_TTEE)
self.data_win.addch(self.DATA_H-1, 13, curses.ACS_BTEE)
self.data_win.vline(9, 22, curses.ACS_VLINE, self.DATA_H-9)
self.data_win.addch(self.DATA_H-1, 22, curses.ACS_BTEE)
y = 0
for line in data_lines:
self.data_win.addstr(y + 1, 1, line, curses.A_BOLD)
@ -245,7 +250,6 @@ class tui:
self.players_win.addch(self.PLAYERS_H-1, 11, curses.ACS_BTEE)
self.players_win.addch(self.PLAYERS_H-1, 29, curses.ACS_BTEE)
self.players_win.addch(self.PLAYERS_H-1, 47, curses.ACS_BTEE)
y = 0
for line in players_lines:
self.players_win.addstr(y+1, 1, line, curses.A_BOLD)
@ -269,29 +273,38 @@ class tui:
# MAP ------------------------------------------------------------------
def draw_map(self, board_map, path):
def draw_map(self, board_map, path, heroes):
"""Draw the map"""
board_size = len(board_map)
self.MAP_H = board_size
self.MAP_W = board_size
if not self.map_win:
self.stdscr.addstr(self.MAP_Y - 1, self.MAP_X + 1, "Map ("+str(board_size)+"X"+str(board_size)+")", curses.A_BOLD)
self.map_win = curses.newwin(board_size + 2, board_size + 2, self.MAP_Y, self.MAP_X)
self.stdscr.addstr(self.MAP_Y - 1, self.MAP_X + 1, "Map ("+str(board_size)+"X"+str(board_size)+")", curses.A_BOLD)
self.stdscr.noutrefresh()
if self.map_win:
x, y = self.map_win.getmaxyx()
if x != board_size + 2 or y != board_size + 2:
# resize the window as needed
self.map_win.erase()
curses.panel.update_panels()
self.map_win.resize(board_size + 2, board_size + 2)
# Time line (Cost cpu time)
self.draw_time_win()
self.map_win.noutrefresh()
else:
self.map_win.erase()
self.map_win.noutrefresh()
else:
self.map_win.erase()
self.map_win.resize(board_size + 2, board_size + 2)
# map doesn't exist
self.map_win = curses.newwin(board_size + 2, board_size + 2, self.MAP_Y, self.MAP_X)
self.map_pan = curses.panel.new_panel(self.map_win)
self.map_win.box()
if not self.time_win:
# Time line (Cost cpu time)
self.draw_time_win()
curses.panel.update_panels()
self.map_win.box()
# highlight choosen path
for cell in path:
self.map_win.addch(cell[0]+1, cell[1] + 1, curses.ACS_BULLET, curses.color_pair(3) + curses.A_BOLD)
# Draw map content
y = 0
for line in board_map:
@ -306,7 +319,19 @@ class tui:
elif char == "T":
attr = curses.A_BOLD + curses.color_pair(5)
elif char == "H":
# default bot color is BLUE
attr = curses.A_BOLD + curses.color_pair(6)
for hero in heroes:
# Select color for bot (cost cpu time)
if hero.pos == (y, x):
if hero.bot_id == 1:
attr = curses.A_BOLD + curses.color_pair(6)
elif hero.bot_id == 2:
attr = curses.A_BOLD + curses.color_pair(8)
elif hero.bot_id == 3:
attr = curses.A_BOLD + curses.color_pair(9)
elif hero.bot_id == 4:
attr = curses.A_BOLD + curses.color_pair(10)
elif char == "@":
attr = curses.A_BOLD + curses.color_pair(2)
elif char == "X":
@ -331,8 +356,15 @@ class tui:
for i in range(1, 21, 2):
# Clear player tab
self.players_win.hline(i, x, " ", 17)
self.players_win.addstr(1, x, str(hero.name[0:17]), curses.A_BOLD)
if hero.bot_id == 1:
attr = curses.A_BOLD + curses.color_pair(6)
elif hero.bot_id == 2:
attr = curses.A_BOLD + curses.color_pair(8)
elif hero.bot_id == 3:
attr = curses.A_BOLD + curses.color_pair(9)
elif hero.bot_id == 4:
attr = curses.A_BOLD + curses.color_pair(10)
self.players_win.addstr(1, x, str(hero.name[0:17]), attr)
self.players_win.addstr(3, x, str(hero.user_id))
self.players_win.addstr(5, x, str(hero.bot_id))
self.players_win.addstr(7, x, str(hero.elo))
@ -494,8 +526,7 @@ class tui:
"""Cut string to parts with appropriate length"""
self.log_entries.append(str(data)[i:i+self.LOG_W - 2])
self.purge_log()
if not self.paused:
self.display_log()
self.display_log()
def purge_log(self):
"""Purge log of oldest entries"""
@ -511,7 +542,6 @@ class tui:
for entry in self.log_entries:
attr = 0
regexp = re.compile('Error')
if regexp.search(entry) is not None:
attr = curses.color_pair(3) + curses.A_BOLD
self.log_win.hline(i+1, 1, " ", self.LOG_W - 2)
@ -685,7 +715,7 @@ class tui:
self.draw_banner()
self.menu_win.addstr(10, offset + 19, "ARENA MODE", curses.A_BOLD)
self.menu_win.addstr(13, offset + 8, "Number of games to play:", curses.A_BOLD)
text_box = curses.textpad.rectangle(self.menu_win, 12, offset + 33, 14, offset + 42)
curses.textpad.rectangle(self.menu_win, 12, offset + 33, 14, offset + 42)
self.input_win = self.menu_win.subwin(1, 8, 13, offset + 34)
self.input_win.bkgd(curses.color_pair(4) + curses.A_REVERSE)
input_pan = curses.panel.new_panel(self.input_win)
@ -707,7 +737,7 @@ class tui:
self.draw_banner()
self.menu_win.addstr(10, offset + 17, "TRAINING MODE", curses.A_BOLD)
self.menu_win.addstr(13, offset + 8, "Number of turns:", curses.A_BOLD)
text_box = curses.textpad.rectangle(self.menu_win, 12, offset + 25, 14, offset + 34)
curses.textpad.rectangle(self.menu_win, 12, offset + 25, 14, offset + 34)
self.input_win = self.menu_win.subwin(1, 8, 13, offset + 26)
self.input_win.bkgd(curses.color_pair(4) + curses.A_REVERSE)
input_pan = curses.panel.new_panel(self.input_win)
@ -733,7 +763,7 @@ class tui:
offset_2 = 17
self.menu_win.addstr(10, offset + offset_2, game_mode.upper()+" MODE", curses.A_BOLD)
self.menu_win.addstr(13, offset + 6, "Server URL:", curses.A_BOLD)
text_box = curses.textpad.rectangle(self.menu_win, 12, offset + 18, 14, offset + 48)
curses.textpad.rectangle(self.menu_win, 12, offset + 18, 14, offset + 48)
self.input_win = self.menu_win.subwin(1, 29, 13, offset + 19)
self.input_win.bkgd(curses.color_pair(4) + curses.A_REVERSE)
self.input_win.addstr(0, 0, server_url)
@ -760,7 +790,7 @@ class tui:
offset_2 = 17
self.menu_win.addstr(10, offset + offset_2, game_mode.upper()+" MODE", curses.A_BOLD)
self.menu_win.addstr(13, offset + 6, "Player key:", curses.A_BOLD)
text_box = curses.textpad.rectangle(self.menu_win, 12, offset + 18, 14, offset + 48)
curses.textpad.rectangle(self.menu_win, 12, offset + 18, 14, offset + 48)
self.input_win = self.menu_win.subwin(1, 29, 13, offset + 19)
self.input_win.bkgd(curses.color_pair(4) + curses.A_REVERSE)
input_pan = curses.panel.new_panel(self.input_win)
@ -781,7 +811,7 @@ class tui:
offset = screen_x / 2 - 25
self.draw_banner()
self.menu_win.addstr(13, offset - 6, "File URL:", curses.A_BOLD)
text_box = curses.textpad.rectangle(self.menu_win, 12, offset + 5, 14, offset + 55)
curses.textpad.rectangle(self.menu_win, 12, offset + 5, 14, offset + 55)
self.input_win = self.menu_win.subwin(1, 49, 13, offset + 6)
self.input_win.bkgd(curses.color_pair(4) + curses.A_REVERSE)
self.input_win.addstr(0, 0, file_url)
@ -803,7 +833,7 @@ class tui:
offset = screen_x / 2 - 25
self.draw_banner()
self.menu_win.addstr(13, offset - 6, "File path:", curses.A_BOLD)
text_box = curses.textpad.rectangle(self.menu_win, 12, offset + 5, 14, offset + 55)
curses.textpad.rectangle(self.menu_win, 12, offset + 5, 14, offset + 55)
self.input_win = self.menu_win.subwin(1, 49, 13, offset + 6)
self.input_win.bkgd(curses.color_pair(4) + curses.A_REVERSE)
self.input_win.addstr(0, 0, file_path)
@ -885,10 +915,10 @@ class tui:
def ask_quit(self):
"""What don't you understand in 'press q to quit' ? ;-)"""
keys = ["q", "s", "r"]
keys = ["m", "s", "r"]
self.help_win.hline(0, 1, " ", 98)
self.help_win.addstr(0, 1, "Q", curses.A_BOLD + curses.A_STANDOUT)
self.help_win.addstr(0, 2, "uit")
self.help_win.addstr(0, 1, "M", curses.A_BOLD + curses.A_STANDOUT)
self.help_win.addstr(0, 2, "enu")
self.help_win.addstr(0, 8, "S", curses.A_BOLD + curses.A_STANDOUT)
self.help_win.addstr(0, 9, "ave")
self.help_win.addstr(0, 15, "R", curses.A_BOLD + curses.A_STANDOUT)


Loading…
Cancel
Save