view gamelib/gamegui.py @ 249:8c237a830efe

merge
author Rizmari Versfeld <rizziepit@gmail.com>
date Sun, 13 May 2012 00:51:29 +0200
parents 25ea20b9803c 235b1faf590e
children c5fdfa96cfb2
line wrap: on
line source

# -*- coding: utf-8 -*-
# vim:fileencoding=utf-8 ai ts=4 sts=4 et sw=4

"""Gui for the actual game"""

try:
    import simplejson
    json = simplejson
except ImportError:
    import json


from gamelib.data import load_image
from gamelib.game_base import get_save_filename
from gamelib.gui_base import (Window, TextLabel, TextBox, font_small,
        font_medium, font_large)
from gamelib.gui import BigButton, ImageDrawable, IconTextButton
from gamelib.engine import PopWindow, AddWindow, GameOver
from gamelib.constants import WIDTH, HEIGHT, FAILURE, SUCCESS, GAME_WIN, INFO
from gamelib.gamestate import Game


def _lookup_reputation(rep):
    """Turn reputation in a nice string"""

    if rep < 0:
        return 'Mindless Thug'
    if rep < 10:
        return 'Fringe Lunatic'
    if rep < 20:
        return 'Batty Experimenter'
    if rep < 50:
        return 'Demented Analyst'
    if rep < 70:
        return 'Deranged Researcher'
    if rep < 150:
        return 'Mad Scientist'
    return 'Major Threat to World Peace'


class PopWindowButton(BigButton):

    def __init__(self, pos, text):
        super(PopWindowButton, self).__init__(pos, text)

    def on_click(self):
        PopWindow.post()


class ExitGameButton(PopWindowButton):
    def __init__(self):
        super(ExitGameButton, self).__init__((WIDTH - 128, HEIGHT - 60),
                'Exit')


class EndTurnButton(BigButton):

    def __init__(self, parent):
        super(EndTurnButton, self).__init__(((WIDTH - 128), 0), 'End Turn')
        self.parent = parent

    def on_click(self):
        self.parent.end_turn()


class NextTurnButton(PopWindowButton):
    def __init__(self):
        super(NextTurnButton, self).__init__(((WIDTH - 128), 0), 'Next Turn')


class AssignButton(PopWindowButton):
    def __init__(self, parent):
        super(AssignButton, self).__init__(((WIDTH - 128), 0),
                'Assign Scheme')
        self.parent = parent

    def on_click(self):
        self.parent.accept()
        super(AssignButton, self).on_click()


class CancelButton(PopWindowButton):
    def __init__(self, parent):
        super(CancelButton, self).__init__(((WIDTH - 256), 0),
                'Cancel Scheme')
        self.parent = parent

    def on_click(self):
        self.parent.cancel()
        super(CancelButton, self).on_click()


class ResetButton(BigButton):

    def __init__(self, parent, text='Clear calendars'):
        super(ResetButton, self).__init__((10, 0), text)
        self.parent = parent

    def on_click(self):
        self.parent.do_reset()


class SwitchWinButton(BigButton):

    def __init__(self, pos, text, new_window):
        super(SwitchWinButton, self).__init__(pos, text)
        self.new_window = new_window

    def on_click(self):
        PopWindow.post()
        # Refresh state
        self.new_window.update()
        AddWindow.post(self.new_window)


class ScienceWidget(IconTextButton):

    def __init__(self, science, pos, parent):
        self.science = science
        self.points = 0
        self.parent = parent
        super(ScienceWidget, self).__init__(
            pos, science.get_image_name(), self.make_text(), font_small)

    def make_text(self):
        #name = self.science.NAME[:4]
        name = self.science.NAME
        text = '%s: %d' % (name, self.science.points)
        if self.points > 0:
            text = "%s + %d" % (text, self.points)
        return text

    def on_click(self):
        if (self.parent.game.get_available_points() > 0 and
                self.science.can_spend(self.parent.game.lab, self.points + 1)):
            self.points += 1
            self.parent.game.cur_allocation.append(self.science)
            self.parent.update_labels()
            self.set_text()

    def set_text(self):
        self.text = self.make_text()
        self._draw_text()

    def reset(self):
        while self.points > 0:
            self.parent.game.cur_allocation.remove(self.science)
            self.points -= 1
        self.set_text()
        self.parent.update_labels()

    def restore_selection(self, old_selection):
        points = old_selection.count(self.science)
        # We skip things we cannot allocate enoigh points to (temp points
        # boosts, etc.) or we cannot currently develop further
        if (points != self.points and
                points <= self.parent.game.get_available_points() and
                self.science.can_spend(self.parent.game.lab,
                    self.points + points)):
            self.points = points
            self.parent.game.cur_allocation.extend([self.science] * points)
            self.parent.update_labels()
            self.set_text()


class MissionWidget(BigButton):

    WIDTH = 260
    HEIGHT = 48

    BG_IMAGE_NORMAL = load_image('images/science_normal.png')
    BG_IMAGE_DOWN = load_image('images/science_down.png')
    BG_IMAGE_SELECTED = load_image('images/mission_selected.png')

    def __init__(self, mission, pos, parent):
        self.mission = mission
        self.parent = parent
        self.game = self.parent.game
        self.minions = 0
        super(MissionWidget, self).__init__(pos, '%s' % mission.NAME,
                font_small)
        selected = ImageDrawable((0, 0, self.WIDTH, self.HEIGHT),
                self.BG_IMAGE_SELECTED)
        self.add_state('SELECTED', selected)
        self.mode = 'NORMAL'
        self.equipment = []

    def on_mouse_cancel(self, pos):
        self.set_state(self.mode)

    def on_mouse_up(self, pos):
        self.set_state(self.mode)
        self.on_click()

    def select(self):
        if not self.selected():
            self.game.minions -= self.minions
            self.mode = 'SELECTED'
            self.set_state(self.mode)
        self.parent.update_labels()

    def unselect(self):
        if self.selected():
            # Unassign the minions from the main game
            self.game.minions += self.minions
        self.reset()

    def on_click(self):
        # select mission and equipment
        if self.game.minions > 0 or self.selected():
            if not self.selected():
                # Assign the minions automatically
                self.minions = self.mission.MINIONS_REQUIRED
            equip = EquipWindow(self.parent.screen, self.mission,
                    self.game, self)
            AddWindow.post(equip)

    def selected(self):
        return self.mode == 'SELECTED'

    def get_equipment(self):
        return self.equipment

    def reset(self):
        for equip in self.equipment:
            # Release funds
            self.game.money += equip.COST
        self.minions = 0
        self.parent.update()
        self.equipment = []
        self.mode = 'NORMAL'
        self.set_state('NORMAL')


class EquipWidget(BigButton):

    WIDTH = 260
    HEIGHT = 48

    BG_IMAGE_NORMAL = load_image('images/science_normal.png')
    BG_IMAGE_DOWN = load_image('images/science_down.png')
    BG_IMAGE_UNAVAILABLE = load_image('images/equip_grey.png')

    def __init__(self, equip, pos, parent, copies, available):
        self.equip = equip
        self.parent = parent
        self.available = available
        super(EquipWidget, self).__init__(pos, '[%s : %d] - %d'
                % (equip.NAME, equip.points, equip.COST), font_small)
        if not self.available:
            unavailable = ImageDrawable((0, 0, self.WIDTH, self.HEIGHT),
                    self.BG_IMAGE_UNAVAILABLE)
            # Override the default
            self.add_state('NORMAL', unavailable)
            self.set_state('NORMAL')
        self.equipment = []

    def on_mouse_down(self, pos):
        if self.available:
            super(EquipWidget, self).on_mouse_down(pos)

    def on_mouse_up(self, pos):
        if self.available:
            super(EquipWidget, self).on_mouse_up(pos)

    def on_click(self):
        self.parent.buy(self.equip)


class ValueLabel(TextLabel):

    def __init__(self, pos, description, width=300):
        self.description = description
        rect = (pos[0], pos[1], width, 20)
        super(ValueLabel, self).__init__(rect,
                '%s : 0' % description, font_medium, (255, 255, 0))

    def set_value(self, value):
        self.text = '%s : %s' % (self.description, value)
        self._draw_text()


class EquipWindow(Window):

    def __init__(self, screen, mission, game, parent):
        super(EquipWindow, self).__init__(screen)
        assign = AssignButton(self)
        self.add_child(assign)
        cancel = CancelButton(self)
        self.add_child(cancel)
        reset = ResetButton(self, 'Clear inventory')
        self.add_child(reset)
        title = TextBox((170, 10, 370, 50), "Choose equipment for %s"
                % mission.NAME, font_medium, (255, 255, 255))
        self.add_child(title)
        self.description = TextBox((10, 60, 790, 20),
                mission.get_description(), font_medium, (255, 255, 255))
        self.add_child(self.description)
        self.parent = parent
        self.game = game
        self.money = ValueLabel((10, self.description.rect.height + 58),
                'Money')
        self.money.set_value(self.game.money)
        self.minions = ValueLabel((310, self.description.rect.height + 58),
                'Minions Assigned')
        self.minions.set_value(self.parent.minions)
        self.add_child(self.money)
        self.add_child(self.minions)
        inventory = TextLabel((10, self.description.rect.height + 75, 400, 20),
                'Current Inventory', font_medium, (255, 255, 255))
        self.add_child(inventory)
        self._equip = []
        self._inventory = []
        self._update_widgets()

    def cancel(self):
        self.do_reset()
        self.parent.unselect()

    def accept(self):
        self.parent.select()

    def _update_widgets(self):
        self._make_inventory()
        self._make_equip_widgets()

    def _make_inventory(self):
        for widget in self._inventory:
            self.remove_child(widget)
        self._inventory = []
        x = 10
        y = 100 + self.description.rect.height
        # collections.Counter is python 2.7. This is a bother
        equipment = sorted(set(self.parent.equipment), key=lambda x: x.NAME)
        for equip in equipment:
            count = self.parent.equipment.count(equip)
            widget = TextLabel((x, y, 700, 15), '%s  x %s'
                    % (equip.NAME, count), font_medium, (255, 255, 128))
            x += 270
            if x > WIDTH:
                x = 10
                y += 18
            self._inventory.append(widget)
            self.add_child(widget)

    def _make_equip_widgets(self):
        for widget in self._equip:
            self.remove_child(widget)
        self._equip = []
        x = 0
        y = max(240, 100 + self.description.rect.height +
                18 * (len(self._inventory) // 3 + 1))
        available = self.game.get_available_equipment()
        for equip in sorted(self.game.get_all_equipment(),
                key=lambda x: x.NAME):
            copies = self.parent.equipment.count(equip)
            widget = EquipWidget(equip, (x, y), self, copies,
                    equip in available)
            self._equip.append(widget)
            self.add_child(widget)
            x += widget.WIDTH + 10
            if x >= WIDTH:
                x = 0
                y += 45

    def buy(self, equip):
        self.game.money -= equip.COST
        self.money.set_value(self.game.money)
        self.parent.equipment.append(equip)
        self.parent.parent.update()
        self._update_widgets()

    def do_reset(self):
        for equip in self.parent.equipment:
            # Release funds
            self.game.money += equip.COST
        self.money.set_value(self.game.money)
        self.parent.equipment = []
        self.parent.parent.update()
        self._update_widgets()


class ResultsWindow(Window):
    message_colours = {
        INFO: (60, 60, 255),
        FAILURE: (255, 60, 60),
        SUCCESS: (60, 255, 60),
        }

    def __init__(self, screen, messages, turn):
        super(ResultsWindow, self).__init__(screen)
        nextbut = NextTurnButton()
        self.add_child(nextbut)
        title = TextLabel((200, 20, 400, 50), "Results for turn %d" % turn,
                font_medium, (255, 255, 255))
        self.add_child(title)
        self.is_game_over = False
        if not messages:
            results = TextBox((200, 200, 400, 50),
                    "Nothing of interest happened", font_medium,
                    (255, 255, 255))
            self.add_child(results)
        else:
            y = 200
            for mission, msg_type, msg, loot in messages:
                # FIXME: Better widgets
                if msg_type == GAME_WIN:
                    self._make_win_screen(turn, msg)
                    self.is_game_over = True
                    break
                colour = self.message_colours.get(msg_type, (255, 255, 255))
                if mission:
                    msg = '%s: %s' % (mission, msg)
                y = self.display_message(y, msg, loot, colour)

    def display_message(self, y, msg, loot, colour, font=font_medium):
        text = TextBox((50, y, 700, 25), msg, font, colour)
        y += text.rect.height + 5
        self.add_child(text)
        for kind, value in loot.items():
            y += self.display_loot_item(y, kind, value)
        return y + 5

    def display_loot_item(self, y, kind, value):
        if hasattr(value, 'NAME'):
            value = value.NAME
        msg = "* %s: %s" % (kind, value)
        text = TextBox((80, y, 670, 25), msg, font_medium, (255, 255, 60))
        self.add_child(text)
        return text.rect.height + 5

    def _make_win_screen(self, turn, msg):
        # Clear existing widgets, and turn this into a won screen
        for child in self.children[:]:
            self.remove_child(child)
        # Replace background
        self.background_image = load_image('images/main_background.jpg')
        exitbut = ExitGameButton()
        # FIXME: 1:20 minutes to hackery here
        exitbut.rect.topleft = (500, 350)
        self.add_child(exitbut)
        title = TextLabel((500, 20, 200, 50), "Results for turn %d" % turn,
                font_medium, (255, 255, 255))
        self.add_child(title)
        won = TextBox((400, 200, 400, 50), "You've succeeded in your quest",
                font_large, (255, 255, 255))
        self.add_child(won)
        won = TextBox((450, 250, 400, 50), msg, font_large,
                (255, 255, 255))
        self.add_child(won)


class GameStateWindow(Window):
    """Base class for windows that show a lot of game state info"""

    def __init__(self, screen, game):
        super(GameStateWindow, self).__init__(screen)
        self.game = game
        self.screen = screen
        exitbut = ExitGameButton()
        self.add_child(exitbut)
        end_turn = EndTurnButton(self)
        self.add_child(end_turn)
        reset = ResetButton(self)
        self.add_child(reset)

        self.points = ValueLabel((10, 60), 'Blue-Sky Research Projects')
        self.add_child(self.points)
        self.minions = ValueLabel((310, 60), 'Minions available')
        self.add_child(self.minions)
        self.money = ValueLabel((510, 60), 'Money')
        self.add_child(self.money)

        self.milestone = ValueLabel((10, 80), 'Currently taken over')
        self.add_child(self.milestone)
        self.reputation = ValueLabel((310, 80), 'Reputation')
        self.add_child(self.reputation)

        self.advice = ValueLabel((10, 100), 'Research advice', width=780)
        self.add_child(self.advice)

    def update_labels(self):
        self.points.set_value(self.game.get_available_points())
        self.money.set_value(self.game.money)
        self.minions.set_value(self.game.minions)
        self.milestone.set_value(self.game.milestone)
        self.reputation.set_value(_lookup_reputation(self.game.reputation))
        self.advice.set_value(self.game.advice)


class ActivityWindow(GameStateWindow):

    def __init__(self, screen, lab, develop):
        super(ActivityWindow, self).__init__(screen, lab.game)
        self.background_image = image.load(
                filepath('images/schemes_background.jpg'))
        self.lab = lab
        self.develop = develop

        labbut = SwitchWinButton((150, 0), 'SCIENCE!!', lab)
        self.add_child(labbut)
        devbut = SwitchWinButton((300, 0), 'Engineering', develop)
        self.add_child(devbut)

        self.update_labels()
        self._missions = []
        self._make_widgets()

    def _make_widgets(self):
        for widget in self._missions:
            self.remove_child(widget)
        self._missions = []
        x = 0
        y = 130
        for mission in sorted(self.game.get_available_missions(),
                key=lambda x: x.NAME):
            widget = MissionWidget(mission, (x, y), self)
            self._missions.append(widget)
            self.add_child(widget)
            x += widget.WIDTH + 10
            if x >= WIDTH:
                x = 0
                y += 55

    def end_turn(self):
        # Drop back to the research screen
        PopWindow.post()
        self.lab.update()
        AddWindow.post(self.lab)
        self.lab.end_turn()

    def update_widgets(self):
        self._make_widgets()

    def update(self):
        self.update_labels()

    def do_reset(self):
        self.lab.reset()
        self.develop.reset()
        self.reset()

    def get_mission_list(self):
        selected_missions = []
        for widget in self._missions:
            if widget.selected():
                equipment = widget.get_equipment()
                mission = widget.mission
                selected_missions.append((mission, equipment))
        return selected_missions

    def reset(self):
        for widget in self._missions:
            widget.unselect()
            widget.reset()
        self.update_labels()


class DevelopmentWindow(GameStateWindow):
    """Window for handling schematics research"""

    def __init__(self, screen, lab):
        super(DevelopmentWindow, self).__init__(screen, lab.game)
        self.background_image = load_image('images/engineering_background.jpg')
        self.lab = lab

        labbut = SwitchWinButton((150, 0), 'SCIENCE!!', lab)
        self.add_child(labbut)
        self.activity = None

        self.update_labels()
        self._sciences = []
        self._make_science_widgets()

    def set_activity_window(self, activity):
        # Oh, what tangled webs we weave
        if not self.activity:
            self.activity = activity
            actbut = SwitchWinButton((300, 0), 'Schemes', activity)
            self.add_child(actbut)

    def _make_science_widgets(self):
        for widget in self._sciences:
            self.remove_child(widget)
        self._sciences = []
        x = 0
        y = 130
        for science in sorted(self.game.lab.science, key=lambda x: x.NAME):
            if science.SCIENCE_TYPE == 'schematic':
                widget = ScienceWidget(science, (x, y), self)
                self.add_child(widget)
                self._sciences.append(widget)
                x += widget.WIDTH + 10
                if x >= WIDTH:
                    x = 0
                    y += widget.HEIGHT

    def end_turn(self):
        # Drop back to the research screen
        PopWindow.post()
        self.lab.update()
        AddWindow.post(self.lab)
        self.lab.end_turn()

    def update_widgets(self):
        self._make_science_widgets()
        self.update_labels()

    def update(self):
        self.update_labels()

    def do_reset(self):
        self.reset()
        self.lab.reset()
        self.activity.reset()

    def reset(self):
        for widget in self._sciences:
            widget.reset()

    def restore_selection(self, old_selection):
        for widget in self._sciences:
            widget.restore_selection(old_selection)


class LabWindow(GameStateWindow):
    """Window for the research lab"""

    def __init__(self, screen, game_dict):
        self.game = Game(game_dict)
        super(LabWindow, self).__init__(screen, self.game)
        self.autosave = get_save_filename()
        self.background_image = load_image('images/lab_background.jpg')

        # Ensure we setup everything with the correct state set
        self.game.start_turn()

        self.develop = DevelopmentWindow(screen, self)
        self.activity = ActivityWindow(screen, self, self.develop)
        self.develop.set_activity_window(self.activity)

        devbut = SwitchWinButton((150, 0), 'Engineering', self.develop)
        self.add_child(devbut)
        actbut = SwitchWinButton((300, 0), 'Schemes', self.activity)
        self.add_child(actbut)

        self._sciences = []
        # Setup for the first turn
        self.update_labels()
        self._make_science_widgets()

    def _make_science_widgets(self):
        # FIXME: Horrible hackery
        for widget in self._sciences:
            self.remove_child(widget)
        self._sciences = []
        x = 0
        y = 130
        for science in sorted(self.game.lab.science, key=lambda x: x.NAME):
            if science.SCIENCE_TYPE == 'research':
                widget = ScienceWidget(science, (x, y), self)
                self.add_child(widget)
                self._sciences.append(widget)
                x += widget.WIDTH + 10
                if x >= WIDTH:
                    x = 0
                    y += widget.HEIGHT + 5

    def update_widgets(self):
        self._make_science_widgets()

    def restore_selection(self, old_selection):
        for widget in self._sciences:
            widget.restore_selection(old_selection)

    def end_turn(self):
        self.game.cur_missions = self.activity.get_mission_list()
        old_allocation = self.game.cur_allocation
        messages = self.game.end_turn()
        results = ResultsWindow(self.screen, messages, self.game.turn)
        if results.is_game_over:
            PopWindow.post()
            GameOver.post(results)
            return
        self.save_game()
        self.game.start_turn()
        self.update_widgets()
        self.develop.update_widgets()
        self.activity.update_widgets()
        # restore previous allocation
        self.restore_selection(old_allocation)
        self.develop.restore_selection(old_allocation)
        self.update_labels()
        self.develop.update_labels()
        AddWindow.post(results)

    def save_game(self):
        game_data = self.game.save_data()
        if self.autosave:
            # Don't corrupt the savefile if json crashes
            data = json.dumps(game_data)
            savefile = open(self.autosave, 'w')
            savefile.write(data)
            savefile.close()

    def update(self):
        self.update_labels()

    def do_reset(self):
        self.reset()
        self.develop.reset()
        self.activity.reset()

    def reset(self):
        for widget in self._sciences:
            widget.reset()