Skip to content

Commit

Permalink
0.3
Browse files Browse the repository at this point in the history
- EventDispatcher added for easy event handling
- New dependency: pygame (for music)
- supported music file types: mp3, ogg, wav, mid
  • Loading branch information
simongoricar committed Jul 26, 2016
1 parent e6f5095 commit 45d4442
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 46 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -7,6 +7,7 @@ A library for creating a text-based story.
- Inventory, "crafting" (called blueprints)
- Requirements before specific actions can be done
- Walking ;)
- Event handling
- Music!

More coming in the future!
Expand Down
5 changes: 5 additions & 0 deletions changes.txt
@@ -1,3 +1,8 @@
0.3
- EventDispatcher added for easy event handling
- New dependency: pygame (for music)
- supported music file types: mp3, ogg, wav, mid

0.2.1
- StaticObjects can have music
- Additional docstrings
Expand Down
42 changes: 25 additions & 17 deletions examples/customevents.py
@@ -1,14 +1,12 @@
# coding=utf-8
"""
For more examples with explanations, take a look at the other example(s).
For simpler examples with explanations, take a look at the other example(s) or demos.
This one focuses on inserting your code into events that happen in the game.
"""

# Interpreter import
from pac import PaCInterpreter

import threading
import time
from pac import PaCInterpreter, EventDispatcher
from pac import PICKUP, COMBINE, START, ENTER, USE_ITEM, USE_OBJECT, MUSIC_CHANGE # All available events (for now)

# Instance of the interpreter
pac = PaCInterpreter()
Expand All @@ -19,6 +17,13 @@
desc="This is where I sleep every night.",
starting=True)

room2 = pac.createRoom(
name="hall",
desc="I'm in the hall of my apartment.",
starting=False)

pac.linkroom(room1, room2, True)

# Items
item1 = pac.createItem(
name="phone",
Expand All @@ -37,18 +42,21 @@

pac.setStartingMessage("Custom event handling demo. When you pick up the phone, It will print the current time.\nUsing this, you can insert your code to some extent.")

# Starts the game in a thread, effectively not blocking your code
thr = threading.Thread(target=pac.start).start()

#
# HERE GOES YOUR CODE!
# \/ \/ \/ \/ \/
# PaC-Adventure also has an event handling module (implemented in 0.3)
events = EventDispatcher()

def onstart():
print("Game has started.")

def onenter(data):
print("Went from {} to {} (First time: {}).".format(data.get("from").name, data.get("to").name, data.get("first-time")))

events.registerEvent(START, onstart)
events.registerEvent(ENTER, onenter)
# etc...

# Demonstrates some possibilities
while True:
pac.setEventDispatcher(events) # Needs to be done before start() method.

if item1.pickedup: # Waits until the phone gets picked up
print(time.time())
break
else:
time.sleep(0.1)
# Starts the game
pac.start()
9 changes: 6 additions & 3 deletions examples/demo_storyofaman.py
Expand Up @@ -6,7 +6,10 @@
Create rooms, items, static objects and link them together.
Add requirements, custom messages, blueprints, music ...
register events,
and use start() method to start the "Adventure", the rest is left to the TextInterface
For more advanced examples see customevents.py
"""

# Interpreter import
Expand Down Expand Up @@ -89,9 +92,9 @@

# Add your music!

# pac.addMusic("DemoMusic2.wav", room3) # winsound sadly supports only wav file format.
# pac.addMusic("DemoMusic2.wav", room3) # Supported file formats: mp3, ogg, wav, mid

# pac.addMusic("DemoMusic.wav", static1) # Music can be added to either Room or StaticObject objects
# pac.addMusic("DemoMusic.wav", room2) # Music can be added to either Room or StaticObject objects

#object1.addUseRequirement(object2) # Commented out, but this pretty much means that you need to have that item (item2 - charger) before you can pick it up/use it (item1 - phone)
#object1.addPickUpRequirement(object2)
Expand Down Expand Up @@ -123,5 +126,5 @@

pac.setStartingMessage("'The Story of a Man' (demo)\n---------------------------\n\nYou are in the dining room. You can smell the scent of washed dishes.\nYour family is out for this evening.")

# Starts the game
# Starts the game (blocking call)
pac.start()
133 changes: 107 additions & 26 deletions pac.py
Expand Up @@ -3,13 +3,23 @@
# This is the PaC Interpreter

# Imports
import winsound # Only for Windows!
from pygame import mixer
import threading
import time
import os

__author__ = "DefaltSimon"
__version__ = "0.2.1"
__version__ = "0.3"

# Constants
PICKUP = "pickup"
USE_ITEM = "use-item"
USE_OBJECT = "use-object"
COMBINE = "combine"
START = "start"
ENTER = "enter"
MUSIC_CHANGE = "music"


# For simple threading

Expand Down Expand Up @@ -70,34 +80,29 @@ def __init__(self, path):
self._keepalive = threading.Event()
self.isstarted = False

@threaded
def start(self, repeat=True):
"""
Starts playing the music (creates a thread).
Starts playing the music (threading is now not needed).
:param repeat: optional, defaults to True; specifies if the sound file should be repeatedly played.
:return: None
"""
if repeat:
mixer.music.load(self.path)
mixer.music.play(-1)
else:
mixer.music.load(self.path)
mixer.music.play()

while not self._keepalive.isSet():

if repeat:
if not self.isstarted:
winsound.PlaySound(self.path, winsound.SND_FILENAME | winsound.SND_ASYNC | winsound.SND_LOOP | winsound.SND_NODEFAULT)
else:
if not self.isstarted:
winsound.PlaySound(self.path, winsound.SND_FILENAME | winsound.SND_ASYNC | winsound.SND_NODEFAULT)

self.isstarted = True
time.sleep(0.1)
time.sleep(0.1)

return # Quits the thread when Event is set to True

def stop(self):
"""
Sets a flag (Event) to True so the thread quits on next iteration (max time for next iteration: 0.1 s)
:return: None
"""
self._keepalive.set()
mixer.music.fadeout(500) # Quits the thread when Event is set to True
return

# Room Object

Expand All @@ -113,7 +118,7 @@ def __init__(self, name, desc, enterdesc=None, starting=False):
self.onfirstenterdesc = enterdesc

self.isdefault = bool(starting)
self.beenhere = False
self.firstenter = False

self.items = {}
self.itemdescs = {}
Expand Down Expand Up @@ -179,8 +184,8 @@ def enter(self):
else:
statics = ""

if not self.beenhere:
self.beenhere = True
if not self.firstenter:
self.firstenter = True

if self.onfirstenterdesc:
return str(self.onfirstenterdesc + statics + "\n" + self.desc + itms)
Expand Down Expand Up @@ -560,6 +565,49 @@ def addMusic(self, music):
self.music = music


class EventDispatcher:
# todo docstrings
def __init__(self):
self.events = {
"pickup" : [],
"use-item" : [],
"use-object" : [],
"start" : [],
"combine" : [],
"enter" : [],
"music": []
}

def registerEvent(self, type, fn):
if type == PICKUP:
self.events.get(PICKUP).append(fn)

elif type == USE_ITEM:
self.events.get(USE_ITEM).append(fn)

elif type == USE_OBJECT:
self.events.get(USE_OBJECT).append(fn)

elif type == START:
self.events.get(START).append(fn)

elif type == COMBINE:
self.events.get(COMBINE).append(fn)

elif type == ENTER:
self.events.get(ENTER).append(fn)

elif type == MUSIC_CHANGE:
self.events.get(MUSIC_CHANGE).append(fn)

def dispatchEvent(self, type, data=None):
for fn in self.events.get(type):

if not data:
fn()

else:
fn(data)

# TextInterface handles player interaction

Expand All @@ -568,7 +616,7 @@ class TextInterface:
The basic Text interface that the player interacts with.
"""
def __init__(self):
pass
self.running = True

def beginAdventure(self, pac):
"""
Expand Down Expand Up @@ -840,7 +888,9 @@ def textadventure():

if str(n).lower().startswith(("yes", "yup", "ye", "sure", "y")):
print("Bye for now!")
exit(0)
self.running = False
return

elif str(n).lower().startswith(("no", "nope", "n", "not sure")):
return

Expand All @@ -849,7 +899,7 @@ def textadventure():

print(pac.startingmessage + "\n\n" + getRoomHeader(pac.startingroom.name) + "\n" + pac.startingroom.enter())

while True:
while self.running: # Defaults to True
textadventure()

# Code never reaches this point... probably (except when quitting)
Expand All @@ -861,7 +911,7 @@ class PaCInterpreter:
PaC stands for point and click (adventure) ;)
"""

def __init__(self):
def __init__(self, eventdispatcher=None):
self.rooms = {}
self.items = {}
self.statics = {}
Expand All @@ -887,14 +937,33 @@ def __init__(self):
self.defaultfailedcombine = "Can't do that..."

self.musicthread = None
self.events = eventdispatcher

def setEventDispatcher(self, eventdispatcher):
"""
Associates the instance of EventDispatcher with PacInterpreter
:param eventdispatcher: an instance of EventDispatcher (optional)
:return: None
"""
if not isinstance(eventdispatcher, EventDispatcher):
raise InvalidParameters

self.events = eventdispatcher

def start(self):
"""
Starts the adventure with you in the default room and the starting message. If you have not defined a starting room or message, MissingParameters will be raised.
:return: starting message (to be printed)
:return: None
"""
self.running = True

mixer.init() # Inits the mixer module (for Music)

if not self.events:
self.events = EventDispatcher()

self.events.dispatchEvent(START)

if not self.startingroom or not self.startingmessage:
raise MissingParameters

Expand Down Expand Up @@ -1142,6 +1211,9 @@ def combine(self, itemone, itemtwo):

self.putIntoInv(result)

# Dispatch event
self.events.dispatchEvent(COMBINE, {"item1": item1, "item2": item2, "result": result})

return result.craft()

return False
Expand Down Expand Up @@ -1236,6 +1308,7 @@ def pickUpItem(self, item):
return self.defaultfailedpickup

it = self.currentroom.pickUpItem(item)
self.events.dispatchEvent(PICKUP, {"item": item, "desc": it})

thatitem = self.items[item.name]
self.putIntoInv(thatitem)
Expand Down Expand Up @@ -1263,6 +1336,7 @@ def useItem(self, item):
return self.defaultfaileduse

desc = item.use()
self.events.dispatchEvent(USE_ITEM, {"item": item, "desc": desc})
return desc

def useStaticObject(self, obj, item=None):
Expand Down Expand Up @@ -1295,6 +1369,7 @@ def useStaticObject(self, obj, item=None):
self._startMusicThread(obj.music)
desc = obj.useWithItem(item)

self.events.dispatchEvent(USE_OBJECT, {"object": obj, "desc": desc})
return desc

def walk(self, room):
Expand Down Expand Up @@ -1332,6 +1407,9 @@ def walk(self, room):
itemr = room.hasItemRequirements(self.inv)
roomr = room.hasVisitRequirement(self.visits)

if itemr or roomr:
self.events.dispatchEvent(ENTER, {"from": self.currentroom, "to": room, "first-time": not room.firstenter})

if itemr == 1:
if roomr == 1: # Only if everything is fulfilled, return room description
desc = room.enter()
Expand Down Expand Up @@ -1401,6 +1479,7 @@ def addMusic(self, music, place):

place.addMusic(music)

@threaded
def _startMusicThread(self, music, repeat=True):
"""
Starts the music, stopping any existing threads.
Expand All @@ -1418,4 +1497,6 @@ def _startMusicThread(self, music, repeat=True):
self.musicthread = music
self.lastmusicthread = music
self.musicthread.__init__(self.musicthread.path)
self.musicthread.start(repeat)

self.events.dispatchEvent(MUSIC_CHANGE, {"music": music, "path": music.path})
self.musicthread.start(repeat)

0 comments on commit 45d4442

Please sign in to comment.