#!/usr/bin/python """ My first app to play with Python and curses programming. This is a minimalistic (so far) clone of Yahtzee(tm). Yahtzee(tm) is owned by Milton-Bradley, and used here without permission. """ __author__ = 'Philip Patterson' __date__ = 'January 2007' __version__ = '1.0' __copyright__ = '(c) 2007 Philip Patterson' __license__ = 'GNU GPL Version 2' import curses import logging import copy from random import Random class ScoreCard: def __init__(self): self.dCardCol = 15 self.dCardRow = 0 self.dScoreWidth = 4 self.dSeperator2 = "+======+======+======+" self.dSeperator1 = "+------+------+------+" self.dBoxWall1 = "| | | |" self.dDescOffset = len(self.dSeperator2)+self.dCardCol+2 self.dScoreOffset1 = self.dCardCol+2 self.dScoreOffset2 = self.dCardCol+5+self.dScoreWidth self.dScoreOffset3 = self.dCardCol+8+self.dScoreWidth+self.dScoreWidth self.dScoreRows = (1,3,5,7,9,11,13,15,17,20,22,24,26,28,30,32,34,36,38,41) self.dSepRows = ((0,self.dSeperator1), (2,self.dSeperator1), (4,self.dSeperator1), (6,self.dSeperator1), (8,self.dSeperator1), (10,self.dSeperator1), (12,self.dSeperator2), (14,self.dSeperator1), (16,self.dSeperator1), (18,self.dSeperator1), (19,self.dSeperator1), (21,self.dSeperator1), (23,self.dSeperator1), (25,self.dSeperator1), (27,self.dSeperator1), (29,self.dSeperator1), (31,self.dSeperator1), (33,self.dSeperator2), (35,self.dSeperator1), (37,self.dSeperator1), (39,self.dSeperator2), (40,self.dSeperator2), (42,self.dSeperator2)) self.dScoreDesc = ("A - Ones","B - Twos","C - Threes","D - Fours","E - Fives", "F - Sixes"," Top card subtotal"," Bonus for top sub >= 63", " Top card TOTAL","G - Three of a kind","H - Four of a kind", "I - Full House","J - Small straight","K - Large straight", "L - Yahtzee!!","M - Chance"," Bottom card TOTAL", " Bonus Yahtzees"," Top and Bottom Combined TOTAL"," Tripple Yahtzee TOTAL", " X Y Z ") self.dScoreValues =[['','',''],['','',''],['','',''],['','',''],['','',''], ['','',''],[0,0,0],[0,0,0],[0,0,0],['','',''], ['','',''],['','',''],['','',''],['','',''],['','',''], ['','',''],[0,0,0],[0,0,0],[0,0,0],[0,0,0]] def mDrawGridValues(self,dScr): x=0 while x < len(self.dScoreRows): dScr.addstr(self.dCardRow +self.dScoreRows[x],self.dScoreOffset1,"%4s" % self.dScoreValues[x][0],curses.A_NORMAL) dScr.addstr(self.dCardRow +self.dScoreRows[x],self.dScoreOffset2,"%4s" % self.dScoreValues[x][1],curses.A_NORMAL) dScr.addstr(self.dCardRow +self.dScoreRows[x],self.dScoreOffset3,"%4s" % self.dScoreValues[x][2],curses.A_NORMAL) x = x+1 def mDrawGridDesc(self,dScr): x = 0 while x < len(self.dScoreRows): dScr.addstr(self.dCardRow +self.dScoreRows[x],self.dDescOffset,self.dScoreDesc[x],curses.A_NORMAL) x = x+1 dScr.addstr(self.dCardRow +self.dScoreRows[(x-1)]+2,self.dCardCol,self.dScoreDesc[x],curses.A_NORMAL) def mDrawGrid(self,dScr): x = 0 while x < len(self.dScoreRows): dScr.addstr(self.dCardRow +self.dScoreRows[x],self.dCardCol,self.dBoxWall1,curses.A_NORMAL) x = x+1 x = 0 while x < len(self.dSepRows): dScr.addstr(self.dCardRow +self.dSepRows[x][0],self.dCardCol,self.dSepRows[x][1],curses.A_NORMAL) x = x+1 class MyDie: def __init__(self): self.dValue = 0 self.dHold = False self.dRand = Random() Random.seed(self.dRand,None) self.dBigStr = (" +-------+ \n | | \n | | \n | | \n +-------+ ", " +-------+ \n | | \n | 1 | \n | | \n +-------+ ", " +-------+ \n | 2 | \n | | \n | 2 | \n +-------+ ", " +-------+ \n | 3 | \n | 3 | \n | 3 | \n +-------+ ", " +-------+ \n | 4 4 | \n | | \n | 4 4 | \n +-------+ ", " +-------+ \n | 5 5 | \n | 5 | \n | 5 5 | \n +-------+ ", " +-------+ \n | 6 6 | \n | 6 6 | \n | 6 6 | \n +-------+ ") def mDraw(self, dScr, dWhere): #set the display attribute, based on the hold value if self.dHold == False: self.dCurrAttr = curses.A_STANDOUT else: self.dCurrAttr = curses.A_NORMAL #display the string representation of the die on the appropriate window dScr.addstr(dWhere,0,self.dBigStr[self.dValue],self.dCurrAttr) def mRoll(self): if self.dHold == False: self.dValue = Random.randint(self.dRand,1,6) def mToggleHold(self): if self.dHold == True: self.dHold = False else: self.dHold = True def mSetSeed(self,dSeed=None): Random.seed(self.dRand,dSeed) def CalcScore(dValues,cRow,Score=None): Score = 0 HiCount = 0 LoCount = 0 x = 0 bList=[0]*7 #list to store the sum and count of the dice values logging.debug("dValues="+str(dValues)) if cRow >= 0 and cRow <= 5: #top card for x in dValues: if x == (cRow +1): Score = Score + (cRow +1) elif cRow >= 9 and cRow <= 14: #bottom card excluding chance bList[0] = sum(dValues) #sum for x in dValues: logging.debug("x="+str(x)+",bList="+str(bList)) bList[x] += 1 #count logging.debug("bList="+str(bList)) x = 1 while x < len(bList): #find the high/low die counts if bList[x] > HiCount: LoCount = HiCount HiCount = bList[x] elif bList[x] > LoCount and bList[x] != 0: LoCount = bList[x] x = x+1 logging.debug("HiCount="+str(HiCount)+",LoCount="+str(LoCount)) if cRow == 9 and HiCount >= 3: #3 of a kind Score = bList[0] if cRow == 10 and HiCount >= 4: #4 of a kind Score = bList[0] if cRow == 11 and ((HiCount == 5) or HiCount == 3 and LoCount == 2): #full house Score = 25 if cRow == 12 and HiCount <= 2: #small straight # valid combos are 1234 2345 3456 ValidFlag = False if bList[3] != 0 and bList[4] != 0: #both are required if bList[2] != 0 and (bList[1] !=0 or bList[5] != 0): ValidFlag = True elif bList[5] != 0 and (bList[2] != 0 or bList[6] !=0): ValidFlag = True if ValidFlag == True: Score = 30 if cRow == 13 and HiCount <= 2: #large straight # valid combos are 12345 23456 ValidFlag = False if bList[2] != 0 and bList[3] != 0 and bList[4] != 0 and bList[5] != 0: #2-5 are required if bList[1] != 0 or bList[6] != 0: Score = 40 if cRow == 14 and HiCount == 5: #Yahtzee Score = 50 elif cRow == 15: #chance Score = sum(dValues) return Score def MyCurseFunc(stdscr): logging.basicConfig(level=logging.ERROR,format='%(asctime)s %(levelname)s %(message)s', filename='./yahtzee.log',filemode='w') TotalDice = 5 MaxRolls = 3 CurrRoll = 0 MySeed=Random() Random.seed(MySeed,None) #Enumerated list containing the dice objects and the positions to display them at # Has an empty (0) dummy record at the beginning to make indexing easier DiceList=((0,0,0),(1,MyDie(),2),(2,MyDie(),8),(3,MyDie(),14),(4,MyDie(),20),(5,MyDie(),26)) #Randomize the seeds for each die iLooper = 1 while iLooper <= TotalDice: DiceList[iLooper][1].mSetSeed(Random.random(MySeed)) iLooper = iLooper + 1 stdscr.addstr(" Yahtzee(tm)\n for CURSES!\n") #stdscr.border() MyCard = ScoreCard() CanSave = False curses.doupdate() while 1: iLooper = 1 while iLooper <= TotalDice: DiceList[iLooper][1].mDraw(stdscr,DiceList[iLooper][2]) iLooper = iLooper + 1 stdscr.addstr(32,1,"Roll #: ",curses.A_NORMAL) stdscr.addstr(32,9,str(CurrRoll),curses.A_STANDOUT) stdscr.addstr(34,1,"SPACEBAR=roll",curses.A_NORMAL) stdscr.addstr(35,1,"1-5=hold die",curses.A_NORMAL) stdscr.addstr(36,1,"r=reset dice",curses.A_NORMAL) stdscr.addstr(37,1,"q to quit",curses.A_NORMAL) stdscr.addstr(38,1,"u to undo",curses.A_NORMAL) stdscr.addstr(39,1,"n new game",curses.A_NORMAL) GameOver = True for dVal in MyCard.dScoreValues: if '' in dVal: GameOver = False #game not over yet if not GameOver: #game not over yet stdscr.addstr(41,1,"To save, pick",curses.A_NORMAL) stdscr.addstr(42,1,"x-z then a-m ",curses.A_NORMAL) else: #game over stdscr.addstr(41,1,"**GAME OVER**",curses.A_BLINK) stdscr.addstr(42,1,"TOTAL = %5s"%str(sum(MyCard.dScoreValues[19])),curses.A_BOLD) MyCard.mDrawGrid(stdscr) MyCard.mDrawGridValues(stdscr) MyCard.mDrawGridDesc(stdscr) curses.doupdate() c = stdscr.getch() if c in (ord('q'),ord('Q')): break # Exit the while() elif c in (ord('n'),ord('N')): # New Game MyCard = ScoreCard() #reset the score card #reset counters and other status vars CanSave = False CurrRoll = 0 #Randomize the seeds for each die, and reset them to zero iLooper = 1 while iLooper <= TotalDice: DiceList[iLooper][1].mSetSeed(Random.random(MySeed)) #reset the random seed DiceList[iLooper][1].dValue = 0 #reset the dice to zero DiceList[iLooper][1].dHold = False #reset the dice hold value iLooper = iLooper + 1 elif ((c >= ord('1')) and (c <= ord(str(TotalDice)))): DiceList[(c - ord('0'))][1].mToggleHold() elif ((CurrRoll < MaxRolls) and (c == ord(' ')) and (CanSave == True or CurrRoll == 0)): #roll the dice CurrRoll = CurrRoll + 1 CanSave = True iLooper = 1 while iLooper <= TotalDice: DiceList[iLooper][1].mRoll() iLooper = iLooper + 1 elif c in (ord('u'),ord('U')) and CanSave == False: #undo last save, unless you reset already CanSave = True logging.debug("CurrentScores="+str(MyCard.dScoreValues)) logging.debug("OldScores="+str(OldScores)) MyCard.dScoreValues = copy.deepcopy(OldScores) logging.debug("ResetScores="+str(MyCard.dScoreValues)) elif c in (ord('r'),ord('R')) and CanSave == False: #reset the hold status on all dice and roll again, after a save CurrRoll = 1 CanSave = True iLooper = 1 while iLooper <= TotalDice: DiceList[iLooper][1].dHold = False DiceList[iLooper][1].mRoll() iLooper = iLooper + 1 elif c in (ord('x'),ord('X'),ord('y'),ord('Y'),ord('z'),ord('Z')): #save the current roll if c in (ord('x'),ord('X')): MyCardCol = 0 elif c in (ord('y'),ord('Y')): MyCardCol = 1 elif c in (ord('z'),ord('Z')): MyCardCol = 2 else: continue c = stdscr.getch() if c in (ord('a'),ord('A'),ord('b'),ord('B'),ord('c'),ord('C'),ord('d'),ord('D'),ord('e'),ord('E'), ord('f'),ord('F'),ord('g'),ord('G'),ord('h'),ord('H'),ord('i'),ord('I'),ord('j'),ord('J'), ord('k'),ord('K'),ord('l'),ord('L'),ord('m'),ord('M'),): if c in (ord('a'),ord('A')): MyCardRow = 0 elif c in (ord('b'),ord('B')): MyCardRow = 1 elif c in (ord('c'),ord('C')): MyCardRow = 2 elif c in (ord('d'),ord('D')): MyCardRow = 3 elif c in (ord('e'),ord('E')): MyCardRow = 4 elif c in (ord('f'),ord('F')): MyCardRow = 5 elif c in (ord('g'),ord('G')): MyCardRow = 9 elif c in (ord('h'),ord('H')): MyCardRow = 10 elif c in (ord('i'),ord('I')): MyCardRow = 11 elif c in (ord('j'),ord('J')): MyCardRow = 12 elif c in (ord('k'),ord('K')): MyCardRow = 13 elif c in (ord('l'),ord('L')): MyCardRow = 14 elif c in (ord('m'),ord('M')): MyCardRow = 15 if MyCard.dScoreValues[MyCardRow][MyCardCol] == '' and CanSave == True: #not yet used CanSave = False OldScores = copy.deepcopy(MyCard.dScoreValues) dList = (DiceList[1][1].dValue,DiceList[2][1].dValue,DiceList[3][1].dValue, DiceList[4][1].dValue,DiceList[5][1].dValue) CurrScore = CalcScore(dList,MyCardRow) MyCard.dScoreValues[MyCardRow][MyCardCol] = CurrScore if MyCardRow <= 5: #check to see if a bonus has been earned if ((MyCard.dScoreValues[6][MyCardCol] < 63) and ((MyCard.dScoreValues[6][MyCardCol] + CurrScore) >62)): #give a bonus MyCard.dScoreValues[7][MyCardCol] = 35 MyCard.dScoreValues[8][MyCardCol] = MyCard.dScoreValues[8][MyCardCol] + 35 MyCard.dScoreValues[18][MyCardCol] = MyCard.dScoreValues[18][MyCardCol] + 35 MyCard.dScoreValues[19][MyCardCol] =\ MyCard.dScoreValues[19][MyCardCol] + (35 * (MyCardCol +1)) MyCard.dScoreValues[6][MyCardCol] = MyCard.dScoreValues[6][MyCardCol] + CurrScore MyCard.dScoreValues[8][MyCardCol] = MyCard.dScoreValues[8][MyCardCol] + CurrScore MyCard.dScoreValues[18][MyCardCol] = MyCard.dScoreValues[18][MyCardCol] + CurrScore MyCard.dScoreValues[19][MyCardCol] =\ MyCard.dScoreValues[19][MyCardCol] + (CurrScore * (MyCardCol +1)) else: MyCard.dScoreValues[16][MyCardCol] = MyCard.dScoreValues[16][MyCardCol] + CurrScore MyCard.dScoreValues[18][MyCardCol] = MyCard.dScoreValues[18][MyCardCol] + CurrScore MyCard.dScoreValues[19][MyCardCol] =\ MyCard.dScoreValues[19][MyCardCol] + (CurrScore * (MyCardCol +1)) if MyCardRow <= 11: #check to see if a bonus Yahtzee has been earned if (min(dList) == max(dList)): #the dice all had the same value if ((MyCard.dScoreValues[14][0] not in (0,'')) and (MyCard.dScoreValues[14][1] not in (0,'')) and (MyCard.dScoreValues[14][2] not in (0,''))): #make sure that the 3 basic Yahtzees were filled 1st MyCard.dScoreValues[17][MyCardCol] = MyCard.dScoreValues[17][MyCardCol] +100 MyCard.dScoreValues[18][MyCardCol] = MyCard.dScoreValues[18][MyCardCol] +100 MyCard.dScoreValues[19][MyCardCol] =\ MyCard.dScoreValues[19][MyCardCol] +(100 * (MyCardCol +1)) else: # invalid choice, squawk at the user curses.flash() curses.beep() if __name__ == "__main__": curses.wrapper(MyCurseFunc)