I've been migrating from Pygame to Arcade and overall it's much better. That said, the method I was using to draw the lines of my track for my car game in Pygame is exorbitantly laggy in Arcade. I know it's lagging from drawing all the lines, I'm just wondering if there's a better way to do it that doesn't cause so much lag.
import arcade
import os
import math
import numpy as np
SPRITE_SCALING = 0.5
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Move Sprite by Angle Example"
MOVEMENT_SPEED = 5
ANGLE_SPEED = 5
class Player(arcade.Sprite):
""" Player class """
def __init__(self, image, scale):
""" Set up the player """
# Call the parent init
super().__init__(image, scale)
# Create a variable to hold our speed. 'angle' is created by the parent
self.speed = 0
def update(self):
# Convert angle in degrees to radians.
angle_rad = math.radians(self.angle)
# Rotate the ship
self.angle += self.change_angle
# Use math to find our change based on our speed and angle
self.center_x += -self.speed * math.sin(angle_rad)
self.center_y += self.speed * math.cos(angle_rad)
def upgraded_distance_check(player_sprite, point_arrays, distance_cap):
center_coord = player_sprite.center
intersect_array = []
distance_array = []
sensor_array = player_sprite.points
for sensor in sensor_array:
intersect_array.append([-10000, -10000])
for point_array in point_arrays:
for i in range(len(point_array[:-1])):
v = line_intersection(
[sensor, center_coord], [point_array[i], point_array[i + 1]]
)
if v == (None, None):
continue
if (
(point_array[i][0] <= v[0] and point_array[i + 1][0] >= v[0])
or (point_array[i][0] >= v[0] and point_array[i + 1][0] <= v[0])
) and (
(point_array[i][1] <= v[1] and point_array[i + 1][1] >= v[1])
or (point_array[i][1] >= v[1] and point_array[i + 1][1] <= v[1])
):
if intersect_array[-1] is None or math.sqrt(
(intersect_array[-1][0] - center_coord[0]) ** 2
+ (intersect_array[-1][1] - center_coord[1]) ** 2
) > math.sqrt(
(v[0] - center_coord[0]) ** 2
+ (v[1] - center_coord[1]) ** 2
):
if not is_between(sensor, center_coord, v):
intersect_array[-1] = v
for i in range(len(sensor_array)):
if distance(sensor_array[i], intersect_array[i]) > distance_cap:
intersect_array[i] = None
distance_array.append(None)
else:
distance_array.append(distance(sensor_array[i], intersect_array[i]))
return intersect_array
class MyGame(arcade.Window):
"""
Main application class.
"""
def __init__(self, width, height, title):
"""
Initializer
"""
# Call the parent class initializer
super().__init__(width, height, title)
# Set the working directory (where we expect to find files) to the same
# directory this .py file is in. You can leave this out of your own
# code, but it is needed to easily run the examples using "python -m"
# as mentioned at the top of this program.
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
# Variables that will hold sprite lists
self.player_list = None
# Set up the player info
self.player_sprite = None
# Set the background color
arcade.set_background_color(arcade.color.WHITE)
def setup(self):
""" Set up the game and initialize the variables. """
# Sprite lists
self.player_list = arcade.SpriteList()
# Set up the player
self.player_sprite = Player(":resources:images/space_shooter/playerShip1_orange.png", SPRITE_SCALING)
self.player_sprite.center_x = SCREEN_WIDTH / 2
self.player_sprite.center_y = SCREEN_HEIGHT / 2
self.player_list.append(self.player_sprite)
#Setup all the array BS
self.track_arrays = []
self.drawing = False
def on_draw(self):
"""
Render the screen.
"""
# This command has to happen before we start drawing
arcade.start_render()
# Draw all the sprites.
# for i, ele in enumerate(self.player_sprite.points):
# arcade.draw_circle_filled(ele[0], ele[1], 7, arcade.color.AO)
self.player_list.draw()
if len(self.track_arrays) > 0 and len(self.track_arrays[0]) > 2:
for track_array in self.track_arrays:
for i in range(len(track_array[:-1])):
arcade.draw_line(track_array[i][0], track_array[i][1], track_array[i+1][0], track_array[i+1][1], arcade.color.BLACK, 1)
def on_update(self, delta_time):
""" Movement and game logic """
# Call update on all sprites (The sprites don't do much in this
# example though.)
self.player_list.update()
# print(self.drawing)
# print(self.player_sprite.points)
def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed. """
# Forward/back
if key == arcade.key.UP:
self.player_sprite.speed = MOVEMENT_SPEED
elif key == arcade.key.DOWN:
self.player_sprite.speed = -MOVEMENT_SPEED
# Rotate left/right
elif key == arcade.key.LEFT:
self.player_sprite.change_angle = ANGLE_SPEED
elif key == arcade.key.RIGHT:
self.player_sprite.change_angle = -ANGLE_SPEED
def on_key_release(self, key, modifiers):
"""Called when the user releases a key. """
if key == arcade.key.UP or key == arcade.key.DOWN:
self.player_sprite.speed = 0
elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
self.player_sprite.change_angle = 0
def on_mouse_press(self, x, y, button, modifiers):
"""
Called when the user presses a mouse button.
"""
self.track_arrays.append([])
self.drawing = True
def on_mouse_release(self, x, y, button, modifiers):
"""
Called when a user releases a mouse button.
"""
self.drawing = False
def on_mouse_motion(self, x, y, dx, dy):
""" Called to update our objects. Happens approximately 60 times per second."""
if self.drawing:
self.track_arrays[-1].append([x,y])
def main():
""" Main method """
window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
window.setup()
arcade.run()
if __name__ == "__main__":
main()
Related
I'm trying to make a very simple game with my son in Kivy and Python. We are trying to make our viewport (camera) centered over the player as they move around our map that is self generating. We get the initial view, then as the player moves the initial chunk is shown in the correct place, but new chunks aren't being drawn at all.
By debugging, we can tell that we are creating chunks, that they have good values, and that our draw_chunks function knows to grab more chunks and to draw them. They just aren't being drawn. We think that our code for drawing the rectangles is probably wrong, but works for the initial load of the game. We've spent a couple hours trying to fix it. We've adjusted the viewport position a couple different ways as well as the rectangle code, but nothing seems to work. I'm hoping someone can point out what we missed. It is probably something very obvious or silly that we are overlooking. Does anyone have any ideas?
import kivy
import random
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics import Color, Ellipse, Line, Rectangle
from kivy.uix.widget import Widget
from kivy.config import Config
from kivy.core.window import Window
import enum
# Constants for the chunk size and map dimensions
CHUNK_SIZE = 48
TILE_SIZE = 16
MAP_WIDTH = 256
MAP_HEIGHT = 256
#**************************
#* Tile Int Enum Class *
#**************************
class TileEnum(enum.IntEnum):
GRASS = 0
DIRT = 1
CONCRETE = 2
ASPHALT = 3
# Class to represent the game map
class GameMap:
def __init__(self, seed=None):
self.seed = seed
if seed is not None:
random.seed(seed)
self.chunks = {}
self.first_chunk = False
def generate_chunk(self, chunk_x, chunk_y):
# Use the RNG to generate the terrain for this chunk
terrain = []
for x in range(0, CHUNK_SIZE):
column = []
for y in range(0, CHUNK_SIZE):
column.append(random.randint(0, 3))
terrain.append(column)
return terrain
def get_chunk(self, chunk_x, chunk_y):
# Check if the chunk has already been generated
if (chunk_x, chunk_y) in self.chunks:
print("found it",chunk_x, chunk_y)
return self.chunks[(chunk_x, chunk_y)]
else:
# Generate the chunk and store it in the chunk cache
chunk = self.generate_chunk(chunk_x, chunk_y)
self.chunks[(chunk_x, chunk_y)] = chunk
print("made it",chunk_x,chunk_y)
return chunk
# Class to represent the player
class Player:
def __init__(self, pos=(0, 0)):
self.x, self.y = pos
self.speed = TILE_SIZE/2
def move_left(self):
self.x += self.speed
def move_right(self):
self.x -= self.speed
def move_up(self):
self.y -= self.speed
def move_down(self):
self.y += self.speed
class GameScreen(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.viewport_size = (TILE_SIZE*CHUNK_SIZE, TILE_SIZE*CHUNK_SIZE)
self.viewport_pos = (0, 0)
self.size = self.viewport_size
self.map = GameMap(seed=123)
self.player = Player((self.viewport_size[0]/2, self.viewport_size[1]/2))
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def draw_chunks(self):
# Determine the chunks that are currently in view
viewport_left = int(self.viewport_pos[0] // (CHUNK_SIZE * TILE_SIZE))
viewport_top = int(self.viewport_pos[1] // (CHUNK_SIZE * TILE_SIZE))
viewport_right = int((self.viewport_pos[0] + self.viewport_size[0]) // (CHUNK_SIZE * TILE_SIZE))
viewport_bottom = int((self.viewport_pos[1] + self.viewport_size[1]) // (CHUNK_SIZE * TILE_SIZE))
print(viewport_left, viewport_top, viewport_right, viewport_bottom)
# Iterate over the visible chunks and draw them
for x in range(viewport_left, viewport_right + 1):
for y in range(viewport_top, viewport_bottom + 1):
chunk = self.map.get_chunk(x, y)
#print(chunk)
for i in range(len(chunk)):
for j in range(len(chunk[i])):
if chunk[i][j] == TileEnum.GRASS:
# Draw a green square for grass
with self.canvas:
Color(0.25, 0.75, 0.25)
elif chunk[i][j] == TileEnum.DIRT:
# Draw a brown square for dirt
with self.canvas:
Color(0.75, 0.5, 0.25)
elif chunk[i][j] == TileEnum.CONCRETE:
# Draw a gray square for concrete
with self.canvas:
Color(0.5, 0.5, 0.75)
elif chunk[i][j] == TileEnum.ASPHALT:
# Draw a black square for asphalt
with self.canvas:
Color(0.25, 0.25, 0.5)
with self.canvas:
Rectangle(pos=(
(x * CHUNK_SIZE + i) * TILE_SIZE + self.viewport_pos[0],
(y * CHUNK_SIZE + j) * TILE_SIZE + self.viewport_pos[1]),
size=(TILE_SIZE, TILE_SIZE))
def draw_player(self):
# Draw a circle for the player
with self.canvas:
Color(0, 0.5, 0)
Ellipse(pos=(self.viewport_size[0]/2 - (TILE_SIZE/2), self.viewport_size[0]/2 - (TILE_SIZE/2)), size=(TILE_SIZE, TILE_SIZE))
def update(self, dt):
# Update the viewport position to keep the player centered
self.viewport_pos = (self.player.x - self.viewport_size[0]/2, self.player.y - self.viewport_size[1]/2)
print(self.viewport_pos)
# Redraw the chunks and player
self.canvas.clear()
self.draw_chunks()
self.draw_player()
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
#print(keycode)
if keycode[1] == 'left':
self.player.move_left()
elif keycode[1] == 'right':
self.player.move_right()
elif keycode[1] == 'up':
self.player.move_up()
elif keycode[1] == 'down':
self.player.move_down()
# Main application class
class ProceduralGenerationGameApp(App):
def build(self):
self.title = "Procedural Generation Game"
Config.set("graphics", "width", "768")
Config.set("graphics", "height", "768")
Config.set("graphics", "resizable", False)
Config.set("graphics", "borderless", False)
Config.set("graphics", "fullscreen", False)
Config.set("graphics", "window_state", "normal")
Config.set("graphics", "show_cursor", True)
Config.write()
window_width = Config.getint("graphics", "width")
window_height = Config.getint("graphics", "height")
# Create the game screen and schedule the update function to be called every frame
game_screen = GameScreen()
Window.size = (window_width, window_height)
Clock.schedule_interval(game_screen.update, 1)# 1.0 / 60.0)
return game_screen
if __name__ == "__main__":
ProceduralGenerationGameApp().run()
We updated the Rectangle code to this and reversed the players direction in his move functions:
with self.canvas:
x_chunk_offset = (x * CHUNK_SIZE * TILE_SIZE)
y_chunk_offset = (y * CHUNK_SIZE * TILE_SIZE)
x_tile_offset = (i * TILE_SIZE)
y_tile_offset = (j * TILE_SIZE)
actual_x = x_chunk_offset + x_tile_offset - self.viewport_pos[0]
actual_y = y_chunk_offset + y_tile_offset - self.viewport_pos[1]
Rectangle(pos=(actual_x, actual_y), size=(TILE_SIZE, TILE_SIZE))
im working on a game in python arcade, where the player runs around, shoots zombies, etc. very basic game. just recently, i started implementing the spawn over time part, but i get an error that just makes no sense:
AttributeError: 'MyGame' object has no attribute 'total_time'
it makes no sense because i have stated self.total_time in MyGame.
how do i fix this?
import arcade
import random
import math
import arcade.gui
import time
import timeit
SPRITE_SCALING = 0.35
SPRITE_SCALING_LASER = 0.8
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
SCREEN_TITLE = "zombier shooter"
ENEMY_COUNT = 20
BULLET_SPEED = 30
MOVEMENT_SPEED = 5
SPRITE_SPEED = 1
INDICATOR_BAR_OFFSET = 32
ENEMY_ATTACK_COOLDOWN = 1
PLAYER_HEALTH = 5
SCENE_MENU = 'SCENE_MENU'
SCENE_GAME = 'SCENE_GAME'
class QuitButton(arcade.gui.UIFlatButton):
def on_click(self, event: arcade.gui.UIOnClickEvent):
arcade.exit()
class Player(arcade.Sprite):
def update(self):
""" moves the player """
# move player.
self.center_x += self.change_x
self.center_y += self.change_y
# check for out of bounds
if self.left < 0:
self.left = 0
elif self.right > SCREEN_WIDTH - 1:
self.right = SCREEN_WIDTH - 1
if self.bottom < 0:
self.bottom = 0
elif self.top > SCREEN_HEIGHT - 1:
self.top = SCREEN_HEIGHT - 1
class Enemy(arcade.Sprite):
"""
This class represents the enemies on our screen.
"""
def follow_sprite(self, player_sprite):
"""
This function will move the current sprite towards whatever
other sprite is specified as a parameter.
"""
if self.center_y < player_sprite.center_y:
self.center_y += min(SPRITE_SPEED, player_sprite.center_y - self.center_y)
elif self.center_y > player_sprite.center_y:
self.center_y -= min(SPRITE_SPEED, self.center_y - player_sprite.center_y)
if self.center_x < player_sprite.center_x:
self.center_x += min(SPRITE_SPEED, player_sprite.center_x - self.center_x)
elif self.center_x > player_sprite.center_x:
self.center_x -= min(SPRITE_SPEED, self.center_x - player_sprite.center_x)
class MyGame(arcade.Window):
"""
main game class
"""
def __init__(self, width, height, title):
"""
initialises stuff
"""
# call the parent class initializer
super().__init__(width, height, title)
self.scene = SCENE_MENU
# variables that will hold sprite lists
self.player_list = None
# set up the player info
self.player_sprite = None
# track the current state of what key is pressed
self.left_pressed = False
self.right_pressed = False
self.up_pressed = False
self.down_pressed = False
# --- Required for all code that uses UI element,
# a UIManager to handle the UI.
self.manager = arcade.gui.UIManager()
self.manager.enable()
# Set background color
arcade.set_background_color(arcade.color.DARK_BLUE_GRAY)
# Create a vertical BoxGroup to align buttons
self.v_box = arcade.gui.UIBoxLayout()
# Create the buttons
start_button = arcade.gui.UIFlatButton(text="Start Game", width=200)
self.v_box.add(start_button.with_space_around(bottom=20))
settings_button = arcade.gui.UIFlatButton(text="Settings", width=200)
self.v_box.add(settings_button.with_space_around(bottom=20))
# Again, method 1. Use a child class to handle events.
quit_button = QuitButton(text="Quit", width=200)
self.v_box.add(quit_button)
# --- Method 2 for handling click events,
# assign self.on_click_start as callback
start_button.on_click = self.on_click_start
# --- Method 3 for handling click events,
# use a decorator to handle on_click events
#settings_button.event("on_click")
def on_click_settings(event):
print("Settings:", event)
# Create a widget to hold the v_box widget, that will center the buttons
self.manager.add(
arcade.gui.UIAnchorWidget(
anchor_x="center_x",
anchor_y="center_y",
child=self.v_box)
)
def setup(self):
""" Set up the game and initialize the variables. """
# sprite lists
self.player_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.bullet_list = arcade.SpriteList()
#setup timer
self.total_time = 0.0
# setup score
self.score = 0
self.score_text = None
# setup health info
self.health = 5
self.health_text = None
self.dead = None
# set up the player
self.player_sprite = Player(":resources:images/animated_characters/female_person/femalePerson_idle.png",
SPRITE_SCALING)
self.player_sprite.center_x = 50
self.player_sprite.center_y = 50
self.player_list.append(self.player_sprite)
def on_draw(self):
""" render the screen. """
# clear the screen
self.clear()
if self.scene == SCENE_MENU:
self.manager.draw()
elif self.scene == SCENE_GAME:
# draw all the sprites.
self.player_list.draw()
self.enemy_list.draw()
self.bullet_list.draw()
# put score text on the screen
output = f"Score: {self.score}"
arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
# put helth text on the screen
output = f"Health: {self.health}"
arcade.draw_text(output, 10, 40, arcade.color.WHITE, 14)
if self.health <= 0:
self.player_sprite.remove_from_sprite_lists()
# put u died text on the screen
output = f"YOU DIED"
arcade.draw_text(output, 500, 400, arcade.color.RED, 50)
output = f"Click to Exit"
arcade.draw_text(output, 550, 300, arcade.color.BLACK, 30)
def on_click_start(self, event):
self.setup()
self.scene = SCENE_GAME
self.manager.disable()
print("Start:", event)
def on_mouse_press(self, x, y, button, modifiers):
""" Called whenever the mouse button is clicked. """
if self.health <= 0:
exit()
# create a bullet
bullet = arcade.Sprite(":resources:images/space_shooter/laserBlue01.png", SPRITE_SCALING_LASER)
# Position the bullet at the player's current location
start_x = self.player_sprite.center_x
start_y = self.player_sprite.center_y
bullet.center_x = start_x
bullet.center_y = start_y
# Get from the mouse the destination location for the bullet
# IMPORTANT! If you have a scrolling screen, you will also need
# to add in self.view_bottom and self.view_left.
dest_x = x
dest_y = y
# Do math to calculate how to get the bullet to the destination.
# Calculation the angle in radians between the start points
# and end points. This is the angle the bullet will travel.
x_diff = dest_x - start_x
y_diff = dest_y - start_y
angle = math.atan2(y_diff, x_diff)
# Angle the bullet sprite so it doesn't look like it is flying
# sideways.
bullet.angle = math.degrees(angle)
print(f"Bullet angle: {bullet.angle:.2f}")
# Taking into account the angle, calculate our change_x
# and change_y. Velocity is how fast the bullet travels.
bullet.change_x = math.cos(angle) * BULLET_SPEED
bullet.change_y = math.sin(angle) * BULLET_SPEED
# Add the bullet to the appropriate lists
self.bullet_list.append(bullet)
def update_player_speed(self):
# calculate speed based on the keys pressed
self.player_sprite.change_x = 0
self.player_sprite.change_y = 0
if self.up_pressed and not self.down_pressed:
self.player_sprite.change_y = MOVEMENT_SPEED
elif self.down_pressed and not self.up_pressed:
self.player_sprite.change_y = -MOVEMENT_SPEED
if self.left_pressed and not self.right_pressed:
self.player_sprite.change_x = -MOVEMENT_SPEED
elif self.right_pressed and not self.left_pressed:
self.player_sprite.change_x = MOVEMENT_SPEED
def on_update(self, delta_time):
""" updates values n stuff """
if self.scene == SCENE_GAME:
# call update to move the sprite
self.player_list.update()
# Call update on all sprites
self.bullet_list.update()
# go through each bullet
for bullet in self.bullet_list:
# check each bullet to see if it hit a zombie
hit_list = arcade.check_for_collision_with_list(bullet, self.enemy_list)
# if it did, remove the bullet
if len(hit_list) > 0:
bullet.remove_from_sprite_lists()
# for each enemy we hit with a bullet, remove enemy and add to the score
for enemy in hit_list:
enemy.remove_from_sprite_lists()
self.score += 1
# if bullet goes off screen, then remove it
if bullet.bottom > self.width or bullet.top < 0 or bullet.right < 0 or bullet.left > self.width:
bullet.remove_from_sprite_lists()
for enemy in self.enemy_list:
Enemy.follow_sprite(enemy, self.player_sprite)
# create a list of all sprites that had a collision with the player.
hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.enemy_list)
# go through each sprite, if it got hit, then remove the sprite and lower score and health
for enemy in hit_list:
enemy.remove_from_sprite_lists()
self.score -= 1
self.health -= 1
# Accumulate the total time
self.total_time += delta_time
# Calculate minutes
minutes = int(self.total_time) // 60
# Calculate seconds by using a modulus (remainder)
seconds = int(self.total_time) % 60
# Calculate 100s of a second
seconds_100s = int((self.total_time - seconds) * 100)
if self.total_time > 5:
for i in range(5):
# enemy texture
enemy = arcade.Sprite(":resources:images/animated_characters/zombie/zombie_idle.png", SPRITE_SCALING)
enemy.center_x = random.randrange(SCREEN_WIDTH)
enemy.center_y = random.randrange(SCREEN_HEIGHT)
self.enemy_list.append(enemy)
self.total_time = 0.0
def on_key_press(self, key, modifiers):
"""called when user presses a key. """
if key == arcade.key.UP:
self.up_pressed = True
self.update_player_speed()
elif key == arcade.key.DOWN:
self.down_pressed = True
self.update_player_speed()
elif key == arcade.key.LEFT:
self.left_pressed = True
self.update_player_speed()
elif key == arcade.key.RIGHT:
self.right_pressed = True
self.update_player_speed()
def on_key_release(self, key, modifiers):
"""called when user releases a key. """
if key == arcade.key.UP:
self.up_pressed = False
self.update_player_speed()
elif key == arcade.key.DOWN:
self.down_pressed = False
self.update_player_speed()
elif key == arcade.key.LEFT:
self.left_pressed = False
self.update_player_speed()
elif key == arcade.key.RIGHT:
self.right_pressed = False
self.update_player_speed()
def main():
""" Main function """
MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
arcade.run()
if __name__ == "__main__":
main()
You defined self.total_time in setup() and not in __init__(), which should be a red-flag, because it could happen that self.total_time is actually accessed before setup() is called. And exactly this happened.
The error appears in on_update() when you try to run self.total_time += delta_time - in order for this line to work, self.total_time has to be initialised before - but setup() wasn't executed before, so self.total_time 'does not exist' yet.
So you can fix your code by moving the line self.total_time = 0.0 from setup() to __init__() - this way you make sure that you create this variable once the class gets initialised and before anything else gets executed.
If you plan on using variables in a class across different methods it's better to define them in __init__() to avoid such problems.
I'm currently working on building the game Pong. One aspect of the game is displaying the current level. What I want to happen is have 3 levels. When the score is equal to 5, I want to display "Level 2". When the score is equal to 10, I want to display "Level 3" and then at 15 I want to display "You Win!" What's happening right now is as soon as the score is equal to 5, the Level number counts up every time it updates the window (super fast). Where or how do I write this so that it functions the way I want?
I created a function in the Pong class called level_up for this.
import arcade
import random
# These are Global constants to use throughout the game
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 300
BALL_RADIUS = 10
PADDLE_WIDTH = 10
PADDLE_HEIGHT = 50
MOVE_AMOUNT = 5
SCORE_HIT = 1
SCORE_MISS = 5
LEVEL_UP = 1
"""Point will identify the x and y coordinates of the ball"""
class Point:
def __init__(self):
"""The __init__ initializes the x and y coordinates"""
self.x=float(0)
self.y=float(0)
"""Velocity will identify the velocity"""
class Velocity:
def __init__(self):
"""The __init__ initializes the x and y velocity"""
self.dx=float(0)
self.dy=float(0)
"""Ball will identify the coordinates and the movement of the ball"""
class Ball:
def __init__(self):
"""The __init__ will initialize the Point and Velocity class values"""
self.center=Point()
#self.center will call the self.x and self.y float values that are found in the Point class.
self.velocity=Velocity()
#self.velocity will call the self.dx and self.dy values that are found in the Velocity class.
self.velocity.dx=2
self.velocity.dy=2
def draw(self):
"""This creates the ball"""
arcade.draw_circle_filled(self.center.x, self.center.y,
BALL_RADIUS, arcade.color.FLUORESCENT_YELLOW)
def advance(self):
self.center.x += self.velocity.dx
self.center.y += self.velocity.dy
def bounce_horizontal(self):
self.velocity.dx *=-1
def bounce_vertical(self):
self.velocity.dy *=-1
def restart(self):
self.center.x=0
self.center.y=random.uniform(0,SCREEN_HEIGHT)
self.velocity.dx=random.uniform(1,8)
self.velocity.dy=random.uniform(0,8)
"""Paddle will represent the paddle"""
class Paddle:
def __init__(self):
"""The __init__ will initialize the location of the paddle"""
self.center=Point()
#self.center calls the Point class
self.center.x=SCREEN_WIDTH
self.center.y=SCREEN_HEIGHT//2
def draw(self):
arcade.draw_rectangle_filled(self.center.x, self.center.y,
PADDLE_WIDTH, PADDLE_HEIGHT, arcade.color.FLUORESCENT_PINK)
def move_up(self):
self.center.y+=MOVE_AMOUNT
if self.center.y > SCREEN_HEIGHT:
self.center.y -= MOVE_AMOUNT
def move_down(self):
self.center.y-=MOVE_AMOUNT
if self.center.y < 0:
self.center.y += MOVE_AMOUNT
class Pong(arcade.Window):
"""
This class handles all the game callbacks and interaction
It assumes the following classes exist:
Point
Velocity
Ball
Paddle
This class will then call the appropriate functions of
each of the above classes.
You are welcome to modify anything in this class,
but should not have to if you don't want to.
"""
def __init__(self, width, height):
"""
Sets up the initial conditions of the game
:param width: Screen width
:param height: Screen height
"""
super().__init__(width, height)
self.ball = Ball()
self.paddle = Paddle()
self.score = 0
self.level = 1
# These are used to see if the user is
# holding down the arrow keys
self.holding_left = False
self.holding_right = False
arcade.set_background_color(arcade.color.BLACK)
def on_draw(self):
"""
Called automatically by the arcade framework.
Handles the responsiblity of drawing all elements.
"""
# clear the screen to begin drawing
arcade.start_render()
# draw each object
self.ball.draw()
self.paddle.draw()
self.draw_score()
self.draw_level()
def draw_score(self):
"""
Puts the current score on the screen
"""
score_text = "Score: {}".format(self.score)
start_x = 190
start_y = SCREEN_HEIGHT - 20
arcade.draw_text(score_text, start_x=start_x, start_y=start_y, font_size=12,
color=arcade.color.DEEP_SKY_BLUE)
def draw_level(self):
"""Displays the level"""
level_text = f"LEVEL {self.level}"
start_x= 175
start_y=SCREEN_HEIGHT - 40
arcade.draw_text(level_text, start_x=start_x, start_y=start_y, font_size=20,
color=arcade.color.ELECTRIC_GREEN)
def update(self, delta_time):
"""
Update each object in the game.
:param delta_time: tells us how much time has actually elapsed
"""
# Move the ball forward one element in time
self.ball.advance()
# Check to see if keys are being held, and then
# take appropriate action
self.check_keys()
# check for ball at important places
self.check_miss()
self.check_hit()
self.check_bounce()
def check_hit(self):
"""
Checks to see if the ball has hit the paddle
and if so, calls its bounce method.
:return:
"""
too_close_x = (PADDLE_WIDTH / 2) + BALL_RADIUS
too_close_y = (PADDLE_HEIGHT / 2) + BALL_RADIUS
if (abs(self.ball.center.x - self.paddle.center.x) < too_close_x and
abs(self.ball.center.y - self.paddle.center.y) < too_close_y and
self.ball.velocity.dx > 0):
# we are too close and moving right, this is a hit!
self.ball.bounce_horizontal()
self.score += SCORE_HIT
def level_up(self):
if self.score ==5:
self.level += LEVEL_UP
if self.score ==10:
self.level += LEVEL_UP
def check_miss(self):
"""
Checks to see if the ball went past the paddle
and if so, restarts it.
"""
if self.ball.center.x > SCREEN_WIDTH:
# We missed!
self.score -= SCORE_MISS
self.ball.restart()
def check_bounce(self):
"""
Checks to see if the ball has hit the borders
of the screen and if so, calls its bounce methods.
"""
if self.ball.center.x < 0 and self.ball.velocity.dx < 0:
self.ball.bounce_horizontal()
if self.ball.center.y < 0 and self.ball.velocity.dy < 0:
self.ball.bounce_vertical()
if self.ball.center.y > SCREEN_HEIGHT and self.ball.velocity.dy > 0:
self.ball.bounce_vertical()
def check_keys(self):
"""
Checks to see if the user is holding down an
arrow key, and if so, takes appropriate action.
"""
if self.holding_left:
self.paddle.move_down()
if self.holding_right:
self.paddle.move_up()
def on_key_press(self, key, key_modifiers):
"""
Called when a key is pressed. Sets the state of
holding an arrow key.
:param key: The key that was pressed
:param key_modifiers: Things like shift, ctrl, etc
"""
if key == arcade.key.LEFT or key == arcade.key.DOWN:
self.holding_left = True
if key == arcade.key.RIGHT or key == arcade.key.UP:
self.holding_right = True
def on_key_release(self, key, key_modifiers):
"""
Called when a key is released. Sets the state of
the arrow key as being not held anymore.
:param key: The key that was pressed
:param key_modifiers: Things like shift, ctrl, etc
"""
if key == arcade.key.LEFT or key == arcade.key.DOWN:
self.holding_left = False
if key == arcade.key.RIGHT or key == arcade.key.UP:
self.holding_right = False
# Creates the game and starts it going
window = Pong(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.run()
I just answered my own question, I changed the level_up function to say:
def level_up(self):
if self.score ==5 and self.level==1:
self.level += LEVEL_UP
if self.score ==10 and self.level==2:
self.level += LEVEL_UP
So, I'm writing a snake program using the tkinter Library. The program is globally working but I have a little problem with the inputs' treatment indeed if i give two input too quickly only the last one will be interpret. And i don't really know how to solve this i try to force the update after every player's input but it's clearly not the good solution because it force the snake to move and make it able to teleport so I'm would be glad if someone has an idea to solve this issue. There is my code I'm sure that it could be improved but for now I would like to focus on the first issue.
import tkinter as tk
import numpy.random as rd
class snake:
def __init__(self,n,m):
self.n = n
self.m = m
self.body = [(n//2,m//2),(n//2,m//2-1)]
self.lenght = 2
self.food = (0,0)
self.relocate_food()
self.Game = -2
self.vector = (0,1) #(0,-1) = up, (0,1) = right, (0,1) = down, (-1,0) = left
self.speed = 120
def up(self):
self.vector = (-1,0)
def right(self):
self.vector = (0,1)
def down(self):
self.vector = (1,0)
def left(self):
self.vector = (0,-1)
def relocate_food(self):
x = rd.randint(0,self.n)
y = rd.randint(0,self.m)
i = 0
test = True
while i<self.lenght and test:
if (x,y) == self.body[i]:
test = False
self.relocate_food()
else:
i += 1
if i == self.lenght:
self.food = (x,y)
def collide(self):
head = self.body[0]
for i in range(1,self.lenght):
if head == self.body[i]:
self.Game = -1
break
x,y = head
if x>=self.n or y>=self.m or x<0 or y<0:
self.Game = -1
def eat(self):
head = self.body[0]
if head == self.food:
self.lenght +=1
x0, y0 = self.body[-1]
x1, y1 = self.body[-2]
x = x0 - x1
y = y0 - y1
self.body.append((x0+x,y0+y))
self.relocate_food()
if self.lenght%5 == 0:
self.speed = int(self.speed * 0.90)
def move(self):
dx, dy = self.vector
last_x, last_y = self.body[0]
new_x = last_x + dx
new_y = last_y + dy
self.body[0] = (new_x, new_y)
for k in range(1, self.lenght):
x, y = self.body[k]
self.body[k] = (last_x,last_y)
last_x, last_y = x, y
return
class screen(snake):
def __init__(self,root,n,m):
snake.__init__(self,n,m)
root.minsize(n*20,m*20)
root.maxsize(n*20,m*20)
root.configure(background='white')
self.root = root
self.n = n
self.m = m
self.speed = 130
self.canvas = tk.Canvas(root, width = n*20, height =m*20,bg='black')
self.canvas.bind_all("<Key-Up>",self.move_up)
self.canvas.bind_all("<Key-Down>",self.move_down)
self.canvas.bind_all("<Key-Left>",self.move_left)
self.canvas.bind_all("<Key-Right>",self.move_right)
self.canvas.grid(row=1,column=0)
self.draw_snake()
self.draw_food()
def draw_snake(self):
y,x = self.body[0]
self.canvas.create_rectangle(x*20,y*20,(x+1)*20,(y+1)*20,fill= 'red4')
for k in range(1,self.lenght):
y,x = self.body[k]
self.canvas.create_rectangle(x*20,y*20,(x+1)*20,(y+1)*20,fill= 'red')
def draw_food(self):
y,x =self.food
self.canvas.create_rectangle(x*20,y*20,(x+1)*20,(y+1)*20,fill= 'green')
def move_up(self,event):
if self.Game == -2:
self.Game =0
self.up()
self.update()
else:
self.up()
def move_down(self,event):
if self.Game == -2:
self.Game =0
self.down()
self.update()
else:
self.down()
def move_left(self,event):
if self.Game == -2:
self.Game =0
self.left()
self.update()
else:
self.left()
def move_right(self,event):
if self.Game == -2:
self.Game =0
self.right()
self.update()
else:
self.right()
def update(self):
if self.Game == -2:
return
self.move()
self.eat()
self.collide()
if self.Game == -1:
self.root.destroy()
return
self.canvas.delete("all")
self.draw_snake()
self.draw_food()
self.root.after(self.speed,self.update)
window = tk.Tk()
snake = screen(window,35,35)
snake.update()
window.mainloop()
This is not really a bug. Your animation uses an 'update' function that is executed every 120ms. So if you hit 2 arrow keys within 120ms (i.e. between two successive calls of 'update'), only the last hit is considered, because only one translation vector can be considered for each snake update. Nobody can blame you on that point, as time controlled animation is a discrete process with a given time window. It's the only solution to get fluid and regular animation (all video games are based on such a process), so that's clearly correct.
However, your code may still be improved on several aspects. For instance, at each animation frame, you delete all Canvas items and create a whole new set of items ('create_rectangle') for the snake elements and the food. This is not very efficient. It would be better to simply change the coordinates of the items (check the Canvas.coords function from the doc). Note that animating a snake simply requires to move the previous tail position to the new head position, to give the illusion of a moving beast. So moving only 1 item (2 items when eating food) is necessary at each frame, which is must faster to process.
Thank Furas for the basic idea it was what i needed. New code with my correction :
def __init__(self, root,n,m):
"""
"""
self.input = []
"""
"""
def move_up(self,event):
if self.Game == -2:
self.Game =0
self.up()
self.update()
else:
self.input.append(0)
"""
Same for all the move
"""
def update(self):
if self.Game == -2:
return
if len(self.input)>3: #Make sure that the player doesn't stack instruction
self.pop()
try:
input = self.input.pop(0)
except:
input = -1
if input == 0:
self.up()
elif input == 1:
self.right()
elif input == 2:
self.down()
elif input == 3:
self.left()
self.move()
self.eat()
self.collide()
if self.Game == -1:
self.root.destroy()
return
self.canvas.delete("all")
self.draw_snake()
self.draw_food()
self.root.after(self.speed,self.update)
Right now our code creates a grid starting at the top left and filling in rows and columns from left to right, row by row. Currently, there are a bunch of images it can pick from. It is set using a handful of IF statements that picks between shapes and rareshapes. What I am trying to figure out how to do is change the code so instead of it picking a random rareshape, I can decide what rareshape spawns. Still new to Python and finding a lot of little things that make sense to me from other languages don't work in Python so its throwing me off a little.
EDIT:
Here is the full code. Credit for the base code written by cactusbin and revised by Gareth Rees.
import pygame, random, time, sys
from pygame.locals import *
import itertools
import os
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
SHAPE_WIDTH = 64 # Width of each shape (pixels).
SHAPE_HEIGHT = 64 # Height of each shape (pixels).
PUZZLE_COLUMNS = 10 # Number of columns on the board.
PUZZLE_ROWS = 11 # Number of rows on the board.
MARGIN = 118 # Margin around the board (pixels).
WINDOW_WIDTH = PUZZLE_COLUMNS * SHAPE_WIDTH + 2 * MARGIN + 485
WINDOW_HEIGHT = PUZZLE_ROWS * SHAPE_HEIGHT + 2 * MARGIN - 150
FONT_SIZE = 60
TEXT_OFFSET = MARGIN + 950
# Map from number of matches to points scored.
MINIMUM_MATCH = 10
EXTRA_LENGTH_POINTS = .1
RANDOM_POINTS = .3
DELAY_PENALTY_SECONDS = 1
DELAY_PENALTY_POINTS = 0
FPS = 30
EXPLOSION_SPEED = 15 # In frames per second.
SPIN_SPEED = 15
REFILL_SPEED = 10 # In cells per second.
VERTICAL = False
class Cell(object):
"""
A cell on the board, with properties:
`image` -- a `Surface` object containing the sprite to draw here.
`offset` -- vertical offset in pixels for drawing this cell.
"""
def __init__(self, image):
self.offset = 0.0
self.image = image
def tick(self, dt):
self.offset = max(0.0, self.offset - dt * REFILL_SPEED)
class Board(object):
"""
A rectangular board of cells, with properties:
`w` -- width in cells.
`h` -- height in cells.
`size` -- total number of cells.
`board` -- list of cells.
`matches` -- list of matches, each being a list of exploding cells.
`refill` -- list of cells that are moving up to refill the board.
`score` -- score due to chain reactions.
"""
def __init__(self, width, height):
self.explosion = [pygame.image.load('images/explosion{}.png'.format(i))
for i in range(1, 7)]
self.spin = [pygame.image.load('images/powerframe{}.png'.format(i))
for i in range (1, 12)]
self.image_color = {}
self.shapes = []
self.rareshapes = []
colors = 'red blue yellow'
letters = 'acgtu'
for c in colors.split():
im = pygame.image.load('images/{}.png'.format(c))
self.shapes.append(im)
self.image_color[im] = c
for l in letters:
im = pygame.image.load('rareimages/{}{}.png'.format(c, l))
self.rareshapes.append(im)
self.image_color[im] = l
self.background = pygame.image.load("images/bg.png")
self.blank = pygame.image.load("images/blank.png")
self.x = pygame.image.load("images/x.png")
self.w = width
self.h = height
self.size = width * height
self.board = [Cell(self.blank) for _ in range(self.size)]
self.matches = []
self.refill = []
self.score = 0.0
self.spin_time = 15
def randomize(self):
"""
Replace the entire board with fresh shapes.
"""
rare_shapes = [1, 9, 23, 27, 40, 42, 50, 56, 70, 81, 90]
for i in range(self.size):
if i in rare_shapes:
self.board[i] = Cell(random.choice(self.rareshapes))
else:
self.board[i] = Cell(random.choice(self.shapes))
def pos(self, i, j):
"""
Return the index of the cell at position (i, j).
"""
assert(0 <= i < self.w)
assert(0 <= j < self.h)
return j * self.w + i
def busy(self):
"""
Return `True` if the board is busy animating an explosion or a
refill and so no further swaps should be permitted.
"""
return self.refill or self.matches
def tick(self, dt):
"""
Advance the board by `dt` seconds: move rising blocks (if
any); otherwise animate explosions for the matches (if any);
otherwise check for matches.
"""
if self.refill:
for c in self.refill:
c.tick(dt)
self.refill = [c for c in self.refill if c.offset > 0]
if self.refill:
return
elif self.matches:
self.explosion_time += dt
f = int(self.explosion_time * EXPLOSION_SPEED)
if f < len(self.explosion):
self.update_matches(self.explosion[f])
return
self.update_matches(self.blank)
self.refill = list(self.refill_columns())
self.explosion_time = 0
self.matches = self.find_matches()
def draw(self, display):
"""
Draw the board on the pygame surface `display`.
"""
display.blit(self.background, (0, 0))
for i, c in enumerate(self.board):
display.blit(c.image,
(MARGIN + SHAPE_WIDTH * (i % self.w),
MARGIN + SHAPE_HEIGHT * (i // self.w - c.offset) - 68))
display.blit(self.x, (995, 735))
display.blit(self.x, (1112, 735))
display.blit(self.x, (1228, 735))
def swap(self, cursor):
"""
Swap the two board cells covered by `cursor` and update the
matches.
"""
i = self.pos(*cursor)
b = self.board
b[i], b[i+1] = b[i+1], b[i]
self.matches = self.find_matches()
def find_matches(self):
"""
Search for matches (lines of cells with identical images) and
return a list of them, each match being represented as a list
of board positions.
"""
def lines():
for j in range(self.h):
yield range(j * self.w, (j + 1) * self.w)
for i in range(self.w):
yield range(i, self.size, self.w)
def key(i):
return self.image_color.get(self.board[i].image)
def matches():
for line in lines():
for _, group in itertools.groupby(line, key):
match = list(group)
if len(match) >= MINIMUM_MATCH:
yield match
self.score = self.score + 1
return list(matches())
def update_matches(self, image):
"""
Replace all the cells in any of the matches with `image`.
"""
for match in self.matches:
for position in match:
self.board[position].image = image
def refill_columns(self):
"""
Move cells downwards in columns to fill blank cells, and
create new cells as necessary so that each column is full. Set
appropriate offsets for the cells to animate into place.
"""
for i in range(self.w):
target = self.size - i - 1
for pos in range(target, -1, -self.w):
if self.board[pos].image != self.blank:
c = self.board[target]
c.image = self.board[pos].image
c.offset = (target - pos) // self.w
target -= self.w
yield c
offset = 1 + (target - pos) // self.w
for pos in range(target, -1, -self.w):
c = self.board[pos]
c.image = random.choice(self.shapes)
c.offset = offset
yield c
class Game(object):
"""
The state of the game, with properties:
`clock` -- the pygame clock.
`display` -- the window to draw into.
`font` -- a font for drawing the score.
`board` -- the board of cells.
`cursor` -- the current position of the (left half of) the cursor.
`score` -- the player's score.
`last_swap_ticks` --
`swap_time` -- time since last swap (in seconds).
"""
def __init__(self):
pygame.init()
pygame.display.set_caption("Nucleotide")
self.clock = pygame.time.Clock()
self.display = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT),
DOUBLEBUF)
self.board = Board(PUZZLE_COLUMNS, PUZZLE_ROWS)
self.font = pygame.font.Font(None, FONT_SIZE)
def start(self):
"""
Start a new game with a random board.
"""
self.board.randomize()
self.cursor = [0, 0]
self.score = 0.0
self.swap_time = 125
def quit(self):
"""
Quit the game and exit the program.
"""
pygame.quit()
sys.exit()
def play(self):
"""
Play a game: repeatedly tick, draw and respond to input until
the QUIT event is received.
"""
self.start()
while True:
self.draw()
dt = min(self.clock.tick(FPS) / 1000, 1 / FPS)
self.swap_time -= dt
for event in pygame.event.get():
if event.type == KEYUP:
self.input(event.key)
elif event.type == QUIT:
self.quit()
elif self.swap_time == 0:
self.quit()
self.board.tick(dt)
def input(self, key):
"""
Respond to the player pressing `key`.
"""
if key == K_q:
self.quit()
elif key == K_RIGHT and self.cursor[0] < self.board.w - 2:
self.cursor[0] += 1
elif key == K_LEFT and self.cursor[0] > 0:
self.cursor[0] -= 1
elif key == K_DOWN and self.cursor[1] < self.board.h - 1:
self.cursor[1] += 1
elif key == K_UP and self.cursor[1] > 0:
self.cursor[1] -= 1
elif key == K_SPACE and not self.board.busy():
self.swap()
def swap(self):
"""
Swap the two cells under the cursor and update the player's score.
"""
self.board.swap(self.cursor)
def draw(self):
self.board.draw(self.display)
self.draw_score()
self.draw_time()
if VERTICAL == False:
self.draw_cursor()
elif VERTICAL == True:
self.draw_cursor2()
pygame.display.update()
def draw_time(self):
s = int(self.swap_time)
text = self.font.render(str(int(s/60)) + ":" + str(s%60).zfill(2),
True, BLACK)
self.display.blit(text, (TEXT_OFFSET, WINDOW_HEIGHT - 170))
def draw_score(self):
total_score = self.score
def draw_cursor(self):
topLeft = (MARGIN + self.cursor[0] * SHAPE_WIDTH,
MARGIN + self.cursor[1] * SHAPE_HEIGHT - 68)
topRight = (topLeft[0] + SHAPE_WIDTH * 2, topLeft[1])
bottomLeft = (topLeft[0], topLeft[1] + SHAPE_HEIGHT)
bottomRight = (topRight[0], topRight[1] + SHAPE_HEIGHT)
pygame.draw.lines(self.display, WHITE, True,
[topLeft, topRight, bottomRight, bottomLeft], 3)
if __name__ == '__main__':
Game().play()
If what you are asking for is a way to more easily specify at which rareshapecount intervals you should place a rare shape instead of a normal shape, the following approach is more readable:
def randomize(self):
"""
Replace the entire board with fresh shapes.
"""
# locations we want to place a rare shape
rare_shapes = [9, 23, 27]
for i in range(self.size):
if i in rare_shapes:
self.board[i] = Cell(random.choice(self.rareshapes))
else:
self.board[i] = Cell (random.choice(self.shapes))
Optionally, you could randomly populate rare_shapes if you don't feel like hardcoding the intervals each time, making for a more varied experience (i.e., if you're designing a game or something similar).
What you mean by "I can decide what rareshape spawns instead of it picking a random rareshape" is unclear to me. Would you care to give more explanations ? Like how you would tell the program which rareshape to use ?
In the meantime, here's a somewhat more pythonic version of your code:
def randomize(self):
"""
Replace the entire board with fresh shapes.
"""
specials = dict((x, self.rareshapes) for x in (9, 23, 27))
get_shape_source = lambda x: specials.get(x, self.shapes)
for i in xrange(min(self.size, 41)):
self.board[i] = Cell(random.choice(get_shape_source(i)))
Note that this would break if len(self.board) < min(self.size, 41) but well, that's still basically what your current code do.
edit: given your comment, the obvious way to explicitly choose which rareshape goes where is to explicitly associate images with spots. Now what's the best way to do so / the best place ton configure this really depends on your whole code or at least on more than what you posted. As a very simple and minimal exemple, you could just have this:
from collections import ordereddict
def load_images(self)
self.image_color = {}
self.shapes = []
self.rareshapes = ordereddict()
colors = 'red', 'blue', 'yellow'
letters = 'acgtu'
for c in colors:
im = pygame.image.load('images/{}.png'.format(c))
self.shapes.append(im)
self.image_color[im] = c
for l in letters:
im = pygame.image.load('rareimages/{}{}.png'.format(c, l))
self.rareshapes.[(c, l)] = im
self.image_color[im] = l
def randomize(self):
"""
Replace the entire board with fresh shapes.
"""
raremap = {
# spot index : rareshape
9: ('red', 'a')],
23: ('blue', 'u'),
27: ('yellow', 'g')
}
for i in xrange(self.size):
if i in raremap:
im = self.rareshapes[raremap[i]]
else:
im = random.choice(self.shapes)
self.board[i] = Cell(im)
But it will be just unmaintainable in the long run - too much hardcoded stuff, and too much knowledge leaking from one method to another. I don't know what 'self' is an instance of, but you should considered splitting the responsabilities so you have the invariant parts in one class and the "configration" (images to load, spots / rareshapes mapping etc) in another. Some design patterns that come to mind are TemplateMethod (where you have an abstract base class with the invariant parts and concrete subclasses implementing the "configuration" part), Builder, and of course Strategy (in your case the "Strategy" class would take care of the configuration).