Jump to content

Demonic God

Member
  • Posts

    257
  • Joined

  • Last visited

  • Days Won

    12

Posts posted by Demonic God

  1. 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.

  2. 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

  3. 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:

    1. We run the simulations 100 times, and tally up!
    2. 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.

  4. 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 :D

    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 :D

    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!

  5. Well... I pushed him to make this quest, and god forbids me from not submitting an entry into a cooking quest!

    So I present:

    The Runny Chocolatey Brownie of Addiction!

    Ingredients:

    • 8 Winderwild eggs (preferably the unfertilized unhatch-able chicken-like kind). Replace with Angien Eggs if you're filthy rich without a moral compass. Replace with Drachorn Eggs if you have a death wish. Just not Elemental Eggs *glares at Fyrd*
    • Butter, two sticks. I have no idea how you'd even find them, perhaps Taurion might know where you can get some.
    • Sugar, about 1.2 cups. Crushing some candy canes should do niceeely.
    • Some flavorful herbs. I've heard they're called "vanilla". Maybe you can find them with a herb basket. Maybe you can't.
    • Coffee extract. Or powder. Or Espresso if you're fancy. Popular with lab researchers. Perhaps you should visit the Golemus lab to find out more. Or not. I can't promise you won't be turned into an experiment.
    • Flowers! Wait, no, flour! Go rob MaG, he collected a ton! Wait, no, that's still flower. Erm, perhaps the Golem Mill might have some? Anyway, you need 2 cups of this stuff.
    • Salt. This shouldn't be hard to find. Could even bully someone and harvest their tears for their salt content! Or blood, blood is pretty salty too, right?
    • CHOCOLATE! 1 bar (better be dark chocolate, 70%). Ask your date on Valentine. What, no date? Well, at least that would take care of your salt issues!
    • Cocoa powder. 0.4 cup. Don't snort them. They may be addictive, but not THAT type of addictive.
    • Utensils and an oven. A whip, mixing bowl, container bowls, a pot, spatula, measuring cups, baking tray, parchment paper. If you feel like this is a bit NSFW, you're thinking of the wrong type of whip.

    Steps

    1. Chop the chocolate. Doesn't matter how, they'll get melted later. Thin flakes makes it easier to melt, but not too thin, else it'll melt on the chopping board
    2. Put the butter into a small pot, and heat it slowly to a boil. Don't burn it.
    3. Crack 4 eggs into the mixing bowl. Separate the yolk from the remaining 4. Also put them into the bowl. Do whatever with the extra whites.
    4. Whip the eggs together, until harmonious. As harmonious as Ledah and MaG were when they schemed to kill Granos.
    5. Slowly pour the boiling butter over the egg, whipping vigorously to prevent the egg from cooking
    6. Put the chocolate into the hot mix, and whip some more. No, not BDSM style. Whip til the choco melts into a nice blend!
    7. Put in the vanilla, coffee extract, sugar crushed candy canes, cocoa powder, and salt, and continue mixing
    8. Put in the flour! Make sure to pick off the pedals... wait, no, that's flower again, urg! Make sure the flour don't clump, use your hand, a strainer, whatever.
    9. Now, get rid of the whip and use a spatula to fold the mixture together. The longer you mix, the chewier the final brownie will get
    10. Put your parchment paper over your baking tray. Some oil could be used to help it stick. Some trimming and folding can make things looks nice. Some social skill may help with that valentine chocolate. Just sayin :3
    11. Pour the mixture into the tray. Put into oven. This is a very complicated step. No, really, it varies a LOT based on your equipment. Usually I'd say half an hour at around 220 Celsius. Or 493.15 for you weirdos with a Kelvin oven.
      • Personally, I pour half. Bake for ~7 minutes. Pour remaining half. Bake for 8. Cover with foil. Bake for another 5-10 minutes. This is the result of repeatedly experimenting with my oven, which is technically not even an oven!
      • You'll need to tinker a lot to get a just-right result to your liking. Including with ingredients.
    12. Take it out. Wait. Wait longer. Wait til it stop smoking, I don't care how good it smell damnit!
    13. Cut and enjoy. With tea. Or coffee. Depending on how "cultured" you are ;)

    Why this makes me happy

    I've been using this recipe a lot. Tinkered with it, a lot. Changed multiple steps. Tried multiple techniques. Baked dozens.
    The thing is - I also barely ate any. This is a treat I make for family. Friends. Guests. I do like sweet, and I love what I made, but what makes me happy is to see others enjoys it (and choke on that sweet, sweet calories!). I'm still tweaking it batch-by-batch. The process of making, the experimentation, and most of all, the joys and appreciation, is what makes this dish special.

    So, while I'd love to have the MD community try it some day, the best I could do is to post pictures here :D

    These pics may have come from 3 different batches that I made in the past week - I recently tested the double baking process (that also changed a lot of the steps). It's just one improvement of, what I hope to be, many more in the future :D

    kulrj4M.jpgSG2mhSC.jpgM9hr8Dj.jpgZphEeF2.jpgWkZsPVA.jpgWDaIwsk.jpg999kPGm.jpgSQCard7.jpg

     

  6. Notable mention would be Steno, who did send in an outside-the-box submission jokingly stating that there's no way to tell, since the construction crew could've screwed up the walls and resulted in strange bounces!

    I was tempted to give him the creativity award, but sadly, as we all know, his current lack of access to a computer (and busy hosting various quests too), prevented him from giving me a theory on how the wall deforms could've looked like :(

  7. Here is the final list of all who submitted a correct solution!

    Please let me know which spellstone you want here or via PM. I'll try to get them to you before the next anni :P

    Also:

    Lady Aia wins an extra GC for having a correct solution less than30 minutes after the quest went online!

    As for the anni crits - well, it's hard to say, rather, since there was two particular methods that people used to solve this problem:

    1. Observing that the balls slowly move towards the two corners, 1 cm at a time, every 2 bounces. Thus, it took 2020 (smaller edges) x 2 (bounces per cm) x 2 (back and forth) - 1 = 8079 bounces
    2. Tracing the bounces for smaller n/(n+1) rectangles, and finding a general formula of 4 * n - 1.

    As such, the winners were selected as the solutions that stood out the most to me, for each of this category. Hence:

    1. Tissy wins an anni, for a creative solution (that actually resembles a general case solution for any n x m rectangle and any starting launch angle, that I know of). While he did eventually returned to the 1cm logic, his way of reaching it stood out
    2. Yoshi wins an anni, for observing the pattern (with pictures demonstrating the bounces) for smaller rooms, before concluding on the general formula.
  8. 10. Ungod

    submission.png.04bb2ddcc93eb968e6a51c305bc62ef9.png

    I'll... just assume I understand his schematics. It also seems that he's missing 6 empty spaces unaccounted for, if I'm reading his material summary correctly xD

    Regardless, a very creative approach, that comes with some actual masonry reasoning! - had it come with some actual rocks and such, or a small scale physical model with some stones, I'd have awarded a wp :P

  9. 3. Yoshi

    submission.thumb.png.a94126cf79a36d5bcd846dd310cf62d0.png

    The first submission (and so far... the only submission that used my suggestion of a numbering format. With multiple revisions. He himself was the reason why I made the python script for visualization and scoring xD.

    (Note to Yoshi - you could've gotten 184 - and the second prize, as you did submit before Fyrd)

    sub.thumb.png.402537d6cb0bac0991d84a638380e907.png

  10. Here's a the results!

    1. @Aia del Mana - 188
    2. @Fyrd Argentus - 184
    3. @Yoshi - 181
    4. @Kaya - 181 (Yoshi submitted earlier)
    5. @Nepgear- 177
    6. @Jubaris - 174
    7. @Dracoloth - 165
    8. @Pipstickz - 164
    9. @Aelis - 160
    10. @Ungod - 116

    Congratulations to the top masons!

    In addition, @Aia del Manaand @Fyrd Argentusboth presented solutions that are quite fascinating. They both won a WP each! Congratulations!

    Below are each player's submission, in order:

×
×
  • Create New...