diff --git a/Amnesia_Theme.ogg b/Amnesia_Theme.ogg new file mode 100644 index 0000000..d9c1a4f Binary files /dev/null and b/Amnesia_Theme.ogg differ diff --git a/Connect_Sound.ogg b/Connect_Sound.ogg new file mode 100644 index 0000000..f696e6a Binary files /dev/null and b/Connect_Sound.ogg differ diff --git a/Dying_Sound.ogg b/Dying_Sound.ogg new file mode 100644 index 0000000..0b74401 Binary files /dev/null and b/Dying_Sound.ogg differ diff --git a/Escape_The_Maze.py b/Escape_The_Maze.py new file mode 100644 index 0000000..53249fc --- /dev/null +++ b/Escape_The_Maze.py @@ -0,0 +1,986 @@ +import pygame +# import network module +from PodSixNet.Connection import connection, ConnectionListener +from pygame.locals import QUIT, KEYDOWN, MOUSEMOTION +import time +import sys +import math +from random import choice +import random +from pygame.locals import * + +pygame.init() +##import sounds +pygame.mixer.init(frequency=22050,size=-16,channels=4) +amnesia_sound = pygame.mixer.Sound('Amnesia_Theme.ogg') +illuminati_sound = pygame.mixer.Sound('Illuminati_Sound.ogg') +underground_sound = pygame.mixer.Sound('Underground_Theme.ogg') +sewers_sound = pygame.mixer.Sound('Sewers_Theme.ogg') +scroll_sound = pygame.mixer.Sound('Scroll_Collect.ogg') +connect_sound = pygame.mixer.Sound('Connect_Sound.ogg') +dying_sound = pygame.mixer.Sound('Dying_Sound.ogg') +class PygameEscapeTheMazeView(object): + def __init__(self, model, screen, listener): + """Initialize the view with the specified model""" + self.model = model + self.screen = screen + self.listener = listener + self.font1 = pygame.font.SysFont('purisa', 30, True) + self.font2 = pygame.font.SysFont('sans,freesans,courier,arial', 48, True) + self.font4 = pygame.font.SysFont('sans,freesans,courier,arial', 100, True) + self.font3 = pygame.font.SysFont('purisa', 70, True) + self.font5 = pygame.font.SysFont('sans,freesans,courier,arial', 44, True) + self.font6 = pygame.font.SysFont('purisa', 28, True) + self.altar_revealed = False + self.ticker = 0 + self.played = False + self.win_ticker = 0 + self.fog_ticker = 0 + self.lose_ticker = 0 + self.played1 = False + + def draw(self): + if self.listener.start and self.ticker > 30: + ## if the actual game has started background is grey + self.screen.fill(pygame.Color('grey')) + is_monster = self.model.monster_num == self.model.player_num + ##create the mazes for the rectangle + for rect in self.model.lists.maze_segment_rect_list: + pygame.draw.rect(self.screen, pygame.Color('black'), rect) + + ##if you are in a certain distance of the exit + dist = self.model.cartesian_dist() + if self.altar_revealed and not is_monster: + altar_color = (pygame.Color('black')) + pygame.draw.rect(self.screen, altar_color, self.model.exit.rect) + self.altar_revealed = True + elif not is_monster: + if dist < 200: + altar_color = ((192.0/200)*dist,(192.0/200)*dist,(192.0/200)*dist) + ##draw the exit + pygame.draw.rect(self.screen, altar_color, self.model.exit.rect) + ##play the sound if you are close + if dist < 50 and not self.altar_revealed and not is_monster: + self.altar_revealed = True + illuminati_sound.play() + + ##draw all the scrolls and borders + for i, scroll in enumerate(self.model.lists.scroll_list): + if scroll.is_visible: + rect = pygame.Rect(scroll.border_x_pos, scroll.border_y_pos, scroll.border_width, scroll.border_height) + pygame.draw.rect(self.screen, pygame.Color('black'), rect) + rect = pygame.Rect(scroll.x_pos, scroll.y_pos, scroll.width, scroll.height) + pygame.draw.rect(self.screen, pygame.Color('gold'), rect) + + rect = pygame.Rect(scroll.border_nub_x_pos, scroll.border_nub2_y_pos, scroll.border_nub_size, scroll.border_nub_size) + pygame.draw.rect(self.screen, pygame.Color('black'), rect) + rect = pygame.Rect(scroll.border_nub_x_pos, scroll.border_nub1_y_pos, scroll.border_nub_size, scroll.border_nub_size) + pygame.draw.rect(self.screen, pygame.Color('black'), rect) + + rect = pygame.Rect(scroll.nub_x_pos, scroll.nub2_y_pos, scroll.nub_size, scroll.nub_size) + pygame.draw.rect(self.screen, pygame.Color('white'), rect) + rect = pygame.Rect(scroll.nub_x_pos, scroll.nub1_y_pos, scroll.nub_size, scroll.nub_size) + pygame.draw.rect(self.screen, pygame.Color('white'), rect) + + ##draw all the characters + for char in self.model.players: + #if self.model.players[self.model.player_num].still_alive: + if char.still_alive and not char.win: + pygame.draw.rect(self.screen, pygame.Color(char.color), char.rect) + + ##collision Rectangles + #for rect in self.model.lists.collision_rect_list: + # pygame.draw.rect(self.screen, pygame.Color('green'), rect) + + ##create fog of war + if not self.model.spectator: + self.fog_ticker += 1 + self.model.fog_of_war.draw_fog_of_war(self.screen, is_monster, self.fog_ticker) + + ##draw the scroll hud + scrolls_collected = self.model.lists.total_number_of_scrolls - self.model.lists.number_of_scrolls + if self.model.lists.total_number_of_scrolls > 10: + scrolls_total = 10 + else: + scrolls_total = self.model.lists.total_number_of_scrolls + if scrolls_collected > scrolls_total: + scrolls_collected = scrolls_total + if scrolls_collected < 0: + scrolls_collected = 0 + + if scrolls_collected == scrolls_total and self.model.monster_num != self.model.player_num and not self.model.spectator: + self.screen.blit(self.font1.render("All Scrolls Have Been found!", True, (255,0,0)), (350, 700)) + self.screen.blit(self.font1.render("GET TO THE ALTAR BEFORE HE EATS YOU!", True, (255,0,0)), (350, 750)) + + for i in range(scrolls_collected): + pygame.draw.rect(self.screen, pygame.Color('white'), (20*i + 10, 10, 15, 15)) + self.screen.blit(self.font1.render(str(scrolls_collected) + '/' + str(scrolls_total), True, (255,255,255)), (220, 6) ) + + if self.model.monster_num == self.model.player_num: + self.screen.blit(self.font1.render("You Thirst for blood!", True, (0,0,0)), (460, 50)) + + if self.model.player_num != self.model.monster_num and not self.model.spectator: + if (scrolls_collected == scrolls_total and self.model.exit_collision) and not self.model.spectator: + self.model.players[self.model.player_num].win = True + self.model.win_screen = True + elif self.model.exit_collision: + self.screen.blit(self.font1.render("The Altar is still locked", True, (255,255,255)), (400, 700)) + elif not self.model.spectator: + self.win_ticker = 0 + if not self.model.players[self.model.player_num].still_alive: + self.model.lose_screen = True + + if self.model.lose_screen: + if self.lose_ticker < 200: + self.screen.fill((255,0,0)) + self.screen.blit(self.font2.render("YOU GOT EATEN", True, (0,0,0)), (460, 500)) + self.lose_ticker += 1 + else: + self.model.spectator = True + self.model.lose_screen = False + + if self.model.win_screen: + if self.win_ticker < 200: + self.screen.fill(pygame.Color('black')) + self.screen.blit(self.font2.render("You have escaped!", True, (0, 0, 255)), (300, 500)) + self.win_ticker += 1 + else: + self.model.spectator = True + self.model.win_screen = False + if self.model.spectator: + self.screen.blit(self.font2.render("Spectator", True, (0, 0, 255)), (400, 100)) + + if is_monster and self.model.alive_players == 1: + self.screen.fill(pygame.Color('black')) + self.screen.blit(self.font2.render("You have killed all the imbeciles!", True, (255, 0, 0)), (300, 500)) + elif is_monster and (self.model.alive_players - self.model.won_players == 1): + self.screen.fill(pygame.Color('black')) + self.screen.blit(self.font2.render("You killed " + str(len(self.model.players) - self.model.won_players -1) + " players", True, (255, 0, 0)), (400, 450)) + if is_monster and len(self.model.players) - self.model.won_players == 1: + self.screen.fill(pygame.Color('black')) + self.screen.blit(self.font2.render("You have failed!", True, (255, 0, 0)), (400, 450)) + self.screen.blit(self.font2.render("You haven't killed a single person!", True, (255, 0, 0)), (300, 500)) + + self.model.check_game() + pygame.display.update() + elif self.ticker < 31: + self.ticker += 1 + + def draw_lobby(self): + """draw lobby before the game starts""" + self.screen.fill(pygame.Color('black')) + for i in range(self.model.connected_players): + if len(self.model.is_players_ready) != 0: + if self.model.is_players_ready[i]: + pygame.draw.rect(self.screen, pygame.Color('green'), (100*i + 300, 450, 70, 70)) + else: + if i == self.model.monster_num: + pygame.draw.rect(self.screen, pygame.Color('red'), (100*i + 300, 450, 70, 70)) + else: + pygame.draw.rect(self.screen, pygame.Color('blue'), (100*i + 300, 450, 70, 70)) + if self.model.player_ready: + self.screen.blit(self.font1.render('Waiting for Other Players', True, (0, 255, 0)), (400, 700)) + else: + self.screen.blit(self.font1.render('Press SpaceBar to Ready', True, (255, 0, 0)), (400, 700)) + self.screen.blit(self.font3.render('ESCAPE THE MAZE', True, (255, 255, 255)), (200, 100)) + pygame.display.update() + + def draw_load_screen(self): + """draws the screen before entering the story screen""" + if not self.played: + self.ticker = 0 + self.played = True + self.screen.fill(pygame.Color('black')) + for i in range(self.model.connected_players): + if self.model.is_players_ready[i]: + pygame.draw.rect(self.screen, pygame.Color('green'), (100*i + 300, 450, 70, 70)) + self.screen.blit(self.font3.render('ESCAPE THE MAZE', True, (255, 255, 255)), (200, 100)) + if self.ticker > 600: + self.screen.blit(self.font4.render('0', True, (255, 255, 255)), (550, 700)) + elif self.ticker > 400: + self.screen.blit(self.font4.render('1', True, (255, 255, 255)), (550, 700)) + elif self.ticker > 200: + self.screen.blit(self.font4.render('2', True, (255, 255, 255)), (550, 700)) + elif self.ticker < 200: + self.screen.blit(self.font4.render('3', True, (255, 255, 255)), (550, 700)) + + if (self.ticker < 10) or (self.ticker > 200 and self.ticker < 210) or (self.ticker > 400 and self.ticker < 410) or (self.ticker > 600 and self.ticker < 610): + connect_sound.play() + + pygame.display.update() + self.ticker += 1 + + def draw_story(self): + """draws the story and instructions before beginning the game""" + self.screen.fill((0,0,0)) + if self.model.player_num == self.model.monster_num: + self.screen.blit(self.font6.render('You have been trapped in a maze and mutated into a monster.', True, (255, 255, 255)), (10, 200)) + self.screen.blit(self.font6.render('Kill your companions before they escape.', True, (255, 255, 255)), (175, 275)) + + else: + self.screen.blit(self.font6.render('You have been trapped in a maze', True, (255, 255, 255)), (235, 200)) + self.screen.blit(self.font6.render('One of your companions starts going insane.', True, (255, 255, 255)), (145, 250)) + self.screen.blit(self.font6.render('Escape before he kills you.', True, (255, 255, 255)), (300, 325)) + + self.screen.blit(self.font6.render('Move with:', True, (255,255,255)), (460, 550)) + pygame.draw.rect(self.screen, pygame.Color('white'), pygame.Rect(519,690,50,50)) + pygame.draw.rect(self.screen, pygame.Color('white'), pygame.Rect(459,750,50,50)) + pygame.draw.rect(self.screen, pygame.Color('white'), pygame.Rect(519,750,50,50)) + pygame.draw.rect(self.screen, pygame.Color('white'), pygame.Rect(579,750,50,50)) + self.screen.blit(self.font5.render('W', True, (0, 0, 0)), (524, 690)) + self.screen.blit(self.font2.render('A S D', True, (0, 0, 0)), (467, 747)) + + pygame.display.update() + + +class FogOfWar(object): + """Class creates fog around the character""" + def __init__(self, character, x_pos, y_pos, radius): + self.x_pos = x_pos + self.y_pos = y_pos + self.radius = radius + self.character = character + def update_fog_of_war(self): + self.x_pos = self.character.x_pos + self.character.width/2 + self.y_pos = self.character.y_pos + self.character.height/2 + def draw_fog_of_war(self, screen, is_monster, time): + if is_monster: ##if you are the monster, fog of war expands over time + radius = 200 + time**2/300000 + circle_radius = 80 + time/100 + color = 'red' + else: + color = 'black' + radius = 300 + circle_radius = 80 + + left_rect = pygame.Rect(0, 0, self.x_pos - radius, 1100) + right_rect = pygame.Rect(self.x_pos + radius, 0, 1100, 1100) + bottom_rect = pygame.Rect(0, self.y_pos + radius, 1100, 1100) + top_rect = pygame.Rect(0, 0, 1100, self.y_pos - radius) + pygame.draw.rect(screen, pygame.Color(color), left_rect) + pygame.draw.rect(screen, pygame.Color(color), right_rect) + pygame.draw.rect(screen, pygame.Color(color), bottom_rect) + pygame.draw.rect(screen, pygame.Color(color), top_rect) + for i in range(70): + ang = i * math.pi * 2.0 / 70 + dx = int(math.cos(ang) * (radius + 80)) + dy = int(math.sin(ang) * (radius + 80)) + x = self.x_pos + dx + y = self.y_pos + dy + pygame.draw.circle(screen, + pygame.Color(color), + (int(x), int(y)), + circle_radius) + +class Maze(object): + def __init__(self): + """Initializes all attributes necessary to create a maze""" + self.maze_segment_list = [] + self.WALL_WIDTH = 4 + self.MARGIN = 0 + self.WALL_LENGTH = 48 + self.WALL_WIDTH + self.MATRIX_CENTERS = 53*2 + self.isolated_direction = 'yes' + self.maze_matrix = [] + self.row_length = None + self.column_length = None + #self.maze_segment = Maze_Segment(0, 0, 0, 0) + +class Maze_Segment(object): + def __init__(self, x_pos, y_pos, width, height): + """holds the attributes for the maze""" + self.x_pos = x_pos + self.y_pos = y_pos + self.width = width + self.height = height + +class Scroll(object): + def __init__(self, x_pos, y_pos, width, height, is_visible): + """holds the attributes for the scroll""" + self.x_pos = x_pos + self.y_pos = y_pos + self.BORDER_MARGIN = 2 + self.width = width + self.height = height + self.border_x_pos = self.x_pos - self.BORDER_MARGIN + self.border_y_pos = self.y_pos - self.BORDER_MARGIN + self.border_width = self.width + self.BORDER_MARGIN*2 + self.border_height = self.height + self.BORDER_MARGIN*2 + self.nub_size = 6 + self.NUB_MARGIN = 2 + self.nub_x_pos = self.x_pos + (self.width - self.nub_size)/2 + self.nub1_y_pos = self.y_pos - self.nub_size - self.NUB_MARGIN + self.nub2_y_pos = self.y_pos + self.height + self.NUB_MARGIN + self.border_nub_x_pos = self.nub_x_pos - self.NUB_MARGIN + self.border_nub1_y_pos = self.nub1_y_pos - self.NUB_MARGIN + self.border_nub2_y_pos = self.nub2_y_pos - self.NUB_MARGIN + self.border_nub_size = self.nub_size + self.NUB_MARGIN*2 + self.is_visible = is_visible + +class Exit(object): + def __init__(self, x_pos, y_pos): + """attributes for the exit""" + self.x_pos = x_pos + self.y_pos = y_pos + self.width = 60 + self.height = 60 + self.rect = pygame.Rect(self.x_pos - self.width/2, self.y_pos - self.height/2, self.width, self.height) + self.center = self.update_center() + + def update_center(self): + return [self.x_pos - self.width/2, self.y_pos - self.height/2] + +class Character(object): + """represents the character""" + def __init__(self, x_pos, y_pos, rel_x_vel, rel_y_vel, width, height): + self.x_pos = x_pos + self.y_pos = y_pos + self.rel_x_pos = rel_x_vel + self.rel_y_pos = rel_y_vel + self.width = width + self.height = height + self.color = "blue" + self.VEL = 3 #how many pixels it updates + self.DIAG_VEL = 3/1.4 + self.rect = pygame.Rect(self.x_pos, self.y_pos, self.width, self.height) + self.monster = False + self.still_alive = True + self.win = False + self.center = self.update_center() + + def update_center(self): + return [self.x_pos - self.width/2, self.y_pos - self.height/2] + + def update_relative_positions(rel_x_pos, rel_y_pos): + """updates the relative position of the character""" + self.rel_x_pos = rel_x_pos + self.rel_y_pos = rel_y_pos + +class Lists(): + def __init__(self, character, collision, maze): + """lists has a bunch of lists of different attributes, (collision, scrolls, etc.)""" + self.character = character + self.collision = collision + self.maze = maze + self.maze_segment_list = [] ##create a list of objects for the maze segments + self.maze_segment_rect_list = [] ##create rectangles to go in the list + self.collision_rect_list = [0,0,0,0] ##create 4 rectangles to place around the character + self.collision_rect_is_colliding_list = [False, False, False, False] ##create 4 bools to see if the rectangles colide with the maze + self.scroll_is_colliding_list = [0,0,0,0] + self.scroll_rect_list = [] + self.number_of_scrolls = 0 + self.total_number_of_scrolls = 0 + self.scroll_list = [] + #################################### + ##GERENATE MAZE SEGMENT RECTANGLES## + #################################### + ###create rectangles for the maze to draw + for i in range(self.maze.row_length): ##for each of the rows + for j in range(self.maze.column_length): ##for each of the columns + ####four different cases here#### + if self.maze.maze_matrix[i][j] == 1: + if i != 0: + if self.maze.maze_matrix[i-1][j] == 1: ##upper wall + self.maze_segment_list.append(Maze_Segment(self.maze.MARGIN + j*self.maze.MATRIX_CENTERS, + self.maze.MARGIN +i*self.maze.MATRIX_CENTERS - self.maze.WALL_LENGTH, + self.maze.WALL_WIDTH, + self.maze.WALL_LENGTH + self.maze.WALL_WIDTH)) + if self.maze.isolated_direction == 'yes': + self.maze.isolated_direction = 'down' + else: + self.maze.isolated_direction = 'no' + if j != 0: + if self.maze.maze_matrix[i][j-1] == 1: + self.maze_segment_list.append(Maze_Segment(self.maze.MARGIN +j*self.maze.MATRIX_CENTERS - self.maze.WALL_LENGTH, + self.maze.MARGIN + i*self.maze.MATRIX_CENTERS, + self.maze.WALL_LENGTH + self.maze.WALL_WIDTH, + self.maze.WALL_WIDTH)) + if self.maze.isolated_direction == 'yes': + self.maze.isolated_direction = 'right' + else: + self.maze.isolated_direction = 'no' + if j != self.maze.column_length - 1: + if self.maze.maze_matrix[i][j + 1] == 1: + self.maze_segment_list.append(Maze_Segment(self.maze.MARGIN +j*self.maze.MATRIX_CENTERS, + self.maze.MARGIN + i*self.maze.MATRIX_CENTERS, + self.maze.WALL_LENGTH + self.maze.WALL_WIDTH, + self.maze.WALL_WIDTH)) + if self.maze.isolated_direction == 'yes': + self.maze.isolated_direction = 'left' + else: + self.maze.isolated_direction = 'no' + if i != self.maze.row_length - 1: + if self.maze.maze_matrix[i+1][j] == 1: + self.maze_segment_list.append(Maze_Segment(self.maze.MARGIN + j*self.maze.MATRIX_CENTERS, + self.maze.MARGIN +i*self.maze.MATRIX_CENTERS, + self.maze.WALL_WIDTH, + self.maze.WALL_LENGTH + self.maze.WALL_WIDTH)) + if self.maze.isolated_direction == 'yes': + self.maze.isolated_direction = 'up' + else: + self.maze.isolated_direction = 'no' + self.maze.isolated_direction = 'yes' + for maze_segment in self.maze_segment_list: ##for the objects in maze segments + self.maze_segment_rect_list.append(pygame.Rect(maze_segment.x_pos, ##add the rectangle to a new list + maze_segment.y_pos, + maze_segment.width, + maze_segment.height)) + + def update_maze_segment_rect_list(self): + """updates the maze_segments rectangles into a list""" + self.maze_segment_rect_list = [] + for maze_segment in self.maze_segment_list: ##for the objects in maze segments + self.maze_segment_rect_list.append(pygame.Rect(maze_segment.x_pos, ##add the rectangle to a new list + maze_segment.y_pos, + maze_segment.width, + maze_segment.height)) + + def update_collision_rect_list(self, is_monster): + """updates the collision rectangles into a list""" + if is_monster: ##if you are the monster, your collision rectangles are bigger + collision_rect = CollisionRectangle(0, 0, 3, 60) + else: + collision_rect = CollisionRectangle(0, 0, 3, self.character.width) + self.collision_rect_list[0] = pygame.Rect(self.character.x_pos - collision_rect.width, + self.character.y_pos, + collision_rect.width, + collision_rect.height) + self.collision_rect_list[1] = pygame.Rect(self.character.x_pos + self.character.width, + self.character.y_pos, + collision_rect.width, + collision_rect.height) + self.collision_rect_list[2] = pygame.Rect(self.character.x_pos, + self.character.y_pos - collision_rect.width, + collision_rect.height, + collision_rect.width) + self.collision_rect_list[3] = pygame.Rect(self.character.x_pos, + self.character.y_pos + self.character.height, + collision_rect.height, + collision_rect.width) + + def update_scroll_rect_list(self): + """updates the scroll rectangles into a list""" + self.scroll_rect_list = [] + ## for each obj in scroll_list, create a new list of the rectangles + for scroll in self.scroll_list: + self.scroll_rect_list.append(pygame.Rect(scroll.x_pos, scroll.y_pos, scroll.width, scroll.height)) + + def update_collision_rect_is_colliding_list(self): + """updates the booleans in the collision list""" + for i, collision_rect in enumerate(self.collision_rect_list): + self.collision_rect_is_colliding_list[i] = self.collision.return_collision_bool(collision_rect, + self.maze_segment_rect_list) + +class CollisionRectangle(object): + """defines the collision rectangle""" + def __init__(self, x_pos, y_pos, width = 2, height = 40): + self.x_pos = x_pos + self.y_pos = y_pos + self.width = width + self.height = height + +class CollisionDetection(object): + """defines collisions""" + def __init__(self, character, model): + self.character = character + self.model = model + self.char_is_colliding = False + # self.exit_collision = False + ##create rectangle list and boolean list + #def create_collision_rectangle(): + def return_collision_bool(self, rect, rect_list): + """check if rect is intersecting with elements in maze_rect_list""" + return rect.collidelist(rect_list) != -1 + + def update_character_collision(self): + """"sees if the character collides with the maze""" + self.char_is_colliding = self.return_collision_bool(self.character.rect, self.model.lists.maze_segment_rect_list) + +class EscapeTheMazeClientModel(object): + """CREATE MODEL FOR CLIENT""" + def __init__(self): + self.WINDOW_WIDTH = 1100 + self.WINDOW_HEIGHT = 1100 + self.players = [] + self.collision = None + self.player_num = 0 + self.maze = Maze() + self.lists = None ##initiate lists object + self.char_list = None + self.scroll_entity_list = [] + self.scroll_collision_index = -1 + self.temp_scroll_collision_index = -1 + self.scroll_removed = True + self.fog_of_war = None + self.exit = None + self.connected_players = 0 + self.ticker = 0 + self.player_ready = False + self.is_players_ready = [] + self.win_screen = False + self.spectator = False + self.run_once = False + self.lose_screen = False + self.alive_players = -1 + self.won_players = -1 + + def run_model(self): + self.fog_of_war.update_fog_of_war() + + def update_monster(self): + if self.ticker >20: + self.monster = self.players[self.monster_num] + ##if you collide with the monster, still_alive = false + if self.players[self.player_num].rect.colliderect(self.monster.rect) and self.player_num != self.monster_num and not self.spectator and not self.win_screen: + self.players[self.player_num].still_alive = False + if not self.run_once: + pygame.mixer.stop() + dying_sound.play() + self.run_once = True + if self.ticker < 21: + self.ticker += 1 + + def update_exit_collision(self): + self.exit_collision = self.players[self.player_num].rect.colliderect(self.exit.rect) == 1 + + def update_characters(self): + for char in self.players: + char.rect.left = char.x_pos + char.rect.top = char.y_pos + + def update_entities(self): + ##updates all the things + self.update_scrolls() + self.lists.update_scroll_rect_list() + self.update_exit() + self.collision.update_character_collision() + self.update_exit_collision() + self.lists.update_maze_segment_rect_list() + self.update_characters() + self.update_monster() + self.lists.update_collision_rect_list() + self.lists.update_collision_rect_is_colliding_list() + + def update_scrolls(self): + """if you hit a scroll, set its visibility to false, and send the index of the scroll to the other players""" + if self.player_num != self.monster_num and not self.spectator: + self.scroll_collision_index = self.players[self.player_num].rect.collidelist(self.lists.scroll_rect_list) + if self.temp_scroll_collision_index != -1: + if(self.lists.scroll_list[self.temp_scroll_collision_index].is_visible): + scroll_sound.play() + self.lists.scroll_list[self.temp_scroll_collision_index].is_visible = False + self.temp_scroll_collision_index = -1 + number_of_scrolls = 0 + for scroll in self.lists.scroll_list: + if scroll.is_visible: + number_of_scrolls += 1 + self.lists.number_of_scrolls = number_of_scrolls + def update_exit(self): + self.exit.rect = pygame.Rect(self.exit.x_pos - self.exit.width/2, self.exit.y_pos - self.exit.height/2, self.exit.width, self.exit.height) + + def move_maze(self, x_vel, y_vel): + """moves the maze when hitting wasd""" + for maze_segment in self.lists.maze_segment_list: + maze_segment.x_pos += x_vel + maze_segment.y_pos += y_vel + + def move_scrolls(self, x_vel, y_vel): + """moves scrolls relative to player""" + for scroll in self.lists.scroll_list: + scroll.x_pos += x_vel + scroll.y_pos += y_vel + scroll.border_x_pos += x_vel + scroll.border_y_pos += y_vel + scroll.nub_x_pos += x_vel + scroll.nub1_y_pos += y_vel + scroll.nub2_y_pos += y_vel + scroll.border_nub1_y_pos += y_vel + scroll.border_nub2_y_pos += y_vel + scroll.border_nub_x_pos +=x_vel + + def move_exit(self, x_vel, y_vel): + """moves exit relative to player""" + self.exit.x_pos += x_vel + self.exit.y_pos += y_vel + + def move_objects(self, x_vel, y_vel): + """moves scroll, exit, and maze together""" + self.move_maze(x_vel, y_vel) + self.move_scrolls(x_vel, y_vel) + self.move_exit(x_vel,y_vel) + + def edit_maze_position(self): + """initializes the position of the maze to be relative to the character""" + ##this is run once on initilization + for maze_segment in self.lists.maze_segment_list: + maze_segment.x_pos += self.WINDOW_WIDTH/2 - self.players[self.player_num].rel_x_pos + maze_segment.y_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_y_pos + + def edit_scroll_position(self): + """initializes the position of the scrolls to be relative to the character""" + for scroll in self.lists.scroll_list: + scroll.x_pos += self.WINDOW_WIDTH/2 - self.players[self.player_num].rel_x_pos + scroll.y_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_y_pos + scroll.border_x_pos += self.WINDOW_WIDTH/2 - self.players[self.player_num].rel_x_pos + scroll.border_y_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_y_pos + + scroll.nub_x_pos += self.WINDOW_WIDTH/2 - self.players[self.player_num].rel_x_pos + scroll.nub1_y_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_y_pos + scroll.nub2_y_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_y_pos + scroll.border_nub1_y_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_y_pos + scroll.border_nub2_y_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_y_pos + scroll.border_nub_x_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_x_pos + + def edit_exit_position(self): + """initializes the position of the exit to be relative to the character""" + self.exit.x_pos += self.WINDOW_WIDTH/2 - self.players[self.player_num].rel_x_pos + self.exit.y_pos += self.WINDOW_HEIGHT/2 - self.players[self.player_num].rel_y_pos + + def create_fog_of_war(self): + """creates fog of war""" + self.fog_of_war = FogOfWar(self.players[self.player_num], ##create fog of war + self.players[self.player_num].x_pos, + self.players[self.player_num].x_pos, 100) + + def create_players(self): + """creates a character""" + for char_entity in self.char_list: + ##create a new character in players for each entity in char_list + new_char = Character(self.WINDOW_WIDTH/2, + self.WINDOW_HEIGHT/2, + char_entity[0], + char_entity[1], + 40, + 40) + self.monster_num = char_entity[2] + self.players.append(new_char) + ##turns character entities into a Character to add to player + + def cartesian_dist(self): + """creates a distance between the player and exit""" + return math.sqrt((self.players[self.player_num].update_center()[0] - self.exit.update_center()[0])**2 + + (self.players[self.player_num].update_center()[1] - self.exit.update_center()[1])**2) + + def create_scrolls(self, scroll_entity_list): + """edits the scroll_list based off of server input""" + for scroll_entity in scroll_entity_list: + ##create a new scroll in scroll_list for each entity in the list + self.lists.scroll_list.append(Scroll(scroll_entity[0], scroll_entity[1], 10, 25, True)) ##adds a scroll into scroll_list, with width 7, height 20 + self.lists.total_number_of_scrolls = len(self.lists.scroll_list) + self.lists.number_of_scrolls = len(self.lists.scroll_list) + + def create_scroll_is_visible(self): + """Initializes the scroll_is_visible list to be true""" + self.scroll_is_visible = [] + for i in range(self.lists.number_of_scrolls): + self.scroll_is_visible.append(True) + + def check_game(self): + """checks the number of players alive""" + self.alive_players = 0 + for character in self.players: + if character.still_alive: + self.alive_players += 1 + self.won_players = 0 + for character in self.players: + if character.win: + self.won_players += 1 + + def create_monster(self): + self.players[self.monster_num].width = self.players[self.monster_num].width*1.5 + self.players[self.monster_num].height = self.players[self.monster_num].height*1.5 + self.players[self.monster_num].color = "red" + self.players[self.monster_num].rect = pygame.Rect(self.players[self.monster_num].x_pos, + self.players[self.monster_num].y_pos, + self.players[self.monster_num].width, + self.players[self.monster_num].height) + self.monster = self.players[self.monster_num] + +# class for I/O on network +# this represent the player, too +class Listener(ConnectionListener): + # init the player + def __init__(self, model, host, port): + self.Connect((host, port)) + self.model = model + self.ran_initiations = False + # True if the server sended the ready message + self.ready = False + self.load_screen = False + self.story = False + # True if the game is working + self.start = False + self.running = True + self.spectator = True + # font for writing the scores + self.font = pygame.font.SysFont('sans,freesans,courier,arial', 18, True) + self.played2 = False + self.played3 = False + + # function to manage character movement + def Network_move(self, data): + if data['player_number'] != self.model.player_num: + self.model.players[data['player_number']].x_pos = 550 - self.model.players[self.model.player_num].rel_x_pos + data['rel_x_pos'] + self.model.players[data['player_number']].y_pos = 550 - self.model.players[self.model.player_num].rel_y_pos + data['rel_y_pos'] + + def Network_generate_maze(self, data): + self.model.maze.maze_matrix = data['maze_matrix'] + self.model.maze.row_length = len(self.model.maze.maze_matrix) + self.model.maze.column_length = len(self.model.maze.maze_matrix[0][:]) + + # get the player number + def Network_number(self, data): + self.model.player_num = data['num'] + def Network_initialize_entities(self, data): + #self.model.test = data['char_list'] + self.model.char_list = data['char_list'] + self.model.scroll_entity_list = data['scroll_list'] + self.model.create_players() + self.model.create_fog_of_war() + + def Network_exit_location(self,data): + exit_location = data['exit'] + self.model.exit = Exit(exit_location[0],exit_location[1]) + + def Network_update_win(self, data): + self.model.players[data['player_number']].win = data['won'] + + def Network_update_entities(self, data): + #if data['player_number'] != self.model.player_num: + self.model.temp_scroll_collision_index = data['scroll_collision_index'] + + def Network_update_alive(self, data): + self.model.players[data['player_number']].still_alive = data['still_alive'] + + # def Network_monster_number(self,data): + # self.model.monster_num = data['monster_num'] + def Network_ready_players(self, data): + self.model.connected_players = data['connected_players'] + self.model.is_players_ready = [] + for i in range(self.model.connected_players): + self.model.is_players_ready.append(False) + + def Network_lobby(self, data): + #self.model.connected_players = data['connected_players'] + player_ready = data['p_ready'] + self.model.is_players_ready[data['player_number']] = player_ready + + def Network_update_condition(self, data): + self.story = data['story'] + self.load_screen = data['ready'] + self.start = data['start'] + + # if the game is ready + def Network_ready(self, data): + self.load_screen = True + + # start the game + def Network_start(self, data): + self.ready = False + self.start = True + + # mainloop + def update_listener(self): + if self.running: + # update connection + connection.Pump() + # update the listener + self.Pump() + if self.start: + self.ready = False + if not self.ran_initiations: + sound_int = random.randint(0,2) + if sound_int == 0: + amnesia_sound.play(-1) + if sound_int == 1: + underground_sound.play(-1) + if sound_int == 2: + sewers_sound.play(-1) + self.model.collision = CollisionDetection(self.model.players[self.model.player_num], self.model) + self.model.lists = Lists(self.model.players[self.model.player_num], self.model.collision, self.model.maze) + self.model.create_scrolls(self.model.scroll_entity_list) + self.model.edit_maze_position() + self.model.edit_scroll_position() + self.model.edit_exit_position() + self.ran_initiations = True + self.model.create_monster() + + # send to the server information about movement + self.model.update_exit() + self.model.update_scrolls() + self.model.lists.update_scroll_rect_list() + self.model.collision.update_character_collision() + connection.Send({'action': 'move', + 'player_number': self.model.player_num, + 'rel_x_pos': self.model.players[self.model.player_num].rel_x_pos, + 'rel_y_pos': self.model.players[self.model.player_num].rel_y_pos}) + self.model.lists.update_maze_segment_rect_list() + self.model.update_characters() + self.model.update_exit_collision() + connection.Send({'action': 'move', + 'player_number': self.model.player_num, + 'rel_x_pos': self.model.players[self.model.player_num].rel_x_pos, + 'rel_y_pos': self.model.players[self.model.player_num].rel_y_pos}) + self.model.lists.update_collision_rect_list(self.model.player_num == self.model.monster_num) + self.model.lists.update_collision_rect_is_colliding_list() + self.model.update_monster() + connection.Send({'action': 'move', + 'player_number': self.model.player_num, + 'rel_x_pos': self.model.players[self.model.player_num].rel_x_pos, + 'rel_y_pos': self.model.players[self.model.player_num].rel_y_pos}) + + if self.model.scroll_collision_index != -1: + connection.Send({'action': 'update_entities', + 'player_number': self.model.player_num, + 'scroll_collision_index': self.model.scroll_collision_index}) + + if not self.model.players[self.model.player_num].still_alive: #and not self.played2: + connection.Send({'action': 'update_alive', + 'player_number': self.model.player_num, + 'still_alive': False}) + self.played2 = True + if self.model.players[self.model.player_num].win and not self.played3: + connection.Send({'action': 'update_win', + 'player_number': self.model.player_num, + 'won': True}) + self.played3 = True + else: + connection.Send({'action': 'lobby', + 'player_number': self.model.player_num, + 'p_ready': self.model.player_ready, + 'is_players_ready': self.model.is_players_ready}) ##this part for server + + +class PyGameKeyboardController(object): + def __init__(self, model): + self.model = model + self.move_ticker = 0 + self.REFRESH_RATE = 0 #how many loops before it updates the velocity + self.DIAG_VEL = 6/1.4 + self.VEL = 6 + self.pressed = False + def handle_event(self, event): + if self.model.spectator: + self.DIAG_VEL = 9/1.4 + self.VEL = 9 + + y_vel = 0 + x_vel = 0 + keys = pygame.key.get_pressed() ##find what keys were pressed + #self.model.run_model() ## run the model so we can change its attributes + if self.move_ticker > self.REFRESH_RATE and not self.model.win_screen: + ## change diagonals first + if keys[pygame.K_a] and keys[pygame.K_s]: + x_vel = -self.DIAG_VEL + y_vel = self.DIAG_VEL + elif keys[pygame.K_a] and keys[pygame.K_w]: + x_vel = -self.DIAG_VEL + y_vel = -self.DIAG_VEL + elif keys[pygame.K_d] and keys[pygame.K_s]: + x_vel = self.DIAG_VEL + y_vel = self.DIAG_VEL + elif keys[pygame.K_d] and keys[pygame.K_w]: + x_vel = self.DIAG_VEL + y_vel = -self.DIAG_VEL + ##check horizontal/vertical after + elif keys[pygame.K_a] and (not self.model.collision.char_is_colliding or self.model.spectator): + x_vel = -self.VEL + elif keys[pygame.K_d] and (not self.model.collision.char_is_colliding or self.model.spectator): + x_vel = self.VEL + elif keys[pygame.K_w] and (not self.model.collision.char_is_colliding or self.model.spectator): + y_vel = -self.VEL + elif keys[pygame.K_s] and (not self.model.collision.char_is_colliding or self.model.spectator): + y_vel = self.VEL + ##if there is a collision, and the key is pressed, the velocity is zero + if (self.model.lists.collision_rect_is_colliding_list[0] and not self.model.spectator) and keys[pygame.K_a]: + x_vel = 0 + if (self.model.lists.collision_rect_is_colliding_list[1] and not self.model.spectator) and keys[pygame.K_d]: + x_vel = 0 + if (self.model.lists.collision_rect_is_colliding_list[2] and not self.model.spectator) and keys[pygame.K_w]: + y_vel = 0 + if (self.model.lists.collision_rect_is_colliding_list[3] and not self.model.spectator) and keys[pygame.K_s]: + y_vel = 0 + ##for the keys pressed, we can add the velocity to the position + if keys[pygame.K_a] or keys[pygame.K_d]: + self.model.players[self.model.player_num].rel_x_pos += x_vel + self.model.move_objects(-x_vel, 0) + #self.model.players[self.model.player_num].x_pos += x_vel + if keys[pygame.K_s] or keys[pygame.K_w]: + self.model.players[self.model.player_num].rel_y_pos += y_vel + self.model.move_objects(0, -y_vel) + #self.model.players[self.model.player_num].y_pos += y_vel + self.move_ticker = 0 + + ##if original collides, move outwards + if self.model.collision.char_is_colliding and not self.model.spectator: + if self.model.lists.collision_rect_is_colliding_list[0]: + self.model.move_objects(-1, 0) + self.model.players[self.model.player_num].rel_x_pos += 1 + #self.model.players[self.model.player_num].x_pos += 1 + if self.model.lists.collision_rect_is_colliding_list[1]: + self.model.move_objects(1, 0) + self.model.players[self.model.player_num].rel_x_pos -= 1 + #self.model.players[self.model.player_num].x_pos -= 1 + if self.model.lists.collision_rect_is_colliding_list[2]: + self.model.move_objects(0, -1) + self.model.players[self.model.player_num].rel_y_pos += 1 + #self.model.players[self.model.player_num].y_pos += 1 + if self.model.lists.collision_rect_is_colliding_list[3]: + self.model.move_objects(0, 1) + self.model.players[self.model.player_num].rel_y_pos -= 1 + #self.model.players[self.model.player_num].y_pos -= 1 + #self.model.players[self.model.player_num].update_relative_positions(rel_x_pos, rel_y_pos) + self.move_ticker += 1 + + def handle_lobby_event(self, event): + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_SPACE and not self.pressed: ##if space is pressed, + self.model.player_ready = not self.model.player_ready ##ready is swapped (false- true, true- false) + self.pressed = True + else: + self.pressed = False + +if __name__ == '__main__': + pygame.init() + + print 'Enter the server ip address' + print 'Empty for localhost' + #ask the server ip addresss + server = raw_input('server ip: ') + # control if server is empty + if server == '': + server = 'localhost' + #server = '10.7.64.193' + # init the listener + model = EscapeTheMazeClientModel() + size = (model.WINDOW_WIDTH, model.WINDOW_HEIGHT) + screen = pygame.display.set_mode(size) + controller = PyGameKeyboardController(model) + listener = Listener(model, server, 31500) + view = PygameEscapeTheMazeView(model, screen, listener) + running = True + while running: + listener.update_listener() + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + running = False + if event.type == QUIT: + running = False + if listener.start: + model.run_model() ## run the model + controller.handle_event(event) + view.draw() + elif listener.load_screen: + controller.handle_lobby_event(event) + view.draw_load_screen() + for boolean in model.is_players_ready: + if boolean == False: + view.played = False + view.ticker = 0 + listener.load_screen = False + elif listener.story: + view.draw_story() + else: + controller.handle_lobby_event(event) + view.draw_lobby() \ No newline at end of file diff --git a/Escape_The_Maze.pyc b/Escape_The_Maze.pyc new file mode 100644 index 0000000..3aea885 Binary files /dev/null and b/Escape_The_Maze.pyc differ diff --git a/Illuminati_Sound.ogg b/Illuminati_Sound.ogg new file mode 100644 index 0000000..c079cb2 Binary files /dev/null and b/Illuminati_Sound.ogg differ diff --git a/Maze.py b/Maze.py new file mode 100644 index 0000000..f0bc2eb --- /dev/null +++ b/Maze.py @@ -0,0 +1,150 @@ +import random +import sys +from os import path + +#X = 5 +#Y = 5 + +class Grouper(object): + def __init__(self, init=[]): + mapping = self._mapping = {} + for x in init: + mapping[x] = [x] + + def join(self, a, *args): + """Join given arguments into the same set. +Accepts one or more arguments.""" + mapping = self._mapping + set_a = mapping.setdefault(a, [a]) + + for arg in args: + set_b = mapping.get(arg) + if set_b is None: + set_a.append(arg) + mapping[arg] = set_a + elif set_b is not set_a: + if len(set_b) > len(set_a): + set_a, set_b = set_b, set_a + set_a.extend(set_b) + for elem in set_b: + mapping[elem] = set_a + + def joined(self, a, b): + """Returns True if a and b are members of the same set.""" + mapping = self._mapping + try: + return mapping[a] is mapping[b] + except KeyError: + return False + + def __iter__(self): + """Returns an iterator returning each of the disjoint sets as a list.""" + seen = set() + for elem, group in self._mapping.iteritems(): + if elem not in seen: + yield group + seen.update(group) + +class Cell(): + """Represents a cell in the maze, with an x and y coordinate and its + right hand wall and downwards wall. + + """ + def __init__(self, x, y): + self.x, self.y = x, y + self.right_wall = self.down_wall = None + +class Wall(): + """Represents a wall in the maze with its two neighbouring cells. + """ + def __init__(self): + self.neighbours = None + self.active = True + +def popchoice(seq): + """Takes an iterable and pops a random item from it. + """ + return seq.pop(random.randrange(len(seq))) + +def create_maze(X, Y): + # A mapping of coord tuple to Cell object + cells = {} + # A list of all the non-edge walls + walls = [] + + # Generate cells + for y in range(Y): + for x in range(X): + cells[(x, y)] = Cell(x, y) + + # Generate walls and add to the neighbouring cells + for y in range(Y): + for x in range(X): + current_cell = cells[(x,y)] + down_wall = Wall() + current_cell.down_wall = down_wall + right_wall = Wall() + current_cell.right_wall = right_wall + if y != Y-1: + down_wall.neighbours = (current_cell, cells[(x,y+1)]) + walls.append(down_wall) + + if x != X-1: + right_wall.neighbours = (current_cell, cells[(x+1,y)]) + walls.append(right_wall) + + grouper = Grouper() + # Get a list of all the cell objects to give to the Grouper + cell_list = [cells[key] for key in cells] + + maze = Grouper(cell_list) + + for _ in range(len(walls)): + # Pop a random wall from the list and get its neighbours + wall = popchoice(walls) + cell_1, cell_2 = wall.neighbours + # If the cells on either side of the wall aren't already connected, + # destroy the wall + if not maze.joined(cell_1, cell_2): + wall.active = False + maze.join(cell_1, cell_2) + + # Draw the maze + + maze_map = [] + + x_max = (X*2)+1 + y_max = (Y*2)+1 + + # Make an empty maze map with True for wall and False for space + # Make top wall + maze_map.append([True for _ in range(x_max)]) + for y in range(1, y_max): + # Make rows with left side wall + maze_map.append([True]+[False for _ in range(1, x_max)]) + + # Add the down and right walls from each cell to the map + for coords, cell in cells.items(): + x, y = coords + # Add the intersection wall for each cell (down 1 right 1) + maze_map[(y*2)+2][(x*2)+2] = True + if cell.right_wall.active: + maze_map[(y*2)+1][(x*2)+2] = True + if cell.down_wall.active: + maze_map[(y*2)+2][(x*2)+1] = True + + # Print the map + maze = [] + for i in range(len(maze_map)): + n = random.randint(0, X) + p = random.randint(0, X) + maze_row = [] + for j in range(len(maze_map[0])): + #if(n >= rand_number or j == X or j == 0 or i == 0 or i == Y): + if maze_map[i][j] and ((j != n and j!= p) or j == 2*X or j == 0 or i == 0 or i == 2*Y): + maze_row.append(1) + else: + maze_row.append(0) + maze.append(maze_row) + print maze_row + return maze \ No newline at end of file diff --git a/PodSixNet/COPYING b/PodSixNet/COPYING new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/PodSixNet/COPYING @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/PodSixNet/Channel.py b/PodSixNet/Channel.py new file mode 100644 index 0000000..fddc219 --- /dev/null +++ b/PodSixNet/Channel.py @@ -0,0 +1,58 @@ +import asynchat +import sys, traceback + +from rencode import loads, dumps + +class Channel(asynchat.async_chat): + endchars = '\0---\0' + def __init__(self, conn=None, addr=(), server=None): + asynchat.async_chat.__init__(self, conn) + self.addr = addr + self._server = server + self._ibuffer = "" + self.set_terminator(self.endchars) + self.sendqueue = [] + + def collect_incoming_data(self, data): + self._ibuffer += data + + def found_terminator(self): + data = loads(self._ibuffer) + self._ibuffer = "" + + if type(dict()) == type(data) and data.has_key('action'): + [getattr(self, n)(data) for n in ('Network_' + data['action'], 'Network') if hasattr(self, n)] + else: + print "OOB data:", data + + def Pump(self): + [asynchat.async_chat.push(self, d) for d in self.sendqueue] + self.sendqueue = [] + + def Send(self, data): + self.sendqueue.append(dumps(data) + self.endchars) + + def handle_connect(self): + if hasattr(self, "Connected"): + self.Connected() + else: + print "Unhandled Connected()" + + def handle_error(self): + try: + self.close() + except: + pass + if hasattr(self, "Error"): + self.Error(sys.exc_info()[1]) + else: + asynchat.async_chat.handle_error(self) + + def handle_expt(self): + pass + + def handle_close(self): + if hasattr(self, "Close"): + self.Close() + asynchat.async_chat.handle_close(self) + diff --git a/PodSixNet/Channel.pyc b/PodSixNet/Channel.pyc new file mode 100644 index 0000000..8f18a40 Binary files /dev/null and b/PodSixNet/Channel.pyc differ diff --git a/PodSixNet/Connection.py b/PodSixNet/Connection.py new file mode 100644 index 0000000..ac9df94 --- /dev/null +++ b/PodSixNet/Connection.py @@ -0,0 +1,53 @@ +""" +A client's connection to the server. + +This module contains two components: a singleton called 'connection' and a class called 'ConnectionListener'. + +'connection' is a singleton instantiation of an EndPoint which will be connected to the server at the other end. It's a singleton because each client should only need one of these in most multiplayer scenarios. (If a client needs more than one connection to the server, a more complex architecture can be built out of instantiated EndPoint()s.) The connection is based on Python's asyncore and so it should have it's polling loop run periodically, probably once per gameloop. This just means putting "from Connection import connection; connection.Pump()" somewhere in your top level gameloop. + +Subclass ConnectionListener in order to have an object that will receive network events. For example, you might have a GUI element which is a label saying how many players there are online. You would declare it like 'class NumPlayersLabel(ConnectionListener, ...):' Later you'd instantitate it 'n = NumPlayersLabel()' and then somewhere in your loop you'd have 'n.Pump()' which asks the connection singleton if there are any new messages from the network, and calls the 'Network_' callbacks for each bit of new data from the server. So you'd implement a method like "def Network_players(self, data):" which would be called whenever a message from the server arrived which looked like {"action": "players", "number": 5}. +""" + +from EndPoint import EndPoint + +connection = EndPoint() + +class ConnectionListener: + """ + Looks at incoming data and calls "Network_" methods in self, based on what messages come in. + Subclass this to have your own classes monitor incoming network messages. + For example, a method called "Network_players(self, data)" will be called when a message arrives like: + {"action": "players", "number": 5, ....} + """ + def Connect(self, *args, **kwargs): + connection.DoConnect(*args, **kwargs) + # check for connection errors: + self.Pump() + + def Pump(self): + for data in connection.GetQueue(): + [getattr(self, n)(data) for n in ("Network_" + data['action'], "Network") if hasattr(self, n)] + +if __name__ == "__main__": + from time import sleep + from sys import exit + class ConnectionTest(ConnectionListener): + def Network(self, data): + print "Network:", data + + def Network_error(self, error): + print "error:", error['error'] + print "Did you start a server?" + exit(-1) + + def Network_connected(self, data): + print "connection test Connected" + + c = ConnectionTest() + + c.Connect() + while 1: + connection.Pump() + c.Pump() + sleep(0.001) + diff --git a/PodSixNet/Connection.pyc b/PodSixNet/Connection.pyc new file mode 100644 index 0000000..a179c9d Binary files /dev/null and b/PodSixNet/Connection.pyc differ diff --git a/PodSixNet/EndPoint.py b/PodSixNet/EndPoint.py new file mode 100644 index 0000000..f4e7899 --- /dev/null +++ b/PodSixNet/EndPoint.py @@ -0,0 +1,103 @@ +# coding=utf-8 +import socket +import sys + +if float(sys.version[:3]) < 2.5: + from asyncore import poll2 as poll +else: + from asyncore import poll + +from Channel import Channel + +class EndPoint(Channel): + """ + The endpoint queues up all network events for other classes to read. + """ + def __init__(self, address=("127.0.0.1", 31425)): + self.address = address + self.isConnected = False + self.queue = [] + + def DoConnect(self, address=None): + if address: + self.address = address + try: + Channel.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.connect(self.address) + except socket.gaierror, e: + self.queue.append({"action": "error", "error": e.args}) + + def GetQueue(self): + return self.queue + + def Pump(self): + Channel.Pump(self) + self.queue = [] + poll() + + # methods to add network data to the queue depending on network events + + def Close(self): + self.isConnected = False + self.close() + self.queue.append({"action": "disconnected"}) + + def Connected(self): + self.queue.append({"action": "socketConnect"}) + + def Network_connected(self, data): + self.isConnected = True + + def Network(self, data): + self.queue.append(data) + + def Error(self, error): + self.queue.append({"action": "error", "error": error}) + + def ConnectionError(self): + self.isConnected = False + self.queue.append({"action": "error", "error": (-1, "Connection error")}) + +if __name__ == "__main__": + from time import sleep + class ServerChannel(Channel): + def Network_hello(self, data): + print "*Server* ran test method for 'hello' action" + print "*Server* received:", data + self.Send({"action": "gotit", "data": "Yeah, we got it: " + str(len(data['data'])) + " elements"}) + + print "Trying failing endpoint" + print "-----------------------" + endpoint_bad = EndPoint(("mccormick.cx", 23342)) + endpoint_bad.DoConnect() + for i in range(50): + endpoint_bad.Pump() + if endpoint_bad.GetQueue(): + print endpoint_bad.GetQueue() + sleep(0.001) + + from Server import Server + server = Server(channelClass=ServerChannel) + + print + print "Trying successful endpoint" + print "--------------------------" + + endpoint = EndPoint(("localhost", 31425)) + endpoint.DoConnect() + endpoint.Send({"action": "hello", "data": {"a": 321, "b": [2, 3, 4], "c": ["afw", "wafF", "aa", "weEEW", "w234r"], "d": ["x"] * 256}}) + endpoint.Send({"action": "hello", "data": [454, 35, 43, 543, "aabv"]}) + endpoint.Send({"action": "hello", "data": [10] * 512}) + endpoint.Send({"action": "hello", "data": [10] * 512, "otherstuff": "hello\0---\0goodbye", "x": [0, "---", 0], "y": "zäö"}) + + print "polling for half a second" + for x in range(50): + server.Pump() + endpoint.Pump() + if endpoint.GetQueue(): + print "*Endpoint*:", endpoint.GetQueue() + sleep(0.001) + + endpoint.Close() + print endpoint.GetQueue() diff --git a/PodSixNet/EndPoint.pyc b/PodSixNet/EndPoint.pyc new file mode 100644 index 0000000..f6c6fa3 Binary files /dev/null and b/PodSixNet/EndPoint.pyc differ diff --git a/PodSixNet/README b/PodSixNet/README new file mode 100644 index 0000000..be80a61 --- /dev/null +++ b/PodSixNet/README @@ -0,0 +1,173 @@ +PodSixNet - Lightweight Multiplayer Game Library in Python +---------------------------------------------------------- + +Copyright Chris McCormick, 2009. ([Google code page](http://code.google.com/p/podsixnet/)) ([Launchpad project page](https://launchpad.net/podsixnet)) + +[http://mccormick.cx/](http://mccormick.cx/) + +[http://podsix.com.au/](http://podsix.com.au/) + +[Join the PodSixNet mailing list](http://groups.google.com/group/podsixnet) + +PodSixNet is a lightweight network layer designed to make it easy to write multiplayer games in Python. It uses Python's built in asyncore library and rencode.py (included) to asynchronously serialise network events and arbitrary data structures, and deliver them to your high level classes through simple callback methods. + +Each class within your game client which wants to receive network events, subclasses the ConnectionListener class and then implements `Network_*` methods to catch specific user-defined events from the server. You don't have to wait for buffers to fill, or check sockets for waiting data or anything like that, just do `connection.Pump()` once per game loop and the library will handle everything else for you, passing off events to all classes that are listening. Sending data back to the server is just as easy, using `connection.Send(mydata)`. Likewise on the server side, events are propagated to `Network_*` method callbacks and data is sent back to clients with the `client.Send(mydata)` method. + +If you find a bug, please report it on the mailing list or the [Google code issues page](http://code.google.com/p/podsixnet/issues/list). + +Install +------- + +First make sure you have [Python](http://python.org/) 2.4 or greater installed. + +Next you'll want to get the PodSixNet source. + +You can either check the latest cutting-edge code out of the bzr repository: + + * bzr co http://mccormick.cx/dev/PodSixNet/ + +Of if you prefer SVN check it out of the Google code project: + + * svn co http://podsixnet.googlecode.com/svn/podsixnet/ PodSixNet + +Or you can [download a tarball of the latest release (version ### version ###)](PodSixNet-### version ###.tar.gz). + +There's an `__init__.py` at the top level, so you can just copy or symlink the PodSixNet directory into your own project and then do `import PodSixNet`. + +By default PodSixNet uses a binary encoder to transfer data over the network, but it can optionally use the [JSON](http://json.org/) format or other formats supported by a serialiser which has 'dumps' and 'loads' methods. If you want to serialise your data using JSON you can change the first line of Channel.py to 'from simplejson import dumps, loads' or use the built-in json library in Python 2.6 or higher. This will allow you to write game clients in languages that can't read the 'rencode' binary format, such as Javascript. + +Examples +-------- + +Chat example: + + * examples/ChatServer.py + * and a couple of instances of examples/ChatClient.py + +Whiteboard example: + + * examples/WhiteboardServer.py + * and a couple of instances of examples/WhiteboardServer.py + +Quick start - Server +-------------------- + +You will need to subclass two classes in order to make your own server. Each time a client connects, a new Channel based class will be created, so you should subclass Channel to make your own server-representation-of-a-client class like this: + + from PodSixNet.Channel import Channel + + class ClientChannel(Channel): + + def Network(data): + print data + + def Network_myaction(data): + print "myaction:", data + +Whenever the client does `connection.Send(mydata)`, the `Network()` method will be called. The method `Network_myaction()` will only be called if your data has a key called 'action' with a value of "myaction". In other words if it looks something like this: + + data = {"action": "myaction", "blah": 123, ... } + +Next you need to subclass the Server class like this: + + from PodSixNet.Server import Server + + class MyServer(Server): + + channelClass = ClientChannel + + def Connected(self, channel, addr): + print 'new connection:', channel + +Set `channelClass` to the channel class that you created above. The method `Connected()` will be called whenever a new client connects to your server. See the example servers for an idea of what you might do each time a client connects. You need to call `Server.Pump()` every now and then, probably once per game loop. For example: + + myserver = MyServer() + while True: + myserver.Pump() + sleep(0.0001) + +When you want to send data to a specific client/channel, use the Send method of the Channel class: + + channel.Send({"action": "hello", "message": "hello client!"}) + +Quick start - Client +-------------------- + +To have a client connect to your new server, you should use the Connection module. See `pydoc Connection` for more details, but here's a summary: + +`Connection.connection` is a singleton Channel which connects to the server. You'll only have one of these in your game code, and you'll use it to connect to the server and send messages to the server. + + from Connection import connection + + # connect to the server - optionally pass hostname and port like: ("mccormick.cx", 31425) + connection.Connect() + + connection.Send({"action": "myaction", "blah": 123, "things": [3, 4, 3, 4, 7]}) + +You'll also need to put the following code once somewhere in your game loop: + + connection.Pump() + +Any time you have an object in your game which you want to receive messages from the server, subclass `ConnectionListener`. For example: + + from Connection import ConnectionListener + + class MyNetworkListener(ConnectionListener): + + def Network(self, data): + print 'network data:', data + + def Network_connected(self, data): + print "connected to the server" + + def Network_error(self, data): + print "error:", data['error'][1] + + def Network_disconnected(self, data): + print "disconnected from the server" + + def Network_myaction(data): + print "myaction:", data + +Just like in the server case, the network events are received by `Network_*` callback methods. You can implement as many or as few of the above as you like. For example, NetworkGUI would probably only want to listen for the `_connected`, `_disconnected`, and `_error` network events. The data for `_error` always comes in the form of network exceptions, like (111, 'Connection refused') - these are passed straight from the socket layer and are standard socket errors. + +Another class might implement custom methods like `Network_myaction()`, which will receive any data that gets sent from the server with an 'action' key that has the name 'myaction'. For example, the server might send a message with the number of players currently connected like so: + + channel.Send({"action": "numplayers", "players": 10}) + +And the listener would look like this: + + from Connection import ConnectionListener + + class MyPlayerListener(ConnectionListener): + + def Network_numplayers(data): + # update gui element displaying the number of currently connected players + print data['players'] + +You can subclass `ConnectionListener` as many times as you like in your application, and every class you make which subclasses it will receive the network events via named Network callbacks. You should call the `Pump()` method on each object you instantiate once per game loop: + + gui = MyPlayerListener() + while 1: + connection.Pump() + gui.Pump() + +License +------- + +PodSixNet is licensed under the terms of the LGPL v3.0 or higher. See the file called [COPYING](COPYING) for details. + +This basically means that you can use it in most types of projects (commercial or otherwise), but if you make changes to the PodSixNet code you must make the modified code available with the distribution of your software. Hopefully you'll tell us about it so we can incorporate your changes. I am not a lawyer, so please read the license carefully to understand your rights with respect to this code. + +Why not use Twisted instead? +--------------------------- + +Twisted is a fantastic library for writing robust network code. I have used it in several projects in the past, and it was quite nice to work with. That said, Twisted: + +* wants to steal the mainloop +* is bloated not KISS (it implements many many different protocols) +* has a weird template launching language when Python should do just fine +* is not written 100% for the specfic use-case of multiplayer games + +These are some of the reasons why I decided to write a library that is lightweight, has no dependencies except Python, and is dedicated 100% to the task of multiplayer game networking. + diff --git a/PodSixNet/Server.py b/PodSixNet/Server.py new file mode 100644 index 0000000..c9ff1bc --- /dev/null +++ b/PodSixNet/Server.py @@ -0,0 +1,81 @@ +import socket +import asyncore +import sys + +if float(sys.version[:3]) < 2.5: + from asyncore import poll2 as poll +else: + from asyncore import poll + +from Channel import Channel + +class Server(asyncore.dispatcher): + channelClass = Channel + + def __init__(self, channelClass=None, localaddr=("127.0.0.1", 31425), listeners=5): + if channelClass: + self.channelClass = channelClass + self.channels = [] + asyncore.dispatcher.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind(localaddr) + self.listen(listeners) + + def handle_accept(self): + try: + conn, addr = self.accept() + except socket.error: + print 'warning: server accept() threw an exception' + return + except TypeError: + print 'warning: server accept() threw EWOULDBLOCK' + return + + self.channels.append(self.channelClass(conn, addr, self)) + self.channels[-1].Send({"action": "connected"}) + if hasattr(self, "Connected"): + self.Connected(self.channels[-1], addr) + + def Pump(self): + [c.Pump() for c in self.channels] + poll() + +######################### +# Test stub # +######################### + +if __name__ == "__main__": + class ServerChannel(Channel): + def Network_hello(self, data): + print "*Server* ran test method for 'hello' action" + print "*Server* received:", data + + class EndPointChannel(Channel): + def Connected(self): + print "*EndPoint* Connected()" + + def Network_connected(self, data): + print "*EndPoint* Network_connected(", data, ")" + print "*EndPoint* initiating send" + outgoing.Send({"action": "hello", "data": {"a": 321, "b": [2, 3, 4], "c": ["afw", "wafF", "aa", "weEEW", "w234r"], "d": ["x"] * 256}}) + + def Connected(channel, addr): + print "*Server* Connected() ", channel, "connected on", addr + + server = Server(channelClass=ServerChannel) + server.Connected = Connected + + sender = asyncore.dispatcher() + sender.create_socket(socket.AF_INET, socket.SOCK_STREAM) + sender.connect(("localhost", 31425)) + outgoing = EndPointChannel(sender) + + from time import sleep + + print "*** polling for half a second" + for x in range(50): + server.Pump() + outgoing.Pump() + sleep(0.001) + diff --git a/PodSixNet/Server.pyc b/PodSixNet/Server.pyc new file mode 100644 index 0000000..ccd898f Binary files /dev/null and b/PodSixNet/Server.pyc differ diff --git a/PodSixNet/TODO b/PodSixNet/TODO new file mode 100644 index 0000000..5f18d33 --- /dev/null +++ b/PodSixNet/TODO @@ -0,0 +1,28 @@ +* automatic load testing script + +IMHO, the example code would be clearer and/or easier to test if: + +- it never used "import *" (this makes it hard to figure out what is +actually being used, from where); + +- there were another couple of examples (as it is, I can only try the chat +example since I don't have pygame installed); + +- the examples would grab IP address and port from sys.argv (using default +values for missing arguments) rather than relying on default values buried +inside the library functions. + +The docs would be clearer if the customizable part of the method names was +made more explicit in the docs. (I forget the exact string used for that +part since I don't have the docs in front of me right now. I think it +started with "my".) + +The only big missing feature I've noticed is letting a client connect to +more than one server. (I have use cases for this, though they are not +"multiplayer games".) + +The chat example uses about 3.5% CPU when doing nothing (I think that was +true for both server and client; on Mac Leopard), not affected by changing +the sleep times to 0.01. This is ok, but mysterious, and might not always be +ok. Probably it should be documented (and explained if possible). + diff --git a/PodSixNet/__init__.py b/PodSixNet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/PodSixNet/__init__.pyc b/PodSixNet/__init__.pyc new file mode 100644 index 0000000..50a3306 Binary files /dev/null and b/PodSixNet/__init__.pyc differ diff --git a/PodSixNet/publish.sh b/PodSixNet/publish.sh new file mode 100755 index 0000000..0391189 --- /dev/null +++ b/PodSixNet/publish.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +BZRREV=`bzr log -r -1.. --line | cut -d":" -f 1` +SRV=mccormick.cx +DIR='~/mccormick.cx/projects/PodSixNet/' +REMOTE=$SRV:$DIR +TARFILE=PodSixNet-$BZRREV.tar.gz + +echo "Generating and uploading HTML to mccormick.cx:" +cp COPYING web/ +html=`markdown README` +infile=`cat web/base.html` +echo "${infile//\#\#\# content \#\#\#/$html}" > web/index.html +infile=`cat web/index.html` +echo "${infile//\#\#\# version \#\#\#/$BZRREV}" > web/index.html +scp web/* $REMOTE + +echo "Exporting tarball and uploading to mccormick.cx" +bzr export PodSixNet-$BZRREV.tar.gz . +scp PodSixNet-$BZRREV.tar.gz $REMOTE +ssh $SRV "cd $DIR && rm PodSixNet.tar.gz && ln -s PodSixNet-$BZRREV.tar.gz PodSixNet.tar.gz" + +echo "Pushing changes to google code" +bzr svn-push https://mccormix@podsixnet.googlecode.com/svn/podsixnet/ diff --git a/PodSixNet/rencode.py b/PodSixNet/rencode.py new file mode 100644 index 0000000..91043ff --- /dev/null +++ b/PodSixNet/rencode.py @@ -0,0 +1,559 @@ + +""" +rencode -- Web safe object pickling/unpickling. + +The rencode module is a modified version of bencode from the +BitTorrent project. For complex, heterogeneous data structures with +many small elements, r-encodings take up significantly less space than +b-encodings: + + >>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99})) + 13 + >>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99})) + 26 + +The rencode format is not standardized, and may change with different +rencode module versions, so you should check that you are using the +same rencode version throughout your project. +""" + +__version__ = '1.0.0-ntk' +__all__ = ['dumps', 'loads', 'serializable'] + +from base64 import b64encode, b64decode + +##TODO +# - Why the hell it encodes both tuples and lists to tuple? +# Try loads(dumps([1,(2,3)])) +# Grr +# +# - extend it! Support other basic types, f.e. Set() +# +## + +# Original bencode module by Petru Paler, et al. +# +# Modifications by Daniele Tricoli: +# +# - Added support for instances +# Only registered instances can be serialized. An instance to be serialized +# must provide a '_pack` method. +# E.g. +# +# class X(object): +# +# def __init__(self, x): +# self.x = x +# +# def _pack(self) +# return (self.x,) # a tuple +# +# - Lists are decoded again as list +# +# Modifications by Connelly Barnes: +# +# - Added support for floats (sent as 32-bit or 64-bit in network +# order), bools, None. +# - Allowed dict keys to be of any serializable type. +# - Lists/tuples are always decoded as tuples (thus, tuples can be +# used as dict keys). +# - Embedded extra information in the 'typecodes' to save some space. +# - Added a restriction on integer length, so that malicious hosts +# cannot pass us large integers which take a long time to decode. +# +# Licensed by Bram Cohen under the "MIT license": +# +# "Copyright (C) 2001-2002 Bram Cohen +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# The Software is provided "AS IS", without warranty of any kind, +# express or implied, including but not limited to the warranties of +# merchantability, fitness for a particular purpose and +# noninfringement. In no event shall the authors or copyright holders +# be liable for any claim, damages or other liability, whether in an +# action of contract, tort or otherwise, arising from, out of or in +# connection with the Software or the use or other dealings in the +# Software." +# +# (The rencode module is licensed under the above license as well). +# + +import inspect +import struct + +from threading import Lock + +from types import (StringType, + IntType, + LongType, + DictType, + ListType, + TupleType, + FloatType, + NoneType) + +class AlreadyRegistered(Exception): pass + +class NotRegistered(Exception): + + def __init__(self, class_): + self.class_ = class_ + + def __str__(self): + return 'Class %s is not registered' % self.class_ + +class NotSerializable(Exception): pass + +def add_class_name(func): + + if inspect.ismethod(func): + def decorate(*args, **kargs): + result = func(*args, **kargs) + result = (str(func.im_class.__name__),) + result + return result + + return decorate + +class _SerializableRegistry(object): + + def __init__(self): + self._registry = {} + + def __contains__(self, item): + return item in self._registry + + def __getitem__(self, key): + return self._registry[key] + + def register(self, cls): + ''' ''' + if inspect.isclass(cls): + + if cls.__name__ in self._registry: + msg = 'Class %s is already registered' % cls.__name__ + raise AlreadyRegistered(msg) + + try: + if inspect.ismethod(cls._pack): + cls._pack = add_class_name(cls._pack) + self._registry[cls.__name__] = cls + except AttributeError, err: + raise NotSerializable(err) + + def unregister(self, cls): + ''' ''' + if inspect.isclass(cls): + + if cls in self._registry: + del self._registry[cls.__name__] + else: + raise NotRegistered(cls.__name__) + +serializable = _SerializableRegistry() + +# Number of bits for serialized floats, either 32 or 64. +FLOAT_BITS = 32 + +# Maximum length of integer when written as base 10 string. +MAX_INT_LENGTH = 64 + +# The bencode 'typecodes' such as i, d, etc have been extended and +# relocated on the base-256 character set. +# Can't be used chr(48) to chr(57) because they are manually set +CHR_INSTANCE = chr(47) +CHR_TUPLE = chr(58) +CHR_LIST = chr(59) +CHR_DICT = chr(60) +CHR_INT = chr(61) +CHR_INT1 = chr(62) +CHR_INT2 = chr(63) +CHR_INT4 = chr(64) +CHR_INT8 = chr(65) +CHR_FLOAT = chr(66) +CHR_TRUE = chr(67) +CHR_FALSE = chr(68) +CHR_NONE = chr(69) +CHR_TERM = chr(127) + +# Positive integers with value embedded in typecode. +INT_POS_FIXED_START = 0 +INT_POS_FIXED_COUNT = 32 + +# Dictionaries with length embedded in typecode. +DICT_FIXED_START = 102 +DICT_FIXED_COUNT = 25 + +# Negative integers with value embedded in typecode. +INT_NEG_FIXED_START = 70 +INT_NEG_FIXED_COUNT = 32 + +# Strings with length embedded in typecode. +STR_FIXED_START = 128 +STR_FIXED_COUNT = 64 + +# Lists with length embedded in typecode. +LIST_FIXED_START = STR_FIXED_START + STR_FIXED_COUNT +LIST_FIXED_COUNT = 32 + +# Tuples with length embedded in typecode. +TUPLE_FIXED_START = LIST_FIXED_START + LIST_FIXED_COUNT +TUPLE_FIXED_COUNT = 32 + +def decode_int(x, f): + f += 1 + newf = x.index(CHR_TERM, f) + if newf - f >= MAX_INT_LENGTH: + raise ValueError('overflow') + try: + n = int(x[f:newf]) + except (OverflowError, ValueError): + n = long(x[f:newf]) + if x[f] == '-': + if x[f + 1] == '0': + raise ValueError + elif x[f] == '0' and newf != f+1: + raise ValueError + return (n, newf+1) + +def decode_intb(x, f): + f += 1 + return (struct.unpack('!b', x[f:f+1])[0], f+1) + +def decode_inth(x, f): + f += 1 + return (struct.unpack('!h', x[f:f+2])[0], f+2) + +def decode_intl(x, f): + f += 1 + return (struct.unpack('!l', x[f:f+4])[0], f+4) + +def decode_intq(x, f): + f += 1 + return (struct.unpack('!q', x[f:f+8])[0], f+8) + +def decode_float(x, f): + f += 1 + if FLOAT_BITS == 32: + n = struct.unpack('!f', x[f:f+4])[0] + return (n, f+4) + elif FLOAT_BITS == 64: + n = struct.unpack('!d', x[f:f+8])[0] + return (n, f+8) + else: + raise ValueError + +def decode_string(x, f): + colon = x.index(':', f) + try: + n = int(x[f:colon]) + except (OverflowError, ValueError): + n = long(x[f:colon]) + if x[f] == '0' and colon != f+1: + raise ValueError + colon += 1 + return (b64decode(x[colon:colon+n]), colon+n) + +def decode_list(x, f): + r, f = [], f+1 + while x[f] != CHR_TERM: + v, f = decode_func[x[f]](x, f) + r.append(v) + return (r, f + 1) + +def decode_tuple(x, f): + r, f = [], f+1 + while x[f] != CHR_TERM: + v, f = decode_func[x[f]](x, f) + r.append(v) + return (tuple(r), f + 1) + +def decode_dict(x, f): + r, f = {}, f+1 + while x[f] != CHR_TERM: + k, f = decode_func[x[f]](x, f) + r[k], f = decode_func[x[f]](x, f) + return (r, f + 1) + +def decode_true(x, f): + return (True, f+1) + +def decode_false(x, f): + return (False, f+1) + +def decode_none(x, f): + return (None, f+1) + +def decode_instance(x, f): + f += 1 + while x[f] != CHR_TERM: + v, f = decode_func[x[f]](x, f) + if v[0] in serializable: + r = serializable[v[0]](*v[1:]) + else: + raise NotRegistered(v[0]) + return (r, f+1) + +decode_func = {} +decode_func['0'] = decode_string +decode_func['1'] = decode_string +decode_func['2'] = decode_string +decode_func['3'] = decode_string +decode_func['4'] = decode_string +decode_func['5'] = decode_string +decode_func['6'] = decode_string +decode_func['7'] = decode_string +decode_func['8'] = decode_string +decode_func['9'] = decode_string +decode_func[CHR_LIST ] = decode_list +decode_func[CHR_TUPLE] = decode_tuple +decode_func[CHR_DICT ] = decode_dict +decode_func[CHR_INT ] = decode_int +decode_func[CHR_INT1 ] = decode_intb +decode_func[CHR_INT2 ] = decode_inth +decode_func[CHR_INT4 ] = decode_intl +decode_func[CHR_INT8 ] = decode_intq +decode_func[CHR_FLOAT] = decode_float +decode_func[CHR_TRUE ] = decode_true +decode_func[CHR_FALSE] = decode_false +decode_func[CHR_NONE ] = decode_none +decode_func[CHR_INSTANCE] = decode_instance + +def make_fixed_length_string_decoders(): + def make_decoder(slen): + def f_fixed_string(x, f): + return (b64decode(x[f+1:f+1+slen]), f+1+slen) + return f_fixed_string + for i in range(STR_FIXED_COUNT): + decode_func[chr(STR_FIXED_START+i)] = make_decoder(i) + +make_fixed_length_string_decoders() + +def make_fixed_length_list_decoders(): + def make_decoder(slen): + def f_fixed_list(x, f): + r, f = [], f+1 + for i in range(slen): + v, f = decode_func[x[f]](x, f) + r.append(v) + return (r, f) + return f_fixed_list + for i in range(LIST_FIXED_COUNT): + decode_func[chr(LIST_FIXED_START+i)] = make_decoder(i) + +make_fixed_length_list_decoders() + +def make_fixed_length_tuple_decoders(): + def make_decoder(slen): + def f_fixed_tuple(x, f): + r, f = [], f+1 + for i in range(slen): + v, f = decode_func[x[f]](x, f) + r.append(v) + return (tuple(r), f) + return f_fixed_tuple + for i in range(TUPLE_FIXED_COUNT): + decode_func[chr(TUPLE_FIXED_START+i)] = make_decoder(i) + +make_fixed_length_tuple_decoders() + +def make_fixed_length_int_decoders(): + def make_decoder(j): + def f(x, f): + return (j, f+1) + return f + for i in range(INT_POS_FIXED_COUNT): + decode_func[chr(INT_POS_FIXED_START+i)] = make_decoder(i) + for i in range(INT_NEG_FIXED_COUNT): + decode_func[chr(INT_NEG_FIXED_START+i)] = make_decoder(-1-i) + +make_fixed_length_int_decoders() + +def make_fixed_length_dict_decoders(): + def make_decoder(slen): + def f(x, f): + r, f = {}, f+1 + for j in range(slen): + k, f = decode_func[x[f]](x, f) + r[k], f = decode_func[x[f]](x, f) + return (r, f) + return f + for i in range(DICT_FIXED_COUNT): + decode_func[chr(DICT_FIXED_START+i)] = make_decoder(i) + +make_fixed_length_dict_decoders() + +def loads(x): + try: + r, l = decode_func[x[0]](x, 0) + except (IndexError, KeyError): + raise + if l != len(x): + raise ValueError + return r + +def encode_int(x, r): + if 0 <= x < INT_POS_FIXED_COUNT: + r.append(chr(INT_POS_FIXED_START+x)) + elif -INT_NEG_FIXED_COUNT <= x < 0: + r.append(chr(INT_NEG_FIXED_START-1-x)) + elif -128 <= x < 128: + r.extend((CHR_INT1, struct.pack('!b', x))) + elif -32768 <= x < 32768: + r.extend((CHR_INT2, struct.pack('!h', x))) + elif -2147483648 <= x < 2147483648: + r.extend((CHR_INT4, struct.pack('!l', x))) + elif -9223372036854775808 <= x < 9223372036854775808: + r.extend((CHR_INT8, struct.pack('!q', x))) + else: + s = str(x) + if len(s) >= MAX_INT_LENGTH: + raise ValueError('overflow') + r.extend((CHR_INT, s, CHR_TERM)) + +def encode_float(x, r): + if FLOAT_BITS == 32: + r.extend((CHR_FLOAT, struct.pack('!f', x))) + elif FLOAT_BITS == 64: + r.extend((CHR_FLOAT, struct.pack('!d', x))) + else: + raise ValueError + +def encode_bool(x, r): + r.extend({False: CHR_FALSE, True: CHR_TRUE}[bool(x)]) + +def encode_none(x, r): + r.extend(CHR_NONE) + +def encode_string(x, r): + x = b64encode(x) + if len(x) < STR_FIXED_COUNT: + r.extend((chr(STR_FIXED_START + len(x)), x)) + else: + r.extend((str(len(x)), ':', x)) + +def encode_list(x, r): + if len(x) < LIST_FIXED_COUNT: + r.append(chr(LIST_FIXED_START + len(x))) + for i in x: + encode_func.get(type(i), encode_instance)(i, r) + else: + r.append(CHR_LIST) + for i in x: + encode_func.get(type(i), encode_instance)(i, r) + r.append(CHR_TERM) + + +def encode_tuple(x, r): + if len(x) < TUPLE_FIXED_COUNT: + r.append(chr(TUPLE_FIXED_START + len(x))) + for i in x: + encode_func.get(type(i), encode_instance)(i, r) + else: + r.append(CHR_TUPLE) + for i in x: + encode_func.get(type(i), encode_instance)(i, r) + r.append(CHR_TERM) + +def encode_dict(x,r): + if len(x) < DICT_FIXED_COUNT: + r.append(chr(DICT_FIXED_START + len(x))) + for k, v in x.items(): + encode_func[type(k)](k, r) + encode_func[type(v)](v, r) + else: + r.append(CHR_DICT) + for k, v in x.items(): + encode_func[type(k)](k, r) + encode_func[type(v)](v, r) + r.append(CHR_TERM) + +encode_func = {} +encode_func[IntType] = encode_int +encode_func[LongType] = encode_int +encode_func[FloatType] = encode_float +encode_func[StringType] = encode_string +encode_func[ListType] = encode_list +encode_func[TupleType] = encode_tuple +encode_func[DictType] = encode_dict +encode_func[NoneType] = encode_none + +try: + from types import BooleanType + encode_func[BooleanType] = encode_bool +except ImportError: + pass + +def encode_instance(x, r): + if hasattr(x, '_pack'): + if x.__class__.__name__ in serializable: + # Calling the class of instance `x' passing it to the + # unbound method + result = serializable[x.__class__.__name__]._pack(x) + r.append(CHR_INSTANCE) + encode_func[type(result)](result, r) + r.append(CHR_TERM) + else: + raise NotRegistered(x.__class__.__name__) + +lock = Lock() + +def dumps(x): + lock.acquire() + r = [] + encode_func.get(type(x), encode_instance)(x, r) + lock.release() + return ''.join(r) + +def test(): + f1 = struct.unpack('!f', struct.pack('!f', 25.5))[0] + f2 = struct.unpack('!f', struct.pack('!f', 29.3))[0] + f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0] + L = (({'a':15, 'bb':f1, 'ccc':f2, '':(f3,(),False,True,'')},('a',10**20),tuple(range(-100000,100000)),'b'*31,'b'*62,'b'*64,2**30,2**33,2**62,2**64,2**30,2**33,2**62,2**64,False,False, True, -1, 2, 0),) + assert loads(dumps(L)) == L + d = dict(zip(range(-100000,100000),range(-100000,100000))) + d.update({'a':20, 20:40, 40:41, f1:f2, f2:f3, f3:False, False:True, True:False}) + L = (d, {}, {5:6}, {7:7,True:8}, {9:10, 22:39, 49:50, 44: ''}) + assert loads(dumps(L)) == L + L = ('', 'a'*10, 'a'*100, 'a'*1000, 'a'*10000, 'a'*100000, 'a'*1000000, 'a'*10000000) + assert loads(dumps(L)) == L + L = tuple([dict(zip(range(n),range(n))) for n in range(100)]) + ('b',) + assert loads(dumps(L)) == L + L = tuple([dict(zip(range(n),range(-n,0))) for n in range(100)]) + ('b',) + assert loads(dumps(L)) == L + L = tuple([tuple(range(n)) for n in range(100)]) + ('b',) + assert loads(dumps(L)) == L + L = tuple(['a'*n for n in range(100)]) + ('b',) + assert loads(dumps(L)) == L + L = tuple(['a'*n for n in range(100)]) + (None,True,None) + assert loads(dumps(L)) == L + L = list(['a'*n for n in range(100)]) + [None,True,None] + assert loads(dumps(L)) == L + assert loads(dumps(None)) == None + assert loads(dumps({None:None})) == {None:None} + + class A(object): + def __init__(self, a, b, c): + self.a = a + self.b = b + self.c = c + + def _pack(self): + return (self.a, self.b, self.c) + + serializable.register(A) + + instance = [A(1,2,3), 1, A(1,3,4), 'sss'] + print loads(dumps(instance)) + +if __name__ == '__main__': + test() diff --git a/PodSixNet/rencode.pyc b/PodSixNet/rencode.pyc new file mode 100644 index 0000000..fdc0378 Binary files /dev/null and b/PodSixNet/rencode.pyc differ diff --git a/Project Reflection.pdf b/Project Reflection.pdf new file mode 100644 index 0000000..26cd1a1 Binary files /dev/null and b/Project Reflection.pdf differ diff --git a/README.md b/README.md index 61ec120..b04ecf1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,13 @@ -# InteractiveProgramming -This is the base repo for the interactive programming project for Software Design, Spring 2016 at Olin College. +Authors: Cedric Kim, Kevin Guo + +README: +General running instructions: +-Ensure you are running Ubuntu OS +-Download all files from github repository (including .ogg files and PodSixNet folder) +-If you are hosting the server, click on the wifi icon and select ‘connection information’ +-The IP address under IPv4 is the IP everyone will enter into the game +-If host--from the command window, run one instance of “server.py”. +-Enter previously selected IP +-Enter desired game attributes +-All players run “escape_the_maze.py” and enter the same IP +-To restart the game, close all windows and re-run all programs in the same order \ No newline at end of file diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..b04ecf1 --- /dev/null +++ b/README.txt @@ -0,0 +1,13 @@ +Authors: Cedric Kim, Kevin Guo + +README: +General running instructions: +-Ensure you are running Ubuntu OS +-Download all files from github repository (including .ogg files and PodSixNet folder) +-If you are hosting the server, click on the wifi icon and select ‘connection information’ +-The IP address under IPv4 is the IP everyone will enter into the game +-If host--from the command window, run one instance of “server.py”. +-Enter previously selected IP +-Enter desired game attributes +-All players run “escape_the_maze.py” and enter the same IP +-To restart the game, close all windows and re-run all programs in the same order \ No newline at end of file diff --git a/Scroll_Collect.ogg b/Scroll_Collect.ogg new file mode 100644 index 0000000..8bf1c64 Binary files /dev/null and b/Scroll_Collect.ogg differ diff --git a/Sewers_Theme.ogg b/Sewers_Theme.ogg new file mode 100644 index 0000000..5bd09a2 Binary files /dev/null and b/Sewers_Theme.ogg differ diff --git a/Underground_Theme.ogg b/Underground_Theme.ogg new file mode 100644 index 0000000..1f8900a Binary files /dev/null and b/Underground_Theme.ogg differ diff --git a/server.py b/server.py new file mode 100644 index 0000000..f653a82 --- /dev/null +++ b/server.py @@ -0,0 +1,252 @@ +import pygame +from PodSixNet.Channel import Channel +from PodSixNet.Server import Server +from random import randint +import random +from Maze import create_maze +import time + + +class Time(object): + def __init__(self): + self.start_time = 0 + self.time_elapsed = 0 + self.time_started = False + def reset_timer(self): + if not self.time_started: + self.start_time = int(time.clock()*1000) + self.time_started = True + def current_time(self): + return int(time.clock()*1000) - self.start_time + def reset_timer_bool(self): + self.time_started = False + +class GenerateMaze(object): + def __init__(self, MAZE_LENGTH, MAZE_HEIGHT): + self.MAZE_LENGTH = MAZE_LENGTH + self.MAZE_HEIGHT = MAZE_HEIGHT + self.MATRIX_CENTERS = 53*2 + self.maze_matrix = create_maze(self.MAZE_LENGTH, self.MAZE_HEIGHT) + +class EscapeTheMazeServerModel(object): + """Model""" + def __init__(self, number_of_players): + self.players = [] ##keep empty + self.NUMBER_OF_CHARACTERS = number_of_players + self.NUMBER_OF_SCROLLS = 12 + self.MAZE_SIZE = 20 + self.maze = GenerateMaze(self.MAZE_SIZE, self.MAZE_SIZE) + self.char_list = [] ##this list contains a list of attributes for each character (gets sent over network) + self.char = [] ##this creates characters for the server + self.scroll_list = [] + self.still_alive = [] + self.exit = [] + self.is_players_ready = [] + + self.count_down = False + for i in range(self.NUMBER_OF_CHARACTERS): + self.still_alive.append(False) + self.connected_players = 0 + GenerateCharacterLocations(self) + GenerateScrollLocations(self) + GenerateExitLocation(self) + +class GenerateExitLocation(object): + """creates a random location for the exit""" + def __init__(self,model): + self.model = model + self.maze = model.maze + x = random.randint(0, self.maze.MAZE_LENGTH - 1) + x_pos = x*self.maze.MATRIX_CENTERS*2 + self.maze.MATRIX_CENTERS + y = random.randint(0, self.maze.MAZE_HEIGHT - 1) + y_pos = y*self.maze.MATRIX_CENTERS*2 + self.maze.MATRIX_CENTERS + exit_entity = [x_pos, y_pos] + self.model.exit = exit_entity + +class GenerateScrollLocations(object): + """generates the location of the scrolls randomly""" + def __init__(self, model): + self.model = model + self.maze = model.maze + self.add_scroll = True + while len(self.model.scroll_list) < self.model.NUMBER_OF_SCROLLS: + x = random.randint(0, self.maze.MAZE_LENGTH - 1) + x_pos = x*self.maze.MATRIX_CENTERS*2 + self.maze.MATRIX_CENTERS + y = random.randint(0, self.maze.MAZE_HEIGHT - 1) + y_pos = y*self.maze.MATRIX_CENTERS*2 + self.maze.MATRIX_CENTERS + scroll_entity = [x_pos, y_pos] + for char in self.model.char_list: + if not(x_pos == char[0] and y_pos == char[1]): + if len(self.model.scroll_list) != 0: + for scroll in self.model.scroll_list: + if (x_pos == scroll[0] and y_pos == scroll[1]): + self.add_scroll = False + else: + self.add_scroll = False + if self.add_scroll: + self.model.scroll_list.append(scroll_entity) + self.add_scroll = True + + + +class GenerateCharacterLocations(object): + """generates the location of characters randomly""" + def __init__(self, model): + self.model = model + self.maze = model.maze + self.add_char = True + + monster_int = random.randint(0,(self.model.NUMBER_OF_CHARACTERS)-1) + + while len(self.model.char_list) < self.model.NUMBER_OF_CHARACTERS: + x = random.randint(0, self.maze.MAZE_LENGTH - 1) + x_pos = x*self.maze.MATRIX_CENTERS*2 + self.maze.MATRIX_CENTERS + y = random.randint(0, self.maze.MAZE_HEIGHT - 1) + y_pos = y*self.maze.MATRIX_CENTERS*2 + self.maze.MATRIX_CENTERS + char_entity = [x_pos, y_pos, monster_int] + if len(self.model.char_list) != 0: + for char in self.model.char_list: + if (x_pos == char[0] and y_pos == char[1]): + self.add_char = False + if self.add_char: + self.model.char_list.append(char_entity) + char = Character(x_pos, y_pos, 20, 20) + self.model.char.append(char) + self.add_char = True + +class Character(object): + """represents the character""" + def __init__(self, x_pos, y_pos, width, height): + self.x_pos = x_pos + self.y_pos = y_pos + self.width = width + self.height = height + self.rect = pygame.Rect(self.x_pos, self.y_pos, self.width, self.height) + +# class representing a sigle connection with a client +# this can also represent a player + +class ClientChannel(Channel): + def __init__(self, *args, **kwargs): + Channel.__init__(self, *args, **kwargs) + self.model = model + # function called when a player begin a movement + def Network_move(self, data): + self.char.x_pos = data['rel_x_pos'] + self.char.y_pos = data['rel_y_pos'] + # send to all other clients the information about moving + self._server.SendToAll(data) + + def Network_update_alive(self, data): + self._server.SendToAll(data) + + def Network_update_entities(self, data): + self._server.SendToAll(data) + + def Network_update_win(self, data): + self._server.SendToAll(data) + + def Network_lobby(self, data): + self.model.is_players_ready = data['is_players_ready'] + self._server.SendToAll(data) + + def Network_update_condition(self, data): + self._server.SendToAll(data) + +# class representing the server +class MyServer(Server): + channelClass = ClientChannel + + # Start the server + def __init__(self, model, *args, **kwargs): + Server.__init__(self, *args, **kwargs) + + self.model = model + # if self.start is True the game is working + self.start = False + self.story = False + # time before strating the game in milliseconds + self.wait_to_start = -1 + # addresss and port at which server is started + address, port = kwargs['localaddr'] + + print 'Server started at', address, 'at port', str(port) + print 'Now you can start the clients' + + # function called on every connection + def Connected(self, player, addr): + print 'Player connected at', addr[0], 'at port', addr[1] + + ## add player to the list + self.model.players.append(player) + self.model.connected_players = len(self.model.players) + ## set the bar rect of the player + player.char = self.model.char[len(self.model.players)-1] ##add player.char to players[] + player.Send({'action': 'number', 'num': len(self.model.players)-1}) + player.Send({'action': 'generate_maze', 'maze_matrix' : self.model.maze.maze_matrix}) + player.Send({'action': 'initialize_entities', 'char_list' : self.model.char_list, 'scroll_list' : self.model.scroll_list}) + player.Send({'action': 'exit_location', 'exit': self.model.exit}) + self.SendToAll({'action': 'ready_players', 'connected_players': self.model.connected_players}) + + # send all clients the same data + def SendToAll(self, data): + [p.Send(data) for p in self.model.players] + + def Loop(self): + t1 = Time() + while True: + # update server connection + myserver.Pump() + if self.story: + t1.reset_timer() + if t1.current_time() > 600: + self.start = True + self.story = False + self.ready = False + self.SendToAll({'action': 'update_condition', + 'story' : self.story, + 'ready': self.ready, + 'start': self.start}) + pygame.time.wait(20) + elif not self.start and not self.story: + t1.reset_timer() + ready = True + if len(self.model.is_players_ready) == 0: + ready = False + for player_ready in self.model.is_players_ready: + if not player_ready: + ready = False + if len(self.model.is_players_ready) != self.model.NUMBER_OF_CHARACTERS: + ready = False + self.ready = ready + if self.ready: + self.SendToAll({'action': 'ready', + 'player_ready': self.ready}) + pygame.time.wait(20) + else: + t1.reset_timer_bool() + if t1.current_time() > 300: + t1.reset_timer_bool() + self.story = True + self.ready = False + self.SendToAll({'action': 'update_condition', + 'story' : self.story, + 'ready': self.ready, + 'start': self.start}) + pygame.time.wait(20) + +print 'Enter the ip address of the server. Normally the ip address of the computer.' +print 'example: localhost or 192.168.0.2' +print 'Empty for localhost' +# ip address of the server (normally the ip address of the computer) +address = raw_input('Server ip: ') +number_of_players = int(raw_input('Players: ')) +#control if address is empty +if address == '': + address = 'localhost' +#address = '10.7.64.193' +# inizialize the server +model = EscapeTheMazeServerModel(number_of_players) +myserver = MyServer(model, localaddr=(address, 31500)) +# start mainloop +myserver.Loop() \ No newline at end of file