diff --git a/Makefile b/Makefile deleted file mode 100644 index a757d72..0000000 --- a/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -libworm.so: worm.c - gcc -c worm.c -l curses -fPIC - gcc -shared -o libworm.so worm.o -l curses - rm worm.o diff --git a/worm.c b/worm.c deleted file mode 100644 index 04e3a7b..0000000 --- a/worm.c +++ /dev/null @@ -1,371 +0,0 @@ -/* $NetBSD: worm.c,v 1.25 2004/01/27 20:30:31 jsm Exp $ */ - -/* - * Copyright (c) 1980, 1993 - * The Regents of the University of California. All rights reserved. - * - * Copyright (c) 2021 - * Samuel L Sloniker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* -#include -#ifndef lint -__COPYRIGHT("@(#) Copyright (c) 1980, 1993\n\ - The Regents of the University of California. All rights reserved.\n"); -#endif / * not lint * / - -#ifndef lint -#if 0 -static char sccsid[] = "@(#)worm.c 8.1 (Berkeley) 5/31/93"; -#else -__RCSID("$NetBSD: worm.c,v 1.25 2004/01/27 20:30:31 jsm Exp $"); -#endif -#endif / * not lint * / -*/ - -/* - * Worm. Written by Michael Toy - * UCSC - */ - -#include -#include -#include -#include -#include -#include -#include - -#define newlink() (struct body *) malloc(sizeof (struct body)); -#define HEAD '@' -#define BODY 'o' -#define LENGTH 7 -#define RUNLEN 8 -#define CNTRL(p) (p-'A'+1) - -WINDOW *tv; -WINDOW *stw; -struct body { - int x; - int y; - struct body *prev; - struct body *next; -} *head, *tail, goody; -int growing = 0; -int running = 0; -int slow = 0; -int score = 0; -int start_len = LENGTH; -int visible_len; -int lastch; -char outbuf[BUFSIZ]; - -void crash(void) __attribute__((__noreturn__)); -void display(const struct body *, char); -void set_size(int); -int run_worm(); -void leave(int) __attribute__((__noreturn__)); -void life(void); -void newpos(struct body *); -void process(int); -void prize(void); -int rnd(int); -void setup(void); -void wake(int); - -void set_size(int sl) { - start_len = sl; -} - -int run_worm() { - /* Revoke setgid privileges */ - setregid(getgid(), getgid()); - - setbuf(stdout, outbuf); - srand(getpid()); - signal(SIGALRM, wake); - signal(SIGINT, leave); - signal(SIGQUIT, leave); - initscr(); - cbreak(); - noecho(); -#ifdef KEY_LEFT - keypad(stdscr, TRUE); -#endif - slow = (baudrate() <= 1200); - clear(); - if (COLS < 18 || LINES < 5) { - /* - * Insufficient room for the line with " Worm" and the - * score if fewer than 18 columns; insufficient room for - * anything much if fewer than 5 lines. - */ - endwin(); - errx(1, "screen too small"); - } - stw = newwin(1, COLS-1, 0, 0); - tv = newwin(LINES-1, COLS-1, 1, 0); - box(tv, '*', '*'); - scrollok(tv, FALSE); - scrollok(stw, FALSE); - wmove(stw, 0, 0); - wprintw(stw, " Worm"); - refresh(); - wrefresh(stw); - wrefresh(tv); - life(); /* Create the worm */ - prize(); /* Put up a goal */ - while(1) - { - if (running) - { - running--; - process(lastch); - } - else - { - fflush(stdout); - process(getch()); - } - } -} - -void -life() -{ - struct body *bp, *np; - int i, j = 1; - - np = NULL; - head = newlink(); - if (head == NULL) - err(1, NULL); - head->x = start_len % (COLS-5) + 2; - head->y = LINES / 2; - head->next = NULL; - display(head, HEAD); - for (i = 0, bp = head; i < start_len; i++, bp = np) { - np = newlink(); - if (np == NULL) - err(1, NULL); - np->next = bp; - bp->prev = np; - if (((bp->x <= 2) && (j == 1)) || ((bp->x >= COLS-4) && (j == -1))) { - j *= -1; - np->x = bp->x; - np->y = bp->y + 1; - } else { - np->x = bp->x - j; - np->y = bp->y; - } - display(np, BODY); - } - tail = np; - tail->prev = NULL; - visible_len = start_len + 1; -} - -void -display(pos, chr) - const struct body *pos; - char chr; -{ - wmove(tv, pos->y, pos->x); - waddch(tv, chr); -} - -void -leave(dummy) - int dummy; -{ - endwin(); - - if (dummy == 0){ /* called via crash() */ - printf("\nWell, you ran into something and the game is over.\n"); - printf("Your final score was %d\n\n", score); - } - exit(0); -} - -void -wake(dummy) - int dummy __attribute__((__unused__)); -{ - signal(SIGALRM, wake); - fflush(stdout); - process(lastch); -} - -int -rnd(range) - int range; -{ - return abs((rand()>>5)+(rand()>>5)) % range; -} - -void -newpos(bp) - struct body * bp; -{ - if (visible_len == (LINES-3) * (COLS-3) - 1) { - endwin(); - - printf("\nYou won!\n"); - printf("Your final score was %d\n\n", score); - exit(0); - } - do { - bp->y = rnd(LINES-3)+ 1; - bp->x = rnd(COLS-3) + 1; - wmove(tv, bp->y, bp->x); - } while(winch(tv) != ' '); -} - -void -prize() -{ - int value; - - value = rnd(9) + 1; - newpos(&goody); - waddch(tv, value+'0'); - wrefresh(tv); -} - -void -process(ch) - int ch; -{ - int x,y; - struct body *nh; - - alarm(0); - x = head->x; - y = head->y; - switch(ch) - { -#ifdef KEY_LEFT - case KEY_LEFT: -#endif - case 'h': - x--; break; - -#ifdef KEY_DOWN - case KEY_DOWN: -#endif - case 'j': - y++; break; - -#ifdef KEY_UP - case KEY_UP: -#endif - case 'k': - y--; break; - -#ifdef KEY_RIGHT - case KEY_RIGHT: -#endif - case 'l': - x++; break; - - case 'H': x--; running = RUNLEN; ch = tolower(ch); break; - case 'J': y++; running = RUNLEN/2; ch = tolower(ch); break; - case 'K': y--; running = RUNLEN/2; ch = tolower(ch); break; - case 'L': x++; running = RUNLEN; ch = tolower(ch); break; - case '\f': setup(); return; - - case ERR: - case CNTRL('C'): - case CNTRL('D'): - crash(); - return; - - default: if (! running) alarm(1); - return; - } - lastch = ch; - if (growing == 0) - { - display(tail, ' '); - tail->next->prev = NULL; - nh = tail->next; - free(tail); - tail = nh; - visible_len--; - } - else growing--; - display(head, BODY); - wmove(tv, y, x); - if (isdigit(ch = winch(tv))) - { - growing += ch-'0'; - prize(); - score += growing; - running = 0; - wmove(stw, 0, COLS - 12); - wprintw(stw, "Score: %3d", score); - wrefresh(stw); - } - else if(ch != ' ') crash(); - nh = newlink(); - if (nh == NULL) - err(1, NULL); - nh->next = NULL; - nh->prev = head; - head->next = nh; - nh->y = y; - nh->x = x; - display(nh, HEAD); - head = nh; - visible_len++; - if (!(slow && running)) - { - wmove(tv, head->y, head->x); - wrefresh(tv); - } - if (!running) - alarm(1); -} - -void -crash() -{ - leave(0); -} - -void -setup() -{ - clear(); - refresh(); - touchwin(stw); - wrefresh(stw); - touchwin(tv); - wrefresh(tv); - alarm(1); -} diff --git a/worm.py b/worm.py index dce1d0a..d82c0ad 100644 --- a/worm.py +++ b/worm.py @@ -1,11 +1,170 @@ -import ctypes import sys +import random +import time +import contextlib +import termios +import timeout_decorator +import os +from blessings import Terminal -worm = ctypes.cdll.LoadLibrary('./libworm.so') +term = Terminal() + +height = term.height +width = term.width +last_dir = 'x' + +class QuitGameError(BaseException): + pass + + +class RanIntoSomethingError(QuitGameError): + pass if len(sys.argv) == 2: - worm.set_size(int(sys.argv[1])) + try: + size = int(sys.argv[1]) + if size <= 0 or size >= width-13: + size = 7 + except ValueError: + size = 7 else: - worm.set_size(7) + size = 7 + + + +score = 0 + +def draw_worm(): + for location in worm_locations: + print(term.move(*reversed(location)) + 'o', end='') + + print(term.move(*reversed(worm_head)) + '@' + term.move(*reversed(worm_head)), end='') + + +def draw_frame(): + print(term.clear, end='') + print(term.move(0, 1) + 'Worm', end='') + if score > -1: + print(term.move(0, width-12) + f'Score:{" "*(4-len(str(score)))}{score}', end='') + print(term.move(1, 0) + '┌' + ('*' * (width-3)) + '┐', end='') + for y in range(2, height-1): + print(term.move(y, 0) + '*' + term.move(y, width-2) + '*', end='') + print(term.move(height-1, 0) + '└' + ('*' * (width-3)) + '┘', end='') + print(term.move(*reversed(bonus_location)) + 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 + bonus_points = random.randint(1, 9) + while worm_head == bonus_location or bonus_location in worm_locations: + bonus_location = [ + random.randint(1, width-3), + random.randint(2, height-2), + ] + + 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) + +worm_y = height // 2 + +worm_locations = [[i+10, worm_y] for i in range(size)] +worm_head = [size+10, worm_y] + +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), + ] + + +do_automove = True + +@timeout_decorator.timeout(1) +def run(*_): + global do_automove + if do_automove: + move_head(last_dir, 1) + draw_frame() + + do_automove = True + + while True: + k = sys.stdin.read(1) + if k in 'hjklHJKLABCDwasd': + move_head(k, 10 if k in 'HJKL' else 1) + draw_frame() + do_automove = False + return + elif k in '': + # That string is Ctrl-C Ctrl-\, in case you editor doesn't handle + # control characters as well as Vim does. + sys.exit(0) -worm.run_worm() +try: + with term.fullscreen(): + os.system('stty raw -echo') + draw_frame() + while True: + try: + run() + except timeout_decorator.timeout_decorator.TimeoutError: + pass +except KeyboardInterrupt: + sys.exit(0) +except RanIntoSomethingError: + os.system('stty -raw echo') + print('', end='\r\n') + 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')