mirror of
https://github.com/robmcmullen/fujirun.git
synced 2024-12-29 14:33:23 +00:00
1524 lines
49 KiB
Python
1524 lines
49 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Basic maze: 40x24, rightmost 7 cols are the score area
|
|
#
|
|
# 00 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_______
|
|
# 01 X/----T----T----T----T----T----\X_______
|
|
# 02 X|XXXX|XXXX|XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 03 X|XXXX|XXXX|XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 04 X|XXXX|XXXX|XXXX|XXXX+----+XXXX|X_______
|
|
# 05 X|XXXX|XXXX+----+XXXX|XXXX+----+X_______
|
|
# 06 X|XXXX|XXXX|XXXX+----+XXXX|XXXX|X_______
|
|
# 07 X|XXXX+----+XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 08 X+----+XXXX+----+XXXX|XXXX|XXXX|X_______
|
|
# 09 X|XXXX|XXXX|XXXX|XXXX+----+XXXX|X_______
|
|
# 10 X|XXXX+----+XXXX|XXXX|XXXX+----+X_______
|
|
# 11 X|XXXX|XXXX|XXXX+----+XXXX|XXXX|X_______
|
|
# 12 X+----+XXXX|XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 13 X|XXXX+----+XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 14 X|XXXX|XXXX+----+XXXX+----+XXXX|X_______
|
|
# 15 X|XXXX|XXXX|XXXX|XXXX|XXXX+----+X_______
|
|
# 16 X|XXXX|XXXX|XXXX+----+XXXX|XXXX|X_______
|
|
# 17 X|XXXX+----+XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 18 X+----+XXXX+----+XXXX|XXXX+----+X_______
|
|
# 19 X|XXXX|XXXX|XXXX|XXXX+----+XXXX|X_______
|
|
# 20 X|XXXX+----+XXXX+----+XXXX|XXXX|X_______
|
|
# 21 X|XXXX|XXXX|XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 22 X\----^----^----^----^----^----/X_______
|
|
# 23 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_______
|
|
#
|
|
# Terminology:
|
|
#
|
|
# vpath - vertical path
|
|
# hpath - horizontal path
|
|
# boxes - area inside path boundaries that gets filled when dots around it are collected
|
|
# enemy - uses Amidar movement
|
|
# player - joystick control
|
|
# actor - either a player or an enemy
|
|
|
|
import time
|
|
import random
|
|
import curses
|
|
|
|
import numpy as np
|
|
|
|
import logging
|
|
logging.basicConfig(level=logging.WARNING)
|
|
init_log = logging.getLogger("init")
|
|
logic_log = logging.getLogger("logic")
|
|
#logic_log.setLevel(logging.DEBUG)
|
|
draw_log = logging.getLogger("draw")
|
|
maze_log = logging.getLogger("maze")
|
|
box_log = logging.getLogger("maze")
|
|
game_log = logging.getLogger("game")
|
|
collision_log = logging.getLogger("collision")
|
|
player_log = logging.getLogger("player")
|
|
player_log.setLevel(logging.DEBUG)
|
|
|
|
CURSES = 1
|
|
|
|
##### Game loader
|
|
|
|
pad = None
|
|
|
|
def init():
|
|
if CURSES:
|
|
curses.wrapper(init_screen)
|
|
else:
|
|
main()
|
|
|
|
def init_screen(*args, **kwargs):
|
|
global pad, curseschars
|
|
|
|
curses.use_default_colors()
|
|
curses.start_color()
|
|
curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_WHITE)
|
|
curses.curs_set(0)
|
|
pad = curses.newpad(40, 80)
|
|
pad.timeout(0)
|
|
pad.keypad(1)
|
|
|
|
# have to define these here because initscr hasn't been called when parsing
|
|
# the python source.
|
|
curseschars = [
|
|
curses.ACS_CKBOARD, # illegal
|
|
curses.ACS_CKBOARD,
|
|
curses.ACS_CKBOARD,
|
|
curses.ACS_VLINE, # 3: up/down
|
|
curses.ACS_CKBOARD,
|
|
curses.ACS_ULCORNER, # 5: down/right
|
|
curses.ACS_LLCORNER, # 6: up/right
|
|
curses.ACS_LTEE, # 7: up/down/right
|
|
curses.ACS_CKBOARD,
|
|
curses.ACS_URCORNER, # 9: left/down
|
|
curses.ACS_LRCORNER, # 10: left/up
|
|
curses.ACS_RTEE, # 11: left/up/down
|
|
curses.ACS_HLINE, # 12: left/right
|
|
curses.ACS_TTEE, # 13: left/right/down
|
|
curses.ACS_BTEE, # 14: left/right/up
|
|
curses.ACS_CKBOARD,
|
|
|
|
# And same again, with dots
|
|
curses.ACS_CKBOARD, # illegal
|
|
curses.ACS_CKBOARD,
|
|
curses.ACS_CKBOARD,
|
|
curses.ACS_VLINE, # 3: up/down
|
|
curses.ACS_CKBOARD,
|
|
curses.ACS_ULCORNER, # 5: down/right
|
|
curses.ACS_LLCORNER, # 6: up/right
|
|
curses.ACS_LTEE, # 7: up/down/right
|
|
curses.ACS_CKBOARD,
|
|
curses.ACS_URCORNER, # 9: left/down
|
|
curses.ACS_LRCORNER, # 10: left/up
|
|
curses.ACS_RTEE, # 11: left/up/down
|
|
curses.ACS_HLINE, # 12: left/right
|
|
curses.ACS_TTEE, # 13: left/right/down
|
|
curses.ACS_BTEE, # 14: left/right/up
|
|
curses.ACS_CKBOARD,
|
|
]
|
|
|
|
#main()
|
|
play_game()
|
|
|
|
|
|
def main():
|
|
while True:
|
|
wipe_down()
|
|
wipe_title()
|
|
if config_quit:
|
|
return
|
|
|
|
|
|
def read_menu_input():
|
|
global config_quit, config_start
|
|
|
|
read_user_input()
|
|
if config_quit:
|
|
sys.exit()
|
|
if config_start:
|
|
play_game()
|
|
config_quit = 0
|
|
config_start = 0
|
|
|
|
|
|
def clear_line(y, c):
|
|
addr = screenrow(y)
|
|
x = 0
|
|
while x < SCREEN_COLS:
|
|
addr[x] = c
|
|
x += 1
|
|
|
|
|
|
def wipe_down():
|
|
y = 0
|
|
while y < SCREEN_ROWS:
|
|
read_menu_input()
|
|
clear_line(y, 0)
|
|
show_screen()
|
|
time.sleep(.02)
|
|
y += 1
|
|
|
|
|
|
def wipe_title():
|
|
y = 0
|
|
while y < 10:
|
|
read_menu_input()
|
|
clear_line(y, 32)
|
|
show_screen()
|
|
time.sleep(.02)
|
|
y += 1
|
|
i = 0
|
|
while i < len(title_text):
|
|
read_menu_input()
|
|
x = 0
|
|
addr = screenrow(y)
|
|
while x < SCREEN_COLS:
|
|
addr[x] = ord(title_text[i][x])
|
|
x += 1
|
|
show_screen()
|
|
time.sleep(.02)
|
|
y += 1
|
|
i += 1
|
|
while y < SCREEN_ROWS:
|
|
read_menu_input()
|
|
clear_line(y, 32)
|
|
show_screen()
|
|
time.sleep(.02)
|
|
y += 1
|
|
i = TITLE_SCREEN_TIME
|
|
while i >= 0:
|
|
read_menu_input()
|
|
show_screen()
|
|
time.sleep(.02)
|
|
i -= 1
|
|
|
|
|
|
title_text = [
|
|
" LINE 1 ",
|
|
" LINE 2 ",
|
|
" LINE 3 ",
|
|
" LINE 4 ",
|
|
]
|
|
|
|
|
|
def play_game():
|
|
init_maze()
|
|
screen[:,0:33] = maze
|
|
|
|
show_screen()
|
|
|
|
zp.level = 1
|
|
zp.last_enemy = level_enemies[zp.level] + FIRST_AMIDAR
|
|
zp.num_players = 2
|
|
init_actors()
|
|
init_static_background()
|
|
|
|
game_loop()
|
|
|
|
##### Memory usage
|
|
|
|
# Zero page
|
|
|
|
class ZeroPage(object):
|
|
current_actor = 0 # index of sprite currently being processed
|
|
current_dir = 0 # direction of actor
|
|
r = 0
|
|
c = 0
|
|
|
|
round_robin_index = [0, 0] # down, up
|
|
level = 0
|
|
|
|
sprite_addr = None
|
|
|
|
# enemy info
|
|
last_enemy = 0
|
|
|
|
# player info
|
|
num_players = 1
|
|
|
|
# sprite
|
|
num_sprites_drawn = 0
|
|
|
|
|
|
# box drawing workspace
|
|
next_level_box = 0
|
|
box_col_save = 0
|
|
box_row_save = 0
|
|
|
|
|
|
zp = ZeroPage()
|
|
|
|
# 2 byte addresses
|
|
|
|
maze = np.empty((24, 33), dtype=np.uint8)
|
|
|
|
screen = np.empty((24, 40), dtype=np.uint8)
|
|
|
|
TILE_DOWN = 0x1
|
|
TILE_UP = 0x2
|
|
TILE_RIGHT = 0x4
|
|
TILE_LEFT= 0x8
|
|
TILE_HORZ = TILE_LEFT | TILE_RIGHT
|
|
TILE_VERT = TILE_UP | TILE_DOWN
|
|
DIR_MASK = 0x0f
|
|
TILE_DOT = 0x10
|
|
|
|
LEFT_TILE = TILE_DOT | TILE_RIGHT
|
|
MIDDLE_TILE = TILE_DOT | TILE_LEFT | TILE_RIGHT
|
|
RIGHT_TILE = TILE_DOT | TILE_LEFT
|
|
|
|
VPATH_NUM = 6
|
|
if VPATH_NUM == 7:
|
|
BOX_WIDTH = 4
|
|
else:
|
|
VPATH_NUM = 6
|
|
BOX_WIDTH = 5
|
|
VPATH_COL_SPACING = BOX_WIDTH + 1
|
|
|
|
vpath_cols = [1 + i * VPATH_COL_SPACING for i in range(VPATH_NUM)]
|
|
|
|
vpath_top_tile = [MIDDLE_TILE | TILE_DOWN] * VPATH_NUM
|
|
vpath_top_tile[0] = LEFT_TILE | TILE_DOWN
|
|
vpath_top_tile[-1] = RIGHT_TILE | TILE_DOWN
|
|
|
|
vpath_bot_tile = [MIDDLE_TILE | TILE_UP] * VPATH_NUM
|
|
vpath_bot_tile[0] = LEFT_TILE | TILE_UP
|
|
vpath_bot_tile[-1] = RIGHT_TILE | TILE_UP
|
|
|
|
if VPATH_NUM == 7:
|
|
player_start_col = [
|
|
[255, 255, 255, 255],
|
|
[vpath_cols[3], 0, 0, 0],
|
|
[vpath_cols[2], vpath_cols[4], 0, 0],
|
|
[vpath_cols[1], vpath_cols[3], vpath_cols[5], 0],
|
|
[vpath_cols[0], vpath_cols[2], vpath_cols[4], vpath_cols[6]],
|
|
]
|
|
else:
|
|
player_start_col = [
|
|
[255, 255, 255, 255],
|
|
[(vpath_cols[2] + vpath_cols[3])//2, 0, 0, 0],
|
|
[vpath_cols[1], vpath_cols[4], 0, 0],
|
|
[vpath_cols[1], vpath_cols[3], vpath_cols[5], 0],
|
|
[vpath_cols[0], vpath_cols[2], vpath_cols[4], vpath_cols[5]],
|
|
]
|
|
|
|
amidar_start_col = [0] * VPATH_NUM
|
|
round_robin_up = [0] * VPATH_NUM
|
|
round_robin_down = [0] * VPATH_NUM
|
|
|
|
# up/down/left/right would be 0xf, but this is not legal for ghost legs
|
|
tilechars = [
|
|
"x", # illegal
|
|
"x",
|
|
"x",
|
|
"|", # 3: up/down
|
|
"x",
|
|
"/", # 5: down/right
|
|
"\\", # 6: up/right
|
|
"+", # 7: up/down/right
|
|
"x",
|
|
"\\", # 9: left/down
|
|
"/", # 10: left/up
|
|
"+", # 11: left/up/down
|
|
"-", # 12: left/right
|
|
"T", # 13: left/right/down
|
|
"^", # 14: left/right/up
|
|
"x",
|
|
|
|
# And same again, with dots
|
|
"x", # illegal
|
|
"x",
|
|
"x",
|
|
"|", # 3: up/down
|
|
"x",
|
|
"/", # 5: down/right
|
|
"\\", # 6: up/right
|
|
"+", # 7: up/down/right
|
|
"x",
|
|
"\\", # 9: left/down
|
|
"/", # 10: left/up
|
|
"+", # 11: left/up/down
|
|
"-", # 12: left/right
|
|
"T", # 13: left/right/down
|
|
"^", # 14: left/right/up
|
|
"x",
|
|
|
|
"@", # 32: enemy (temporary)
|
|
"$", # 33: player
|
|
]
|
|
|
|
|
|
# up/down/left/right would be 0xf, but this is not legal for ghost legs
|
|
curseschars = None
|
|
|
|
|
|
##### Constants
|
|
|
|
# Screen has rows 0 - 23
|
|
# Maze is rows 1 - 22
|
|
MAZE_TOP_ROW = 1
|
|
MAZE_BOT_ROW = 22
|
|
SCREEN_ROWS = 24
|
|
|
|
# Screen has cols 0 - 39
|
|
# cols 0 - 32 are the maze, of which 1 - 31 are actually used
|
|
# 0 and 32 are border tiles having the value zero
|
|
# cols 33 - 39 is the score area
|
|
MAZE_LEFT_COL = 1
|
|
MAZE_RIGHT_COL = 31
|
|
MAZE_SCORE_COL = 33
|
|
SCREEN_COLS = 40
|
|
|
|
# Orbiter goes around the outside border, but not through the maze
|
|
ORBITER_START_COL = MAZE_RIGHT_COL
|
|
ORBITER_START_ROW = (MAZE_TOP_ROW + MAZE_BOT_ROW) / 2
|
|
|
|
|
|
##### Utility functions
|
|
|
|
# Returns address of tile in col 0 of row y
|
|
def mazerow(y):
|
|
return maze[y]
|
|
|
|
# Returns address of tile in col 0 of row y of the "screen" memory
|
|
def screenrow(y):
|
|
return screen[y]
|
|
|
|
# Return a random number between 3 and 5 (inclusive) to represent next row that
|
|
# contains an hpath. 3 is the minimum number so that if necessary, the last
|
|
# spacing on the bottom can be adjusted upward by 1 to guarantee no cross-
|
|
# throughs
|
|
def get_rand_spacing():
|
|
return random.randint(3, 5)
|
|
|
|
# Random number between 0 and VPATH_NUM (exclusive) used for column starting
|
|
# positions
|
|
def get_rand_col():
|
|
return random.randint(0, VPATH_NUM - 1)
|
|
|
|
def get_rand_byte():
|
|
return random.randint(0, 255)
|
|
|
|
# Get random starting columns for enemies by swapping elements in a list
|
|
# several times
|
|
def get_col_randomizer(r):
|
|
r[:] = vpath_cols[:]
|
|
x = 10
|
|
while x >= 0:
|
|
i1 = get_rand_col()
|
|
i2 = get_rand_col()
|
|
old1 = r[i1]
|
|
r[i1] = r[i2]
|
|
r[i2] = old1
|
|
x -= 1
|
|
|
|
|
|
###### Level creation functions
|
|
|
|
def clear_maze():
|
|
y = 0
|
|
while y < SCREEN_ROWS:
|
|
addr = mazerow(y)
|
|
x = 0
|
|
while x < MAZE_SCORE_COL:
|
|
addr[x] = 0
|
|
x += 1
|
|
y += 1
|
|
init_boxes()
|
|
|
|
# Set all elements in a row to dot + left + right; only top and bottom
|
|
def setrow(row):
|
|
addr = mazerow(row)
|
|
x = MAZE_LEFT_COL
|
|
while x <= MAZE_RIGHT_COL:
|
|
addr[x] = TILE_DOT|TILE_LEFT|TILE_RIGHT
|
|
x += 1
|
|
|
|
# Create all vpaths, using top/bot character from a list to handle both
|
|
# corners and T connections.
|
|
def setvpath(col):
|
|
x = vpath_cols[col]
|
|
y = MAZE_TOP_ROW
|
|
addr = mazerow(y)
|
|
addr[x] = vpath_top_tile[col]
|
|
y += 1
|
|
while y < MAZE_BOT_ROW:
|
|
addr = mazerow(y)
|
|
addr[x] = TILE_DOT|TILE_UP|TILE_DOWN
|
|
y += 1
|
|
addr = mazerow(y)
|
|
addr[x] = vpath_bot_tile[col]
|
|
|
|
|
|
# Create hpaths such that there are no hpaths that meet at the same row in
|
|
# adjacent columns (cross-throughs are not allowed in ghost legs). Starts at
|
|
# the rightmost vpath and moves left using the rightmost vpath as the input to
|
|
# this function and building hpaths between it and the vpath to the left. The
|
|
# first time this routine is called there won't be any existing columns to
|
|
# compare to, otherwise if a tile on the left vpath has a rightward pointing
|
|
# hpath, move up one and draw the hpath there. This works because the minimum
|
|
# hpath vertical positioning leaves 2 empty rows, so moving up by one still
|
|
# leaves 1 empty row.
|
|
def sethpath(col):
|
|
x1_save = vpath_cols[col - 1]
|
|
x2 = vpath_cols[col]
|
|
y = MAZE_TOP_ROW
|
|
start_box(y, x1_save)
|
|
y += get_rand_spacing()
|
|
while y < MAZE_BOT_ROW - 1:
|
|
addr = mazerow(y)
|
|
|
|
# If not working on the rightmost column, check to see there are
|
|
# no cross-throughs.
|
|
if col < VPATH_NUM - 1:
|
|
tile = addr[x2]
|
|
if tile & TILE_RIGHT:
|
|
maze_log.debug("at y=%d on col %d, found same hpath level at col %d" % (y, col, col + 1))
|
|
y -= 1
|
|
addr = mazerow(y)
|
|
add_box(y)
|
|
|
|
x = x1_save
|
|
addr[x] = TILE_DOT | TILE_UP | TILE_DOWN | TILE_RIGHT
|
|
x += 1
|
|
while x < x2:
|
|
addr[x] = TILE_DOT | TILE_LEFT | TILE_RIGHT
|
|
x += 1
|
|
addr[x2] = TILE_DOT | TILE_UP | TILE_DOWN | TILE_LEFT
|
|
y += get_rand_spacing()
|
|
add_box(MAZE_BOT_ROW)
|
|
|
|
|
|
def init_maze():
|
|
clear_maze()
|
|
|
|
# Draw top and bottom; no intersections anywhere. Corners and T
|
|
# intesections will be placed in setvpath
|
|
setrow(MAZE_TOP_ROW)
|
|
setrow(MAZE_BOT_ROW)
|
|
|
|
# Draw all vpaths, including corners and top/bot T intersections
|
|
counter = VPATH_NUM
|
|
counter -= 1
|
|
while counter >= 0:
|
|
setvpath(counter)
|
|
counter -= 1
|
|
|
|
# Draw connectors between vpaths, starting with the rightmost column and
|
|
# the one immediately left of it. This is performed 6 times because it
|
|
# always needs a pair of columns to work with.
|
|
counter = VPATH_NUM
|
|
counter -= 1
|
|
while counter > 0: # note >, not >=
|
|
sethpath(counter)
|
|
counter -= 1
|
|
|
|
finish_boxes()
|
|
|
|
|
|
##### Box handling/painting
|
|
|
|
# Level box storage uses the left column (we don't need to store the right side
|
|
# because they are always a fixed distance away) and a list of rows.
|
|
#
|
|
# To examine the boundary of each box to check for dots, the top row and the
|
|
# bottom row must look at BOX_WIDTH + 2 tiles, all the middle rows only have to
|
|
# check the left and right tiles
|
|
#
|
|
# The entire list of rows doesn't need to be stored, either; only the top and
|
|
# bottom because everything else is a middle row. Therefore, all we need is the
|
|
# x of the left vpath, the top row and the bottom row:
|
|
#
|
|
# x1, ytop, ybot
|
|
#
|
|
# is 3 bytes. Max number of boxes is 10 per column, 6 columns that's 10 * 6 * 3
|
|
# = 180 bytes. Less than 256, yay!
|
|
#
|
|
# n can also be used as a flag: if n == 0, the box has already been checked and
|
|
# painted. n == 0xff is the flag to end processing.
|
|
|
|
# for VPATH_NUM == 7:
|
|
# 01 X/----T----T----T----T----T----\X_______
|
|
# 02 X|XXXX|XXXX|XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 03 X|XXXX|XXXX|XXXX|XXXX|XXXX|XXXX|X_______
|
|
# 04 X|XXXX|XXXX|XXXX|XXXX+----+XXXX|X_______
|
|
|
|
# for VPATH_NUM == 6:
|
|
# 01 X/-----T-----T-----T-----T-----\X_______
|
|
# 02 X|XXXXX|XXXXX|XXXXX|XXXXX|XXXXX|X_______
|
|
# 03 X|XXXXX|XXXXX|XXXXX|XXXXX|XXXXX|X_______
|
|
# 04 X|XXXXX|XXXXX|XXXXX+-----+XXXXX|X_______
|
|
|
|
NUM_LEVEL_BOX_PARAMS = 3
|
|
level_boxes = [0] * 10 * 6 * NUM_LEVEL_BOX_PARAMS
|
|
|
|
# Box painting will be in hires so this array will become a tracker for the
|
|
# hires display. It will need y address, y end address, x byte number. It's
|
|
# possible for up to 3 boxes to get triggered to start painting when collecting
|
|
# a dot, and because it will take multiple frames to paint a box there may be
|
|
# even more active at one time, so for safety use 16 as possible max.
|
|
#
|
|
# player #, xbyte, ytop, ybot
|
|
NUM_BOX_PAINTING_PARAMS = 4
|
|
box_painting = [0] * NUM_BOX_PAINTING_PARAMS * 16
|
|
|
|
def init_boxes():
|
|
zp.next_level_box = 0
|
|
|
|
def start_box(r, c):
|
|
zp.box_col_save = c
|
|
zp.box_row_save = r
|
|
|
|
def add_box(r):
|
|
i = zp.next_level_box
|
|
level_boxes[i] = zp.box_col_save
|
|
level_boxes[i + 1] = zp.box_row_save
|
|
level_boxes[i + 2] = r
|
|
zp.box_row_save = r
|
|
zp.next_level_box += NUM_LEVEL_BOX_PARAMS
|
|
|
|
def finish_boxes():
|
|
i = zp.next_level_box
|
|
level_boxes[i] = 0xff
|
|
|
|
def check_boxes():
|
|
x = 0
|
|
pad.addstr(28, 0, str(level_boxes[0:21]))
|
|
while level_boxes[x] < 0xff:
|
|
c = level_boxes[x]
|
|
if c > 0:
|
|
r1 = level_boxes[x + 1]
|
|
addr = mazerow(r1)
|
|
r1 += 1
|
|
r1_save = r1
|
|
|
|
# If there's a dot anywhere, then the box isn't painted. We don't
|
|
# care where it is so we don't need to keep track of individual
|
|
# locations.
|
|
dot = addr[c] | addr[c + 1] | addr[c + 2] | addr[c + 3] | addr[c + 4] | addr[c + 5] | addr[c + BOX_WIDTH + 1]
|
|
|
|
r2 = level_boxes[x + 2]
|
|
addr = mazerow(r2)
|
|
dot |= addr[c] | addr[c + 1] | addr[c + 2] | addr[c + 3] | addr[c + 4] | addr[c + 5] | addr[c + BOX_WIDTH + 1]
|
|
|
|
while r1 < r2:
|
|
addr = mazerow(r1)
|
|
dot |= addr[c] | addr[c + BOX_WIDTH + 1]
|
|
r1 += 1
|
|
|
|
if (dot & TILE_DOT) == 0:
|
|
# No dots anywhere! Start painting
|
|
mark_box_for_painting(r1_save, r2, c + 1)
|
|
num_rows = r2 - r1_save
|
|
player_score[zp.current_actor] += num_rows * 100
|
|
level_boxes[x] = 0 # Set flag so we don't check this box again
|
|
|
|
x += 3
|
|
|
|
def mark_box_for_painting(r1, r2, c):
|
|
box_log.debug("Marking box, player $%d @ %d,%d -> %d,%d" % (zp.current_actor, r1, c, r2, c + BOX_WIDTH))
|
|
x = 0
|
|
while x < NUM_BOX_PAINTING_PARAMS * 16:
|
|
if box_painting[x] == 0:
|
|
box_painting[x] = c
|
|
box_painting[x + 1] = r1
|
|
box_painting[x + 2] = r2
|
|
box_painting[x + 3] = zp.current_actor
|
|
break
|
|
x += NUM_BOX_PAINTING_PARAMS
|
|
pad.addstr(27, 0, "starting box, player @ %d %d,%d -> %d,%d" % (zp.current_actor, r1, c, r2, c + BOX_WIDTH))
|
|
|
|
|
|
##### Gameplay storage
|
|
|
|
config_num_players = 1
|
|
config_quit = 0
|
|
config_start = 0
|
|
|
|
level = -1
|
|
level_enemies = [255, 4, 5, 6, 7, 8] # level starts counting from 1, so dummy zeroth level info
|
|
level_speeds = [255, 200, 210, 220, 230, 240] # increment of fractional pixel per game frame
|
|
player_score_row = [2, 7, 12, 17]
|
|
|
|
# sprites all use the same table. In the sample configuration, sprites 0 - 3
|
|
# are players, 4 and above are enemies. One is an orbiter enemy, the rest use
|
|
# amidar movement.
|
|
MAX_PLAYERS = 4
|
|
MAX_AMIDARS = VPATH_NUM + 1 # one enemy per vpath + one orbiter
|
|
MAX_ACTORS = MAX_PLAYERS + MAX_AMIDARS
|
|
FIRST_PLAYER = 0
|
|
FIRST_AMIDAR = MAX_PLAYERS
|
|
LAST_PLAYER = FIRST_AMIDAR - 1
|
|
LAST_AMIDAR = MAX_ACTORS - 1
|
|
|
|
STARTING_LIVES = 3
|
|
BONUS_LIFE = 10000
|
|
MAX_LIVES = 8
|
|
|
|
X_MIDPOINT = 3
|
|
X_TILEMAX = 7
|
|
Y_MIDPOINT = 3
|
|
Y_TILEMAX = 8
|
|
|
|
actor_col = [0] * MAX_ACTORS # current tile column
|
|
actor_xpixel = [0] * MAX_ACTORS # current pixel offset in col
|
|
actor_xfrac = [0] * MAX_ACTORS # current fractional pixel
|
|
actor_xspeed = [0] * MAX_ACTORS # current speed (affects fractional)
|
|
actor_row = [0] * MAX_ACTORS # current tile row
|
|
actor_ypixel = [0] * MAX_ACTORS # current pixel offset in row
|
|
actor_yfrac = [0] * MAX_ACTORS # current fractional pixel
|
|
actor_yspeed = [0] * MAX_ACTORS # current speed (affects fractional)
|
|
actor_updown = [0] * MAX_ACTORS # preferred direction
|
|
actor_dir = [0] * MAX_ACTORS # actual direction
|
|
actor_target_col = [0] * MAX_ACTORS # target column at bot or top T
|
|
actor_status = [0] * MAX_ACTORS # alive, exploding, dead, regenerating, invulnerable, ???
|
|
actor_frame_counter = [0] * MAX_ACTORS # frame counter for sprite changes
|
|
actor_input_dir = [0] * MAX_ACTORS # current joystick input direction
|
|
|
|
dot_eaten_row = [255, 255, 255, 255] # dot eaten by player
|
|
dot_eaten_col = [255, 255, 255, 255]
|
|
player_score = [0, 0, 0, 0]
|
|
player_next_target_score = [0, 0, 0, 0]
|
|
player_lives = [0, 0, 0, 0] # lives remaining
|
|
|
|
NOT_VISIBLE = 0
|
|
PLAYER_DEAD = 1
|
|
PLAYER_ALIVE = 2
|
|
PLAYER_EXPLODING = 3
|
|
PLAYER_REGENERATING = 4
|
|
AMIDAR_NORMAL = 5
|
|
ORBITER_NORMAL = 6
|
|
GAME_OVER = 255
|
|
|
|
# Scores
|
|
|
|
DOT_SCORE = 10
|
|
PAINT_SCORE_PER_LINE = 100
|
|
|
|
##### Gameplay initialization
|
|
|
|
def init_actor():
|
|
# Common initialization params for all actors
|
|
actor_col[zp.current_actor] = MAZE_LEFT_COL
|
|
actor_xpixel[zp.current_actor] = 3
|
|
actor_xfrac[zp.current_actor] = 0
|
|
actor_xspeed[zp.current_actor] = 0
|
|
actor_row[zp.current_actor] = MAZE_BOT_ROW
|
|
actor_ypixel[zp.current_actor] = 3
|
|
actor_yfrac[zp.current_actor] = 0
|
|
actor_yspeed[zp.current_actor] = 0
|
|
actor_input_dir[zp.current_actor] = 0
|
|
actor_updown[zp.current_actor] = TILE_UP
|
|
actor_dir[zp.current_actor] = TILE_UP
|
|
actor_status[zp.current_actor] = NOT_VISIBLE
|
|
actor_frame_counter[zp.current_actor] = 0
|
|
actor_target_col[zp.current_actor] = 0
|
|
actor_input_dir[zp.current_actor] = 0
|
|
|
|
def init_orbiter():
|
|
init_actor()
|
|
actor_col[zp.current_actor] = ORBITER_START_COL
|
|
actor_row[zp.current_actor] = ORBITER_START_ROW
|
|
actor_dir[zp.current_actor] = TILE_UP
|
|
actor_status[zp.current_actor] = ORBITER_NORMAL
|
|
#set_speed(TILE_UP)
|
|
|
|
def init_amidar():
|
|
init_actor()
|
|
amidar_index = zp.current_actor - FIRST_AMIDAR - 1 # orbiter always 1st enemy
|
|
actor_col[zp.current_actor] = amidar_start_col[amidar_index]
|
|
actor_row[zp.current_actor] = MAZE_TOP_ROW
|
|
actor_ypixel[zp.current_actor] = 4
|
|
actor_updown[zp.current_actor] = TILE_DOWN
|
|
actor_dir[zp.current_actor] = TILE_DOWN
|
|
actor_status[zp.current_actor] = AMIDAR_NORMAL
|
|
#set_speed(TILE_DOWN)
|
|
|
|
def init_player():
|
|
init_actor()
|
|
addr = player_start_col[zp.num_players]
|
|
actor_col[zp.current_actor] = addr[zp.current_actor]
|
|
actor_row[zp.current_actor] = MAZE_BOT_ROW
|
|
actor_status[zp.current_actor] = PLAYER_ALIVE
|
|
set_speed(TILE_DOWN)
|
|
|
|
def init_actors():
|
|
get_col_randomizer(amidar_start_col)
|
|
get_col_randomizer(round_robin_up)
|
|
get_col_randomizer(round_robin_down)
|
|
zp.current_actor = 0
|
|
while zp.current_actor <= zp.last_enemy:
|
|
if zp.current_actor <= LAST_PLAYER:
|
|
if zp.current_actor < zp.num_players:
|
|
init_player()
|
|
player_lives[zp.current_actor] = STARTING_LIVES
|
|
player_next_target_score[zp.current_actor] = BONUS_LIFE
|
|
else:
|
|
if zp.current_actor == FIRST_AMIDAR:
|
|
init_orbiter()
|
|
else:
|
|
init_amidar()
|
|
zp.current_actor += 1
|
|
zp.round_robin_index[:] = [0, 0]
|
|
|
|
|
|
##### Drawing routines
|
|
|
|
# Sprites use a backing store array that captures the background before each
|
|
# sprite is drawn. Each sprite's backing store is a a rectange of bytes (always
|
|
# starting on a byte boundary) stored consecutively starting with the first row
|
|
# of bytes, then the second, etc. on down the streen for the height of the
|
|
# sprite. The backing store doesn't actually care what the x value of the
|
|
# actual sprite is, only the byte number in the row of the first pixel
|
|
# affected.
|
|
|
|
# Max 16 sprites?
|
|
last_sprite_byte = [0] * 16 # byte number in row (0 - 39)
|
|
last_sprite_y = [0] * 16 # y coord of upper left corner of sprite
|
|
last_sprite_addr = [0] * 16 # Addr of sprite? Index of sprite?
|
|
sprite_backing_store = [0] * 16 # Addr of backing store? Index into array?
|
|
sprite_bytes_per_row = [0] * 16 # backing store is a rectangle of bytes
|
|
sprite_num_rows = [0] * 16 # Addr of sprite? Index of sprite?
|
|
|
|
exploding_char = ['*', '*', '@', '#', '\\', '-', '/', '|', '\\', '-', '/', '|', '\\', '-', '/', '|', '\\', '-', '/', '|']
|
|
EXPLODING_TIME = len(exploding_char) - 1
|
|
DEAD_TIME = 40
|
|
REGENERATING_TIME = 60
|
|
END_GAME_TIME = 100
|
|
TITLE_SCREEN_TIME = 100
|
|
|
|
# Erase sprites in reverse order that they're drawn to restore the background
|
|
# properly
|
|
def erase_sprites():
|
|
while zp.num_sprites_drawn > 0:
|
|
zp.num_sprites_drawn -= 1
|
|
i = zp.num_sprites_drawn
|
|
val = sprite_backing_store[i]
|
|
r = last_sprite_y[i]
|
|
addr = screenrow(r)
|
|
c = last_sprite_byte[i]
|
|
draw_log.debug("restoring background %d @ (%d,%d)" % (i, r, c))
|
|
addr[c] = val
|
|
|
|
def save_backing_store(r, c):
|
|
addr = mazerow(r)
|
|
i = zp.num_sprites_drawn
|
|
draw_log.debug("saving background %d @ (%d,%d)" % (i, r, c))
|
|
last_sprite_byte[i] = c
|
|
last_sprite_y[i] = r
|
|
last_sprite_addr[i] = zp.sprite_addr
|
|
sprite_backing_store[i] = addr[c]
|
|
sprite_bytes_per_row[i] = 1
|
|
sprite_num_rows[i] = 1
|
|
zp.num_sprites_drawn += 1
|
|
|
|
def draw_sprite(r, c):
|
|
if zp.sprite_addr is not None:
|
|
save_backing_store(r, c)
|
|
addr = screenrow(r)
|
|
addr[c] = zp.sprite_addr
|
|
|
|
def draw_actors():
|
|
zp.current_actor = 0
|
|
while zp.current_actor <= zp.last_enemy:
|
|
r = actor_row[zp.current_actor]
|
|
c = actor_col[zp.current_actor]
|
|
get_sprite()
|
|
draw_sprite(r, c)
|
|
zp.current_actor += 1
|
|
|
|
def get_sprite():
|
|
a = actor_status[zp.current_actor]
|
|
if a == PLAYER_ALIVE:
|
|
c = ord("$") + zp.current_actor
|
|
elif a == PLAYER_EXPLODING:
|
|
collision_log.debug("p%d: exploding, frame=%d" % (zp.current_actor, actor_frame_counter[zp.current_actor]))
|
|
c = ord(exploding_char[actor_frame_counter[zp.current_actor]])
|
|
actor_frame_counter[zp.current_actor] -= 1
|
|
if actor_frame_counter[zp.current_actor] <= 0:
|
|
actor_status[zp.current_actor] = PLAYER_DEAD
|
|
actor_frame_counter[zp.current_actor] = DEAD_TIME
|
|
elif a == PLAYER_DEAD:
|
|
collision_log.debug("p%d: dead, waiting=%d" % (zp.current_actor, actor_frame_counter[zp.current_actor]))
|
|
c = None
|
|
actor_frame_counter[zp.current_actor] -= 1
|
|
if actor_frame_counter[zp.current_actor] <= 0:
|
|
player_lives[zp.current_actor] -= 1
|
|
if player_lives[zp.current_actor] > 0:
|
|
init_player()
|
|
actor_status[zp.current_actor] = PLAYER_REGENERATING
|
|
actor_frame_counter[zp.current_actor] = REGENERATING_TIME
|
|
else:
|
|
actor_status[zp.current_actor] = GAME_OVER
|
|
elif a == PLAYER_REGENERATING:
|
|
collision_log.debug("p%d: regenerating, frame=%d" % (zp.current_actor, actor_frame_counter[zp.current_actor]))
|
|
if actor_frame_counter[zp.current_actor] & 1:
|
|
c = ord("$") + zp.current_actor
|
|
else:
|
|
c = ord(" ")
|
|
actor_frame_counter[zp.current_actor] -= 1
|
|
if actor_frame_counter[zp.current_actor] <= 0:
|
|
actor_status[zp.current_actor] = PLAYER_ALIVE
|
|
elif a == AMIDAR_NORMAL or a == ORBITER_NORMAL:
|
|
c = ord("0") + zp.current_actor - FIRST_AMIDAR
|
|
else:
|
|
c = None
|
|
zp.sprite_addr = c
|
|
|
|
|
|
##### Game logic
|
|
|
|
# Determine which of the 4 directions is allowed at the given row, col
|
|
def get_allowed_dirs(r, c):
|
|
addr = mazerow(r)
|
|
allowed = addr[c] & DIR_MASK
|
|
return allowed
|
|
|
|
# See if current tile has a dot
|
|
def has_dot(r, c):
|
|
addr = mazerow(r)
|
|
return addr[c] & TILE_DOT
|
|
|
|
# clear a dot
|
|
def clear_dot(r, c):
|
|
addr = mazerow(r)
|
|
addr[c] &= ~TILE_DOT
|
|
|
|
# Determine the tile location given the direction of the actor's movement
|
|
def get_next_tile(r, c, dir):
|
|
if dir & TILE_UP:
|
|
r -= 1
|
|
elif dir & TILE_DOWN:
|
|
r += 1
|
|
elif dir & TILE_LEFT:
|
|
c -= 1
|
|
elif dir & TILE_RIGHT:
|
|
c += 1
|
|
else:
|
|
logic_log.error("bad direction % dir")
|
|
return r, c
|
|
|
|
# Choose a target column for the next up/down direction at a bottom or top T
|
|
def get_next_round_robin(rr_table, x):
|
|
target_col = rr_table[zp.round_robin_index[x]]
|
|
logic_log.debug("target: %d, indexes=%s, table=%s" % (target_col, str(zp.round_robin_index), rr_table))
|
|
zp.round_robin_index[x] += 1
|
|
if zp.round_robin_index[x] >= VPATH_NUM:
|
|
zp.round_robin_index[x] = 0
|
|
return target_col
|
|
|
|
# Find target column when enemy reaches top or bottom
|
|
def get_target_col(c, allowed_vert):
|
|
if allowed_vert & TILE_UP:
|
|
x = 1
|
|
rr_table = round_robin_up
|
|
else:
|
|
x = 0
|
|
rr_table = round_robin_down
|
|
|
|
target_col = get_next_round_robin(rr_table, x)
|
|
if target_col == c:
|
|
# don't go back up the same column, skip to next one
|
|
target_col = get_next_round_robin(rr_table, x)
|
|
|
|
if target_col < c:
|
|
current = TILE_LEFT
|
|
else:
|
|
current = TILE_RIGHT
|
|
actor_target_col[zp.current_actor] = target_col
|
|
return current
|
|
|
|
def check_midpoint(current):
|
|
# set up decision point flag to see if we have crossed the midpoint
|
|
# after the movement
|
|
if current & TILE_VERT:
|
|
sub = actor_ypixel[zp.current_actor]
|
|
return sub == Y_MIDPOINT
|
|
else:
|
|
sub = actor_xpixel[zp.current_actor]
|
|
return sub == X_MIDPOINT
|
|
|
|
def move_tile():
|
|
# check if moved to next tile. pixel fraction stays the same to keep
|
|
# the speed consistent, only the pixel gets adjusted
|
|
if actor_xpixel[zp.current_actor] < 0:
|
|
actor_col[zp.current_actor] -= 1
|
|
actor_xpixel[zp.current_actor] += X_TILEMAX
|
|
elif actor_xpixel[zp.current_actor] >= X_TILEMAX:
|
|
actor_col[zp.current_actor] += 1
|
|
actor_xpixel[zp.current_actor] -= X_TILEMAX
|
|
elif actor_ypixel[zp.current_actor] < 0:
|
|
actor_row[zp.current_actor] -= 1
|
|
actor_ypixel[zp.current_actor] += Y_TILEMAX
|
|
elif actor_ypixel[zp.current_actor] >= Y_TILEMAX:
|
|
actor_row[zp.current_actor] += 1
|
|
actor_ypixel[zp.current_actor] -= Y_TILEMAX
|
|
|
|
# Move enemy given the enemy index
|
|
def move_enemy():
|
|
current = actor_dir[zp.current_actor]
|
|
|
|
# check sub-pixel location to see if we've reached a decision point
|
|
temp = check_midpoint(current)
|
|
pixel_move(current)
|
|
move_tile()
|
|
s = "#%d: tile=%d,%d pix=%d,%d frac=%d,%d " % (zp.current_actor, actor_col[zp.current_actor], actor_row[zp.current_actor], actor_xpixel[zp.current_actor], actor_ypixel[zp.current_actor], actor_xfrac[zp.current_actor], actor_yfrac[zp.current_actor])
|
|
logic_log.debug(s)
|
|
pad.addstr(0 + zp.current_actor, 40, s)
|
|
if not temp:
|
|
if check_midpoint(current):
|
|
# crossed the midpoint! Make a decision on the next allowed direction
|
|
if actor_status[zp.current_actor] == ORBITER_NORMAL:
|
|
decide_orbiter()
|
|
else:
|
|
decide_direction()
|
|
|
|
def pixel_move(current):
|
|
if current & TILE_UP:
|
|
actor_yfrac[zp.current_actor] -= actor_yspeed[zp.current_actor]
|
|
if actor_yfrac[zp.current_actor] < 0:
|
|
actor_ypixel[zp.current_actor] -= 1
|
|
actor_yfrac[zp.current_actor] += 256
|
|
elif current & TILE_DOWN:
|
|
actor_yfrac[zp.current_actor] += actor_yspeed[zp.current_actor]
|
|
if actor_yfrac[zp.current_actor] > 255:
|
|
actor_ypixel[zp.current_actor] += 1
|
|
actor_yfrac[zp.current_actor] -= 256
|
|
elif current & TILE_LEFT:
|
|
actor_xfrac[zp.current_actor] -= actor_xspeed[zp.current_actor]
|
|
if actor_xfrac[zp.current_actor] < 0:
|
|
actor_xpixel[zp.current_actor] -= 1
|
|
actor_xfrac[zp.current_actor] += 256
|
|
elif current & TILE_RIGHT:
|
|
actor_xfrac[zp.current_actor] += actor_xspeed[zp.current_actor]
|
|
if actor_xfrac[zp.current_actor] > 255:
|
|
actor_xpixel[zp.current_actor] += 1
|
|
actor_xfrac[zp.current_actor] -= 256
|
|
|
|
def set_speed(current):
|
|
if current & TILE_VERT:
|
|
actor_xspeed[zp.current_actor] = 0
|
|
actor_yspeed[zp.current_actor] = level_speeds[zp.level]
|
|
else:
|
|
actor_xspeed[zp.current_actor] = level_speeds[zp.level]
|
|
actor_yspeed[zp.current_actor] = 0
|
|
|
|
def decide_orbiter():
|
|
current = actor_dir[zp.current_actor]
|
|
r = actor_row[zp.current_actor]
|
|
c = actor_col[zp.current_actor]
|
|
allowed = get_allowed_dirs(r, c)
|
|
|
|
if allowed & current:
|
|
# Can continue the current direction, so keep on doing it
|
|
|
|
logic_log.debug("orbiter %d: continuing %s" % (zp.current_actor, str_dirs(current)))
|
|
else:
|
|
# Can't continue, and because we must be at a corner, turn 90 degrees.
|
|
# So, if we are moving vertically, go horizontally, and vice versa.
|
|
|
|
if current & TILE_VERT:
|
|
current = allowed & TILE_HORZ
|
|
else:
|
|
current = allowed & TILE_VERT
|
|
actor_dir[zp.current_actor] = current
|
|
set_speed(current)
|
|
|
|
def decide_direction():
|
|
current = actor_dir[zp.current_actor]
|
|
r = actor_row[zp.current_actor]
|
|
c = actor_col[zp.current_actor]
|
|
allowed = get_allowed_dirs(r, c)
|
|
updown = actor_updown[zp.current_actor]
|
|
|
|
allowed_horz = allowed & TILE_HORZ
|
|
allowed_vert = allowed & TILE_VERT
|
|
if allowed_horz:
|
|
# left or right is available, we must go that way, because that's the
|
|
# Amidar(tm) way
|
|
|
|
if allowed_horz == TILE_HORZ:
|
|
# *Both* left and right are available, which means we're either in
|
|
# the middle of an box horz segment *or* at the top or bottom (but
|
|
# not at a corner)
|
|
|
|
if allowed_vert:
|
|
# At a T junction at the top or bottom. What we do depends on
|
|
# which direction we approached from
|
|
|
|
if current & TILE_VERT:
|
|
# approaching vertically means go L or R; choose direction
|
|
# based on a round robin so the enemy doesn't go back up
|
|
# the same path. Sets the target column for this enemy to
|
|
# be used when approaching the T horizontally
|
|
current = get_target_col(c, allowed_vert)
|
|
|
|
if allowed_vert & TILE_UP:
|
|
logic_log.debug("enemy %d: at bot T, new dir %x, col=%d target=%d" % (zp.current_actor, current, c, actor_target_col[zp.current_actor]))
|
|
else:
|
|
logic_log.debug("enemy %d: at top T, new dir %x, col=%d target=%d" % (zp.current_actor, current, c, actor_target_col[zp.current_actor]))
|
|
else:
|
|
# approaching horizontally, so check to see if this is the
|
|
# vpath to use
|
|
|
|
if actor_target_col[zp.current_actor] == c:
|
|
# Going vertical! Reverse desired up/down direction
|
|
updown = allowed_vert
|
|
current = allowed_vert
|
|
|
|
if allowed_vert & TILE_UP:
|
|
logic_log.debug("enemy %d: at bot T, reached target=%d, going up" % (zp.current_actor, c))
|
|
else:
|
|
logic_log.debug("enemy %d: at top T, reached target=%d, going down" % (zp.current_actor, c))
|
|
else:
|
|
# skip this vertical, keep on moving
|
|
|
|
if allowed_vert & TILE_UP:
|
|
logic_log.debug("enemy %d: at bot T, col=%d target=%d; skipping" % (zp.current_actor, c, actor_target_col[zp.current_actor]))
|
|
else:
|
|
logic_log.debug("enemy %d: at top T, col=%d target=%d; skipping" % (zp.current_actor, c, actor_target_col[zp.current_actor]))
|
|
|
|
else:
|
|
# no up or down available, so keep marching on in the same
|
|
# direction.
|
|
logic_log.debug("enemy %d: no up/down, keep moving %s" % (zp.current_actor, str_dirs(current)))
|
|
|
|
else:
|
|
# only one horizontal dir is available
|
|
|
|
if allowed_vert == TILE_VERT:
|
|
# At a left or right T junction...
|
|
|
|
if current & TILE_VERT:
|
|
# moving vertically. Have to take the horizontal path
|
|
current = allowed_horz
|
|
logic_log.debug("enemy %d: taking hpath, start moving %s" % (zp.current_actor, str_dirs(current)))
|
|
else:
|
|
# moving horizontally into the T, forcing a vertical turn.
|
|
# Go back to preferred up/down direction
|
|
current = updown
|
|
logic_log.debug("enemy %d: hpath end, start moving %s" % (zp.current_actor, str_dirs(current)))
|
|
else:
|
|
# At a corner, because this tile has exactly one vertical and
|
|
# one horizontal path.
|
|
|
|
if current & TILE_VERT:
|
|
# moving vertically, and because this is a corner, the
|
|
# target column must be set up
|
|
current = get_target_col(c, allowed_vert)
|
|
|
|
if allowed_horz & TILE_LEFT:
|
|
logic_log.debug("enemy %d: at right corner col=%d, heading left to target=%d" % (zp.current_actor, c, actor_target_col[zp.current_actor]))
|
|
else:
|
|
logic_log.debug("enemy %d: at left corner col=%d, heading right to target=%d" % (zp.current_actor, c, actor_target_col[zp.current_actor]))
|
|
else:
|
|
# moving horizontally along the top or bottom. If we get
|
|
# here, the target column must also be this column
|
|
current = allowed_vert
|
|
updown = allowed_vert
|
|
if allowed_vert & TILE_UP:
|
|
logic_log.debug("enemy %d: at bot corner col=%d with target %d, heading up" % (zp.current_actor, c, actor_target_col[zp.current_actor]))
|
|
else:
|
|
logic_log.debug("enemy %d: at top corner col=%d with target=%d, heading down" % (zp.current_actor, c, actor_target_col[zp.current_actor]))
|
|
|
|
elif allowed_vert:
|
|
# left or right is not available, so we must be in the middle of a
|
|
# vpath segment. Only thing to do is keep moving
|
|
logic_log.debug("enemy %d: keep moving %x" % (zp.current_actor, current))
|
|
|
|
else:
|
|
# only get here when moving into an illegal space
|
|
logic_log.debug("enemy %d: illegal move to %d,%d" % (zp.current_actor, r, c))
|
|
current = 0
|
|
|
|
actor_updown[zp.current_actor] = updown
|
|
actor_dir[zp.current_actor] = current
|
|
set_speed(current)
|
|
|
|
def move_player():
|
|
r = actor_row[zp.current_actor]
|
|
c = actor_col[zp.current_actor]
|
|
allowed = get_allowed_dirs(r, c)
|
|
current = actor_dir[zp.current_actor]
|
|
d = actor_input_dir[zp.current_actor]
|
|
pad.addstr(26, 0, "r=%d c=%d allowed=%s d=%s current=%s " % (r, c, str_dirs(allowed), str_dirs(d), str_dirs(current)))
|
|
|
|
turn_zone = False
|
|
x = actor_xpixel[zp.current_actor]
|
|
if x in [2, 3, 4]:
|
|
y = actor_ypixel[zp.current_actor]
|
|
if y in [2, 3, 4]:
|
|
turn_zone = True
|
|
|
|
if d:
|
|
if allowed & d:
|
|
# player wants to go in an allowed direction
|
|
player_log.debug("allowed, cur=%s, d=%s, turnzone=%s x=%d.%d y=%d.%d, r=%d c=%d" % (current, d, turn_zone, actor_xpixel[zp.current_actor], actor_xfrac[zp.current_actor], actor_ypixel[zp.current_actor], actor_yfrac[zp.current_actor], actor_col[zp.current_actor], actor_row[zp.current_actor]))
|
|
# is desired direction a change in axes?
|
|
if current & TILE_VERT: # current is vertical
|
|
if d & TILE_HORZ: # dir change; wants horizontal
|
|
if turn_zone:
|
|
actor_ypixel[zp.current_actor] = 3
|
|
actor_yfrac[zp.current_actor] = 0
|
|
actor_dir[zp.current_actor] = d
|
|
set_speed(d)
|
|
pixel_move(d)
|
|
else: # wants horz but not in turn zone
|
|
if current & allowed:
|
|
player_log.debug("same")
|
|
actor_dir[zp.current_actor] = current
|
|
set_speed(current)
|
|
pixel_move(current)
|
|
move_tile()
|
|
else: # opposite of allowed; valid before turn zone
|
|
player_log.debug("opposite!")
|
|
actor_dir[zp.current_actor] = current
|
|
set_speed(current)
|
|
pixel_move(current)
|
|
move_tile()
|
|
else: # current vertical, wants vertical, allowed
|
|
actor_dir[zp.current_actor] = d
|
|
set_speed(d)
|
|
pixel_move(d)
|
|
move_tile()
|
|
else: # current is horizontal
|
|
if d & TILE_VERT: # dir change; wants vertical
|
|
y = actor_ypixel[zp.current_actor]
|
|
if y in [2, 3, 4]:
|
|
actor_xpixel[zp.current_actor] = 3
|
|
actor_xfrac[zp.current_actor] = 0
|
|
actor_dir[zp.current_actor] = d
|
|
set_speed(d)
|
|
pixel_move(d)
|
|
else: # wants vert but not in turn zone
|
|
if current & allowed:
|
|
actor_dir[zp.current_actor] = current
|
|
set_speed(current)
|
|
pixel_move(current)
|
|
move_tile()
|
|
else: # opposite of allowed; valid before turn zone
|
|
player_log.debug("opposite!")
|
|
actor_dir[zp.current_actor] = current
|
|
set_speed(current)
|
|
pixel_move(current)
|
|
move_tile()
|
|
else: # current horz, wants horz, allowed
|
|
actor_dir[zp.current_actor] = d
|
|
pixel_move(d)
|
|
move_tile()
|
|
else:
|
|
# player wants to go in an illegal direction. instead, continue in
|
|
# direction that was last requested
|
|
player_log.debug("illegal: allowed=%s cur=%s, d=%s, turnzone=%s x=%d.%d y=%d.%d, r=%d c=%d" % (allowed, current, d, turn_zone, actor_xpixel[zp.current_actor], actor_xfrac[zp.current_actor], actor_ypixel[zp.current_actor], actor_yfrac[zp.current_actor], actor_col[zp.current_actor], actor_row[zp.current_actor]))
|
|
if allowed & current:
|
|
player_log.debug("continuing current dir")
|
|
actor_dir[zp.current_actor] = current
|
|
set_speed(current)
|
|
pixel_move(current)
|
|
move_tile()
|
|
# r, c = get_next_tile(r, c, current)
|
|
# actor_row[zp.current_actor] = r
|
|
# actor_col[zp.current_actor] = c
|
|
|
|
|
|
##### Collision detection
|
|
|
|
# Check possible collisions between the current player and any enemies
|
|
def check_collisions():
|
|
r = actor_row[zp.current_actor]
|
|
c = actor_col[zp.current_actor]
|
|
enemy_index = FIRST_AMIDAR
|
|
while enemy_index <= zp.last_enemy:
|
|
# Will provide pac-man style bug where they could pass through each
|
|
# other because it's only checking tiles
|
|
if actor_row[enemy_index] == r and actor_col[enemy_index] == c:
|
|
start_exploding()
|
|
break
|
|
enemy_index += 1
|
|
|
|
def start_exploding():
|
|
actor_status[zp.current_actor] = PLAYER_EXPLODING
|
|
actor_frame_counter[zp.current_actor] = EXPLODING_TIME
|
|
|
|
|
|
##### Scoring routines
|
|
|
|
def check_dots():
|
|
r = actor_row[zp.current_actor]
|
|
c = actor_col[zp.current_actor]
|
|
if has_dot(r, c):
|
|
dot_eaten_row[zp.current_actor] = r
|
|
dot_eaten_col[zp.current_actor] = c
|
|
|
|
# Update maze here so we can check which player closed off a box
|
|
addr = mazerow(r)
|
|
addr[c] &= ~TILE_DOT
|
|
|
|
player_score[zp.current_actor] += DOT_SCORE
|
|
|
|
def update_background():
|
|
zp.current_actor = 0
|
|
while zp.current_actor < zp.num_players:
|
|
if dot_eaten_col[zp.current_actor] < 128:
|
|
# Here we update the screen; note the maze has already been updated
|
|
# but we don't change the background until now so sprites can
|
|
# restore their saved backgrounds first.
|
|
|
|
r = dot_eaten_row[zp.current_actor]
|
|
c = dot_eaten_col[zp.current_actor]
|
|
addr = screenrow(r)
|
|
addr[c] &= ~TILE_DOT
|
|
|
|
# mark as completed
|
|
dot_eaten_col[zp.current_actor] = 255
|
|
update_score()
|
|
zp.current_actor += 1
|
|
|
|
paint_boxes()
|
|
|
|
def paint_boxes():
|
|
x = 0
|
|
pad.addstr(28, 0, "Checking box:")
|
|
while x < NUM_BOX_PAINTING_PARAMS * 16:
|
|
pad.addstr(29, x, "%d " % x)
|
|
if box_painting[x] > 0:
|
|
c1 = box_painting[x]
|
|
r1 = box_painting[x + 1]
|
|
r2 = box_painting[x + 2]
|
|
i = box_painting[x + 3]
|
|
box_log.debug("Painting box line, player %d at %d,%d" % (i, r1, c1))
|
|
pad.addstr(30, 0, "painting box line at %d,%d" % (r1, c1))
|
|
addr = screenrow(r1)
|
|
for c in range(BOX_WIDTH):
|
|
if i == 0:
|
|
addr[c1 + c] = ord("X")
|
|
else:
|
|
addr[c1 + c] = ord(".")
|
|
r1 += 1
|
|
print "ROW", r1
|
|
box_painting[x + 1] = r1
|
|
if r1 >= r2:
|
|
box_painting[x] = 0
|
|
x += NUM_BOX_PAINTING_PARAMS
|
|
|
|
def init_static_background():
|
|
zp.current_actor = 0
|
|
while zp.current_actor < zp.num_players:
|
|
row = player_score_row[zp.current_actor]
|
|
pad.addstr(row - 1, MAZE_SCORE_COL, " ")
|
|
pad.addstr(row, MAZE_SCORE_COL, "Player%d" % (zp.current_actor + 1))
|
|
zp.current_actor += 1
|
|
|
|
def show_lives(row, num):
|
|
i = 1
|
|
col = SCREEN_COLS
|
|
while col > MAZE_SCORE_COL:
|
|
col -= 1
|
|
if i < num:
|
|
c = "*"
|
|
else:
|
|
c = " "
|
|
pad.addch(row, col, ord(c))
|
|
i += 1
|
|
|
|
def update_score():
|
|
row = player_score_row[zp.current_actor]
|
|
if actor_status[zp.current_actor] == GAME_OVER:
|
|
pad.addstr(row - 1, MAZE_SCORE_COL, "GAME ")
|
|
pad.addstr(row, MAZE_SCORE_COL, " OVER")
|
|
else:
|
|
pad.addstr(row + 1, MAZE_SCORE_COL, " %06d" % player_score[zp.current_actor])
|
|
show_lives(row + 2, player_lives[zp.current_actor])
|
|
|
|
|
|
##### User input routines
|
|
|
|
def read_user_input():
|
|
if CURSES:
|
|
read_curses()
|
|
|
|
def read_curses():
|
|
global config_quit, config_start
|
|
|
|
key = pad.getch()
|
|
pad.addstr(25, 0, "key = %d " % key)
|
|
if key > 0:
|
|
print "%d " % key
|
|
if key == ord('q'):
|
|
print "QUIT!!!"
|
|
config_quit = 1
|
|
elif key == 27:
|
|
print("QUIT!!!, but need to press ESC one more time for some reason")
|
|
config_quit = 1
|
|
|
|
if key == curses.KEY_UP:
|
|
actor_input_dir[0] = TILE_UP
|
|
elif key == curses.KEY_DOWN:
|
|
actor_input_dir[0] = TILE_DOWN
|
|
elif key == curses.KEY_LEFT:
|
|
actor_input_dir[0] = TILE_LEFT
|
|
elif key == curses.KEY_RIGHT:
|
|
actor_input_dir[0] = TILE_RIGHT
|
|
else:
|
|
actor_input_dir[0] = 0
|
|
|
|
if key == ord(',') or key == ord('.'):
|
|
actor_input_dir[1] = TILE_UP
|
|
elif key == ord('o'):
|
|
actor_input_dir[1] = TILE_DOWN
|
|
elif key == ord('a'):
|
|
actor_input_dir[1] = TILE_LEFT
|
|
elif key == ord('e'):
|
|
actor_input_dir[1] = TILE_RIGHT
|
|
else:
|
|
actor_input_dir[1] = 0
|
|
|
|
if key == ord('1'):
|
|
config_start = 1
|
|
|
|
|
|
##### Game loop
|
|
|
|
def game_loop():
|
|
global config_quit
|
|
|
|
count = 0
|
|
zp.num_sprites_drawn = 0
|
|
countdown_time = END_GAME_TIME
|
|
time_0 = time.time()
|
|
while True:
|
|
game_log.debug("Turn %d" % count)
|
|
read_user_input()
|
|
if config_quit:
|
|
return
|
|
game_log.debug(chr(12))
|
|
|
|
zp.current_actor = FIRST_AMIDAR
|
|
while zp.current_actor <= zp.last_enemy:
|
|
move_enemy()
|
|
zp.current_actor += 1
|
|
|
|
zp.current_actor = 0
|
|
still_alive = 0
|
|
while zp.current_actor < zp.num_players:
|
|
if actor_status[zp.current_actor] == PLAYER_REGENERATING:
|
|
# If regenerating, change to alive if the player starts to move
|
|
if actor_input_dir[zp.current_actor] > 0:
|
|
actor_status[zp.current_actor] = PLAYER_ALIVE
|
|
if actor_status[zp.current_actor] == PLAYER_ALIVE:
|
|
# only move and check collisions if alive
|
|
move_player()
|
|
check_collisions()
|
|
if actor_status[zp.current_actor] == PLAYER_ALIVE:
|
|
# only check for points if still alive
|
|
check_dots()
|
|
check_boxes()
|
|
if actor_status[zp.current_actor] != GAME_OVER:
|
|
still_alive += 1
|
|
zp.current_actor += 1
|
|
|
|
if still_alive == 0:
|
|
countdown_time -= 1
|
|
if countdown_time <= 0:
|
|
break
|
|
|
|
erase_sprites()
|
|
update_background()
|
|
draw_actors()
|
|
show_screen()
|
|
#time.sleep(.01)
|
|
if count % 50 == 0:
|
|
pad.addstr(20, MAZE_SCORE_COL, "%f" % (time.time() - time_0))
|
|
|
|
count += 1
|
|
|
|
|
|
# Debugging stuff below here, things that won't get converted to 6502
|
|
|
|
def str_dirs(d):
|
|
s = ""
|
|
if d & TILE_LEFT:
|
|
s += "L"
|
|
if d & TILE_RIGHT:
|
|
s += "R"
|
|
if d & TILE_UP:
|
|
s += "U"
|
|
if d & TILE_DOWN:
|
|
s += "D"
|
|
return s
|
|
|
|
def get_text_maze(m):
|
|
lines = []
|
|
for y in range(24):
|
|
line = ""
|
|
for x in range(33):
|
|
tile = m[y][x]
|
|
if tile < 32:
|
|
line += tilechars[tile]
|
|
else:
|
|
line += chr(tile)
|
|
lines.append(line)
|
|
return lines
|
|
|
|
actor_history = [[], [], [], [], [], [], []]
|
|
player_history = [[], [], [], []]
|
|
|
|
def print_maze(append=""):
|
|
m = maze.copy()
|
|
|
|
# Loop by time history instead of by enemy number so enemy #1 doesn't
|
|
# always overwrite enemy #0's trail
|
|
remain = True
|
|
index = 0
|
|
while remain:
|
|
remain = False
|
|
for i in range(zp.num_enemies):
|
|
if index < len(actor_history[i]):
|
|
remain = True
|
|
r, c = actor_history[i][index]
|
|
m[r][c] = ord("0") + i
|
|
for i in range(zp.num_players):
|
|
if index < len(player_history[i]):
|
|
remain = True
|
|
r, c = player_history[i][index]
|
|
m[r][c] = ord("$") + i
|
|
index += 1
|
|
lines = get_text_maze(m)
|
|
for i in range(24):
|
|
print "%02d %s%s" % (i, lines[i], append)
|
|
|
|
def print_screen():
|
|
print_maze("_______")
|
|
|
|
|
|
def show_screen():
|
|
for r in range(24):
|
|
for c in range(33):
|
|
tile = screen[r, c]
|
|
if tile < 16:
|
|
val = curseschars[tile]
|
|
pad.addch(r, c, val, curses.color_pair(1))
|
|
elif tile < 32:
|
|
val = curseschars[tile]
|
|
pad.addch(r, c, val, curses.A_NORMAL)
|
|
else:
|
|
val = int(tile)
|
|
pad.addch(r, c, val, curses.A_NORMAL)
|
|
pad.refresh(0, 0, 0, 0, 29, 39)
|
|
lines = get_text_maze(screen)
|
|
for i in range(24):
|
|
game_log.debug("%02d %s" % (i, lines[i]))
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
global config_start
|
|
|
|
#random.seed(31415)
|
|
if "-1" in sys.argv:
|
|
config_start = 1
|
|
init()
|