Hello!
As my hobby, I’m working on an ice-hockey game, using Python (pygame). It will be based on wego-turns, with each turn representing 3 seconds of “realtime playback”. In other words: both users plan their skaters’ movements and actions for the upcoming 3 seconds while the game is paused, and then, when they both have confirmed their turn, the game takes their plans, calculates what happens and finally presents the outcome of the turn to the users as a 3 seconds-replay. All frames of the action are stored so that users can re-watch the action, using rewind, forward, play functions etc.
As I'm almost totally new to programming, I’m worried about the planning phase. Although my code does achieve what I want, I’m worried that it is overly complicated and will give me lots of headaches later on. Therefore, I would be very thankful if someone more experienced in programming could take a look and/or lend me a hand and give me tips how to simplify and shorten it. I will post my code at the end of the post in the spoiler, but I fear that it might be so messed up that noone but me understands it.
Here is what the code should do:
IDEA
- Context: When a certain input is given while a skater is selected during the planning phase, planning mode is enabled.In planning mode, the user can set waypoints for the selected skater.
- The higher the speed of a skater, the narrower the allowed angle to set the next waypoint needs to be. E.g. if your skater goes at full speed, you cannot perform a 90° turn within just two waypoints.
- The number of waypoints that can be set for a skater in the planning phase depends on the skater’s speed. The greater the speed, the more waypoints can be set. The effect of speed needs to be tracked during the planning-phase itself, so that - for example - if a user sets acceleration-waypoints he will be able to set more waypoints in this very planning-phase.
- All information for movement (between waypoints) must be stored, as it needs to be retrievable in the replay-phase.
If you have any ideas or hints how to come up with a code that achieves these things, please tell me, never mind how basic or in-depth! What follows below is my complicated attempt at it.
MY ATTEMPT
While in planning mode, the function “plan_movement” (see spoiler) is called every frame of the gameloop.
All skater-instances (these are the ice-hockey-players) have these attributes:
- Time=movement points remaining for this turn (frames)
- A list storing all waypoints-instances that have been planned for the skater for the upcoming turn (plan_book).
- A list storing all the move-steps for the upcoming turn (move_steps).
Waypoint-objects have these attributes: a number, a coordinate (x,y), a facing, a speed (it stores the speed of the skater at the waypoint).
Move_step-objects are movement-instructions for a skater for a single frame. They have these attributes: dx,dy, pivot-info, link to waypoint (None or waypoint-number).
Waypoints are set at fixed intervalls. In order to set waypoints, the user has two options:
- He can move his cursor close to the facing-vector (a line indicating the facing) of the last waypoint. If the cursor comes close enough to a legal (in terms of the angle) waypoint-position, a waypoint will be set automatically
- The second way to set waypoints is to press the right/left keys. This will create a waypoint at the minimum allowed angle. (I left out this function in the code posted below)
Whenever the player sets a waypoint, the game immediately translates the movement to it into move-steps:
- It takes the speed of the last waypoint, expressed as movement distance per frame, and checks how many times this speed=distance fits into the vector leading from the old to the new waypoint. It then calculates all the small-move-step-vectors and stores them to the skater’s move_steps.
- The number of frames that are needed to travel from one waypoint to the next is subtracted from the skater’s frames=movement points for that turn. If the user sets an “acceleration” waypoint, the number of frames is increased. For deceleration, it’s vice versa.
- The game also calculates the difference between the facings of the the start- and end- waypoint and divides it by the number of frames used and adds the info to the move_step. S So the skater should pivot continously and fluidly when moving from one waypoint to another.
The question how to deal with excess frames (what if there are still frames left for a skater, but not enough to set a new waypoint at the fixed intervall) was solved rather pragmatically: the excess frames are lost. It shouldn’t be a big deal if you consider that the distance between two waypoints is just 50 pixels. So you don’t ever loose more than 50 pixels of movement this way. The alternative - namely allowing for a short final waypoint, overruling the regular waypoint-intervall - looked very weird and added to the complexity of the code.
Another issue I ran into was the connection between move-steps and waypoints. I want the game to track when a skater reaches a waypoint (as users will set actions like passes and shots to waypoints). Here, I also went an untidy, pragmatical way: I simply set the final movestep of each section to be on the waypoint itself.
At the end of the planning phase, the plan_books and move_steps of all skaters would be filled. The game then proceeds to the action-calculation-phase: for each frame, it moves all skaters according to their movesteps.
def plan_movement(planning_phase, type=0):
# handles movement-planning input by the player / sets waypoints and translates them into move_steps
phase = planning_phase
skater = phase.selected_skater
if skater.frames > 0:
st_wp = skater.last_wp # ST_WP = last WP object
st_p = st_wp.coll_rect.center # ST_P = last WP coordinates (needed in this form below)
aim_p = lo.world_pos_a(pg.mouse.get_pos(), skater.match.camera) # ending coordinates (=Mousepos translated from screen into world coord)
wp_vec = lo.Vector(aim_p[0] - st_p[0], aim_p[1] - st_p[1], skater.match, st_p) # WP_VEC = vector from last WP to mousecoordinates
if wp_vec.length > 5: # needed; if vector is too small, cannot change length; crashes the game
wp_vec.change_length(WP_INTERVALL) # give wp_vec the proper length
wp_vec_facing = give_absolute_facing(skater, wp_vec) # tell facing of wp_vec
move_dir = check_angle(skater, st_wp.facing, wp_vec_facing) # determine legality and direction of move
if move_dir: # if the move is a legal move in a direction:
new_WP_coord = (st_p[0] + wp_vec.x, st_p[1] + wp_vec.y) # determine the coordinates for the new WP
if aim_p[0] in range(int(new_WP_coord[0]) - 10, int(new_WP_coord[0]) + 10) and aim_p[1] in range(
int(new_WP_coord[1]) - 10, int(new_WP_coord[1]) + 10): # and cHeck if the mouse cursor is within 10 pixels
frame_speed = skater.last_wp.speed # the distance covered in a single frame = speed at last wp
if frame_speed < start_up_speed: # if the player was stationary, give a minimum speed
frame_speed = start_up_speed
duration = WP_INTERVALL / frame_speed # determine the duration of the wp/intervall
if duration <= skater.frames: # if the skater has enough frames left to pay the full duration:
if move_dir == 2: #BACKWARDS-SKATING (IGNORE)
wp_vec_facing += 180
if wp_vec_facing > 360:
wp_vec_facing -= 360
pivot_step = (wp_vec_facing - st_wp.facing) / duration # calculate pivot-steps (facing-difference / duration)
frame_vec = copy.copy(wp_vec) # to get a move-step-vector, copy the wp_vec ...
frame_vec.change_length(frame_speed) # ... and shorten it down to the proper speed/length
st_p_facing = st_wp.facing
# GENERATE MOVE_STEPS AND WAYPOINT
generate_move_steps(st_p, frame_vec, pivot_step, duration, move_dir, new_WP_coord, skater, st_p_facing)
generate_waypoint(skater, new_WP_coord, move_dir, wp_vec, wp_vec_facing, type)
else:
print('illegal angle!')
else:
print('ALl movement spent this turn')
phase.is_depleted = 1
def generate_move_steps(st_p, frame_vec, pivot_step, duration, move_dir, new_WP_coord, skater, st_p_facing):
# generate approaching movesteps
absolved = 0
while absolved < duration - 1:
st_p = st_p # first start-point is the last_wp startpoint, then always take last_startpoint + frame_vec as new start point
new_facing = st_p_facing + pivot_step
if new_facing < 0:
new_facing = 360 - new_facing
elif new_facing > 360:
new_facing = new_facing - 360
move_step = MoveStep(frame_vec.x, frame_vec.y, new_facing, None, move_dir)
skater.move_steps.append(move_step)
st_p = (st_p[0] + move_step.dx, st_p[1] + move_step.dy)
st_p_facing = new_facing
absolved += 1
skater.frames -= 1
# generate final movestep on the planned WP
last_st_p = st_p
last_endpoint = new_WP_coord
new_facing = st_p_facing + pivot_step
if new_facing < 0:
new_facing = 360 - new_facing
elif new_facing > 360:
new_facing = new_facing - 360
last_movestep = MoveStep(last_endpoint[0] - last_st_p[0], last_endpoint[1] - last_st_p[1],
new_facing, skater.last_wp.seq_number + 1,
move_dir)
skater.move_steps.append(last_movestep)
skater.frames -= 1
def generate_waypoint(skater, new_WP_coord, move_dir, wp_vec, wp_vec_facing, type):
#FORWARDS
if move_dir == 1:
# glide forwards
if type == 0:
new_speed = skater.last_wp.speed - norm_skater_airdrag
if new_speed < start_up_speed:
new_speed = start_up_speed
color = C_glide_1
# accelerate forwards
elif type == 1:
new_speed = skater.last_wp.speed + norm_skater_accel * stat_accel[skater.acceleration_stat]
if new_speed > max_speed * stat_speed[skater.speed_stat]:
new_speed = max_speed * stat_speed[skater.speed_stat]
color = C_accel_1
# decelerate forwards
elif type == 2:
new_speed = skater.last_wp.speed - norm_skater_decel
if new_speed < 1:
new_speed = 0
color = C_decel_1
# full break forwards
elif type == 3:
new_speed = skater.last_wp.speed - norm_skater_fullbreak
if new_speed < 1:
new_speed = 0
color = C_break_1
# BACKWARDS
if move_dir == 2:
# glide backwards
if type == 0:
new_speed = skater.last_wp.speed - norm_skater_airdrag
if new_speed < start_up_speed:
new_speed = start_up_speed
color = C_glide_2
# accelerate backwards
elif type == 1:
new_speed = skater.last_wp.speed + norm_skater_accel * stat_accel[skater.acceleration_stat]
if new_speed > max_speed_bw * stat_speed[skater.speed_stat]:
new_speed = max_speed_bw * stat_speed[skater.speed_stat]
color = C_accel_2
# decelerate backwards
elif type == 2:
new_speed = skater.last_wp.speed - norm_skater_decel
if new_speed < 1:
new_speed = 0
color = C_decel_2
# full break backwards
elif type == 3:
new_speed = skater.last_wp.speed - norm_skater_fullbreak
if new_speed < 1:
new_speed = 0
color = C_break_2
# COMPILE AND CREATE
wp_vec.color = color
new_WP_seq = skater.last_wp.seq_number + 1
new_WP = h.WayPoint(new_WP_coord, round(wp_vec_facing), skater, new_WP_seq, wp_vec, new_speed, move_dir)
skater.plan_book.append(new_WP)
skater.last_wp = new_WP
skater.cur_speed = new_speed
print('WAYPOINT', new_WP_seq,' CREATED, movedir =', move_dir)
def check_angle(skater, start_facing, end_facing):
# returns False if the current move is not allowed for the skater at the given facing with the given speed
# if the move is legal, returns the direction: 1 = forwards, 2 = backwards
skater_speed_limit = 1.1-(skater.cur_speed / max_speed)
allowed_angle = norm_skater_angle * stat_curve[skater.curve_stat] * skater_speed_limit
angle = start_facing - end_facing
if abs(angle) < allowed_angle/2:
return 1
elif abs(angle) in range (160, 201):
return 2
else:
return False
VIDEO
Here is a video that shows how the game works right now. It might give you a better idea of what I'm trying to achieve.
https://www.youtube.com/watch?v=Vlxf3VxvxnA