Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Інтерактивна література і Python

with 12 comments

Це буде доволі довгий пост про мої стосунки з таким жанром не то програмування, не то літератури, як інтерактивна література. В кінці буде, мій Hello world для IF, та й для Python також.

Спочатку спробую пояснити що це таке. Інтерактивна література, з одного боку – це книжка, де читач керує діями героя, чи кількох героїв. З іншого боку, це гра, де роль графічної підсистеми дісталась не бібліотеці OpenGL, а уяві гравця. Сцени гри виводяться чистим текстом.

Що про це каже Вікіпедія?
Interactive Fiction (дословно — интерактивная литература, IF; текстовые квесты; adventure — «адвенчуры») — жанр компьютерных игр, в котором общение с игроком осуществляется посредством текстовой информации. Развитие этого жанра, в связи с низким требованием к ресурсам, началось весьма давно, и не прекратилось даже с появлением графических игр. Существуют два вида интерфейса — интерфейс с вводом текста с клавиатуры или интерфейс в виде меню, где игрок выбирает действие из нескольких предложенных (CYOA — Choose Your Own Adventure).

Також виявляється, що інтерактивна література, має вже доволі давню історію. Перша така гра з’явилась ще в 1975, і називалась ADVENT, бо в її операційній системі шість букв – це було найдовше ім’я для файлу.

Сьогодні можна було б сказати, що інерактивна література загинула, під тиском ігор які використовують всі можливості сучасної комп’ютерної графіки, але текстові ігри, все ще завойовують маленькі відсотки сердець гравців. Ви не грали в Космічні ренджери? Якщо ні, тоді ви багато чого втратили. Це шедевр, який скоро стане класикою. І хоча це не інтерактивна література в класичному розумінні, але планетарні квести інакше не назвеш.

Я познайомився з цим жанром ще десь в десятому класі, з сайту Тяп-ляп. Правда схоже на те, що сайт вже кілька років мертвий. І мало того, мені завжди хотілось програмувати текстові ігри більше ніж грати їх. Не знаю чому це. Напевне тому, що вони зазвичай дуже складні. Автор хоче, щоб гравці витратили на розгадування головоломок більше часу, ніж він на написання гри. А це трохи підло. Англомовні ігри ще важчі, бо треба читати все, і думати над завуальованими підказками. А для цього треба мати знання англійської більші ніж у мене.

Що я справді пройшов до кінця – це Stone of Shady Sands. Класна гра, яка відбувається в світі Falloutа. Сам Fallout я не грав 🙂 Гра гарно ілюстрована.

Єдина україномовна гра – це “Стародавній замок” студії “Фатум”.Гра була теж з гарними ілюстраціями, і сюжетом, але як на мене, в одному місці трохи заскладна. Посилання не дам, бо не маю. Зате зараз спробую вставити відео, зроблене цією ж студією, щоб ви зрозуміли, яка там була глибока філософія, настрій, і рівень фантазії.

Моя гра

Мала кількість того, що мені б подобалось грати, і що я б хотів грати, завжди стимулювала мене писати свої. Стримувало те, що мови які я знаю мало пристосовані до такого, а вчити спеціалізовані мови мало кому хочеться. Але, як я писав раніше, карантин дає змогу вивчити Python. І як виявилось, Python – це якраз те що треба. Шикарні вбудовані структури даних. Щоб написати таке навіть з STL треба було б витратити в три рази більше часу і зусиль. Про читабельність коду промовчу, бо це не для мене.

Хоча цю гру, я б поки що не називав повноцінною грою. Сюжет придумувався разом з розвитком структури даних, тому не шукайте там логіку. Хоча думаю, цю гру можна пройти, бо я не старався робити занадто заплутані головоломки. Також варто зауважити, що коли на сцені висить гвинтівка, вона не обов’язково вистрелить. Може вона для краси (хоча й має можливіть стріляти).

І щоб вам було легше розібратись в грі, дам уривок з мого проходження:

bunyk@bunyk:~$ python IF.py
Як тебе звати?
Тарас
Привіт, Тарас.
Щоб отримати підказку наберіть "доп"
Щоб вийти наберіть "вих" або "кін"
Старт>доп

		Текстова гра
Щоб вийти набери "вих"(ід) або "кін"(ець).
Взагалі, можна вводити повні слова для команд, але розпізнаються вони за першими трьома буквами.
Команди можуть мати параметри, які їх уточнюють, але спочатку завжди йдуть три букви одної з наступних команд:
Щоб роздивитись навколо наберіть "див"(ись). Можна дивитись на якийсь конкретний об'єкт, наприклад "дивись літак"
Щоб поритись в своїх кишенях наберіть "киш"(ені).
Щоб іти кудись набетіть "йти туди" або "йди сюди" (тобто можна як через "т" так і через "д". Куди можна йти стає зрозуміло після того як ви роздивитесь все навколо
Щоб взяти предмет, який вам потрібно наберіть "віз"(ьми),"бра"(ти),"взя"(ти)
Щоб покласти предмет, наберіть "пок"(ласти), "вик"(инути), "зал"(ишити)
Щоб використати предмет, наберіть "зас"(тосувати)
Приємної гри
Старт>див 

Ти знаходишся на стадіоні, навколо ні душі. Ти стоїш на білій смузі, перед якою великими буквами написано "Старт". Стадіон оточений високими рядами крісел. Озирнувшись навколо, в дальньому кінці можна побачити вихід.
Також ви бачите: кроси
Можливі виходи: крісла, вихід
Старт>взяти кроси

Беру кроси
Старт>кишені

Порившись в кишенях ти знаходиш:
рюкзак - Вмістимий, зручний наплічник, що дозволяє носити багато речей
кроси - Старі, подерті кросовки Абібас
Старт>див рюкзак

Вмістимий, зручний наплічник, що дозволяє носити багато речей
Також ви бачите: ручка, блокнот, телефон
Старт>взяти ручка

Беру ручка
Старт>взяти блокнот

Беру блокнот
Старт>див блокнот 

Невеликий блокнот. В ньому записано: "Купити хліб". Ти думаєш: "А що тоді я роблю тут? І хто я взагалі такий?"
Старт>застосувати блокнот

Ви взяли в руки блокнот і ручку. Що будемо писати?
Блокнот>Поки що нічого
Старт>див блокнот

Невеликий блокнот. В ньому записано: "Купити хліб". Ти думаєш: "А що тоді я роблю тут? І хто я взагалі такий?"
 Ще запис: Поки що нічого
Старт>див       

Ти знаходишся на стадіоні, навколо ні душі. Ти стоїш на білій смузі, перед якою великими буквами написано "Старт". Стадіон оточений високими рядами крісел. Озирнувшись навколо, в дальньому кінці можна побачити вихід.
Можливі виходи: крісла, вихід
Старт>йти вихід

Ти стоїш в красивому зеленому лісі. Сонячне проміння ледь пробивається крізь листя дерев. Десь високо в гіллі щебечуть пташки. Повітря свіже і чисте. Тільки куди йти далі? Всюди, як не кинь оком непролазні хащі.
Можливі виходи: стадіон, вперед
Ліс>йти вперед

Ожина - не дуже приємна рослина. Особливо, коли тобі треба пройти, а навколо вона. І це дуже неприємно, коли вона торкається твоєї шкіри. Тобі вже не до співу пташок
Можливі виходи: вперед, назад
Хащі>йти вперед

Ліщина - набагато приємніша рослина. Вона не тільки не колеться, але на ній ростуть смачні лісові горіхи. Правда йти крізь її зарості все одно важко.
Також ви бачите: горіхи
Можливі виходи: вперед, назад
Ліщина>взяти горіхи

Беру горіхи
Ліщина>застосувати горіхи

Хрум, хрум, хрумсь. Ну в тебе і зуби. Як ти розгриз шкаралупу? 
Ліщина>

Думаю це допоможе вам зрозуміти, як шукати вихід з лісу.

Щоб зіграти в гру, вам потрібен інтерпритатор Пайтона, в більшості дистрибутивів Linux просто пишете в терміналі: python назва_файлу_куди_ви_запхали_гру.py і все працює, а в інших системах вам бажано його встановити. Повірте, це варто зробити, якщо не заради моєї гри, то заради Пайтонівського калькулятора. Знаєте скільки зернят хотів винахідник шахмат? А Python знає:

>>> 2**65-1
36893488147419103231L

Код гри:

# coding=utf-8
""" 	
		PYIF
	The interaktive fictionf Python game engine.
	Bunyk T. 2009
"""

def JoinDict(list,field):
	""" Joins selected field from list of dictionaries into string """
	#return ", ".join([l[field] for l in lst])
	if list==None or len(list)==0:
		return ""
	res=list[0][field]
	for i in range(1,len(list)):
		res+=", "+list[i][field]
	return res

def FindDict(list,field,key):
	""" Finds dictionary in list where given field value equals to given key."""
	for i in list:
		try:
			if i[field]==key:
				return i
		except:
			return None

""" Here goes the sections for functions implementing user commands.
Each function goes such format:
def FunctionName(player,loc)
where player is the variable for player, and loc is variable for locations (environment)
"""

def GameFinish(player,loc,params):
	""" Еxits from game """
	print "До побачення, %s!"%player["name"]
	exit()
def GameHelp(player,loc,params):
	""" Help on a game """
	print "\t\tТекстова гра"
	print 'Щоб вийти набери "вих"(ід) або "кін"(ець).'
	print 'Взагалі, можна вводити повні слова для команд, але розпізнаються вони за першими трьома буквами.'
	print 'Команди можуть мати параметри, які їх уточнюють, але спочатку завжди йдуть три букви одної з наступних команд:'
	print 'Щоб роздивитись навколо наберіть "див"(ись). Можна дивитись на якийсь конкретний об\'єкт, наприклад "дивись літак"'
	print 'Щоб поритись в своїх кишенях наберіть "киш"(ені).'
	print 'Щоб іти кудись набетіть "йти туди" або "йди сюди" (тобто можна як через "т" так і через "д". Куди можна йти стає зрозуміло після того як ви роздивитесь все навколо'
	print 'Щоб взяти предмет, який вам потрібно наберіть "віз"(ьми),"бра"(ти),"взя"(ти)'
	print 'Щоб покласти предмет, наберіть "пок"(ласти), "вик"(инути), "зал"(ишити)'
	print 'Щоб використати предмет, наберіть "зас"(тосувати)'
	print 'Приємної гри'

def GameOver(player,locations,params):
	""" Scenario for locations where player is killed """
	loc=locations[player['location']]
	print loc['view']
	print 'Більше ніяких дій зробити не можна. Натисніть Enter щоб вийти'
	raw_input()
	GameFinish(player,locations,params)

def EatNuts(player,locations,params):
	print "Хрум, хрум, хрумсь. Ну в тебе і зуби. Як ти розгриз шкаралупу? "
	obj=FindDict(player['items'],'name','горіхи')
	player['items'].remove(obj)

def GrowNuts(player,locations,params):
	""" If player get nuts from location, nuts grow up again """
	player['location']='Ліщина'

	list=locations['Ліщина']['items']
	if len(list)==0:
		list.append({"name":"горіхи","view":"смачні ліщинові горішки","use":EatNuts})


def NavigationInForest(player,locations,params):
	""" Player will exit from forest if he called his friend, and know advise about directions """
	if locations['flags']['called']:
		print "Ви вперто стараєтесь тримати маршрут з правильним азимутом, орієнтуючись по сонячному промінні, моху на деревах, і мурашниках. І це приносить свій результат..."
		player['location']='Дорога'
		GameOver(player,locations,params)
		return
	else:
		player['location']='Хащі'
		if 'inbushes' in locations['flags']:
			locations['flags']['inbushes']+=1
			if locations['flags']['inbushes']>3:
				print "Вам здається, що ви вже не раз проходили цим місцем."""
		else:
			locations['flags']['inbushes']=0

lastview={"it":"lives"} # what we examine last. For searching in its list of items
def View(player,locations,params):
	""" Implementate view, and examination of objects """
	global lastview
	def printloc(loc): #print something that have view property and may be list of inner items
		global lastview
		if 'items' in loc: # we need only views with list of items
			lastview=loc # that we examine last
		print loc['view']
		
		try:
			items=JoinDict(loc['items'],'name')
			if items!="":
				print "Також ви бачите: "+items
		except:
			pass
		try:
			items=JoinDict(loc['exits'],'name')
			if items!="":
				print "Можливі виходи: "+items
		except:
			pass

	if player['location'] in locations:
		loc=locations[player['location']]
	else:
		print "Location %s not found. Debug the game"
		return 
	if params=="":# General view
		printloc(loc)
	else:
		if 'items' in loc:
			obj=FindDict(loc['items'],'name',params)#search item in location
		else: obj=None
		if obj:
			printloc(obj)
			return None

		if 'items' in player:
			obj=FindDict(player['items'],'name',params) # search item in inventory
		else: obj=None
		if obj:
			printloc(obj)
			return None
		if 'items' in lastview:
			obj=FindDict(lastview['items'],'name',params)
		else: obj=None
		if obj:
			printloc(obj)
			return None

		print "Не бачу нічого схожого на "+params



def Inventory(player,locations,params):
	""" View players inventory """
	print "Порившись в кишенях ти знаходиш:"
	for i in player['items']:
		print i['name']+" - "+i['view']

def GoTo(player,locations,params):
	""" Change players location """
	if params=="":
		print "Куди йти?"
		return None

	curloc=locations[player['location']]
	loc=player['location']
	exits=[]
	if 'exits' in curloc:
		exits=curloc['exits']
	if len(exits)>0:
		way=FindDict(exits,'name',params)
		if way==None:
			print "%s, це куди?"%params
			return None
		if way['location'] in locations:
			loc=way['location']
		else:
			print "Location %s not found. Debug the game!"%way['location']
	else:
		print "Location %s without exits. Debug the game!"%player['location']

	if 'script' in locations[loc]:
		locations[loc]['script'](player,locations,loc) #location script must tranport player by himself
	else:
		player['location']=loc
	View(player,locations,"")

def GetItem(player,locations,params):
	""" Move item from current location into inventory """
	if params=="":
		print 'Що взяти?'
		return None
	global lastview
	#print lastview
	obj=None
	if 'items' in lastview:
			obj=FindDict(lastview['items'],'name',params)
	if obj:
		print "Беру %s"%params
		player['items'].append(obj)
		lastview['items'].remove(obj)

def DropItem(player,locations,params):
	""" Move item from inventory to current location """
	if params=="":
		print "Що залишити?"
		return None
	obj=FindDict(player['items'],'name',params)
	if obj:
		loc=locations[player['location']]
		if 'items' in loc:
			loc['items'].append(obj)
			player['items'].remove(obj)
	else:
		print "У вас немає предмета %s"%params

def Write(player,locations,params):
	""" Writing in notebook """
	notebook=FindDict(player['items'],'name','блокнот')
	if not notebook:
		print "Є чим писати, але нема на чому."
		return None
	pen=FindDict(player['items'],'name','ручка')
	if not pen:
		print notebook['view']
		return None
	print "Ви взяли в руки блокнот і ручку. Що будемо писати?"
	writing=raw_input('Блокнот>')
	notebook['view']+="\n Ще запис: "+writing

def UsePhone(player,locations,params):
	""" Using a phone """
	if locations['flags']['called']:
		print "Ви не знаєте кому ще подзвонити"
		return
	if player['location']=='На дубі':
		print "На дубі, якість зв'язку стала краща, що логічно. Ви набрали номер."
		print "- Алло! "
		print "- Алло, це",player['name']+". Я тут заблудився серед лісу, недалеко від якогось стадіону. Як мені вибратись?" 
		print "- Як можна заблудитись в лісі? Та ще й такому маленькому. Спробуй йти постійно в одному напрямку, обов'язково вийдеш до людей."
		locations['flags']['called']=True
	else:
		print "В телефонній книзі кілька незнайомих імен. Але відсутнє покриття мережі, тому зробити дзвінок неможливо"

def UseItem(player,locations,params):
	""" Use some item """
	if params=="":
		print "Чим ви хочете скористатись?"
		return None
	obj=FindDict(player['items'],'name',params)
	if obj:
		if 'use' in obj:
			obj['use'](player,locations,params)
		else:
			print "У вас є", params, "але ви не знеєте як цим користуватись"
			return
	else:
		print "На жаль у вас немає", params

def UnknownCommand(command,player,locations,params):
	print "Невідома команда: "+command
	print "Параметри: "+params


""" Map user console commands to aproppriate functions """
commands=	{
		"кін":GameFinish,
		"вих":GameFinish,

		"доп":GameHelp,
		'дов':GameHelp,

		'див':View,
		'киш':Inventory,

		'йди':GoTo,
		'йти':GoTo,
		
		'взя':GetItem,
		'бра':GetItem,
		'віз':GetItem,

		'вик':DropItem,
		'зал':DropItem,
		'пок':DropItem,

		'зас':UseItem
		}

""" WORLD PRESENTATION """
locations=	{
		"flags":{
			"called":False
			},
		"Старт":{
			"view":'Ти знаходишся на стадіоні, навколо ні душі. Ти стоїш на білій смузі, перед якою великими буквами написано "Старт". Стадіон оточений високими рядами крісел. Озирнувшись навколо, в дальньому кінці можна побачити вихід.', 
			"items":[{"name":'кроси',"view":'Старі, подерті кросовки Абібас'}],
			"exits":[{"name":'крісла','location':'Крісла'},{'name':'вихід','location':'Ліс'}]
			},
		"Крісла":{
			"view":'Ти проходишся між рядами пластикових сидінь. З верхнього ряду можна оглянути краєвид за межами стадіону. На твоє здивування, до самого горизонту видно тільки зелений праліс.',
			"exits":[{"name":'стадіон',"location":'Старт'},{'name':'ліс','location':'Смерть в лісі'}]
			},
		"Смерть в лісі":{
			"script":GameOver,
			"view":"Ти необачно стрибнув з високої стіни стадіону, прямо в густі хащі лісу. На твоє нещастя, там внизу була дуже тверда скеля, і в тебе немає ніяких шансів вижити. Який жаль..."
			},
		"Ліс":{
			"view":"Ти стоїш в красивому зеленому лісі. Сонячне проміння ледь пробивається крізь листя дерев. Десь високо в гіллі щебечуть пташки. Повітря свіже і чисте. Тільки куди йти далі? Всюди, як не кинь оком непролазні хащі.",
			"exits":[{"name":'стадіон','location':'Старт'},{"name":'вперед','location':'Хащі'}]
			},
		"Хащі":{
			"view":"Ожина - не дуже приємна рослина. Особливо, коли тобі треба пройти, а навколо вона. І це дуже неприємно, коли вона торкається твоєї шкіри. Тобі вже не до співу пташок",
			"exits":[{"name":"вперед","location":"Ліщина"},{"name":"назад","location":"Хащі"}],
			"script":NavigationInForest
			},
		"Ліщина":{
			"view":"Ліщина - набагато приємніша рослина. Вона не тільки не колеться, але на ній ростуть смачні лісові горіхи. Правда йти крізь її зарості все одно важко.",
			"items":[],
			"exits":[{"name":"вперед","location":"Під дубом"},{"name":"назад","location":"Хащі"}],
			"script":GrowNuts
			},
		"Під дубом":{
			"view":"Ти знайшов затишну місцину під гіллям крислатого дуба. Щоб його обхопити напевне потрібно хоч п'ятеро таких людей як ви.",
			"exits":[{"name":"вперед","location":"Хащі"},{"name":"назад","location":"Ліщина"},{"name":"на дуб","location":"На дубі"}]
			},
		"На дубі":{
			"view":"Ви вилізли на дуб, але крізь його листя нічого не видно. Чути як воно шелестить на вітрі",
			"exits":[{"name":"вниз","location":"Під дубом"}]
			},
		"Дорога":{
			"view":"Ви знайшли лісову дорогу. Тепер є надія вийти з лісу. Можна сказати, що ви виграли цю гру ",
			"script":GameOver
			}
		}

Player={"location":"Старт",
	"items":[
		{'name':'рюкзак','view':'Вмістимий, зручний наплічник, що дозволяє носити багато речей',
			'items':[
					{'name':'ручка','view':'Чорнильна китайська ручка','use':Write},
					{'name':'блокнот','view':'Невеликий блокнот. В ньому записано: "Купити хліб". Ти думаєш: "А що тоді я роблю тут? І хто я взагалі такий?"','use':Write},
					{'name':'телефон','view':'Звичайний сотовий GSM телефон.','use':UsePhone}
				]
		}
		]
	}

def main():
	print "Як тебе звати?"
	nm=raw_input()
	#nm="Гравець"
	Player["name"]=nm
	
	print "Привіт, %s."%Player["name"]
	print 'Щоб отримати підказку наберіть "доп"'
	print 'Щоб вийти наберіть "вих" або "кін"'
	
	
	while True:
		command=raw_input(Player["location"]+">")
		print ''
		params=""
		sppos=command.find(' ')
		if sppos>0:
			params=command[sppos+1:]
			if sppos>6: sppos=6 # Ukrainian string with 3 characters has 6 characters. Unicode paradox.
		else: sppos=6
		command=command[:sppos]
		if command in commands:
			commands[command](Player,locations,params)
		else:
			UnknownCommand(command,Player,locations,params)

if __name__=="__main__":
	main()
Advertisements

Written by bunyk

Листопад 5, 2009 at 13:21

Оприлюднено в Кодерство, Творчість

Tagged with ,

Відповідей: 12

Subscribe to comments with RSS.

  1. Колись ця тема мене дуже цікавила. Ще на початку 90-х купив одну з книг Дмитра Браславського… Потім неодноразово повертався до жанру.
    ЗачОт, пиши ще! 😉

    theOleg

    Листопад 6, 2009 at 18:05

  2. Чи взагалі хто небудь…

    bunyk

    Листопад 6, 2009 at 20:52

  3. Мене трохи насторожує широке використання словників водночас з повною відсутність об’єктів. Бунику, краще б ти вчився будувати адекватні об’єктні моделі і мислити в стилі ООП…

    ulidtko

    Листопад 16, 2009 at 13:31

    • Ну, я ще не дочитав до об’єктів. Точніше дочитав, але не розібрався. 🙂

      Мене трохи насторожує відсутність приватних полів. Там ж є щось подібне до методів get & set С# ?

      Якщо хто знає якісь гарні книжки – дайте посилання.

      bunyk

      Листопад 16, 2009 at 23:58

      • Пройшо багато років… Я вчу JavaScript…

        І думаю: “а яка різниця між словником і об’єктом?”.

        Здається ООП я осягну ще не скоро. 😦

        bunyk

        Серпень 11, 2011 at 22:14

    • Щось типу приватних полів є — це атрибути, назви яких починаються з одного підкреслення. C# я не знаю; методи getProperty() і setProperty() тобі доведеться писати самостійно, як і в C++.

      ulidtko

      Листопад 17, 2009 at 09:53

    • а, ну і гарна книжка — Dive into Python, шукається легко

      ulidtko

      Листопад 17, 2009 at 09:56

  4. max@ulidtko:~$ python PYIF.py
    File “PYIF.py”, line 60
    print “Хрум, хрум, хрумсь. Ну в тебе і зуби. Як ти розгриз шкаралупу? ”
    ^
    SyntaxError: invalid syntax

    😦

    ulidtko

    Листопад 16, 2009 at 13:45

    • і тут вордпрес вирізав html тег img… Ех… 😦 Як завжди, все руками…

      ulidtko

      Листопад 16, 2009 at 13:49

    • і поки я виправляв код, щоб мати змогу його запустити, дізнався як його “проходити”

      ulidtko

      Листопад 16, 2009 at 13:53

      • Так, сумна історія. Все через те, що движок падло замінив смайлики в коді. Зараз виправлю.

        bunyk

        Листопад 16, 2009 at 23:52


Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: