Python RPG

Lion Kimbro : Projects : Python RPG

Update!

Mon Sep 10 14:38:56 PDT 2001

Someone has adopted the PyRPG!

Check out their web site.

Introduction

I didn't write the Python RPG, it is primarily the work of Brian Blackwell and John Michaelsome

However, I did pull the code down one evening, and cleaned it up a bit. The code I came up with is at the end of this web page.

Game Library Image Loading

I was proudest of these 3 lines:

images = {}
for filename in filter( lambda x: x[-4:] == ".gif", os.listdir( os.curdir ) ):
	images[ filename[:-4] ] = Tkinter.PhotoImage( file = filename )

I wish that every game library that was striving towards user-friendly-ness would automatically load all the gif's (or whatevers) in the current directory. The code above shows how to do it.

Extra points if it's then just a matter of saying images[ "robot1" ].draw( 50, 60 ) to draw the image robot1.gif to 50, 60. Unfortunately, I didn't make it do that. =^_^=

Python is so cool, that if you don't want to have to use the images dictionary, you can just change...

	images[ filename[:-4] ] = Tkinter.PhotoImage( file = filename )

...to...

	globals()[ filename[:-4] ] = Tkinter.PhotoImage( file = filename )

...and all of the image files are automatically loaded, and assigned to global variables! So if you have a file "robot.gif" in your directory, you would now be able to type: robot.draw( 40, 34 ) and the robot would draw to 40, 34! Tres cool!

Code

# pyrpg - The Python Role-Playing Game Engine
# (C) 1999 Brian Blackwell and John Michelsen, distributed under the terms of the
#     GNU General Public License.
#
#  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 useful,
#  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; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# drawmap.py - simple 3D maze drawing module for pyrpg

ver = '0.0.4 lion'

import Tkinter
import os
import time



# GPL #

print "pyrpg", ver, "- (C) 1999 Brian Blackwell."
print "Distributed under the terms of the GNU General Public License."
print "See file COPYING for details."



# build map #

map = [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
       [1,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,1,1,1],
       [1,0,1,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1],
       [1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,1],
       [1,1,1,1,0,0,0,1,0,0,1,1,5,1,1,0,0,1,1],
       [1,0,7,0,0,0,0,0,1,0,1,0,1,0,0,1,0,1,1],
       [1,0,1,0,0,1,1,0,0,0,0,0,0,0,1,0,1,0,1],
       [1,0,1,1,0,1,0,1,2,0,1,1,1,1,0,0,1,0,1],
       [1,0,0,0,0,0,0,1,0,0,0,0,1,0,1,1,1,1,1],
       [1,0,1,0,0,1,0,1,0,1,0,0,1,0,1,0,1,0,1],
       [1,0,0,0,0,1,0,0,0,0,1,0,0,1,1,0,0,1,1],
       [1,0,1,1,0,0,1,0,1,1,0,0,1,0,0,0,1,0,1],
       [1,1,0,0,0,1,0,0,0,1,0,0,0,0,1,1,1,0,1],
       [1,0,0,1,0,0,1,1,0,1,0,1,0,0,0,1,0,1,1],
       [1,4,0,1,0,1,0,0,0,1,1,0,1,0,1,0,1,1,1],
       [1,4,1,0,0,1,0,1,1,1,0,0,1,0,1,0,1,0,1],
       [1,0,1,0,1,1,0,1,0,0,6,1,0,0,1,8,1,3,1],
       [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]

map_height = len( map )
map_width  = len( map[ 0 ] )

# turn map template into collection of cells #

def new_cell( state ):
	return { "state":    state,
		 "visited":  0,
		 "contents": [] }

templateDict = { 0: 'empty',
		 1: 'wall',
		 2: 'enemy',
		 3: 'treasure',
		 4: 'fake_wall',
		 5: 'teleport',
		 6: 'stairs_up',
		 7: 'stairs_down',
		 8: 'stairs_both' }

for y in range( len( map ) ):
	for x in range( len( map[ y ] ) ):
		map[ y ][ x ] = new_cell( templateDict[ map[ y ][ x ] ] )

# initial settings #

pos = [1, 1]  # player pos: x, y
facing = 'S'  # N,E,S,W?

# UI response functions #

def firstperson_click( event = None ):
	(w,h) = ( canv.winfo_width(), canv.winfo_height() )
	if   event.x < w/3:   move_left()
	elif event.x > 2*w/3: move_right()
	else:
		if   event.y < h/3:   print "climb the stairs"
		elif event.y > 2*h/3: print "decend the stairs"
		else: move_forward()

def overhead_click(self, event):
	pos = [int(event.x/canv2scale),int(event.y/canv2scale)]
	drawmap()

# player motion functions #

def move_forward( event = None ):
	global pos, map
	(x,y) = (pos[0], pos[1])
	dx = { "N":  0, "E": 1, "S": 0, "W": -1 }[ facing ]
	dy = { "N": -1, "E": 0, "S": 1, "W":  0 }[ facing ]
	if map[ y+dy ][ x+dx ][ "state" ] != 'wall':
		pos = [ x+dx, y+dy ]
		map[ y ][ x ][ "visited" ] = 1
		drawmap()

def move_back( event = None ):
	global pos, map
	(x,y) = (pos[0], pos[1])
	dx = { "N": 0, "E": -1, "S":  0, "W": 1 }[ facing ]
	dy = { "N": 1, "E":  0, "S": -1, "W": 0 }[ facing ]
	if map[ y+dy ][ x+dx ][ "state" ] != 'wall':
		pos = [ x+dx, y+dy ]
		map[ y ][ x ][ "visited" ] = 1
		drawmap()

def turn_right( event = None ):
	global facing
	facing = { "N": "E",  "E": "S",  "S": "W",  "W": "N" }[ facing ]
	drawmap()

def turn_left( event = None ):
	global facing
	facing = { "N": "W",  "E": "N",  "S": "E",  "W": "S" }[ facing ]
	drawmap()

def turn_around( event = None ):
	global facing
	facing = { "N": "S",  "E": "W",  "S": "N",  "W": "E" }[ facing ]
	drawmap()

# UI #

root = Tkinter.Tk()
root.title( "PyRPG" )

images = {}
for filename in filter( lambda x: x[-4:] == ".gif", os.listdir( os.curdir ) ):
	images[ filename[:-4] ] = Tkinter.PhotoImage( file = filename )

firstperson = Tkinter.Frame( root )
firstperson.pack( side='left', anchor=Tkinter.NW )
overhead = Tkinter.Frame( root )
overhead.pack( side='left', anchor=Tkinter.NW )
canv = Tkinter.Canvas( firstperson, width=253, height=253, relief='sunken', bd=1 )
canv.pack()
canv.bind( "", firstperson_click )
canv.bind( "", turn_around )
canv.bind( "", move_back )
canv2 = Tkinter.Canvas( overhead, width=253, height=253, relief='sunken', bd=1 )
canv2.pack()
canv2.bind( "", overhead_click )
side = Tkinter.Frame( firstperson )
side.pack( side='left' )
arrows = Tkinter.Frame( firstperson )
arrows.pack( side='left' )
title = Tkinter.Label( side, image=images[ "pyrpg" ] )
title.pack( padx=20 )

def build_button( imagename, commandval, rowval, colval, bindstr ):
	b = Tkinter.Button( arrows, image=images[ imagename ], command=commandval,
			    width=21, height=21 )
	b.grid( row=rowval, column=colval )
	root.bind_all( bindstr, commandval )
	return b

button_forward = build_button( "up",     move_forward, 0, 1, "" )
button_back    = build_button( "down",   move_back,    2, 1, "" )
button_right   = build_button( "right",  turn_right,   1, 2, "" )
button_left    = build_button( "left",   turn_left,    1, 0, "" )
button_turn    = build_button( "rotate", turn_around,  1, 1, "" )

label = Tkinter.Label( overhead, text="could put some zooming mode buttons here" )
label.pack( pady=30 )

# map windows & calculate drawing scaling factor #

root.update()
canv2scale = canv2.winfo_width()/(map_width+1)



# screen drawing functions #

def valid_cell( x, y ):
	if (0 <= y < map_height) and (0 <= x < map_width):
		return 1
	return 0

def getview():
	"""
	Return player's point of view
	consisting of three lists,
	closest to farthest.
	"""
	view = []
	(xpos,ypos) = (pos[0],pos[1])

	# use facing to determine rasterization #
	
	across_dx, across_dy = { "N": (+1,0),
				 "E": (0,+1),
				 "S": (-1,0),
				 "W": (0,-1) }[ facing ]
	
	deep_dx, deep_dy = { "N": (-2,-1),
			     "E": (+1,-2),
			     "S": (+2,+1),
			     "W": (-1,+2) }[ facing ]
	
	# cue up to start position #
	
	xpos = xpos + across_dx + deep_dx
	ypos = ypos + across_dy + deep_dy

	for godeep in range( 3 ):

		# clear this level #
		
		vlevel = []

		# scan this level #
		
		for goacross in range( 3 ):
			if valid_cell( xpos, ypos ):
				vlevel.append( map[ ypos ][ xpos ] )
			else:
				vlevel.append( new_cell( "wall" ) )
			xpos = xpos + across_dx
			ypos = ypos + across_dy
			
		# go back to last spot #

		# (Yes, we could get rid of this by changing the deep_dx/dy.)
		
		xpos = xpos - across_dx
		ypos = ypos - across_dy
		
		# cue up to start of next across #

		xpos = xpos + deep_dx
		ypos = ypos + deep_dy

		view.append( vlevel )

	return view

def image( x, y, imagename ):
	canv.create_image( x, y, anchor=Tkinter.NW, image=images[ imagename ] )

def drawmap():
	local = getview()
	canv.delete( Tkinter.ALL )
	canv2.delete( Tkinter.ALL )
	image( 0, 0, "back" )

	if len( local ) > 1:
		if local[1][0][ "state" ] == 'wall': image(0,   92, "vlevel-2-left" )
		if local[1][1][ "state" ] == 'wall': image(80,  92, "vlevel-2-mid" )
		if local[1][2][ "state" ] == 'wall': image(153, 92, "vlevel-2-right" )
	
	if local[0][0][ "state" ] == 'wall': image( 0,   30, "vlevel-1-left" )
	if local[0][2][ "state" ] == 'wall': image( 178, 30, "vlevel-1-right" )
	if local[0][1][ "state" ] == 'wall': image( 0,   30, "vlevel-1-mid" )

	drawoverhead()

def drawoverhead():

	# draw map #
	
	y = 0
	for row in map:
		y = y + 1
		x = 0
		for cell in row:
			x = x + 1
			# call function depending on cell state #
			{ "wall":  draw_wall,
			  "enemy": draw_enemy,
			  "treasure": draw_treasure,
			  "fake_wall": draw_wall,
			  "teleport": draw_teleport,
			  "stairs_up": draw_stairs_up,
			  "stairs_down": draw_stairs_down,
			  "stairs_both": draw_stairs_both,
			  "empty": draw_nothing }[ cell[ "state" ] ]( x, y )

	# draw player #
	
	draw_rect( pos[0]+1, pos[1]+1, "black", 0.7, 1 )
	
	(_x,_y) = ( (pos[0]+1)*canv2scale, (pos[1]+1)*canv2scale )
	d = (canv2scale / 2) * .9
	
	(_x1, _y1, _x2, _y2) = { "N": (_x, _y+d, _x, _y-d ),
				 "E": (_x-d, _y, _x+d, _y ),
				 "S": (_x, _y-d, _x, _y+d ),
				 "W": (_x+d, _y, _x-d, _y ) }[ facing ]

	canv2.create_line( _x1, _y1, _x2, _y2, arrow='last', fill='white' )


def draw_rect( x, y, color, scale, fill ):
	(_x,_y) = ( x * canv2scale, y * canv2scale )
	d = (canv2scale / 2) * scale
	if fill == 0:
		canv2.create_rectangle( _x-d, _y-d, _x+d, _y+d, outline=color )
	else:
		canv2.create_rectangle( _x-d, _y-d, _x+d, _y+d, fill=color, outline=color )

def draw_wall( x, y ):        draw_rect( x, y, "#808080", 1.0, 1 )
def draw_enemy( x, y ):       draw_rect( x, y, "red", 0.7, 1 )
def draw_treasure( x, y ):    draw_rect( x, y, "gold", 0.7, 1 )
def draw_teleport( x, y ):    draw_rect( x, y, "darkgreen", 0.7, 1 )
def draw_stairs_up( x, y ):   draw_rect( x, y, "darkgreen", 0.7, 0 )
def draw_stairs_down( x, y ): draw_rect( x, y, "darkgreen", 0.7, 0 )
def draw_stairs_both( x, y ): draw_rect( x, y, "darkgreen", 0.7, 0 )
def draw_nothing( x, y ):     pass



root.update()
drawmap()
root.mainloop()