jester_game.py
Improved prototype: event-based actions, timer resets, clamped movement, small refactor.
import pygame
import sys
pygame.init()
WIDTH, HEIGHT = 800, 600
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Jester's Tale - Prototype")
FONT = pygame.font.SysFont("consolas", 24)
BIG_FONT = pygame.font.SysFont("consolas", 40)
CLOCK = pygame.time.Clock()
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (200, 40, 40)
YELLOW = (230, 210, 80)
BLUE = (80, 120, 230)
GRAY = (80, 80, 80)
GREEN = (60, 160, 60)
Game states
STATE_WAKE = "wake"
STATE_CLEARING = "clearing"
STATE_COIN_EVENT = "coin_event"
STATE_BLACKOUT = "blackout"
STATE_AFTER_BLACKOUT = "after_blackout"
STATE_SLOT = "slot"
STATE_PATH_CHOICE = "path_choice"
STATE_PATH_LOCKED = "path_locked"
state = STATE_WAKE
Jester
jester = pygame.Rect(WIDTH // 2 - 20, HEIGHT // 2, 40, 60)
jester_color = GREEN
Objects
coin_rect = pygame.Rect(WIDTH // 2 - 10, HEIGHT // 2 + 80, 20, 20)
coin_visible = False
coin_obtained = False
serpent_rect = pygame.Rect(WIDTH // 2 - 80, HEIGHT // 2 - 40, 160, 60)
serpent_visible = False
slot_rect = pygame.Rect(WIDTH // 2 - 25, HEIGHT // 2 + 40, 50, 10)
slot_active = False
door_rect = pygame.Rect(WIDTH // 2 - 80, HEIGHT // 2 - 150, 160, 200)
door_open = False
Paths
path1_rect = pygame.Rect(WIDTH // 2 - 260, HEIGHT // 2 - 100, 140, 200)
path2_rect = pygame.Rect(WIDTH // 2 - 70, HEIGHT // 2 - 100, 140, 200)
path3_rect = pygame.Rect(WIDTH // 2 + 120, HEIGHT // 2 - 100, 140, 200)
chosen_path = None
Timers (frame counts)
wake_timer = 0
coin_event_timer = 0
blackout_timer = 0
Input action flags (set from event loop)
action_space = False
action_e = False
def draw_text(text, font, color, x, y, center=False):
surf = font.render(text, True, color)
rect = surf.get_rect()
if center:
rect.center = (x, y)
else:
rect.topleft = (x, y)
WIN.blit(surf, rect)
def clamp_rect_to_window(r: pygame.Rect):
r.x = max(0, min(WIDTH - r.width, r.x))
r.y = max(0, min(HEIGHT - r.height, r.y))
def change_state(new_state):
global state, wake_timer, coin_event_timer, blackout_timer
state = new_state
# reset state-specific timers
wake_timer = 0
coin_event_timer = 0
blackout_timer = 0
# Optionally reset or reposition the jester when entering some states
if new_state in (STATE_CLEARING, STATE_AFTER_BLACKOUT, STATE_SLOT, STATE_PATH_CHOICE):
jester.center = (WIDTH // 2, HEIGHT // 2 + 120)
clamp_rect_to_window(jester)
def handle_wake():
global coin_visible, wake_timer
WIN.fill(BLACK)
wake_timer += 1
draw_text("...", BIG_FONT, WHITE, WIDTH // 2, HEIGHT // 2 - 40, center=True)
draw_text("Jester wakes up in a place he doesn't know.", FONT, WHITE, 80, 400)
draw_text("Press SPACE to stand up.", FONT, WHITE, 80, 430)
# Use event-driven SPACE press
if action_space:
coin_visible = True
change_state(STATE_CLEARING)
def handle_clearing():
global coin_obtained, state
WIN.fill((20, 40, 60))
# Simple "forest" background
for x in range(0, WIDTH, 80):
pygame.draw.rect(WIN, (10, 30, 40), (x, 0, 40, HEIGHT))
# Jester
pygame.draw.rect(WIN, jester_color, jester)
# Coin slot & coin
pygame.draw.rect(WIN, GRAY, slot_rect)
if coin_visible and not coin_obtained:
pygame.draw.rect(WIN, YELLOW, coin_rect)
draw_text("Jester: 'Where... am I? Why can't I remember anything?'", FONT, WHITE, 40, 40)
draw_text("Use arrow keys to move. Stand on the coin and press E to touch it.", FONT, WHITE, 40, 70)
keys = pygame.key.get_pressed()
speed = 3
if keys[pygame.K_LEFT]:
jester.x -= speed
if keys[pygame.K_RIGHT]:
jester.x += speed
if keys[pygame.K_UP]:
jester.y -= speed
if keys[pygame.K_DOWN]:
jester.y += speed
clamp_rect_to_window(jester)
# Touch coin (use event-based E press)
if coin_visible and not coin_obtained and jester.colliderect(coin_rect) and action_e:
change_state(STATE_COIN_EVENT)
def handle_coin_event():
global serpent_visible, coin_obtained, coin_visible, coin_event_timer
WIN.fill((40, 10, 10))
# Jester & coin (bloody)
pygame.draw.rect(WIN, jester_color, jester)
pygame.draw.rect(WIN, RED, coin_rect)
draw_text("The ground shakes as Jester touches the coin...", FONT, WHITE, 40, 40)
draw_text("A giant serpent rises from the earth!", FONT, WHITE, 40, 70)
serpent_visible = True
pygame.draw.rect(WIN, (0, 150, 0), serpent_rect)
draw_text("Everything goes black...", FONT, RED, 40, 110)
coin_event_timer += 1
# wait ~2 seconds (assuming 60 fps)
if coin_event_timer > 120:
# mark coin as obtained now that event has played
coin_obtained = True
coin_visible = False
change_state(STATE_BLACKOUT)
def handle_blackout():
global blackout_timer
WIN.fill(BLACK)
draw_text("TAKE A NAP FOR A SECOND, JESTER.", BIG_FONT, RED, WIDTH // 2, HEIGHT // 2, center=True)
blackout_timer += 1
# ~2 seconds blackout
if blackout_timer > 120:
change_state(STATE_AFTER_BLACKOUT)
def handle_after_blackout():
global serpent_visible, slot_active
WIN.fill((20, 40, 60))
# Background
for x in range(0, WIDTH, 80):
pygame.draw.rect(WIN, (10, 30, 40), (x, 0, 40, HEIGHT))
# Jester
pygame.draw.rect(WIN, jester_color, jester)
# Dead serpent (simple)
serpent_visible = True
pygame.draw.rect(WIN, (0, 80, 0), serpent_rect)
pygame.draw.line(WIN, RED, (serpent_rect.left, serpent_rect.centery),
(serpent_rect.right, serpent_rect.centery), 4)
draw_text("Jester wakes up. The giant serpent is dead beside him.", FONT, WHITE, 40, 40)
draw_text("There's a bloody coin in his hand.", FONT, WHITE, 40, 70)
draw_text("[You got: BLOODY COIN]", FONT, RED, 40, 100)
draw_text("Jester: 'I... I didn't do this... I couldn't have...'", FONT, WHITE, 40, 140)
draw_text("Press SPACE to move toward the coin slot.", FONT, WHITE, 40, 180)
# Move slowly until player hits SPACE; event-driven SPACE triggers moving to slot state
keys = pygame.key.get_pressed()
speed = 2
if keys[pygame.K_LEFT]:
jester.x -= speed
if keys[pygame.K_RIGHT]:
jester.x += speed
if keys[pygame.K_UP]:
jester.y -= speed
if keys[pygame.K_DOWN]:
jester.y += speed
clamp_rect_to_window(jester)
if action_space:
slot_active = True
change_state(STATE_SLOT)
def handle_slot():
global door_open
WIN.fill((20, 40, 60))
# Background
for x in range(0, WIDTH, 80):
pygame.draw.rect(WIN, (10, 30, 40), (x, 0, 40, HEIGHT))
pygame.draw.rect(WIN, jester_color, jester)
pygame.draw.rect(WIN, GRAY, slot_rect)
draw_text("Jester can barely move, but he reaches the slot.", FONT, WHITE, 40, 40)
draw_text("Press E to place the BLOODY COIN into the slot.", FONT, WHITE, 40, 70)
keys = pygame.key.get_pressed()
speed = 1.5 # heavy movement
if keys[pygame.K_LEFT]:
jester.x -= speed
if keys[pygame.K_RIGHT]:
jester.x += speed
if keys[pygame.K_UP]:
jester.y -= speed
if keys[pygame.K_DOWN]:
jester.y += speed
clamp_rect_to_window(jester)
if jester.colliderect(slot_rect) and action_e:
door_open = True
change_state(STATE_PATH_CHOICE)
def handle_path_choice():
global chosen_path
WIN.fill((10, 10, 20))
# Door
pygame.draw.rect(WIN, GRAY, door_rect)
draw_text("The ground opens. A door appears.", FONT, WHITE, 40, 40)
draw_text("Inside, three paths glow. Only one can be chosen.", FONT, WHITE, 40, 70)
draw_text("Walk into a path to choose it. The others will close.", FONT, RED, 40, 100)
# Paths
pygame.draw.rect(WIN, YELLOW, path1_rect)
pygame.draw.rect(WIN, BLUE, path2_rect)
pygame.draw.rect(WIN, RED, path3_rect)
draw_text("Soft Glow", FONT, BLACK, path1_rect.centerx - 50, path1_rect.centery - 10)
draw_text("Cold Echo", FONT, WHITE, path2_rect.centerx - 50, path2_rect.centery - 10)
draw_text("Red Pulse", FONT, WHITE, path3_rect.centerx - 50, path3_rect.centery - 10)
pygame.draw.rect(WIN, jester_color, jester)
keys = pygame.key.get_pressed()
speed = 3
if keys[pygame.K_LEFT]:
jester.x -= speed
if keys[pygame.K_RIGHT]:
jester.x += speed
if keys[pygame.K_UP]:
jester.y -= speed
if keys[pygame.K_DOWN]:
jester.y += speed
clamp_rect_to_window(jester)
if jester.colliderect(path1_rect):
chosen_path = "soft_glow"
change_state(STATE_PATH_LOCKED)
elif jester.colliderect(path2_rect):
chosen_path = "cold_echo"
change_state(STATE_PATH_LOCKED)
elif jester.colliderect(path3_rect):
chosen_path = "red_pulse"
change_state(STATE_PATH_LOCKED)
def handle_path_locked():
WIN.fill(BLACK)
if chosen_path == "soft_glow":
msg = "You chose the Soft Glow path."
color = YELLOW
elif chosen_path == "cold_echo":
msg = "You chose the Cold Echo path."
color = BLUE
elif chosen_path == "red_pulse":
msg = "You chose the Red Pulse path."
color = RED
else:
msg = "You chose a path."
color = WHITE
draw_text(msg, BIG_FONT, color, WIDTH // 2, HEIGHT // 2 - 40, center=True)
draw_text("The other two paths slam shut behind you.", FONT, WHITE, 80, 360)
draw_text("(This is the end of the prototype for now.)", FONT, WHITE, 80, 390)
def main():
global action_space, action_e
change_state(STATE_WAKE)
while True:
CLOCK.tick(60)
# Reset action flags each frame; they are set true on KEYDOWN events
action_space = False
action_e = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Use KEYDOWN for one-off actions so the player doesn't have to hold the key
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
action_space = True
if event.key == pygame.K_e:
action_e = True
if state == STATE_WAKE:
handle_wake()
elif state == STATE_CLEARING:
handle_clearing()
elif state == STATE_COIN_EVENT:
handle_coin_event()
elif state == STATE_BLACKOUT:
handle_blackout()
elif state == STATE_AFTER_BLACKOUT:
handle_after_blackout()
elif state == STATE_SLOT:
handle_slot()
elif state == STATE_PATH_CHOICE:
handle_path_choice()
elif state == STATE_PATH_LOCKED:
handle_path_locked()
pygame.display.flip()
if name == "main":
main()
jester_game.py
Improved prototype: event-based actions, timer resets, clamped movement, small refactor.
import pygame
import sys
pygame.init()
WIDTH, HEIGHT = 800, 600
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Jester's Tale - Prototype")
FONT = pygame.font.SysFont("consolas", 24)
BIG_FONT = pygame.font.SysFont("consolas", 40)
CLOCK = pygame.time.Clock()
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (200, 40, 40)
YELLOW = (230, 210, 80)
BLUE = (80, 120, 230)
GRAY = (80, 80, 80)
GREEN = (60, 160, 60)
Game states
STATE_WAKE = "wake"
STATE_CLEARING = "clearing"
STATE_COIN_EVENT = "coin_event"
STATE_BLACKOUT = "blackout"
STATE_AFTER_BLACKOUT = "after_blackout"
STATE_SLOT = "slot"
STATE_PATH_CHOICE = "path_choice"
STATE_PATH_LOCKED = "path_locked"
state = STATE_WAKE
Jester
jester = pygame.Rect(WIDTH // 2 - 20, HEIGHT // 2, 40, 60)
jester_color = GREEN
Objects
coin_rect = pygame.Rect(WIDTH // 2 - 10, HEIGHT // 2 + 80, 20, 20)
coin_visible = False
coin_obtained = False
serpent_rect = pygame.Rect(WIDTH // 2 - 80, HEIGHT // 2 - 40, 160, 60)
serpent_visible = False
slot_rect = pygame.Rect(WIDTH // 2 - 25, HEIGHT // 2 + 40, 50, 10)
slot_active = False
door_rect = pygame.Rect(WIDTH // 2 - 80, HEIGHT // 2 - 150, 160, 200)
door_open = False
Paths
path1_rect = pygame.Rect(WIDTH // 2 - 260, HEIGHT // 2 - 100, 140, 200)
path2_rect = pygame.Rect(WIDTH // 2 - 70, HEIGHT // 2 - 100, 140, 200)
path3_rect = pygame.Rect(WIDTH // 2 + 120, HEIGHT // 2 - 100, 140, 200)
chosen_path = None
Timers (frame counts)
wake_timer = 0
coin_event_timer = 0
blackout_timer = 0
Input action flags (set from event loop)
action_space = False
action_e = False
def draw_text(text, font, color, x, y, center=False):
surf = font.render(text, True, color)
rect = surf.get_rect()
if center:
rect.center = (x, y)
else:
rect.topleft = (x, y)
WIN.blit(surf, rect)
def clamp_rect_to_window(r: pygame.Rect):
r.x = max(0, min(WIDTH - r.width, r.x))
r.y = max(0, min(HEIGHT - r.height, r.y))
def change_state(new_state):
global state, wake_timer, coin_event_timer, blackout_timer
state = new_state
# reset state-specific timers
wake_timer = 0
coin_event_timer = 0
blackout_timer = 0
# Optionally reset or reposition the jester when entering some states
if new_state in (STATE_CLEARING, STATE_AFTER_BLACKOUT, STATE_SLOT, STATE_PATH_CHOICE):
jester.center = (WIDTH // 2, HEIGHT // 2 + 120)
clamp_rect_to_window(jester)
def handle_wake():
global coin_visible, wake_timer
WIN.fill(BLACK)
wake_timer += 1
def handle_clearing():
global coin_obtained, state
WIN.fill((20, 40, 60))
def handle_coin_event():
global serpent_visible, coin_obtained, coin_visible, coin_event_timer
WIN.fill((40, 10, 10))
def handle_blackout():
global blackout_timer
WIN.fill(BLACK)
draw_text("TAKE A NAP FOR A SECOND, JESTER.", BIG_FONT, RED, WIDTH // 2, HEIGHT // 2, center=True)
blackout_timer += 1
# ~2 seconds blackout
if blackout_timer > 120:
change_state(STATE_AFTER_BLACKOUT)
def handle_after_blackout():
global serpent_visible, slot_active
WIN.fill((20, 40, 60))
def handle_slot():
global door_open
WIN.fill((20, 40, 60))
def handle_path_choice():
global chosen_path
WIN.fill((10, 10, 20))
def handle_path_locked():
WIN.fill(BLACK)
def main():
global action_space, action_e
change_state(STATE_WAKE)
while True:
CLOCK.tick(60)
if name == "main":
main()