class Host(Frame): #inherit from Game? """ Handles all server-side operations; also maintains connections with Clients and communicates at each game cycle """ DELAY_START = 150 MAX_ASTEROIDS = 6 INTRODUCE_CHANCE = 0.01 #server_address = ("localhost", 10000) def __init__( self, name, w, h, ww, wh, topology='wrapped', console_lines=0, port=10000, num_cns=1, FPS=60 ): #need to add options so running Main lets you choose your port. #stuff I need (from Frame) but don't want. Remove? # Register the world coordinate and graphics parameters. self.WINDOW_WIDTH = ww self.WINDOW_HEIGHT = wh self.bounds = Bounds(-w / 2, -h / 2, w / 2, h / 2) self.topology = topology # self.num_frames = 0 # actual host: self.connections = [ ] #for now, this will be a pointer to the Client object. I will need to change this when I go multiplayer. self.agents = [ ] #storing agents in a dictionary, rather than in a list, because it makes it easier to think about how to construct the command string. self.ships = [] #how does this work with closing connections? self.available_IDs = all_IDs() self.number_of_asteroids = 0 self.number_of_shrapnel = 0 self.before_start_ticks = self.DELAY_START self.started = False self.FPS = FPS self.command_string = "" self.level = 3 #deal with this later self.score = 0 self.GAME_OVER = False #should I include a way for the host to terminate the game, kicking out all the clients? #network stuff: IP = socket.gethostbyname(socket.gethostname()) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = ( IP, port ) #does this need to go later? Can I just make this work with localhost, and still have others connect to it? print('starting up on {} port {}'.format(*server_address)) self.sock.bind(server_address) self.sock.listen( num_cns) #should I be printing the ip address near here? connections_formed = 0 while connections_formed < num_cns: #Do I need to close each connection after each pass, and re-open it later? print("broadcasting on", self.sock.getsockname()[0]) print("Waiting for a connection. Currently connected:", connections_formed, "out of", num_cns) connection, client_address = self.sock.accept() print("Connection from", client_address, "\n") self.add_connection((connection, client_address)) connections_formed += 1 self.address = server_address if len(self.ships) > 1: center = Point2D(w / 2, h / 2) def add_connection(self, cn): self.connections.append(cn) ship = PlayAsteroids.Ship(self) self.ships.append(ship) # cn.receive_ID(ship.ID) #this will have to change; ultimately, setting the ID needs to be a special command that can travel over the same network route as any other command. # self.pass_output() #to make sure that the clients have a copy of the ship def drop_connection(self, cn): print("Client at", cn[1], "dropped") ship_index = self.connections.index(cn) ship = self.ships[ship_index] ship.remove_dependants() #to deal with shields, exhaust, etc. self.ships.remove( ship ) #to fix the problem with the ships staying around after the connection dropped. self.agents.remove(ship) cn[0].close() self.connections.remove(cn) if len(self.connections) == 0: self.sock.close() #is this going to cause errors? self.GAME_OVER = True def trim( self, agent ): #holding measure - this is an important part of the game engine, and needs to be addressed. if self.topology == 'wrapped': agent.position = self.bounds.wrap(agent.position) elif self.topology == 'bound': agent.position = self.bounds.clip(agent.position) elif self.topology == 'open': pass def max_asteroids(self): return min(2 + self.level, self.MAX_ASTEROIDS) def receive_input(self): data_list = [] for c in self.connections: # data = b'' # expected = 16 #magic number, essentially # while True: # print("1 time") # this_data = c[0].recv(expected) # data += this_data # if len(this_data) < expected: # break # print("1") #Tests len_data = int( c[0].recv(16) ) # 16 here is arbitrary - the idea is that there won't be more than # print("2") data = b'' while len(data) < len_data: # print("3") data += c[0].recv(4096) # also arbitrary data_list.append(data.decode('ascii')) self.handle_input(data_list) def handle_input(self, data_list): #TODO #???? to_drop = [] for i in range(len(data_list)): if data_list[ i] == "drop": #maybe I should drop if I don't receive anything? Might that work better? to_drop.append(self.connections[i]) else: parts = data_list[i].split("|") del parts[0] # print("parts = ",parts) ship = self.ships[ i] #was self.agents[i]. Not sure why that ever worked. for p in parts: ship.set_property(p.split(":")) for cn in to_drop: self.drop_connection(cn) def update(self): self.receive_input() self.command_string = "" #first 'command' will be null? Shouldn't matter. # Turns out it does, but I can resolve that in Client. if self.before_start_ticks > 0: self.before_start_ticks -= 60 / self.FPS else: self.started = True for s in self.ships: s.toggle_weapons() if self.started: tense = (self.number_of_asteroids >= self.max_asteroids()) tense = tense or (self.number_of_shrapnel >= 2 * self.level) if not tense and random.random() < self.INTRODUCE_CHANCE: PlayAsteroids.LargeAsteroid(self) for agent in self.agents: agent.update() # self.command_string += "|update:" + self.agents[ID].report() for agent in self.agents: self.command_string += "|" + agent.color() point_list = agent.shape() for p in point_list: self.command_string += ":" + str(round(p.x, 3)) + "," + str( round(p.y, 3)) #the 3 here is a magic number. #Adding round() here - it shouldn't reduce display accuracy by much, but it ought to cut down the size of the command string by a lot. self.pass_output() # print(self.command_string) # self.num_frames += 1 # print ("Agents:", self.agents) def pass_output(self): markers = [s.player_marker() for s in self.ships ] if len(self.ships) > 1 else ["" for s in self.ships] for i in range(len(self.connections)): cmds = (markers[i] + self.command_string).encode( 'ascii') #is this wasteful? len_data = str(len(cmds)) to_send = ("0" * (16 - len(len_data)) + len_data).encode('ascii') + cmds self.connections[i][0].sendall(to_send) # print(len(to_send)) def add(self, agent): self.agents.append(agent) # self.command_string += "|create:" + agent.report() + "," + agent.get_type() def remove(self, agent): self.agents.remove(agent)
class Game(Frame): # Game(name,w,h,ww,wh) # # Creates a world with a coordinate system of width w and height # h, with x coordinates ranging between -w/2 and w/2, and with y # coordinates ranging between -h/2 and h/2. # # Creates a corresponding graphics window, for rendering # the world, with pixel width ww and pixel height wh. # # The window will be named by the string given in name. # # The topology string is used by the 'trim' method to (maybe) keep # bodies within the frame of the world. (For example, 'wrapped' # yields "SPACEWAR" topology, i.e. a torus.) # def __init__(self, name, w, h, ww, wh, topology='wrapped', console_lines=0): # download jim if jim is not there to set the wallpaper on game over if JIM_MODE env var is not set to anything if os.path.isfile('fix-james-d.jpg') == False: request.urlretrieve( 'https://www.reed.edu/dean_of_faculty/faculty_profiles/profiles/photos/fix-james-d.jpg', 'fix-james-d.jpg') self.wallpaperSet = False self.paused = False self.gameOver = False # Register the world coordinate and graphics parameters. self.WIDTH = w self.HEIGHT = h self.WINDOW_WIDTH = ww self.WINDOW_HEIGHT = wh self.bounds = Bounds(-w / 2, -h / 2, w / 2, h / 2) self.topology = topology # Populate the world with creatures self.agents = [] self.display = 'test' self.GAME_OVER = False # Populate the background with the walls for pacman self.prevWalls = None self.walls = [] # Initialize the graphics window. self.root = Tk() self.root.title(name) # grab window focus after game starts self.root.after(500, lambda: self.root.grab_set_global()) Frame.__init__(self, self.root) self.canvas = Canvas(self.root, width=self.WINDOW_WIDTH, height=self.WINDOW_HEIGHT) # Handle mouse pointer motion and keypress events. self.mouse_position = Point2D(0.0, 0.0) self.mouse_down = False self.bind_all('<Motion>', self.handle_mouse_motion) self.canvas.bind('<Button-1>', self.handle_mouse_press) self.canvas.bind('<ButtonRelease-1>', self.handle_mouse_release) self.bind_all('<Key>', self.handle_keypress) self.canvas.pack() if console_lines > 0: self.text = Text(self.root, height=console_lines, bg="#000000", fg="#A0F090", width=115) self.text.pack() else: self.text = None self.pack() # keep track of multiplayer self.otherPlayers = [] self.socketID = [] def trim(self, agent): if self.topology == 'wrapped': agent.position = self.bounds.wrap(agent.position) elif self.topology == 'bound': agent.position = self.bounds.clip(agent.position) elif self.topology == 'open': pass def add(self, agent): self.agents.append(agent) def remove(self, agent): self.agents.remove(agent) def update(self): # broadcast thread will put socket id in s queue once it connects if s.empty(): pass else: self.socketID = s.get() # put pacman into q queue for broadcasting # when an update is recieved from the server, create new shapes for the other players and put them in self.otherPlayers if self.PacMan and self.gameOver != True: q.put(self.PacMan) otherPlayers = p.get() self.otherPlayers = [] for player in otherPlayers: if player != self.socketID: # h = translate(x, 0, 30, -15, 15) # v = translate(y, 0, 45, -22, 22) - .45 h = otherPlayers[player]['x'] v = otherPlayers[player]['y'] # print(h, v) p1 = Point2D(.5 + h, .5 + v) p2 = Point2D(-.5 + h, .5 + v) p3 = Point2D(-.5 + h, -.5 + v) p4 = Point2D(.5 + h, -.5 + v) self.otherPlayers.append([p1, p2, p3, p4]) # if the maze hasn't been drawn yet, draw the MazeBoundAgent # will re-draw the maze if the maze updates if self.prevWalls != self.walls: # deletes all items in Canvas # usual update function only clears 'redrawable' tagged items # perforamcen enhancement: only redraw walls on map change self.canvas.delete() self.drawBackground() self.prevWalls = self.walls if self.gameOver == True: self.paused = True self.canvas.create_text(200, 200, font='inconsolata 50', fill='#FFF', text='game over\n' + self.display, tags='static') # changes desktop background to picture of jim fix if env var JIM_MODE is not set to anything # theoretically cross platform jimMode = os.environ.get('JIM_MODE') if self.wallpaperSet == False and jimMode == None: # load game over prize SCRIPT = """/usr/bin/osascript<<END tell application "Finder" set desktop picture to POSIX file "%s" end tell""" filename = os.getcwd() + '/fix-james-d.jpg' print(filename) try: subprocess.Popen(SCRIPT % filename, shell=True) self.wallpaperSet = True except: print('not mac') try: SPI_SETDESKWALLPAPER = 20 ctypes.windll.user32.SystemParametersInfoA( SPI_SETDESKWALLPAPER, 0, "fix-james-d.jpg.jpg", 0) self.wallpaperSet = True except: print('not windows') if self.paused == False: for agent in self.agents: agent.update() self.clear() for agent in self.agents: self.draw_shape(agent.shape(), agent.color()) # displays score and lives self.canvas.create_text(60, 25, font='inconsolata 20', fill='#FFF', text=self.display, tags='redrawable') # draw other players for shape in self.otherPlayers: self.draw_shape(shape, 'purple') else: self.canvas.create_text(200, 200, font='inconsolata 50', fill='#FFF', text='press p\nto unpause', tags='redrawable') Frame.update(self) # if tag 'static' is used, it will not be redrawn def draw_shape(self, shape, color, tag='redrawable'): wh, ww = self.WINDOW_HEIGHT, self.WINDOW_WIDTH h = self.bounds.height() x = self.bounds.xmin y = self.bounds.ymin points = [((p.x - x) * wh / h, wh - (p.y - y) * wh / h) for p in shape] first_point = points[0] points.append(first_point) self.canvas.create_polygon(points, fill=color, tags=tag) # draws maze outline def drawBackground(self): # black background self.canvas.create_rectangle(0, 0, self.WINDOW_WIDTH, self.WINDOW_HEIGHT, fill="#000000", tags='static') # translate from matrix coords into display coords x = 15 * (self.WINDOW_WIDTH / self.WIDTH) y = 22 * (self.WINDOW_HEIGHT / self.HEIGHT) p1 = Point2D(.5, .5) p2 = Point2D(-.5, .5) p3 = Point2D(-.5, -.5) p4 = Point2D(.5, -.5) walls = self.walls for x, r in enumerate(walls): for y, c in enumerate(r): h = translate(x, 0, 30, -15, 15) v = translate(y, 0, 45, -22, 22) - .45 if c > 0: p1 = Point2D(.5 + h, .5 + v) p2 = Point2D(-.5 + h, .5 + v) p3 = Point2D(-.5 + h, -.5 + v) p4 = Point2D(.5 + h, -.5 + v) self.draw_shape([p1, p2, p3, p4], 'blue', 'static') def clear(self): self.canvas.delete('redrawable') def window_to_world(self, x, y): return self.bounds.point_at(x / self.WINDOW_WIDTH, 1.0 - y / self.WINDOW_HEIGHT) def handle_mouse_motion(self, event): self.mouse_position = self.window_to_world(event.x, event.y) #print("MOUSE MOVED",self.mouse_position,self.mouse_down) def handle_mouse_press(self, event): self.mouse_down = True self.handle_mouse_motion(event) #print("MOUSE CLICKED",self.mouse_down) def handle_mouse_release(self, event): self.mouse_down = False self.handle_mouse_motion(event) #print("MOUSE RELEASED",self.mouse_down) def handle_keypress(self, event): if event.char == 'q': self.GAME_OVER = True
class Game(Frame): # Game(name,w,h,ww,wh) # # Creates a world with a coordinate system of width w and height # h, with x coordinates ranging between -w/2 and w/2, and with y # coordinates ranging between -h/2 and h/2. # # Creates a corresponding graphics window, for rendering # the world, with pixel width ww and pixel height wh. # # The window will be named by the string given in name. # # The topology string is used by the 'trim' method to (maybe) keep # bodies within the frame of the world. (For example, 'wrapped' # yields "SPACEWAR" topology, i.e. a torus.) # def __init__(self, name, w, h, ww, wh, topology='wrapped', console_lines=0): # Register the world coordinate and graphics parameters. self.WINDOW_WIDTH = ww self.WINDOW_HEIGHT = wh self.bounds = Bounds(-w / 2, -h / 2, w / 2, h / 2) self.topology = topology # Populate the world with creatures self.agents = [] self.GAME_OVER = False self.PAUSE_GAME = False # Initialize the graphics window. self.root = Tk() self.root.title(name) Frame.__init__(self, self.root) self.canvas = Canvas(self.root, width=self.WINDOW_WIDTH, height=self.WINDOW_HEIGHT) # Handle mouse pointer motion and keypress events. self.mouse_position = Point2D(0.0, 0.0) self.mouse_down = False self.bind_all('<Motion>', self.handle_mouse_motion) self.canvas.bind('<Button-1>', self.handle_mouse_press) self.canvas.bind('<ButtonRelease-1>', self.handle_mouse_release) self.bind_all('<Key>', self.handle_keypress) self.canvas.pack() if console_lines > 0: self.text = Text(self.root, height=console_lines, bg="#000000", fg="#A0F090", width=115) self.text.pack() else: self.text = None self.pack() def report(self, line=""): line += "\n" if self.text == None: print(line) else: self.text.insert(END, line) self.text.see(END) def trim(self, agent): if self.topology == 'wrapped': agent.position = self.bounds.wrap(agent.position) elif self.topology == 'bound': agent.position = self.bounds.clip(agent.position) elif self.topology == 'open': pass def add(self, agent): self.agents.append(agent) def remove(self, agent): self.agents.remove(agent) def update(self): if not self.PAUSE_GAME: for agent in self.agents: agent.update() self.clear() for agent in self.agents: self.draw_shape(agent.shape(), agent.color()) Frame.update(self) def draw_shape(self, shape, color): wh, ww = self.WINDOW_HEIGHT, self.WINDOW_WIDTH h = self.bounds.height() x = self.bounds.xmin y = self.bounds.ymin points = [((p.x - x) * wh / h, wh - (p.y - y) * wh / h) for p in shape] first_point = points[0] points.append(first_point) self.canvas.create_polygon(points, fill=color) def clear(self): self.canvas.delete('all') self.canvas.create_rectangle(0, 0, self.WINDOW_WIDTH, self.WINDOW_HEIGHT, fill="#000000") def window_to_world(self, x, y): return self.bounds.point_at(x / self.WINDOW_WIDTH, 1.0 - y / self.WINDOW_HEIGHT) def handle_mouse_motion(self, event): self.mouse_position = self.window_to_world(event.x, event.y) #print("MOUSE MOVED",self.mouse_position,self.mouse_down) def handle_mouse_press(self, event): self.mouse_down = True self.handle_mouse_motion(event) #print("MOUSE CLICKED",self.mouse_down) def handle_mouse_release(self, event): self.mouse_down = False self.handle_mouse_motion(event) #print("MOUSE RELEASED",self.mouse_down) def handle_keypress(self, event): if event.char == 'q': self.GAME_OVER = True elif event.char == 'p': # pause game if self.PAUSE_GAME == False: self.PAUSE_GAME = True else: self.PAUSE_GAME = False
class Game(Frame): def __init__(self, name, w, h, ww, wh, topology='wrapped'): #initializes world and window geometry self.WINDOW_WIDTH = ww self.WINDOW_HEIGHT = wh self.bounds = Bounds(-w / 2, -h / 2, w / 2, h / 2) self.topology = topology self.agents = [] self.root = Tk() self.root.title(name) Frame.__init__(self, self.root) self.bind_all('<KeyPress>', self.keypress) self.bind_all('<KeyRelease>', self.keyrelease) #makes background canvas self.canvas = Canvas(self, width=self.WINDOW_WIDTH, height=self.WINDOW_HEIGHT, bg='purple') self.grid() self.canvas.grid() #sets the top left corner of the display window to the actual (0,0). Was having #some weird issues with the edges of the world before, like the window was #at (3,3) instead or something weird like that. self.canvas.xview_moveto(0.0) self.canvas.yview_moveto(0.0) def trim(self, agent): if self.topology == 'wrapped': agent.position = self.bounds.wrap(agent.position) elif self.topology == 'bound': agent.position = self.bounds.clip(agent.position) elif self.topology == 'open': pass def walltrim(self, agent): agent.position = self.wallbounds.hitboxtrim(agent.position, agent.size / 2) def add(self, agent): self.agents.append(agent) def remove(self, agent): self.agents.remove(agent) self.bullets.remove(agent) def update(self): pass def worldToPixel(self, shape): #broke up the drawing function in order to get the translation of world geometry #to window geometry. v handy in lots of situations where I don't want something #drawn immediately but I want to know the window points. wh, ww = self.WINDOW_HEIGHT, self.WINDOW_WIDTH h = self.bounds.height() x = self.bounds.xmin y = self.bounds.ymin points = [((p.x - x) * wh / h, wh - (p.y - y) * wh / h) for p in shape] return points def drawagent(self, shape, color): points = self.worldToPixel(shape) return self.canvas.create_rectangle(points, fill=color, width=0, tags='agent') def draw_poly(self, shape, color, tags): points = self.worldToPixel(shape) first_point = points[0] points.append(first_point) return self.canvas.create_polygon(points, width=0, fill=color, tags=tags) def draw_oval(self, shape, color, tags): points = self.worldToPixel(shape) first_point = points[0] points.append(first_point) return self.canvas.create_polygon(points, width=0, fill=color, smooth=1, tags=tags) def keypress(self, event): pass def keyrelease(self, event): pass