Jul-24-2024, 08:56 PM
Dear deanhystad,
Thank you so much for your tireless, patient support!!
The game is now finished and I can give it to a friend for his 50th birthday!!
I am very grateful to you, because I couldn't have done it without your help.
For those who might need the code as a guide for their own game projects, I am posting the code here.
Thank you so much for your tireless, patient support!!
The game is now finished and I can give it to a friend for his 50th birthday!!
I am very grateful to you, because I couldn't have done it without your help.
For those who might need the code as a guide for their own game projects, I am posting the code here.
import time import pygame from PIL import Image import random, math from itertools import product import sys, os # hides errors in console sys.stdout = os.devnull sys.stderr = os.devnull pygame.init() clock = pygame.time.Clock() fps = 60 screen_width = 1280 screen_height = 720 screen = pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption("Space Invaders") # define fonts font30 = pygame.font.SysFont('Constantia', 30) font40 = pygame.font.SysFont('Constantia', 40) alien_cooldown = 1000 # bullet cooldown in milliseconds last_count = pygame.time.get_ticks() last_alien_shot = pygame.time.get_ticks() # define colours red = (255, 0, 0) green = (0, 255, 0) white = (255, 255, 255) # load image bg = pygame.image.load("bg.png") screen_rect = bg.get_rect() def draw_bg(): screen.blit(bg, (0, 0)) # define function for creating text def draw_text(text, font, text_col, x, y): img = font.render(text, True, text_col) screen.blit(img, (x, y)) ################ space invaders things ############################################################################### # create spaceship class class Spaceship(pygame.sprite.Sprite): def __init__(self, x, y, health): pygame.sprite.Sprite.__init__(self) self.x = x self.y = y self.health_start = health self.health_remaining = health self.image = pygame.image.load("ship.png") self.rect = self.image.get_rect() self.rect.center = x, y self.xmin = self.rect.width // 2 # Compute Spaceship x range. self.xmax = screen_width - self.xmin self.last_shot = pygame.time.get_ticks() def move(self, xpos): self.xpos = xpos self.rect.centerx = max(self.xmin, min(self.xmax, xpos)) def update(self): # set a cooldown variable cooldown = 500 # milliseconds game_over = 0 # record current time time_now = pygame.time.get_ticks() # shoot, get key press key = pygame.key.get_pressed() if key[pygame.K_SPACE] and time_now - self.last_shot > cooldown: weapon_list = ["bullet.png", "flash.png", "rocket.png"] chosen = random.choice(weapon_list) if level == 1: # single bullet bullet = Bullets(self.rect.centerx, self.rect.top, chosen) bullet_group.add(bullet) self.last_shot = time_now else: # double bullets bullet_1 = Bullets(self.rect.centerx - 43, self.rect.top, chosen) bullet_2 = Bullets(self.rect.centerx + 43, self.rect.top, chosen) bullet_group.add(bullet_1, bullet_2) self.last_shot = time_now # update mask self.mask = pygame.mask.from_surface(self.image) # draw health bar pygame.draw.rect(screen, red, (self.rect.x, (self.rect.bottom + 10), self.rect.width, 15)) if self.health_remaining > 0: pygame.draw.rect(screen, green, ( self.rect.x, (self.rect.bottom + 10), int(self.rect.width * (self.health_remaining / self.health_start)), 15)) elif self.health_remaining <= 0: explosion = Explosion(self.rect.centerx, self.rect.centery, 3, "yellow") explosion_group.add(explosion) self.kill() game_over = -1 return game_over # create player spaceship = Spaceship(screen_width / 2, screen_height - 100, 3) paddle_group = pygame.sprite.Group() ball_group = pygame.sprite.Group() # create Bullets class class Bullets(pygame.sprite.Sprite): def __init__(self, x, y, chosen): pygame.sprite.Sprite.__init__(self) self.x = x self.y = y self.chosen = chosen self.image = pygame.image.load(chosen) self.rect = self.image.get_rect() self.rect.center = x, y def hit_endboss(self): x = self.rect.centerx y = self.rect.centery return x, y def update(self): self.rect.y -= 5 if self.rect.bottom < 0: self.kill() hits = pygame.sprite.spritecollide(self, alien_group, True, pygame.sprite.collide_mask) if hits: self.kill() for alien in hits: x, y = alien.hit() if self.chosen == "rocket.png": explosion = Explosion(x, y, 4, "yellow") explosion_group.add(explosion) hits_rocket_expl_and_aliens = pygame.sprite.spritecollide(explosion, alien_group, True, pygame.sprite.collide_mask) if hits_rocket_expl_and_aliens: for alien in hits_rocket_expl_and_aliens: x, y = alien.hit() explosion_red = Explosion(x, y, 2, "red") explosion_group.add(explosion_red) else: explosion = Explosion(x, y, 2, "yellow") explosion_group.add(explosion) # create Aliens class class Aliens(pygame.sprite.Sprite): def __init__(self, x, y, move_direction): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("alien" + str(random.randint(1, 6)) + ".png") self.x = x self.y = y self.move_direction = move_direction self.rect = self.image.get_rect() self.rect.center = x, y def hit(self): x = self.rect.centerx y = self.rect.centery self.kill() return x, y def update(self, move_direction): self.rect.x += self.move_direction if self.rect.right >= screen_width or self.rect.left <= 0: self.move_direction = -self.move_direction self.rect.y += 20 if pygame.sprite.spritecollide(self, spaceship_group, False, pygame.sprite.collide_mask): spaceship.health_remaining = -5 if pygame.sprite.spritecollide(self, paddle_group, False, pygame.sprite.collide_mask): draw_text('GAME OVER!', font40, white, int(screen_width / 2 - 100), int(screen_height / 2 + 50)) # create Alien Bullets class class Alien_Bullets(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("alien_bullet.png") self.rect = self.image.get_rect() self.rect.center = x, y def update(self): self.rect.y += 2 if self.rect.top > screen_height: self.kill() if pygame.sprite.spritecollide(self, spaceship_group, False, pygame.sprite.collide_mask): self.kill() # reduce spaceship health spaceship.health_remaining -= 1 explosion = Explosion(self.rect.centerx, self.rect.centery, 1, "yellow") explosion_group.add(explosion) # create Explosion class class Explosion(pygame.sprite.Sprite): def __init__(self, x, y, size, color): pygame.sprite.Sprite.__init__(self) self.images = [] if color == "yellow": for num in range(1, 8): img = pygame.image.load(f"explosion{num}.png") # ship is hit if size == 1: img = pygame.transform.scale(img, (20, 20)) # alien is hit if size == 2: img = pygame.transform.scale(img, (100, 100)) # ship gameover if size == 3: img = pygame.transform.scale(img, (160, 160)) # rocket hits alien if size == 4: img = pygame.transform.scale(img, (500, 500)) # add the image to the list self.images.append(img) self.index = 0 self.image = self.images[self.index] self.rect = self.image.get_rect() self.rect.center = x, y self.counter = 0 # add the image to the list if color == "red": # rocketExplosion hits alien, it occurs a red explosion for num1 in range(1, 8): img = pygame.image.load(f"explosion2_{num1}.png") img = pygame.transform.scale(img, (100, 100)) # add the image to the list self.images.append(img) self.index = 0 self.image = self.images[self.index] self.rect = self.image.get_rect() self.rect.center = x, y self.counter = 0 def update(self): explosion_speed = 3 # update explosion animation self.counter += 1 if self.counter >= explosion_speed and self.index < len(self.images) - 1: self.counter = 0 self.index += 1 self.image = self.images[self.index] # if the animation is complete, delete explosion if self.index >= len(self.images) - 1 and self.counter >= explosion_speed: self.kill() # create sprite groups spaceship_group = pygame.sprite.Group() spaceship_group.add(spaceship) bullet_group = pygame.sprite.Group() alien_group = pygame.sprite.Group() alien_bullet_group = pygame.sprite.Group() explosion_group = pygame.sprite.Group() # a different red explosion explosion_red_group = pygame.sprite.Group() def create_aliens(rows, cols, move_direction): # generate aliens for row in range(rows): for item in range(cols): alien = Aliens(100 + item * 100, 100 + row * 100, move_direction) alien_group.add(alien) def play_space_invaders(level, move_direction): game_over = 0 last_count = pygame.time.get_ticks() last_alien_shot = pygame.time.get_ticks() create_aliens(4, 10, move_direction) countdown = 3 run = True while run: clock.tick(fps) # draw background draw_bg() # space invaders single bullets level if countdown == 0: # create random alien bullets # record current time time_now = pygame.time.get_ticks() # shoot if time_now - last_alien_shot > alien_cooldown and len(alien_bullet_group) < 5 and len( alien_group) > 0: attacking_alien = random.choice(alien_group.sprites()) alien_bullet = Alien_Bullets(attacking_alien.rect.centerx, attacking_alien.rect.bottom) alien_bullet_group.add(alien_bullet) last_alien_shot = time_now # check if all the aliens have been killed if len(alien_group) == 0: game_over = 1 level += 1 if len(spaceship_group) == 0: game_over = -1 if game_over == 0: # update spaceship game_over = spaceship.update() # update sprite groups bullet_group.update() alien_group.update(move_direction) alien_bullet_group.update() explosion_group.update() explosion_red_group.update() else: if game_over == -1: draw_text('GAME OVER!', font40, white, int(screen_width / 2 - 100), int(screen_height / 2 + 50)) if game_over == 1: draw_text('YOU WIN!', font40, white, int(screen_width / 2 - 100), int(screen_height / 2 + 50)) return level if countdown > 0: draw_text('GET READY!', font40, white, int(screen_width / 2 - 110), int(screen_height / 2 + 50)) draw_text(str(countdown), font40, white, int(screen_width / 2 - 10), int(screen_height / 2 + 100)) count_timer = pygame.time.get_ticks() if count_timer - last_count > 1000: countdown -= 1 last_count = count_timer # draw sprite groups spaceship_group.draw(screen) bullet_group.draw(screen) alien_group.draw(screen) alien_bullet_group.draw(screen) explosion_group.draw(screen) explosion_red_group.draw(screen) # event handlers for event in pygame.event.get(): if event.type == pygame.QUIT: run = False elif event.type == pygame.MOUSEMOTION: # space invaders level spaceship.move(event.pos[0]) pygame.display.flip() pygame.quit() # breakout things ############################################################# TEXT_COLOR = (255, 255, 255) FOREGROUND = (0, 0, 0) # Recolor image pixels that are this color TRANSPARENT = (255, 255, 255) # Make image pixels this color transparent BALL_COLOR = (255, 255, 255) PADDLE_COLOR = (255, 255, 255) BALL_IMAGE = "ball.png" PADDLE_IMAGE = "paddle.png" def create_image(file, color=None): """ Create image from a file. If color is specified, replace all FOREGROUND pixels with color pixels. Modify image so TRANSPARENT colored pixels are transparent. """ if color: # Recolor the image image = Image.open(file).convert("RGB") for xy in product(range(image.width), range(image.height)): if image.getpixel(xy) == FOREGROUND: image.putpixel(xy, color) image = pygame.image.fromstring(image.tobytes(), image.size, "RGB") else: image = pygame.image.load(file) image.set_colorkey(TRANSPARENT) return image.convert() class EnhancedSprite(pygame.sprite.Sprite): def __init__(self, image, group=None, **kwargs): super().__init__(**kwargs) self.image = image self.rect = image.get_rect() if group is not None: group.add(self) def at(self, x, y): """Convenience method for setting my position""" self.x = x self.y = y return self # Properties below expose properties of my rectangle so you can use # self.x = 10 or self.centery = 30 instead of self.rect.x = 10 @property def x(self): return self.rect.x @x.setter def x(self, value): self.rect.x = value @property def y(self): return self.rect.y @y.setter def y(self, value): self.rect.y = value @property def centerx(self): return self.rect.centerx @centerx.setter def centerx(self, value): self.rect.centerx = value @property def centery(self): return self.rect.centery @centery.setter def centery(self, value): self.rect.centery = value @property def right(self): return self.rect.right @right.setter def right(self, value): self.rect.right = value @property def bottom(self): return self.rect.bottom @bottom.setter def bottom(self, value): self.rect.bottom = value @property def width(self): return self.rect.width @property def height(self): return self.rect.height class Paddle(EnhancedSprite): """The sprite the player moves around to redirect the ball""" group = pygame.sprite.Group() def __init__(self, x, y, health): super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group) self.x = x self.y = y self.health_start = health self.health_remaining = health self.image = pygame.image.load("paddle.png") self.rect = self.image.get_rect() self.rect.center = x, y self.xmin = self.rect.width // 2 # Compute Spaceship x range. self.xmax = screen_width - self.xmin self.last_shot = pygame.time.get_ticks() def move(self, xpos): """Move to follow the cursor. Clamp to window bounds""" self.rect.centerx = max(self.xmin, min(self.xmax, xpos)) class LifeCounter(): """Keep track of lives count. Display lives remaining using ball image""" def __init__(self, x, y, count=5): self.x, self.y = x, y self.image = create_image(BALL_IMAGE, BALL_COLOR) self.spacing = self.image.get_width() + 5 self.group = pygame.sprite.Group() self.reset(count) def reset(self, count): """Reset number of lives""" self.count = count for c in range(count - 1): EnhancedSprite(self.image, self.group).at(self.x + c * self.spacing, self.y) def __len__(self): """Return number of lives remaining""" return self.count def kill(self): """Reduce number of lives""" if self.count > 1: self.group.sprites()[-1].kill() self.count = max(0, self.count - 1) class Ball(EnhancedSprite): """Ball bounces around colliding with walls, paddles and bricks""" group = pygame.sprite.Group() def __init__(self, paddle, lives, speed=5): super().__init__(create_image(BALL_IMAGE, BALL_COLOR), self.group) self.paddle = paddle self.lives = lives self.speed = speed self.dx = self.dy = 0 self.xfloat = self.yfloat = 0 self.xmax = screen_width - self.rect.width self.ymax = paddle.bottom - self.rect.height self.reset(0) def at(self, x, y): self.xfloat = x self.yfloat = y return super().at(x, y) def reset(self, score=None): """Reset for a new game""" self.active = False if score is not None: self.score = score def start(self): """Start moving the ball in a random direction""" angle = random.random() - 0.5 # Launch angle limited to about +/-60 degrees self.dx = self.speed * math.sin(angle) self.dy = -self.speed * math.cos(angle) self.active = True def move(self): """Update the ball position. Check for collisions with bricks, walls and the paddle""" hit_status = 0 if not self.active: # Sit on top of the paddle self.at(self.paddle.centerx - self.width // 2, self.paddle.y - self.height - 2) return self # Did I hit some bricks? Update the bricks and the score x1, y1 = self.xfloat, self.yfloat x2, y2 = x1 + self.dx, y1 + self.dy if (xhits := pygame.sprite.spritecollide(self.at(x2, y1), alien_group, True, pygame.sprite.collide_mask)): self.dx = -self.dx hit_status += 1 if (yhits := pygame.sprite.spritecollide(self.at(x1, y2), alien_group, True, pygame.sprite.collide_mask)): self.dy = -self.dy hit_status += 2 # hits = pygame.sprite.spritecollide(self, alien_group, True, pygame.sprite.collide_mask) if xhits or yhits: for alien in xhits or yhits: x, y = alien.hit() explosion = Explosion(x, y, 2, "yellow") explosion_group.add(explosion) # Did I hit a wall? if x2 <= 0 or x2 >= self.xmax: self.dx = -self.dx hit_status += 4 if y2 <= 0: self.dy = abs(self.dy) hit_status += 8 # Did I get past the paddle? if (y2 >= self.paddle.y) and ((self.x > self.paddle.right) or (self.right < self.paddle.x)): self.lives.kill() self.active = False elif self.dy > 0 and pygame.Rect.colliderect(self.at(x2, y2).rect, self.paddle.rect): # I hit the paddle. Compute angle of reflection bangle = math.atan2(-self.dx, self.dy) # Ball angle of approach pangle = math.atan2(self.centerx - self.paddle.centerx, 30) # Paddle angle rangle = (pangle - bangle) / 2 # Angle of reflection self.dx = math.sin(rangle) * self.speed self.dy = -math.cos(rangle) * self.speed hit_status += 16 if hit_status > 0: self.at(x1, y1) else: self.at(x2, y2) def play_breakout(level, move_direction): game_over = 0 paddle = Paddle(screen_width / 2, screen_height - 100, 3) paddle_group.add(paddle) lives = LifeCounter(10, screen_height - 30) ball = Ball(paddle, lives) ball_group.add(ball) last_count = pygame.time.get_ticks() create_aliens(3, 10, move_direction) countdown = 3 run = True while run: p = pygame.mouse.get_pos() clock.tick(fps) # draw background draw_bg() # breakout level if countdown == 0: # create random alien bullets # record current time time_now = pygame.time.get_ticks() # in breakout levels aliens shouldn't shoot # check if all the aliens have been killed if len(alien_group) == 0: paddle.kill() ball.kill() # game_over = 1 level += 1 return level if game_over == 0: # update paddle game_over = paddle.move(p[0]) # update sprite groups alien_group.update(level) else: if game_over == -1: draw_text('GAME OVER!', font40, white, int(screen_width / 2 - 100), int(screen_height / 2 + 50)) if game_over == 1: draw_text('YOU WIN!', font40, white, int(screen_width / 2 - 100), int(screen_height / 2 + 50)) if countdown > 0: draw_text('GET READY!', font40, white, int(screen_width / 2 - 110), int(screen_height / 2 + 50)) draw_text(str(countdown), font40, white, int(screen_width / 2 - 10), int(screen_height / 2 + 100)) count_timer = pygame.time.get_ticks() if count_timer - last_count > 1000: countdown -= 1 last_count = count_timer # update explosion group alien_group.update(move_direction) explosion_group.update() paddle_group.update() ball_group.update() # draw sprite groups alien_group.draw(screen) explosion_group.draw(screen) paddle_group.draw(screen) ball_group.draw(screen) # event handlers for event in pygame.event.get(): if event.type == pygame.QUIT: run = False elif event.type == pygame.MOUSEMOTION: # breakout level paddle.move(event.pos[0]) elif event.type == pygame.MOUSEBUTTONUP: if not ball.active: ball.start() ball.move() pygame.display.flip() pygame.quit() ##### end boss things ############################################################## # level = 0 end_boss_cooldown = 4000 # bullet cooldown in milliseconds end_boss_centerx = screen_width / 2 end_boss_centery = 220 end_boss_center = end_boss_centerx, end_boss_centery bullet_group = pygame.sprite.Group() # Create EndBoss class class EndBoss(pygame.sprite.Sprite): def __init__(self, center, health): super().__init__() self.center = center self.health_start = health self.health_remaining = health self.image = pygame.image.load("ship_4.png") self.rect = self.image.get_rect() self.rect.center = center def update(self): if pygame.sprite.spritecollide(self, bullet_group, True, pygame.sprite.collide_mask): x, y = Bullets.hit_endboss(self) explosion = Explosion(x, y, 2, "yellow") end_boss_explosion_group.add(explosion) # reduce spaceship health end_boss.health_remaining -= 2 # draw health bar pygame.draw.rect(screen, red, (self.rect.x, (self.rect.top - 15), self.rect.width, 15)) if self.health_remaining > 0: pygame.draw.rect(screen, green, ( self.rect.x, (self.rect.top - 15), int(self.rect.width * (self.health_remaining / self.health_start)), 15)) elif self.health_remaining <= 0: explosion = Explosion(self.rect.centerx, self.rect.centery, 4, "yellow") explosion_group.add(explosion) end_boss_gun.kill() self.kill() end_boss_laser_group = pygame.sprite.Group() class EndBossGun(pygame.sprite.Sprite): def __init__(self, center, angle=0): super().__init__() self.image = pygame.image.load("gun4.png") self.gun_image = self.image.copy() self.rect = self.gun_image.get_rect() self.center = center self.rect.center = self.center # guns is (x, y) position of gun relative to center of spaceship. self.guns = [pygame.math.Vector2(12, 70), pygame.math.Vector2(-12, 70)] self.lasers = pygame.sprite.Group() self.angle = angle self.last_shot = pygame.time.get_ticks() def aim(self, p): # Rotate end_boss' gun x_dist = p[0] - end_boss_centerx y_dist = end_boss_centery - (screen_height - 100) self.angle = math.degrees(math.atan2(y_dist, x_dist)) + 90 self.image = pygame.transform.rotate(self.gun_image, self.angle) self.rect = self.image.get_rect(center=(end_boss_centerx, end_boss_centery)) return self.angle def fire(self, angle): """Fire a laser.""" for gun in self.guns: origin = pygame.math.Vector2(end_boss_center) + gun.rotate(-angle) end_boss_laser = EndBossLaser(angle, origin) end_boss_laser_group.add(end_boss_laser) class EndBossLaser(pygame.sprite.Sprite): """A laser shot from the spaceship.""" def __init__(self, angle, origin): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("endbosslaser.png") clean_laser_image = self.image.copy() self.rect = clean_laser_image.get_rect() """Fire laser. angle : angle in degrees the gun is pointing. origin : origin of my path (end of gun). """ self.angle = angle self.image = pygame.transform.rotate(clean_laser_image, angle) self.origin = origin self.rect.center = origin self.unit_vector = pygame.math.Vector2(math.sin(self.angle / 180 * math.pi), math.cos(self.angle / 180 * math.pi)) self.distance = 23 def update(self): """Update position.""" self.distance += 1 if self.distance > 500: self.kill() else: self.rect.center = self.origin + self.unit_vector * self.distance if pygame.sprite.spritecollide(self, spaceship_group, False, pygame.sprite.collide_mask): self.kill() # reduce spaceship health spaceship.health_remaining -= 0.5 explosion = Explosion(self.rect.centerx, self.rect.centery, 1, "yellow") explosion_group.add(explosion) if spaceship.health_remaining <= 0: spaceship.kill() end_boss_group = pygame.sprite.Group() end_boss = EndBoss(end_boss_center, 300) end_boss_gun = EndBossGun(end_boss_center, 0) end_boss_group.add(end_boss) end_boss_group.add(end_boss_gun) end_boss_explosion_group = pygame.sprite.Group() def play_end_boss_level(): game_over = 0 last_count = pygame.time.get_ticks() last_end_boss_shot = pygame.time.get_ticks() countdown = 3 run = True while run: p = pygame.mouse.get_pos() clock.tick(fps) draw_bg() if countdown == 0: angle = end_boss_gun.aim(p) time_now = pygame.time.get_ticks() if time_now - last_end_boss_shot > end_boss_cooldown and len(end_boss_group) > 0: end_boss_gun.fire(angle) last_end_boss_shot = time_now if len(end_boss_group) == 0: end_boss_gun.kill() game_over = 1 if len(spaceship_group) == 0: draw_text('GAME OVER!', font40, white, int(screen_width / 2 - 100), int(screen_height / 2 + 50)) if game_over == 0: spaceship.update() bullet_group.update() end_boss_group.update() # end_boss_gun_group.update() explosion_group.update() end_boss_laser_group.update() end_boss_explosion_group.update() else: if game_over == -1: draw_text('GAME OVER!', font40, (255, 255, 255), int(screen_width / 2 - 100), int(screen_height / 2 + 50)) if game_over == 1: draw_text('YOU SAVED THE EARTH!', font40, (255, 255, 255), int(screen_width / 2 - 100), int(screen_height / 2 + 50)) if countdown > 0: draw_text('GET READY!', font40, (255, 255, 255), int(screen_width / 2 - 110), int(screen_height / 2 + 50)) draw_text(str(countdown), font40, (255, 255, 255), int(screen_width / 2 - 10), int(screen_height / 2 + 100)) count_timer = pygame.time.get_ticks() if count_timer - last_count > 1000: countdown -= 1 last_count = count_timer spaceship_group.draw(screen) bullet_group.draw(screen) explosion_group.draw(screen) end_boss_group.draw(screen) end_boss_laser_group.draw(screen) end_boss_explosion_group.draw(screen) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False spaceship.move(p[0]) pygame.display.flip() #################################################################################### # game_over = 0 # 0 is no game over, 1 means player has won, -1 means player has lost space_invaders_levels = [1, 3, 5, 7] breakout_levels = [2, 4, 6] end_boss_level = [8] level = 1 move_direction = 1 for i in range(1, 9): if level in space_invaders_levels: level = play_space_invaders(level, move_direction) else: if level in breakout_levels: level = play_breakout(level, move_direction) if level in end_boss_level: play_end_boss_level()