Replace C version with Python

This is a (slightly buggy) Python port of Worm. The only change from bsdgames
worm (other than the programming language and any bugs that were added or
fixed) is that WASD is now supported.
This commit is contained in:
Samuel Sloniker 2021-08-19 13:53:12 -07:00
parent 1772e8df6d
commit 76c9dc37b7
3 changed files with 164 additions and 380 deletions

View File

@ -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

371
worm.c
View File

@ -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 <sys/cdefs.h>
#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 <ctype.h>
#include <curses.h>
#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#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);
}

169
worm.py
View File

@ -1,11 +1,170 @@
import ctypes
import sys 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: 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: else:
worm.set_size(7) size = 7
worm.run_worm()
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)
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')