1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
|
##---------------------------------------------------------------------------##
##
## pyChing -- a Python program to cast and interpret I Ching hexagrams
##
## Copyright (C) 1999,2000 Stephen M. Gava
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be of some
## interest to somebody, but WITHOUT ANY WARRANTY; without even the
## implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## See the GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; see the file COPYING or COPYING.txt. If not,
## write to the Free Software Foundation, Inc.,
## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
## The license can also be found at the GNU/FSF website: http://www.gnu.org
##
## Stephen M. Gava
## <elguavas@users.sourceforge.net>
## http://pyching.sourgeforge.net
##
##---------------------------------------------------------------------------##
"""
engine module for pyching
classes and utility functions
"""
#python library imports
import sys, os, string, whrandom, pickle, time
#
# classes
################
#private classes - should not be directly accessed from outside this module
class Hexagram:
"""
single Hexagram data structure template, private class
should only be accessed as an attribute of an instance of the Hexagrams class (below)
this class is defined at module level to enable pickling of Hexagrams instances
"""
def __init__(self):
self.number = ''
self.name = ''
self.lineValues = [0,0,0,0,0,0]
self.infoSource = None
#public classes
class PychingAppDetails:
"""
holds information about a running instance of the pyching application, public class
"""
def __init__(self,createConfigDir=1):
self.title = 'pyChing'
self.version = '1.0.4'
self.os = sys.platform
self.osType = os.name
self.execPath = os.path.abspath(sys.path[0]) + os.sep
if os.path.expanduser('~') != '~': #platform supports unix-style home directories
self.configPath = os.path.join(os.path.expanduser('~'), '.pyching') #user specific config storage
if createConfigDir:#config dir should be created if it doesn't exist
if not os.path.exists(self.configPath):
try:
os.mkdir(self.configPath)#make the config dir if it doesn't exist yet
except IOError:
sys.stderr.write('warning - unable to create '+self.configPath+'!\n')
self.savePath = self.configPath #default save path
else: #platform does not support unix-style home directories
self.configPath = self.execPath #config for all users is stored in data directory
self.savePath = os.curdir #default save path is curent working directory
self.configFile = os.path.join(self.configPath, 'pychingrc')
self.saveFileExt = '.psv'
self.internalImageExt = '.#@~'
self.internalHtmlExt = '.~@#'
self.saveFileID = ('pyching_save_file',self.version)
self.emailAddress = 'elguavas@users.sourceforge.net'
self.webAddress = 'http://pyching.sourceforge.net'
# create an instance of the app details for use throughout this module
#
pyching = PychingAppDetails()
#
#
class Hexagrams:
"""
holds both Hexagrams for a reading, public class
"""
def __init__(self, oracleType='coin'):
"""
initialise self by setting oracle type
defaults to coin, if specified must be a valid oracle type (coin or yarrow)
"""
#public data attributes - should read but not written to from outside this module
#any attributes that need to be modified from outside this module have a 'set_xxx'
#method available below
self.question = ''#use SetQuestion (below) to set this attribute from outside this module
self.oracle = oracleType #oracle being used
self.hex1 = Hexagram() #Hexagram 1 data structure
self.hex2 = Hexagram() #Hexagram 2 data structure
self.currentLine = 0 #current line being cast in Hex1
self.currentOracleValues = [] #list of oracle values for current line
def __GetHexDetails(self, hexKey):
"""
lookup hex name and number, private method
hexKey value should be a list of the non-moving line numbers for the
Hexagram being enquired upon (Hex?.Key)
returns a list of Hexagram details in the form [number, name]
"""
if hexKey == [7,7,7,7,7,7]: return ('1', "Tch'ien")
elif hexKey == [8,8,8,8,8,8]: return ('2', "Koun")
elif hexKey == [7,8,8,8,7,8]: return ('3', "T'oun")
elif hexKey == [8,7,8,8,8,7]: return ('4', "Mong")
elif hexKey == [7,7,7,8,7,8]: return ('5', "Hsu")
elif hexKey == [8,7,8,7,7,7]: return ('6', "Song")
elif hexKey == [8,7,8,8,8,8]: return ('7', "Cheu")
elif hexKey == [8,8,8,8,7,8]: return ('8', "Pi")
elif hexKey == [7,7,7,8,7,7]: return ('9', "Siao Tch'ou")
elif hexKey == [7,7,8,7,7,7]: return ('10', "Li")
elif hexKey == [7,7,7,8,8,8]: return ('11', "T'ai")
elif hexKey == [8,8,8,7,7,7]: return ('12', "P'i")
elif hexKey == [7,8,7,7,7,7]: return ('13', "Tong Jen")
elif hexKey == [7,7,7,7,8,7]: return ('14', "Ta You")
elif hexKey == [8,8,7,8,8,8]: return ('15', "Tchien")
elif hexKey == [8,8,8,7,8,8]: return ('16', "Yu")
elif hexKey == [7,8,8,7,7,8]: return ('17', "Souei")
elif hexKey == [8,7,7,8,8,7]: return ('18', "Kou")
elif hexKey == [7,7,8,8,8,8]: return ('19', "Lin")
elif hexKey == [8,8,8,8,7,7]: return ('20', "Kouan")
elif hexKey == [7,8,8,7,8,7]: return ('21', "Che Ho")
elif hexKey == [7,8,7,8,8,7]: return ('22', "Pi")
elif hexKey == [8,8,8,8,8,7]: return ('23', "Po")
elif hexKey == [7,8,8,8,8,8]: return ('24', "Fou")
elif hexKey == [7,8,8,7,7,7]: return ('25', "Wou Wang")
elif hexKey == [7,7,7,8,8,7]: return ('26', "Ta Tch'ou")
elif hexKey == [7,8,8,8,8,7]: return ('27', "I")
elif hexKey == [8,7,7,7,7,8]: return ('28', "Ta Kouo")
elif hexKey == [8,7,8,8,7,8]: return ('29', "K'an")
elif hexKey == [7,8,7,7,8,7]: return ('30', "Li")
elif hexKey == [8,8,7,7,7,8]: return ('31', "Hsien")
elif hexKey == [8,7,7,7,8,8]: return ('32', "Hong")
elif hexKey == [8,8,7,7,7,7]: return ('33', "Toun")
elif hexKey == [7,7,7,7,8,8]: return ('34', "Ta Tch'ouang")
elif hexKey == [8,8,8,7,8,7]: return ('35', "Tchin")
elif hexKey == [7,8,7,8,8,8]: return ('36', "Ming Yi")
elif hexKey == [7,8,7,8,7,7]: return ('37', "Tchia Jen")
elif hexKey == [7,7,8,7,8,7]: return ('38', "K'ouei")
elif hexKey == [8,8,7,8,7,8]: return ('39', "Tch'ien")
elif hexKey == [8,7,8,7,8,8]: return ('40', "Tchieh")
elif hexKey == [7,7,8,8,8,7]: return ('41', "Soun")
elif hexKey == [7,8,8,8,7,7]: return ('42', "Yi")
elif hexKey == [7,7,7,7,7,8]: return ('43', "Kouai")
elif hexKey == [8,7,7,7,7,7]: return ('44', "Keou")
elif hexKey == [8,8,8,7,7,8]: return ('45', "Ts'ouei")
elif hexKey == [8,7,7,8,8,8]: return ('46', "Cheng")
elif hexKey == [8,7,8,7,7,8]: return ('47', "K'oun")
elif hexKey == [8,7,7,8,7,8]: return ('48', "Tsing")
elif hexKey == [7,8,7,7,7,8]: return ('49', "Keu")
elif hexKey == [8,7,7,7,8,7]: return ('50', "Ting")
elif hexKey == [7,8,8,7,8,8]: return ('51', "Tchen")
elif hexKey == [8,8,7,8,8,7]: return ('52', "Ken")
elif hexKey == [8,8,7,8,7,7]: return ('53', "Tchien")
elif hexKey == [7,7,8,7,8,8]: return ('54', "Kouei Mei")
elif hexKey == [7,8,7,7,8,8]: return ('55', "Fong")
elif hexKey == [8,8,7,7,8,7]: return ('56', "Lu")
elif hexKey == [8,7,7,8,7,7]: return ('57', "Hsuan")
elif hexKey == [7,7,8,7,7,8]: return ('58', "Touei")
elif hexKey == [8,7,8,8,7,7]: return ('59', "Houan")
elif hexKey == [7,7,8,8,7,8]: return ('60', "Tchieh")
elif hexKey == [7,7,8,8,7,7]: return ('61', "Tchong Fou")
elif hexKey == [8,8,7,7,8,8]: return ('62', "Siao Kouo")
elif hexKey == [7,8,7,8,7,8]: return ('63', "Tchi Tchi")
elif hexKey == [8,7,8,7,8,7]: return ('64', "Wei Tchi")
else: #raise an exception
return (0, "lookup error")
# pass
def NewLine(self):
"""
builds next line in Hex1 and completes both Hexagrams after line 6, public method
"""
if self.currentLine < 6: #build a new Hex1 line
rc = whrandom.choice #returns a random value from the specified sequence
#handle each oracle type
if self.oracle == 'coin':
self.currentOracleValues = [rc([2,3]), rc([2,3]), rc([2,3])]
#for item in self.currentOracleValues: #line value = sum of oracle values
# self.hex1.lineValues[self.currentLine] = self.hex1.lineValues[self.currentLine] + item
self.hex1.lineValues[self.currentLine] = reduce(lambda x,y: x+y, self.currentOracleValues) #line value = sum of oracle values
#elif self.oracle == 'yarrow':
# self.oracleValues = [0, 0, 0] #dummy results
# self.hex1.lineValues[CurrentLine] = 0 #dummy result
self.currentLine = self.currentLine + 1 #next line is current
if self.currentLine == 6: #Hex1 is all built
hex1Key = [0,0,0,0,0,0] #Hex1's details lookup key
i = 0 #used as a counter in the loop below
for item in self.hex1.lineValues: #populate Hex1's details lookup key
if item == 6: hex1Key[i] = 8 #revert to unmoving line number
elif item == 9: hex1Key[i] = 7 #revert to unmoving line number
else: hex1Key[i] = item #no change
i = i + 1 #increment counter
[self.hex1.number, self.hex1.name] = self.__GetHexDetails(hex1Key) #lookup Hex1 details
self.hex1.infoSource = 'pyching_inhtx_data.in'+self.hex1.number+'data()'
if self.hex1.lineValues != hex1Key: #if there are some moving lines in Hex1
i = 0 #used as a counter in the loop below
for item in self.hex1.lineValues: #populate Hex2.lineValues
if item == 6: self.hex2.lineValues[i] = 7 #move to new line number
elif item == 9: self.hex2.lineValues[i] = 8 #move to new line number
else: self.hex2.lineValues[i] = item #no change
i = i + 1 #increment counter
[self.hex2.number, self.hex2.name] = self.__GetHexDetails(self.hex2.lineValues) #lookup Hex2 details
self.hex2.infoSource = 'pyching_inhtx_data.in'+self.hex2.number+'data()'
def SetQuestion(self, questionText):
"""
used to set the Hexagrams.question attribute from outside this module, public method
"""
self.question = questionText
def __HexStorage(self, file, action):
"""
store or load a Hexagrams instance to/from disk file using the utility routine
Storage(), private method
this private method should be called from the public load and save
routines below. action should be 'save' or 'load' .
"""
if os.path.expanduser('~') != '~': #unix-style home directories
if not os.path.exists(pyching.savePath): #failsafe if user deleted ~/.pyching while program running :-)
os.mkdir(pyching.savePath)#make the save dir
try:
if action == 'save':
hexData = (pyching.saveFileID, self.question, self.oracle, self.hex1,
self.hex2, self.currentLine, self.currentOracleValues)
Storage(file, data=hexData)
elif action == 'load':
hexData = Storage(file)
except IOError: #pass the error back up the line
raise #re-raise the exception
else: #no exception, so proceed
if action == 'load':
saveFileID, self.question, self.oracle, self.hex1, self.hex2, \
self.currentLine, self.currentOracleValues = hexData
return saveFileID #to enable savefile verification and version checking
def Save(self, file):
"""
save instance data to disk file, public method
this function should be called in a
try:
except IOError:
block, to handle potential disk IO errors
"""
#fileName = time.strftime('%Y_%m_%d_%H_%M_%S.sav', time.localtime(time.time()))
try:
self.__HexStorage(file, 'save')
except IOError: #pass the error back up the line
raise #re-raise the exception
def Load(self, file):
"""
load instance data from disk file, public method, returns savefile version
this function should be called in a
try:
except IOError:
block, to handle potential disk IO errors
"""
try:
version = self.__HexStorage(file, 'load')
except IOError: #pass the error back up the line
raise #re-raise the exception
else:
return version #to enable savefile version check if required
def ReadingAsText(self):
"""
create a multi-line text representation of the reading as a formatted string, public method,
returns the string
"""
#textReading = []
lineStrings = {6:'---X---',7:'-------',8:'--- ---',9:'---O---',0:''}#the 0 takes care of an empty Hex2
linePositions = {1:'first',2:'second',3:'third',4:'fourth',5:'fifth',6:'topmost'}
lineTypes = {6:'(6 moving yin)',7:'(7 yang)',8:'(8 yin)',9:'(9 moving yang)',0:''}#the 0 takes care of an empty Hex2
textReadingParts = []
textReadingParts.append( '\n '+string.ljust(self.hex1.number, 2)+\
' '+string.ljust(self.hex1.name, 30)+\
' '+string.ljust(self.hex2.number, 2)+' '+self.hex2.name+'\n\n' )
for i in range(5,-1,-1):
if i == 3:
if (6 in self.hex1.lineValues) or (9 in self.hex1.lineValues): #if there are moving lines
separator = ' becomes '
else:
separator = ' no moving lines'
else:
separator = ' '
textReadingParts.append( ' '+string.rjust(linePositions[i +1], 9)+' '+lineStrings[self.hex1.lineValues[i]]+\
' '+string.ljust(lineTypes[self.hex1.lineValues[i]], 15)+separator+\
lineStrings[self.hex2.lineValues[i]]+' '+lineTypes[self.hex2.lineValues[i]]+'\n' )
textReadingParts.append('\n '+self.question+'\n\n')
textReading = string.join(textReadingParts)
return textReading
#
# utility routines
######################
def Storage(file, data=None):
"""
store or load data to/from file using pickler
data should be a list of data items if storing, or None if loading
returns an unpickled list of data items on successful load
this function should be called in a
try:
except IOError:
except pychingPickleError:
except pychingUnpickleError:
block, to handle potential disk IO and pickle/unpickle errors
"""
if data: openType = 'w'
else: openType = 'r'
try:
pickleFile = open(file, openType)
except IOError:
raise #re-raise the exception to pass it back up the line
else: #no exception, so proceed
try:
try:
if data: #pickle required data
pickle.dump(data, pickleFile)
else: #unpickle data
pickleData = pickle.load(pickleFile)
return pickleData
except:
if data: raise 'pychingPickleError' #raise an error indicating that the pickle failed
else: raise 'pychingUnpickleError' #raise an error indicating that the unpickle failed
finally: pickleFile.close()
|