Simple two player snake game
Clash 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, '+')
python snake-game
add a comment |Â
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, '+')
python snake-game
add a comment |Â
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, '+')
python snake-game
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, '+')
python snake-game
edited May 13 at 15:03
200_success
123k14143399
123k14143399
asked May 13 at 13:49
Nathan
1555
1555
add a comment |Â
add a comment |Â
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 float
s 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.
@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
add a comment |Â
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 float
s 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.
@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
add a comment |Â
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 float
s 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.
@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
add a comment |Â
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 float
s 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.
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 float
s 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.
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
add a comment |Â
@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
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password