Ejemplo n.º 1
0
 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))
Ejemplo n.º 2
0
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