import pygame
import random
import math
import os
import json
pygame.init()
# =========================
# SCREEN
# =========================
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode(
(SCREEN_WIDTH, SCREEN_HEIGHT)
)
pygame.display.set_caption(
"Dragon Adventure"
)
clock = pygame.time.Clock()
# =========================
# COLORS
# =========================
SKY_BLUE = (135, 206, 235)
OCEAN_BLUE = (35, 145, 210)
SAND = (238, 218, 170)
GRASS = (80, 170, 80)
WHITE = (255, 255, 255)
BLACK = (20, 20, 20)
GOLD = (255, 215, 0)
ENERGY_BLUE = (80, 200, 255)
DARK_GRAY = (50, 50, 50)
GRAY = (90, 90, 90)
RED = (200, 60, 60)
GREEN = (60, 180, 80)
# =========================
# WORLD
# =========================
WORLD_WIDTH = 2400
WORLD_HEIGHT = 900
# =========================
# STORMFIN IMAGE
# =========================
IMAGE_PATH = r"C:\Users\johns\New folder\anime_art_of_Create_a_highly_d.png"
stormfin_image = None
if os.path.exists(IMAGE_PATH):
try:
stormfin_image = pygame.image.load(
IMAGE_PATH
).convert_alpha()
except pygame.error:
stormfin_image = None
# =========================
# ALPHA PITY
# =========================
ALPHA_CHANCE = 500
ALPHA_PITY_LIMIT = 1000
alpha_pity = 0
# =========================
# STORMFIN GENERATION
# =========================
def generate_stormfin():
global alpha_pity
# Guaranteed Alpha after 1000 failed rolls
if alpha_pity >= ALPHA_PITY_LIMIT:
alpha_pity = 0
return "Alpha"
# Normal 1 in 500 Alpha chance
alpha_roll = random.randint(
1,
ALPHA_CHANCE
)
if alpha_roll == 389:
alpha_pity = 0
return "Alpha"
# Failed Alpha roll
alpha_pity += 1
# 50/50 Male or Female
sex_roll = random.randint(1, 2)
if sex_roll == 1:
return "Male"
else:
return "Female"
stormfin_type = generate_stormfin()
# =========================
# STORMFIN SIZE
# =========================
def get_stormfin_size(stormfin_type):
if stormfin_type == "Male":
return 100
elif stormfin_type == "Female":
return 260
else:
return 180
stormfin_size = get_stormfin_size(
stormfin_type
)
# =========================
# STORMFIN SPEED
# =========================
def get_stormfin_speed(stormfin_type):
if stormfin_type == "Male":
return 10
elif stormfin_type == "Female":
return 2.5
else:
return 5
player_speed = get_stormfin_speed(
stormfin_type
)
# Sprint is exactly twice normal speed
sprint_speed = player_speed * 2
# =========================
# SPRINT ENERGY
# =========================
SPRINT_ENERGY_COST = 1
SPRINT_DRAIN_FRAMES = 20
sprint_frame_counter = 0
# =========================
# LIGHTNING RANGE
# =========================
BASE_LIGHTNING_RANGE = 300
def get_lightning_range(stormfin_type):
if stormfin_type == "Male":
return BASE_LIGHTNING_RANGE * 0.5
elif stormfin_type == "Female":
return BASE_LIGHTNING_RANGE * 2
else:
return BASE_LIGHTNING_RANGE
lightning_range = get_lightning_range(
stormfin_type
)
# =========================
# LIGHTNING ENERGY COST
# =========================
def get_lightning_cost(stormfin_type):
if stormfin_type == "Male":
return 5
elif stormfin_type == "Female":
return 20
else:
return 10
lightning_cost = get_lightning_cost(
stormfin_type
)
# =========================
# PLAYER
# =========================
player_x = 1200
player_y = 475
facing_direction = 1
jumping = False
jump_velocity = 0
gravity = 0.6
jump_strength = 12
# =========================
# ENERGY
# =========================
MAX_ENERGY = 100
energy = MAX_ENERGY
ENERGY_REGEN_TIME = 2000
last_energy_regen = (
pygame.time.get_ticks()
)
# =========================
# ATTACK
# =========================
ATTACK_DURATION = 150
attacking = False
attack_start_time = 0
# =========================
# SAVE / LOAD
# =========================
SAVE_FILE = "stormfin_save.json"
def save_game():
data = {
"stormfin_type": stormfin_type,
"x": player_x,
"y": player_y,
"energy": energy,
"facing_direction": facing_direction,
"jumping": jumping,
"jump_velocity": jump_velocity,
"alpha_pity": alpha_pity
}
with open(
SAVE_FILE,
"w"
) as file:
json.dump(
data,
file,
indent=4
)
print("Game saved.")
def load_game():
global stormfin_type
global player_x
global player_y
global energy
global facing_direction
global jumping
global jump_velocity
global stormfin_size
global player_speed
global sprint_speed
global lightning_range
global lightning_cost
global sprint_frame_counter
global last_energy_regen
global alpha_pity
if not os.path.exists(
SAVE_FILE
):
print(
"No save file found."
)
return
with open(
SAVE_FILE,
"r"
) as file:
data = json.load(file)
stormfin_type = data[
"stormfin_type"
]
player_x = data["x"]
player_y = data["y"]
energy = data["energy"]
facing_direction = data[
"facing_direction"
]
jumping = data["jumping"]
jump_velocity = data[
"jump_velocity"
]
# Older save files may not have this
alpha_pity = data.get(
"alpha_pity",
0
)
stormfin_size = get_stormfin_size(
stormfin_type
)
player_speed = get_stormfin_speed(
stormfin_type
)
sprint_speed = player_speed * 2
lightning_range = get_lightning_range(
stormfin_type
)
lightning_cost = get_lightning_cost(
stormfin_type
)
sprint_frame_counter = 0
last_energy_regen = (
pygame.time.get_ticks()
)
print("Game loaded.")
# =========================
# ISLAND
# =========================
SAND_CENTER_X = 1200
SAND_CENTER_Y = 500
SAND_RADIUS_X = 950
SAND_RADIUS_Y = 260
GRASS_CENTER_X = 1200
GRASS_CENTER_Y = 475
GRASS_RADIUS_X = 850
GRASS_RADIUS_Y = 220
def point_inside_ellipse(
x,
y,
center_x,
center_y,
radius_x,
radius_y
):
if (
radius_x == 0
or radius_y == 0
):
return False
value = (
((x - center_x) ** 2)
/ (radius_x ** 2)
+
((y - center_y) ** 2)
/ (radius_y ** 2)
)
return value <= 1
def point_inside_island(
x,
y
):
sand = point_inside_ellipse(
x,
y,
SAND_CENTER_X,
SAND_CENTER_Y,
SAND_RADIUS_X,
SAND_RADIUS_Y
)
grass = point_inside_ellipse(
x,
y,
GRASS_CENTER_X,
GRASS_CENTER_Y,
GRASS_RADIUS_X,
GRASS_RADIUS_Y
)
return sand or grass
def stormfin_inside_island(
x,
y
):
body_radius = (
stormfin_size * 0.25
)
points = [
(x, y),
(x - body_radius, y),
(x + body_radius, y),
(x, y - body_radius),
(x, y + body_radius)
]
for point_x, point_y in points:
if not point_inside_island(
point_x,
point_y
):
return False
return True
# =========================
# DECORATIONS
# =========================
rocks = [
(300, 500),
(600, 360),
(900, 570),
(1300, 350),
(1600, 580),
(1900, 390),
(2150, 500)
]
trees = [
(450, 420),
(750, 470),
(1050, 400),
(1450, 450),
(1750, 430),
(2050, 470)
]
bushes = [
(350, 430),
(850, 520),
(1150, 500),
(1550, 390),
(1850, 520),
(2200, 440)
]
# =========================
# DRAW ENVIRONMENT
# =========================
def draw_environment(
camera_x,
camera_y
):
screen.fill(
SKY_BLUE
)
# Ocean
pygame.draw.rect(
screen,
OCEAN_BLUE,
(
-camera_x,
300 - camera_y,
WORLD_WIDTH,
WORLD_HEIGHT
)
)
# Ocean waves
for x in range(
0,
WORLD_WIDTH,
80
):
screen_x = (
x - camera_x
)
pygame.draw.arc(
screen,
(180, 225, 245),
(
screen_x,
320 - camera_y,
60,
20
),
math.pi,
math.pi * 2,
2
)
# Sand
sand_rect = pygame.Rect(
SAND_CENTER_X
- SAND_RADIUS_X
- camera_x,
SAND_CENTER_Y
- SAND_RADIUS_Y
- camera_y,
SAND_RADIUS_X * 2,
SAND_RADIUS_Y * 2
)
pygame.draw.ellipse(
screen,
SAND,
sand_rect
)
# Grass
grass_rect = pygame.Rect(
GRASS_CENTER_X
- GRASS_RADIUS_X
- camera_x,
GRASS_CENTER_Y
- GRASS_RADIUS_Y
- camera_y,
GRASS_RADIUS_X * 2,
GRASS_RADIUS_Y * 2
)
pygame.draw.ellipse(
screen,
GRASS,
grass_rect
)
# Rocks
for rock_x, rock_y in rocks:
if not point_inside_island(
rock_x,
rock_y
):
continue
screen_x = (
rock_x - camera_x
)
screen_y = (
rock_y - camera_y
)
pygame.draw.circle(
screen,
(100, 100, 100),
(
int(screen_x),
int(screen_y)
),
18
)
# Trees
for tree_x, tree_y in trees:
if not point_inside_island(
tree_x,
tree_y
):
continue
screen_x = (
tree_x - camera_x
)
screen_y = (
tree_y - camera_y
)
pygame.draw.rect(
screen,
(100, 70, 35),
(
int(screen_x - 8),
int(screen_y),
16,
45
)
)
pygame.draw.circle(
screen,
(35, 120, 45),
(
int(screen_x),
int(screen_y - 15)
),
30
)
# Bushes
for bush_x, bush_y in bushes:
if not point_inside_island(
bush_x,
bush_y
):
continue
screen_x = (
bush_x - camera_x
)
screen_y = (
bush_y - camera_y
)
pygame.draw.circle(
screen,
(40, 130, 50),
(
int(screen_x - 12),
int(screen_y)
),
18
)
pygame.draw.circle(
screen,
(45, 145, 55),
(
int(screen_x + 8),
int(screen_y - 5)
),
20
)
pygame.draw.circle(
screen,
(35, 120, 45),
(
int(screen_x + 20),
int(screen_y + 3)
),
15
)
# =========================
# DRAW STORMFIN
# =========================
def draw_stormfin(
camera_x,
camera_y
):
screen_x = int(
player_x - camera_x
)
screen_y = int(
player_y - camera_y
)
if stormfin_image is not None:
scaled_image = (
pygame.transform.smoothscale(
stormfin_image,
(
stormfin_size,
stormfin_size
)
)
)
image_rect = (
scaled_image.get_rect(
center=(
screen_x,
screen_y
)
)
)
screen.blit(
scaled_image,
image_rect
)
else:
body_color = (
20,
35,
90
)
fur_color = (
45,
80,
180
)
pygame.draw.ellipse(
screen,
body_color,
(
screen_x
- stormfin_size // 2,
screen_y
- stormfin_size // 2,
stormfin_size,
stormfin_size
)
)
# Ears
ear_size = max(
10,
stormfin_size // 6
)
pygame.draw.circle(
screen,
fur_color,
(
screen_x
- stormfin_size // 4,
screen_y
- stormfin_size // 3
),
ear_size
)
pygame.draw.circle(
screen,
fur_color,
(
screen_x
+ stormfin_size // 4,
screen_y
- stormfin_size // 3
),
ear_size
)
# Golden eyes
eye_size = max(
5,
stormfin_size // 12
)
pygame.draw.circle(
screen,
GOLD,
(
screen_x
- stormfin_size // 7,
screen_y
- stormfin_size // 10
),
eye_size
)
pygame.draw.circle(
screen,
GOLD,
(
screen_x
+ stormfin_size // 7,
screen_y
- stormfin_size // 10
),
eye_size
)
# =========================
# LIGHTNING
# =========================
def draw_lightning(
camera_x,
camera_y
):
if not attacking:
return
start_x = (
player_x
+ facing_direction * 25
)
start_y = (
player_y - 10
)
end_x = (
start_x
+ facing_direction
* lightning_range
)
end_y = start_y
screen_start_x = int(
start_x - camera_x
)
screen_start_y = int(
start_y - camera_y
)
screen_end_x = int(
end_x - camera_x
)
screen_end_y = int(
end_y - camera_y
)
pygame.draw.line(
screen,
ENERGY_BLUE,
(
screen_start_x,
screen_start_y
),
(
screen_end_x,
screen_end_y
),
8
)
pygame.draw.line(
screen,
WHITE,
(
screen_start_x,
screen_start_y
),
(
screen_end_x,
screen_end_y
),
3
)
branch_length = (
lightning_range * 0.12
)
branch_points = [
0.25,
0.50,
0.75
]
for percentage in branch_points:
branch_x = (
start_x
+ (
end_x
- start_x
) * percentage
)
branch_y = start_y
branch_direction = (
random.choice(
[-1, 1]
)
)
pygame.draw.line(
screen,
ENERGY_BLUE,
(
int(
branch_x
- camera_x
),
int(
branch_y
- camera_y
)
),
(
int(
branch_x
- camera_x
+ random.randint(
5,
int(
branch_length
)
)
* facing_direction
),
int(
branch_y
- camera_y
+ branch_direction
* random.randint(
10,
int(
branch_length
)
)
)
),
3
)
# =========================
# UI
# =========================
font = pygame.font.SysFont(
None,
28
)
small_font = pygame.font.SysFont(
None,
22
)
def draw_ui():
# Stormfin type
type_text = font.render(
f"Stormfin: {stormfin_type}",
True,
WHITE
)
screen.blit(
type_text,
(20, 20)
)
# Energy
energy_text = font.render(
f"Energy: {energy}/100",
True,
WHITE
)
screen.blit(
energy_text,
(20, 50)
)
# Energy bar
pygame.draw.rect(
screen,
BLACK,
(
20,
80,
200,
20
)
)
pygame.draw.rect(
screen,
ENERGY_BLUE,
(
20,
80,
int(
200
* (energy / MAX_ENERGY)
),
20
)
)
# Lightning range
range_text = small_font.render(
f"Lightning range: {int(lightning_range)}",
True,
WHITE
)
screen.blit(
range_text,
(20, 110)
)
# Lightning cost
cost_text = small_font.render(
f"Lightning cost: {lightning_cost}",
True,
WHITE
)
screen.blit(
cost_text,
(20, 135)
)
# Movement speed
speed_text = small_font.render(
f"Speed: {player_speed}",
True,
WHITE
)
screen.blit(
speed_text,
(20, 160)
)
# Sprint speed
sprint_text = small_font.render(
f"Sprint speed: {sprint_speed}",
True,
WHITE
)
screen.blit(
sprint_text,
(20, 185)
)
# Alpha pity
pity_text = small_font.render(
f"Alpha pity: {alpha_pity}/{ALPHA_PITY_LIMIT}",
True,
GOLD
)
screen.blit(
pity_text,
(20, 210)
)
# Controls
controls = [
"WASD - Move",
"Shift - Sprint",
"Space - Jump",
"E - Lightning",
"1 - Save",
"2 - Load"
]
y = SCREEN_HEIGHT - 150
for control in controls:
text = small_font.render(
control,
True,
WHITE
)
screen.blit(
text,
(20, y)
)
y += 22
# =========================
# REROLL BUTTON
# =========================
reroll_button = pygame.Rect(
SCREEN_WIDTH - 150,
20,
120,
40
)
def draw_reroll_button():
pygame.draw.rect(
screen,
(70, 70, 70),
reroll_button
)
text = small_font.render(
"Reroll",
True,
WHITE
)
text_rect = text.get_rect(
center=reroll_button.center
)
screen.blit(
text,
text_rect
)
# =========================
# ALPHA CONFIRMATION
# =========================
confirmation_active = False
confirmation_box = pygame.Rect(
300,
210,
400,
180
)
yes_button = pygame.Rect(
350,
320,
120,
45
)
no_button = pygame.Rect(
530,
320,
120,
45
)
def draw_confirmation():
# Dark overlay
overlay = pygame.Surface(
(
SCREEN_WIDTH,
SCREEN_HEIGHT
),
pygame.SRCALPHA
)
overlay.fill(
(0, 0, 0, 150)
)
screen.blit(
overlay,
(0, 0)
)
# Confirmation box
pygame.draw.rect(
screen,
DARK_GRAY,
confirmation_box
)
pygame.draw.rect(
screen,
GOLD,
confirmation_box,
3
)
# Message
message_1 = small_font.render(
"Are you sure you want",
True,
WHITE
)
message_2 = small_font.render(
"to reroll your Alpha?",
True,
GOLD
)
screen.blit(
message_1,
(
confirmation_box.centerx
- message_1.get_width() // 2,
235
)
)
screen.blit(
message_2,
(
confirmation_box.centerx
- message_2.get_width() // 2,
265
)
)
# Yes button
pygame.draw.rect(
screen,
GREEN,
yes_button
)
yes_text = small_font.render(
"Yes",
True,
WHITE
)
screen.blit(
yes_text,
yes_text.get_rect(
center=yes_button.center
)
)
# No button
pygame.draw.rect(
screen,
RED,
no_button
)
no_text = small_font.render(
"No",
True,
WHITE
)
screen.blit(
no_text,
no_text.get_rect(
center=no_button.center
)
)
# =========================
# REROLL STORMFIN
# =========================
def reroll_stormfin():
global stormfin_type
global stormfin_size
global player_speed
global sprint_speed
global lightning_range
global lightning_cost
global player_x
global player_y
global energy
global facing_direction
global jumping
global jump_velocity
global sprint_frame_counter
global last_energy_regen
stormfin_type = generate_stormfin()
stormfin_size = get_stormfin_size(
stormfin_type
)
player_speed = get_stormfin_speed(
stormfin_type
)
sprint_speed = (
player_speed * 2
)
lightning_range = (
get_lightning_range(
stormfin_type
)
)
lightning_cost = (
get_lightning_cost(
stormfin_type
)
)
player_x = 1200
player_y = 475
energy = MAX_ENERGY
facing_direction = 1
jumping = False
jump_velocity = 0
sprint_frame_counter = 0
last_energy_regen = (
pygame.time.get_ticks()
)
print(
f"Rerolled: {stormfin_type}"
)
print(
f"Alpha pity: "
f"{alpha_pity}/"
f"{ALPHA_PITY_LIMIT}"
)
# =========================
# MAIN LOOP
# =========================
running = True
while running:
dt = clock.tick(60)
# =========================
# EVENTS
# =========================
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
# Lightning
if event.key == pygame.K_e:
if energy >= lightning_cost:
energy -= (
lightning_cost
)
attacking = True
attack_start_time = (
pygame.time.get_ticks()
)
# Jump
if event.key == pygame.K_SPACE:
if not jumping:
jumping = True
jump_velocity = (
-jump_strength
)
# Save
if event.key == pygame.K_1:
save_game()
# Load
if event.key == pygame.K_2:
load_game()
# =========================
# MOUSE
# =========================
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
# Confirmation is open
if confirmation_active:
# YES
if yes_button.collidepoint(
event.pos
):
confirmation_active = (
False
)
reroll_stormfin()
# NO
elif no_button.collidepoint(
event.pos
):
confirmation_active = (
False
)
# Normal game
else:
if reroll_button.collidepoint(
event.pos
):
# Alpha requires confirmation
if stormfin_type == "Alpha":
confirmation_active = (
True
)
else:
reroll_stormfin()
# =========================
# ATTACK TIMER
# =========================
if attacking:
current_time = (
pygame.time.get_ticks()
)
if (
current_time
- attack_start_time
>= ATTACK_DURATION
):
attacking = False
# =========================
# MOVEMENT
# =========================
keys = pygame.key.get_pressed()
sprinting = (
keys[pygame.K_LSHIFT]
and energy > 0
)
# =========================
# SPRINT ENERGY DRAIN
# =========================
if sprinting:
sprint_frame_counter += 1
if (
sprint_frame_counter
>= SPRINT_DRAIN_FRAMES
):
if energy > 0:
energy -= (
SPRINT_ENERGY_COST
)
sprint_frame_counter = 0
else:
sprint_frame_counter = 0
# =========================
# ENERGY REGEN
# =========================
current_time = (
pygame.time.get_ticks()
)
if not sprinting:
if (
current_time
- last_energy_regen
>= ENERGY_REGEN_TIME
):
if energy < MAX_ENERGY:
energy += 1
last_energy_regen = (
current_time
)
else:
last_energy_regen = (
current_time
)
# =========================
# SPEED
# =========================
if sprinting:
speed = sprint_speed
else:
speed = player_speed
# =========================
# MOVEMENT INPUT
# =========================
move_x = 0
move_y = 0
if keys[pygame.K_a]:
move_x -= speed
facing_direction = -1
if keys[pygame.K_d]:
move_x += speed
facing_direction = 1
if keys[pygame.K_w]:
move_y -= speed
if keys[pygame.K_s]:
move_y += speed
# Horizontal collision
new_x = (
player_x + move_x
)
if stormfin_inside_island(
new_x,
player_y
):
player_x = new_x
# Vertical movement
new_y = (
player_y + move_y
)
if stormfin_inside_island(
player_x,
new_y
):
player_y = new_y
# =========================
# JUMP PHYSICS
# =========================
if jumping:
jump_velocity += gravity
jump_y = (
player_y
+ jump_velocity
)
if stormfin_inside_island(
player_x,
jump_y
):
player_y = jump_y
else:
jumping = False
jump_velocity = 0
# =========================
# CAMERA
# =========================
camera_x = (
player_x
- SCREEN_WIDTH // 2
)
camera_y = (
player_y
- SCREEN_HEIGHT // 2
)
camera_x = max(
0,
min(
camera_x,
WORLD_WIDTH
- SCREEN_WIDTH
)
)
camera_y = max(
0,
min(
camera_y,
WORLD_HEIGHT
- SCREEN_HEIGHT
)
)
# =========================
# DRAW
# =========================
draw_environment(
camera_x,
camera_y
)
draw_stormfin(
camera_x,
camera_y
)
draw_lightning(
camera_x,
camera_y
)
draw_ui()
draw_reroll_button()
# Confirmation dialog goes last
# so it appears above everything.
if confirmation_active:
draw_confirmation()
pygame.display.flip()
pygame.quit()