Simple two player snake game

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
5
down vote

favorite












I've been working on a two player snake game in python. I would really appreciate any suggestions/general input that would help improve my coding.



"""
5-13-2018
Nathan van 't Hof
2 player snake game
players can move around using wasd (player 1) and ijkl (player 2)
eating apples: - will increase players length
"""

import random
import keyboard
import os
import colorama
import time

def get_apple(width, height, snake1, snake2):
"""
find new random coordinate to place the apple
"""
if len(snake1) + len(snake2) >= width*height:
u = raw_input('You win!')
quit()
while True:
apple = [random.randint(0, width - 1), random.randint(0, height-1)]
if apple not in snake1 and apple not in snake2:
return apple


def draw(game_field, i, key):
"""
change a specific coordinate of the game field
to a different type
"""
game_field[i[0]][i[1]] = key
return game_field


def move_snake(game_field, snake, typ):
"""
move the snake one step
"""
game_field = draw(game_field, snake[-2], typ)
game_field = draw(game_field, snake[-1], 'O')
return game_field


def change_pos(prev, width, height, dx, dy):
"""
change the coordinate of the head of the snake
"""
return [(prev[0]+dx)%width, (prev[1]+dy)%height]


def print_game(game_field):
"""
print the game (in a readable format)
"""
output = '.' * ((len(game_field)) * 2 + 1) + 'n'
for i in game_field:
output += '.' + ' '.join(i) + '.' + 'n'
output += '.' * ((len(game_field)) * 2 + 1)
# moves the marker to the top to prevent flikkering
print('33[H' + output)


def check_die(snake1, snake2):
"""
check whether the snakes 'die'
by letting the head bump into a tail/other head
"""
dead = False
if snake1[-1] == snake2[-1]:
u = raw_input('Both snakes died')
dead = True
elif snake1[-1] in snake2 or snake1.count(snake1[-1]) >= 2:
u = raw_input('Snake 1 died')
dead = True
elif snake2[-1] in snake1 or snake2.count(snake2[-1]) >= 2:
u = raw_input('Snake 2 died')
dead = True
if dead:
quit()


def check_movement(dx, dy, ch1, ch2, ch3, ch4):
"""
check where the snake moves
"""
if keyboard.is_pressed(ch1) and dx != -1 and dy != 0:
return -1, 0
if keyboard.is_pressed(ch2) and dx != 0 and dy != -1:
return 0, -1
if keyboard.is_pressed(ch3) and dx != 1 and dy != 0:
return 1, 0
if keyboard.is_pressed(ch4) and dx != 0 and dy != 1:
return 0,1
return dx, dy


def update_snake(new_pos, apple, game_field, snake1, snake2):
snake1.append(new_pos)
if new_pos == apple:
apple = get_apple(width, height, snake1, snake2)
game_field = draw(game_field, apple, '-')
else:
game_field = draw(game_field, snake1[0], ' ')
del snake1[0]
return snake1, apple, game_field


# init
width, height = 20, 20
snake1 = [[0,0],[0,1]]
snake2 = [[width/2, 0], [width/2 + 1, 0]]

apple = get_apple(width, height, snake1, snake2)
dx1, dy1 = 0, 1
dx2, dy2 = 0, 1

os.system('cls' if os.name == 'nt' else 'clear')
# this allows '33[H' to work
colorama.init()

# draw inital positions of the field
game_field = [height*[' '] for i in range(width)]
for i in snake1:
game_field = draw(game_field, i, 'x')
for i in snake2:
game_field = draw(game_field, i, '+')

game_field = draw(game_field, snake1[-1], 'O')
game_field = draw(game_field, snake2[-1], 'O')
game_field = draw(game_field, apple, '-')

prev_time = time.time()
while True:
try:
fps = float(input('What framerate would you like to run at?'))
break
except:
print('Please input a number')

os.system('cls' if os.name == 'nt' else 'clear')

while True:
# check inputs from players
dx1, dy1 = check_movement(dx1, dy1, 'w', 'a', 's', 'd')
dx2, dy2 = check_movement(dx2, dy2, 'i', 'j', 'k', 'l')

# update screen if enough time has passed
if time.time() - prev_time > 1./fps:
prev_time = time.time()
print_game(game_field)
new_pos1 = change_pos(snake1[-1], width, height, dx1, dy1)
new_pos2 = change_pos(snake2[-1], width, height, dx2, dy2)

# update snakes and playing field
check_die(snake1, snake2)

snake1, apple, game_field = update_snake(new_pos1, apple, game_field, snake1, snake2)
snake2, apple, game_field = update_snake(new_pos2, apple, game_field, snake2, snake1)

game_field = move_snake(game_field, snake1, 'x')
game_field = move_snake(game_field, snake2, '+')






share|improve this question



























    up vote
    5
    down vote

    favorite












    I've been working on a two player snake game in python. I would really appreciate any suggestions/general input that would help improve my coding.



    """
    5-13-2018
    Nathan van 't Hof
    2 player snake game
    players can move around using wasd (player 1) and ijkl (player 2)
    eating apples: - will increase players length
    """

    import random
    import keyboard
    import os
    import colorama
    import time

    def get_apple(width, height, snake1, snake2):
    """
    find new random coordinate to place the apple
    """
    if len(snake1) + len(snake2) >= width*height:
    u = raw_input('You win!')
    quit()
    while True:
    apple = [random.randint(0, width - 1), random.randint(0, height-1)]
    if apple not in snake1 and apple not in snake2:
    return apple


    def draw(game_field, i, key):
    """
    change a specific coordinate of the game field
    to a different type
    """
    game_field[i[0]][i[1]] = key
    return game_field


    def move_snake(game_field, snake, typ):
    """
    move the snake one step
    """
    game_field = draw(game_field, snake[-2], typ)
    game_field = draw(game_field, snake[-1], 'O')
    return game_field


    def change_pos(prev, width, height, dx, dy):
    """
    change the coordinate of the head of the snake
    """
    return [(prev[0]+dx)%width, (prev[1]+dy)%height]


    def print_game(game_field):
    """
    print the game (in a readable format)
    """
    output = '.' * ((len(game_field)) * 2 + 1) + 'n'
    for i in game_field:
    output += '.' + ' '.join(i) + '.' + 'n'
    output += '.' * ((len(game_field)) * 2 + 1)
    # moves the marker to the top to prevent flikkering
    print('33[H' + output)


    def check_die(snake1, snake2):
    """
    check whether the snakes 'die'
    by letting the head bump into a tail/other head
    """
    dead = False
    if snake1[-1] == snake2[-1]:
    u = raw_input('Both snakes died')
    dead = True
    elif snake1[-1] in snake2 or snake1.count(snake1[-1]) >= 2:
    u = raw_input('Snake 1 died')
    dead = True
    elif snake2[-1] in snake1 or snake2.count(snake2[-1]) >= 2:
    u = raw_input('Snake 2 died')
    dead = True
    if dead:
    quit()


    def check_movement(dx, dy, ch1, ch2, ch3, ch4):
    """
    check where the snake moves
    """
    if keyboard.is_pressed(ch1) and dx != -1 and dy != 0:
    return -1, 0
    if keyboard.is_pressed(ch2) and dx != 0 and dy != -1:
    return 0, -1
    if keyboard.is_pressed(ch3) and dx != 1 and dy != 0:
    return 1, 0
    if keyboard.is_pressed(ch4) and dx != 0 and dy != 1:
    return 0,1
    return dx, dy


    def update_snake(new_pos, apple, game_field, snake1, snake2):
    snake1.append(new_pos)
    if new_pos == apple:
    apple = get_apple(width, height, snake1, snake2)
    game_field = draw(game_field, apple, '-')
    else:
    game_field = draw(game_field, snake1[0], ' ')
    del snake1[0]
    return snake1, apple, game_field


    # init
    width, height = 20, 20
    snake1 = [[0,0],[0,1]]
    snake2 = [[width/2, 0], [width/2 + 1, 0]]

    apple = get_apple(width, height, snake1, snake2)
    dx1, dy1 = 0, 1
    dx2, dy2 = 0, 1

    os.system('cls' if os.name == 'nt' else 'clear')
    # this allows '33[H' to work
    colorama.init()

    # draw inital positions of the field
    game_field = [height*[' '] for i in range(width)]
    for i in snake1:
    game_field = draw(game_field, i, 'x')
    for i in snake2:
    game_field = draw(game_field, i, '+')

    game_field = draw(game_field, snake1[-1], 'O')
    game_field = draw(game_field, snake2[-1], 'O')
    game_field = draw(game_field, apple, '-')

    prev_time = time.time()
    while True:
    try:
    fps = float(input('What framerate would you like to run at?'))
    break
    except:
    print('Please input a number')

    os.system('cls' if os.name == 'nt' else 'clear')

    while True:
    # check inputs from players
    dx1, dy1 = check_movement(dx1, dy1, 'w', 'a', 's', 'd')
    dx2, dy2 = check_movement(dx2, dy2, 'i', 'j', 'k', 'l')

    # update screen if enough time has passed
    if time.time() - prev_time > 1./fps:
    prev_time = time.time()
    print_game(game_field)
    new_pos1 = change_pos(snake1[-1], width, height, dx1, dy1)
    new_pos2 = change_pos(snake2[-1], width, height, dx2, dy2)

    # update snakes and playing field
    check_die(snake1, snake2)

    snake1, apple, game_field = update_snake(new_pos1, apple, game_field, snake1, snake2)
    snake2, apple, game_field = update_snake(new_pos2, apple, game_field, snake2, snake1)

    game_field = move_snake(game_field, snake1, 'x')
    game_field = move_snake(game_field, snake2, '+')






    share|improve this question























      up vote
      5
      down vote

      favorite









      up vote
      5
      down vote

      favorite











      I've been working on a two player snake game in python. I would really appreciate any suggestions/general input that would help improve my coding.



      """
      5-13-2018
      Nathan van 't Hof
      2 player snake game
      players can move around using wasd (player 1) and ijkl (player 2)
      eating apples: - will increase players length
      """

      import random
      import keyboard
      import os
      import colorama
      import time

      def get_apple(width, height, snake1, snake2):
      """
      find new random coordinate to place the apple
      """
      if len(snake1) + len(snake2) >= width*height:
      u = raw_input('You win!')
      quit()
      while True:
      apple = [random.randint(0, width - 1), random.randint(0, height-1)]
      if apple not in snake1 and apple not in snake2:
      return apple


      def draw(game_field, i, key):
      """
      change a specific coordinate of the game field
      to a different type
      """
      game_field[i[0]][i[1]] = key
      return game_field


      def move_snake(game_field, snake, typ):
      """
      move the snake one step
      """
      game_field = draw(game_field, snake[-2], typ)
      game_field = draw(game_field, snake[-1], 'O')
      return game_field


      def change_pos(prev, width, height, dx, dy):
      """
      change the coordinate of the head of the snake
      """
      return [(prev[0]+dx)%width, (prev[1]+dy)%height]


      def print_game(game_field):
      """
      print the game (in a readable format)
      """
      output = '.' * ((len(game_field)) * 2 + 1) + 'n'
      for i in game_field:
      output += '.' + ' '.join(i) + '.' + 'n'
      output += '.' * ((len(game_field)) * 2 + 1)
      # moves the marker to the top to prevent flikkering
      print('33[H' + output)


      def check_die(snake1, snake2):
      """
      check whether the snakes 'die'
      by letting the head bump into a tail/other head
      """
      dead = False
      if snake1[-1] == snake2[-1]:
      u = raw_input('Both snakes died')
      dead = True
      elif snake1[-1] in snake2 or snake1.count(snake1[-1]) >= 2:
      u = raw_input('Snake 1 died')
      dead = True
      elif snake2[-1] in snake1 or snake2.count(snake2[-1]) >= 2:
      u = raw_input('Snake 2 died')
      dead = True
      if dead:
      quit()


      def check_movement(dx, dy, ch1, ch2, ch3, ch4):
      """
      check where the snake moves
      """
      if keyboard.is_pressed(ch1) and dx != -1 and dy != 0:
      return -1, 0
      if keyboard.is_pressed(ch2) and dx != 0 and dy != -1:
      return 0, -1
      if keyboard.is_pressed(ch3) and dx != 1 and dy != 0:
      return 1, 0
      if keyboard.is_pressed(ch4) and dx != 0 and dy != 1:
      return 0,1
      return dx, dy


      def update_snake(new_pos, apple, game_field, snake1, snake2):
      snake1.append(new_pos)
      if new_pos == apple:
      apple = get_apple(width, height, snake1, snake2)
      game_field = draw(game_field, apple, '-')
      else:
      game_field = draw(game_field, snake1[0], ' ')
      del snake1[0]
      return snake1, apple, game_field


      # init
      width, height = 20, 20
      snake1 = [[0,0],[0,1]]
      snake2 = [[width/2, 0], [width/2 + 1, 0]]

      apple = get_apple(width, height, snake1, snake2)
      dx1, dy1 = 0, 1
      dx2, dy2 = 0, 1

      os.system('cls' if os.name == 'nt' else 'clear')
      # this allows '33[H' to work
      colorama.init()

      # draw inital positions of the field
      game_field = [height*[' '] for i in range(width)]
      for i in snake1:
      game_field = draw(game_field, i, 'x')
      for i in snake2:
      game_field = draw(game_field, i, '+')

      game_field = draw(game_field, snake1[-1], 'O')
      game_field = draw(game_field, snake2[-1], 'O')
      game_field = draw(game_field, apple, '-')

      prev_time = time.time()
      while True:
      try:
      fps = float(input('What framerate would you like to run at?'))
      break
      except:
      print('Please input a number')

      os.system('cls' if os.name == 'nt' else 'clear')

      while True:
      # check inputs from players
      dx1, dy1 = check_movement(dx1, dy1, 'w', 'a', 's', 'd')
      dx2, dy2 = check_movement(dx2, dy2, 'i', 'j', 'k', 'l')

      # update screen if enough time has passed
      if time.time() - prev_time > 1./fps:
      prev_time = time.time()
      print_game(game_field)
      new_pos1 = change_pos(snake1[-1], width, height, dx1, dy1)
      new_pos2 = change_pos(snake2[-1], width, height, dx2, dy2)

      # update snakes and playing field
      check_die(snake1, snake2)

      snake1, apple, game_field = update_snake(new_pos1, apple, game_field, snake1, snake2)
      snake2, apple, game_field = update_snake(new_pos2, apple, game_field, snake2, snake1)

      game_field = move_snake(game_field, snake1, 'x')
      game_field = move_snake(game_field, snake2, '+')






      share|improve this question













      I've been working on a two player snake game in python. I would really appreciate any suggestions/general input that would help improve my coding.



      """
      5-13-2018
      Nathan van 't Hof
      2 player snake game
      players can move around using wasd (player 1) and ijkl (player 2)
      eating apples: - will increase players length
      """

      import random
      import keyboard
      import os
      import colorama
      import time

      def get_apple(width, height, snake1, snake2):
      """
      find new random coordinate to place the apple
      """
      if len(snake1) + len(snake2) >= width*height:
      u = raw_input('You win!')
      quit()
      while True:
      apple = [random.randint(0, width - 1), random.randint(0, height-1)]
      if apple not in snake1 and apple not in snake2:
      return apple


      def draw(game_field, i, key):
      """
      change a specific coordinate of the game field
      to a different type
      """
      game_field[i[0]][i[1]] = key
      return game_field


      def move_snake(game_field, snake, typ):
      """
      move the snake one step
      """
      game_field = draw(game_field, snake[-2], typ)
      game_field = draw(game_field, snake[-1], 'O')
      return game_field


      def change_pos(prev, width, height, dx, dy):
      """
      change the coordinate of the head of the snake
      """
      return [(prev[0]+dx)%width, (prev[1]+dy)%height]


      def print_game(game_field):
      """
      print the game (in a readable format)
      """
      output = '.' * ((len(game_field)) * 2 + 1) + 'n'
      for i in game_field:
      output += '.' + ' '.join(i) + '.' + 'n'
      output += '.' * ((len(game_field)) * 2 + 1)
      # moves the marker to the top to prevent flikkering
      print('33[H' + output)


      def check_die(snake1, snake2):
      """
      check whether the snakes 'die'
      by letting the head bump into a tail/other head
      """
      dead = False
      if snake1[-1] == snake2[-1]:
      u = raw_input('Both snakes died')
      dead = True
      elif snake1[-1] in snake2 or snake1.count(snake1[-1]) >= 2:
      u = raw_input('Snake 1 died')
      dead = True
      elif snake2[-1] in snake1 or snake2.count(snake2[-1]) >= 2:
      u = raw_input('Snake 2 died')
      dead = True
      if dead:
      quit()


      def check_movement(dx, dy, ch1, ch2, ch3, ch4):
      """
      check where the snake moves
      """
      if keyboard.is_pressed(ch1) and dx != -1 and dy != 0:
      return -1, 0
      if keyboard.is_pressed(ch2) and dx != 0 and dy != -1:
      return 0, -1
      if keyboard.is_pressed(ch3) and dx != 1 and dy != 0:
      return 1, 0
      if keyboard.is_pressed(ch4) and dx != 0 and dy != 1:
      return 0,1
      return dx, dy


      def update_snake(new_pos, apple, game_field, snake1, snake2):
      snake1.append(new_pos)
      if new_pos == apple:
      apple = get_apple(width, height, snake1, snake2)
      game_field = draw(game_field, apple, '-')
      else:
      game_field = draw(game_field, snake1[0], ' ')
      del snake1[0]
      return snake1, apple, game_field


      # init
      width, height = 20, 20
      snake1 = [[0,0],[0,1]]
      snake2 = [[width/2, 0], [width/2 + 1, 0]]

      apple = get_apple(width, height, snake1, snake2)
      dx1, dy1 = 0, 1
      dx2, dy2 = 0, 1

      os.system('cls' if os.name == 'nt' else 'clear')
      # this allows '33[H' to work
      colorama.init()

      # draw inital positions of the field
      game_field = [height*[' '] for i in range(width)]
      for i in snake1:
      game_field = draw(game_field, i, 'x')
      for i in snake2:
      game_field = draw(game_field, i, '+')

      game_field = draw(game_field, snake1[-1], 'O')
      game_field = draw(game_field, snake2[-1], 'O')
      game_field = draw(game_field, apple, '-')

      prev_time = time.time()
      while True:
      try:
      fps = float(input('What framerate would you like to run at?'))
      break
      except:
      print('Please input a number')

      os.system('cls' if os.name == 'nt' else 'clear')

      while True:
      # check inputs from players
      dx1, dy1 = check_movement(dx1, dy1, 'w', 'a', 's', 'd')
      dx2, dy2 = check_movement(dx2, dy2, 'i', 'j', 'k', 'l')

      # update screen if enough time has passed
      if time.time() - prev_time > 1./fps:
      prev_time = time.time()
      print_game(game_field)
      new_pos1 = change_pos(snake1[-1], width, height, dx1, dy1)
      new_pos2 = change_pos(snake2[-1], width, height, dx2, dy2)

      # update snakes and playing field
      check_die(snake1, snake2)

      snake1, apple, game_field = update_snake(new_pos1, apple, game_field, snake1, snake2)
      snake2, apple, game_field = update_snake(new_pos2, apple, game_field, snake2, snake1)

      game_field = move_snake(game_field, snake1, 'x')
      game_field = move_snake(game_field, snake2, '+')








      share|improve this question












      share|improve this question




      share|improve this question








      edited May 13 at 15:03









      200_success

      123k14143399




      123k14143399









      asked May 13 at 13:49









      Nathan

      1555




      1555




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          3
          down vote



          accepted










          First impressions:



          Good work on the multiple platform attempt, however given that you are already using colorama and raw ansi codes (i.e. 33[H), there exist one for clearing the screen which should be used instead of os.system. Also while at it, avoid duplicating code, i.e. wrap repeated calls in a function, something like:



          def clear_screen():
          print('33[1J33[1;1H')


          That clears the screen (33[1J) and place the cursor on top-left of the terminal (33[1;1H). (diff)



          The other thing that should be done is wrap the main game in a function (from # init and down), and call it inside the if __name__ == '__main__': section.



          However, once moving everything inside we find that the update_snake function is now broken. This is caused by that function not actually taking in the width and height arguments like you had structure your program. Not a big deal, just add them as appropriate (so that it is not coupled to the value defined for the module):



          def update_snake(new_pos, apple, game_field, width, height, snake1, snake2):


          Fix the function call as appropriate with the same signature. I just followed the ordering that you used for get_apple. (diff)



          Another thing that jumped out (when running this under Linux) is that the root user is required for the keyboard module. This might be why the question didn't really get looked at, as that's the administrator account. The curses module may be more appropriate, however Windows user will require an additional wheel to be installed. As a bonus, ncurses provides a lot more flexibility with drawing on the terminal, it is something good to get familiar with. (doing cross-platform terminal interaction (or anything) can be surprisingly painful, so this is not a fault of your program, rather, I have to commend you for trying).



          You had used print as a function. Good, this brings up the Python 3 compatibility. However, this does not work under Python 3 as one might expect. First, width / 2 has the / (division) operator which no longer return an int if given floats as arguments. The // operator or the floor division operator should be used instead to get int for sure.



          snake2 = [[width//2, 0], [width//2 + 1, 0]]


          The other bit that could be problematic is towards the end of the game, where the call to raw_input crashes the game under Python 3. That is changed and since you are simply outputting a string, just use a print statement, and perhaps an input('') call at the end of that. While at it, normalise input to raw_input for Python 2 with a statement like this (diff):



          import sys

          if sys.version_info < (3,):
          input = raw_input


          I also wouldn't use quit() as that's for the interactive shell, you should use sys.exit instead. Ideally, random calls to abort the program shouldn't be invoked like so, but I am going to let this slide for now.



          Also the framerate conversion from input, for that matter any use of except should only trap the Exception class that is expected. Having it like that means the user cannot break out of the program with Ctrl-C even though they might want to terminate the program. It should only trap the ValueError exception (diff).



          While at that, we can also trap that on the main function, so invoking it might look like:



          if __name__ == '__main__':
          try:
          main()
          except KeyboardInterrupt:
          print('user exit')
          sys.exit(1)


          Although the keyboard library underneath has threads that might not terminate nicely like this, there is probably documentation in there that might reveal how that might be fixed, but in general this is a way to ensure that when user Ctrl-C on a program to terminate it, rather than showing them a big long traceback, a simple message will suffice.



          The code is pretty nicely formatted, almost fully PEP-8 compliant. Just remember to space out the symbols and operators for equations to improve readability.



          Other improvements I would do, but we are starting to make actual logic changes to the program. I had mentioned sprinkling random exits in functions isn't a good thing, as that needlessly couple the game logic to program lifecycle. Let's change the check_die function to return the exit string if a death happened, and an empty string if no death happened.



          def check_die(snake1, snake2):
          if snake1[-1] == snake2[-1]:
          return 'Both snakes died'
          # and so on ...


          In the game loop (diff)



          # update snakes and playing field
          death = check_die(snake1, snake2)
          if death:
          print(death)
          input('') # capture input characters in buffer
          return


          As for the get_apple, the while loop in there is going to be a massive liability for performance once you have really good players - the game will slow down as it tries to randomly find the last remaining empty spots. I would use random.choice on the tiles that are not currently occupied by a snake... while this map isn't available since empty tiles are not being tracked, generating that should be acceptable, though it will take some time, it may not matter too much. One last thing though, we can't exactly return a value for winning given how get_apple is used (two consecutive calls for each snake). One can use an Exception to signify that there is no valid spot remaining.



          try:
          snake1, apple, game_field = update_snake(
          new_pos1, apple, game_field, width, height, snake1, snake2)
          snake2, apple, game_field = update_snake(
          new_pos2, apple, game_field, width, height, snake2, snake1)
          except NoSpaceForApple:
          print('You win!')
          input('') # capture input characters in buffer
          return


          Finally, to better present what was done here, I created a gist that shows all the changes as a complete program. The reason why I used a gist is that the way they show revisions is very useful in showing how the corrections progressed. You may have noticed this already with the diff links that I've added, but you should be able to follow everything from bottom up, or git clone https://gist.github.com/7eb66780a852ee6e32862ba4ee896b61 should get you a copy. By the way, if you don't know git (or other version control) you should start using it, because you can make use of it like so to show how the program changes over time by the fixes done to it.



          Verdict: This is a really neat little game, you really invoke my nostalgia (back when I wrote things in DOS) and I give you props for that.






          share|improve this answer























          • @Nathan I am glad that you appreciated it. Have you found the gist with the revisions useful?
            – metatoaster
            May 17 at 2:58










          • Definitely, showing exactly what you mean makes understanding the differences really easy. I'll be sure to start using git as well.
            – Nathan
            May 17 at 5:53










          • @Nathan Excellent!
            – metatoaster
            May 17 at 5:53










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194311%2fsimple-two-player-snake-game%23new-answer', 'question_page');

          );

          Post as a guest






























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          3
          down vote



          accepted










          First impressions:



          Good work on the multiple platform attempt, however given that you are already using colorama and raw ansi codes (i.e. 33[H), there exist one for clearing the screen which should be used instead of os.system. Also while at it, avoid duplicating code, i.e. wrap repeated calls in a function, something like:



          def clear_screen():
          print('33[1J33[1;1H')


          That clears the screen (33[1J) and place the cursor on top-left of the terminal (33[1;1H). (diff)



          The other thing that should be done is wrap the main game in a function (from # init and down), and call it inside the if __name__ == '__main__': section.



          However, once moving everything inside we find that the update_snake function is now broken. This is caused by that function not actually taking in the width and height arguments like you had structure your program. Not a big deal, just add them as appropriate (so that it is not coupled to the value defined for the module):



          def update_snake(new_pos, apple, game_field, width, height, snake1, snake2):


          Fix the function call as appropriate with the same signature. I just followed the ordering that you used for get_apple. (diff)



          Another thing that jumped out (when running this under Linux) is that the root user is required for the keyboard module. This might be why the question didn't really get looked at, as that's the administrator account. The curses module may be more appropriate, however Windows user will require an additional wheel to be installed. As a bonus, ncurses provides a lot more flexibility with drawing on the terminal, it is something good to get familiar with. (doing cross-platform terminal interaction (or anything) can be surprisingly painful, so this is not a fault of your program, rather, I have to commend you for trying).



          You had used print as a function. Good, this brings up the Python 3 compatibility. However, this does not work under Python 3 as one might expect. First, width / 2 has the / (division) operator which no longer return an int if given floats as arguments. The // operator or the floor division operator should be used instead to get int for sure.



          snake2 = [[width//2, 0], [width//2 + 1, 0]]


          The other bit that could be problematic is towards the end of the game, where the call to raw_input crashes the game under Python 3. That is changed and since you are simply outputting a string, just use a print statement, and perhaps an input('') call at the end of that. While at it, normalise input to raw_input for Python 2 with a statement like this (diff):



          import sys

          if sys.version_info < (3,):
          input = raw_input


          I also wouldn't use quit() as that's for the interactive shell, you should use sys.exit instead. Ideally, random calls to abort the program shouldn't be invoked like so, but I am going to let this slide for now.



          Also the framerate conversion from input, for that matter any use of except should only trap the Exception class that is expected. Having it like that means the user cannot break out of the program with Ctrl-C even though they might want to terminate the program. It should only trap the ValueError exception (diff).



          While at that, we can also trap that on the main function, so invoking it might look like:



          if __name__ == '__main__':
          try:
          main()
          except KeyboardInterrupt:
          print('user exit')
          sys.exit(1)


          Although the keyboard library underneath has threads that might not terminate nicely like this, there is probably documentation in there that might reveal how that might be fixed, but in general this is a way to ensure that when user Ctrl-C on a program to terminate it, rather than showing them a big long traceback, a simple message will suffice.



          The code is pretty nicely formatted, almost fully PEP-8 compliant. Just remember to space out the symbols and operators for equations to improve readability.



          Other improvements I would do, but we are starting to make actual logic changes to the program. I had mentioned sprinkling random exits in functions isn't a good thing, as that needlessly couple the game logic to program lifecycle. Let's change the check_die function to return the exit string if a death happened, and an empty string if no death happened.



          def check_die(snake1, snake2):
          if snake1[-1] == snake2[-1]:
          return 'Both snakes died'
          # and so on ...


          In the game loop (diff)



          # update snakes and playing field
          death = check_die(snake1, snake2)
          if death:
          print(death)
          input('') # capture input characters in buffer
          return


          As for the get_apple, the while loop in there is going to be a massive liability for performance once you have really good players - the game will slow down as it tries to randomly find the last remaining empty spots. I would use random.choice on the tiles that are not currently occupied by a snake... while this map isn't available since empty tiles are not being tracked, generating that should be acceptable, though it will take some time, it may not matter too much. One last thing though, we can't exactly return a value for winning given how get_apple is used (two consecutive calls for each snake). One can use an Exception to signify that there is no valid spot remaining.



          try:
          snake1, apple, game_field = update_snake(
          new_pos1, apple, game_field, width, height, snake1, snake2)
          snake2, apple, game_field = update_snake(
          new_pos2, apple, game_field, width, height, snake2, snake1)
          except NoSpaceForApple:
          print('You win!')
          input('') # capture input characters in buffer
          return


          Finally, to better present what was done here, I created a gist that shows all the changes as a complete program. The reason why I used a gist is that the way they show revisions is very useful in showing how the corrections progressed. You may have noticed this already with the diff links that I've added, but you should be able to follow everything from bottom up, or git clone https://gist.github.com/7eb66780a852ee6e32862ba4ee896b61 should get you a copy. By the way, if you don't know git (or other version control) you should start using it, because you can make use of it like so to show how the program changes over time by the fixes done to it.



          Verdict: This is a really neat little game, you really invoke my nostalgia (back when I wrote things in DOS) and I give you props for that.






          share|improve this answer























          • @Nathan I am glad that you appreciated it. Have you found the gist with the revisions useful?
            – metatoaster
            May 17 at 2:58










          • Definitely, showing exactly what you mean makes understanding the differences really easy. I'll be sure to start using git as well.
            – Nathan
            May 17 at 5:53










          • @Nathan Excellent!
            – metatoaster
            May 17 at 5:53














          up vote
          3
          down vote



          accepted










          First impressions:



          Good work on the multiple platform attempt, however given that you are already using colorama and raw ansi codes (i.e. 33[H), there exist one for clearing the screen which should be used instead of os.system. Also while at it, avoid duplicating code, i.e. wrap repeated calls in a function, something like:



          def clear_screen():
          print('33[1J33[1;1H')


          That clears the screen (33[1J) and place the cursor on top-left of the terminal (33[1;1H). (diff)



          The other thing that should be done is wrap the main game in a function (from # init and down), and call it inside the if __name__ == '__main__': section.



          However, once moving everything inside we find that the update_snake function is now broken. This is caused by that function not actually taking in the width and height arguments like you had structure your program. Not a big deal, just add them as appropriate (so that it is not coupled to the value defined for the module):



          def update_snake(new_pos, apple, game_field, width, height, snake1, snake2):


          Fix the function call as appropriate with the same signature. I just followed the ordering that you used for get_apple. (diff)



          Another thing that jumped out (when running this under Linux) is that the root user is required for the keyboard module. This might be why the question didn't really get looked at, as that's the administrator account. The curses module may be more appropriate, however Windows user will require an additional wheel to be installed. As a bonus, ncurses provides a lot more flexibility with drawing on the terminal, it is something good to get familiar with. (doing cross-platform terminal interaction (or anything) can be surprisingly painful, so this is not a fault of your program, rather, I have to commend you for trying).



          You had used print as a function. Good, this brings up the Python 3 compatibility. However, this does not work under Python 3 as one might expect. First, width / 2 has the / (division) operator which no longer return an int if given floats as arguments. The // operator or the floor division operator should be used instead to get int for sure.



          snake2 = [[width//2, 0], [width//2 + 1, 0]]


          The other bit that could be problematic is towards the end of the game, where the call to raw_input crashes the game under Python 3. That is changed and since you are simply outputting a string, just use a print statement, and perhaps an input('') call at the end of that. While at it, normalise input to raw_input for Python 2 with a statement like this (diff):



          import sys

          if sys.version_info < (3,):
          input = raw_input


          I also wouldn't use quit() as that's for the interactive shell, you should use sys.exit instead. Ideally, random calls to abort the program shouldn't be invoked like so, but I am going to let this slide for now.



          Also the framerate conversion from input, for that matter any use of except should only trap the Exception class that is expected. Having it like that means the user cannot break out of the program with Ctrl-C even though they might want to terminate the program. It should only trap the ValueError exception (diff).



          While at that, we can also trap that on the main function, so invoking it might look like:



          if __name__ == '__main__':
          try:
          main()
          except KeyboardInterrupt:
          print('user exit')
          sys.exit(1)


          Although the keyboard library underneath has threads that might not terminate nicely like this, there is probably documentation in there that might reveal how that might be fixed, but in general this is a way to ensure that when user Ctrl-C on a program to terminate it, rather than showing them a big long traceback, a simple message will suffice.



          The code is pretty nicely formatted, almost fully PEP-8 compliant. Just remember to space out the symbols and operators for equations to improve readability.



          Other improvements I would do, but we are starting to make actual logic changes to the program. I had mentioned sprinkling random exits in functions isn't a good thing, as that needlessly couple the game logic to program lifecycle. Let's change the check_die function to return the exit string if a death happened, and an empty string if no death happened.



          def check_die(snake1, snake2):
          if snake1[-1] == snake2[-1]:
          return 'Both snakes died'
          # and so on ...


          In the game loop (diff)



          # update snakes and playing field
          death = check_die(snake1, snake2)
          if death:
          print(death)
          input('') # capture input characters in buffer
          return


          As for the get_apple, the while loop in there is going to be a massive liability for performance once you have really good players - the game will slow down as it tries to randomly find the last remaining empty spots. I would use random.choice on the tiles that are not currently occupied by a snake... while this map isn't available since empty tiles are not being tracked, generating that should be acceptable, though it will take some time, it may not matter too much. One last thing though, we can't exactly return a value for winning given how get_apple is used (two consecutive calls for each snake). One can use an Exception to signify that there is no valid spot remaining.



          try:
          snake1, apple, game_field = update_snake(
          new_pos1, apple, game_field, width, height, snake1, snake2)
          snake2, apple, game_field = update_snake(
          new_pos2, apple, game_field, width, height, snake2, snake1)
          except NoSpaceForApple:
          print('You win!')
          input('') # capture input characters in buffer
          return


          Finally, to better present what was done here, I created a gist that shows all the changes as a complete program. The reason why I used a gist is that the way they show revisions is very useful in showing how the corrections progressed. You may have noticed this already with the diff links that I've added, but you should be able to follow everything from bottom up, or git clone https://gist.github.com/7eb66780a852ee6e32862ba4ee896b61 should get you a copy. By the way, if you don't know git (or other version control) you should start using it, because you can make use of it like so to show how the program changes over time by the fixes done to it.



          Verdict: This is a really neat little game, you really invoke my nostalgia (back when I wrote things in DOS) and I give you props for that.






          share|improve this answer























          • @Nathan I am glad that you appreciated it. Have you found the gist with the revisions useful?
            – metatoaster
            May 17 at 2:58










          • Definitely, showing exactly what you mean makes understanding the differences really easy. I'll be sure to start using git as well.
            – Nathan
            May 17 at 5:53










          • @Nathan Excellent!
            – metatoaster
            May 17 at 5:53












          up vote
          3
          down vote



          accepted







          up vote
          3
          down vote



          accepted






          First impressions:



          Good work on the multiple platform attempt, however given that you are already using colorama and raw ansi codes (i.e. 33[H), there exist one for clearing the screen which should be used instead of os.system. Also while at it, avoid duplicating code, i.e. wrap repeated calls in a function, something like:



          def clear_screen():
          print('33[1J33[1;1H')


          That clears the screen (33[1J) and place the cursor on top-left of the terminal (33[1;1H). (diff)



          The other thing that should be done is wrap the main game in a function (from # init and down), and call it inside the if __name__ == '__main__': section.



          However, once moving everything inside we find that the update_snake function is now broken. This is caused by that function not actually taking in the width and height arguments like you had structure your program. Not a big deal, just add them as appropriate (so that it is not coupled to the value defined for the module):



          def update_snake(new_pos, apple, game_field, width, height, snake1, snake2):


          Fix the function call as appropriate with the same signature. I just followed the ordering that you used for get_apple. (diff)



          Another thing that jumped out (when running this under Linux) is that the root user is required for the keyboard module. This might be why the question didn't really get looked at, as that's the administrator account. The curses module may be more appropriate, however Windows user will require an additional wheel to be installed. As a bonus, ncurses provides a lot more flexibility with drawing on the terminal, it is something good to get familiar with. (doing cross-platform terminal interaction (or anything) can be surprisingly painful, so this is not a fault of your program, rather, I have to commend you for trying).



          You had used print as a function. Good, this brings up the Python 3 compatibility. However, this does not work under Python 3 as one might expect. First, width / 2 has the / (division) operator which no longer return an int if given floats as arguments. The // operator or the floor division operator should be used instead to get int for sure.



          snake2 = [[width//2, 0], [width//2 + 1, 0]]


          The other bit that could be problematic is towards the end of the game, where the call to raw_input crashes the game under Python 3. That is changed and since you are simply outputting a string, just use a print statement, and perhaps an input('') call at the end of that. While at it, normalise input to raw_input for Python 2 with a statement like this (diff):



          import sys

          if sys.version_info < (3,):
          input = raw_input


          I also wouldn't use quit() as that's for the interactive shell, you should use sys.exit instead. Ideally, random calls to abort the program shouldn't be invoked like so, but I am going to let this slide for now.



          Also the framerate conversion from input, for that matter any use of except should only trap the Exception class that is expected. Having it like that means the user cannot break out of the program with Ctrl-C even though they might want to terminate the program. It should only trap the ValueError exception (diff).



          While at that, we can also trap that on the main function, so invoking it might look like:



          if __name__ == '__main__':
          try:
          main()
          except KeyboardInterrupt:
          print('user exit')
          sys.exit(1)


          Although the keyboard library underneath has threads that might not terminate nicely like this, there is probably documentation in there that might reveal how that might be fixed, but in general this is a way to ensure that when user Ctrl-C on a program to terminate it, rather than showing them a big long traceback, a simple message will suffice.



          The code is pretty nicely formatted, almost fully PEP-8 compliant. Just remember to space out the symbols and operators for equations to improve readability.



          Other improvements I would do, but we are starting to make actual logic changes to the program. I had mentioned sprinkling random exits in functions isn't a good thing, as that needlessly couple the game logic to program lifecycle. Let's change the check_die function to return the exit string if a death happened, and an empty string if no death happened.



          def check_die(snake1, snake2):
          if snake1[-1] == snake2[-1]:
          return 'Both snakes died'
          # and so on ...


          In the game loop (diff)



          # update snakes and playing field
          death = check_die(snake1, snake2)
          if death:
          print(death)
          input('') # capture input characters in buffer
          return


          As for the get_apple, the while loop in there is going to be a massive liability for performance once you have really good players - the game will slow down as it tries to randomly find the last remaining empty spots. I would use random.choice on the tiles that are not currently occupied by a snake... while this map isn't available since empty tiles are not being tracked, generating that should be acceptable, though it will take some time, it may not matter too much. One last thing though, we can't exactly return a value for winning given how get_apple is used (two consecutive calls for each snake). One can use an Exception to signify that there is no valid spot remaining.



          try:
          snake1, apple, game_field = update_snake(
          new_pos1, apple, game_field, width, height, snake1, snake2)
          snake2, apple, game_field = update_snake(
          new_pos2, apple, game_field, width, height, snake2, snake1)
          except NoSpaceForApple:
          print('You win!')
          input('') # capture input characters in buffer
          return


          Finally, to better present what was done here, I created a gist that shows all the changes as a complete program. The reason why I used a gist is that the way they show revisions is very useful in showing how the corrections progressed. You may have noticed this already with the diff links that I've added, but you should be able to follow everything from bottom up, or git clone https://gist.github.com/7eb66780a852ee6e32862ba4ee896b61 should get you a copy. By the way, if you don't know git (or other version control) you should start using it, because you can make use of it like so to show how the program changes over time by the fixes done to it.



          Verdict: This is a really neat little game, you really invoke my nostalgia (back when I wrote things in DOS) and I give you props for that.






          share|improve this answer















          First impressions:



          Good work on the multiple platform attempt, however given that you are already using colorama and raw ansi codes (i.e. 33[H), there exist one for clearing the screen which should be used instead of os.system. Also while at it, avoid duplicating code, i.e. wrap repeated calls in a function, something like:



          def clear_screen():
          print('33[1J33[1;1H')


          That clears the screen (33[1J) and place the cursor on top-left of the terminal (33[1;1H). (diff)



          The other thing that should be done is wrap the main game in a function (from # init and down), and call it inside the if __name__ == '__main__': section.



          However, once moving everything inside we find that the update_snake function is now broken. This is caused by that function not actually taking in the width and height arguments like you had structure your program. Not a big deal, just add them as appropriate (so that it is not coupled to the value defined for the module):



          def update_snake(new_pos, apple, game_field, width, height, snake1, snake2):


          Fix the function call as appropriate with the same signature. I just followed the ordering that you used for get_apple. (diff)



          Another thing that jumped out (when running this under Linux) is that the root user is required for the keyboard module. This might be why the question didn't really get looked at, as that's the administrator account. The curses module may be more appropriate, however Windows user will require an additional wheel to be installed. As a bonus, ncurses provides a lot more flexibility with drawing on the terminal, it is something good to get familiar with. (doing cross-platform terminal interaction (or anything) can be surprisingly painful, so this is not a fault of your program, rather, I have to commend you for trying).



          You had used print as a function. Good, this brings up the Python 3 compatibility. However, this does not work under Python 3 as one might expect. First, width / 2 has the / (division) operator which no longer return an int if given floats as arguments. The // operator or the floor division operator should be used instead to get int for sure.



          snake2 = [[width//2, 0], [width//2 + 1, 0]]


          The other bit that could be problematic is towards the end of the game, where the call to raw_input crashes the game under Python 3. That is changed and since you are simply outputting a string, just use a print statement, and perhaps an input('') call at the end of that. While at it, normalise input to raw_input for Python 2 with a statement like this (diff):



          import sys

          if sys.version_info < (3,):
          input = raw_input


          I also wouldn't use quit() as that's for the interactive shell, you should use sys.exit instead. Ideally, random calls to abort the program shouldn't be invoked like so, but I am going to let this slide for now.



          Also the framerate conversion from input, for that matter any use of except should only trap the Exception class that is expected. Having it like that means the user cannot break out of the program with Ctrl-C even though they might want to terminate the program. It should only trap the ValueError exception (diff).



          While at that, we can also trap that on the main function, so invoking it might look like:



          if __name__ == '__main__':
          try:
          main()
          except KeyboardInterrupt:
          print('user exit')
          sys.exit(1)


          Although the keyboard library underneath has threads that might not terminate nicely like this, there is probably documentation in there that might reveal how that might be fixed, but in general this is a way to ensure that when user Ctrl-C on a program to terminate it, rather than showing them a big long traceback, a simple message will suffice.



          The code is pretty nicely formatted, almost fully PEP-8 compliant. Just remember to space out the symbols and operators for equations to improve readability.



          Other improvements I would do, but we are starting to make actual logic changes to the program. I had mentioned sprinkling random exits in functions isn't a good thing, as that needlessly couple the game logic to program lifecycle. Let's change the check_die function to return the exit string if a death happened, and an empty string if no death happened.



          def check_die(snake1, snake2):
          if snake1[-1] == snake2[-1]:
          return 'Both snakes died'
          # and so on ...


          In the game loop (diff)



          # update snakes and playing field
          death = check_die(snake1, snake2)
          if death:
          print(death)
          input('') # capture input characters in buffer
          return


          As for the get_apple, the while loop in there is going to be a massive liability for performance once you have really good players - the game will slow down as it tries to randomly find the last remaining empty spots. I would use random.choice on the tiles that are not currently occupied by a snake... while this map isn't available since empty tiles are not being tracked, generating that should be acceptable, though it will take some time, it may not matter too much. One last thing though, we can't exactly return a value for winning given how get_apple is used (two consecutive calls for each snake). One can use an Exception to signify that there is no valid spot remaining.



          try:
          snake1, apple, game_field = update_snake(
          new_pos1, apple, game_field, width, height, snake1, snake2)
          snake2, apple, game_field = update_snake(
          new_pos2, apple, game_field, width, height, snake2, snake1)
          except NoSpaceForApple:
          print('You win!')
          input('') # capture input characters in buffer
          return


          Finally, to better present what was done here, I created a gist that shows all the changes as a complete program. The reason why I used a gist is that the way they show revisions is very useful in showing how the corrections progressed. You may have noticed this already with the diff links that I've added, but you should be able to follow everything from bottom up, or git clone https://gist.github.com/7eb66780a852ee6e32862ba4ee896b61 should get you a copy. By the way, if you don't know git (or other version control) you should start using it, because you can make use of it like so to show how the program changes over time by the fixes done to it.



          Verdict: This is a really neat little game, you really invoke my nostalgia (back when I wrote things in DOS) and I give you props for that.







          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited May 14 at 13:49


























          answered May 14 at 13:05









          metatoaster

          23614




          23614











          • @Nathan I am glad that you appreciated it. Have you found the gist with the revisions useful?
            – metatoaster
            May 17 at 2:58










          • Definitely, showing exactly what you mean makes understanding the differences really easy. I'll be sure to start using git as well.
            – Nathan
            May 17 at 5:53










          • @Nathan Excellent!
            – metatoaster
            May 17 at 5:53
















          • @Nathan I am glad that you appreciated it. Have you found the gist with the revisions useful?
            – metatoaster
            May 17 at 2:58










          • Definitely, showing exactly what you mean makes understanding the differences really easy. I'll be sure to start using git as well.
            – Nathan
            May 17 at 5:53










          • @Nathan Excellent!
            – metatoaster
            May 17 at 5:53















          @Nathan I am glad that you appreciated it. Have you found the gist with the revisions useful?
          – metatoaster
          May 17 at 2:58




          @Nathan I am glad that you appreciated it. Have you found the gist with the revisions useful?
          – metatoaster
          May 17 at 2:58












          Definitely, showing exactly what you mean makes understanding the differences really easy. I'll be sure to start using git as well.
          – Nathan
          May 17 at 5:53




          Definitely, showing exactly what you mean makes understanding the differences really easy. I'll be sure to start using git as well.
          – Nathan
          May 17 at 5:53












          @Nathan Excellent!
          – metatoaster
          May 17 at 5:53




          @Nathan Excellent!
          – metatoaster
          May 17 at 5:53












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194311%2fsimple-two-player-snake-game%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Chat program with C++ and SFML

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          Will my employers contract hold up in court?