diff --git a/source/main.py b/source/main.py new file mode 100644 index 0000000..92fcdc1 --- /dev/null +++ b/source/main.py @@ -0,0 +1,14 @@ +__author__ = 'marble_xu' + +from . import tool +from . import constants as c +from .state import mainmenu, screen, level + +def main(): + game = tool.Control() + state_dict = {c.MAIN_MENU: mainmenu.Menu(), + c.GAME_VICTORY: screen.GameVictoryScreen(), + c.GAME_LOSE: screen.GameLoseScreen(), + c.LEVEL: level.Level()} + game.setup_states(state_dict, c.MAIN_MENU) + game.main() \ No newline at end of file diff --git a/source/state/__init__.py b/source/state/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/state/level.py b/source/state/level.py new file mode 100644 index 0000000..133cc28 --- /dev/null +++ b/source/state/level.py @@ -0,0 +1,549 @@ +__author__ = 'marble_xu' + +import os +import json +import pygame as pg +from .. import tool +from .. import constants as c +from ..component import map, plant, zombie, menubar + +class Level(tool.State): + def __init__(self): + tool.State.__init__(self) + + def startup(self, current_time, persist): + self.game_info = persist + self.persist = self.game_info + self.game_info[c.CURRENT_TIME] = current_time + self.map_y_len = c.GRID_Y_LEN + self.map = map.Map(c.GRID_X_LEN, self.map_y_len) + + self.loadMap() + self.setupBackground() + self.initState() + + def loadMap(self): + map_file = 'level_' + str(self.game_info[c.LEVEL_NUM]) + '.json' + file_path = os.path.join('source', 'data', 'map', map_file) + f = open(file_path) + self.map_data = json.load(f) + f.close() + + def setupBackground(self): + img_index = self.map_data[c.BACKGROUND_TYPE] + self.background_type = img_index + self.background = tool.GFX[c.BACKGROUND_NAME][img_index] + self.bg_rect = self.background.get_rect() + + self.level = pg.Surface((self.bg_rect.w, self.bg_rect.h)).convert() + self.viewport = tool.SCREEN.get_rect(bottom=self.bg_rect.bottom) + self.viewport.x += c.BACKGROUND_OFFSET_X + + def setupGroups(self): + self.sun_group = pg.sprite.Group() + self.head_group = pg.sprite.Group() + + self.plant_groups = [] + self.zombie_groups = [] + self.hypno_zombie_groups = [] #zombies who are hypno after eating hypnoshroom + self.bullet_groups = [] + for i in range(self.map_y_len): + self.plant_groups.append(pg.sprite.Group()) + self.zombie_groups.append(pg.sprite.Group()) + self.hypno_zombie_groups.append(pg.sprite.Group()) + self.bullet_groups.append(pg.sprite.Group()) + + def setupZombies(self): + def takeTime(element): + return element[0] + + self.zombie_list = [] + for data in self.map_data[c.ZOMBIE_LIST]: + self.zombie_list.append((data['time'], data['name'], data['map_y'])) + self.zombie_start_time = 0 + self.zombie_list.sort(key=takeTime) + + def setupCars(self): + self.cars = [] + for i in range(self.map_y_len): + _, y = self.map.getMapGridPos(0, i) + self.cars.append(plant.Car(-25, y+20, i)) + + def update(self, surface, current_time, mouse_pos, mouse_click): + self.current_time = self.game_info[c.CURRENT_TIME] = current_time + if self.state == c.CHOOSE: + self.choose(mouse_pos, mouse_click) + elif self.state == c.PLAY: + self.play(mouse_pos, mouse_click) + + self.draw(surface) + + def initBowlingMap(self): + print('initBowlingMap') + for x in range(3, self.map.width): + for y in range(self.map.height): + self.map.setMapGridType(x, y, c.MAP_EXIST) + + def initState(self): + if c.CHOOSEBAR_TYPE in self.map_data: + self.bar_type = self.map_data[c.CHOOSEBAR_TYPE] + else: + self.bar_type = c.CHOOSEBAR_STATIC + + if self.bar_type == c.CHOOSEBAR_STATIC: + self.initChoose() + else: + card_pool = menubar.getCardPool(self.map_data[c.CARD_POOL]) + self.initPlay(card_pool) + if self.bar_type == c.CHOSSEBAR_BOWLING: + self.initBowlingMap() + + def initChoose(self): + self.state = c.CHOOSE + self.panel = menubar.Panel(menubar.all_card_list, self.map_data[c.INIT_SUN_NAME]) + + def choose(self, mouse_pos, mouse_click): + if mouse_pos and mouse_click[0]: + self.panel.checkCardClick(mouse_pos) + if self.panel.checkStartButtonClick(mouse_pos): + self.initPlay(self.panel.getSelectedCards()) + + def initPlay(self, card_list): + self.state = c.PLAY + if self.bar_type == c.CHOOSEBAR_STATIC: + self.menubar = menubar.MenuBar(card_list, self.map_data[c.INIT_SUN_NAME]) + else: + self.menubar = menubar.MoveBar(card_list) + self.drag_plant = False + self.hint_image = None + self.hint_plant = False + if self.background_type == c.BACKGROUND_DAY and self.bar_type == c.CHOOSEBAR_STATIC: + self.produce_sun = True + else: + self.produce_sun = False + self.sun_timer = self.current_time + + self.removeMouseImage() + self.setupGroups() + self.setupZombies() + self.setupCars() + + def play(self, mouse_pos, mouse_click): + if self.zombie_start_time == 0: + self.zombie_start_time = self.current_time + elif len(self.zombie_list) > 0: + data = self.zombie_list[0] + if data[0] <= (self.current_time - self.zombie_start_time): + self.createZombie(data[1], data[2]) + self.zombie_list.remove(data) + + for i in range(self.map_y_len): + self.bullet_groups[i].update(self.game_info) + self.plant_groups[i].update(self.game_info) + self.zombie_groups[i].update(self.game_info) + self.hypno_zombie_groups[i].update(self.game_info) + for zombie in self.hypno_zombie_groups[i]: + if zombie.rect.x > c.SCREEN_WIDTH: + zombie.kill() + + self.head_group.update(self.game_info) + self.sun_group.update(self.game_info) + + if not self.drag_plant and mouse_pos and mouse_click[0]: + result = self.menubar.checkCardClick(mouse_pos) + if result: + self.setupMouseImage(result[0], result[1]) + elif self.drag_plant: + if mouse_click[1]: + self.removeMouseImage() + elif mouse_click[0]: + if self.menubar.checkMenuBarClick(mouse_pos): + self.removeMouseImage() + else: + self.addPlant() + elif mouse_pos is None: + self.setupHintImage() + + if self.produce_sun: + if(self.current_time - self.sun_timer) > c.PRODUCE_SUN_INTERVAL: + self.sun_timer = self.current_time + map_x, map_y = self.map.getRandomMapIndex() + x, y = self.map.getMapGridPos(map_x, map_y) + self.sun_group.add(plant.Sun(x, 0, x, y)) + if not self.drag_plant and mouse_pos and mouse_click[0]: + for sun in self.sun_group: + if sun.checkCollision(mouse_pos[0], mouse_pos[1]): + self.menubar.increaseSunValue(sun.sun_value) + + for car in self.cars: + car.update(self.game_info) + + self.menubar.update(self.current_time) + + self.checkBulletCollisions() + self.checkZombieCollisions() + self.checkPlants() + self.checkCarCollisions() + self.checkGameState() + + def createZombie(self, name, map_y): + x, y = self.map.getMapGridPos(0, map_y) + if name == c.NORMAL_ZOMBIE: + self.zombie_groups[map_y].add(zombie.NormalZombie(c.ZOMBIE_START_X, y, self.head_group)) + elif name == c.CONEHEAD_ZOMBIE: + self.zombie_groups[map_y].add(zombie.ConeHeadZombie(c.ZOMBIE_START_X, y, self.head_group)) + elif name == c.BUCKETHEAD_ZOMBIE: + self.zombie_groups[map_y].add(zombie.BucketHeadZombie(c.ZOMBIE_START_X, y, self.head_group)) + elif name == c.FLAG_ZOMBIE: + self.zombie_groups[map_y].add(zombie.FlagZombie(c.ZOMBIE_START_X, y, self.head_group)) + elif name == c.NEWSPAPER_ZOMBIE: + self.zombie_groups[map_y].add(zombie.NewspaperZombie(c.ZOMBIE_START_X, y, self.head_group)) + + def canSeedPlant(self): + x, y = pg.mouse.get_pos() + return self.map.showPlant(x, y) + + def addPlant(self): + pos = self.canSeedPlant() + if pos is None: + return + + if self.hint_image is None: + self.setupHintImage() + x, y = self.hint_rect.centerx, self.hint_rect.bottom + map_x, map_y = self.map.getMapIndex(x, y) + if self.plant_name == c.SUNFLOWER: + new_plant = plant.SunFlower(x, y, self.sun_group) + elif self.plant_name == c.PEASHOOTER: + new_plant = plant.PeaShooter(x, y, self.bullet_groups[map_y]) + elif self.plant_name == c.SNOWPEASHOOTER: + new_plant = plant.SnowPeaShooter(x, y, self.bullet_groups[map_y]) + elif self.plant_name == c.WALLNUT: + new_plant = plant.WallNut(x, y) + elif self.plant_name == c.CHERRYBOMB: + new_plant = plant.CherryBomb(x, y) + elif self.plant_name == c.THREEPEASHOOTER: + new_plant = plant.ThreePeaShooter(x, y, self.bullet_groups, map_y) + elif self.plant_name == c.REPEATERPEA: + new_plant = plant.RepeaterPea(x, y, self.bullet_groups[map_y]) + elif self.plant_name == c.CHOMPER: + new_plant = plant.Chomper(x, y) + elif self.plant_name == c.PUFFSHROOM: + new_plant = plant.PuffShroom(x, y, self.bullet_groups[map_y]) + elif self.plant_name == c.POTATOMINE: + new_plant = plant.PotatoMine(x, y) + elif self.plant_name == c.SQUASH: + new_plant = plant.Squash(x, y) + elif self.plant_name == c.SPIKEWEED: + new_plant = plant.Spikeweed(x, y) + elif self.plant_name == c.JALAPENO: + new_plant = plant.Jalapeno(x, y) + elif self.plant_name == c.SCAREDYSHROOM: + new_plant = plant.ScaredyShroom(x, y, self.bullet_groups[map_y]) + elif self.plant_name == c.SUNSHROOM: + new_plant = plant.SunShroom(x, y, self.sun_group) + elif self.plant_name == c.ICESHROOM: + new_plant = plant.IceShroom(x, y) + elif self.plant_name == c.HYPNOSHROOM: + new_plant = plant.HypnoShroom(x, y) + elif self.plant_name == c.WALLNUTBOWLING: + new_plant = plant.WallNutBowling(x, y, map_y, self) + elif self.plant_name == c.REDWALLNUTBOWLING: + new_plant = plant.RedWallNutBowling(x, y) + + if new_plant.can_sleep and self.background_type == c.BACKGROUND_DAY: + new_plant.setSleep() + self.plant_groups[map_y].add(new_plant) + if self.bar_type == c.CHOOSEBAR_STATIC: + self.menubar.decreaseSunValue(self.select_plant.sun_cost) + self.menubar.setCardFrozenTime(self.plant_name) + else: + self.menubar.deleateCard(self.select_plant) + + if self.bar_type != c.CHOSSEBAR_BOWLING: + self.map.setMapGridType(map_x, map_y, c.MAP_EXIST) + self.removeMouseImage() + #print('addPlant map[%d,%d], grid pos[%d, %d] pos[%d, %d]' % (map_x, map_y, x, y, pos[0], pos[1])) + + def setupHintImage(self): + pos = self.canSeedPlant() + if pos and self.mouse_image: + if (self.hint_image and pos[0] == self.hint_rect.x and + pos[1] == self.hint_rect.y): + return + width, height = self.mouse_rect.w, self.mouse_rect.h + image = pg.Surface([width, height]) + image.blit(self.mouse_image, (0, 0), (0, 0, width, height)) + image.set_colorkey(c.BLACK) + image.set_alpha(128) + self.hint_image = image + self.hint_rect = image.get_rect() + self.hint_rect.centerx = pos[0] + self.hint_rect.bottom = pos[1] + self.hint_plant = True + else: + self.hint_plant = False + + def setupMouseImage(self, plant_name, select_plant): + frame_list = tool.GFX[plant_name] + if plant_name in tool.PLANT_RECT: + data = tool.PLANT_RECT[plant_name] + x, y, width, height = data['x'], data['y'], data['width'], data['height'] + else: + x, y = 0, 0 + rect = frame_list[0].get_rect() + width, height = rect.w, rect.h + + if (plant_name == c.POTATOMINE or plant_name == c.SQUASH or + plant_name == c.SPIKEWEED or plant_name == c.JALAPENO or + plant_name == c.SCAREDYSHROOM or plant_name == c.SUNSHROOM or + plant_name == c.ICESHROOM or plant_name == c.HYPNOSHROOM or + plant_name == c.WALLNUTBOWLING or plant_name == c.REDWALLNUTBOWLING): + color = c.WHITE + else: + color = c.BLACK + self.mouse_image = tool.get_image(frame_list[0], x, y, width, height, color, 1) + self.mouse_rect = self.mouse_image.get_rect() + pg.mouse.set_visible(False) + self.drag_plant = True + self.plant_name = plant_name + self.select_plant = select_plant + + def removeMouseImage(self): + pg.mouse.set_visible(True) + self.drag_plant = False + self.mouse_image = None + self.hint_image = None + self.hint_plant = False + + def checkBulletCollisions(self): + collided_func = pg.sprite.collide_circle_ratio(0.7) + for i in range(self.map_y_len): + for bullet in self.bullet_groups[i]: + if bullet.state == c.FLY: + zombie = pg.sprite.spritecollideany(bullet, self.zombie_groups[i], collided_func) + if zombie and zombie.state != c.DIE: + zombie.setDamage(bullet.damage, bullet.ice) + bullet.setExplode() + + def checkZombieCollisions(self): + if self.bar_type == c.CHOSSEBAR_BOWLING: + ratio = 0.6 + else: + ratio = 0.7 + collided_func = pg.sprite.collide_circle_ratio(ratio) + for i in range(self.map_y_len): + hypo_zombies = [] + for zombie in self.zombie_groups[i]: + if zombie.state != c.WALK: + continue + plant = pg.sprite.spritecollideany(zombie, self.plant_groups[i], collided_func) + if plant: + if plant.name == c.WALLNUTBOWLING: + if plant.canHit(i): + zombie.setDamage(c.WALLNUT_BOWLING_DAMAGE) + plant.changeDirection(i) + elif plant.name == c.REDWALLNUTBOWLING: + if plant.state == c.IDLE: + plant.setAttack() + elif plant.name != c.SPIKEWEED: + zombie.setAttack(plant) + + for hypno_zombie in self.hypno_zombie_groups[i]: + if hypno_zombie.health <= 0: + continue + zombie_list = pg.sprite.spritecollide(hypno_zombie, + self.zombie_groups[i], False,collided_func) + for zombie in zombie_list: + if zombie.state == c.DIE: + continue + if zombie.state == c.WALK: + zombie.setAttack(hypno_zombie, False) + if hypno_zombie.state == c.WALK: + hypno_zombie.setAttack(zombie, False) + + def checkCarCollisions(self): + collided_func = pg.sprite.collide_circle_ratio(0.8) + for car in self.cars: + zombies = pg.sprite.spritecollide(car, self.zombie_groups[car.map_y], False, collided_func) + for zombie in zombies: + if zombie and zombie.state != c.DIE: + car.setWalk() + zombie.setDie() + if car.dead: + self.cars.remove(car) + + def boomZombies(self, x, map_y, y_range, x_range): + for i in range(self.map_y_len): + if abs(i - map_y) > y_range: + continue + for zombie in self.zombie_groups[i]: + if abs(zombie.rect.centerx - x) <= x_range: + zombie.setBoomDie() + + def freezeZombies(self, plant): + for i in range(self.map_y_len): + for zombie in self.zombie_groups[i]: + if zombie.rect.centerx < c.SCREEN_WIDTH: + zombie.setFreeze(plant.trap_frames[0]) + + def killPlant(self, plant): + x, y = plant.getPosition() + map_x, map_y = self.map.getMapIndex(x, y) + if self.bar_type != c.CHOSSEBAR_BOWLING: + self.map.setMapGridType(map_x, map_y, c.MAP_EMPTY) + if (plant.name == c.CHERRYBOMB or plant.name == c.JALAPENO or + (plant.name == c.POTATOMINE and not plant.is_init) or + plant.name == c.REDWALLNUTBOWLING): + self.boomZombies(plant.rect.centerx, map_y, plant.explode_y_range, + plant.explode_x_range) + elif plant.name == c.ICESHROOM and plant.state != c.SLEEP: + self.freezeZombies(plant) + elif plant.name == c.HYPNOSHROOM and plant.state != c.SLEEP: + zombie = plant.kill_zombie + zombie.setHypno() + _, map_y = self.map.getMapIndex(zombie.rect.centerx, zombie.rect.bottom) + self.zombie_groups[map_y].remove(zombie) + self.hypno_zombie_groups[map_y].add(zombie) + plant.kill() + + def checkPlant(self, plant, i): + zombie_len = len(self.zombie_groups[i]) + if plant.name == c.THREEPEASHOOTER: + if plant.state == c.IDLE: + if zombie_len > 0: + plant.setAttack() + elif (i-1) >= 0 and len(self.zombie_groups[i-1]) > 0: + plant.setAttack() + elif (i+1) < self.map_y_len and len(self.zombie_groups[i+1]) > 0: + plant.setAttack() + elif plant.state == c.ATTACK: + if zombie_len > 0: + pass + elif (i-1) >= 0 and len(self.zombie_groups[i-1]) > 0: + pass + elif (i+1) < self.map_y_len and len(self.zombie_groups[i+1]) > 0: + pass + else: + plant.setIdle() + elif plant.name == c.CHOMPER: + for zombie in self.zombie_groups[i]: + if plant.canAttack(zombie): + plant.setAttack(zombie, self.zombie_groups[i]) + break + elif plant.name == c.POTATOMINE: + for zombie in self.zombie_groups[i]: + if plant.canAttack(zombie): + plant.setAttack() + break + elif plant.name == c.SQUASH: + for zombie in self.zombie_groups[i]: + if plant.canAttack(zombie): + plant.setAttack(zombie, self.zombie_groups[i]) + break + elif plant.name == c.SPIKEWEED: + can_attack = False + for zombie in self.zombie_groups[i]: + if plant.canAttack(zombie): + can_attack = True + break + if plant.state == c.IDLE and can_attack: + plant.setAttack(self.zombie_groups[i]) + elif plant.state == c.ATTACK and not can_attack: + plant.setIdle() + elif plant.name == c.SCAREDYSHROOM: + need_cry = False + can_attack = False + for zombie in self.zombie_groups[i]: + if plant.needCry(zombie): + need_cry = True + break + elif plant.canAttack(zombie): + can_attack = True + if need_cry: + if plant.state != c.CRY: + plant.setCry() + elif can_attack: + if plant.state != c.ATTACK: + plant.setAttack() + elif plant.state != c.IDLE: + plant.setIdle() + elif(plant.name == c.WALLNUTBOWLING or + plant.name == c.REDWALLNUTBOWLING): + pass + else: + can_attack = False + if (plant.state == c.IDLE and zombie_len > 0): + for zombie in self.zombie_groups[i]: + if plant.canAttack(zombie): + can_attack = True + break + if plant.state == c.IDLE and can_attack: + plant.setAttack() + elif (plant.state == c.ATTACK and not can_attack): + plant.setIdle() + + def checkPlants(self): + for i in range(self.map_y_len): + for plant in self.plant_groups[i]: + if plant.state != c.SLEEP: + self.checkPlant(plant, i) + if plant.health <= 0: + self.killPlant(plant) + + def checkVictory(self): + if len(self.zombie_list) > 0: + return False + for i in range(self.map_y_len): + if len(self.zombie_groups[i]) > 0: + return False + return True + + def checkLose(self): + for i in range(self.map_y_len): + for zombie in self.zombie_groups[i]: + if zombie.rect.right < 0: + return True + return False + + def checkGameState(self): + if self.checkVictory(): + self.game_info[c.LEVEL_NUM] += 1 + self.next = c.GAME_VICTORY + self.done = True + elif self.checkLose(): + self.next = c.GAME_LOSE + self.done = True + + def drawMouseShow(self, surface): + if self.hint_plant: + surface.blit(self.hint_image, self.hint_rect) + x, y = pg.mouse.get_pos() + self.mouse_rect.centerx = x + self.mouse_rect.centery = y + surface.blit(self.mouse_image, self.mouse_rect) + + def drawZombieFreezeTrap(self, i, surface): + for zombie in self.zombie_groups[i]: + zombie.drawFreezeTrap(surface) + + def draw(self, surface): + self.level.blit(self.background, self.viewport, self.viewport) + surface.blit(self.level, (0,0), self.viewport) + if self.state == c.CHOOSE: + self.panel.draw(surface) + elif self.state == c.PLAY: + self.menubar.draw(surface) + for i in range(self.map_y_len): + self.plant_groups[i].draw(surface) + self.zombie_groups[i].draw(surface) + self.hypno_zombie_groups[i].draw(surface) + self.bullet_groups[i].draw(surface) + self.drawZombieFreezeTrap(i, surface) + for car in self.cars: + car.draw(surface) + self.head_group.draw(surface) + self.sun_group.draw(surface) + + if self.drag_plant: + self.drawMouseShow(surface) \ No newline at end of file diff --git a/source/state/mainmenu.py b/source/state/mainmenu.py new file mode 100644 index 0000000..6ac345f --- /dev/null +++ b/source/state/mainmenu.py @@ -0,0 +1,69 @@ +__author__ = 'marble_xu' + +import pygame as pg +from .. import tool +from .. import constants as c + +class Menu(tool.State): + def __init__(self): + tool.State.__init__(self) + + def startup(self, current_time, persist): + self.next = c.LEVEL + self.persist = persist + self.game_info = persist + + self.setupBackground() + self.setupOption() + + def setupBackground(self): + frame_rect = [80, 0, 800, 600] + self.bg_image = tool.get_image(tool.GFX[c.MAIN_MENU_IMAGE], *frame_rect) + self.bg_rect = self.bg_image.get_rect() + self.bg_rect.x = 0 + self.bg_rect.y = 0 + + def setupOption(self): + self.option_frames = [] + frame_names = [c.OPTION_ADVENTURE + '_0', c.OPTION_ADVENTURE + '_1'] + frame_rect = [0, 0, 165, 77] + + for name in frame_names: + self.option_frames.append(tool.get_image(tool.GFX[name], *frame_rect, c.BLACK, 1.7)) + + self.option_frame_index = 0 + self.option_image = self.option_frames[self.option_frame_index] + self.option_rect = self.option_image.get_rect() + self.option_rect.x = 435 + self.option_rect.y = 75 + + self.option_start = 0 + self.option_timer = 0 + self.option_clicked = False + + def checkOptionClick(self, mouse_pos): + x, y = mouse_pos + if(x >= self.option_rect.x and x <= self.option_rect.right and + y >= self.option_rect.y and y <= self.option_rect.bottom): + self.option_clicked = True + self.option_timer = self.option_start = self.current_time + return False + + def update(self, surface, current_time, mouse_pos, mouse_click): + self.current_time = self.game_info[c.CURRENT_TIME] = current_time + + if not self.option_clicked: + if mouse_pos: + self.checkOptionClick(mouse_pos) + else: + if(self.current_time - self.option_timer) > 200: + self.option_frame_index += 1 + if self.option_frame_index >= 2: + self.option_frame_index = 0 + self.option_timer = self.current_time + self.option_image = self.option_frames[self.option_frame_index] + if(self.current_time - self.option_start) > 1300: + self.done = True + + surface.blit(self.bg_image, self.bg_rect) + surface.blit(self.option_image, self.option_rect) \ No newline at end of file diff --git a/source/state/screen.py b/source/state/screen.py new file mode 100644 index 0000000..a624460 --- /dev/null +++ b/source/state/screen.py @@ -0,0 +1,59 @@ +__author__ = 'marble_xu' + +import pygame as pg +from .. import tool +from .. import constants as c + +class Screen(tool.State): + def __init__(self): + tool.State.__init__(self) + self.end_time = 3000 + + def startup(self, current_time, persist): + self.start_time = current_time + self.next = c.LEVEL + self.persist = persist + self.game_info = persist + name = self.getImageName() + self.setupImage(name) + self.next = self.set_next_state() + + def getImageName(self): + pass + + def set_next_state(self): + pass + + def setupImage(self, name): + frame_rect = [0, 0, 800, 600] + self.image = tool.get_image(tool.GFX[name], *frame_rect) + self.rect = self.image.get_rect() + self.rect.x = 0 + self.rect.y = 0 + + def update(self, surface, current_time, mouse_pos, mouse_click): + if(current_time - self.start_time) < self.end_time: + surface.fill(c.WHITE) + surface.blit(self.image, self.rect) + else: + self.done = True + +class GameVictoryScreen(Screen): + def __init__(self): + Screen.__init__(self) + + def getImageName(self): + return c.GAME_VICTORY_IMAGE + + def set_next_state(self): + return c.LEVEL + +class GameLoseScreen(Screen): + def __init__(self): + Screen.__init__(self) + + def getImageName(self): + return c.GAME_LOOSE_IMAGE + + def set_next_state(self): + return c.MAIN_MENU \ No newline at end of file diff --git a/source/tool.py b/source/tool.py new file mode 100644 index 0000000..1a319c8 --- /dev/null +++ b/source/tool.py @@ -0,0 +1,176 @@ +__author__ = 'marble_xu' + +import os +import json +from abc import abstractmethod +import pygame as pg +from . import constants as c + +class State(): + def __init__(self): + self.start_time = 0.0 + self.current_time = 0.0 + self.done = False + self.next = None + self.persist = {} + + @abstractmethod + def startup(self, current_time, persist): + '''abstract method''' + + def cleanup(self): + self.done = False + return self.persist + + @abstractmethod + def update(self, surface, keys, current_time): + '''abstract method''' + +class Control(): + def __init__(self): + self.screen = pg.display.get_surface() + self.done = False + self.clock = pg.time.Clock() + self.fps = 60 + self.keys = pg.key.get_pressed() + self.mouse_pos = None + self.mouse_click = [False, False] # value:[left mouse click, right mouse click] + self.current_time = 0.0 + self.state_dict = {} + self.state_name = None + self.state = None + self.game_info = {c.CURRENT_TIME:0.0, + c.LEVEL_NUM:c.START_LEVEL_NUM} + + def setup_states(self, state_dict, start_state): + self.state_dict = state_dict + self.state_name = start_state + self.state = self.state_dict[self.state_name] + self.state.startup(self.current_time, self.game_info) + + def update(self): + self.current_time = pg.time.get_ticks() + if self.state.done: + self.flip_state() + self.state.update(self.screen, self.current_time, self.mouse_pos, self.mouse_click) + self.mouse_pos = None + self.mouse_click[0] = False + self.mouse_click[1] = False + + def flip_state(self): + previous, self.state_name = self.state_name, self.state.next + persist = self.state.cleanup() + self.state = self.state_dict[self.state_name] + self.state.startup(self.current_time, persist) + + def event_loop(self): + for event in pg.event.get(): + if event.type == pg.QUIT: + self.done = True + elif event.type == pg.KEYDOWN: + self.keys = pg.key.get_pressed() + elif event.type == pg.KEYUP: + self.keys = pg.key.get_pressed() + elif event.type == pg.MOUSEBUTTONDOWN: + self.mouse_pos = pg.mouse.get_pos() + self.mouse_click[0], _, self.mouse_click[1] = pg.mouse.get_pressed() + print('pos:', self.mouse_pos, ' mouse:', self.mouse_click) + + def main(self): + while not self.done: + self.event_loop() + self.update() + pg.display.update() + self.clock.tick(self.fps) + print('game over') + +def get_image(sheet, x, y, width, height, colorkey=c.BLACK, scale=1): + image = pg.Surface([width, height]) + rect = image.get_rect() + + image.blit(sheet, (0, 0), (x, y, width, height)) + image.set_colorkey(colorkey) + image = pg.transform.scale(image, + (int(rect.width*scale), + int(rect.height*scale))) + return image + +def load_image_frames(directory, image_name, colorkey, accept): + frame_list = [] + tmp = {} + # image_name is "Peashooter", pic name is 'Peashooter_1', get the index 1 + index_start = len(image_name) + 1 + frame_num = 0; + for pic in os.listdir(directory): + name, ext = os.path.splitext(pic) + if ext.lower() in accept: + index = int(name[index_start:]) + img = pg.image.load(os.path.join(directory, pic)) + if img.get_alpha(): + img = img.convert_alpha() + else: + img = img.convert() + img.set_colorkey(colorkey) + tmp[index]= img + frame_num += 1 + + for i in range(frame_num): + frame_list.append(tmp[i]) + return frame_list + +def load_all_gfx(directory, colorkey=c.WHITE, accept=('.png', '.jpg', '.bmp', '.gif')): + graphics = {} + for name1 in os.listdir(directory): + # subfolders under the folder resources\graphics + dir1 = os.path.join(directory, name1) + if os.path.isdir(dir1): + for name2 in os.listdir(dir1): + dir2 = os.path.join(dir1, name2) + if os.path.isdir(dir2): + # e.g. subfolders under the folder resources\graphics\Zombies + for name3 in os.listdir(dir2): + dir3 = os.path.join(dir2, name3) + # e.g. subfolders or pics under the folder resources\graphics\Zombies\ConeheadZombie + if os.path.isdir(dir3): + # e.g. it's the folder resources\graphics\Zombies\ConeheadZombie\ConeheadZombieAttack + image_name, _ = os.path.splitext(name3) + graphics[image_name] = load_image_frames(dir3, image_name, colorkey, accept) + else: + # e.g. pics under the folder resources\graphics\Plants\Peashooter + image_name, _ = os.path.splitext(name2) + graphics[image_name] = load_image_frames(dir2, image_name, colorkey, accept) + break + else: + # e.g. pics under the folder resources\graphics\Screen + name, ext = os.path.splitext(name2) + if ext.lower() in accept: + img = pg.image.load(dir2) + if img.get_alpha(): + img = img.convert_alpha() + else: + img = img.convert() + img.set_colorkey(colorkey) + graphics[name] = img + return graphics + +def loadZombieImageRect(): + file_path = os.path.join('source', 'data', 'entity', 'zombie.json') + f = open(file_path) + data = json.load(f) + f.close() + return data[c.ZOMBIE_IMAGE_RECT] + +def loadPlantImageRect(): + file_path = os.path.join('source', 'data', 'entity', 'plant.json') + f = open(file_path) + data = json.load(f) + f.close() + return data[c.PLANT_IMAGE_RECT] + +pg.init() +pg.display.set_caption(c.ORIGINAL_CAPTION) +SCREEN = pg.display.set_mode(c.SCREEN_SIZE) + +GFX = load_all_gfx(os.path.join("resources","graphics")) +ZOMBIE_RECT = loadZombieImageRect() +PLANT_RECT = loadPlantImageRect()