def init_graphic(self): pygame.display.init() self.screen = pygame.display.set_mode((self.W, self.H), pygame.DOUBLEBUF, 32) pygame.display.set_caption("MSRA2012 Python version") self.viewBox = ViewBox((self.W, self.H))
class Shippo: W = config.W H = config.H SpeedUp = 1 FPS = 30 LFPS = 30 # logic frame per sec SeaColor = (0, 0x55, 0xFF, 0xFF) BoardSize = (270, H - 4) BoardPos = (W - 270 - 2, 2) SubBoardSize = (260, 200) def __init__(self): self.reset() def reset(self): self.players = [] self.ships = [] self.resources = [] self.events = [] self.UISprites = [] self.visualSprites = [] self.needQuit = 0 self.running = False self.renderCnt = 0 self.blockings = [] self.accuCallbacks = [] self.pyeventHandlers = [] self.ViewControl = None self.winScreen = None def add_player(self, player): self.players.append(player) def apply_instruction(self, inst): # apply an instruction # self.inform('apply: %s' % inst) print "apply:", inst cmd = inst.cmd args = inst.args if cmd == "MoveTo": self.ins_move_to(inst.faction, int(args[0]), float(args[1]), float(args[2])) elif cmd == "Data": self.ins_data(inst.faction) elif cmd == "Attack": self.ins_attack(inst.faction, int(args[0]), int(args[1])) elif cmd == "StartRotating": self.ins_start_rotating(inst.faction, int(args[0]), float(args[1])) elif cmd == "StartMoving": self.ins_start_moving(inst.faction, int(args[0])) elif cmd == "StopRotating": self.ins_stop_rotating(inst.faction, int(args[0])) elif cmd == "Stop": self.ins_stop(inst.faction, int(args[0])) elif cmd == "StartRotatingTo": self.ins_start_rotating_to(inst.faction, int(args[0]), float(args[1]), float(args[2])) else: # invalid command self.inform("Unknown instruction %s" % inst) def to_rawstr(self, faction): # generate the info to player specified by playerId shipInfos = [] resourceInfos = [] for resource in self.resources: resourceInfos.append(ResourceInfo(resource)) myships = [ship for ship in self.ships if ship.faction == faction] for ship in self.ships: if ship.faction != faction: for ship0 in myships: if (ship0.position - ship.position).length <= config.RangeOfView: shipInfos.append(ShipInfo(ship)) break else: shipInfos.append(ShipInfo(ship)) lines = [] lines.append("OSInterface %d" % (5)) lines.append("Faction %d" % (faction,)) lines.append("Running %s" % (self.running,)) lines.append("TimeLeft %d" % (int(self.timeLimit - self.t),)) lines.append("Resource %d" % (len(resourceInfos),)) for info in resourceInfos: lines.append("%s" % info.to_rawstr()) lines.append("Ship %d" % (len(shipInfos),)) for info in shipInfos: lines.append("%s" % info.to_rawstr()) lines.append("") return "\n".join(lines) def get_ship_by_id(self, shipId): for ship in self.ships: if ship.id == shipId: return ship return None def ins_move_to(self, faction, shipId, x, y): ship = self.get_ship_by_id(shipId) if ship is None or ship.faction != faction: return False ship.rotateTarget = ship.moveTarget = Vec2d(x, y) ship.moving = True ship.rotating = True return True def ins_data(self, faction): data = self.to_rawstr(faction) player = self.players[faction - 1] player.send_data(self) def ins_attack(self, faction, shipId1, shipId2): ship1 = self.get_ship_by_id(shipId1) if ship1 is None or ship1.faction != faction: return False ship2 = self.get_ship_by_id(shipId2) # attack if not ship2 or ship2.faction != ship1.faction: ship1.attackTarget = ship2 return True return False def ins_start_rotating(self, faction, shipId, angle): ship = self.get_ship_by_id(shipId) if ship is None or ship.faction != faction: return False ship.rotating = True ship.moveTarget = None ship.rotateTarget = angle return True def ins_stop_rotating(self, faction, shipId): ship = self.get_ship_by_id(shipId) if ship is None or ship.faction != faction: return False ship.rotating = False ship.rotateTarget = None ship.moveTarget = None return True def ins_start_moving(self, faction, shipId): ship = self.get_ship_by_id(shipId) if ship is None or ship.faction != faction: return False ship.moveTarget = None ship.moving = True return True def ins_stop_moving(self, faction, shipId): # stop both moving and rotating ship = self.get_ship_by_id(shipId) if ship is None or ship.faction != faction: return False ship.moving = False ship.moveTarget = None return True def ins_stop(self, faction, shipId): # stop both moving and rotating ship = self.get_ship_by_id(shipId) if ship is None or ship.faction != faction: return False ship.moving = False ship.rotating = False ship.moveTarget = None ship.rotateTarget = None return True def ins_start_rotating_to(self, faction, shipId, x, y): ship = self.get_ship_by_id(shipId) if ship is None or ship.faction != faction: return False ship.moveTarget = None ship.rotating = True ship.rotateTarget = (x, y) return True def render(self): self.renderTimer.tick() screen = self.screen viewBox = self.viewBox # draw the sea screen.fill(self.SeaColor) # screen.fill((0, 0, 0, 0xff)) # self.background.update(viewBox) # screen.blit(self.background.image, self.background.rect) # draw the white border w, h = (config.MapWidth, config.MapHeight) rect = pygame.Rect((0, 0), (viewBox.lenWorld2screen(w), viewBox.lenWorld2screen(h))) rect.center = viewBox.posWorld2screen((0, 0)) pygame.draw.rect(screen, (0x7F, 0x7F, 0x7F, 0x30), rect, 1) # update sprites # draw visual effects # draw the resources # draw the ships sprites = self.resources + self.ships + self.visualSprites sprites.sort(sprite_cmp) for sp in sprites: if viewBox.inside(sp): sp.update(viewBox) screen.blit(sp.image, sp.rect) # draw ship moving targets, attacking target, if exist attackR = viewBox.lenWorld2screen(config.CannonRange) attackRect = pygame.Rect((0, 0), (attackR * 2,) * 2) for ship in self.ships: target = ship.moveTarget center = map(int, viewBox.posWorld2screen(ship.position)) if target: pygame.draw.aaline(screen, pygame.Color("yellow"), center, map(int, viewBox.posWorld2screen(target))) if ship.attackTarget: pygame.draw.aaline( screen, pygame.Color("red"), center, map(int, viewBox.posWorld2screen(ship.attackTarget.position)) ) if ship._show_debug: # draw range of view pygame.draw.circle( screen, pygame.Color("green"), center, int(viewBox.lenWorld2screen(config.RangeOfView)), 1 ) # draw range of attack # DOC: pygame.draw.arc(Surface, color, Rect, start_angle, stop_angle, width=1): return Rect angle = ship.direction.angle attackRect.center = center a0 = (180 - config.CannonAngle) / 2 # pygame.draw.arc(screen, Color("red"), attackRect, math.radians(angle + a0), math.radians(angle + 180)) pygame.draw.arc( screen, Color("red"), attackRect, -math.radians(angle + 180 - a0), -math.radians(angle + a0) ) pygame.draw.arc( screen, Color("red"), attackRect, -math.radians(angle - a0), -math.radians(angle - 180 + a0) ) pygame.draw.circle( screen, pygame.Color("green"), center, int(viewBox.lenWorld2screen(config.RangeOfView)), 1 ) # draw dashBoard dash = self.dashBoard self.renderCnt += 1 if self.renderCnt % 3 == 0: dash.update() screen.blit(dash.image, dash.rect) self.statusBoard.update() screen.blit(self.statusBoard.image, self.statusBoard.rect) self.informBoard.update() screen.blit(self.informBoard.image, self.informBoard.rect) # draw UI sprites for sp in self.UISprites: if sp.visible: sp.update() self.screen.blit(sp.image, sp.rect) # draw winScreen if self.winScreen: self.winScreen.update() screen.blit(self.winScreen.image, self.winScreen.rect) pygame.display.flip() def quit(self): self.needQuit = 1 def inform(self, msg): print msg self.informBoard.append_info(msg) def show_menu(self): from menu import Menu import Tkinter as tk root = tk.Tk() self.add_human = 0 self.goOn = 0 def set_AI_vs_human(): self.add_human = 1 root.destroy() self.goOn = 1 def set_AI_vs_AI(): root.destroy() self.goOn = 1 def quit(): root.destroy() items = [("AI vs Human", set_AI_vs_human), ("AI vs AI", set_AI_vs_AI), ("Quit", quit)] menu = Menu(root, items) menu.pack() root.mainloop() if not self.goOn: self.quit() else: del self.goOn def init_graphic(self): pygame.display.init() self.screen = pygame.display.set_mode((self.W, self.H), pygame.DOUBLEBUF, 32) pygame.display.set_caption("MSRA2012 Python version") self.viewBox = ViewBox((self.W, self.H)) def render_connection(self): self.screen.fill(self.SeaColor) self.screen.blit(self.informBoard.image, self.informBoard.rect) pygame.display.flip() def wait_connect(self): if self.needQuit: return self.init_graphic() self.informBoard = dashboard.InformBoard((10, 25), 15) test = 0 server = Socket(socket.AF_INET, socket.SOCK_STREAM) if not test: while 1: try: server.bind(("", config.Port)) break except socket.error as e: self.inform(str(e)) for event in pygame.event.get(): if event.type == QUIT: self.quit() break self.render_connection() sleep(1) server.listen(2) server.settimeout(0.0) player1 = Player(1, server) else: player1 = HumanPlayer(1) if self.add_human: player2 = HumanPlayer(2) # add handler for human player self.pyeventHandlers.append(player2) else: player2 = Player(2, server) players = [player1, player2, None] timer = pygame.time.Clock() # wait until connected 2 players finished = 0 player = players[finished] try: self.inform("waiting for AI to connect...") while finished < 2: for event in pygame.event.get(): if event.type == QUIT: self.quit() break if self.needQuit: break if player.connect(): self.add_player(player) self.inform("AI %s connected" % (player.name)) finished += 1 player = players[finished] self.render_connection() timer.tick(10) except KeyboardInterrupt: server.close() return False server.close() return True def setup_level(self, level=0): if self.needQuit: return self.background = visual.Background("sea_wrap.png") self.dashBoard = dashboard.DashBoard(self.BoardPos, self.BoardSize) self.statusBoard = dashboard.StatusBoard() self.informBoard.clear() p0 = Vec2d(250, 900) p1 = Vec2d(config.MapWidth - p0.x, p0.y) p2 = Vec2d(600, 600) p3 = Vec2d(config.MapWidth - p2.x, config.MapHeight - p2.y) p0 = Vec2d(-750, -100) p1 = Vec2d(750, p0.y) p2 = Vec2d(-400, -400) p3 = Vec2d(400, 400) dh = 40 level0 = { "rPoss": [(p2.x, p2.y), (p2.x, p3.y), (p3.x, p2.y), (p3.x, p3.y), (p2 + p3) / 2], "sPoss1": [p0 + (0, i * dh) for i in xrange(5)], "sPoss2": [p1 + (0, i * dh) for i in xrange(5)], } self.timeLimit = 60 * 5 id = 1 for pos in level0["rPoss"]: resource = Resource(id, pos) id += 1 self.resources.append(resource) self.pyeventHandlers.append(resource.button) # add ship for player 1 id = 1 player = self.players[0] for pos in level0["sPoss1"]: ship = Ship(id, 1, pos) # ship.moving = 1 ship.color = player.color player.ships.append(ship) self.ships.append(ship) self.pyeventHandlers.append(ship.button) id += 1 # add ship for player 2 player = self.players[1] for pos in level0["sPoss2"]: ship = Ship(id, 2, pos) ship.direction.rotate(180) ship.color = player.color player.ships.append(ship) self.ships.append(ship) self.pyeventHandlers.append(ship.button) id += 1 # add dahsboard for player for player in self.players: dash = dashboard.ShipStatus(self.SubBoardSize, player) self.dashBoard.add_board(dash) # add ship detail board self.detailBoard = dashboard.ShipDetailStatus() self.dashBoard.add_board(self.detailBoard) # add view controller self.viewControl = ViewControl(self) self.pyeventHandlers.append(self.viewControl) self.viewBox.zoom((0, 0), -0.4) self.viewBox.move_to((0, 0)) # add render timer self.renderTimer = pygame.time.Clock() def handle_pyevents(self): for event in pygame.event.get(): if event.type == QUIT: self.quit() break for handler in self.pyeventHandlers: handler.handle(event) # make handler alive for handler in self.pyeventHandlers: handler.step() def accept_insts(self): # accept instructions for player in self.players: done = False while not done: inst = player.fetch_instruction() if inst is None: break # apply the instruction, maybe not success try: self.apply_instruction(inst) except Exception as e: self.inform(e) done = True def mainloop(self): # start game self.t = 0.0 self.running = True renderDt = 1.0 / self.FPS * self.SpeedUp instDt = 1.0 / config.MaxInstPerSec eventDt = 1.0 / self.FPS logicDt = 1.0 / self.LFPS timer = pygame.time.Clock() timer1 = pygame.time.Clock() # accuCallbacks, add by order self.register_accumulate_callback(eventDt, self.handle_pyevents) self.register_accumulate_callback(instDt, self.accept_insts) self.register_accumulate_callback(renderDt, self.render) # add visual effect def add_visual_effect(): for ship in self.ships: speed = ship.velocity.length if speed > 1: effect = visual.WaterEffect(ship.position, speed) self.visualSprites.append(effect) self.register_accumulate_callback(0.1, add_visual_effect) def update_status(): sec = max(int(self.timeLimit - self.t), 0) self.statusBoard.update_info( "FPS %.1f PFPS: %.1f TIME LEFT: %02d:%02d" % (self.renderTimer.get_fps(), timer.get_fps(), sec / 60, sec % 60) ) self.register_accumulate_callback(1.0, update_status) while not self.needQuit: timer1.tick() # try activate accumulate callbacks for acb in self.accuCallbacks: at, adt, callback = acb if at >= adt: acb[0] -= adt callback() if self.needQuit: break # game logic # dt = (logicDt - timer1.tick()/1000) * self.SpeedUp dt = logicDt * self.SpeedUp self.step(dt) if self.needQuit: break for acb in self.accuCallbacks: acb[0] += dt # acb[0] += logicDt timer.tick(self.LFPS) # print 'LFPS:', timer.get_fps() def register_accumulate_callback(self, dt, callback): self.accuCallbacks.append([0, dt, callback]) def add_event(self, e): heappush(self.events, e) def phy_step(self, dt0): dtl, dtr = 0.005, dt0 t = 0 for ship in self.ships: ship.short_step(dtl - t) t = dtl while dtr - dtl > 1e-4: dtm = (dtl + dtr) / 2 for ship in self.ships: ship.short_step(dtm - t) t = dtm if self.exist_hit(): dtr = dtm else: dtl = dtm return t def ship_attack(self, time, ship): if ship.attackTarget is None: return for cannon in [0, 1]: if ship.cooldowns[cannon] == 0 and ship.in_cannon_range(cannon, ship.attackTarget): target = ship.attackTarget dp = target.position - ship.position d = dp.length R = config.CannonRange damage = ( 200 * (1 - (d / R) ** 2) ** 0.5 * (1 - ship.direction.normalized().dot(ship.velocity - target.velocity) / 100) ) p1 = target.direction.rotated(-30) p2 = target.direction.rotated(30) if p1.cross(dp) * p2.cross(dp) <= 0: # bonus hit damage *= 2 self.add_event(ShipDamage(time + d / 150, target, damage)) self.visualSprites.append(visual.BulletHit(ship.position, target.position, d / 150)) ship.cooldowns[cannon] = config.CannonSpan break def handle_event(self, event): if isinstance(event, ObjHit): # handle ObjHit obj1 = event.obj1 obj2 = event.obj2 dp = obj2.position - obj1.position R = obj1.hitRadius + obj2.hitRadius dpnor = dp.normalized() # hit damage detection if isinstance(obj2, Ship) and obj2.faction != obj1.faction: # if isinstance(obj2, Ship): dv = obj1.velocity - obj2.velocity if dv.dot(dpnor) > 7.5 and dpnor.dot(obj1.direction.normalized()) >= 0.8660254037: # sqrt(3)/2 hitDamage = obj1.direction.dot(dv) * 14 + 100 self.add_event(ShipDamage(event.time, obj2, hitDamage)) self.add_event(ShipDamage(event.time, obj1, hitDamage / 2)) self.visualSprites.append(visual.ExplodeEffect((obj1.position + obj2.position) / 2, hitDamage)) # print '%d hit %d, damage %s' %(obj1.id, obj2.id, hitDamage) obj1.position -= (2 + R - dp.length) / 2 * dpnor obj2.position += (2 + R - dp.length) / 2 * dpnor else: obj1.position -= (R - dp.length) * dpnor v1 = obj1.velocity # v1 -= dpnor * dpnor.dot(v1) v1 *= 0 # v1 *= 0.2 obj1.blocked = 1 elif isinstance(event, ShipDamage): ship = event.ship damage = event.damage ship.armor -= damage if ship.armor <= 0: self.add_event(ShipDie(event.time, ship)) elif isinstance(event, ShipRecover): ship = event.ship ship.armor += event.val if ship.armor > config.MaxArmor: ship.armor = config.MaxArmor # elif isinstance(event, ShipAttack): # ship = event.ships # self.ship_attack(ship, event.cannon, event.target) elif isinstance(event, ShipDie): ship = event.ship if ship not in self.ships: return self.ships.remove(ship) for ship1 in self.ships: if ship1.attackTarget == ship: ship1.attackTarget = None self.players[ship.faction - 1].ships.remove(ship) def hittest(self, obj1, obj2): dp = obj2.position - obj1.position R = obj1.hitRadius + obj2.hitRadius if dp.length <= R: if obj1.velocity.dot(dp.normalized()) > 0: return True return False def detect_hit(self): for ship in self.ships: for ship1 in self.ships: if ship != ship1: if self.hittest(ship, ship1): hitEvent = ObjHit(self.t, ship, ship1) # self.blockings.append((ship, ship1)) self.add_event(hitEvent) for resource in self.resources: if self.hittest(ship, resource): hitEvent = ObjHit(self.t, ship, resource) # self.blockings.append((ship, resource)) self.add_event(hitEvent) def step(self, dt0): events = self.events t = 0 for ship in self.ships: ship.blocked = 0 while t < dt0: dt = dt0 - t if events: dt = min(dt, events[0].time - self.t) # dtr = self.phy_step(dt) # for ship in self.ships: # ship.short_step(-dtr) # ship.step(dtr) # t += dtr for ship in self.ships: ship.step(dt) t += dt self.t += dt self.detect_hit() while events and events[0].time <= self.t: e = heappop(events) self.handle_event(e) for sp in self.visualSprites: sp.step(dt0) self.visualSprites = [effect for effect in self.visualSprites if not effect._kill] # ship try attack for ship in self.ships: if ship.attackTarget is not None: # try to attack self.ship_attack(self.t, ship) # ship recover for player in self.players: recover = dt * config.ResourceRestoreRate[sum(1 for r in self.resources if r.faction == player.faction)] if recover: for ship in player.ships: ship.armor = min(ship.armor + recover, config.MaxArmor) # out side damage w = config.MapWidth / 2 h = config.MapHeight / 2 for ship in self.ships: x, y = ship.position if x < -w or x > w or x < -h or x > h: self.add_event(ShipDamage(self.t, ship, 25 * dt0)) # resource distribute for res in self.resources: del res.ships[:] for ship in self.ships: if (ship.position - res.position).length <= config.ResourceRadius: res.ships.append(ship) res.step(dt0) if res.faction: col = res.boundingColor col1 = self.players[res.faction - 1].color col.r, col.g, col.b = col1.r, col1.g, col1.b # event handle while events and events[0].time <= self.t: e = heappop(events) self.handle_event(e) # test gameover if self.winScreen is None: self.test_gameover() elif self.winScreen.finished: self.quit() def test_gameover(self): # test if a player wins winner = None p1 = self.players[0] p2 = self.players[1] cnt1 = len(p1.ships) cnt2 = len(p2.ships) if self.t <= self.timeLimit: if cnt2 == 0: winner = p1 elif cnt1 == 0: winner = p2 else: if cnt1 != cnt2: if cnt1 > cnt2: winner = p1 elif cnt1 < cnt2: winner = p2 else: rcnt1 = sum(1 for r in self.resources if r.faction == player1.faction) rcnt2 = sum(1 for r in self.resources if r.faction == player2.faction) if rcnt1 > rcnt2: winner = p1 elif rcnt1 < rcnt2: winner = p2 else: hp1 = sum(ship.armor for ship in player1.ships) hp2 = sum(ship.armor for ship in player2.ships) if hp1 > hp2: winner = p1 else: winner = p2 if winner is not None: win = WinScreen(winner) self.winScreen = win self.pyeventHandlers.append(win) return True return False