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.
 
 

312 lines
7.5 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
import stty
import shutil
import delete_save
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.black_on_bright_cyan(".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(prog="wormpy")
group = parser.add_mutually_exclusive_group()
group.add_argument(
"--save-game",
"-s",
help="use 80x24 box and save game on ^C (default)",
action="store_true",
default=True,
)
group.add_argument(
"--full-screen",
"-f",
help="use entire screen (disables game saving)",
action="store_true",
)
group.add_argument(
"--delete-save", help="delete saved game and exit", action="store_true"
)
args = parser.parse_args()
if args.delete_save:
delete_save.delete_save(gamesave_path)
elif args.save_game:
height = 24
width = 80
elif args.full_screen:
height = term.height
width = term.width
else:
print("An unhandleable error occurred.")
print("The code was not expected to reach this state.")
print("Please create an issue at https://github.com/kj7rrv/wormpy/issues")
sys.exit(2)
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():
stty.stty(raw=True, echo=False)
draw_frame()
while True:
if do_help:
for info in (info_1, info_2):
stty.stty(raw=False)
print(info, end="")
sys.stdout.flush()
stty.stty(raw=True)
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
stty.stty(raw=False, echo=True)
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:
stty.stty(raw=False, echo=True)