Commits

Arne Babenhauserheide  committed 60dd717 Merge

merged changes till 0.4.8 - slow now done by textrpg

  • Participants
  • Parent commits e107da6, 385cf68
  • Branches avatarsay

Comments (0)

Files changed (9)

 4aabad3acd21beb5c2477693570472ae2d813f12 v0.4.1
 7e734b4b4f5d91c2185f7c43bbfbc6f35d219624 v0.4.1-r1
 19fb8390abadaadd423520d862135dd61fce6256 v0.4.2
+007cb6aa5a6e887b6aa4dcdc708e636c65667086 v0.4.4
+ce604e2dc621650328d6d14a5369147fb79a95d8 v0.4.6
+e6bbaeca3ee36db019680c436cb843cc942e9d2b v0.4.8

File Changelog.txt

+textrpg 0.4.8 (2008-10-24)
+
+- FIX: char.say() now uses autoscrolling, too. 
+
+textrpg 0.4.6 (2008-10-23)
+
+- Improved shell output by using sys.stdout.write() instead of print (thanks to AKF).
+- German tutorial: Character creation chapter added.
+- Added scrolling text (char by char). 
+
+- FIX: textrpg example code now uses story() instead of the low level diag() function to output text. 
+
 textrpg 0.4.4 (2008-09-28)
 
 - Added docstrings to the basic textrpg functions (they were missing after the last large refactoring). 

File branching_story.py

 
 The first chill of the night 
 touches your skin, 
-and your summercoat
+and your summercoat, 
 thin as it is, 
 protects you just feebly. 
 
 after this day of dancing
 with the sister of the sun, 
 a child of the faery kin. 
+
+So you start your journey
+back through the woods, 
+to the safe mundanity of your car. 
 """)
 
 wood = [
 
 'the angry boar': """
 As you enter a darker region of the woods, you hear the rustling of leaves behind you. 
+
 Minutes later, you still don't quite know, how you escaped, 
 but the boar doesn't follow you anymore. 
+""", 
+'the rabbit hill': """
+On a clearing in the woods a small hill rises, fettered with small rabbit 
+droppings and penetrated by so many holes that it seems like the whole hill 
+would have to be hollow to support all these rabbit homes. 
+A lone rabbit looks out of one of the holes and gazes strangely at you, as if 
+you were intruding on a place which isn't for humans to see. 
 """
 }
 # TODO: Write text for all locations. 

File rpg_lib/QtTextRPG.py

+#!/usr/bin/env python
+# encoding: utf-8
+
+from __future__ import division
+import sys
+from math import *
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+class Form(QDialog):
+ def __init__(self, parent=None):
+  super(Form, self).__init__(parent)
+  self.browser = QTextBrowser()
+  self.lineedit = QLineEdit("Type an expression and press Enter")
+  self.lineedit.selectAll()
+  layout = QVBoxLayout()
+  layout.addWidget(self.browser)
+  layout.addWidget(self.lineedit)
+  self.setLayout(layout)
+  self.lineedit.setFocus()
+  self.connect(self.lineedit, SIGNAL("returnPressed()"), self.updateUi)
+  self.setWindowTitle("Calculate")
+
+ def updateUi(self):
+  try:
+   text = unicode(self.lineedit.text())
+   self.browser.append(str(text) + " = <b>" + str(eval(text)) + "</b>")
+  except:
+   self.browser.append("<font color=red>" + str(text) + " is invalid!</font>"
+)
+
+app = QApplication(sys.argv)
+form = Form()
+form.show()
+app.exec_()
+
+# Thread: Is it possible to pass the script file to the program to get it into a seperate thread? 
+# Instead of threading: Seperate processes, communicating via tcp/sockets -> Networked games possible. 

File rpg_lib/anyrpg.py

     - char.compete(other, skill_name) -> See who wins and by how much. 
 
 Ideas: 
-    - Lazy loading modules, to be able to print stuff at once without having to print before the imports.
+    - Lazy loading modules, to be able to print stuff at once without having to print before the imports. 
     - Add getting experience for groups and show the chars together (only one experience header instead of one per char). 
+    - Being able to press "pause" when using autoscrolling output. 
 
 
 Basic design principles for the scripting language: 
 
 __version__   = '0.1' 
 
+import sys
 from textrpg_1d6 import Char as ews_char
 from time import sleep # for autoscrolling
 
 class UserInteraction(object): 
     """Basic user interaction: Showing and asking things."""
     def __init__(self, *args, **kwds): 
+	self.line_length = 80 #: The maximum length of story lines. 
+	self.line_wait = 0.4
+	self.char_wait = 0.05
         super(UserInteraction, self).__init__(*args, **kwds)
+	
         
     def ask(self, question=None, *args, **kwds): 
         """Ask a question.
         
-        Note: Not console based implementations will want to overwrite this. """
+        Note: Not console based implementations will want to overwrite this. 
+	
+	Plans: 
+		- ask(question=None, default=0, *answers, **kwds) - usage: ask("why", "because", "it's how it is", "I don't know", default=2) -> default capitalized, rest small print. 
+	"""
         if question is not None: 
 	    sleep(0.01)
             return raw_input(_(question) + " ")
+
+    def split_diag(self, text, line_length = None, localize=True, autoscroll=False, line_beginning = "", *args, **kwds): 
+	"""Print a dialog in single lines split at word borders. 
+	
+	@param text: The text to output. 
+	@param line_length: Maximum length of a line. 
+	
+	"""
+	# Split the text by words to output at most self.line_length chars per line. 
+	if line_length is None: 
+	    line_length = self.line_length
+	text_to_print = ""
+	# Preserve leading spaces. 
+	if text: 
+	    while text[0] == " ": 
+		text_to_print += " "
+		text = text[1:]
+	# Print as many words as possible without breaking the max line width. 
+	for j in text.split(): 
+	    if len(line_beginning) + len(text_to_print) + len(j) >= line_length: 
+		self.diag(line_beginning + text_to_print, localize=localize, autoscroll=autoscroll, *args, **kwds)
+		# If j > self.line_length -> split j and use only the remaining rest as default, printing all else directly. 
+		if len(line_beginning) + len(j) > self.line_length: 
+		    while j[self.line_length - len(line_beginning):]: 
+			self.diag(line_beginning + j[:self.line_length - len(line_beginning)], localize=localize, autoscroll=autoscroll, *args, **kwds)
+			j = j[self.line_length - len(line_beginning):]
+		text_to_print = j
+	    else: 
+		# if we already have text_to_print, add j with a space. 
+		if text_to_print: 
+		    text_to_print += " " + j
+		else: 
+		    text_to_print = j
+	# Print the last line of this dialog. 
+	self.diag(line_beginning + text_to_print, localize=localize, autoscroll=autoscroll, *args, **kwds)
     
-    def diag(self, data, localize=True, autoscroll=False, *args, **kwds):
+    def diag(self, data, localize=True, autoscroll=False, line_wait = None, char_wait = None, relative_speed = 1, end_with_linebreak = True, position_in_line = 0, *args, **kwds):
+	""" Print a dialog line. 
+	
+	@param line_wait: The time to wait at linebreaks before continuing with the text. 
+	@param char_wait: The time to wait per char before printing the next char. 
+	@param relative_speed: The relative speed of this dialog compared to others. - Limitation: Speed can only be adjusted for whole lines! 
+	@param autoscroll: If diag should output char by char (True) or wait for user input for each lines (False). 
+	@param end_with_linebreak: If there should be a linebreak at the end (True), or if the next dialog should write on the same line (False). 
+	@param position_in_line: Where in the text line the cursor should be positioned when writing. 
+	"""
+	# If the method doesn't get line_wait and/or char_wait, it uses the default values for the UI. 
+	# With this we can customize the speed of the output. 
+	if line_wait is None: 
+	    line_wait = self.line_wait
+	if char_wait is None: 
+	    char_wait = self.char_wait
+
+	# Now adjust the speed by the relative speed: 
+	if relative_speed != 1: 
+	    char_wait *= relative_speed
+	    line_wait *= relative_speed
+
+	# Localize the text
         if localize: 
             text = _(data)
         else: text = data 
     
+	# And output it. 
+	# If we have no autoscrolling, we just output the text. 
         if not autoscroll: 
             raw_input(text)
+	
+	# If we have autoscrolling, we print it char by char. 
+	# Limitation: This looks really dumb, if the text is longer than one line. 
         else: 
-            print text
+	    if text: 
+		sys.stdout.write('\033[' + str(position_in_line) + 'G')
+                sys.stdout.flush()
+	    else: 
+		self.diag("...press enter to read on...") # press enter to read on at each empty line, so readers can rest. 
+                # clear the line after the user pressed enter.
+                sys.stdout.write('\033[2A\033[' + str(position_in_line) + 'G')
+		for i in range(len(_("...press enter to read on..."))): 
+		    sys.stdout.write(" ")
+                sys.stdout.flush()
+		#sleep(line_wait)
             # First sleep 1/10th second. This ensures that the text gets shown before sleeping.
-            sleep(0.1)
-            sleep((len(text) - 1)*0.1)
+            sleep(char_wait)
+	    # If we have text, wait one 10th of a second per char. 
+	    for i in text: 
+		# load the current cursor position
+		sys.stdout.write(i)
+                sys.stdout.flush()
+		sleep(char_wait)
+	
+	# If we want a linebreak at the end, add it. 
+	if end_with_linebreak: 
+	    # Move back after the last char on the last line. 
+	    sys.stdout.write("\n")
+            sys.stdout.flush()
 
 class Dialogs(object): 
     """Basic dialogs. 
         super(Char, self).__init__(template=template, *args, **kwds)
         self.battle_diff_treshold = 4 #: The treshold of the char below which no hit is scored. 
     
-    def say(self, data): 
+    def say(self, data, autoscroll=True, *args, **kwds): 
         """Say something -> Show that the char says it."""
         data = _(data)
         for i in data.split("\n"): 
-            self.diag(self.name + ': ' + i, localize=False)
+            self.split_diag(i, localize=False, autoscroll=autoscroll, line_beginning = self.name + ': ', *args, **kwds)
     
     def ask(self, data, localize=False, *args, **kwds): 
         """Say something -> Show that the char says it."""
             self.diag(self.name + ': ' + i, localize=localize, *args, **kwds)
         return super(Char, self).ask(self.name + ': ' + data.split("\n")[-1], *args, **kwds)
     
-    def act(self, data): 
+    def act(self, data, *args, **kwds): 
         """Do something -> Show that the char does it in the style "<name> walks away.".
         
         Usage: 
         """
         data = _(data)
         for i in data.split("\n"): 
-            self.diag(self.name + ' ' + i, localize=False)
+            self.diag(self.name + ' ' + i, localize=False, *args, **kwds)
         
     def compete_skill(self, other, skill_name, self_mods=[], other_mods=[]): 
         """Compete with the other char in the specified skill. 
 
 ### Functions ###
 
-def _(text):
-    '''String function to allow localizing later on. '''
-    return str(text)
+de_DE = {
+#"""...press enter to read on...""": """...zum weiterlesen Enter drücken...""",
+}
+
+def _(text, locale="en_EN", locales={"de_DE": de_DE}):
+    '''String function to allow localizing later on. 
+    
+    @return: A localized string. '''
+    # TODO: Localize cleanly - this is but a test. 
+    if text in locales["de_DE"]: 
+	return locales["de_DE"][text]
+    else: 
+	return str(text)
+
+
 
 ## Functions needed to be implemented in EVERY simple rpg scripting module (changing their effect for all places where they get used can only be done in the Story class) ###
 def ask(question, *args, **kwds): 

File rpg_lib/textrpg.py

     Start the internal test story of the textrpg. 
 
 Plans: 
-    - put all functions which need diag or ask or similar into a class, 
-      so diag and ask can be overridden by other modules (doesnt work right now). 
     - Simple to use functions in easy to read scriptfiles in the style of the ministory file. 
     - char.compete(other, skill_name) -> See who wins and by how much. 
     - a basic implementation as minimal api reference for anyrpg plugins.
-    - Show the text letter by letter, if that's possible. 
     - Add the basic scripting function "python_interpreter(startup_data)", which shows an interactive python interpreter with the startup data already entered and interpreted. 
 
 
 Ideas: 
     - Lazy loading modules, to be able to print stuff at once without having to print before the imports.
-    - Add getting experience for groups and show the chars together (only one experience header instead of one per char). 
 
 
 Basic design principles for the scripting language: 
     - In the story function, an action is a parameter of the story. 
        -> story(switch_background_image="bg_image.png")
 
-The code for the TextRPG can be found at U{http://dreehg.org/ArneBab/textrpg}
+The code for the TextRPG can be found at U{http://freehg.org/ArneBab/textrpg}
 
 """
 
 except: 
     from rpg_lib.anyrpg import __copyright__, __url__, __author__
 
-__version__   = '0.4.4' 
+__version__   = '0.4.8' 
 # __date__      = '7th March 2007' 
 
 
-print "...Loading rpg library..."
+#print "...Loading rpg library..."
 
 
-try: 
+try: # Get the modules by relative path
     # AnyRPG classes
     from anyrpg import Char
     from anyrpg import Story as any_story
     # AnyRPG function for localizing. 
     from anyrpg import _
-except: 
+except: # get the modules by absolute path
     # AnyRPG classes
     from rpg_lib.anyrpg import Char
     from rpg_lib.anyrpg import Story as any_story
     from rpg_lib.anyrpg import _
 
 
-# Changing things. I want to display "..." in front of every blank story line. 
+# Changing things. I want to tell stories in lines, broken at word borders, so I want story() to use the split diag. 
 
 class Story(any_story): 
     def __init__(self, *args, **kwds): 
         super(Story, self).__init__(*args, **kwds)
     
-    def story(self, data=None, *args, **kwds): 
+    def story(self, data=None, autoscroll=False, *args, **kwds): 
         """Tell a part of the story.
         
         Overridden to show "..." instead of blank lines (override commented out). """
         if data is not None: 
             data = _(data)
             for i in data.split("\n"): 
-                #if i.rstrip() == "": 
-                #    self.diag("...", localize=False)
-                #else: 
-                    self.diag(i, localize=False)
+		self.split_diag(i, localize=False, autoscroll=True, *args, **kwds)
         
 
 # Define helper functions. 
 story_helper = Story()
 
 ### Lines needed in EVERY simple rpg scripting module (changing their effect for all places where they get used can only be done in the Story class) ###
+
 def ask(question, *args, **kwds): 
     """Ask a question. 
     @return: The answer of the user."""
     return story_helper.ask(question, *args, **kwds)
 
 def diag(text, localize=True, autoscroll=False, *args, **kwds): 
-    """Output some text."""
+    """Output some text.
+
+    The method used by diag is also used by story() to output text."""
     return story_helper.diag(text, localize=localize, autoscroll=autoscroll, *args, **kwds)
 
 def story(text=None, *args, **kwds): 
-    """Output story text."""
+    """Output story text line by line. Calls the method behind diag() for each line of text.
+    
+    Translation can be done for the whole text. 
+    """
     return story_helper.story(text, *args, **kwds)
 
 def give_exp(char, amount, *args, **kwds): 
 ### Test story lines ###
 
 def greet(char): 
-    diag("Old man: Welcome traveller. You've come to the right place to learn about your heritage.")
-    diag("Sadly you aren' well liked around here.")
-    diag("Heges for example didn't like your father too much.")
-    diag("Oh well, that was an understatment. You should better prepare yourself to face him. I think he'd love to see your face in the mud. Here's my knife. I like fights to be fair.")
-    diag("...")
-    diag("You say you don't know who we think you are?")
-    diag("Well, at least tell me your name then, and I'll tell you a bit about your father, after you won.")
+    story("Old man: Welcome traveller. You've come to the right place to learn about your heritage.")
+    story("Sadly you aren' well liked around here.")
+    story("Heges for example didn't like your father too much.")
+    story("""Oh well, that was an understatment. You should better prepare yourself to face him. I think he'd love to see your face in the mud. Here's my knife. I like fights to be fair.
+""")
+    story("You say you don't know who we think you are?")
+    story("Well, at least tell me your name then, and I'll tell you a bit about your father, after you won.")
     char.name = raw_input("My Name: ")
-    diag("You've got a nice name, " + char.name + ". Good luck!")
+    story("You've got a nice name, " + char.name + ". Good luck!")
 
 
 if __name__ == "__main__": 
     choss.name = "Hegen"
     won = battle(char, choss)
     if won: 
-        diag(char.name + " won the fight.")
+        story(char.name + " won the fight.")
     else: 
-        diag(choss.name + " won the fight.")
+        story(choss.name + " won the fight.")
     give_exp(char, 3)
     choss.upgrade(3)
     if won: 
-        diag("Well done " + char.name + ". I knew my trust in you was well placed. Now about your father...")
+        story("Well done " + char.name + ". I knew my trust in you was well placed. Now about your father...")
     else: 
-        diag("I didn't expect you to lose to him. Well, fate is a harsh teacher. Better luck next time!")
+        story("I didn't expect you to lose to him. Well, fate is a harsh teacher. Better luck next time!")
     
             "Intended Audience :: Developers", 
             "Intended Audience :: End Users/Desktop", 
             "Environment :: Console", 
-            "Development Status :: 3 - Alpha"
+            "Development Status :: 4 - Beta"
             ],
       url='http://1w6.org/programme',
       packages = find_packages('.'), 

File simple_story.py

 peasant = Char(template=True) # A random human with random name. 
 enemy = Char() # another generic human :)
 
-avatarsay("slow on")
+#avatarsay("slow on")
 avatarsay("size 3, 45")
 story("""Welcome to the world of easy RPG scripting. 
 
 avatarsay("height 1")
 hero.act("attacks " + enemy.name)
 
-avatarsay("slow off")
+#avatarsay("slow off")
 avatarsay("audio fanfare")
 while hero.active and enemy.active: 
    hero.fight_round(enemy)
-avatarsay("slow on")
+#avatarsay("slow on")
 
 if hero.active:
    hero.act("won.")
 Seems I'll have to kill you, too.""")
 
    avatarsay("avatarimage none")
-   avatarsay("slow off")
+#   avatarsay("slow off")
    avatarsay("audio fanfare")
    won = player.battle(hero)
-   avatarsay("slow on")
+#   avatarsay("slow on")
    if not won:
        avatarsay("audio die")
        story("""And so the adventure of """ + player.name + """ended quite early.""")
+
 else:
    enemy.act("won.")
    avatarsay("avatarimage bandit")
 else:
    fight = "No"
 if fight.lower() in ["yes", "y", ""]:
-   avatarsay("slow off")
+#   avatarsay("slow off")
    avatarsay("audio fanfare")
    won = player.battle(enemy)
-   avatarsay("slow on")
+#   avatarsay("slow on")
    if not won:
       avatarsay("audio die")
       story("Sorry, you lost.")

File tutorial_de.py

 
 story("""Willkommen im Interaktiven TextRPG Tutorial!
 
-Dieser Leitfaden wird Ihnen Schritt für Schritt die Möglichkeiten des TextRPG nahebringen und es Ihnen so ermöglichen auf einfache Art eigene TextRPGs zu schreiben. 
+Dieser Leitfaden wird Ihnen Schritt für Schritt die Möglichkeiten des TextRPG nahebringen und es Ihnen so ermöglichen, auf einfache Weise eigene TextRPGs zu schreiben. 
 
-Erstmal zur Definition: Ein TextRPG ist eine interaktive Geschichte, in die ihre Nutzer eintauchen und ihren Ausgang beeinflussen können. 
+Erstmal zur Definition: Ein TextRPG ist eine interaktive Geschichte, in die ihre Leser eintauchen und so deren Ausgang beeinflussen können. 
 
 Auch wenn es mit komplexeren Skripten wohl möglich wäre, mit dem TextRPG ein MMORPG zu implementieren, ist das nicht seine grundlegende Zielsetzung. 
 
 Stattdessen definiert es eine einfache Skriptumgebung, mit der der Code einer interaktiven Geschichte sehr ähnlich aussieht wie ein Skript für ein Theaterstück, Sie gleichzeitig aber auf die gesamte Macht von Python zurückgreifen können, mit dem sowohl einfache Skripte, als auch komplexeste Programme geschrieben werden können (und das auch die NASA und Google nutzen).
 
-Obwohl diese erste Version rein textbasiert ist, sind die Skripte auf eine Art geschrieben, die eine spätere Uebertragung in graphische Oberflächen einfach macht, so dass in den Skripten nur eingefügt werden muss, welche Oberfläche sie nutzen sollen. Das gleiche gilt für sonstige Erweiterungen. 
+Obwohl diese erste Version rein textbasiert ist, sind die Skripte auf eine Art geschrieben, die eine spätere Übertragung in graphische Oberflächen einfach macht, so dass in den Skripten nur eingefügt werden muss, welche Oberfläche sie nutzen sollen. Das gleiche gilt für sonstige Erweiterungen. 
 
-Für Charakterinteraktionen gibt sie Ihnen ausserdem eine vollständige RPG Bibliothek, die das Ein Würfel System implementert, ein universelles und frei lizensiertes Rollenspielsystem. 
+Für Charakterinteraktionen gibt sie Ihnen ausserdem eine vollständige RPG Bibliothek, die das Ein Würfel System implementert, ein universelles und frei lizensiertes Rollenspielsystem (weitere Informationen zum Ein Würfel System finden Sie auf http://1w6.org). 
 
 Da das TextRPG in Python geschrieben wurde, funktioniert es direkt auf den verschiedensten Plattformen, GNU/Linux, MacOSX und Windows eingeschlossen. 
 
 
 Damit können sie dann die Funktionen des TextRPG nutzen, z.B. story(). 
 
-Als nächsten Schritt rufen sie wie bereits beschrieben die Funktion story() auf: 
+Als nächsten Schritt rufen Sie wie bereits beschrieben die Funktion story() auf (das heißt, Sie tippen einfach das folgende ein): 
 
 story('''Träume in Texten, 
 Welten in Träumen
 Wir wechseln nun in den Python Interpreter. """)
 
 call("python")
+# mit call("python -ic 'from rpg_lib.textrpg import *'") kann das TextRPG schon vorgeladen werden. 
 
 story("""= Lektion 2: Dem Nutzer Fragen stellen und Geschichten weitergeben =
 
 
 ask('''Frage? (Antwort 1, antwort 2, ...)''')
 
-ask liefert die Antwort des Benutzers zurück. In obigem Beispiel verfällt sie allerdings. Damit Sie mit ihr arbeiten können müssen Sie diese Antwort noch auffangen. Beispielsweise können Sie sie als antwort speichern: 
+ask liefert die Antwort des Benutzers zurück. 
+
+In obigem Beispiel verfällt sie allerdings. Damit Sie mit ihr arbeiten können müssen Sie diese Antwort noch auffangen. Beispielsweise können Sie sie als "antwort" speichern: 
 
 antwort = ask('''Frage? (Antwort 1, antwort 2, ...)''')
 
 In dem Fall ruft sie story() mit dem Wert '''Gerne!''' auf. 
 
 Gibt der Nutzer keine Antwort, sondern drückt einfach Enter, dann gilt das als "", also eine leere Antwort. Diesen Wert können Sie sehr einfach nutzen, um die Standardantwort zu wählen. 
-Nach Konvention wird die Standardantwort bei Fragen groß geschrieben und der Rest klein. 
+Nach Konvention wird die Standardantwort bei Fragen groß geschrieben und der Rest klein. Damit haben Nutzer mehr Orientierung. Ob Sie sich daran halten ist natürlich Ihre Sache. 
 
 Der Doppelpunkt bei "if ... :" und die Einrückung von story() zeigen in TextRPG Skripten an, dass story('''Oh Ja!''') zu dem Block der if Abfrage gehört. Wo es wieder zur ursprünglichen Einrückung zurück geht (bei elif) ist der if-block beendet. 
 
 
 mein_spiel/
     mein_spiel.py
-    lib/
+    rpg_lib/
 
 Um die Geschichte zu starten, muss der Nutzer nun einfach mein_spiel.py (doppelt) anklicken. 
 
 Im gleich startenden Python Interpreter können Sie einfach Abschnitte Ihrer eigenen Geschichte ausprobieren, bevor Sie sie in die Textdatei schreiben. Um ihn zu verlassen, benutzen sie wie üblich exit(). 
+
+Anders als in den vorigen Übungen haben wir hier bereits die die TextRPG Funktionen importiert, so dass Sie genau so schreiben können, als würden sie in ihrer vorbereiteten Textdatei schreiben. 
+
 Damit kommen wir zum Ende von Lektion 2. Viel Spaß beim experimentieren! """)
 
-call("python")
+call("python -ic 'from rpg_lib.textrpg import *")
 
 story("""Lektion 3: Charaktere erzeugen und sprechen lassen.
 
-In Arbeit. 
+Nun ist es soweit, den Geschichten eins ihrer Grundelemente zu geben: Charaktere. 
+
+Das TextRPG gibt Ihnen eine einfache Möglichkeit Charaktere zu erzeugen, die auf vielfältige Weise interagieren können. Als erste Interaktion zeige ich Ihnen in dieser Lektion die direkte Rede. 
+
+Um einen Charakter zu erzeugen, erstellen Sie ihn wie im folgenden Beispiel: 
+
+charakter = Char()
+
+Das bedeutet: "Erzeuge einen Char und nenne ihn charakter, um später etwas mit ihm machen zu können". 
+
+Um ihm mehr Persönlichkeit zu geben, sollten Sie ihm noch einen Namen geben: 
+
+charakter.name = "Rannar"
+
+Um den Charakter etwas sagen zu lassen, nutzen Sie einfach die folgende Syntax 
+
+charakter.say('''Endlich kann ich frei sprechen!''')
+
+charakter.say() unterscheidet sie nur dadurch von story(), dass es der Charakter ist, der spricht, was im TexTRPG dadurch ausgedrückt wird, dass der Name des Charakters vor jeder Zeile steht. 
+""")
+
+test_in_interpreter = ask("Wollen Sie das gleich im Interpreter testen?  (Ja, nein)")
+
+if test_in_interpreter.lower() in ["ja", "j", ""]: 
+    story("""Der Python Interpreter wird gleich gestartet. 
+
+Wie üblich können Sie ihn mit exit() verlassen, um mit dem zweiten Teil von Lektion 3 fortzufahren.""")
+    call("python -ic 'from rpg_lib.textrpg import *")
+
+story("""Mit diesen einfachen Schritten können Sie nun bereits Charaktere erzeugen und sprechen lassen, aber ich möchte mir noch den Moment Zeit nehmen, Ihnen zu erklären, was sie damit intern tun. 
+
+instanz - charakter von charakterdatei - say <-> story. 
 
 """)