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 %}
+
+ {{ attempt(solution) }} |
+
+ {% endif %}
+
+ Turn |
+ Your guess |
+ {{ players[player].name }} |
+ {% for p in players.values() %}
+ {% if p.uuid == player %}{% continue %}{% endif %}
+ {{ p.name }} |
+ {% endfor %}
+
+ {% for i in range(turn) %}
+
+ {{ i+1 }} |
+ {% if attempts | length >= i+1 %}{{ attempt(attempts[i]) }}{% endif %} |
+ {% if responses[player] | length >= i+1 %}{{ response(responses[player][i], false) }}{% endif %} |
+ {% for p in players.values() %}
+ {% if p.uuid == player %}{% continue %}{% endif %}
+ {% if responses[p.uuid] | length >= i+1 %}{{ response(responses[p.uuid][i], true) }}{% endif %} |
+ {% endfor %}
+
+ {% endfor %}
+
+
+ {% if done %}
+ {% if winners | length == 0 %}
+ Nobody won
+ {% else %}
+ {{ winners | join(', ') }} won!
+ {% endif %}
+ Game Over! Return to lobby
+ {% elif player not in played %}
+
+
+ {% 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()