Опрос клавиатуры при помощи curses несколько запутан. Во первых есть способ получить одиночное нажатие кнопки при помощи:
res=stdscr.getkey()
В этом случае в res попадёт непосредственно сам символ в виде строки.
Второй способ — использование :
res=stdscr.getch()
В этом случае в res попадет число вида int — код нажатой кнопки. Однако оба способа не работают, когда нужно получить данные о нажатой специальной клавиши, типа стрелок, F1..F12 и т.д. Если необходимо их получить, то нужно сначала включить данную возможность:
stdscr.keypad(True)
Зачем так сделано, загадка. Почему нельзя получить эти данные сразу?
Так-же есть способ отключить ожидание нажатия кнопки, при помощи:
curses.cbreak()
Итог: задача «Опрос клавиатуры при помощи curses» выполнена. Можете почитать и другие заметки посвященные Python. Официальная документация на curses здесь
Итак, продолжаем продолжаем писать квест в консоли на языке Python. Первая часть описана здесь. В ней мы реализовали автоматическую загрузку и сохранение состояния прохождения квеста. Сейчас же займемся (начнем по крайне мере) отрисовкой локации, и реакцией на нажатые кнопки. В локации предусмотрим возможность отображения картинки из ASCII. Например json стартовой локации может выглядеть примерно так:
Создадим класс TLocation, при инициалиизации будем передавать в него инициализированный класс player. В переменной класса data — будем хранить загруженную локацию.
class TLocation:
data = {}
player={}
stdscr=curses.initscr()
scr_size=stdscr.getmaxyx()
def __init__(self,player):
self.player=player;
def load_location(self, location):
f = open("locations/" + str(location) + ".json", mode='r', encoding='utf-8')
self.data = json.load(f)
f.close()
Далее нарисуем верхнее меню, где сообщаем игроку, на какие локации он может перемещаться, и что он держит в руках:
def top_menu(self):
loc="Идти: "
if "left" in self.data["available_locations"]:
loc=loc+"влево(4) "
if "right" in self.data["available_locations"]:
loc=loc+"вправо(6) "
if "forward" in self.data["available_locations"]:
loc=loc+"вперед(8) "
if "back" in self.data["available_locations"]:
loc=loc+"назад(2) "
curses.start_color()
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
self.stdscr.addstr(1, 1, loc,curses.color_pair(1))
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
self.stdscr.addstr(1, 1, loc,curses.color_pair(1))
# что в руках
hands = "В руках: "
self.stdscr.addstr(2, 1, "")
for object in self.player.data["in_hands"]:
hands=hands+"["+object+"]"
if len(self.player.data["in_hands"])==0:
hands = hands+"ничего нет"
self.stdscr.addstr(2, 1, hands, curses.color_pair(1))
Ну и собственно основной код отрисовки локации, включающий бесконечный цикл ожидания нажатий клавиатуры. Предусматриваем переход на другую локацию, выход из игры и сохранение игры.
def location_view(self, location):
self.load_location(location)
self.stdscr.clear()
self.stdscr.border()
self.top_menu() # рисуем верхнее меню
# название локации
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
x = int((self.scr_size[1] - len(self.data["title"])) / 2)
self.stdscr.addstr(2, x, self.data["title"],curses.color_pair(2))
# рисуем рисунок ежели он есть
y=3
if "ascii_art" in self.data:
mass_art=self.data["ascii_art"].split("\n")
i=0
while i<len(mass_art):
x=int((self.scr_size[1]-len(mass_art[i]))/2)
self.stdscr.addstr(y+i, x, mass_art[i])
i+=1
y = y + len(mass_art);
# Выводим описательную часть
curses.init_pair(3, curses.COLOR_CYAN, curses.COLOR_BLACK)
self.stdscr.addstr(y, 1, self.data["description"],curses.color_pair(3))
# Выводим нижнее меню
curses.init_pair(4, curses.COLOR_RED, curses.COLOR_BLACK)
self.stdscr.addstr(y + 1, 1, "Осмотреться вокруг [v] Применить что в руках [h]", curses.color_pair(4))
self.stdscr.addstr(y + 2, 1, "Выйти из квеста [q] Сохранить состояние [r]", curses.color_pair(4))
self.stdscr.refresh()
while True:
key=self.stdscr.getch()
print(key)
# реализация перехода с локации на локацию
if key==52 and "left" in self.data["available_locations"]:
self.player.data["location"]=self.data["available_locations"]["left"]
self.location_view(self.player.data["location"])
if key==54 and "right" in self.data["available_locations"]:
self.player.data["location"]=self.data["available_locations"]["right"]
self.location_view(self.player.data["location"])
if key==56 and "forward" in self.data["available_locations"]:
self.player.data["location"]=self.data["available_locations"]["forward"]
self.location_view(self.player.data["location"])
if key==50 and "back" in self.data["available_locations"]:
self.player.data["location"]=self.data["available_locations"]["back"]
self.location_view(self.player.data["location"])
if key == 114:
self.player.save()
#self.message("Внимание!","Состояние прохождения завершено. \nФайл находится в папке /saves")
if key==113:
curses.reset_shell_mode()
curses.endwin()
exit(0)
print(self.data)
В результате картинка (квест в консоли) на мониторе выглядит уже чуть симпатичнее:
Дело было вечером, делать было нечего (с). Ну не то чтобы совсем нечего, но выдалась свободное немножко время, поэтому для того чтобы не забыть (да уж чего там, и вспомнить уже) окончательно Python, решил сделать маленький движёк для текстовых квестов с выполняющихся в консоли (квест в консоли).
Сначала определимся что где и как:
Локации будем описывать в формате json
Локации будем складывать в папку locations. Имена файлов — номер локации.
В ходе квеста можно «сохраняться», чтобы была возможность продолжить квест
Сохранения будем хранить в папке saves
Все классы храним в папке classes
В результате у меня получилась такая структура папок и файлов:
Первым делом нарисую минимальный json стартовой локации:
Т.е. начинаем на стартовой локации (0), доступны переходы в локации 1,2,3 и 4.
Далее реализуем класс игрока, с реализацией функционала сохранения и стадии прохождения квеста:
from datetime import datetime as dt
import json
import os
class TPlayer:
data = {}
def __init__(self, name):
self.data["name"] = name # Имя пользователя
self.data["location"] = 0 # текущая локация
def load(self, filename):
"""
Загрузить состояние квеста из файла
:param filename: имя файла из папки saves
"""
f = open("saves/" + filename, mode='r', encoding='utf-8')
self.data = json.load(f)
f.close()
def list_saves(self):
"""
Показать доступные сохранения
"""
files = os.listdir("saves")
print(files)
def save(self):
"""
Сохранить текущее состояние пользователя
"""
time = dt.now()
filename = time.strftime("%d_%m_%Y_%H_%M") + ".save"
print(filename)
f = open("saves/" + filename, mode='w', encoding='utf-8')
json.dump(self.data, f)
f.close()
В главном файле (main.py), реализуем проверку аргументов командной строки и переход к началу квеста:
#!/usr/bin/env python3
# encoding: utf-8
import classes.player as tplayer
import classes.location as tlocation
import sys
player = tplayer.TPlayer("Васян")
location = tlocation.TLocation()
def start_location():
print(player.data["name"])
location.location_view(player.data["location"])
if __name__ == '__main__':
for param in sys.argv:
if param == "--load":
if len(sys.argv) == 2:
print("Ошибка: нет имени файла")
exit(0)
player.load(sys.argv[2])
start_location()
exit(0)
if param == "--new":
exit(0)
if param == "--list":
player.list_saves()
exit(0)
if len(sys.argv) == 1:
print("Для запуска квеста необходимо использовать следующие параметры:")
print(" --load - загрузить сохранение и начать квест")
print(" --list - получить список сохранений")
print(" --new <имя участника> - начать квест заново")
exit()
Разработка «квест в консоли» может быть действительно просто.. Вы можете посмотреть и другие мои статьи посвященные разработке на Python
Неожиданно задался вопросом как проверить дату истечения сертификата сайта (дата истечения сертификата сайта), если нет возможности воспользоваться для этого онлайн-сервисами, а еще коварный AVP Kaspersky на ПК в браузере подменяет сертификат на свой. В самих файлах *.key *.crt визуально даты не наблюдаются.. Остаётся несколько вариантов: посмотреть по дате создания файла, и прибавить год ;). Ну так себе вариант. Второй, правильный — воспользоваться утилитой командной строки openssl. Примерно так:
На днях на одном из сайтов, столкнулся с интересной ошибкой, которая выводится в консоли браузера Firefox:
Will-change потребляет слишком много памяти. Лимитом бюджета является площадь поверхности документа умноженная на 3 (510230 пикселей). Вхождения will-change, превышающие бюджет, будут проигнорированы
Проведя небольшое расследование, понял что проблема кроется в коде галереи WordPress, которая выводит картинки на страницы в слишком хорошем качестве (читай в оригинальном). И когда картинок становится на странице больше чем несколько, соответственно и происходит данная оказия. Исправил, покопавшись в исходном коде. А именно заменил функцию wp_get_attachment_metadata(), которая выдает url оригинального файла, на wp_get_attachment_image_src, которая может выдавать URL уже нужного размера. При использовании стандартной медиабиблиотеки в wordpress, она при загрузке автоматически режет файл на наиболее востребованные размеры.