Demonic God Posted April 11, 2021 Report Posted April 11, 2021 (edited) As you can tell from... the Darvin quest, I am a bit obsessed with Advanced Aramors! So, as part of an experiment, I would love to craft Aramors that are capable of doing more, on their own. To that end, I'd love to start with making Aramors that can, for now, participate in simple games of Rock Paper Scissors! Your goal for this quest, would be to aid me in making such a creation. Specifically, I ask for you to design a strategy, one that is unambiguous and non randomized. Something that an Aramor can be programmed with! Your strategy will compete with others in a tournament. The rules for the tournament is as follow: Every strategy will compete with every other strategy Each duel will last until one side receive 3 10 wins. If it is mathematically impossible exceeds 500 draws, both side receives a loss it's considered a draw. This was added because the 500 draws checker got tripped... a lot. The worst strategy will be eliminated after every duel has happened. This will repeat with the surviving strategies until one emerge victorious The worst strategy would be evaluated with the following criteria: Least wins Most losses Lose against strategies with similar win/loss (i.e if A has 2 wins and 5 losses, and B also has 2 wins and 5 losses, and B lost against A, then B is eliminated) If the above criteria fail to select a single loser, all of them would be eliminated The tournament will repeat without the winner(s) until all prizes are filled The rules for strategies are as follow: Your goal is to design a strategy, to participate in a tournament of rock, paper, scissors! Your strategy must not be random and must cover all scenarios. Your strategy is not limited to the game itself. It can adapt to the round it's on, the opponent, etc. Possible strategy includes: Always choose rock If there is less than 3 participant left, always choose rock If opponent is Aia, always choose rock Cooperation is not only allowed, it is welcomed. You are free to rig matches as you please. Rewards 1st - Anniversary creature. A choice between a colored pope, and a colored elemental, courtesy of Lady @Aia del Mana 2nd - Anniversary creature. A colored pope or elemental, whichever remains after the winner had their pick 3rd - Anniversary creature. Deadline: I wish to wrap up the competition as soon as everyone who wish to join had done so. Thus, there would be two deadlines: Hard deadline: End of anniversary + 3 days. Soft deadline: 9 days (April 21st) from now. After this date, I will begin judging as soon as everyone who had announced their participant has submitted their strategy. You may join in after the soft deadline, as long as judging has not begun. Soft deadline is meant to avoid unnecessary waiting. Edited May 3, 2021 by Demonic God Some rule changes Aelis, Aia del Mana, Nep and 2 others 3 2 Quote
Demonic God Posted April 11, 2021 Author Report Posted April 11, 2021 (edited) For those looking to ensure their strategy can cover all scenarios, I suggest looking into programming diagram/flowcharts! Here's an example diagram of a simple logic/strategy: Edited April 11, 2021 by Demonic God Quote
Tissy Posted April 11, 2021 Report Posted April 11, 2021 Good tournament. Should we know the list of the participants before submitting the strategy? Quote
Demonic God Posted April 11, 2021 Author Report Posted April 11, 2021 (edited) 7 minutes ago, Tissy said: Good tournament. Should we know the list of the participants before submitting the strategy? You could announce your participation in this thread, if you want to. Announcing your participation or keeping it a secret would be an interesting strategic choice. The list of participants would be announced before judging begins and admission closed for clarity, but I won't confirm nor deny if someone is playing. Who knows, maybe you could even convince someone to fake their participant for some drama Edited April 11, 2021 by Demonic God Pipstickz 1 Quote
Tissy Posted April 11, 2021 Report Posted April 11, 2021 I think 3 wins are not enough to design a strategy against strategy. as you have to observe at least 2 rounds to guess your opponent's strategy. So one cannot do better than random. Demonic God 1 Quote
Demonic God Posted April 11, 2021 Author Report Posted April 11, 2021 1 minute ago, Tissy said: I think 3 wins are not enough to design a strategy against strategy. as you have to observe at least 2 rounds to guess your opponent's strategy. So one cannot do better than random. Hmn, valid point. I'll change that! Thanks Quote
Aia del Mana Posted April 11, 2021 Report Posted April 11, 2021 (edited) May I clarify if this were a permissible strategy: Roll a 6 sided die. On 1-2, play rock. On 3-4, play scissors, and on 5-6, play paper. Do such aramors have access to such decision-making? Edited April 11, 2021 by Aia del Mana Quote
Demonic God Posted April 12, 2021 Author Report Posted April 12, 2021 8 hours ago, Aia del Mana said: May I clarify if this were a permissible strategy: Roll a 6 sided die. On 1-2, play rock. On 3-4, play scissors, and on 5-6, play paper. Do such aramors have access to such decision-making? Your strategy must not rely on randomness Quote
Demonic God Posted April 29, 2021 Author Report Posted April 29, 2021 I'll be extending this by 4 days due to the fact that I still need to code everything @@ Sorry folks! On the flip side, it's a good chance to refine your strategies! Quote
Demonic God Posted May 3, 2021 Author Report Posted May 3, 2021 (edited) player_move.py from enum import Enum, auto class PlayerMove(Enum): ROCK = 'Rock' PAPER = 'Paper' SCISSORS = 'Scissors' def __gt__(self, obj): if isinstance(obj, PlayerMove): return self == PlayerMove.ROCK and obj == PlayerMove.SCISSORS or \ self == PlayerMove.PAPER and obj == PlayerMove.ROCK or \ self == PlayerMove.SCISSORS and obj == PlayerMove.PAPER else: raise TypeError(f'Not instance of PlayerMove{obj}') def __ge__(self,obj): if isinstance(obj, PlayerMove): return self > obj or self == obj else: raise TypeError(f'Not instance of PlayerMove{obj}') def __lt__(self,obj): if isinstance(obj, PlayerMove): return not self >= obj else: raise TypeError(f'Not instance of PlayerMove{obj}') def __le__(self,obj): if isinstance(obj, PlayerMove): return self < obj or self == obj else: raise TypeError(f'Not instance of PlayerMove{obj}') def getCounter(move): if isinstance(move, PlayerMove): return PlayerMove.PAPER if move == PlayerMove.ROCK else PlayerMove.SCISSORS if move == PlayerMove.PAPER else PlayerMove.ROCK else: raise TypeError(f'Not instance of PlayerMove{obj}') def getNotCounter(move): if isinstance(move, PlayerMove): return PlayerMove.PAPER if move == PlayerMove.SCISSORS else PlayerMove.SCISSORS if move == PlayerMove.ROCK else PlayerMove.ROCK else: raise TypeError(f'Not instance of PlayerMove{obj}') Just an enum representing Rock, Paper and Scissors tournament.py from player_move import * import random class Tournament: def __init__(self, *participants, output = 'output.txt'): self.log = open(output, "w", encoding="utf-8") self.participants = {p.name: p for p in participants} self.moveAnnouncer = EventDispatcher() #Announces player moves result (announce what the two picked) self.matchAnnouncer = EventDispatcher() #Announces match (starting match between two players) self.roundAnnouncer = EventDispatcher() #Announces round (list of players) for participant in participants: self.moveAnnouncer.addListener(participant.moveListener) self.matchAnnouncer.addListener(participant.matchListener) self.roundAnnouncer.addListener(participant.roundListener) self.currentPlayers = [p.name for p in participants] self.currentMatches = [] self.currentResults = {} def _generateMatches(self): # I hate myself for writing codes so ugly... but it's soooo convenient self.currentMatches = [[self.currentPlayers[p], self.currentPlayers[q]] for p in range(len(self.currentPlayers)) for q in range(len(self.currentPlayers)) if p < q] random.shuffle(self.currentMatches) self.currentResults = {p: {q: None for q in self.currentPlayers if p is not q} for p in self.currentPlayers} def _executeMove(self, player1, player2, pos): move1 = self.participants[player1].getMove() move2 = self.participants[player2].getMove() self.moveAnnouncer.broadcast({ 'player1': player1, 'player2': player2, 'move1': move1, 'move2': move2, 'pos': pos, }) return move1, move2 def _executeMatch(self, player1, player2): win1 = 0 win2 = 0 draw = 0 movePos = 0 self.log.write(f'Starting match between {player1} and {player2}\n') self.matchAnnouncer.broadcast({ 'player1': player1, 'player2': player2, 'event': 'start', }) while win1 < 10 and win2 < 10: if draw >= 500: self.log.write(f'Stalemate!\n') break # Executing a single move move1, move2 = self._executeMove(player1, player2, movePos) msg = 'ERR' #this should change if move1 > move2: win1 += 1 msg = f'{player1} wins!' elif move1 < move2: win2 += 1 msg = f'{player2} wins!' else: draw += 1 msg = "it's a draw!" self.log.write(f'Turn {movePos}: {player1} plays {move1.value}, {player2} plays {move2.value}, {msg}\n') movePos += 1 result = player1 if win1 == 10 else player2 if win2 == 10 else 'draw' if result == 'draw': self.log.write(f'Match ended in a draw!\n\n') self.currentResults[player1][player2] = 'draw' self.currentResults[player2][player1] = 'draw' else: self.log.write(f'Player {result} won the match!\n\n') self.currentResults[player1][player2] = 'win' if result == player1 else 'lose' self.currentResults[player2][player1] = 'lose' if result == player1 else 'win' self.matchAnnouncer.broadcast({ 'player1': player1, 'player2': player2, 'event': 'end', 'result': result, }) def _executeRound(self): self.roundAnnouncer.broadcast({ 'event': 'start', 'players': self.currentPlayers, }) self.log.write(f"Here's our contestants for this round: {', '.join(self.currentPlayers)}!\n") self._generateMatches() assert len(self.currentMatches) > 0 while self.currentMatches: player1, player2 = self.currentMatches.pop() self._executeMatch(player1,player2) worst = self._findWorst() stats = self._generateStats() self.log.write(f"Round ended! Here's the score!\n") for p in self.currentPlayers: self.log.write(f"{p}: {stats[p]['wins']} wins, {stats[p]['loses']} loses, {stats[p]['draws']} draws\n") self.log.write(f"The following contestants dropped out: {' '.join(worst)}\n\n") self.currentPlayers = [p for p in self.currentPlayers if p not in worst] self.roundAnnouncer.broadcast({ 'event': 'end', 'dropout': worst, }) def _generateStats(self): stats = {p: {'wins': 0, 'loses': 0, 'draws': 0} for p in self.currentPlayers} for p in self.currentPlayers: for r,v in self.currentResults[p].items(): assert v is not None stats[p]['wins'] += 1 if v == 'win' else 0 stats[p]['loses'] += 1 if v == 'lose' else 0 stats[p]['draws'] += 1 if v == 'draw' else 0 return stats def _findWorst(self): stats = self._generateStats() min_win = min([v['wins'] for p,v in stats.items()]) max_lose_in_min_win = max([v['loses'] for p,v in stats.items() if v['wins'] == min_win]) worst_players = [p for p in stats if stats[p]['wins'] == min_win and stats[p]['loses'] == max_lose_in_min_win] assert len(worst_players) > 0 if len(worst_players) == 1: return worst_players worst_of_worst = [] for p in worst_players: for p2 in worst_players: if p is not p2 and self.currentResults[p][p2] == 'win': break else: worst_of_worst.append(p) if len(worst_of_worst) > 0: return worst_of_worst else: return worst_players def executeGame(self): self.log.write(f"The tournament has begun!\n") draws = [] result = None while len(self.currentPlayers) > 1: draws = self.currentPlayers self._executeRound() if len(self.currentPlayers) == 1: self.log.write(f"Congratulations to {self.currentPlayers[0]} for winning the tournament!\n") result = self.currentPlayers if len(self.currentPlayers) == 0: self.log.write(f"Looks like there's no apparent victor! The following players tied for top place: {' '.join(draws)}\n") result = draws self.log.close() return result class EventDispatcher: def __init__(self): self.listeners = [] def addListener(self, listener): self.listeners.append(listener) def removeListener(self, listener): if listener in self.listeners: self.listeners.remove(listener) def broadcast(self, *data): for listener in self.listeners: listener(*data) # try: # except: # print(f'Error broadcasting {data} to {listener}') The current tournament emulator. It outputs text into "output.txt" (customizable). strategies.py from player_move import * # ALL STRATEGIES SHOULD EXTEND BASE STRATEGY. Or at least implement the same thing as base strategy class BaseStrategy: def __init__(self): self.name = 'Scissors' # 'player1': player1 # 'player2': player2 # 'move1': move by player1 # 'move2': move by player2 # 'pos': turn count (position of the "move") def moveListener(self, move): pass # 'player1': player1 # 'player2': player2 # 'event': 'start' or 'end' # 'result': result if event is 'end' def matchListener(self, match): pass # 'event': 'start' or 'end' # 'players': list of players if event is 'start' # 'dropouts': dropouts if event is 'end' def roundListener(self, round): pass # Your main logic def getMove(self): return PlayerMove.SCISSORS class Rock(BaseStrategy): def __init__(self): self.name = 'Rocks' def getMove(self): return PlayerMove.ROCK class Paper(BaseStrategy): def __init__(self): self.name = 'Papers' def getMove(self): return PlayerMove.PAPER class Rand(BaseStrategy): def __init__(self): self.name = 'Rand' def getMove(self): import random r = random.randrange(0,3) return PlayerMove.ROCK if r == 0 else PlayerMove.PAPER if r == 1 else PlayerMove.SCISSORS Where all the strategies are implemented. BaseStrategy should give you an idea of what is provided by the tournament. A few sample strategy is provided here. Note that random is used only for testing purposes - your strategy cannot be random (fixed seed pseudorandom is acceptable) main.py from tournament import * from strategies import * participants = [] participants.append(Rock()) participants.append(Paper()) participants.append(Rand()) participants.append(BaseStrategy()) tournament = Tournament(*participants) tournament.executeGame() simple main to create a tournament instance, and executes it. Have fun! Please report any bugs you spot too I'll be sending over each player individual strategy as I implement them (via PM). You're free to do so yourself (and save me effort!), though you'd be responsible for your own mistakes hehe. If you have any question, feel free to PM If you really, really wanna toy/benchmark your strategy, send me a PM and I can rush the implementation of your strategy Additionally - The tournament will be postponed to 3 days after I've sent everyone their strategy in code form for any last minute changes and toying around. Sorry for all the delays! Edited May 3, 2021 by Demonic God added support for logging multiple tournament runs Quote
Root Admin Chewett Posted May 3, 2021 Root Admin Report Posted May 3, 2021 Are there unit tests? Quote
Demonic God Posted May 3, 2021 Author Report Posted May 3, 2021 1 minute ago, Chewett said: Are there unit tests? maybe :3. I made some. Not included here. Quote
Demonic God Posted May 3, 2021 Author Report Posted May 3, 2021 some may have been informal tests done directly on IDLE without being written into reusable unit tests as well :3 Quote
Demonic God Posted May 3, 2021 Author Report Posted May 3, 2021 (edited) Codes sent to most participants. I'm eager to announce the observations There are some really, really fascinating interaction going on Edited May 3, 2021 by Demonic God Yoshi 1 Quote
Demonic God Posted May 3, 2021 Author Report Posted May 3, 2021 Ehem, there are some new development during trial runs! Apparently, a lot of you have strategies relying on observation and rounds! And other stuff. Which led to an interesting thing: There's no definite winner. Those strategies works really, really well, and depending on the ordering, can change the winner completely. So I present the following two compromises: We run the simulations 100 times, and tally up! We run it 1 time, and let whoever wins, wins. I've also updated the judging rules a bit on draws and such. As well as how second spots are chosen. Aelis, Nep, Aia del Mana and 1 other 4 Quote
Yoshi Posted May 3, 2021 Report Posted May 3, 2021 I vote for running the simulation 1000 times. Quote
Else Posted May 3, 2021 Report Posted May 3, 2021 (edited) I vote for running the simulation 1 000 000 000 times. But seriously, 100 times Edited May 3, 2021 by Else Quote
Demonic God Posted May 3, 2021 Author Report Posted May 3, 2021 (edited) Also - to clarify I guess since the randomized order does give some issue... but well Straight up random is still not allowed. You can try to implement some strange ways to confuse your opponents. Even as far as a fixed list of moves you wanna do. I guess the line between random and strategy kinda blurs there but... I also don't wanna rule lawyer exactly how can you implement your own version of pseudo random. Line's blurry. Is looping 10 choices on repeat "random"? 20? 50? What about changing where you start? Arg. Just keep it reasonable I suppose. Those of you who wish to make use of that info is free to add a revision :3 Edited May 3, 2021 by Demonic God Quote
Aia del Mana Posted May 3, 2021 Report Posted May 3, 2021 (edited) For the sake of auditability, I would suggest to order the contestants in a random order, and then to run a single simulation; it is quite impossible to audit one hundred such simulations. If a random order were not feasible, one could order the contestants by the time of their first submission, or final amendment to the same. Edited May 3, 2021 by Aia del Mana Quote
Demonic God Posted May 3, 2021 Author Report Posted May 3, 2021 8 minutes ago, Aia del Mana said: For the sake of auditability, I would suggest to order the contestants in a random order, and then to run a single simulation; it is quite impossible to audit one hundred such simulations. If a random order were not feasible, one could order the contestants by the time of their first submission, or final amendment to the same. Random order is what gave rise to our current dilemma, and is the default. Quote
Demonic God Posted May 3, 2021 Author Report Posted May 3, 2021 I'd also have to add, that some of the strategies might not be very easy to audit. I'll be doing extensive testing to make sure things run as intended, but auditing itself is not something I'd suggest other than for the more straightforward logic. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.