(root)/
Python-3.11.7/
Tools/
demo/
life.py
       1  #!/usr/bin/env python3
       2  
       3  """
       4  A curses-based version of Conway's Game of Life.
       5  
       6  An empty board will be displayed, and the following commands are available:
       7   E : Erase the board
       8   R : Fill the board randomly
       9   S : Step for a single generation
      10   C : Update continuously until a key is struck
      11   Q : Quit
      12   Cursor keys :  Move the cursor around the board
      13   Space or Enter : Toggle the contents of the cursor's position
      14  
      15  Contributed by Andrew Kuchling, Mouse support and color by Dafydd Crosby.
      16  """
      17  
      18  import curses
      19  import random
      20  
      21  
      22  class ESC[4;38;5;81mLifeBoard:
      23      """Encapsulates a Life board
      24  
      25      Attributes:
      26      X,Y : horizontal and vertical size of the board
      27      state : dictionary mapping (x,y) to 0 or 1
      28  
      29      Methods:
      30      display(update_board) -- If update_board is true, compute the
      31                               next generation.  Then display the state
      32                               of the board and refresh the screen.
      33      erase() -- clear the entire board
      34      make_random() -- fill the board randomly
      35      set(y,x) -- set the given cell to Live; doesn't refresh the screen
      36      toggle(y,x) -- change the given cell from live to dead, or vice
      37                     versa, and refresh the screen display
      38  
      39      """
      40      def __init__(self, scr, char=ord('*')):
      41          """Create a new LifeBoard instance.
      42  
      43          scr -- curses screen object to use for display
      44          char -- character used to render live cells (default: '*')
      45          """
      46          self.state = {}
      47          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 (x, y) in self.state:
      73              del self.state[x, y]
      74              self.scr.addch(y + 1, x + 1, ' ')
      75          else:
      76              self.state[x, y] = 1
      77              if curses.has_colors():
      78                  # Let's pick a random color!
      79                  self.scr.attrset(curses.color_pair(random.randrange(1, 7)))
      80              self.scr.addch(y + 1, x + 1, self.char)
      81              self.scr.attrset(0)
      82          self.scr.refresh()
      83  
      84      def erase(self):
      85          """Clear the entire board and update the board display"""
      86          self.state = {}
      87          self.display(update_board=False)
      88  
      89      def display(self, update_board=True):
      90          """Display the whole board, optionally computing one generation"""
      91          M, N = self.X, self.Y
      92          if not update_board:
      93              for i in range(0, M):
      94                  for j in range(0, N):
      95                      if (i, j) in self.state:
      96                          self.scr.addch(j + 1, i + 1, self.char)
      97                      else:
      98                          self.scr.addch(j + 1, i + 1, ' ')
      99              self.scr.refresh()
     100              return
     101  
     102          d = {}
     103          self.boring = 1
     104          for i in range(0, M):
     105              L = range(max(0, i - 1), min(M, i + 2))
     106              for j in range(0, N):
     107                  s = 0
     108                  live = (i, j) in self.state
     109                  for k in range(max(0, j - 1), min(N, j + 2)):
     110                      for l in L:
     111                          if (l, k) in self.state:
     112                              s += 1
     113                  s -= live
     114                  if s == 3:
     115                      # Birth
     116                      d[i, j] = 1
     117                      if curses.has_colors():
     118                          # Let's pick a random color!
     119                          self.scr.attrset(curses.color_pair(
     120                              random.randrange(1, 7)))
     121                      self.scr.addch(j + 1, i + 1, self.char)
     122                      self.scr.attrset(0)
     123                      if not live:
     124                          self.boring = 0
     125                  elif s == 2 and live:
     126                      # Survival
     127                      d[i, j] = 1
     128                  elif live:
     129                      # Death
     130                      self.scr.addch(j + 1, i + 1, ' ')
     131                      self.boring = 0
     132          self.state = d
     133          self.scr.refresh()
     134  
     135      def make_random(self):
     136          "Fill the board with a random pattern"
     137          self.state = {}
     138          for i in range(0, self.X):
     139              for j in range(0, self.Y):
     140                  if random.random() > 0.5:
     141                      self.set(j, i)
     142  
     143  
     144  def erase_menu(stdscr, menu_y):
     145      "Clear the space where the menu resides"
     146      stdscr.move(menu_y, 0)
     147      stdscr.clrtoeol()
     148      stdscr.move(menu_y + 1, 0)
     149      stdscr.clrtoeol()
     150  
     151  
     152  def display_menu(stdscr, menu_y):
     153      "Display the menu of possible keystroke commands"
     154      erase_menu(stdscr, menu_y)
     155  
     156      # If color, then light the menu up :-)
     157      if curses.has_colors():
     158          stdscr.attrset(curses.color_pair(1))
     159      stdscr.addstr(menu_y, 4,
     160          'Use the cursor keys to move, and space or Enter to toggle a cell.')
     161      stdscr.addstr(menu_y + 1, 4,
     162          'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
     163      stdscr.attrset(0)
     164  
     165  
     166  def keyloop(stdscr):
     167      # Clear the screen and display the menu of keys
     168      stdscr.clear()
     169      stdscr_y, stdscr_x = stdscr.getmaxyx()
     170      menu_y = (stdscr_y - 3) - 1
     171      display_menu(stdscr, menu_y)
     172  
     173      # If color, then initialize the color pairs
     174      if curses.has_colors():
     175          curses.init_pair(1, curses.COLOR_BLUE, 0)
     176          curses.init_pair(2, curses.COLOR_CYAN, 0)
     177          curses.init_pair(3, curses.COLOR_GREEN, 0)
     178          curses.init_pair(4, curses.COLOR_MAGENTA, 0)
     179          curses.init_pair(5, curses.COLOR_RED, 0)
     180          curses.init_pair(6, curses.COLOR_YELLOW, 0)
     181          curses.init_pair(7, curses.COLOR_WHITE, 0)
     182  
     183      # Set up the mask to listen for mouse events
     184      curses.mousemask(curses.BUTTON1_CLICKED)
     185  
     186      # Allocate a subwindow for the Life board and create the board object
     187      subwin = stdscr.subwin(stdscr_y - 3, stdscr_x, 0, 0)
     188      board = LifeBoard(subwin, char=ord('*'))
     189      board.display(update_board=False)
     190  
     191      # xpos, ypos are the cursor's position
     192      xpos, ypos = board.X // 2, board.Y // 2
     193  
     194      # Main loop:
     195      while True:
     196          stdscr.move(1 + ypos, 1 + xpos)   # Move the cursor
     197          c = stdscr.getch()                # Get a keystroke
     198          if 0 < c < 256:
     199              c = chr(c)
     200              if c in ' \n':
     201                  board.toggle(ypos, xpos)
     202              elif c in 'Cc':
     203                  erase_menu(stdscr, menu_y)
     204                  stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously '
     205                                'updating the screen.')
     206                  stdscr.refresh()
     207                  # Activate nodelay mode; getch() will return -1
     208                  # if no keystroke is available, instead of waiting.
     209                  stdscr.nodelay(1)
     210                  while True:
     211                      c = stdscr.getch()
     212                      if c != -1:
     213                          break
     214                      stdscr.addstr(0, 0, '/')
     215                      stdscr.refresh()
     216                      board.display()
     217                      stdscr.addstr(0, 0, '+')
     218                      stdscr.refresh()
     219  
     220                  stdscr.nodelay(0)       # Disable nodelay mode
     221                  display_menu(stdscr, menu_y)
     222  
     223              elif c in 'Ee':
     224                  board.erase()
     225              elif c in 'Qq':
     226                  break
     227              elif c in 'Rr':
     228                  board.make_random()
     229                  board.display(update_board=False)
     230              elif c in 'Ss':
     231                  board.display()
     232              else:
     233                  # Ignore incorrect keys
     234                  pass
     235          elif c == curses.KEY_UP and ypos > 0:
     236              ypos -= 1
     237          elif c == curses.KEY_DOWN and ypos + 1 < board.Y:
     238              ypos += 1
     239          elif c == curses.KEY_LEFT and xpos > 0:
     240              xpos -= 1
     241          elif c == curses.KEY_RIGHT and xpos + 1 < board.X:
     242              xpos += 1
     243          elif c == curses.KEY_MOUSE:
     244              mouse_id, mouse_x, mouse_y, mouse_z, button_state = curses.getmouse()
     245              if (mouse_x > 0 and mouse_x < board.X + 1 and
     246                  mouse_y > 0 and mouse_y < board.Y + 1):
     247                  xpos = mouse_x - 1
     248                  ypos = mouse_y - 1
     249                  board.toggle(ypos, xpos)
     250              else:
     251                  # They've clicked outside the board
     252                  curses.flash()
     253          else:
     254              # Ignore incorrect keys
     255              pass
     256  
     257  
     258  def main(stdscr):
     259      keyloop(stdscr)                 # Enter the main loop
     260  
     261  if __name__ == '__main__':
     262      curses.wrapper(main)