From 4b5650555b009386d9731e59a8b1844eca23ac30 Mon Sep 17 00:00:00 2001 From: s3lph Date: Sun, 17 Nov 2024 04:45:30 +0100 Subject: [PATCH] feat: add mastermind --- templates/cwa/welcome1.html.j2 | 1 + templates/mastermind/mastermind.html.j2 | 135 ++++++++++++++++++++ webgames/mastermind.py | 160 ++++++++++++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 templates/mastermind/mastermind.html.j2 create mode 100644 webgames/mastermind.py diff --git a/templates/cwa/welcome1.html.j2 b/templates/cwa/welcome1.html.j2 index 945065e..944b08a 100644 --- a/templates/cwa/welcome1.html.j2 +++ b/templates/cwa/welcome1.html.j2 @@ -12,6 +12,7 @@ +
  • Invite other players
  • diff --git a/templates/mastermind/mastermind.html.j2 b/templates/mastermind/mastermind.html.j2 new file mode 100644 index 0000000..0e248e3 --- /dev/null +++ b/templates/mastermind/mastermind.html.j2 @@ -0,0 +1,135 @@ + + + +{% if not done and player in played %} + +{% endif %} + + + + + {% macro attempt(a) %} +
    + {% for i in range(4) %} + + {% endfor %} +
    + {% endmacro %} + + {% macro response(r, other) %} +
    + {% for i in range(4) %} + + {#{% if i == 1 %}
    {% endif %}#} + {% endfor %} +
    + {% endmacro %} + + + {% if done %} + + + + {% endif %} + + + + + {% for p in players.values() %} + {% if p.uuid == player %}{% continue %}{% endif %} + + {% endfor %} + + {% for i in range(turn) %} + + + + + {% for p in players.values() %} + {% if p.uuid == player %}{% continue %}{% endif %} + + {% endfor %} + + {% endfor %} +
    {{ attempt(solution) }}
    TurnYour guess{{ players[player].name }}{{ p.name }}
    {{ i+1 }}{% if attempts | length >= i+1 %}{{ attempt(attempts[i]) }}{% endif %}{% if responses[player] | length >= i+1 %}{{ response(responses[player][i], false) }}{% endif %}{% if responses[p.uuid] | length >= i+1 %}{{ response(responses[p.uuid][i], true) }}{% endif %}
    + + {% if done %} + {% if winners | length == 0 %} + Nobody won + {% else %} + {{ winners | join(', ') }} won! + {% endif %} + Game Over! Return to lobby + {% elif player not in played %} +
    + {% for i in range(4) %} + + + {% endfor %} + +
    +
    + {% for c in colors %} + + {% endfor %} +
    + + {% else %} + Please wait for the other players to finish their turn! + {% endif %} + + diff --git a/webgames/mastermind.py b/webgames/mastermind.py new file mode 100644 index 0000000..cf4d980 --- /dev/null +++ b/webgames/mastermind.py @@ -0,0 +1,160 @@ +import random +import enum +from uuid import uuid4, UUID + +from webgames.puzzle import Puzzle + + + +class _Color: + + def __init__(self, name, html, ansi): + self.name = name + self.html = html + self.ansi = ansi + + +class Color(enum.Enum): + NONE = _Color('none', '#000000', '\033[30m') + RED = _Color('red', '#ff0000', '\033[31m') + GREEN = _Color('green', '#00aa00', '\033[32m') + BLUE = _Color('blue', '#0000ff', '\033[34m') + YELLOW = _Color('yellow', '#ffff00', '\093[33m') + WHITE = _Color('white', '#dddddd', '\033[97m') + GREY = _Color('grey', '#888888', '\033[90m') + PINK = _Color('pink', '#ff0088', '\033[95m') + ORANGE = _Color('orange', '#ff8800', '\033[95m') + + +class ResponseColor(enum.Enum): + RED = _Color('red', '#ff0000', '\033[31m') + WHITE = _Color('white', '#dddddd', '\033[97m') + BLACK = _Color('black', '#000000', '\033[30m') + + def __lt__(self, other): + if self == ResponseColor.RED and other != ResponseColor.RED: + return True + if self == ResponseColor.WHITE and other == ResponseColor.BLACK: + return True + return False + + +class Mastermind(Puzzle): + + def __init__(self, pargs = None): + super().__init__('mastermind', self.__class__) + if not pargs: + pargs = {} + self._solution = [] + self._attempts = {} + self._responses = {} + self._winners = [] + self._played = set() + self._turn = 0 + self._max_turn = 12 + self._done = False + self._urlbase = '/' + + def add_player(self, uuid: UUID) -> None: + self._attempts[uuid] = [] + self._responses[uuid] = [] + + def get_extra_options_html(self) -> str: + return ''' +

    Rule Options

    + +
    + +
    +''' + + def begin(self, options, urlbase): + self._urlbase = urlbase + colors = list(Color) + if 'empty' not in options.getall('rules'): + colors = colors[1:] + if 'repeat' in options.getall('rules'): + self._solution = random.choices(colors, 4) + else: + self._solution = random.sample(colors, 4) + self._turn = 1 + + def next_turn(self): + for p, _a in self._attempts.items(): + attempt = _a[-1] + if attempt == self._solution: + self._winners.append(p) + self._done = True + # Need a separate list that we can remove correct attempts from so the same color doesn't + # get counted as correct a second time. + solution = [x for x in self._solution] + response = [] + for i, a in enumerate(attempt): + if a == self._solution[i]: + response.append(ResponseColor.RED) + solution.remove(a) + elif a in solution: + response.append(ResponseColor.WHITE) + solution.remove(a) + else: + response.append(ResponseColor.BLACK) + response.sort() + self._responses[p].append(response) + self._played.clear() + if self._turn + 1 > self._max_turn: + self._done = True + if not self._done: + self._turn += 1 + + def process_action(self, puuid, action) -> None: + if action.get('action', 'none') != 'Submit': + return + if puuid in self._played: + return + try: + a1 = Color[action.get('attempt1', 'NONE').upper()] + a2 = Color[action.get('attempt2', 'NONE').upper()] + a3 = Color[action.get('attempt3', 'NONE').upper()] + a4 = Color[action.get('attempt4', 'NONE').upper()] + self._attempts[puuid].append([a1, a2, a3, a4]) + self._played.add(puuid) + except BaseException as e: + print(e) + if len(list(filter(lambda x: len(x) < self._turn, self._attempts.values()))) == 0: + self.next_turn() + + def serialize(self, player): + # todo + return {} + + def render(self, env, game, player, duration): + tmpl = env.get_template('mastermind/mastermind.html.j2') + return tmpl.render(players=game._players, + player=player.uuid, + played=self._played, + attempts=self._attempts[player.uuid], + responses=self._responses, + solution=self._solution, + winners=self._winners, + done=self._done, + turn=self._turn, + colors=Color, + rcolors=ResponseColor, + baseurl=self._urlbase) + + @property + def human_name(self) -> str: + return 'Mastermind' + + @property + def scores(self): + return sorted( + [{'player': uuid, 'score': self._max_turn - len(self._attempts[uuid])} for uuid, player in self._players.items()], + key=lambda x: x['score'], reverse=True) + + @property + def is_completed(self) -> bool: + return self._done + + +Mastermind()