Chapter 22. Using Curses
CurseWorldCurseWorld.pyimport curses try: MainWindow=curses.initscr() # initialize curses MainWindow.addstr("Hello, damn it!") MainWindow.refresh() MainWindow.getch() # Read a keystroke finally: curses.endwin() # de-initialize curses DeathRayDeathRay.pyimport curses import curses.textpad import whrandom class CursesButton: def __init__(self,Window,Y,X,Label,Hotkey=0): self.Y=Y self.X=X self.Label=Label self.Width=len(Label)+2 # label, plus lines on side self.Underline=Underline # Draw the button: curses.textpad.rectangle(Window,Y,X,Y+2,X+self.Width) # Draw the button label: Window.addstr(Y+1,X+1,Label,curses.A_BOLD) # Make the hotkey stand out: Window.addstr(Y+1,X+Underline+1,Label[Underline] ,curses.A_REVERSE) Window.refresh() def KeyPressed(self,Char): if (Char>255): return 0 # skip control-characters if chr(Char).upper()==self.Label[self.Underline]: return 1 else: return 0 def MouseClicked(self,MouseEvent): (id,x,y,z,event)=MouseEvent if (self.Y <= y <= self.Y+2) and (self.X <= x < self.X+self.Width): return 1 else: return 0 def ShowDialog(Window): curses.mousemask(curses.BUTTON1_PRESSED) Window.addstr(5,0,"Really, REALLY fire death ray?") YesButton=CursesButton(Window,8,10,"Yes") NoButton=CursesButton(Window,8,20,"No") MaybeButton=CursesButton(Window,8,30,"Maybe") Buttons=[YesButton,NoButton,MaybeButton] Window.nodelay(1) Action="" while 1: Key=Window.getch() if (Key==-1): continue for Button in Buttons: if Button.KeyPressed(Key): Action=Button.Label # Handle mouse-events: if (Key==curses.KEY_MOUSE): MouseEvent=curses.getmouse() for Button in Buttons: if Button.MouseClicked(MouseEvent): Action=Button.Label if Action!="": break # Handle the actions if (Action=="Yes"): FireDeathRay(Window) if (Action=="No"): pass if (Action=="Maybe" and whrandom.random() > 0.5): FireDeathRay(Window) def FireDeathRay(Window): Window.clear() # Kra-ppoowwww! Frrrraapppp!! Window.bkgd("X") Window.nodelay(0) Window.getch() if __name__=="__main__": curses.wrapper(ShowDialog) MaskMask.pyimport curses class Mask: def __init__(self,Window,Top,Bottom,Left,Right): self.Window=Window self.Top=Top self.Bottom=Bottom self.Left=Left self.Right=Right self.OldText=None def Cover(self,Character="X",Attributes=curses.A_DIM): # Cover the current screen contents. Store # them in OldText[RowIndex][ColumnIndex] for later: self.OldText=[] for Row in range(self.Top,self.Bottom+1): self.OldText.append([]) for Col in range(self.Left, self.Right+1): self.OldText[-1].append( self.Window.inch(Row,Col)) self.Window.addstr(Row,Col, Character,Attributes) def Reveal(self): if (self.OldText==None): return for Row in range(self.Top,self.Bottom+1): CurrentLine=self.OldText[Row-self.Top] for Col in range(self.Left, self.Right+1): CurrentCol=(Col-self.Left) Character=chr(CurrentLine[CurrentCol] & 0xFF) Attributes=CurrentLine[CurrentCol] & (~0xFF) self.Window.addstr(Row,Col, Character,Attributes) def Main(MainWindow): MainWindow.addstr(10,10,"Yes it is!") MainWindow.addstr(11,10,"No it isn't!",curses.A_BOLD) MainWindow.addstr(12,10,"Yes it is!",curses.A_UNDERLINE) MainWindow.addstr(13,10,"No it isn't!",curses.A_STANDOUT) MainWindow.addstr(14,10,"YES IT IS!",curses.A_BOLD) MyMask=Mask(MainWindow,10,20,10,40) MainWindow.refresh() MainWindow.getch() MyMask.Cover() MainWindow.refresh() MainWindow.getch() MyMask.Reveal() MainWindow.refresh() MainWindow.getch() if (__name__=="__main__"): curses.wrapper(Main) MazeMaze.pyimport curses import curses.ascii import whrandom # Possible contents of maze-squares: MAZE_WALL="X" MAZE_ENTRANCE="*" MAZE_HALLWAY="." # Attributes for displaying maze squares: MAZE_ATTRIBUTE={MAZE_WALL:curses.A_NORMAL, MAZE_ENTRANCE:curses.A_BOLD, MAZE_HALLWAY:curses.A_DIM,} # Simple class representing a compass direction: class Direction: def __init__(self,Name,XDelta,YDelta): self.Name=Name self.XDelta=XDelta self.YDelta=YDelta self.Marker=Name[0] def SetOpposite(self,Dir): self.Opposite=Dir Dir.Opposite=self NORTH=Direction("North",0,-1) SOUTH=Direction("South",0,1) EAST=Direction("East",1,0) WEST=Direction("West",-1,0) NORTH.SetOpposite(SOUTH) EAST.SetOpposite(WEST) VALID_DIRECTIONS=[NORTH,SOUTH,EAST,WEST] # Maze creation uses direction "markers" to indicate how we got # to a square, so that we can (later) backtrack: MARKED_DIRECTIONS={NORTH.Marker:NORTH,SOUTH.Marker:SOUTH, EAST.Marker:EAST,WEST.Marker:WEST} # Map keystrokes to compass directions: KEY_DIRECTIONS={curses.KEY_UP:NORTH,curses.KEY_DOWN:SOUTH, curses.KEY_LEFT:WEST,curses.KEY_RIGHT:EAST} class Maze: def __init__(self,Size=11): # Maze size must be an odd number: if (Size%2==0): Size+=1 self.Size=Size self.Pad=curses.newpad(self.Size+1,self.Size+1) self.FillWithWalls() def FillWithWalls(self): for Y in range(0,self.Size): self.Pad.addstr(Y,0,MAZE_WALL*self.Size,MAZE_ATTRIBUTE[MAZE_WALL]) def Set(self,X,Y,Char): self.Pad.addstr(Y,X,Char,MAZE_ATTRIBUTE.get(Char,curses.A_NORMAL)) def Get(self,X,Y): return self.Pad.instr(Y,X,1) def BuildRandomMaze(self): self.FillWithWalls() CurrentX=1 CurrentY=1 self.Set(CurrentX,CurrentY,MAZE_ENTRANCE) while (1): Direction=self.GetValidDirection(CurrentX,CurrentY) if (Direction!=None): # Take one step forward self.Set(CurrentX+Direction.XDelta, CurrentY+Direction.YDelta,MAZE_HALLWAY) CurrentX+=Direction.XDelta*2 CurrentY+=Direction.YDelta*2 self.Set(CurrentX,CurrentY,Direction.Marker) else: # Backtrack one step BackDirectionMarker=self.Get(CurrentX,CurrentY) BackDirection=MARKED_DIRECTIONS[BackDirectionMarker].Opposite CurrentX+=BackDirection.XDelta*2 CurrentY+=BackDirection.YDelta*2 # If we backtracked to the entrance, the maze is done! if self.Get(CurrentX,CurrentY)==MAZE_ENTRANCE: break # Fix up the maze: for X in range(0,self.Size): for Y in range(0,self.Size): if self.Get(X,Y) not in [MAZE_HALLWAY,MAZE_WALL, MAZE_ENTRANCE]: self.Set(X,Y,MAZE_HALLWAY) def GetValidDirection(self,X,Y): DirectionIndex=whrandom.randint(0,len(VALID_DIRECTIONS)-1) FirstIndex=DirectionIndex while (1): Direction=VALID_DIRECTIONS[DirectionIndex] NextSquare=(X+Direction.XDelta*2,Y+Direction.YDelta*2) if ((0 < NextSquare[0] < self.Size) and (0 < NextSquare[1] < self.Size) and self.Get(NextSquare[0],NextSquare[1])==MAZE_WALL): return Direction DirectionIndex+=1 if (DirectionIndex>=len(VALID_DIRECTIONS)): DirectionIndex=0 if (DirectionIndex==FirstIndex): return None def ShowSelf(self,ScreenLeft,ScreenTop,PlayerX,PlayerY,Radius): Top=PlayerY-Radius Bottom=PlayerY+Radius Left=PlayerX-Radius Right=PlayerX+Radius ScreenRight=ScreenLeft+Radius*2+1 ScreenBottom=ScreenTop+Radius*2+1 if (Top<0): ScreenTop -= Top Top=0 if (Left<0): ScreenLeft -= Left Left=0 if (Right>self.Size-1): ScreenRight-=(self.Size-1-Right) Right=self.Size-1 if (Bottom>self.Size-1): ScreenBottom-=(self.Size-1-Bottom) Bottom=self.Size-1 self.Pad.refresh(Top,Left,ScreenTop,ScreenLeft,ScreenBottom,ScreenRight) def Main(Window): # Set up colors: curses.init_pair(1,curses.COLOR_GREEN,curses.COLOR_BLACK) curses.init_pair(2,curses.COLOR_BLUE,curses.COLOR_BLACK) curses.init_pair(3,curses.COLOR_RED,curses.COLOR_BLACK) MAZE_ATTRIBUTE[MAZE_HALLWAY] |= curses.color_pair(1) MAZE_ATTRIBUTE[MAZE_ENTRANCE] |= curses.color_pair(2) MAZE_ATTRIBUTE[MAZE_WALL] |= curses.color_pair(3) curses.curs_set(0) # invisible cursor MyMaze=Maze(20) MyMaze.BuildRandomMaze() PlayerX=19 PlayerY=19 LightRadius=3 MazeWindow=curses.newwin(10,10,10+LightRadius*2+1,10+LightRadius*2+1) while 1: MazeWindow.erase() MyMaze.ShowSelf(10,10,PlayerX,PlayerY,LightRadius) Window.addch(10+LightRadius,10+LightRadius,"@", curses.color_pair(2) & curses.A_STANDOUT) Window.refresh() Key=Window.getch() if (Key==ord('q') or Key==curses.ascii.ESC): break Direction=KEY_DIRECTIONS.get(Key,None) if (Direction): TargetSquare=MyMaze.Get(PlayerX+Direction.XDelta, PlayerY+Direction.YDelta) if TargetSquare==MAZE_ENTRANCE: MazeFinished(Window) break if TargetSquare==MAZE_HALLWAY: PlayerX += Direction.XDelta PlayerY += Direction.YDelta def MazeFinished(Window): Window.clear() Window.addstr(5,5,"CONGRATULATION!",curses.color_pair(2)) Window.addstr(6,5,"A WINNER IS YOU!",curses.color_pair(3)) Window.getch() pass if (__name__=="__main__"): curses.wrapper(Main) print "Bye!" SpiralSpiral.pyimport curses import math def DrawSpiral(Window,CenterY,CenterX,Height,Width): ScalingFactor=1.0 Angle=0 HalfHeight = float(Height)/2 HalfWidth = float(Width)/2 while (ScalingFactor>0): Y = CenterY + (HalfHeight*math.sin(Angle)*ScalingFactor) X = CenterX + (HalfWidth*math.cos(Angle)*ScalingFactor) Window.move(int(Y),int(X)) Window.addstr("*") Angle+=0.05 ScalingFactor=ScalingFactor - 0.001 Window.refresh() def Main(Window): (Height,Width)=Window.getmaxyx() Height-=1 # Don't make the spiral too big Width-=1 CenterY=Height/2 CenterX=Width/2 DrawSpiral(Window,CenterY,CenterX,Height,Width) Window.getch() if __name__=="__main__": curses.wrapper(Main)
Email the webmaster.
|