|
|
|
|
#!/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)
|