diff options
author | Joshua Minor <j@lux.vu> | 2008-01-29 08:05:45 (GMT) |
---|---|---|
committer | Joshua Minor <j@lux.vu> | 2008-01-29 08:05:45 (GMT) |
commit | 2a5f179b6f6bb4c7b50566b8abd72e6923e77b63 (patch) | |
tree | 6ad2f0b4103d9985c42f723bb7320c0d5b19973c /Maze.activity | |
parent | 365c204dc7b94001b99c11ec767ec2b242cc5d7a (diff) |
Upgraded Maze to v5
Added triangle and squard bonus players that use the gamepad & game rocker keys
so you can have 3 players on each XO.
Fixed maze seeding across multiple players so that new players can't bump everyone to
a new maze.
Refactored player drawing code into player.py
Diffstat (limited to 'Maze.activity')
-rw-r--r-- | Maze.activity/activity/activity.info | 2 | ||||
-rw-r--r-- | Maze.activity/game.py | 244 | ||||
-rw-r--r-- | Maze.activity/maze.py | 1 | ||||
-rw-r--r-- | Maze.activity/player.py | 53 |
4 files changed, 195 insertions, 105 deletions
diff --git a/Maze.activity/activity/activity.info b/Maze.activity/activity/activity.info index 315f177..764ba6c 100644 --- a/Maze.activity/activity/activity.info +++ b/Maze.activity/activity/activity.info @@ -3,5 +3,5 @@ name = Maze service_name = vu.lux.olpc.Maze class = activity.MazeActivity icon = activity-maze -activity_version = 4 +activity_version = 5 show_launcher = yes diff --git a/Maze.activity/game.py b/Maze.activity/game.py index f1ac957..ab38a35 100644 --- a/Maze.activity/game.py +++ b/Maze.activity/game.py @@ -70,41 +70,67 @@ class MazeGame: WIN_COLOR = (0xff, 0xff, 0x00) def __init__(self, screen): + # note what time it was when we first launched + self.game_start_time = time.time() + xoOwner = presenceService.get_owner() - self.localplayer = Player(xoOwner) - # keep a list of active players, starting empty - self.players = {'xoOwner':self.localplayer} + # keep a list of all local players + self.localplayers = [] + + # start with just one player + player = Player(xoOwner) + self.localplayers.append(player) + # plus some bonus players (all hidden to start with) + self.localplayers.extend(player.bonusPlayers()) + + # keep a dictionary of all remote players, indexed by handle + self.remoteplayers = {} + # keep a list of all players, local and remote, + self.allplayers = [] + self.localplayers self.screen = screen canvas_size = screen.get_size() self.aspectRatio = canvas_size[0] / float(canvas_size[1]) - # start with a small maze - self.start_time = time.time() - self.maze = Maze(int(self.start_time), int(9*self.aspectRatio), 9) + # start with a small maze using a seed that will be different each time you play + self.maze = Maze(int(time.time()), int(9*self.aspectRatio), 9) self.reset() self.frame = 0 self.font = pygame.font.Font(None, 30) # support arrow keys, game pad arrows and game pad buttons - self.upkeys = (pygame.K_UP, pygame.K_KP8, pygame.K_KP9) - self.downkeys = (pygame.K_DOWN, pygame.K_KP2, pygame.K_KP3) - self.leftkeys = (pygame.K_LEFT, pygame.K_KP4, pygame.K_KP7) - self.rightkeys = (pygame.K_RIGHT, pygame.K_KP6, pygame.K_KP1) - self.allkeys = self.upkeys + self.downkeys + self.leftkeys + self.rightkeys + # each set maps to a local player index and a direction + self.arrowkeys = { + # real key: (localplayer index, ideal key) + pygame.K_UP: (0, pygame.K_UP), + pygame.K_DOWN: (0, pygame.K_DOWN), + pygame.K_LEFT: (0, pygame.K_LEFT), + pygame.K_RIGHT: (0, pygame.K_RIGHT), + pygame.K_KP8: (1, pygame.K_UP), + pygame.K_KP2: (1, pygame.K_DOWN), + pygame.K_KP4: (1, pygame.K_LEFT), + pygame.K_KP6: (1, pygame.K_RIGHT), + pygame.K_KP9: (2, pygame.K_UP), + pygame.K_KP3: (2, pygame.K_DOWN), + pygame.K_KP7: (2, pygame.K_LEFT), + pygame.K_KP1: (2, pygame.K_RIGHT) + } + + def game_running_time(self, newelapsed=None): + return int(time.time() - self.game_start_time) def reset(self): """Reset the game state. Everyone starts in the top-left. The goal starts in the bottom-right corner.""" self.running = True - self.start_time = time.time() + self.level_start_time = time.time() self.finish_time = None - for player in self.players.values(): + for player in self.allplayers: player.reset() - self.goal = (self.maze.width-2, self.maze.height-2) self.dirtyRect = None self.dirtyPoints = [] + self.maze.map[self.maze.width-2][self.maze.height-2] = self.maze.GOAL # clear and mark the whole screen as dirty self.screen.fill((0,0,0)) @@ -135,22 +161,22 @@ class MazeGame: self.harder() elif event.key == pygame.K_MINUS: self.easier() - elif event.key in self.upkeys: - self.localplayer.direction=(0,-1) - if len(self.players)>1: - mesh.broadcast("move:%d,%d,%d,%d" % (self.localplayer.position[0], self.localplayer.position[1], self.localplayer.direction[0], self.localplayer.direction[1])) - elif event.key in self.downkeys: - self.localplayer.direction=(0,1) - if len(self.players)>1: - mesh.broadcast("move:%d,%d,%d,%d" % (self.localplayer.position[0], self.localplayer.position[1], self.localplayer.direction[0], self.localplayer.direction[1])) - elif event.key in self.leftkeys: - self.localplayer.direction=(-1,0) - if len(self.players)>1: - mesh.broadcast("move:%d,%d,%d,%d" % (self.localplayer.position[0], self.localplayer.position[1], self.localplayer.direction[0], self.localplayer.direction[1])) - elif event.key in self.rightkeys: - self.localplayer.direction=(1,0) - if len(self.players)>1: - mesh.broadcast("move:%d,%d,%d,%d" % (self.localplayer.position[0], self.localplayer.position[1], self.localplayer.direction[0], self.localplayer.direction[1])) + elif self.arrowkeys.has_key(event.key): + playernum, direction = self.arrowkeys[event.key] + player = self.localplayers[playernum] + player.hidden = False + + if direction == pygame.K_UP: + player.direction=(0,-1) + elif direction == pygame.K_DOWN: + player.direction=(0,1) + elif direction == pygame.K_LEFT: + player.direction=(-1,0) + elif direction == pygame.K_RIGHT: + player.direction=(1,0) + + if len(self.remoteplayers)>0: + mesh.broadcast("move:%s,%d,%d,%d,%d" % (player.nick, player.position[0], player.position[1], player.direction[0], player.direction[1])) elif event.type == pygame.KEYUP: pass elif event.type in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP): @@ -161,65 +187,92 @@ class MazeGame: buddy = mesh.get_buddy(event.handle) if event.handle == mesh.my_handle(): print "Me:", buddy.props.nick, buddy.props.color - del self.players['xoOwner'] - self.players[event.handle] = self.localplayer else: print "Join:", buddy.props.nick, buddy.props.color player = Player(buddy) - self.players[event.handle] = player + self.remoteplayers[event.handle] = player + self.allplayers.append(player) + self.allplayers.extend(player.bonusPlayers()) self.markPointDirty(player.position) # send a test message to the new player mesh.broadcast("Welcome %s" % player.nick) # tell them which maze we are playing, so they can sync up - mesh.send_to(event.handle, "maze:%d,%d,%d" % (self.maze.seed, self.maze.width, self.maze.height)) + mesh.send_to(event.handle, "maze:%d,%d,%d,%d" % (self.game_running_time(), self.maze.seed, self.maze.width, self.maze.height)) + for player in self.localplayers: + if not player.hidden: + mesh.send_to(event.handle, "move:%s,%d,%d,%d,%d" % (player.nick, player.position[0], player.position[1], player.direction[0], player.direction[1])) elif event.type == mesh.PARTICIPANT_REMOVE: - if self.players.has_key(event.handle): - player = self.players[event.handle] + if self.remoteplayers.has_key(event.handle): + player = self.remoteplayers[event.handle] print "Leave:", player.nick self.markPointDirty(player.position) - del self.players[event.handle] + self.allplayers.remove(player) + for bonusplayer in player.bonusPlayers(): + self.markPointDirty(bonusplayer.position) + self.allplayers.remove(bonusplayer) + del self.remoteplayers[event.handle] elif event.type == mesh.MESSAGE_UNI or event.type == mesh.MESSAGE_MULTI: buddy = mesh.get_buddy(event.handle) #print "Message from %s / %s: %s" % (buddy.props.nick, event.handle, event.content) - if self.players.has_key(event.handle): - player = self.players[event.handle] - self.handleMessage(player, event.content) + if event.handle == mesh.my_handle(): + # ignore messages from ourself + pass + elif self.remoteplayers.has_key(event.handle): + player = self.remoteplayers[event.handle] + try: + self.handleMessage(player, event.content) + except: + print "Error handling message: %s\n%s" % (event, sys.exc_info()) else: print "Message from unknown buddy?" else: print "Unknown event:", event def handleMessage(self, player, message): - """Handle a message from a player on the mesh. The messages are: - maze:seed,width,height + """Handle a message from a player on the mesh. + We try to be forward compatible with new versions of Maze by allowing messages to + have extra stuff at the end and ignoring unrecognized messages. + We allow some messages to contain a different nick than the message's source player + to support bonus players on that player's XO. + The valid messages are: + maze:running_time,seed,width,height A player has a differen maze. - The one with the lowest seed # will force all other players to use that maze. - position:x,y - A player has moved to x,y + The one that has been running the longest will force all other players to use that maze. + This way new players will join the existing game properly. + move:nick,x,y,dx,dy + A player's at x,y is now moving in direction dx,dy + finish:nick,elapsed + A player has finished the maze """ # ignore messages from myself - if player == self.localplayer: + if player in self.localplayers: return if message.startswith("move:"): # a player has moved - x,y,dx,dy = message[5:].split(",") + nick,x,y,dx,dy = message[5:].split(",")[:5] + player = player.bonusPlayer(nick) + player.hidden = False self.markPointDirty(player.position) player.position = (int(x),int(y)) player.direction = (int(dx),int(dy)) self.markPointDirty(player.position) elif message.startswith("maze:"): # someone has a different maze than us - seed,width,height = map(lambda x: int(x), message[5:].split(",")) - # is that a different maze than the one we're already playing? - if self.maze.seed>seed: # or self.maze.width!=width or self.maze.height!=height: - # use the smaller seed, so the players who are already playing - # won't have their maps yanked out from under them when someone new joins. + running_time,seed,width,height = map(lambda x: int(x), message[5:].split(",")[:4]) + # is that maze older than the one we're already playing? + # note that we use elapsed time instead of absolute time because + # people's clocks are often set to something totally wrong + if self.game_running_time() < running_time: + # make note of the earlier time that the game really started (before we joined) + self.game_start_time = time.time() - running_time + # use the new seed self.maze = Maze(seed, width, height) self.reset() elif message.startswith("finish:"): # someone finished the maze - elapsed = float(message[7:]) - player.elapsed = elapsed + nick, elapsed = message[7:].split(",")[:2] + player = player.bonusPlayer(nick) + player.elapsed = float(elapsed) self.markPointDirty(player.position) else: # it was something I don't recognize... @@ -261,12 +314,13 @@ class MazeGame: newWidth = int(newHeight * self.aspectRatio) if newWidth % 2 == 0: newWidth -= 1 - # use a smaller seed, so that other players will use this maze also - self.maze = Maze(self.maze.seed-1, newWidth, newHeight) + self.maze = Maze(self.maze.seed+1, newWidth, newHeight) self.reset() # tell everyone which maze we are playing, so they can sync up - if len(self.players)>1: - mesh.broadcast("maze:%d,%d,%d" % (self.maze.seed, self.maze.width, self.maze.height)) + if len(self.remoteplayers)>0: + # but fudge it a little so that we can be sure they'll use our maze + self.game_start_time -= 10 + mesh.broadcast("maze:%d,%d,%d,%d" % (self.game_running_time(), self.maze.seed, self.maze.width, self.maze.height)) def easier(self): """Make a new maze that is easier than the current one.""" @@ -275,38 +329,37 @@ class MazeGame: newWidth = int(newHeight * self.aspectRatio) if newWidth % 2 == 0: newWidth -= 1 - # use a smaller seed, so that other players will use this maze also - self.maze = Maze(self.maze.seed-1, newWidth, newHeight) + self.maze = Maze(self.maze.seed+1, newWidth, newHeight) self.reset() # tell everyone which maze we are playing, so they can sync up - if len(self.players)>1: - mesh.broadcast("maze:%d,%d,%d" % (self.maze.seed, self.maze.width, self.maze.height)) + if len(self.remoteplayers)>0: + # but fudge it a little so that we can be sure they'll use our maze + self.game_start_time -= 10 + mesh.broadcast("maze:%d,%d,%d,%d" % (self.game_running_time(), self.maze.seed, self.maze.width, self.maze.height)) def animate(self): """Animate one frame of action.""" - for player in self.players.values(): + for player in self.allplayers: oldposition = player.position - if oldposition == self.goal: - break newposition = player.animate(self.maze) if oldposition != newposition: self.markPointDirty(oldposition) self.markPointDirty(newposition) - if player == self.localplayer: + if player in self.localplayers: self.maze.map[player.previous[0]][player.previous[1]] = self.maze.SEEN - if newposition == self.goal: - self.finish() + if self.maze.map[newposition[0]][newposition[1]] == self.maze.GOAL: + self.finish(player) - finish_delay = min(2 * len(self.players), 6) + finish_delay = min(2 * len(self.allplayers), 6) if self.finish_time is not None and time.time() > self.finish_time+finish_delay: self.harder() - def finish(self): + def finish(self, player): self.finish_time = time.time() - self.localplayer.elapsed = self.finish_time - self.start_time - if len(self.players)>1: - mesh.broadcast("finish:%.2f" % (self.localplayer.elapsed)) + player.elapsed = self.finish_time - self.level_start_time + if len(self.remoteplayers)>0: + mesh.broadcast("finish:%s,%.2f" % (player.nick, player.elapsed)) def draw(self): """Draw the current state of the game. @@ -317,20 +370,25 @@ class MazeGame: # compute the size of the tiles given the screen size, etc. size = self.screen.get_size() self.tileSize = min(size[0] / self.maze.width, size[1] / self.maze.height) - self.offsetX = (size[0] - self.tileSize * self.maze.width)/2 - self.offsetY = (size[1] - self.tileSize * self.maze.height)/2 + self.bounds = pygame.Rect((size[0] - self.tileSize * self.maze.width)/2, + (size[1] - self.tileSize * self.maze.height)/2, + self.tileSize * self.maze.width, + self.tileSize * self.maze.height) self.outline = int(self.tileSize/5) def drawPoint(x,y): - rect = pygame.Rect(self.offsetX + x*self.tileSize, self.offsetY + y*self.tileSize, self.tileSize, self.tileSize) - if self.maze.map[x][y] == self.maze.EMPTY: + rect = pygame.Rect(self.bounds.x + x*self.tileSize, self.bounds.y + y*self.tileSize, self.tileSize, self.tileSize) + tile = self.maze.map[x][y] + if tile == self.maze.EMPTY: pygame.draw.rect(self.screen, self.EMPTY_COLOR, rect, 0) - elif self.maze.map[x][y] == self.maze.SOLID: + elif tile == self.maze.SOLID: pygame.draw.rect(self.screen, self.SOLID_COLOR, rect, 0) - elif self.maze.map[x][y] == self.maze.SEEN: + elif tile == self.maze.SEEN: pygame.draw.rect(self.screen, self.EMPTY_COLOR, rect, 0) dot = rect.inflate(-self.outline*2, -self.outline*2) pygame.draw.ellipse(self.screen, self.TRAIL_COLOR, dot, 0) + elif tile == self.maze.GOAL: + pygame.draw.rect(self.screen, self.GOAL_COLOR, rect, 0) else: pygame.draw.rect(self.screen, (0xff, 0x00, 0xff), rect, 0) @@ -351,21 +409,13 @@ class MazeGame: for x,y in self.dirtyPoints: drawPoint(x,y) - # draw the goal - rect = self.offsetX+self.goal[0]*self.tileSize, self.offsetY+self.goal[1]*self.tileSize, self.tileSize, self.tileSize - pygame.draw.rect(self.screen, self.GOAL_COLOR, rect, 0) - - # draw all remote players - remotePlayers = list(self.players.values()) - remotePlayers.remove(self.localplayer) - for player in remotePlayers: - self.drawPlayer(player) - - # draw the local player last so he/she will show up on top - self.drawPlayer(self.localplayer) + # draw all players + for player in self.allplayers: + if not player.hidden: + player.draw(self.screen, self.bounds, self.tileSize) # draw the elapsed time for each player that has finished - finishedPlayers = filter(lambda p: p.elapsed is not None, self.players.values()) + finishedPlayers = filter(lambda p: p.elapsed is not None, self.allplayers) finishedPlayers.sort(lambda a,b: cmp(a.elapsed,b.elapsed)) y = 0 for player in finishedPlayers: @@ -385,16 +435,6 @@ class MazeGame: self.dirtyRect = None self.dirtyPoints = [] - def drawPlayer(self, player): - fg, bg = player.colors - posX, posY = player.position - rect = pygame.Rect(self.offsetX+posX*self.tileSize, - self.offsetY+posY*self.tileSize, - self.tileSize, self.tileSize) - pygame.draw.ellipse(self.screen, fg, rect, 0) - dot = rect.inflate(-self.outline, -self.outline) - pygame.draw.ellipse(self.screen, bg, dot, 0) - def main(): """Run a game of Maze.""" #canvas_size = 1024,768-75 diff --git a/Maze.activity/maze.py b/Maze.activity/maze.py index 01b3f35..adc672f 100644 --- a/Maze.activity/maze.py +++ b/Maze.activity/maze.py @@ -26,6 +26,7 @@ class Maze: SOLID = 0 EMPTY = 1 SEEN = 2 + GOAL = 3 def __init__(self, seed, width, height): # use the seed given to us to make a pseudo-random number generator diff --git a/Maze.activity/player.py b/Maze.activity/player.py index 8fea13a..46bf79a 100644 --- a/Maze.activity/player.py +++ b/Maze.activity/player.py @@ -19,18 +19,46 @@ # You should have received a copy of the GNU General Public License # along with Foobar. If not, see <http://www.gnu.org/licenses/>. +from olpcgames.util import get_bundle_path +bundlepath = get_bundle_path() from sugar.graphics.icon import Icon from sugar.graphics.xocolor import XoColor +import pygame +import re, os class Player: - def __init__(self, buddy): + def __init__(self, buddy, shape='circle'): + self.buddy = buddy self.nick = buddy.props.nick colors = buddy.props.color.split(",") def string2Color(str): return (int(str[1:3],16), int(str[3:5],16), int(str[5:7],16)) self.colors = map(string2Color, colors) + self.shape = shape + self.hidden = False + self.bonusplayers = None self.reset() + def draw(self, screen, bounds, size): + rect = pygame.Rect(bounds.x+self.position[0]*size, bounds.y+self.position[1]*size, size, size) + border = size / 10. + center = rect.inflate(-border*2, -border*2) + fg, bg = self.colors + if self.shape == 'circle': + pygame.draw.ellipse(screen, fg, rect, 0) + pygame.draw.ellipse(screen, bg, center, 0) + elif self.shape == 'square': + pygame.draw.rect(screen, fg, rect, 0) + pygame.draw.rect(screen, bg, center, 0) + elif self.shape == 'triangle': + rect = rect.inflate(-1,-1) + pts = [rect.bottomleft, rect.midtop, rect.bottomright] + pygame.draw.polygon(screen, fg, pts, 0) + pts = [(pts[0][0]+border*1.394, pts[0][1]-border), + (pts[1][0], pts[1][1]+border*2.236), + (pts[2][0]-border*1.394, pts[2][1]-border)] + pygame.draw.polygon(screen, bg, pts, 0) + def reset(self): self.direction = (0,0) self.position = (1,1) @@ -39,7 +67,7 @@ class Player: def animate(self, maze): # if the player finished the maze, then don't move - if self.elapsed is not None: + if maze.map[self.position[0]][self.position[1]] == maze.GOAL: self.direction=(0,0) if self.direction == (0,0): return self.position @@ -81,3 +109,24 @@ class Player: else: self.direction = (0,0) + def bonusPlayers(self): + if self.bonusplayers is None: + self.bonusplayers = [] + self.bonusplayers.append(Player(self.buddy,'square')) + self.bonusplayers.append(Player(self.buddy,'triangle')) + + count = 2 + for player in self.bonusplayers: + player.nick = self.nick + "-%d" % count + player.hidden = True + count += 1 + + return self.bonusplayers + + def bonusPlayer(self, nick): + if nick == self.nick: + return self + for bonusplayer in self.bonusPlayers(): + if bonusplayer.nick == nick: + return bonusplayer + |