def play_game(): player_action = None #main loop while True: render_all() check_level_up() #terminal.layer(5) #terminal.put(10, 10, 24) #place individual tile slime: 160:, troll=162: . terminal.refresh() for object in objects: object.clear() player_action = handle_keys() if player_action == 'exit': save_game() break #let monsters take their turn if game_state == 'playing' and player_action != 'didnt-take-turn': for object in objects: if object.ai: object.ai.take_turn()
def check_level_up(): #see if the player's experience is enough to level up level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR if player.fighter.xp >= level_up_xp: #it is! level up player.level += 1 player.fighter.xp -= level_up_xp message('Your battle skills have improved! You reached level ' + str(player.level) + '!', 4294967040) choice = None while choice == None: choice = menu('Level up! Choose a stat to raise:\n', ['Constitution (+20 HP, from ' + str(player.fighter.max_hp) + ')', 'Strength (+1 attack, from ' + str(player.fighter.power) + ')', 'Agility (+1 defense, from ' + str(player.fighter.defense) + ')'], LEVEL_SCREEN_WIDTH) if choice == 0: player.fighter.base_max_hp += 20 player.fighter.hp += 20 elif choice == 1: player.fighter.base_power += 1 elif choice == 2: player.fighter.base_defense += 1 #clear level menu before refresh terminal.layer(6) terminal.clear_area(0, 0, MAP_WIDTH, MAP_HEIGHT)
def run(self): UPDATE_INTERVAL_MS = self.update_interval_ms UPDATE_PER_FRAME_LIMIT = self.update_per_frame_limit clock = 0 previous_time = perf_counter() next_update = 0 proceed = True while proceed: current_time = perf_counter() if current_time - previous_time > (UPDATE_INTERVAL_MS * UPDATE_PER_FRAME_LIMIT): clock += UPDATE_INTERVAL_MS * UPDATE_PER_FRAME_LIMIT else: clock += current_time - previous_time while clock >= next_update: time_elapsed = UPDATE_INTERVAL_MS time_current = next_update self.update(time_elapsed, time_current) next_update += UPDATE_INTERVAL_MS previous_time = perf_counter() self.render() if terminal.has_input(): key = terminal.read() if key is terminal.TK_CLOSE: proceed = False else: proceed = self.process_input(key)
def update_mouse_hovering(self): mouse_x = bl.state(bl.TK_MOUSE_X) mouse_y = bl.state(bl.TK_MOUSE_Y) if mouse_x >= self.x and \ mouse_x <= self.x2 and \ mouse_y >= self.y and \ mouse_y <= self.y2: self.mouse_hovering = True else: self.mouse_hovering = False
def initialize_blt(self): terminal.open() terminal.set( "window: size={}x{}, cellsize={}, title='Roguelike';" "font: default;" "input: filter=[keyboard+];" "".format(str(self.window_width), str(self.window_height), self.cellsize) ) terminal.clear() terminal.refresh() terminal.color("white")
def next_level(): #advance to the next level global dungeon_level terminal.clear() message('You take a moment to rest, and recover your strength.', 4288626687) player.fighter.heal(player.fighter.max_hp / 2) #heal the player by 50% dungeon_level += 1 message('After a rare moment of peace, you descend deeper into the horrid caverns.', 4294901760) make_map() #create a fresh new level! initialize_fov()
def monster_death(monster): #transform it into a nasty corpse! it doesn't block, can't be #attacked and doesn't move message(monster.name.capitalize() + ' is dead! You again ' + str(monster.fighter.xp) + 'xp.', 4294901760) monster.char = 22 monster.blocks = False monster.fighter = None monster.ai = None monster.name = 'remains of ' + monster.name monster.send_to_back() #clearing monster sprite from its layer, since corpse is not 'fighter' type terminal.layer(4) terminal.clear_area(monster.x, monster.y, 1, 1)
def get_names_under_mouse(): #global mouse #mouse = terminal.read() #if mouse == terminal.TK_MOUSE_MOVE: # print terminal.state #return a string with the names of all objects under the mouse (x, y) = (terminal.state(terminal.TK_MOUSE_X), terminal.state(terminal.TK_MOUSE_Y)) #!! #create a list with the names of all objects at the mouse's coordinates and in FOV names = [obj.name for obj in objects if obj.x == x and obj.y == y and libtcod.map_is_in_fov(fov_map, obj.x, obj.y)] names = ', '.join(names) #join the names, separated by commas return names.capitalize()
def load_game(): #open the previously saved shelve and load the game data global map, objects, player, stairs, inventory, game_msgs, game_state, dungeon_level file = shelve.open('savegame', 'r') map = file['map'] objects = file['objects'] player = objects[file['player_index']] #get index of player in objects list and access it stairs = objects[file['stairs_index']] #same for the stairs inventory = file['inventory'] game_msgs = file['game_msgs'] game_state = file['game_state'] dungeon_level = file['dungeon_level'] file.close() terminal.clear() initialize_fov()
def color_map(color_list, keylist): # TODO: List shoudl be tuple ( str , inex ) total_len = keylist[-1] current_key = 0 color_map = [] for i, key in enumerate(keylist): color_map.append(bltColor(color_list[i])) try: interp_num = keylist[current_key+1] - keylist[current_key] - 1 bias_inc = 1.0 / (interp_num + 2) print "Bias_iv: {0}".format(bias_inc) bias = bias_inc for i in xrange(interp_num): colorA = bltColor(color_list[current_key]).getRGB() colorB = bltColor(color_list[current_key+1]).getRGB() a = max(min(int(colorA[3] + ((colorB[3] - colorA[3]) * bias)), 255), 0) r = max(min(int(colorA[0] + ((colorB[0] - colorA[0]) * bias)), 255), 0) g = max(min(int(colorA[1] + ((colorB[1] - colorA[1]) * bias)), 255), 0) b = max(min(int(colorA[2] + ((colorB[2] - colorA[2]) * bias)), 255), 0) color_map.append(bltColor(str(terminal.color_from_argb(a, r, g, b)))) #print "Bias_iv: {1} -> {0}".format(bias_inc, i) bias += bias_inc current_key+=1 print "InterOp: {0}".format(interp_num) index += interp_num except: pass print color_map return color_map
def draw(self): #set the color and then draw the character that represents this object at its position if (libtcod.map_is_in_fov(fov_map, self.x, self.y) or (self.always_visible and map[self.x][self.y].explored)): #set color and then draw character at location if self.fighter: terminal.layer(4) else: terminal.layer(3) terminal.color("#FFFFFF") terminal.put(self.x, self.y, self.char)
def process_input(self, key): if key == terminal.TK_Q and terminal.check(terminal.TK_SHIFT): return False elif key == terminal.TK_ESCAPE: return False if self.state == Game_States.MAIN_MENU: self.menu_input(key) elif self.state == Game_States.IN_GAME: self.in_game_input(key) return True
def __mul__(self, color2): if isinstance(color2, bltColor): r1, g1, b1, a1 = self.getRGB() r2, g2, b2, a2 = color2.getRGB() return bltColor(str(terminal.color_from_argb( a1, max(min(int(r1 * r2) // 255, 255), 0), max(min(int(g1 * g2) // 255, 255), 0), max(min(int(b1 * b2) // 255, 255), 0), ))) else: r1, g1, b1, a1 = self.getRGB() r2, g2, b2, a2 = color2, color2, color2, 1.0 return bltColor(str(terminal.color_from_argb( a1, max(min(int(r1 * r2), 255), 0), max(min(int(g1 * g2), 255), 0), max(min(int(b1 * b2), 255), 0), )))
def __sub__(self, color2): r1, g1, b1, a1 = self.getRGB() r2, g2, b2, a2 = color2.getRGB() return bltColor(str(terminal.color_from_argb( a1, max(r1 - r2, 0), max(g1 - g2, 0), max(b1 - b2, 0), )))
def __add__(self, color2): r1, g1, b1, a1 = self.getRGB() r2, g2, b2, a2 = color2.getRGB() return bltColor(str(terminal.color_from_argb( a1, min(r1 + r2, 255), min(g1 + g2, 255), min(b1 + b2, 255), )))
def blend(self, color2, bias=0.5, alpha=255 ): """Returns bltColor halfway between this color and color2""" colorA = self.getRGB() colorB = color2.getRGB() a = max(min(int(colorA[3] + ((colorB[3] - colorA[3]) * bias)), 255), 0) r = max(min(int(colorA[0] + ((colorB[0] - colorA[0]) * bias)), 255), 0) g = max(min(int(colorA[1] + ((colorB[1] - colorA[1]) * bias)), 255), 0) b = max(min(int(colorA[2] + ((colorB[2] - colorA[2]) * bias)), 255), 0) return bltColor(str(terminal.color_from_argb(a, r, g, b)))
def target_tile(max_range=None): #return the position of a tile left-clicked in player's FOV (optionally in a range), or (None,None) if right-clicked. global key, mouse while True: #render the screen. this erases the inventory and shows the names of objects under the mouse. render_all() terminal.refresh() key = terminal.read() #temporary halt of operation, wait for keypress/click #render_all() (x, y) = (terminal.state(terminal.TK_MOUSE_X), terminal.state(terminal.TK_MOUSE_Y)) #if mouse.rbutton_pressed or key.vk == libtcod.KEY_ESCAPE: if key == terminal.TK_ESCAPE or key == terminal.TK_MOUSE_RIGHT: return (None, None) #cancel if the player right-clicked or pressed Escape #accept the target if the player clicked in FOV, and in case a range is specified, if it's in that range #if (mouse.lbutton_pressed and libtcod.map_is_in_fov(fov_map, x, y) and if (key == terminal.TK_MOUSE_LEFT and libtcod.map_is_in_fov(fov_map, x, y) and (max_range is None or player.distance(x, y) <= max_range)): return (x, y)
def clear(self): #erase the character that represents this object if self.fighter: terminal.layer(4) else: terminal.layer(3) terminal.put(self.x, self.y, ' ')
def in_game_input(self, key): key_released = (terminal.TK_KEY_RELEASED, 0)[terminal.check(terminal.TK_SHIFT)] if key == terminal.TK_L | key_released: self.move_pc(1, 0) elif key == terminal.TK_N | key_released: self.move_pc(1, 1) elif key == terminal.TK_J | key_released: self.move_pc(0, 1) elif key == terminal.TK_B | key_released: self.move_pc(-1, 1) elif key == terminal.TK_H | key_released: self.move_pc(-1, 0) elif key == terminal.TK_Y | key_released: self.move_pc(-1, -1) elif key == terminal.TK_K | key_released: self.move_pc(0, -1) elif key == terminal.TK_U | key_released: self.move_pc(1, -1)
def debug(): terminal.put(10, 10, 133) # terminal.print_(11, 10, ': dark wall') # dark wall terminal.put(10, 11, 130) # terminal.print_(11, 11, ': light wall') # light wall terminal.put(10, 12, 136) # terminal.print_(11, 12, ': dark ground') # dark ground terminal.put(10, 13, 137) # terminal.print_(11, 13, ': light ground') # light ground #terminal.layer(2) terminal.put(10, 10, 389)
def main_menu(): while True: terminal.clear() for y in range(50): for x in range(80): terminal.color(1678238975 - x) terminal.put(x, y, 20) terminal.refresh() #show the game's title and some credits terminal.layer(6) terminal.color(4294967103) terminal.print_(SCREEN_WIDTH/2 - 10, SCREEN_HEIGHT/2-4, 'CAVES OF THE SNAILMEN') terminal.print_(SCREEN_WIDTH/2, SCREEN_HEIGHT-2, 'By Tommy Z') #show options and wait for player's choice choice = menu('', ['Play a new game', 'Continue last game', 'Save & Quit'], 24) if choice == 0: #new game terminal.clear() new_game() play_game() if choice == 1: #load last game try: load_game() except: msgbox('\n No saved game to load.\n', 24) continue play_game() elif choice == 2: #quit terminal.close() break
def trans(self, alpha_value): """Returns a color with the alpha_value""" r, g, b, a= self.getRGB() #print alpha_value, r, g, b alpha_value = max(min(alpha_value, 255), 1) return bltColor(str(terminal.color_from_argb(alpha_value, r, g, b)))
def draw(drawables): for obj in drawables: bl.put(obj.screen_x, obj.screen_y, obj.char) bl.refresh()
def handle_keys(): #global fov_recompute # Read keyboard input global key key = terminal.read() if key == terminal.TK_ESCAPE: # Close terminal return 'exit' if game_state == 'playing': #??? it was 'exit()' a = 'player moved' if key == terminal.TK_KP_2: player_move_or_attack(0, 1) return a elif key == terminal.TK_KP_8: player_move_or_attack(0, -1) return a elif key == terminal.TK_KP_6: player_move_or_attack(1, 0) return a elif key == terminal.TK_KP_4: player_move_or_attack(-1, 0) return a elif key == terminal.TK_KP_7: player_move_or_attack(-1, -1) return a elif key == terminal.TK_KP_9: player_move_or_attack(1, -1) return a elif key == terminal.TK_KP_1: player_move_or_attack(-1, 1) return a elif key == terminal.TK_KP_3: player_move_or_attack(1, 1) return a elif key == terminal.TK_KP_5: return a else: #test for other keys if key == terminal.TK_G: #pick up an item for object in objects: #look for an item in the player's tile if object.x == player.x and object.y == player.y and object.item: object.item.pick_up() break if key == terminal.TK_I: #show the inventory; if an item is selected, use it chosen_item = inventory_menu('Press the key next to an item to use it, or any other to cancel.\n') if chosen_item is not None: chosen_item.use() if key == terminal.TK_D: #show the inventory; if an item is selected, drop it chosen_item = inventory_menu('Press the key next to an item to drop it, or any other to cancel.\n') if chosen_item is not None: chosen_item.drop() if key == terminal.TK_C: #show character info level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR msgbox('Character Information\n\nLevel: ' + str(player.level) + '\nExperience: ' + str(player.fighter.xp) + ' / ' + str(level_up_xp) + '\n\nMaximum HP: ' + str(player.fighter.max_hp) + '\nAttack: ' + str(player.fighter.power) + '\nDefense: ' + str(player.fighter.defense), CHARACTER_SCREEN_WIDTH) if key == terminal.TK_SHIFT: key = terminal.read() if key == terminal.TK_PERIOD and stairs.x == player.x and stairs.y == player.y: #go down stairs, if the player is on them next_level() if key == terminal.TK_BACKSPACE: debug() return 'didnt-take-turn' return 'didnt-take-turn'
def paint(positions): bl.bkcolor('darker red') for pos in positions: bl.put(pos[0], pos[1], ' ') bl.bkcolor('black')
def main(): global WINDOW_WIDTH, WINDOW_HEIGHT, CELLSIZE blt.open_() blt.set_("window: size={}x{}, cellsize={}, title='Grid Test';" "font: default".format( str(WINDOW_WIDTH), str(WINDOW_HEIGHT), CELLSIZE)) blt.clear() blt.refresh() blt.color("white") g = Quadrant() blt.clear() for x in range(30): for y in range(30): if [x, y] in g.all_nodes: blt.print_(x * 2, y * 2, 'O') elif [x, y] in g.roads: # if x != 0 and x != 10 and y != 0 and y != 10: # blt.layer(1) # blt.print_(x * 2, y * 2, str(g.roads.index([x, y]))) # blt.layer(0) # else: blt.print_(x * 2, y * 2, '+') blt.refresh() proceed = True while proceed: key = 0 while blt.has_input(): key = blt.read() if key == blt.TK_CLOSE or key == blt.TK_Q: proceed = False blt.close()
def cleanup(self): terminal.close()
def drawchar(xy, color, alpha=1): x, y = xy terminal.color(color.trans(alpha)) #terminal.bkcolor('black') terminal.put(x, y, 'O')
def __init__(self, color): #long.__init__(self, terminal.color_from_name(color)) self.colorname = color self.color = terminal.color_from_name(color)
class AtMan(MapChild): def __init__(self, x, y, parent_map): MapChild.__init__(self, x, y, '@', parent_map) eventhandler.register(self, *ARROW_EVENTS) def receive_event(self, event): if event == bl.TK_RIGHT: new_pos = (self.x + 1, self.y) elif event == bl.TK_LEFT: new_pos = (self.x - 1, self.y) elif event == bl.TK_UP: new_pos = (self.x, self.y - 1) elif event == bl.TK_DOWN: new_pos = (self.x, self.y + 1) self.parent_map.move(self, new_pos) bl.open() bl.refresh() SCREEN_WIDTH = bl.state(bl.TK_WIDTH) SCREEN_HEIGHT = bl.state(bl.TK_HEIGHT) the_map = Map(SCREEN_WIDTH, SCREEN_HEIGHT, 3, 3, 9, 9) at = AtMan(5, 5, the_map) left_apostrophe = MapChild(0, 1, "'", the_map) right_apostrophe = MapChild(2, 1, "'", the_map) dud = MapChild(4, 4, "\u2193", the_map) the_map.draw_view() eventhandler.register(the_map, *ARROW_EVENTS + WASD_EVENTS) menu = ui.Menu(60, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1) paint(gridtools.hollow_rectangle(2, 2, 10, 10))
def ui_draw(ui_elements): for ui_element in ui_elements: bl.put(ui_element.x, ui_element.y, CHAR_CORNER_TL) bl.put(ui_element.x2, ui_element.y, CHAR_CORNER_TR) bl.put(ui_element.x, ui_element.y2, CHAR_CORNER_BL) bl.put(ui_element.x2, ui_element.y2, CHAR_CORNER_BR) for x in range(ui_element.x + 1, ui_element.x2): bl.put(x, ui_element.y, CHAR_HORIZONTAL) bl.put(x, ui_element.y2, CHAR_HORIZONTAL) for y in range(ui_element.y + 1, ui_element.y2): bl.put(ui_element.x, y, CHAR_VERTICAL) bl.put(ui_element.x2, y, CHAR_VERTICAL)
terminal.print_(11, 10, ': dark wall') # dark wall terminal.put(10, 11, 130) # terminal.print_(11, 11, ': light wall') # light wall terminal.put(10, 12, 136) # terminal.print_(11, 12, ': dark ground') # dark ground terminal.put(10, 13, 137) # terminal.print_(11, 13, ': light ground') # light ground #terminal.layer(2) terminal.put(10, 10, 389) ############################# # Initialisation # ############################# terminal.open() # !! set this \/ for ng terminal.set("window: size=80x50; window.title='Caves of the Snailmen'; font: tilesets/tiles.png, size=16x16; input: filter=[keyboard, mouse_left]") ############################# # Main Loop # ############################# main_menu() #new_game() #play_game()
def render_all(): global fov_map, color_dark_wall, color_light_wall global color_dark_ground, color_light_ground global fov_recompute, game_msgs if fov_recompute: #recompute FOV if needed (the player moved or something) fov_recompute = False libtcod.map_compute_fov(fov_map, player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO) terminal.color("#FFFFFF") #go through all tiles, and set their background color according to the FOV for y in range(MAP_HEIGHT): for x in range(MAP_WIDTH): visible = libtcod.map_is_in_fov(fov_map, x, y) wall = map[x][y].block_sight if not visible: #if it's not visible right now, the player can only see it if it's explored terminal.layer(2) if map[x][y].explored: if wall: terminal.put(x, y, tile_dark_wall) else: terminal.put(x, y, tile_dark_ground) else: #it's visible if wall: terminal.put(x, y, tile_light_wall) else: terminal.put(x, y, tile_light_ground) #since it's visible, explore it map[x][y].explored = True #draw all objects in the list, except the player. we want it to for object in objects: object.draw() #prepare to render the GUI text terminal.layer(6) terminal.clear_area(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) #print the game messages, one line at a time y = 1 for (line, color) in game_msgs: terminal.color(color) terminal.print_(MSG_X, y+PANEL_Y, line) y += 1 #show the player's hp render_bar(1, 3, BAR_WIDTH, 'HP', player.fighter.hp, player.fighter.max_hp, 4294917951, 4286513152) #show xp level_up_xp = LEVEL_UP_BASE + player.level * LEVEL_UP_FACTOR render_bar( 1, 4, BAR_WIDTH, 'XP', player.fighter.xp, level_up_xp, 4282384191, 4278222592) #print dungeon level terminal.layer(6) terminal.color(4282335231) terminal.print_(1,PANEL_Y +1,' S N A I L M A N ') terminal.color(4294967295) terminal.print_(1,PANEL_Y + 5, 'Dungeon level ' + str(dungeon_level)) #display names of objects under the mouse #still on layer 6 ***** (text layer) terminal.print_(1, PANEL_Y, get_names_under_mouse())
def receive_event(self, event): bl.printf(0, 0, ' ') bl.printf(0, 0, str(event))
for w in xrange(width): for h in xrange(height): terminal.printf(x + w, y + h, "[U+2588]") color1 = bltColor("red") color2 = bltColor("#80905025") color3 = bltColor("blue") terminal.open() terminal.set("window: size=80x25, cellsize=auto, title='Omni: menu';" "font: default;" "input: filter={keyboard}") terminal.composition(True) alphas = [16, 32, 64,80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 255, 240, 224, 208, 192, 176, 160, 144, 128, 112, 96, 80, 64, 32] _alphas = cycle(alphas) terminal.color(terminal.color_from_argb(128, 255,30,30)) drawRect(5,5,10,10) color10 = bltColor('blue')
def drawRect(x, y, width, height): for w in xrange(width): for h in xrange(height): terminal.printf(x + w, y + h, "[U+2588]")
def render_bar(x, y, total_width, name, value, maximum, bar_color, back_color): #adjust: move panel to desired location (bottom of console) y = y + PANEL_Y #set console to GUI layer terminal.layer(5) #render a bar (HP, experience, etc). first calculate the width of the bar bar_width = int(float(value) / maximum * total_width) #set total-width back bar color terminal.color(back_color) #terminal: draw a row of given length a = 0 for b in range(total_width): terminal.put(x + a, y, BAR_CHAR) a += 1 #now render the bar on top #set bar itself color terminal.color(bar_color) #libtcod.console_set_default_background(panel, bar_color) if bar_width > 0: a = 0 for b in range(bar_width): terminal.put(x + a, y, BAR_CHAR) a += 1 #finally, some centered text with the values #first clear previous text, then draw new text, centred. terminal.color("#FFFFFF") terminal.layer(6) terminal.clear_area(x, y, total_width, 1) bar_center = len(name + ': ' + str(value) + '/' + str(maximum))/2 terminal.print_(x + total_width/2 - bar_center, y, name + ': ' + str(value) + '/' + str(maximum))
def render(self): terminal.clear() self.render_viewport() self.render_UI() self.render_message_bar() terminal.refresh()
def menu(header, options, width): if len(options) > 26: raise ValueError('Cannot have a menu with more than 26 options.') #calculate total height for the header (after auto-wrap) and one line per option #header_height = libtcod.console_get_height_rect(con, 0, 0, width, SCREEN_HEIGHT, header)!!! header_height = len(textwrap.wrap(header, width)) height = len(options) + header_height #set co-ords of menu menu_x = SCREEN_WIDTH/2 - width/2 menu_y = SCREEN_HEIGHT/2 - height/2 #paint menu background terminal.layer(5) terminal.color(MENU_BACKGROUND_COLOR) #menu for y_bg in range(height): for x_bg in range(width): terminal.put(menu_x + x_bg, menu_y + y_bg, 20) #print the header, with auto-wrap terminal.layer(6) terminal.color('#FFFFFF') y = 0 for line in textwrap.wrap(header, width): terminal.print_(menu_x, menu_y + y, line) y += 1 #position of options, below header (y) y = menu_y + header_height letter_index = ord('a') #print all the options for option_text in options: text = '(' + chr(letter_index) + ') ' + option_text #libtcod.console_print_ex(window, 0, y, libtcod.BKGND_NONE, libtcod.LEFT, text) terminal.print_(menu_x, y, text) y += 1 letter_index += 1 #present the root console to the player and wait for a key-press terminal.refresh() key = terminal.read() #temporary halt of operation, wait for keypress/click #clear the menu from screen terminal.layer(5) terminal.clear_area(menu_x, menu_y, width, height) if terminal.state(terminal.TK_CHAR): #!! maybe no if statement here? #convert the ASCII code to an index; if it corresponds to an option, return it index = terminal.state(terminal.TK_CHAR) - ord('a') if index >= 0 and index < len(options): return index return None
def render_viewport(self): self.set_offset() for r_idx, row in enumerate(self.current_world): for c_idx, tile in enumerate(row): terminal.put(r_idx, c_idx, tile.glyph) terminal.put(self.pc.x, self.pc.y, self.pc.glyph)