#!/usr/bin/env python # encoding: utf-8 """ py-Stones version 12, release candidate B A stone placing, puzzle game. Made in python using ncurses for the terminal. Released under Gnu Public License copyright Amy Nugent, 2008-09. """ import curses, curses.panel, sys, random, time, os.path # # # # # # # # # # # # # # """ GLOBAL VALUES """ # # # # # # # # # # # # # # savefile = os.path.expanduser('~/.stones') alphacoords = {"a":0,"b":1,"c":2,"d":3,"e":4,"f":5,"g":6,"h":7} # translation of letters to numbers scores = {"1":1,"2":2,"3":4,"4":8,"4way":25} # how much different moves are worth board = {"00":0,"01":0,"02":0,"03":0,"04":0,"05":0,"06":0,"07":0,"08":0,"09":0,"010":0,"011":0,\ "10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"110":0,"111":0,\ "20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"210":0,"211":0,\ "30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"310":0,"311":0,\ "40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"410":0,"411":0,\ "50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"510":0,"511":0,\ "60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"610":0,"611":0,\ "70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"710":0,"711":0,\ } # the board in which we put pieces in play # # # # # # # # # # # # # # """ CLASS DEFINITIONS """ # # # # # # # # # # # # # # class score: # so I can manipulate easily def __init__(self): self.modern = 0 self.ancient = 0 def printout(self, Window): Window.addstr(4,10,repr(self.modern),curses.A_BOLD) Window.addstr(5,10,repr(self.ancient),curses.A_BOLD) class move: # so I can attach data to moves in list def __init__(self,y,x,points): self.y=y self.x=x self.points=points class piece: # make it so I can create pieces def __init__(self,shape,color): # pieces initialize with attributes color and shape self.shape=shape self.color=color # see if two are the same piece def same_as(self, otherpiece): if self.color is otherpiece.color and self.shape is otherpiece.shape: return True else: return False def match1(self, otherpiece): if self.color is otherpiece.color or self.shape is otherpiece.shape: return True else: return False def match2(self, firstpiece, secondpiece): if self.color is firstpiece.color and self.shape is secondpiece.shape: return True elif self.shape is firstpiece.shape and self.color is secondpiece.color: return True else: return False def match3(self, firstpiece, secondpiece, thirdpiece): if self.color is firstpiece.color and self.color is secondpiece.color and self.shape is thirdpiece.shape: # 1,2 - 3 color -- shape return True elif self.color is firstpiece.color and self.color is thirdpiece.color and self.shape is secondpiece.shape: # 1,3 - 2 color -- shape return True elif self.color is secondpiece.color and self.color is thirdpiece.color and self.shape is firstpiece.shape: # 2,3 - 1 color -- shape return True elif self.shape is firstpiece.shape and self.shape is secondpiece.shape and self.color is thirdpiece.color: # 1,2 - 3 shape --- color return True elif self.shape is firstpiece.shape and self.shape is thirdpiece.shape and self.color is secondpiece.color: # 1,3 - 2 shape --- color return True elif self.shape is secondpiece.shape and self.shape is thirdpiece.shape and self.color is firstpiece.color: # 2,3 - 1 shape ---- color return True else: return False def match4(self, firstpiece, secondpiece, thirdpiece, fourthpiece): # 1,2 - 3,4 color - shape if self.color is firstpiece.color and self.color is secondpiece.color and self.shape is thirdpiece.shape and self.shape is fourthpiece.shape: return True # 1,3 - 2,4 color - shape elif self.color is firstpiece.color and self.color is thirdpiece.color and self.shape is secondpiece.shape and self.shape is fourthpiece.shape: return True # 1,4 -- 2,3 color - shape elif self.color is firstpiece.color and self.color is fourthpiece.color and self.shape is thirdpiece.shape and self.shape is secondpiece.shape: return True # 1,2 - 3,4 shape - color elif self.shape is firstpiece.shape and self.shape is secondpiece.shape and self.color is thirdpiece.color and self.color is fourthpiece.color: return True # 1,3 - 2,4 shape - color elif self.shape is firstpiece.shape and self.shape is thirdpiece.shape and self.color is secondpiece.color and self.color is fourthpiece.color: return True # 1,4 -- 2,3 shape - color elif self.shape is firstpiece.shape and self.shape is fourthpiece.shape and self.color is thirdpiece.color and self.color is secondpiece.color: return True else: return False # # # # # # # # # # # # # # """ FUNCTIONS """ # # # # # # # # # # # # # # def showscores(Window,panel): panel.top() panel.show() curses.panel.update_panels() Window.refresh() Window.getch() panel.hide() Window.refresh() getready(Window) def scoremodule(Window,records,score): # Put together the highest scores if score.modern > 0: now = time.strftime("%d %B %Y") ## 8 July 2008 (%a) %I:%M %p thescore = repr(score.modern) + " modern " + repr(score.ancient) + " ancient - " + now + "\n" records.append(thescore) records.sort(reverse=True) records = records[0:4] # save the file thefile = open(savefile,'w') for record in records: thefile.write(record) thefile.close() # create a high score window scorewin = curses.newwin(8,45,2,10) scorewin.box() scorewin.addstr(1,15,"High Scores", curses.color_pair(7)) y = 3 for record in records: # this will fail if records is empty scorewin.addstr(y,2,record) y = y+1 panel = curses.panel.new_panel(scorewin) panel.top() panel.show() curses.panel.update_panels() Window.refresh() Window.getch() panel.hide() Window.refresh() getready(Window) return records, panel def printpiece(Window, thepiece): Window.addstr(8,3,thepiece.shape,curses.color_pair(thepiece.color)) Window.refresh() def placepiece(Window, y, x, apiece): x = (int(x) * 3) + 20 ### Translate from grid choices to screen location y = int(y) + 4 Window.addstr(y,x,apiece.shape,curses.color_pair(apiece.color)) Window.refresh() def getready(Window): Window.move(13,25) # bring cursor to input line Window.clrtoeol() Window.refresh() def complain(Window,errortext): clearcomplaint(Window) Window.addstr(14,25,errortext) Window.addstr(13,25,"huh? ") Window.refresh() def clearcomplaint(Window): Window.move(14,25) # bring cursor to info line Window.clrtoeol() # clear out the rest of the line getready(Window) def shakethebag(): colors = [1,2,3,4,5,6] # six colors defined in Main # six arbitrary shapes, just used in this function. basic = [" # "," % "," ^ "," @ "," $ "," + "] # originals symbols = ["<~>",":::","/|\\","\\*/","(?)","-+-"] letters = [" a "," b "," c "," d "," e "," f "] nums = [" 1 "," 2 "," 3 "," 4 "," 5 "," 6 "] vowels = [" a "," e "," i "," o "," u "," y "] shapes = basic # theme choice goes here based on reading .stones file for pref. random.shuffle(colors) random.shuffle(shapes) newbag = [] startpcs = [] for color in colors: for shape in shapes: newpiece = piece(shape, color) # pull out special starting pieces if shape == shapes[color-1]: startpcs.append(newpiece) else: newbag.append(newpiece) # add the second one ( there are two of each stone type ) newbag.append(newpiece) random.shuffle(newbag) return startpcs, newbag def cleanboard(Window): # clean up those stones and put them away! y = 4 while y < 12: Window.move(y,19) # bring cursor to input line. Window.clrtoeol() # clear out last input y = y + 1 ## make a new board list for place in board: board[place] = 0 # this creates a 96 space board def getmoves(thepiece): # lets loop thru possible y and x values rather than the board contents moves = [] # these are possible moves with score value and location for space in board: if board[space] is 0: # if its and empty spot # Get y and x coordinates y = int(space[0]) if len(space) is 3: x = str(space[1]) + str(space[2]) else: x = space[1] x = int(x) # Get 4 relative directions north = repr(y-1) + repr(x) south = repr(y+1) + repr(x) west = repr(y) + repr(x-1) east = repr(y) + repr(x+1) # Get a neighbor list neighbors = [] if y is not 0: pn = board[north] if pn is not 0: neighbors.append(pn) if y is not 7: pn = board[south] if pn is not 0: neighbors.append(pn) if x is not 0: pn = board[west] if pn is not 0: neighbors.append(pn) if x is not 11: pn = board[east] if pn is not 0: neighbors.append(pn) ## count up neighbors and check for matches if len(neighbors) > 0: if len(neighbors) is 1: test = thepiece.match1(neighbors[0]) points = scores["1"] elif len(neighbors) is 2: test = thepiece.match2(neighbors[0],neighbors[1]) points = scores["2"] elif len(neighbors) is 3: test = thepiece.match3(neighbors[0],neighbors[1],neighbors[2]) points = scores["3"] elif len(neighbors) is 4: test = thepiece.match4(neighbors[0],neighbors[1],neighbors[2],neighbors[3]) points = scores["4"] + scores["4way"] if test is True: moves.append(move(y,x,points)) return moves def newgame(Window): cleanboard(Window) # clean window and data # shuffle pieces into bag startpcs, bagopieces = shakethebag() # 6 starting pieces positions = [[0,0],[0,11],[7,0],[7,11],[3,5],[4,6]] # these are the starting positions for position in positions: # take a piece apiece = startpcs.pop() # apiece, startpcs = takeapiecefrom(startpcs) y = position[0] x = position[1] # place it on the board loc = repr(y) + repr(x) board[loc] = apiece placepiece(Window,y,x,apiece) # take out the first piece thepiece = bagopieces.pop() # thepiece, bagopieces = takeapiecefrom(bagopieces) printpiece(Window, thepiece) # show how many pieces are in the bag Window.addstr(12,4, str(len(bagopieces)), curses.A_BOLD ) thescore = score() thescore.printout(Window) # reset score values scores["1"] = 1 scores["2"] = 2 scores["3"] = 4 scores["4"] = 8 scores["4way"] = 25 getready(Window) return thepiece, bagopieces, thescore ################################# ### Draw the game window # - this is a static operation to setup the screen for a game def setupmywindow(Window): Window.keypad(1) # interpret special characters (means f keys won't be read) curses.echo() curses.nocbreak() # Print Menu items... Window.addstr(1,1,"py-Stones", curses.color_pair(8) | curses.A_BOLD) Window.addstr(1,23,"cmds :: new scores help quit", curses.color_pair(9) | curses.A_BOLD) # Show the score so far Window.addstr(4,2,"modern", curses.color_pair(9) | curses.A_BOLD) Window.addstr(5,2,"ancient", curses.color_pair(9) | curses.A_BOLD) # Show the next piece Window.addstr(8,7,"next", curses.color_pair(9) | curses.A_BOLD) # Show piecebag Window.addstr(9,3,"____", curses.color_pair(7) | curses.A_BOLD) Window.addstr(10,3,"\\__/", curses.color_pair(7) | curses.A_BOLD) Window.addstr(11,3,"/ \\", curses.color_pair(7) | curses.A_BOLD) Window.addstr(12,2,"/ \\", curses.color_pair(7) | curses.A_BOLD) Window.addstr(13,2,"\\____/", curses.color_pair(7) | curses.A_BOLD) # input line Window.addstr(13,13,"Your move > ", curses.color_pair(9) | curses.A_BOLD) # cursor is at 13,25 # Window.addstr(14,15,"e.g. f9") # show what to do # board setup, draw the board and number the sides Window.addstr(3,20," 0 ") Window.addstr(3,23," 1 ") Window.addstr(3,26," 2 ") Window.addstr(3,29," 3 ") Window.addstr(3,32," 4 ") Window.addstr(3,35," 5 ") Window.addstr(3,38," 6 ") Window.addstr(3,41," 7 ") Window.addstr(3,44," 8 ") Window.addstr(3,47," 9 ") Window.addstr(3,50,"10 ") Window.addstr(3,53,"11 ") # vertical letters Window.addstr(4,17," a ") Window.addstr(5,17," b ") Window.addstr(6,17," c ") Window.addstr(7,17," d ") Window.addstr(8,17," e ") Window.addstr(9,17," f ") Window.addstr(10,17," g ") Window.addstr(11,17," h ") Window.refresh() # # # # # # # # # # # # # # """ MAIN LOOP """ # # # # # # # # # # # # # # def Main(Window): if curses.has_colors(): # stones won't make any sense without color - sorry. # Setup some colors for reference curses.init_pair(1,curses.COLOR_BLACK,curses.COLOR_GREEN) # black on green curses.init_pair(2,curses.COLOR_BLACK,curses.COLOR_CYAN) # black on cyan curses.init_pair(3,curses.COLOR_BLACK,curses.COLOR_MAGENTA) # black on magenta curses.init_pair(4,curses.COLOR_BLACK,curses.COLOR_YELLOW) # black on yellow curses.init_pair(5,curses.COLOR_BLACK,curses.COLOR_WHITE) # black on white curses.init_pair(6,curses.COLOR_BLACK,curses.COLOR_RED) # black on red curses.init_pair(7,curses.COLOR_YELLOW,curses.COLOR_BLACK) # yellow on black curses.init_pair(8,curses.COLOR_MAGENTA,curses.COLOR_BLACK) # pink on black curses.init_pair(9,curses.COLOR_GREEN,curses.COLOR_BLACK) # green on black curses.init_pair(10,curses.COLOR_CYAN,curses.COLOR_BLACK) # green on black curses.init_pair(11,curses.COLOR_RED,curses.COLOR_BLACK) # Red on black ### Load up the spalsh screen Window.box() Window.addstr(4,10," _______ __", curses.color_pair(7)) Window.addstr(5,10,".-----..--.--.______| __| |_.-----..-----..-----..-----.", curses.color_pair(7)) Window.addstr(6,10,"| _ || | |______|__ | _| _ || || -__||__ --|", curses.color_pair(9)) Window.addstr(7,10,"| __||___ | |_______|____|_____||__|__||_____||_____|", curses.color_pair(10)) Window.addstr(8,10,"|__| |_____| py-Stones v.11 2008", curses.color_pair(8)) Window.addstr(13,37,"start", curses.color_pair(11)) Window.getch() Window.erase() ### setupmywindow(Window) thepiece, bagopieces, score = newgame(Window) moves = getmoves(thepiece) try: # read from the file f = open(savefile,'r') records = f.readlines() f.close() except: # create the file f = open(savefile,'w') f.close() records = [] records, scorepanel = scoremodule(Window,records,score) ### Create a Help panel helpwin1 = curses.newwin(12,60,2,4) helpwin1.erase() helpwin1.box() helpwin2 = curses.newwin(12,60,2,4) helpwin2.erase() helpwin2.box() # First Help Panel helpwin1.addstr(1,2,"Welcome to Stones!", curses.color_pair(7)) helpwin1.addstr(2,2,"The goal of stones is to place all 72 stones onto the") helpwin1.addstr(3,2,"board. Each stone has a color and a shape. There are 6") helpwin1.addstr(4,2,"colors and 6 shapes, for a total of 36 unique pieces.") helpwin1.addstr(5,2,"Each piece occurs twice making 72 stones. Stones must be") helpwin1.addstr(6,2,"placed adjacent to others by matching them in either") helpwin1.addstr(7,2,"color or shape. You get more points when stones are") helpwin1.addstr(8,2,"placed adjacent to more than one stone. Placing a stone") helpwin1.addstr(9,2,"between 4 others earns the most points! To match one") helpwin1.addstr(10,2,"or two stones, ") helpwin1.addstr(10,43,"..read more", curses.color_pair(7)) # Second Help Panel helpwin2.addstr(1,2,"either color or shape must be the same for each stone.") helpwin2.addstr(2,2,"To match three stones, the piece must match two stones") helpwin2.addstr(3,2,"in one way and the third in the other. That is, 2 stones") helpwin2.addstr(4,2,"in shape and 1 in color, or 2 stones in color and 1 in") helpwin2.addstr(5,2,"shape. Matching 4 stones - creating a 4-way - is the") helpwin2.addstr(6,2,"highest scoring move in the game. It is achieved by") helpwin2.addstr(7,2,"matching 2 stones in color and the other 2 in shape.") helpwin2.addstr(8,2,"Easy right? Give Stones a try and have fun!") helpwin2.addstr(10,49,"close", curses.color_pair(7)) helppanel1 = curses.panel.new_panel(helpwin1) helppanel2 = curses.panel.new_panel(helpwin2) helppanel1.top() helppanel1.hide() helppanel2.top() helppanel2.hide() curses.panel.update_panels() Window.refresh() while 1: # stay running mymove = Window.getstr() mymove.lower() # make lowercase getready(Window) # move cursor and clear input line if mymove == "quit" or mymove == "exit": try: records, scorepanel = scoremodule(Window,records,score) except: complain(Window,"Something failed...") sys.exit() elif mymove == "new": try: records, scorepanel = scoremodule(Window,records,score) except: complain(Window,"Something failed...") thepiece, bagopieces, score = newgame(Window) moves = getmoves(thepiece) elif mymove == "help": # show the help panel created at startup helppanel1.show() curses.panel.update_panels() Window.move(12,58) Window.refresh() Window.getch() helppanel1.hide() helppanel2.show() curses.panel.update_panels() Window.move(12,58) Window.refresh() Window.getch() helppanel2.hide() getready(Window) elif mymove == "scores": showscores(Window, scorepanel) elif len(mymove) < 4: ####### try to place a piece ######## y = mymove[0] y = alphacoords[y] if len(mymove) == 3: x = str(mymove[1]) + str(mymove[2]) # make sure they are strings so we can add the letter together else: x = mymove[1] x = int(x) if x < 12: nomove = True for possiblemove in moves: if possiblemove.y is y and possiblemove.x is x: nomove = False placepiece(Window, y, x, thepiece) loc = repr(y) + repr(x) board[loc] = thepiece # update the score # no points for border pieces if x is not 0 and x is not 11 and y is not 0 and y is not 7: score.modern = score.modern + possiblemove.points # update score values # if we scored a 4 way if possiblemove.points is scores["4"] + scores ["4way"]: # update ancient score total score.ancient = score.ancient + 1 for point in scores: scores[point] = scores[point] * 2 score.printout(Window) thepiece = bagopieces.pop() # thepiece, bagopieces = takeapiecefrom(bagopieces) moves = getmoves(thepiece) if len(moves) > 0: printpiece(Window, thepiece) Window.addstr(12,4, str(len(bagopieces)), curses.A_BOLD ) clearcomplaint(Window) getready(Window) else: # bonus scores for high piece placement if len(bagopieces) is 0: score.modern = score.modern + 1000 elif len(bagopieces) is 1: score.modern = score.modern + 500 elif len(bagopieces) is 2: score.modern = score.modern + 100 # otherwise just say no more moves! and show the score board cleanboard(Window) Window.addstr(7,27,"No more moves!",curses.color_pair(9)) Window.getch() records, scorepanel = scoremodule(Window,records,score) thepiece, bagopieces, score = newgame(Window) moves = getmoves(thepiece) if nomove is True: complain(Window, "That's not a valid move!") else: complain(Window,"That's not on the board.") # except: # complain(Window,"I didn't understand that command.") ###################################### else: complain(Window,"Try entering a letter and a number, like b4.") if __name__ == "__main__": curses.wrapper(Main)