bsdgames worm, ported to Python and improved
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

253 lines
6.8 KiB

#!/usr/bin/env python3
import sys
import random
import time
import contextlib
import termios
import timeout_decorator
import os
import argparse
import json
import info
from blessings import Terminal
class QuitGameError(BaseException):
pass
class RanIntoSomethingError(QuitGameError):
pass
class IsFullScreen(BaseException):
pass
def draw_worm():
for location in worm_locations:
print(do_move(*reversed(location)) + term.bright_blue('o'), end='')
print(do_move(*reversed(worm_head)) + '@' + do_move(*reversed(worm_head)), end='')
def draw_frame():
print(term.clear, end='')
print(do_move(0, 0) + term.on_red(' worm') + term.bright_cyan_on_red('.py ') + ' Press ' + term.bold_green('I') + ' for info, ' + term.bold_red('Ctrl-C')+ ' to quit', end='')
if score > -1:
print(do_move(0, width-12) + f'Score:{" "*(4-len(str(score)))}{term.bright_green(str(score))}', end='')
print(do_move(1, 0) + term.white_on_red('' + ('' * (width-3)) + ''), end='')
for y in range(2, height-1):
print(do_move(y, 0) + term.white_on_red('' + do_move(y, width-2) + ''), end='')
print(
do_move(height-1, 0)
+ term.white_on_red('')
+ term.white_on_red('' * (width-3))
+ term.white_on_red('')
, end='')
print(do_move(*reversed(bonus_location)) + term.black_on_green(term.on_bright_green(str(bonus_points))), end='')
draw_worm()
sys.stdout.flush()
def move_head(direction, distance):
global size, worm_head, last_dir, worm_locations, bonus_location, bonus_points, score
if direction not in 'hjklHJKLABCDwasdx':
raise ValueError('argument to move_head() must be one of `hjklHJKLABCDwasdx`')
if direction == 'x':
return
worm_locations.append(worm_head.copy())
worm_head = worm_head.copy()
if direction in 'kKAw': #up
worm_head[1] -= 1
last_dir = 'A'
elif direction in 'jJBs': #down
worm_head[1] += 1
last_dir = 'B'
elif direction in 'hHDa': #left
worm_head[0] -= 1
last_dir = 'D'
elif direction in 'lLCd': #right
worm_head[0] += 1
last_dir = 'C'
while len(worm_locations) > size:
worm_locations.pop(0)
if worm_head in worm_locations \
or worm_head[1] <= 1 \
or worm_head[1] >= height-1 \
or worm_head[0] <= 0 \
or worm_head[0] >= width-2:
raise RanIntoSomethingError()
if worm_head == bonus_location:
size += bonus_points
score += bonus_points
new_bonus()
if distance > 1:
move_head(direction, distance-1)
@contextlib.contextmanager
def decanonize(fd):
old_settings = termios.tcgetattr(fd)
new_settings = old_settings[:]
new_settings[3] &= ~termios.ICANON
termios.tcsetattr(fd, termios.TCSAFLUSH, new_settings)
yield
termios.tcsetattr(fd, termios.TCSAFLUSH, old_settings)
@timeout_decorator.timeout(1)
def run(*_):
global do_automove, do_help
if do_automove:
move_head(last_dir, 1)
draw_frame()
do_automove = True
save_game()
k = await_keys('hjklHJKLABCDwasdiI')
if k in 'hjklHJKLABCDwasd':
move_head(k, 10 if k in 'HJKL' else 1)
draw_frame()
do_automove = False
return
elif k in '':
save_game()
sys.exit(0)
elif k in 'iI':
do_help = True
return
def await_keys(keys):
while True:
k = sys.stdin.read(1)
if k in keys:
return k
def new_bonus():
global bonus_location, bonus_points
bonus_points = random.randint(1, 9)
bonus_location = worm_head
while worm_head == bonus_location or bonus_location in worm_locations:
bonus_location = [
random.randint(1, width-3),
random.randint(2, height-2),
]
def do_move(y, x):
y += (term.height - height) // 2
x += (term.width - width) // 2
return term.move(y, x)
def save_game():
if not args.full_screen:
with open(gamesave_path, 'w+') as f:
json.dump([worm_locations, worm_head, last_dir, score, size, bonus_location, bonus_points, do_automove, do_help], f)
term = Terminal()
gamesave_path = os.path.join(os.getenv('HOME'), '.worm.py-gamesave')
parser = argparse.ArgumentParser()
parser.add_argument("--full-screen", '-f', help="use entire screen (disables game saving)", action='store_true')
parser.add_argument("--delete-save", help="delete saved game and exit", action='store_true')
args = parser.parse_args()
if args.delete_save:
try:
os.unlink(gamesave_path)
print("Saved game deleted.")
except FileNotFoundError:
print("No saved game found.")
except Exception as e:
print("An error occurred.")
print(str(type(e)).split("'")[1] + ': ' + str(e))
sys.exit(1)
sys.exit(0)
if not args.full_screen:
height = 24
width = 80
else:
height = term.height
width = term.width
worm_y = height // 2
x_offset = (term.width - width) // 2
info_1, info_2 = info.get_infos(term, do_move, height, width, x_offset)
try:
if args.full_screen:
raise IsFullScreen()
with open(gamesave_path) as f:
save = json.load(f)
worm_locations, worm_head, last_dir, score, size, bonus_location, bonus_points, do_automove, do_help = save
except (FileNotFoundError, IsFullScreen):
size = 7
worm_locations = [[i+10, worm_y] for i in range(size)]
worm_head = [size+10, worm_y]
last_dir = 'x'
score = 0
new_bonus()
do_automove = True
do_help = False
try:
with term.fullscreen():
os.system('stty raw -echo')
draw_frame()
while True:
if do_help:
for info in (info_1, info_2):
os.system('stty -raw')
print(info, end='')
sys.stdout.flush()
os.system('stty raw')
k = await_keys('cC')
if k in '':
save_game()
sys.exit(0)
do_help = False
else:
try:
run()
except timeout_decorator.timeout_decorator.TimeoutError:
pass
except KeyboardInterrupt:
save_game()
sys.exit(0)
except RanIntoSomethingError:
try:
if not args.full_screen:
os.unlink(gamesave_path)
except FileNotFoundError:
pass
os.system('stty -raw echo')
print('', end='\r\n')
if size + 1 >= (height-3) * (width-1):
print('You won!', end='\r\n')
else:
print('Well, you ran into something and the game is over.', end='\r\n')
print(f'Your final score was {score}', end='\r\n')
print('', end='\r\n')
finally:
os.system('stty -raw echo')