Files

135 lines
5.5 KiB
Python

import arcade
import constants
import random
# map consists of a series of different types of terrain (sea, land, city, etc.) called zones
# first field defines what tile the zone starts with; subsequent tiles are determined based on the transitions table below
# second field defines the number of tiles in the zone
# third field defines the total number of enemies to spawn in the zone (0 means no enemies)
# fourth field defines the list of possible enemy types and probabilities to spawn them (e.g. [("ship01", 1.0)] means 100% chance of spawning ship01)
ZONES = [
("sea", 50, 10, [("ship01", 0.5), ("ship02", 0.5),]),
("uphill", 160, 30, [("tank01", 0.5), ("tank02", 0.5),]),
("city", 20, 10, [("tank01", 0.5), ("tank02", 0.5),]),
("uphill", 30, 10, [("tank01", 0.5), ("tank02", 0.5),]),
("sea", 30, 5, [("ship01", 0.5), ("ship02", 0.5),])
]
TILE_TRANSITIONS = {
# sea always stays sea
"sea": [("sea", 1.0)],
# uphill can transition to flat or downhill with equal probability
"uphill": [("full", 0.75), ("downhill", 0.25)],
# flat can transition to uphill, flat, or downhill with equal probability
"full": [("uphill", 0.33), ("full", 0.33), ("downhill", 0.33)],
# downhill can transition to flat or uphill with equal probability
"downhill": [("uphill", 0.50), ("full", 0.50)],
# city always stays city
"city": [("city", 1.0)]
}
# how to change the height of the next tile when switching from first tile type to the second
# if combination is not specified here, terrain height doesn't change
HEIGHT_TRANSITIONS = {
("uphill", "uphill"): 1,
("full", "uphill"): 1,
("downhill", "downhill"): -1,
("downhill", "full"): -1,
}
# probabilities that a sequence of tiles of the same type (called run) will be of a certain length
RUN_LENGTHS = [
# 10% chance of 1 tile run, 20% chance of 2 tiles run, etc.
(0.1, 1),
(0.2, 2),
(0.25, 3),
(0.25, 4),
(0.2, 5),
]
def get_tile_at_x(sl: arcade.SpriteList[arcade.Sprite], x: int) -> float:
for sprite in sl:
if sprite.center_x == x * sprite.width:
return sprite.center_y
return 0.0
def generate_map(height: float) -> tuple[arcade.SpriteList[arcade.Sprite], arcade.SpriteList[arcade.Sprite]]:
r = arcade.SpriteList[arcade.Sprite]()
e = arcade.SpriteList[arcade.Sprite]()
cx = 0
cy = 0
dy = 0
prev_tile = ZONES[0][0] # start with the first tile in the first zone
for index, zone in enumerate(ZONES):
tile = zone[0]
length = zone[1]
zx = 0
enemies_x = random.choices(range(0, zone[1]), k=zone[2])
while zx < length:
run_len = random.choices(RUN_LENGTHS, weights=[t[0] for t in RUN_LENGTHS])[0][1]
for _ in range(min(run_len, length - zx)):
# if close to the end of ground, start descending to sea level
if index < len(ZONES)-1:
if ZONES[index+1][0] == "sea" and cy > 0 and zx >= length - cy - 2:
tile = "downhill"
run_len = cy
sprite = arcade.Sprite(f"assets/{tile}.png", scale=constants.SCALE_TERRAIN)
dy = HEIGHT_TRANSITIONS[(prev_tile, tile)] if (prev_tile, tile) in HEIGHT_TRANSITIONS else 0
prev_tile = tile
# break current run, forcing tile type change, if it's getting too high (5 tiles or less from the top of the screen) or too low
max_y = int((height - sprite.height / 2) / sprite.height) - 5
if (cy + dy) < 0 or (cy + dy) >= max_y:
break
r.append(sprite)
cy += dy
sprite.center_x = cx * sprite.width
sprite.center_y = (cy * sprite.height) + sprite.height / 2
# add underlying terrain, if needed
if cy > 0:
s = arcade.Sprite(f"assets/full.png", scale=constants.SCALE_TERRAIN)
s.center_x = cx * sprite.width
s.scale_y = s.scale_y * cy
s.center_y = s.height / 2
r.append(s)
# spawn enemies if any
if zx in enemies_x:
enemy_type = random.choices(zone[3], weights=[t[1] for t in zone[3]])[0][0]
enemy = arcade.Sprite(f"assets/{enemy_type}.png", scale=constants.SCALE_TERRAIN)
enemy.center_x = sprite.center_x
enemy.center_y = get_tile_at_x(r, cx) + enemy.height / (1 if tile == "full" else 2)
if tile == "downhill":
enemy.radians = 0.5
elif tile == "uphill":
enemy.radians = -0.5
enemy.properties["hit"] = False # custom property to track if the enemy has been hit
enemy.properties["weapon"] = "flak" # custom property to track the type of weapon
enemy.properties["last_fired"] = 0 # custom property to track the last time the enemy fired weapon
enemy.properties["value"] = 100 # custom property to track the score value for destroying the enemy
e.append(enemy)
cx += 1
zx += 1
while True:
tile = random.choices(TILE_TRANSITIONS[tile], weights=[t[1] for t in TILE_TRANSITIONS[tile]])[0][0]
if tile in ["downhill", "full"] and cy <= 2:
continue
else:
break
return r, e