fujirun/python/pyapple2/mazegen.py

1082 lines
31 KiB
Python
Raw Normal View History

#!/usr/bin/env python
2017-04-24 20:20:01 +00:00
# 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_______
2017-04-25 06:36:48 +00:00
#
# 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
2017-04-24 20:20:01 +00:00
import time
2017-04-24 20:20:01 +00:00
import random
2017-04-25 23:44:08 +00:00
import curses
2017-04-24 20:20:01 +00:00
import numpy as np
2017-04-25 23:44:08 +00:00
import logging
init_log = logging.getLogger("init")
logic_log = logging.getLogger("logic")
draw_log = logging.getLogger("draw")
maze_log = logging.getLogger("maze")
2017-04-26 22:12:58 +00:00
box_log = logging.getLogger("maze")
2017-04-25 23:44:08 +00:00
game_log = logging.getLogger("game")
CURSES = 1
##### Game loader
2017-04-25 23:44:08 +00:00
pad = None
def init():
if CURSES:
curses.wrapper(init_screen)
else:
main()
2017-04-25 23:44:08 +00:00
def init_screen(*args, **kwargs):
global pad, curseschars
curses.use_default_colors()
2017-04-26 04:46:59 +00:00
curses.start_color()
curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_WHITE)
2017-04-25 23:44:08 +00:00
curses.curs_set(0)
pad = curses.newpad(30, 40)
pad.timeout(0)
pad.keypad(1)
2017-04-25 23:44:08 +00:00
# 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()
def main():
global level, cur_enemies, cur_players
init_maze()
print_maze()
screen[:,0:33] = maze
2017-04-25 23:44:08 +00:00
show_screen()
level = 1
cur_enemies = level_enemies[level]
cur_players = 1
init_enemies()
init_players()
2017-04-26 22:32:51 +00:00
init_static_background()
game_loop()
print_maze()
##### Memory usage
# Zero page
r = 0
c = 0
round_robin_index = [0, 0] # down, up
# 2 byte addresses
maze = np.empty((24, 33), dtype=np.uint8)
2017-04-24 21:35:03 +00:00
screen = np.empty((24, 40), dtype=np.uint8)
tiledown = 0x1
tileup = 0x2
tileright = 0x4
tileleft= 0x8
2017-04-25 06:36:48 +00:00
tilehorz = tileleft | tileright
tilevert = tileup | tiledown
dir_mask = 0x0f
tiledot = 0x10
vpath_num = 7
2017-04-26 22:12:58 +00:00
box_width = 4
vpath_col_spacing = box_width + 1
vpath_cols = [
1 + 0 * vpath_col_spacing,
1 + 1 * vpath_col_spacing,
1 + 2 * vpath_col_spacing,
1 + 3 * vpath_col_spacing,
1 + 4 * vpath_col_spacing,
1 + 5 * vpath_col_spacing,
1 + 6 * vpath_col_spacing,
]
vpath_top_tile = [
tiledot|tiledown|tileright,
tiledot|tiledown|tileleft|tileright,
tiledot|tiledown|tileleft|tileright,
tiledot|tiledown|tileleft|tileright,
tiledot|tiledown|tileleft|tileright,
tiledot|tiledown|tileleft|tileright,
tiledot|tiledown|tileleft,
]
vpath_bot_tile = [
tiledot|tileup|tileright,
tiledot|tileup|tileleft|tileright,
tiledot|tileup|tileleft|tileright,
tiledot|tileup|tileleft|tileright,
tiledot|tileup|tileleft|tileright,
tiledot|tileup|tileleft|tileright,
tiledot|tileup|tileleft,
]
2017-04-24 20:20:01 +00:00
# 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",
2017-04-24 21:35:03 +00:00
"@", # 32: enemy (temporary)
"$", # 33: player
]
2017-04-25 23:44:08 +00:00
# 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
mazetoprow = 1
mazebotrow = 22
screenrows = 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
mazeleftcol = 1
mazerightcol = 31
mazescorecol = 33
2017-04-26 22:32:51 +00:00
p1scorerow = 2
p2scorerow = 5
screencols = 40
2017-04-26 02:50:14 +00:00
# Orbiter goes around the outside border, but not through the maze
orbiterstartcol = mazerightcol
orbiterstartrow = (mazetoprow + mazebotrow) / 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 6 (inclusive) used for column starting positions
def get_rand7():
return random.randint(0, 6)
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 = list(vpath_cols)
x = 10
while x >= 0:
i1 = get_rand7()
i2 = get_rand7()
old1 = r[i1]
r[i1] = r[i2]
r[i2] = old1
x -= 1
return r
###### Level creation functions
def clear_maze():
y = 0
while y < screenrows:
addr = mazerow(y)
x = 0
while x < mazescorecol:
addr[x] = 0
x += 1
y += 1
2017-04-26 22:12:58 +00:00
init_boxes()
2017-04-26 22:12:58 +00:00
# Set all elements in a row to dot + left + right; only top and bottom
def setrow(row):
addr = mazerow(row)
x = mazeleftcol
while x <= mazerightcol:
addr[x] = tiledot|tileleft|tileright
x += 1
2017-04-26 22:12:58 +00:00
# Create all 7 vpaths, using top/bot character from a list to handle both
# corners and T connections.
def setvpath(col):
x = vpath_cols[col]
y = mazetoprow
addr = mazerow(y)
addr[x] = vpath_top_tile[col]
y += 1
while y < mazebotrow:
addr = mazerow(y)
addr[x] = tiledot|tileup|tiledown
y += 1
addr = mazerow(y)
addr[x] = vpath_bot_tile[col]
2017-04-24 20:20:01 +00:00
2017-04-26 22:12:58 +00:00
# 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.
2017-04-24 20:20:01 +00:00
def sethpath(col):
x1_save = vpath_cols[col - 1]
x2 = vpath_cols[col]
y = mazetoprow
2017-04-26 22:12:58 +00:00
start_box(y, x1_save)
2017-04-24 20:20:01 +00:00
y += get_rand_spacing()
while y < mazebotrow - 1:
addr = mazerow(y)
2017-04-24 20:20:01 +00:00
# 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 & tileright:
2017-04-25 23:44:08 +00:00
maze_log.debug("at y=%d on col %d, found same hpath level at col %d" % (y, col, col + 1))
2017-04-24 20:20:01 +00:00
y -= 1
addr = mazerow(y)
2017-04-26 22:12:58 +00:00
add_box(y)
2017-04-24 20:20:01 +00:00
x = x1_save
addr[x] = tiledot|tileup|tiledown|tileright
x += 1
while x < x2:
addr[x] = tiledot|tileleft|tileright
x += 1
addr[x2] = tiledot|tileup|tiledown|tileleft
y += get_rand_spacing()
2017-04-26 22:12:58 +00:00
add_box(mazebotrow)
2017-04-24 20:20:01 +00:00
def init_maze():
clear_maze()
# Draw top and bottom; no intersections anywhere. Corners and T
# intesections will be placed in setvpath
setrow(mazetoprow)
setrow(mazebotrow)
# 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.
2017-04-24 20:20:01 +00:00
counter = vpath_num
counter -= 1
while counter > 0: # note >, not >=
sethpath(counter)
counter -= 1
2017-04-26 22:12:58 +00:00
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.
# 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_______
next_level_box = 0 # in the assembly, this will be it the x or y register or zero page
level_boxes = [0] * 10 * 6 * 8
box_col_save = 0
box_row_save = 0
# 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.
#
# xbyte, ytop, ybot
box_painting = [0] * 3 * 16
def init_boxes():
next_level_box = 0
def start_box(r, c):
global box_col_save, box_row_save
box_col_save = c
box_row_save = r
def add_box(r):
global next_level_box, box_row_save
i = next_level_box
level_boxes[i] = box_col_save
level_boxes[i + 1] = box_row_save
level_boxes[i + 2] = r
box_row_save = r
next_level_box += 3
def finish_boxes():
i = next_level_box
level_boxes[i] = 0xff
def check_boxes():
x = 0
2017-04-26 22:32:51 +00:00
pad.addstr(28, 0, str(level_boxes[0:21]))
2017-04-26 22:12:58 +00:00
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]
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]
while r1 < r2:
addr = mazerow(r1)
dot |= addr[c] | addr[c + 5]
r1 += 1
if (dot & tiledot) == 0:
# No dots anywhere! Start painting
mark_box_for_painting(r1_save, r2, c + 1)
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 at %d,%d -> %d,%d" % (r1, c, r2, c + box_width))
x = 0
while x < 3 * 16:
if box_painting[x] == 0:
box_painting[x] = c
box_painting[x + 1] = r1
box_painting[x + 1] = r2
break
x += 3
pad.addstr(27, 0, "starting box %d,%d -> %d,%d" % (r1, c, r2, c + box_width))
##### Gameplay storage
config_num_players = 1
config_quit = 0
level = -1
2017-04-26 02:50:14 +00:00
level_enemies = [255, 4, 5, 6, 7, 8] # level starts counting from 1, so dummy zeroth level info
level_speeds = [255, 0, 0, 0, 0, 0] # probably needs to be 16 bit
level_start_col = [
[255, 255, 255, 255],
[3, 0, 0, 0],
[2, 4, 0, 0],
[1, 3, 5, 0],
[0, 2, 4, 6],
]
2017-04-24 20:20:01 +00:00
# Hardcoded, up to 8 enemies because there are max of 7 vpaths + 1 orbiter
2017-04-26 02:50:14 +00:00
# Enemy #0 is the orbiter
max_enemies = 8
2017-04-24 21:35:03 +00:00
cur_enemies = -1
enemy_col = [0, 0, 0, 0, 0, 0, 0, 0] # current tile column
enemy_row = [0, 0, 0, 0, 0, 0, 0, 0] # current tile row
enemy_updown = [0, 0, 0, 0, 0, 0, 0, 0] # preferred direction
enemy_dir = [0, 0, 0, 0, 0, 0, 0, 0] # actual direction
enemy_target_col = [0, 0, 0, 0, 0, 0, 0, 0] # target column at bot or top T
round_robin_up = [0, 0, 0, 0, 0, 0, 0]
round_robin_down = [0, 0, 0, 0, 0, 0, 0]
2017-04-24 21:35:03 +00:00
# Hardcoded, up to 4 players
max_players = 4
cur_players = 1
player_col = [0, 0, 0, 0] # current tile col
player_row = [0, 0, 0, 0] # current tile row
player_input_dir = [0, 0, 0, 0] # current joystick input direction
player_dir = [0, 0, 0, 0] # current movement direction
2017-04-26 04:46:59 +00:00
dot_eaten_row = [255, 255, 255, 255] # dot eaten by player
dot_eaten_col = [255, 255, 255, 255]
2017-04-26 22:32:51 +00:00
player_score = [0, 0, 0, 0]
2017-04-24 21:35:03 +00:00
2017-04-26 22:32:51 +00:00
# Scores
dot_score = 10
paint_score_per_line = 100
2017-04-25 06:36:48 +00:00
##### Gameplay initialization
2017-04-24 21:35:03 +00:00
def init_enemies():
2017-04-26 02:50:14 +00:00
enemy_col[0] = orbiterstartcol
enemy_row[0] = orbiterstartrow
enemy_dir[0] = tileup
x = 1
2017-04-24 21:35:03 +00:00
randcol = get_col_randomizer()
while x < cur_enemies:
enemy_col[x] = randcol[x]
2017-04-24 21:35:03 +00:00
enemy_row[x] = mazetoprow
enemy_updown[x] = tiledown
enemy_dir[x] = tiledown
enemy_target_col[x] = 0 # Arbitrary, just need valid default
2017-04-24 21:35:03 +00:00
x += 1
round_robin_up[:] = get_col_randomizer()
round_robin_down[:] = get_col_randomizer()
round_robin_index[:] = [0, 0]
2017-04-24 21:35:03 +00:00
def get_col_start():
addr = level_start_col[cur_players]
return addr
def init_players():
x = 0
start = get_col_start()
while x < cur_players:
2017-04-25 06:36:48 +00:00
player_col[x] = vpath_cols[start[x]]
2017-04-24 21:35:03 +00:00
player_row[x] = mazebotrow
player_input_dir[x] = 0
player_dir[x] = 0
x += 1
##### 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?
num_sprites_drawn = 0
# Erase sprites in reverse order that they're drawn to restore the background
# properly
def erase_sprites():
global num_sprites_drawn
while num_sprites_drawn > 0:
num_sprites_drawn -= 1
i = num_sprites_drawn
val = sprite_backing_store[i]
r = last_sprite_y[i]
addr = screenrow(r)
c = last_sprite_byte[i]
2017-04-25 23:44:08 +00:00
draw_log.debug("restoring background %d @ (%d,%d)" % (i, r, c))
addr[c] = val
def save_backing_store(r, c, sprite):
global num_sprites_drawn
addr = mazerow(r)
i = num_sprites_drawn
2017-04-25 23:44:08 +00:00
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] = sprite
sprite_backing_store[i] = addr[c]
sprite_bytes_per_row[i] = 1
sprite_num_rows[i] = 1
num_sprites_drawn += 1
def draw_sprite(r, c, sprite):
save_backing_store(r, c, sprite)
addr = screenrow(r)
addr[c] = sprite
def draw_enemies():
i = 0
while i < cur_enemies:
r = enemy_row[i]
c = enemy_col[i]
sprite = get_enemy_sprite(i)
draw_sprite(r, c, sprite)
enemy_history[i].append((r, c))
i += 1
2017-04-24 21:35:03 +00:00
def draw_players():
i = 0
while i < cur_players:
r = player_row[i]
c = player_col[i]
sprite = get_player_sprite(i)
draw_sprite(r, c, sprite)
player_history[i].append((r, c))
2017-04-24 21:35:03 +00:00
i += 1
def get_enemy_sprite(i):
return ord("0") + i
def get_player_sprite(i):
return ord("$") + i
##### Game logic
2017-04-25 06:36:48 +00:00
# Determine which of the 4 directions is allowed at the given row, col
def get_allowed_dirs(r, c):
addr = mazerow(r)
2017-04-25 06:36:48 +00:00
allowed = addr[c] & dir_mask
return allowed
2017-04-26 04:46:59 +00:00
# See if current tile has a dot
def has_dot(r, c):
addr = mazerow(r)
return addr[c] & tiledot
# clear a dot
def clear_dot(r, c):
addr = mazerow(r)
addr[c] &= ~tiledot
# Determine the tile location given the direction of the actor's movement
2017-04-25 06:36:48 +00:00
def get_next_tile(r, c, dir):
if dir & tileup:
r -= 1
elif dir & tiledown:
r += 1
elif dir & tileleft:
c -= 1
elif dir & tileright:
c += 1
else:
2017-04-25 23:44:08 +00:00
logic_log.error("bad direction % dir")
2017-04-25 06:36:48 +00:00
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[round_robin_index[x]]
2017-04-25 23:44:08 +00:00
logic_log.debug("target: %d, indexes=%s, table=%s" % (target_col, str(round_robin_index), rr_table))
round_robin_index[x] += 1
if round_robin_index[x] >= vpath_num:
round_robin_index[x] = 0
return target_col
# Find target column when enemy reaches top or bottom
def get_target_col(i, c, allowed_vert):
if allowed_vert & tileup:
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 = tileleft
else:
current = tileright
enemy_target_col[i] = target_col
return current
2017-04-26 02:50:14 +00:00
# Move orbiter
def move_orbiter():
r = enemy_row[0]
c = enemy_col[0]
current = enemy_dir[0]
r, c = get_next_tile(r, c, current)
enemy_row[0] = r
enemy_col[0] = c
allowed = get_allowed_dirs(r, c)
if allowed & current:
# Can continue the current direction, so keep on doing it
logic_log.debug("orbiter: continuing %s" % 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 & tilevert:
current = allowed & tilehorz
else:
current = allowed & tilevert
enemy_dir[0] = current
2017-04-25 06:36:48 +00:00
# Move enemy given the enemy index
def move_enemy(i):
r = enemy_row[i]
c = enemy_col[i]
current = enemy_dir[i]
r, c = get_next_tile(r, c, current)
enemy_row[i] = r
enemy_col[i] = c
allowed = get_allowed_dirs(r, c)
updown = enemy_updown[i]
allowed_horz = allowed & tilehorz
allowed_vert = allowed & tilevert
if allowed_horz:
# left or right is available, we must go that way, because that's the
# Amidar(tm) way
if allowed_horz == tilehorz:
# *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 & tilevert:
# 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(i, c, allowed_vert)
if allowed_vert & tileup:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at bot T, new dir %x, col=%d target=%d" % (i, current, c, enemy_target_col[i]))
else:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at top T, new dir %x, col=%d target=%d" % (i, current, c, enemy_target_col[i]))
2017-04-25 06:36:48 +00:00
else:
# approaching horizontally, so check to see if this is the
# vpath to use
if enemy_target_col[i] == c:
# Going vertical! Reverse desired up/down direction
updown = allowed_vert
current = allowed_vert
if allowed_vert & tileup:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at bot T, reached target=%d, going up" % (i, c))
else:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at top T, reached target=%d, going down" % (i, c))
else:
# skip this vertical, keep on moving
if allowed_vert & tileup:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at bot T, col=%d target=%d; skipping" % (i, c, enemy_target_col[i]))
else:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at top T, col=%d target=%d; skipping" % (i, c, enemy_target_col[i]))
2017-04-25 06:36:48 +00:00
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" % (i, str_dirs(current)))
2017-04-25 06:36:48 +00:00
else:
# only one horizontal dir is available
if allowed_vert == tilevert:
# At a left or right T junction...
2017-04-25 06:36:48 +00:00
if current & tilevert:
# moving vertically. Have to take the horizontal path
2017-04-25 06:36:48 +00:00
current = allowed_horz
logic_log.debug("enemy %d: taking hpath, start moving %s" % (i, str_dirs(current)))
2017-04-25 06:36:48 +00:00
else:
# moving horizontally into the T, forcing a vertical turn.
# Go back to preferred up/down direction
2017-04-25 06:36:48 +00:00
current = updown
logic_log.debug("enemy %d: hpath end, start moving %s" % (i, str_dirs(current)))
2017-04-25 06:36:48 +00:00
else:
# At a corner, because this tile has exactly one vertical and
# one horizontal path.
2017-04-25 06:36:48 +00:00
if current & tilevert:
# moving vertically, and because this is a corner, the
# target column must be set up
current = get_target_col(i, c, allowed_vert)
if allowed_horz & tileleft:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at right corner col=%d, heading left to target=%d" % (i, c, enemy_target_col[i]))
else:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at left corner col=%d, heading right to target=%d" % (i, c, enemy_target_col[i]))
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 & tileup:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at bot corner col=%d with target %d, heading up" % (i, c, enemy_target_col[i]))
else:
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: at top corner col=%d with target=%d, heading down" % (i, c, enemy_target_col[i]))
elif allowed_vert:
2017-04-25 06:36:48 +00:00
# left or right is not available, so we must be in the middle of a
# vpath segment. Only thing to do is keep moving
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: keep moving %x" % (i, current))
2017-04-25 06:36:48 +00:00
else:
# only get here when moving into an illegal space
2017-04-25 23:44:08 +00:00
logic_log.debug("enemy %d: illegal move to %d,%d" % (i, r, c))
current = 0
2017-04-25 06:36:48 +00:00
enemy_updown[i] = updown
enemy_dir[i] = current
def move_player(i):
r = player_row[i]
c = player_col[i]
allowed = get_allowed_dirs(r, c)
current = player_dir[i]
d = player_input_dir[i]
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)))
if d:
if allowed & d:
# player wants to go in an allowed direction, so go!
player_dir[i] = d
r, c = get_next_tile(r, c, d)
player_row[i] = r
player_col[i] = c
else:
# player wants to go in an illegal direction. instead, continue in
# direction that was last requested
if allowed & current:
r, c = get_next_tile(r, c, current)
player_row[i] = r
player_col[i] = c
2017-04-26 22:32:51 +00:00
##### Scoring routines
2017-04-26 04:46:59 +00:00
def check_dots(i):
r = player_row[i]
c = player_col[i]
if has_dot(r, c):
dot_eaten_row[i] = r
dot_eaten_col[i] = c
2017-04-26 22:32:51 +00:00
player_score[i] += dot_score
2017-04-26 04:46:59 +00:00
def update_background():
for i in range(cur_players):
if dot_eaten_col[i] < 128:
# dot has been marked as being eaten, so deal with it. Somehow.
r = dot_eaten_row[i]
c = dot_eaten_col[i]
addr = mazerow(r)
addr[c] &= ~tiledot
addr = screenrow(r)
addr[c] &= ~tiledot
# mark as completed
dot_eaten_col[i] = 255
2017-04-26 22:32:51 +00:00
update_score()
def init_static_background():
pad.addstr(p1scorerow, mazescorecol, "Player1")
pad.addstr(p2scorerow, mazescorecol, "Player2")
def update_score():
pad.addstr(p1scorerow+1, mazescorecol, " %05d" % player_score[0])
pad.addstr(p2scorerow+1, mazescorecol, " %05d" % player_score[1])
2017-04-26 04:46:59 +00:00
##### User input routines
def read_user_input():
if CURSES:
read_curses()
def read_curses():
global config_quit
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:
player_input_dir[0] = tileup
elif key == curses.KEY_DOWN:
player_input_dir[0] = tiledown
elif key == curses.KEY_LEFT:
player_input_dir[0] = tileleft
elif key == curses.KEY_RIGHT:
player_input_dir[0] = tileright
else:
player_input_dir[0] = 0
2017-04-25 06:36:48 +00:00
##### Game loop
2017-04-25 06:36:48 +00:00
def game_loop():
global config_quit
2017-04-25 06:36:48 +00:00
count = 0
num_sprites_drawn = 0
while True:
2017-04-25 23:44:08 +00:00
game_log.debug("Turn %d" % count)
read_user_input()
if config_quit:
return
2017-04-25 23:44:08 +00:00
game_log.debug(chr(12))
2017-04-25 06:36:48 +00:00
2017-04-26 02:50:14 +00:00
move_orbiter() # always enemy #0
for i in range(1, cur_enemies):
2017-04-25 06:36:48 +00:00
move_enemy(i)
for i in range(cur_players):
move_player(i)
2017-04-26 04:46:59 +00:00
check_dots(i)
2017-04-25 06:36:48 +00:00
check_boxes()
erase_sprites()
update_background()
draw_enemies()
draw_players()
show_screen()
time.sleep(.02)
2017-04-25 06:36:48 +00:00
count += 1
# Debugging stuff below here, things that won't get converted to 6502
2017-04-24 21:35:03 +00:00
def str_dirs(d):
s = ""
if d & tileleft:
s += "L"
if d & tileright:
s += "R"
if d & tileup:
s += "U"
if d & tiledown:
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
enemy_history = [[], [], [], [], [], [], []]
player_history = [[], [], [], []]
2017-04-24 21:35:03 +00:00
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(cur_enemies):
if index < len(enemy_history[i]):
remain = True
r, c = enemy_history[i][index]
m[r][c] = ord("0") + i
for i in range(cur_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("_______")
2017-04-24 21:35:03 +00:00
def show_screen():
2017-04-25 23:44:08 +00:00
for r in range(24):
for c in range(33):
tile = screen[r, c]
2017-04-26 04:46:59 +00:00
if tile < 16:
val = curseschars[tile]
pad.addch(r, c, val, curses.color_pair(1))
elif tile < 32:
2017-04-25 23:44:08 +00:00
val = curseschars[tile]
2017-04-26 04:46:59 +00:00
pad.addch(r, c, val, curses.A_NORMAL)
2017-04-25 23:44:08 +00:00
else:
val = int(tile)
2017-04-26 04:46:59 +00:00
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):
2017-04-25 23:44:08 +00:00
game_log.debug("%02d %s" % (i, lines[i]))
2017-04-25 06:36:48 +00:00
if __name__ == "__main__":
2017-04-24 20:20:01 +00:00
#random.seed(31415)
2017-04-25 23:44:08 +00:00
init()