linuxport: Merging in the fruits of my labors (Python VFS)
[xbmc:xbmc-antiquated.git] / xbmc / lib / libPython / Python / Demo / curses / life.py
1 #!/usr/bin/env python
2 # life.py -- A curses-based version of Conway's Game of Life.
3 # Contributed by AMK
4 #
5 # An empty board will be displayed, and the following commands are available:
6 #  E : Erase the board
7 #  R : Fill the board randomly
8 #  S : Step for a single generation
9 #  C : Update continuously until a key is struck
10 #  Q : Quit
11 #  Cursor keys :  Move the cursor around the board
12 #  Space or Enter : Toggle the contents of the cursor's position
13 #
14 # TODO :
15 #   Support the mouse
16 #   Use colour if available
17 #   Make board updates faster
18 #
19
20 import random, string, traceback
21 import curses
22
23 class LifeBoard:
24     """Encapsulates a Life board
25
26     Attributes:
27     X,Y : horizontal and vertical size of the board
28     state : dictionary mapping (x,y) to 0 or 1
29
30     Methods:
31     display(update_board) -- If update_board is true, compute the
32                              next generation.  Then display the state
33                              of the board and refresh the screen.
34     erase() -- clear the entire board
35     makeRandom() -- fill the board randomly
36     set(y,x) -- set the given cell to Live; doesn't refresh the screen
37     toggle(y,x) -- change the given cell from live to dead, or vice
38                    versa, and refresh the screen display
39
40     """
41     def __init__(self, scr, char=ord('*')):
42         """Create a new LifeBoard instance.
43
44         scr -- curses screen object to use for display
45         char -- character used to render live cells (default: '*')
46         """
47         self.state={} ; self.scr=scr
48         Y, X = self.scr.getmaxyx()
49         self.X, self.Y = X-2, Y-2-1
50         self.char = char
51         self.scr.clear()
52
53         # Draw a border around the board
54         border_line='+'+(self.X*'-')+'+'
55         self.scr.addstr(0, 0, border_line)
56         self.scr.addstr(self.Y+1,0, border_line)
57         for y in range(0, self.Y):
58             self.scr.addstr(1+y, 0, '|')
59             self.scr.addstr(1+y, self.X+1, '|')
60         self.scr.refresh()
61
62     def set(self, y, x):
63         """Set a cell to the live state"""
64         if x<0 or self.X<=x or y<0 or self.Y<=y:
65             raise ValueError, "Coordinates out of range %i,%i"% (y,x)
66         self.state[x,y] = 1
67
68     def toggle(self, y, x):
69         """Toggle a cell's state between live and dead"""
70         if x<0 or self.X<=x or y<0 or self.Y<=y:
71             raise ValueError, "Coordinates out of range %i,%i"% (y,x)
72         if self.state.has_key( (x,y) ):
73             del self.state[x,y]
74             self.scr.addch(y+1, x+1, ' ')
75         else:
76             self.state[x,y]=1
77             self.scr.addch(y+1, x+1, self.char)
78         self.scr.refresh()
79
80     def erase(self):
81         """Clear the entire board and update the board display"""
82         self.state={}
83         self.display(update_board=0)
84
85     def display(self, update_board=1):
86         """Display the whole board, optionally computing one generation"""
87         M,N = self.X, self.Y
88         if not update_board:
89             for i in range(0, M):
90                 for j in range(0, N):
91                     if self.state.has_key( (i,j) ):
92                         self.scr.addch(j+1, i+1, self.char)
93                     else:
94                         self.scr.addch(j+1, i+1, ' ')
95             self.scr.refresh()
96             return
97
98         d={} ; self.boring=1
99         for i in range(0, M):
100             L=range( max(0, i-1), min(M, i+2) )
101             for j in range(0, N):
102                 s=0
103                 live=self.state.has_key( (i,j) )
104                 for k in range( max(0, j-1), min(N, j+2) ):
105                     for l in L:
106                         if self.state.has_key( (l,k) ):
107                             s=s+1
108                 s=s-live
109                 if s==3:
110                     # Birth
111                     d[i,j]=1
112                     self.scr.addch(j+1, i+1, self.char)
113                     if not live: self.boring=0
114                 elif s==2 and live: d[i,j]=1       # Survival
115                 elif live:
116                     # Death
117                     self.scr.addch(j+1, i+1, ' ')
118                     self.boring=0
119         self.state=d
120         self.scr.refresh()
121
122     def makeRandom(self):
123         "Fill the board with a random pattern"
124         self.state={}
125         for i in range(0, self.X):
126             for j in range(0, self.Y):
127                 if random.random() > 0.5: self.set(j,i)
128
129
130 def erase_menu(stdscr, menu_y):
131     "Clear the space where the menu resides"
132     stdscr.move(menu_y, 0) ; stdscr.clrtoeol()
133     stdscr.move(menu_y+1, 0) ; stdscr.clrtoeol()
134
135 def display_menu(stdscr, menu_y):
136     "Display the menu of possible keystroke commands"
137     erase_menu(stdscr, menu_y)
138     stdscr.addstr(menu_y, 4,
139                   'Use the cursor keys to move, and space or Enter to toggle a cell.')
140     stdscr.addstr(menu_y+1, 4,
141                   'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
142
143 def main(stdscr):
144
145     # Clear the screen and display the menu of keys
146     stdscr.clear()
147     stdscr_y, stdscr_x = stdscr.getmaxyx()
148     menu_y=(stdscr_y-3)-1
149     display_menu(stdscr, menu_y)
150
151     # Allocate a subwindow for the Life board and create the board object
152     subwin=stdscr.subwin(stdscr_y-3, stdscr_x, 0, 0)
153     board=LifeBoard(subwin, char=ord('*'))
154     board.display(update_board=0)
155
156     # xpos, ypos are the cursor's position
157     xpos, ypos = board.X/2, board.Y/2
158
159     # Main loop:
160     while (1):
161         stdscr.move(1+ypos, 1+xpos)     # Move the cursor
162         c=stdscr.getch()                # Get a keystroke
163         if 0<c<256:
164             c=chr(c)
165             if c in ' \n':
166                 board.toggle(ypos, xpos)
167             elif c in 'Cc':
168                 erase_menu(stdscr, menu_y)
169                 stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously '
170                               'updating the screen.')
171                 stdscr.refresh()
172                 # Activate nodelay mode; getch() will return -1
173                 # if no keystroke is available, instead of waiting.
174                 stdscr.nodelay(1)
175                 while (1):
176                     c=stdscr.getch()
177                     if c!=-1: break
178                     stdscr.addstr(0,0, '/'); stdscr.refresh()
179                     board.display()
180                     stdscr.addstr(0,0, '+'); stdscr.refresh()
181
182                 stdscr.nodelay(0)       # Disable nodelay mode
183                 display_menu(stdscr, menu_y)
184
185             elif c in 'Ee': board.erase()
186             elif c in 'Qq': break
187             elif c in 'Rr':
188                 board.makeRandom()
189                 board.display(update_board=0)
190             elif c in 'Ss':
191                 board.display()
192             else: pass                  # Ignore incorrect keys
193         elif c==curses.KEY_UP and ypos>0:            ypos=ypos-1
194         elif c==curses.KEY_DOWN and ypos<board.Y-1:  ypos=ypos+1
195         elif c==curses.KEY_LEFT and xpos>0:          xpos=xpos-1
196         elif c==curses.KEY_RIGHT and xpos<board.X-1: xpos=xpos+1
197         else: pass                      # Ignore incorrect keys
198
199 if __name__=='__main__':
200     try:
201         # Initialize curses
202         stdscr=curses.initscr()
203         # Turn off echoing of keys, and enter cbreak mode,
204         # where no buffering is performed on keyboard input
205         curses.noecho() ; curses.cbreak()
206
207         # In keypad mode, escape sequences for special keys
208         # (like the cursor keys) will be interpreted and
209         # a special value like curses.KEY_LEFT will be returned
210         stdscr.keypad(1)
211         main(stdscr)                    # Enter the main loop
212         # Set everything back to normal
213         stdscr.keypad(0)
214         curses.echo() ; curses.nocbreak()
215         curses.endwin()                 # Terminate curses
216     except:
217         # In the event of an error, restore the terminal
218         # to a sane state.
219         stdscr.keypad(0)
220         curses.echo() ; curses.nocbreak()
221         curses.endwin()
222         traceback.print_exc()           # Print the exception