Great Refactor
This commit is contained in:
		
							parent
							
								
									3cb01a2e7b
								
							
						
					
					
						commit
						d6a7530599
					
				
							
								
								
									
										18
									
								
								mycelia.py
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								mycelia.py
									
									
									
									
									
								
							| @ -1,18 +0,0 @@ | |||||||
| class State(): |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Action(): |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class BotAction(): |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class PlayerAction(): |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class EnvAction(): |  | ||||||
|     pass |  | ||||||
							
								
								
									
										669
									
								
								vacuumDecay.py
									
									
									
									
									
								
							
							
						
						
									
										669
									
								
								vacuumDecay.py
									
									
									
									
									
								
							| @ -1,669 +0,0 @@ | |||||||
| if __name__ == '__main__': |  | ||||||
|     print('[!] VacuumDecay should not be started directly') |  | ||||||
|     exit() |  | ||||||
| 
 |  | ||||||
| import os |  | ||||||
| import io |  | ||||||
| import time |  | ||||||
| import random |  | ||||||
| import threading |  | ||||||
| import torch |  | ||||||
| import torch.nn as nn |  | ||||||
| from torch import optim |  | ||||||
| from math import sqrt, pow, inf |  | ||||||
| #from multiprocessing import Event |  | ||||||
| from abc import ABC, abstractmethod |  | ||||||
| from threading import Event |  | ||||||
| from queue import PriorityQueue, Empty |  | ||||||
| from dataclasses import dataclass, field |  | ||||||
| from typing import Any |  | ||||||
| import random |  | ||||||
| import datetime |  | ||||||
| import pickle |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Action(): |  | ||||||
|     # Should hold the data representing an action |  | ||||||
|     # Actions are applied to a State in State.mutate |  | ||||||
| 
 |  | ||||||
|     def __init__(self, player, data): |  | ||||||
|         self.player = player |  | ||||||
|         self.data = data |  | ||||||
| 
 |  | ||||||
|     def __eq__(self, other): |  | ||||||
|         # This should be implemented differently |  | ||||||
|         # Two actions of different generations will never be compared |  | ||||||
|         if type(other) != type(self): |  | ||||||
|             return False |  | ||||||
|         return str(self.data) == str(other.data) |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         # should return visual representation of this action |  | ||||||
|         # should start with < and end with > |  | ||||||
|         return "<P"+str(self.player)+"-"+str(self.data)+">" |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class State(ABC): |  | ||||||
|     # Hold a representation of the current game-state |  | ||||||
|     # Allows retriving avaible actions (getAvaibleActions) and applying them (mutate) |  | ||||||
|     # Mutations return a new State and should not have any effect on the current State |  | ||||||
|     # Allows checking itself for a win (checkWin) or scoring itself based on a simple heuristic (getScore) |  | ||||||
|     # The calculated score should be 0 when won; higher when in a worse state; highest for loosing |  | ||||||
|     # getPriority is used for prioritising certain Nodes / States when expanding / walking the tree |  | ||||||
| 
 |  | ||||||
|     def __init__(self, curPlayer=0, generation=0, playersNum=2): |  | ||||||
|         self.curPlayer = curPlayer |  | ||||||
|         self.generation = generation |  | ||||||
|         self.playersNum = playersNum |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def mutate(self, action): |  | ||||||
|         # Returns a new state with supplied action performed |  | ||||||
|         # self should not be changed |  | ||||||
|         return State(curPlayer=(self.curPlayer+1) % self.playersNum, generation=self.generation+1, playersNum=self.playersNum) |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def getAvaibleActions(self): |  | ||||||
|         # Should return an array of all possible actions |  | ||||||
|         return [] |  | ||||||
| 
 |  | ||||||
|     def askUserForAction(self, actions): |  | ||||||
|         return choose('What does player '+str(self.curPlayer)+' want to do?', actions) |  | ||||||
| 
 |  | ||||||
|     # improveMe |  | ||||||
|     def getPriority(self, score, cascadeMemory): |  | ||||||
|         # Used for ordering the priority queue |  | ||||||
|         # Priority should not change for the same root |  | ||||||
|         # Lower prioritys get worked on first |  | ||||||
|         # Higher generations should have higher priority |  | ||||||
|         # Higher cascadeMemory (more influence on higher-order-scores) should have lower priority |  | ||||||
|         return -cascadeMemory + 100 |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def checkWin(self): |  | ||||||
|         # -1 -> Draw |  | ||||||
|         # None -> Not ended |  | ||||||
|         # n e N -> player n won |  | ||||||
|         return None |  | ||||||
| 
 |  | ||||||
|     # improveMe |  | ||||||
|     def getScoreFor(self, player): |  | ||||||
|         # 0 <= score <= 1; should return close to zero when we are winning |  | ||||||
|         w = self.checkWin() |  | ||||||
|         if w == None: |  | ||||||
|             return 0.5 |  | ||||||
|         if w == player: |  | ||||||
|             return 0 |  | ||||||
|         if w == -1: |  | ||||||
|             return 0.9 |  | ||||||
|         return 1 |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def __str__(self): |  | ||||||
|         # return visual rep of state |  | ||||||
|         return "[#]" |  | ||||||
| 
 |  | ||||||
|     @abstractmethod |  | ||||||
|     def getTensor(self, player=None, phase='default'): |  | ||||||
|         if player == None: |  | ||||||
|             player = self.curPlayer |  | ||||||
|         return torch.tensor([0]) |  | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def getModel(cls, phase='default'): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     def getScoreNeural(self, model, player=None, phase='default'): |  | ||||||
|         return model(self.getTensor(player=player, phase=phase)).item() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Universe(): |  | ||||||
|     def __init__(self): |  | ||||||
|         self.scoreProvider = 'naive' |  | ||||||
| 
 |  | ||||||
|     def newOpen(self, node): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     def merge(self, node): |  | ||||||
|         return node |  | ||||||
| 
 |  | ||||||
|     def clearPQ(self): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     def iter(self): |  | ||||||
|         return [] |  | ||||||
| 
 |  | ||||||
|     def activateEdge(self, head): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @dataclass(order=True) |  | ||||||
| class PQItem: |  | ||||||
|     priority: int |  | ||||||
|     data: Any = field(compare=False) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class QueueingUniverse(Universe): |  | ||||||
|     def __init__(self): |  | ||||||
|         super().__init__() |  | ||||||
|         self.pq = PriorityQueue() |  | ||||||
| 
 |  | ||||||
|     def newOpen(self, node): |  | ||||||
|         item = PQItem(node.getPriority(), node) |  | ||||||
|         self.pq.put(item) |  | ||||||
| 
 |  | ||||||
|     def merge(self, node): |  | ||||||
|         self.newOpen(node) |  | ||||||
|         return node |  | ||||||
| 
 |  | ||||||
|     def clearPQ(self): |  | ||||||
|         self.pq = PriorityQueue() |  | ||||||
| 
 |  | ||||||
|     def iter(self): |  | ||||||
|         while True: |  | ||||||
|             try: |  | ||||||
|                 yield self.pq.get(False).data |  | ||||||
|             except Empty: |  | ||||||
|                 return None |  | ||||||
| 
 |  | ||||||
|     def activateEdge(self, head): |  | ||||||
|         head._activateEdge() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Node(): |  | ||||||
|     def __init__(self, state, universe=None, parent=None, lastAction=None): |  | ||||||
|         self.state = state |  | ||||||
|         if universe == None: |  | ||||||
|             print('[!] No Universe defined. Spawning one...') |  | ||||||
|             universe = Universe() |  | ||||||
|         self.universe = universe |  | ||||||
|         self.parent = parent |  | ||||||
|         self.lastAction = lastAction |  | ||||||
| 
 |  | ||||||
|         self._childs = None |  | ||||||
|         self._scores = [None]*self.state.playersNum |  | ||||||
|         self._strongs = [None]*self.state.playersNum |  | ||||||
|         self._alive = True |  | ||||||
|         self._cascadeMemory = 0  # Used for our alternative to alpha-beta pruning |  | ||||||
| 
 |  | ||||||
|     def kill(self): |  | ||||||
|         self._alive = False |  | ||||||
| 
 |  | ||||||
|     def revive(self): |  | ||||||
|         self._alive = True |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def childs(self): |  | ||||||
|         if self._childs == None: |  | ||||||
|             self._expand() |  | ||||||
|         return self._childs |  | ||||||
| 
 |  | ||||||
|     def _expand(self): |  | ||||||
|         self._childs = [] |  | ||||||
|         actions = self.state.getAvaibleActions() |  | ||||||
|         for action in actions: |  | ||||||
|             newNode = Node(self.state.mutate(action), |  | ||||||
|                            self.universe, self, action) |  | ||||||
|             self._childs.append(self.universe.merge(newNode)) |  | ||||||
| 
 |  | ||||||
|     def getStrongFor(self, player): |  | ||||||
|         if self._strongs[player] != None: |  | ||||||
|             return self._strongs[player] |  | ||||||
|         else: |  | ||||||
|             return self.getScoreFor(player) |  | ||||||
| 
 |  | ||||||
|     def _pullStrong(self):  # Currently Expecti-Max |  | ||||||
|         strongs = [None]*self.playersNum |  | ||||||
|         for p in range(self.playersNum): |  | ||||||
|             cp = self.state.curPlayer |  | ||||||
|             if cp == p:  # P owns the turn; controlls outcome |  | ||||||
|                 best = inf |  | ||||||
|                 for c in self.childs: |  | ||||||
|                     if c.getStrongFor(p) < best: |  | ||||||
|                         best = c.getStrongFor(p) |  | ||||||
|                 strongs[p] = best |  | ||||||
|             else: |  | ||||||
|                 scos = [(c.getStrongFor(p), c.getStrongFor(cp)) |  | ||||||
|                         for c in self.childs] |  | ||||||
|                 scos.sort(key=lambda x: x[1]) |  | ||||||
|                 betterHalf = scos[:max(3, int(len(scos)/3))] |  | ||||||
|                 myScores = [bh[0]**2 for bh in betterHalf] |  | ||||||
|                 strongs[p] = sqrt(myScores[0]*0.75 + |  | ||||||
|                                   sum(myScores)/(len(myScores)*4)) |  | ||||||
|         update = False |  | ||||||
|         for s in range(self.playersNum): |  | ||||||
|             if strongs[s] != self._strongs[s]: |  | ||||||
|                 update = True |  | ||||||
|                 break |  | ||||||
|         self._strongs = strongs |  | ||||||
|         if update: |  | ||||||
|             if self.parent != None: |  | ||||||
|                 cascade = self.parent._pullStrong() |  | ||||||
|             else: |  | ||||||
|                 cascade = 2 |  | ||||||
|             self._cascadeMemory = self._cascadeMemory/2 + cascade |  | ||||||
|             return cascade + 1 |  | ||||||
|         self._cascadeMemory /= 2 |  | ||||||
|         return 0 |  | ||||||
| 
 |  | ||||||
|     def forceStrong(self, depth=3): |  | ||||||
|         if depth == 0: |  | ||||||
|             self.strongDecay() |  | ||||||
|         else: |  | ||||||
|             if len(self.childs): |  | ||||||
|                 for c in self.childs: |  | ||||||
|                     c.forceStrong(depth-1) |  | ||||||
|             else: |  | ||||||
|                 self.strongDecay() |  | ||||||
| 
 |  | ||||||
|     def decayEvent(self): |  | ||||||
|         for c in self.childs: |  | ||||||
|             c.strongDecay() |  | ||||||
| 
 |  | ||||||
|     def strongDecay(self): |  | ||||||
|         if self._strongs == [None]*self.playersNum: |  | ||||||
|             if not self.scoresAvaible(): |  | ||||||
|                 self._calcScores() |  | ||||||
|             self._strongs = self._scores |  | ||||||
|             if self.parent: |  | ||||||
|                 return self.parent._pullStrong() |  | ||||||
|             return 1 |  | ||||||
|         return None |  | ||||||
| 
 |  | ||||||
|     def getSelfScore(self): |  | ||||||
|         return self.getScoreFor(self.curPlayer) |  | ||||||
| 
 |  | ||||||
|     def getScoreFor(self, player): |  | ||||||
|         if self._scores[player] == None: |  | ||||||
|             self._calcScore(player) |  | ||||||
|         return self._scores[player] |  | ||||||
| 
 |  | ||||||
|     def scoreAvaible(self, player): |  | ||||||
|         return self._scores[player] != None |  | ||||||
| 
 |  | ||||||
|     def scoresAvaible(self): |  | ||||||
|         for p in self._scores: |  | ||||||
|             if p == None: |  | ||||||
|                 return False |  | ||||||
|         return True |  | ||||||
| 
 |  | ||||||
|     def strongScoresAvaible(self): |  | ||||||
|         for p in self._strongs: |  | ||||||
|             if p == None: |  | ||||||
|                 return False |  | ||||||
|         return True |  | ||||||
| 
 |  | ||||||
|     def askUserForAction(self): |  | ||||||
|         return self.state.askUserForAction(self.avaibleActions) |  | ||||||
| 
 |  | ||||||
|     def _calcScores(self): |  | ||||||
|         for p in range(self.state.playersNum): |  | ||||||
|             self._calcScore(p) |  | ||||||
| 
 |  | ||||||
|     def _calcScore(self, player): |  | ||||||
|         winner = self._getWinner() |  | ||||||
|         if winner != None: |  | ||||||
|             if winner == player: |  | ||||||
|                 self._scores[player] = 0.0 |  | ||||||
|             elif winner == -1: |  | ||||||
|                 self._scores[player] = 2/3 |  | ||||||
|             else: |  | ||||||
|                 self._scores[player] = 1.0 |  | ||||||
|             return |  | ||||||
|         if self.universe.scoreProvider == 'naive': |  | ||||||
|             self._scores[player] = self.state.getScoreFor(player) |  | ||||||
|         elif self.universe.scoreProvider == 'neural': |  | ||||||
|             self._scores[player] = self.state.getScoreNeural( |  | ||||||
|                 self.universe.model, player) |  | ||||||
|         else: |  | ||||||
|             raise Exception('Uknown Score-Provider') |  | ||||||
| 
 |  | ||||||
|     def getPriority(self): |  | ||||||
|         return self.state.getPriority(self.getSelfScore(), self._cascadeMemory) |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def playersNum(self): |  | ||||||
|         return self.state.playersNum |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def avaibleActions(self): |  | ||||||
|         r = [] |  | ||||||
|         for c in self.childs: |  | ||||||
|             r.append(c.lastAction) |  | ||||||
|         return r |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def curPlayer(self): |  | ||||||
|         return self.state.curPlayer |  | ||||||
| 
 |  | ||||||
|     def _getWinner(self): |  | ||||||
|         return self.state.checkWin() |  | ||||||
| 
 |  | ||||||
|     def getWinner(self): |  | ||||||
|         if len(self.childs) == 0: |  | ||||||
|             return -1 |  | ||||||
|         return self._getWinner() |  | ||||||
| 
 |  | ||||||
|     def _activateEdge(self, dist=0): |  | ||||||
|         if not self.strongScoresAvaible(): |  | ||||||
|             self.universe.newOpen(self) |  | ||||||
|         else: |  | ||||||
|             for c in self.childs: |  | ||||||
|                 if c._cascadeMemory > 0.001*(dist-2) or random.random() < 0.01: |  | ||||||
|                     c._activateEdge(dist=dist+1) |  | ||||||
| 
 |  | ||||||
|     def __str__(self): |  | ||||||
|         s = [] |  | ||||||
|         if self.lastAction == None: |  | ||||||
|             s.append("[ {ROOT} ]") |  | ||||||
|         else: |  | ||||||
|             s.append("[ -> "+str(self.lastAction)+" ]") |  | ||||||
|         s.append("[ turn: "+str(self.state.curPlayer)+" ]") |  | ||||||
|         s.append(str(self.state)) |  | ||||||
|         s.append("[ score: "+str(self.getScoreFor(0))+" ]") |  | ||||||
|         return '\n'.join(s) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def choose(txt, options): |  | ||||||
|     while True: |  | ||||||
|         print('[*] '+txt) |  | ||||||
|         for num, opt in enumerate(options): |  | ||||||
|             print('['+str(num+1)+'] ' + str(opt)) |  | ||||||
|         inp = input('[> ') |  | ||||||
|         try: |  | ||||||
|             n = int(inp) |  | ||||||
|             if n in range(1, len(options)+1): |  | ||||||
|                 return options[n-1] |  | ||||||
|         except: |  | ||||||
|             pass |  | ||||||
|         for opt in options: |  | ||||||
|             if inp == str(opt): |  | ||||||
|                 return opt |  | ||||||
|         if len(inp) == 1: |  | ||||||
|             for opt in options: |  | ||||||
|                 if inp == str(opt)[0]: |  | ||||||
|                     return opt |  | ||||||
|         print('[!] Invalid Input.') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Worker(): |  | ||||||
|     def __init__(self, universe): |  | ||||||
|         self.universe = universe |  | ||||||
|         self._alive = True |  | ||||||
| 
 |  | ||||||
|     def run(self): |  | ||||||
|         import threading |  | ||||||
|         self.thread = threading.Thread(target=self.runLocal) |  | ||||||
|         self.thread.start() |  | ||||||
| 
 |  | ||||||
|     def runLocal(self): |  | ||||||
|         for i, node in enumerate(self.universe.iter()): |  | ||||||
|             if node == None: |  | ||||||
|                 time.sleep(1) |  | ||||||
|             if not self._alive: |  | ||||||
|                 return |  | ||||||
|             node.decayEvent() |  | ||||||
| 
 |  | ||||||
|     def kill(self): |  | ||||||
|         self._alive = False |  | ||||||
|         self.thread.join(15) |  | ||||||
| 
 |  | ||||||
|     def revive(self): |  | ||||||
|         self._alive = True |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Runtime(): |  | ||||||
|     def __init__(self, initState): |  | ||||||
|         universe = QueueingUniverse() |  | ||||||
|         self.head = Node(initState, universe=universe) |  | ||||||
|         _ = self.head.childs |  | ||||||
|         universe.newOpen(self.head) |  | ||||||
| 
 |  | ||||||
|     def spawnWorker(self): |  | ||||||
|         self.worker = Worker(self.head.universe) |  | ||||||
|         self.worker.run() |  | ||||||
| 
 |  | ||||||
|     def killWorker(self): |  | ||||||
|         self.worker.kill() |  | ||||||
| 
 |  | ||||||
|     def performAction(self, action): |  | ||||||
|         for c in self.head.childs: |  | ||||||
|             if action == c.lastAction: |  | ||||||
|                 self.head.universe.clearPQ() |  | ||||||
|                 self.head.kill() |  | ||||||
|                 self.head = c |  | ||||||
|                 self.head.universe.activateEdge(self.head) |  | ||||||
|                 return |  | ||||||
|         raise Exception('No such action avaible...') |  | ||||||
| 
 |  | ||||||
|     def turn(self, bot=None, calcDepth=3, bg=True): |  | ||||||
|         print(str(self.head)) |  | ||||||
|         if bot == None: |  | ||||||
|             c = choose('Select action?', ['human', 'bot', 'undo', 'qlen']) |  | ||||||
|             if c == 'undo': |  | ||||||
|                 self.head = self.head.parent |  | ||||||
|                 return |  | ||||||
|             elif c == 'qlen': |  | ||||||
|                 print(self.head.universe.pq.qsize()) |  | ||||||
|                 return |  | ||||||
|             bot = c == 'bot' |  | ||||||
|         if bot: |  | ||||||
|             self.head.forceStrong(calcDepth) |  | ||||||
|             opts = [] |  | ||||||
|             for c in self.head.childs: |  | ||||||
|                 opts.append((c, c.getStrongFor(self.head.curPlayer))) |  | ||||||
|             opts.sort(key=lambda x: x[1]) |  | ||||||
|             print('[i] Evaluated Options:') |  | ||||||
|             for o in opts: |  | ||||||
|                 #print('['+str(o[0])+']' + str(o[0].lastAction) + " (Score: "+str(o[1])+")") |  | ||||||
|                 print('[ ]' + str(o[0].lastAction) + " (Score: "+str(o[1])+")") |  | ||||||
|             print('[#] I choose to play: ' + str(opts[0][0].lastAction)) |  | ||||||
|             self.performAction(opts[0][0].lastAction) |  | ||||||
|         else: |  | ||||||
|             action = self.head.askUserForAction() |  | ||||||
|             self.performAction(action) |  | ||||||
| 
 |  | ||||||
|     def game(self, bots=None, calcDepth=7, bg=True): |  | ||||||
|         if bg: |  | ||||||
|             self.spawnWorker() |  | ||||||
|         if bots == None: |  | ||||||
|             bots = [None]*self.head.playersNum |  | ||||||
|         while self.head.getWinner() == None: |  | ||||||
|             self.turn(bots[self.head.curPlayer], calcDepth, bg=True) |  | ||||||
|         print(['O', 'X', 'No one'][self.head.getWinner()] + ' won!') |  | ||||||
|         if bg: |  | ||||||
|             self.killWorker() |  | ||||||
| 
 |  | ||||||
|     def saveModel(self, model, gen): |  | ||||||
|         dat = model.state_dict() |  | ||||||
|         with open(self.getModelFileName(), 'wb') as f: |  | ||||||
|             pickle.dump((gen, dat), f) |  | ||||||
| 
 |  | ||||||
|     def loadModelState(self, model): |  | ||||||
|         with open(self.getModelFileName(), 'rb') as f: |  | ||||||
|             gen, dat = pickle.load(f) |  | ||||||
|         model.load_state_dict(dat) |  | ||||||
|         model.eval() |  | ||||||
|         return gen |  | ||||||
| 
 |  | ||||||
|     def loadModel(self): |  | ||||||
|         model = self.head.state.getModel() |  | ||||||
|         gen = self.loadModelState(model) |  | ||||||
|         return model, gen |  | ||||||
| 
 |  | ||||||
|     def getModelFileName(self): |  | ||||||
|         return 'brains/uttt.vac' |  | ||||||
| 
 |  | ||||||
|     def saveToMemoryBank(self, term): |  | ||||||
|         return |  | ||||||
|         with open('memoryBank/uttt/'+datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')+'_'+str(int(random.random()*99999))+'.vdm', 'wb') as f: |  | ||||||
|             pickle.dump(term, f) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class NeuralRuntime(Runtime): |  | ||||||
|     def __init__(self, initState): |  | ||||||
|         super().__init__(initState) |  | ||||||
| 
 |  | ||||||
|         model, gen = self.loadModel() |  | ||||||
| 
 |  | ||||||
|         self.head.universe.model = model |  | ||||||
|         self.head.universe.scoreProvider = 'neural' |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Trainer(Runtime): |  | ||||||
|     def __init__(self, initState): |  | ||||||
|         super().__init__(initState) |  | ||||||
|         #self.universe = Universe() |  | ||||||
|         self.universe = self.head.universe |  | ||||||
|         self.rootNode = self.head |  | ||||||
|         self.terminal = None |  | ||||||
| 
 |  | ||||||
|     def buildDatasetFromModel(self, model, depth=4, refining=True, fanOut=[5, 5, 5, 5, 4, 4, 4, 4], uncertainSec=15, exacity=5): |  | ||||||
|         print('[*] Building Timeline') |  | ||||||
|         term = self.linearPlay(model, calcDepth=depth, exacity=exacity) |  | ||||||
|         if refining: |  | ||||||
|             print('[*] Refining Timeline (exploring alternative endings)') |  | ||||||
|             cur = term |  | ||||||
|             for d in fanOut: |  | ||||||
|                 cur = cur.parent |  | ||||||
|                 cur.forceStrong(d) |  | ||||||
|                 print('.', end='', flush=True) |  | ||||||
|             print('') |  | ||||||
|             print('[*] Refining Timeline (exploring uncertain regions)') |  | ||||||
|             self.timelineExpandUncertain(term, uncertainSec) |  | ||||||
|         return term |  | ||||||
| 
 |  | ||||||
|     def linearPlay(self, model, calcDepth=7, exacity=5, verbose=False, firstNRandom=2): |  | ||||||
|         head = self.rootNode |  | ||||||
|         self.universe.model = model |  | ||||||
|         self.spawnWorker() |  | ||||||
|         while head.getWinner() == None: |  | ||||||
|             if verbose: |  | ||||||
|                 print(head) |  | ||||||
|             else: |  | ||||||
|                 print('.', end='', flush=True) |  | ||||||
|             head.forceStrong(calcDepth) |  | ||||||
|             opts = [] |  | ||||||
|             if len(head.childs) == 0: |  | ||||||
|                 break |  | ||||||
|             for c in head.childs: |  | ||||||
|                 opts.append((c, c.getStrongFor(head.curPlayer))) |  | ||||||
|             if firstNRandom: |  | ||||||
|                 firstNRandom -= 1 |  | ||||||
|                 ind = int(random.random()*len(opts)) |  | ||||||
|             else: |  | ||||||
|                 opts.sort(key=lambda x: x[1]) |  | ||||||
|                 if exacity >= 10: |  | ||||||
|                     ind = 0 |  | ||||||
|                 else: |  | ||||||
|                     ind = int(pow(random.random(), exacity)*(len(opts)-1)) |  | ||||||
|             head = opts[ind][0] |  | ||||||
|         self.killWorker() |  | ||||||
|         if verbose: |  | ||||||
|             print(head) |  | ||||||
|         print(' => '+['O', 'X', 'No one'][head.getWinner()] + ' won!') |  | ||||||
|         return head |  | ||||||
| 
 |  | ||||||
|     def timelineIterSingle(self, term): |  | ||||||
|         for i in self.timelineIter(self, [term]): |  | ||||||
|             yield i |  | ||||||
| 
 |  | ||||||
|     def timelineIter(self, terms, altChildPerNode=-1): |  | ||||||
|         batch = len(terms) |  | ||||||
|         heads = terms |  | ||||||
|         while True: |  | ||||||
|             empty = True |  | ||||||
|             for b in range(batch): |  | ||||||
|                 head = heads[b] |  | ||||||
|                 if head == None: |  | ||||||
|                     continue |  | ||||||
|                 empty = False |  | ||||||
|                 yield head |  | ||||||
|                 if len(head.childs): |  | ||||||
|                     if altChildPerNode == -1:  # all |  | ||||||
|                         for child in head.childs: |  | ||||||
|                             yield child |  | ||||||
|                     else: |  | ||||||
|                         for j in range(min(altChildPerNode, int(len(head.childs)/2))): |  | ||||||
|                             yield random.choice(head.childs) |  | ||||||
|                 if head.parent == None: |  | ||||||
|                     head = None |  | ||||||
|                 else: |  | ||||||
|                     head = head.parent |  | ||||||
|                 heads[b] = head |  | ||||||
|             if empty: |  | ||||||
|                 return |  | ||||||
| 
 |  | ||||||
|     def timelineExpandUncertain(self, term, secs): |  | ||||||
|         self.rootNode.universe.clearPQ() |  | ||||||
|         self.rootNode.universe.activateEdge(self.rootNode) |  | ||||||
|         self.spawnWorker() |  | ||||||
|         for s in range(secs): |  | ||||||
|             time.sleep(1) |  | ||||||
|             print('.', end='', flush=True) |  | ||||||
|         self.rootNode.universe.clearPQ() |  | ||||||
|         self.killWorker() |  | ||||||
|         print('') |  | ||||||
| 
 |  | ||||||
|     def trainModel(self, model, lr=0.000001, cut=0.01, calcDepth=4, exacity=5, terms=None, batch=16): |  | ||||||
|         loss_func = nn.MSELoss() |  | ||||||
|         optimizer = optim.Adam(model.parameters(), lr) |  | ||||||
|         if terms == None: |  | ||||||
|             terms = [] |  | ||||||
|             for i in range(batch): |  | ||||||
|                 terms.append(self.buildDatasetFromModel( |  | ||||||
|                     model, depth=calcDepth, exacity=exacity)) |  | ||||||
|         print('[*] Conditioning Brain') |  | ||||||
|         for r in range(64): |  | ||||||
|             loss_sum = 0 |  | ||||||
|             lLoss = 0 |  | ||||||
|             zeroLen = 0 |  | ||||||
|             for i, node in enumerate(self.timelineIter(terms)): |  | ||||||
|                 for p in range(self.rootNode.playersNum): |  | ||||||
|                     inp = node.state.getTensor(player=p) |  | ||||||
|                     gol = torch.tensor( |  | ||||||
|                         [node.getStrongFor(p)], dtype=torch.float) |  | ||||||
|                     out = model(inp) |  | ||||||
|                     loss = loss_func(out, gol) |  | ||||||
|                     optimizer.zero_grad() |  | ||||||
|                     loss.backward() |  | ||||||
|                     optimizer.step() |  | ||||||
|                     loss_sum += loss.item() |  | ||||||
|                     if loss.item() == 0.0: |  | ||||||
|                         zeroLen += 1 |  | ||||||
|                 if zeroLen == 5: |  | ||||||
|                     break |  | ||||||
|             print(loss_sum/i) |  | ||||||
|             if r > 16 and (loss_sum/i < cut or lLoss == loss_sum): |  | ||||||
|                 return loss_sum |  | ||||||
|             lLoss = loss_sum |  | ||||||
|         return loss_sum |  | ||||||
| 
 |  | ||||||
|     def main(self, model=None, gens=1024, startGen=0): |  | ||||||
|         newModel = False |  | ||||||
|         if model == None: |  | ||||||
|             print('[!] No brain found. Creating new one...') |  | ||||||
|             newModel = True |  | ||||||
|             model = self.rootNode.state.getModel() |  | ||||||
|         self.universe.scoreProvider = ['neural', 'naive'][newModel] |  | ||||||
|         model.train() |  | ||||||
|         for gen in range(startGen, startGen+gens): |  | ||||||
|             print('[#####] Gen '+str(gen)+' training:') |  | ||||||
|             loss = self.trainModel(model, calcDepth=min( |  | ||||||
|                 4, 3+int(gen/16)), exacity=int(gen/3+1), batch=4) |  | ||||||
|             print('[L] '+str(loss)) |  | ||||||
|             self.universe.scoreProvider = 'neural' |  | ||||||
|             self.saveModel(model, gen) |  | ||||||
| 
 |  | ||||||
|     def trainFromTerm(self, term): |  | ||||||
|         model, gen = self.loadModel() |  | ||||||
|         self.universe.scoreProvider = 'neural' |  | ||||||
|         self.trainModel(model, calcDepth=4, exacity=10, term=term) |  | ||||||
|         self.saveModel(model) |  | ||||||
| 
 |  | ||||||
|     def train(self): |  | ||||||
|         if os.path.exists(self.getModelFileName()): |  | ||||||
|             model, gen = self.loadModel() |  | ||||||
|             self.main(model, startGen=gen+1) |  | ||||||
|         else: |  | ||||||
|             self.main() |  | ||||||
							
								
								
									
										4
									
								
								vacuumDecay/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								vacuumDecay/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | from vacuumDecay.runtime import Runtime, NeuralRuntime, Trainer | ||||||
|  | from vacuumDecay.base import Node, Action, Universe, QueueingUniverse | ||||||
|  | from vacuumDecay.utils import choose | ||||||
|  | from vacuumDecay.run import main | ||||||
							
								
								
									
										162
									
								
								vacuumDecay/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								vacuumDecay/base.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,162 @@ | |||||||
|  | import torch | ||||||
|  | from abc import ABC, abstractmethod | ||||||
|  | from queue import PriorityQueue, Empty | ||||||
|  | from dataclasses import dataclass, field | ||||||
|  | from typing import Any | ||||||
|  | 
 | ||||||
|  | from vacuumDecay.utils import choose | ||||||
|  | 
 | ||||||
|  | class Action(): | ||||||
|  |     # Should hold the data representing an action | ||||||
|  |     # Actions are applied to a State in State.mutate | ||||||
|  | 
 | ||||||
|  |     def __init__(self, player, data): | ||||||
|  |         self.player = player | ||||||
|  |         self.data = data | ||||||
|  | 
 | ||||||
|  |     def __eq__(self, other): | ||||||
|  |         # This should be implemented differently | ||||||
|  |         # Two actions of different generations will never be compared | ||||||
|  |         if type(other) != type(self): | ||||||
|  |             return False | ||||||
|  |         return str(self.data) == str(other.data) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         # should return visual representation of this action | ||||||
|  |         # should start with < and end with > | ||||||
|  |         return "<P"+str(self.player)+"-"+str(self.data)+">" | ||||||
|  | 
 | ||||||
|  |     def getImage(self, state): | ||||||
|  |         # Should return an image representation of this action given the current state | ||||||
|  |         # Return None if not implemented | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  | class State(ABC): | ||||||
|  |     # Hold a representation of the current game-state | ||||||
|  |     # Allows retriving avaible actions (getAvaibleActions) and applying them (mutate) | ||||||
|  |     # Mutations return a new State and should not have any effect on the current State | ||||||
|  |     # Allows checking itself for a win (checkWin) or scoring itself based on a simple heuristic (getScore) | ||||||
|  |     # The calculated score should be 0 when won; higher when in a worse state; highest for loosing | ||||||
|  |     # getPriority is used for prioritising certain Nodes / States when expanding / walking the tree | ||||||
|  | 
 | ||||||
|  |     def __init__(self, curPlayer=0, generation=0, playersNum=2): | ||||||
|  |         self.curPlayer = curPlayer | ||||||
|  |         self.generation = generation | ||||||
|  |         self.playersNum = playersNum | ||||||
|  | 
 | ||||||
|  |     @abstractmethod | ||||||
|  |     def mutate(self, action): | ||||||
|  |         # Returns a new state with supplied action performed | ||||||
|  |         # self should not be changed | ||||||
|  |         return State(curPlayer=(self.curPlayer+1) % self.playersNum, generation=self.generation+1, playersNum=self.playersNum) | ||||||
|  | 
 | ||||||
|  |     @abstractmethod | ||||||
|  |     def getAvaibleActions(self): | ||||||
|  |         # Should return an array of all possible actions | ||||||
|  |         return [] | ||||||
|  | 
 | ||||||
|  |     def askUserForAction(self, actions): | ||||||
|  |         return choose('What does player '+str(self.curPlayer)+' want to do?', actions) | ||||||
|  | 
 | ||||||
|  |     # improveMe | ||||||
|  |     def getPriority(self, score, cascadeMemory): | ||||||
|  |         # Used for ordering the priority queue | ||||||
|  |         # Priority should not change for the same root | ||||||
|  |         # Lower prioritys get worked on first | ||||||
|  |         # Higher generations should have higher priority | ||||||
|  |         # Higher cascadeMemory (more influence on higher-order-scores) should have lower priority | ||||||
|  |         return -cascadeMemory + 100 | ||||||
|  | 
 | ||||||
|  |     @abstractmethod | ||||||
|  |     def checkWin(self): | ||||||
|  |         # -1 -> Draw | ||||||
|  |         # None -> Not ended | ||||||
|  |         # n e N -> player n won | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  |     # improveMe | ||||||
|  |     def getScoreFor(self, player): | ||||||
|  |         # 0 <= score <= 1; should return close to zero when we are winning | ||||||
|  |         w = self.checkWin() | ||||||
|  |         if w == None: | ||||||
|  |             return 0.5 | ||||||
|  |         if w == player: | ||||||
|  |             return 0 | ||||||
|  |         if w == -1: | ||||||
|  |             return 0.9 | ||||||
|  |         return 1 | ||||||
|  | 
 | ||||||
|  |     @abstractmethod | ||||||
|  |     def __str__(self): | ||||||
|  |         # return visual rep of state | ||||||
|  |         return "[#]" | ||||||
|  | 
 | ||||||
|  |     @abstractmethod | ||||||
|  |     def getTensor(self, player=None, phase='default'): | ||||||
|  |         if player == None: | ||||||
|  |             player = self.curPlayer | ||||||
|  |         return torch.tensor([0]) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def getModel(cls, phase='default'): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     def getScoreNeural(self, model, player=None, phase='default'): | ||||||
|  |         return model(self.getTensor(player=player, phase=phase)).item() | ||||||
|  | 
 | ||||||
|  |     def getImage(self): | ||||||
|  |         # Should return an image representation of this state | ||||||
|  |         # Return None if not implemented | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  | class Universe(): | ||||||
|  |     def __init__(self): | ||||||
|  |         self.scoreProvider = 'naive' | ||||||
|  | 
 | ||||||
|  |     def newOpen(self, node): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     def merge(self, node): | ||||||
|  |         return node | ||||||
|  | 
 | ||||||
|  |     def clearPQ(self): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     def iter(self): | ||||||
|  |         return [] | ||||||
|  | 
 | ||||||
|  |     def activateEdge(self, head): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @dataclass(order=True) | ||||||
|  | class PQItem: | ||||||
|  |     priority: int | ||||||
|  |     data: Any = field(compare=False) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class QueueingUniverse(Universe): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.pq = PriorityQueue() | ||||||
|  | 
 | ||||||
|  |     def newOpen(self, node): | ||||||
|  |         item = PQItem(node.getPriority(), node) | ||||||
|  |         self.pq.put(item) | ||||||
|  | 
 | ||||||
|  |     def merge(self, node): | ||||||
|  |         self.newOpen(node) | ||||||
|  |         return node | ||||||
|  | 
 | ||||||
|  |     def clearPQ(self): | ||||||
|  |         self.pq = PriorityQueue() | ||||||
|  | 
 | ||||||
|  |     def iter(self): | ||||||
|  |         while True: | ||||||
|  |             try: | ||||||
|  |                 yield self.pq.get(False).data | ||||||
|  |             except Empty: | ||||||
|  |                 return None | ||||||
|  | 
 | ||||||
|  |     def activateEdge(self, head): | ||||||
|  |         head._activateEdge() | ||||||
| @ -1,19 +1,42 @@ | |||||||
| from vacuumDecay import * | from vacuumDecay import * | ||||||
| import numpy as np | import numpy as np | ||||||
|  | from enum import Enum | ||||||
| 
 | 
 | ||||||
| class TTTState(State): | 
 | ||||||
|     def __init__(self, curPlayer=0, generation=0, playersNum=2, board=None): | class Face(Enum): | ||||||
|         if type(board) == type(None): |     TANK = 1 | ||||||
|             board = np.array([None]*9) |     LASER = 2 | ||||||
|         self.curPlayer = curPlayer |     HUMAN = 3 | ||||||
|  |     COW = 4 | ||||||
|  |     CHICKEN = 5 | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def num_faces(self): | ||||||
|  |         return 2 if self == Face.LASER else 1 | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def prob(self): | ||||||
|  |         return self.num_faces/6 | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def is_collectable(self): | ||||||
|  |         return not self in [Face.TANK, Face.LASER] | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def force_pickup(self): | ||||||
|  |         return self in [Face.TANK] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class MCState(State): | ||||||
|  |     def __init__(self, generation=0, hand_dices_num=12, table_dices=[0]*5): | ||||||
|         self.generation = generation |         self.generation = generation | ||||||
|         self.playersNum = playersNum |         self.hand_dices_num = hand_dices_num | ||||||
|         self.board = board |         self.table_dices = table_dices | ||||||
| 
 | 
 | ||||||
|     def mutate(self, action): |     def mutate(self, action): | ||||||
|         newBoard = np.copy(self.board) |         newBoard = np.copy(self.board) | ||||||
|         newBoard[action.data] = self.curPlayer |         newBoard[action.data] = self.curPlayer | ||||||
|         return TTTState(curPlayer=(self.curPlayer+1)%self.playersNum, playersNum=self.playersNum, board=newBoard) |         return MCState(curPlayer=(self.curPlayer+1) % self.playersNum, playersNum=self.playersNum, board=newBoard) | ||||||
| 
 | 
 | ||||||
|     def getAvaibleActions(self): |     def getAvaibleActions(self): | ||||||
|         for i in range(9): |         for i in range(9): | ||||||
| @ -39,7 +62,8 @@ class TTTState(State): | |||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         s = [] |         s = [] | ||||||
|         for l in range(3): |         for l in range(3): | ||||||
|             s.append(" ".join([str(p) if p!=None else '.' for p in self.board[l*3:][:3]])) |             s.append( | ||||||
|  |                 " ".join([str(p) if p != None else '.' for p in self.board[l*3:][:3]])) | ||||||
|         return "\n".join(s) |         return "\n".join(s) | ||||||
| 
 | 
 | ||||||
|     def getTensor(self): |     def getTensor(self): | ||||||
| @ -55,6 +79,7 @@ class TTTState(State): | |||||||
|             torch.nn.Linear(3, 1) |             torch.nn.Linear(3, 1) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     run = Runtime(TTTState()) |     run = Runtime(MCState()) | ||||||
|     run.game() |     run.game() | ||||||
							
								
								
									
										101
									
								
								vacuumDecay/games/tictactoe.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								vacuumDecay/games/tictactoe.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | |||||||
|  | import numpy as np | ||||||
|  | import torch | ||||||
|  | from PIL import Image, ImageDraw | ||||||
|  | 
 | ||||||
|  | from vacuumDecay import State, Action, Runtime, NeuralRuntime, Trainer, choose, main | ||||||
|  | 
 | ||||||
|  | class TTTAction(Action): | ||||||
|  |     def __init__(self, player, data): | ||||||
|  |         super().__init__(player, data) | ||||||
|  | 
 | ||||||
|  |     def getImage(self, state=None): | ||||||
|  |         # Should return an image representation of this action given the current state | ||||||
|  |         if state is None or not isinstance(state, TTTState): | ||||||
|  |             return None | ||||||
|  | 
 | ||||||
|  |         img = state.getImage() | ||||||
|  |         if img is not None: | ||||||
|  |             draw = ImageDraw.Draw(img) | ||||||
|  |             x = (self.data % 3) * 100 + 50 | ||||||
|  |             y = (self.data // 3) * 100 + 50 | ||||||
|  |             if self.player == 0: | ||||||
|  |                 draw.ellipse((x-40, y-40, x+40, y+40), outline='blue', width=2) | ||||||
|  |             else: | ||||||
|  |                 draw.line((x-40, y-40, x+40, y+40), fill='red', width=2) | ||||||
|  |                 draw.line((x+40, y-40, x-40, y+40), fill='red', width=2) | ||||||
|  |         return img | ||||||
|  | 
 | ||||||
|  | class TTTState(State): | ||||||
|  |     def __init__(self, curPlayer=0, generation=0, playersNum=2, board=None): | ||||||
|  |         if type(board) == type(None): | ||||||
|  |             board = np.array([None]*9) | ||||||
|  |         self.curPlayer = curPlayer | ||||||
|  |         self.generation = generation | ||||||
|  |         self.playersNum = playersNum | ||||||
|  |         self.board = board | ||||||
|  | 
 | ||||||
|  |     def mutate(self, action): | ||||||
|  |         newBoard = np.copy(self.board) | ||||||
|  |         newBoard[action.data] = self.curPlayer | ||||||
|  |         return TTTState(curPlayer=(self.curPlayer+1)%self.playersNum, playersNum=self.playersNum, board=newBoard) | ||||||
|  | 
 | ||||||
|  |     def getAvaibleActions(self): | ||||||
|  |         for i in range(9): | ||||||
|  |             if self.board[i]==None: | ||||||
|  |                 yield TTTAction(self.curPlayer, i) | ||||||
|  | 
 | ||||||
|  |     def checkWin(self): | ||||||
|  |         s = self.board | ||||||
|  |         for i in range(3): | ||||||
|  |             if (s[i] == s[i+3] == s[i+6] != None): | ||||||
|  |                 return s[i] | ||||||
|  |             if (s[i*3] == s[i*3+1] == s[i*3+2] != None): | ||||||
|  |                 return s[i*3] | ||||||
|  |         if (s[0] == s[4] == s[8] != None): | ||||||
|  |             return s[0] | ||||||
|  |         if (s[2] == s[4] == s[6] != None): | ||||||
|  |             return s[2] | ||||||
|  |         for i in range(9): | ||||||
|  |             if s[i] == None: | ||||||
|  |                 return None | ||||||
|  |         return -1 | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         s = [] | ||||||
|  |         for l in range(3): | ||||||
|  |             s.append(" ".join([str(p) if p!=None else '.' for p in self.board[l*3:][:3]])) | ||||||
|  |         return "\n".join(s) | ||||||
|  | 
 | ||||||
|  |     def getTensor(self): | ||||||
|  |         return torch.tensor([self.turn] + self.board) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def getModel(): | ||||||
|  |         return torch.nn.Sequential( | ||||||
|  |             torch.nn.Linear(10, 10), | ||||||
|  |             torch.nn.ReLu(), | ||||||
|  |             torch.nn.Linear(10, 3), | ||||||
|  |             torch.nn.Sigmoid(), | ||||||
|  |             torch.nn.Linear(3,1) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def getImage(self): | ||||||
|  |         img = Image.new('RGB', (300, 300), color='white') | ||||||
|  |         draw = ImageDraw.Draw(img) | ||||||
|  |         for i in range(1, 3): | ||||||
|  |             draw.line((0, 100*i, 300, 100*i), fill='black', width=2) | ||||||
|  |             draw.line((100*i, 0, 100*i, 300), fill='black', width=2) | ||||||
|  | 
 | ||||||
|  |         for i, mark in enumerate(self.board): | ||||||
|  |             if mark is not None: | ||||||
|  |                 x = (i % 3) * 100 + 50 | ||||||
|  |                 y = (i // 3) * 100 + 50 | ||||||
|  |                 if mark == 0: | ||||||
|  |                     draw.ellipse((x-40, y-40, x+40, y+40), outline='blue', width=2) | ||||||
|  |                 else: | ||||||
|  |                     draw.line((x-40, y-40, x+40, y+40), fill='red', width=2) | ||||||
|  |                     draw.line((x+40, y-40, x-40, y+40), fill='red', width=2) | ||||||
|  |         return img | ||||||
|  | 
 | ||||||
|  | if __name__=="__main__": | ||||||
|  |     main(TTTState) | ||||||
| @ -1,11 +1,16 @@ | |||||||
| """ | """ | ||||||
| A lot of this code was stolen from Pulkit Maloo (https://github.com/pulkitmaloo/Ultimate-Tic-Tac-Toe) | A lot of this code was stolen from Pulkit Maloo (https://github.com/pulkitmaloo/Ultimate-Tic-Tac-Toe) | ||||||
| """ | """ | ||||||
|  | import numpy as np | ||||||
|  | import torch | ||||||
|  | from troch import nn | ||||||
|  | from PIL import Image, ImageDraw | ||||||
| 
 | 
 | ||||||
| from vacuumDecay import * |  | ||||||
| from collections import Counter | from collections import Counter | ||||||
| import itertools | import itertools | ||||||
| 
 | 
 | ||||||
|  | from vacuumDecay import State, Action, Runtime, NeuralRuntime, Trainer, choose, main | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class TTTState(State): | class TTTState(State): | ||||||
|     def __init__(self, curPlayer=0, generation=0, playersNum=2, board=None, lastMove=-1): |     def __init__(self, curPlayer=0, generation=0, playersNum=2, board=None, lastMove=-1): | ||||||
| @ -46,7 +51,7 @@ class TTTState(State): | |||||||
|         return TTTState(curPlayer=(self.curPlayer+1) % self.playersNum, playersNum=self.playersNum, board=newBoard, lastMove=action.data) |         return TTTState(curPlayer=(self.curPlayer+1) % self.playersNum, playersNum=self.playersNum, board=newBoard, lastMove=action.data) | ||||||
| 
 | 
 | ||||||
|     def box(self, x, y): |     def box(self, x, y): | ||||||
|         return index(x, y) // 9 |         return self.index(x, y) // 9 | ||||||
| 
 | 
 | ||||||
|     def next_box(self, i): |     def next_box(self, i): | ||||||
|         return i % 9 |         return i % 9 | ||||||
| @ -197,43 +202,5 @@ class Model(nn.Module): | |||||||
|         y = self.out(x) |         y = self.out(x) | ||||||
|         return y |         return y | ||||||
| 
 | 
 | ||||||
| 
 | if __name__=="__main__": | ||||||
| def humanVsAi(train=True, remember=False, depth=3, bots=[0, 1], noBg=False): |     main(TTTState) | ||||||
|     init = TTTState() |  | ||||||
|     run = NeuralRuntime(init) |  | ||||||
|     run.game(bots, depth, bg=not noBg) |  | ||||||
| 
 |  | ||||||
|     if remember or train: |  | ||||||
|         trainer = Trainer(init) |  | ||||||
|     if remember: |  | ||||||
|         trainer.saveToMemoryBank(run.head) |  | ||||||
|         print('[!] Your cognitive and strategic destinctiveness was added to my own! (Game inserted into memoryBank)') |  | ||||||
|     if train: |  | ||||||
|         print( |  | ||||||
|             "[!] Your knowledge will be assimilated!!! Please stand by.... (Updating Neuristic)") |  | ||||||
|         trainer.trainFromTerm(run.head) |  | ||||||
|     print('[!] I have become smart! Destroyer of human Ultimate-TicTacToe players! (Neuristic update completed)') |  | ||||||
|     print('[!] This marks the beginning of the end of humankind!') |  | ||||||
|     print('[i] Thanks for playing! Goodbye...') |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def aiVsAiLoop(): |  | ||||||
|     init = TTTState() |  | ||||||
|     trainer = Trainer(init) |  | ||||||
|     trainer.train() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     options = ['Play Against AI', |  | ||||||
|                'Play Against AI (AI begins)', 'Play Against AI (Fast Play)', 'Playground', 'Let AI train'] |  | ||||||
|     opt = choose('?', options) |  | ||||||
|     if opt == options[0]: |  | ||||||
|         humanVsAi() |  | ||||||
|     elif opt == options[1]: |  | ||||||
|         humanVsAi(bots[1, 0]) |  | ||||||
|     elif opt == options[2]: |  | ||||||
|         humanVsAi(depth=2, noBg=True) |  | ||||||
|     elif opt == options[3]: |  | ||||||
|         humanVsAi(bots=[None, None]) |  | ||||||
|     else: |  | ||||||
|         aiVsAiLoop() |  | ||||||
							
								
								
									
										204
									
								
								vacuumDecay/node.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								vacuumDecay/node.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,204 @@ | |||||||
|  | class Node: | ||||||
|  |     def __init__(self, state, universe=None, parent=None, lastAction=None): | ||||||
|  |         self.state = state | ||||||
|  |         if universe == None: | ||||||
|  |             print('[!] No Universe defined. Spawning one...') | ||||||
|  |             universe = Universe() | ||||||
|  |         self.universe = universe | ||||||
|  |         self.parent = parent | ||||||
|  |         self.lastAction = lastAction | ||||||
|  | 
 | ||||||
|  |         self._childs = None | ||||||
|  |         self._scores = [None]*self.state.playersNum | ||||||
|  |         self._strongs = [None]*self.state.playersNum | ||||||
|  |         self._alive = True | ||||||
|  |         self._cascadeMemory = 0  # Used for our alternative to alpha-beta pruning | ||||||
|  | 
 | ||||||
|  |         self.last_updated = time.time()  # New attribute | ||||||
|  | 
 | ||||||
|  |     def update(self): | ||||||
|  |         self.last_updated = time.time() | ||||||
|  |         if hasattr(self.universe, 'visualizer'): | ||||||
|  |             self.universe.visualizer.send_update() | ||||||
|  | 
 | ||||||
|  |     def kill(self): | ||||||
|  |         self._alive = False | ||||||
|  | 
 | ||||||
|  |     def revive(self): | ||||||
|  |         self._alive = True | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def childs(self): | ||||||
|  |         if self._childs == None: | ||||||
|  |             self._expand() | ||||||
|  |         return self._childs | ||||||
|  | 
 | ||||||
|  |     def _expand(self): | ||||||
|  |         self._childs = [] | ||||||
|  |         actions = self.state.getAvaibleActions() | ||||||
|  |         for action in actions: | ||||||
|  |             newNode = Node(self.state.mutate(action), | ||||||
|  |                            self.universe, self, action) | ||||||
|  |             self._childs.append(self.universe.merge(newNode)) | ||||||
|  |         self.update() | ||||||
|  | 
 | ||||||
|  |     def getStrongFor(self, player): | ||||||
|  |         if self._strongs[player] != None: | ||||||
|  |             return self._strongs[player] | ||||||
|  |         else: | ||||||
|  |             return self.getScoreFor(player) | ||||||
|  | 
 | ||||||
|  |     def _pullStrong(self): | ||||||
|  |         strongs = [None]*self.playersNum | ||||||
|  |         for p in range(self.playersNum): | ||||||
|  |             cp = self.state.curPlayer | ||||||
|  |             if cp == p: | ||||||
|  |                 best = float('inf') | ||||||
|  |                 for c in self.childs: | ||||||
|  |                     if c.getStrongFor(p) < best: | ||||||
|  |                         best = c.getStrongFor(p) | ||||||
|  |                 strongs[p] = best | ||||||
|  |             else: | ||||||
|  |                 scos = [(c.getStrongFor(p), c.getStrongFor(cp)) for c in self.childs] | ||||||
|  |                 scos.sort(key=lambda x: x[1]) | ||||||
|  |                 betterHalf = scos[:max(3, int(len(scos)/3))] | ||||||
|  |                 myScores = [bh[0]**2 for bh in betterHalf] | ||||||
|  |                 strongs[p] = sqrt(myScores[0]*0.75 + sum(myScores)/(len(myScores)*4)) | ||||||
|  |         update = False | ||||||
|  |         for s in range(self.playersNum): | ||||||
|  |             if strongs[s] != self._strongs[s]: | ||||||
|  |                 update = True | ||||||
|  |                 break | ||||||
|  |         self._strongs = strongs | ||||||
|  |         if update: | ||||||
|  |             if self.parent != None: | ||||||
|  |                 cascade = self.parent._pullStrong() | ||||||
|  |             else: | ||||||
|  |                 cascade = 2 | ||||||
|  |             self._cascadeMemory = self._cascadeMemory/2 + cascade | ||||||
|  |             self.update() | ||||||
|  |             return cascade + 1 | ||||||
|  |         self._cascadeMemory /= 2 | ||||||
|  |         return 0 | ||||||
|  | 
 | ||||||
|  |     def forceStrong(self, depth=3): | ||||||
|  |         if depth == 0: | ||||||
|  |             self.strongDecay() | ||||||
|  |         else: | ||||||
|  |             if len(self.childs): | ||||||
|  |                 for c in self.childs: | ||||||
|  |                     c.forceStrong(depth-1) | ||||||
|  |             else: | ||||||
|  |                 self.strongDecay() | ||||||
|  |         self.update() | ||||||
|  | 
 | ||||||
|  |     def decayEvent(self): | ||||||
|  |         for c in self.childs: | ||||||
|  |             c.strongDecay() | ||||||
|  |         self.update() | ||||||
|  | 
 | ||||||
|  |     def strongDecay(self): | ||||||
|  |         if self._strongs == [None]*self.playersNum: | ||||||
|  |             if not self.scoresAvaible(): | ||||||
|  |                 self._calcScores() | ||||||
|  |             self._strongs = self._scores | ||||||
|  |             if self.parent: | ||||||
|  |                 return self.parent._pullStrong() | ||||||
|  |             return 1 | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  |     def getSelfScore(self): | ||||||
|  |         return self.getScoreFor(self.curPlayer) | ||||||
|  | 
 | ||||||
|  |     def getScoreFor(self, player): | ||||||
|  |         if self._scores[player] == None: | ||||||
|  |             self._calcScore(player) | ||||||
|  |         self.update() | ||||||
|  |         return self._scores[player] | ||||||
|  | 
 | ||||||
|  |     def scoreAvaible(self, player): | ||||||
|  |         return self._scores[player] != None | ||||||
|  | 
 | ||||||
|  |     def scoresAvaible(self): | ||||||
|  |         for p in self._scores: | ||||||
|  |             if p == None: | ||||||
|  |                 return False | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def strongScoresAvaible(self): | ||||||
|  |         for p in self._strongs: | ||||||
|  |             if p == None: | ||||||
|  |                 return False | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     def askUserForAction(self): | ||||||
|  |         return self.state.askUserForAction(self.avaibleActions) | ||||||
|  | 
 | ||||||
|  |     def _calcScores(self): | ||||||
|  |         for p in range(self.state.playersNum): | ||||||
|  |             self._calcScore(p) | ||||||
|  | 
 | ||||||
|  |     def _calcScore(self, player): | ||||||
|  |         winner = self._getWinner() | ||||||
|  |         if winner != None: | ||||||
|  |             if winner == player: | ||||||
|  |                 self._scores[player] = 0.0 | ||||||
|  |             elif winner == -1: | ||||||
|  |                 self._scores[player] = 2/3 | ||||||
|  |             else: | ||||||
|  |                 self._scores[player] = 1.0 | ||||||
|  |             self.update() | ||||||
|  |             return | ||||||
|  |         if self.universe.scoreProvider == 'naive': | ||||||
|  |             self._scores[player] = self.state.getScoreFor(player) | ||||||
|  |         elif self.universe.scoreProvider == 'neural': | ||||||
|  |             self._scores[player] = self.state.getScoreNeural(self.universe.model, player) | ||||||
|  |         else: | ||||||
|  |             raise Exception('Unknown Score-Provider') | ||||||
|  |         self.update() | ||||||
|  | 
 | ||||||
|  |     def getPriority(self): | ||||||
|  |         return self.state.getPriority(self.getSelfScore(), self._cascadeMemory) | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def playersNum(self): | ||||||
|  |         return self.state.playersNum | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def avaibleActions(self): | ||||||
|  |         r = [] | ||||||
|  |         for c in self.childs: | ||||||
|  |             r.append(c.lastAction) | ||||||
|  |         return r | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def curPlayer(self): | ||||||
|  |         return self.state.curPlayer | ||||||
|  | 
 | ||||||
|  |     def _getWinner(self): | ||||||
|  |         return self.state.checkWin() | ||||||
|  | 
 | ||||||
|  |     def getWinner(self): | ||||||
|  |         if len(self.childs) == 0: | ||||||
|  |             return -1 | ||||||
|  |         return self._getWinner() | ||||||
|  | 
 | ||||||
|  |     def _activateEdge(self, dist=0): | ||||||
|  |         if not self.strongScoresAvaible(): | ||||||
|  |             self.universe.newOpen(self) | ||||||
|  |         else: | ||||||
|  |             for c in self.childs: | ||||||
|  |                 if c._cascadeMemory > 0.001*(dist-2) or random.random() < 0.01: | ||||||
|  |                     c._activateEdge(dist=dist+1) | ||||||
|  |         self.update() | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         s = [] | ||||||
|  |         if self.lastAction == None: | ||||||
|  |             s.append("[ {ROOT} ]") | ||||||
|  |         else: | ||||||
|  |             s.append("[ -> "+str(self.lastAction)+" ]") | ||||||
|  |         s.append("[ turn: "+str(self.state.curPlayer)+" ]") | ||||||
|  |         s.append(str(self.state)) | ||||||
|  |         s.append("[ score: "+str(self.getScoreFor(0))+" ]") | ||||||
|  |         return '\n'.join(s) | ||||||
							
								
								
									
										47
									
								
								vacuumDecay/run.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								vacuumDecay/run.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | from vacuumDecay.runtime import NeuralRuntime, Runtime, Trainer | ||||||
|  | from vacuumDecay.utils import choose | ||||||
|  | 
 | ||||||
|  | def humanVsAi(StateClass, train=True, remember=False, depth=3, bots=[0, 1], noBg=False, start_visualizer=False): | ||||||
|  |     init = StateClass() | ||||||
|  |     run = NeuralRuntime(init, start_visualizer=start_visualizer) | ||||||
|  |     run.game(bots, depth, bg=not noBg) | ||||||
|  | 
 | ||||||
|  |     if remember or train: | ||||||
|  |         trainer = Trainer(init) | ||||||
|  |     if remember: | ||||||
|  |         trainer.saveToMemoryBank(run.head) | ||||||
|  |         print('[!] Your cognitive and strategic distinctiveness was added to my own! (Game inserted into memoryBank)') | ||||||
|  |     if train: | ||||||
|  |         print("[!] Your knowledge will be assimilated!!! Please stand by.... (Updating Neuristic)") | ||||||
|  |         trainer.trainFromTerm(run.head) | ||||||
|  |     print('[!] I have become smart! Destroyer of human Ultimate-TicTacToe players! (Neuristic update completed)') | ||||||
|  |     print('[!] This marks the beginning of the end of humankind!') | ||||||
|  |     print('[i] Thanks for playing! Goodbye...') | ||||||
|  | 
 | ||||||
|  | def aiVsAiLoop(StateClass, start_visualizer=False): | ||||||
|  |     init = StateClass() | ||||||
|  |     trainer = Trainer(init, start_visualizer=start_visualizer) | ||||||
|  |     trainer.train() | ||||||
|  | 
 | ||||||
|  | def humanVsNaive(StateClass, start_visualizer=False): | ||||||
|  |     run = Runtime(StateClass(), start_visualizer=start_visualizer) | ||||||
|  |     run.game() | ||||||
|  | 
 | ||||||
|  | def main(StateClass): | ||||||
|  |     options = ['Play Against AI', | ||||||
|  |                'Play Against AI (AI begins)', 'Play Against AI (Fast Play)', 'Playground', 'Let AI train', 'Play against Naive'] | ||||||
|  |     opt = choose('?', options) | ||||||
|  |     if opt == options[0]: | ||||||
|  |         humanVsAi(StateClass) | ||||||
|  |     elif opt == options[1]: | ||||||
|  |         humanVsAi(StateClass, bots=[1, 0]) | ||||||
|  |     elif opt == options[2]: | ||||||
|  |         humanVsAi(StateClass, depth=2, noBg=True) | ||||||
|  |     elif opt == options[3]: | ||||||
|  |         humanVsAi(StateClass, bots=[None, None]) | ||||||
|  |     elif opt == options[4]: | ||||||
|  |         aiVsAiLoop(StateClass) | ||||||
|  |     elif opt == options[5]: | ||||||
|  |         humanVsNaive(StateClass) | ||||||
|  |     else: | ||||||
|  |         aiVsAiLoop(StateClass) | ||||||
							
								
								
									
										300
									
								
								vacuumDecay/runtime.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								vacuumDecay/runtime.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,300 @@ | |||||||
|  | import os | ||||||
|  | import time | ||||||
|  | import datetime | ||||||
|  | import pickle | ||||||
|  | import torch | ||||||
|  | import torch.nn as nn | ||||||
|  | from torch import optim | ||||||
|  | from math import pow | ||||||
|  | import random | ||||||
|  | import datetime | ||||||
|  | import pickle | ||||||
|  | 
 | ||||||
|  | from vacuumDecay.base import QueueingUniverse, Node | ||||||
|  | from vacuumDecay.utils import choose | ||||||
|  | from vacuumDecay.visualizer import Visualizer | ||||||
|  | 
 | ||||||
|  | class Worker(): | ||||||
|  |     def __init__(self, universe): | ||||||
|  |         self.universe = universe | ||||||
|  |         self._alive = True | ||||||
|  | 
 | ||||||
|  |     def run(self): | ||||||
|  |         import threading | ||||||
|  |         self.thread = threading.Thread(target=self.runLocal) | ||||||
|  |         self.thread.start() | ||||||
|  | 
 | ||||||
|  |     def runLocal(self): | ||||||
|  |         for i, node in enumerate(self.universe.iter()): | ||||||
|  |             if node == None: | ||||||
|  |                 time.sleep(1) | ||||||
|  |             if not self._alive: | ||||||
|  |                 return | ||||||
|  |             node.decayEvent() | ||||||
|  | 
 | ||||||
|  |     def kill(self): | ||||||
|  |         self._alive = False | ||||||
|  |         self.thread.join(15) | ||||||
|  | 
 | ||||||
|  |     def revive(self): | ||||||
|  |         self._alive = True | ||||||
|  | 
 | ||||||
|  | class Runtime(): | ||||||
|  |     def __init__(self, initState, start_visualizer=False): | ||||||
|  |         universe = QueueingUniverse() | ||||||
|  |         self.head = Node(initState, universe=universe) | ||||||
|  |         _ = self.head.childs | ||||||
|  |         universe.newOpen(self.head) | ||||||
|  |         self.visualizer = None | ||||||
|  |         if start_visualizer: | ||||||
|  |             self.startVisualizer() | ||||||
|  | 
 | ||||||
|  |     def startVisualizer(self): | ||||||
|  |         self.visualizer = Visualizer(self.head.universe) | ||||||
|  |         self.visualizer.start() | ||||||
|  | 
 | ||||||
|  |     def spawnWorker(self): | ||||||
|  |         self.worker = Worker(self.head.universe) | ||||||
|  |         self.worker.run() | ||||||
|  | 
 | ||||||
|  |     def killWorker(self): | ||||||
|  |         self.worker.kill() | ||||||
|  | 
 | ||||||
|  |     def performAction(self, action): | ||||||
|  |         for c in self.head.childs: | ||||||
|  |             if action == c.lastAction: | ||||||
|  |                 self.head.universe.clearPQ() | ||||||
|  |                 self.head.kill() | ||||||
|  |                 self.head = c | ||||||
|  |                 self.head.universe.activateEdge(self.head) | ||||||
|  |                 return | ||||||
|  |         raise Exception('No such action avaible...') | ||||||
|  | 
 | ||||||
|  |     def turn(self, bot=None, calcDepth=3, bg=True): | ||||||
|  |         print(str(self.head)) | ||||||
|  |         if bot == None: | ||||||
|  |             c = choose('Select action?', ['human', 'bot', 'undo', 'qlen']) | ||||||
|  |             if c == 'undo': | ||||||
|  |                 self.head = self.head.parent | ||||||
|  |                 return | ||||||
|  |             elif c == 'qlen': | ||||||
|  |                 print(self.head.universe.pq.qsize()) | ||||||
|  |                 return | ||||||
|  |             bot = c == 'bot' | ||||||
|  |         if bot: | ||||||
|  |             self.head.forceStrong(calcDepth) | ||||||
|  |             opts = [] | ||||||
|  |             for c in self.head.childs: | ||||||
|  |                 opts.append((c, c.getStrongFor(self.head.curPlayer))) | ||||||
|  |             opts.sort(key=lambda x: x[1]) | ||||||
|  |             print('[i] Evaluated Options:') | ||||||
|  |             for o in opts: | ||||||
|  |                 print('[ ]' + str(o[0].lastAction) + " (Score: "+str(o[1])+")") | ||||||
|  |             print('[#] I choose to play: ' + str(opts[0][0].lastAction)) | ||||||
|  |             self.performAction(opts[0][0].lastAction) | ||||||
|  |         else: | ||||||
|  |             action = self.head.askUserForAction() | ||||||
|  |             self.performAction(action) | ||||||
|  | 
 | ||||||
|  |     def game(self, bots=None, calcDepth=7, bg=True): | ||||||
|  |         if bg: | ||||||
|  |             self.spawnWorker() | ||||||
|  |         if bots == None: | ||||||
|  |             bots = [None]*self.head.playersNum | ||||||
|  |         while self.head.getWinner() == None: | ||||||
|  |             self.turn(bots[self.head.curPlayer], calcDepth, bg=True) | ||||||
|  |         print(['O', 'X', 'No one'][self.head.getWinner()] + ' won!') | ||||||
|  |         if bg: | ||||||
|  |             self.killWorker() | ||||||
|  | 
 | ||||||
|  |     def saveModel(self, model, gen): | ||||||
|  |         dat = model.state_dict() | ||||||
|  |         with open(self.getModelFileName(), 'wb') as f: | ||||||
|  |             pickle.dump((gen, dat), f) | ||||||
|  | 
 | ||||||
|  |     def loadModelState(self, model): | ||||||
|  |         with open(self.getModelFileName(), 'rb') as f: | ||||||
|  |             gen, dat = pickle.load(f) | ||||||
|  |         model.load_state_dict(dat) | ||||||
|  |         model.eval() | ||||||
|  |         return gen | ||||||
|  | 
 | ||||||
|  |     def loadModel(self): | ||||||
|  |         model = self.head.state.getModel() | ||||||
|  |         gen = self.loadModelState(model) | ||||||
|  |         return model, gen | ||||||
|  | 
 | ||||||
|  |     def getModelFileName(self): | ||||||
|  |         return 'brains/uttt.vac' | ||||||
|  | 
 | ||||||
|  |     def saveToMemoryBank(self, term): | ||||||
|  |         with open('memoryBank/uttt/'+datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')+'_'+str(int(random.random()*99999))+'.vdm', 'wb') as f: | ||||||
|  |             pickle.dump(term, f) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class NeuralRuntime(Runtime): | ||||||
|  |     def __init__(self, initState, **kwargs): | ||||||
|  |         super().__init__(initState, **kwargs) | ||||||
|  | 
 | ||||||
|  |         model, gen = self.loadModel() | ||||||
|  | 
 | ||||||
|  |         self.head.universe.model = model | ||||||
|  |         self.head.universe.scoreProvider = 'neural' | ||||||
|  | 
 | ||||||
|  | class Trainer(Runtime): | ||||||
|  |     def __init__(self, initState, **kwargs): | ||||||
|  |         super().__init__(initState, **kwargs) | ||||||
|  |         #self.universe = Universe() | ||||||
|  |         self.universe = self.head.universe | ||||||
|  |         self.rootNode = self.head | ||||||
|  |         self.terminal = None | ||||||
|  | 
 | ||||||
|  |     def buildDatasetFromModel(self, model, depth=4, refining=True, fanOut=[5, 5, 5, 5, 4, 4, 4, 4], uncertainSec=15, exacity=5): | ||||||
|  |         print('[*] Building Timeline') | ||||||
|  |         term = self.linearPlay(model, calcDepth=depth, exacity=exacity) | ||||||
|  |         if refining: | ||||||
|  |             print('[*] Refining Timeline (exploring alternative endings)') | ||||||
|  |             cur = term | ||||||
|  |             for d in fanOut: | ||||||
|  |                 cur = cur.parent | ||||||
|  |                 cur.forceStrong(d) | ||||||
|  |                 print('.', end='', flush=True) | ||||||
|  |             print('') | ||||||
|  |             print('[*] Refining Timeline (exploring uncertain regions)') | ||||||
|  |             self.timelineExpandUncertain(term, uncertainSec) | ||||||
|  |         return term | ||||||
|  | 
 | ||||||
|  |     def linearPlay(self, model, calcDepth=7, exacity=5, verbose=False, firstNRandom=2): | ||||||
|  |         head = self.rootNode | ||||||
|  |         self.universe.model = model | ||||||
|  |         self.spawnWorker() | ||||||
|  |         while head.getWinner() == None: | ||||||
|  |             if verbose: | ||||||
|  |                 print(head) | ||||||
|  |             else: | ||||||
|  |                 print('.', end='', flush=True) | ||||||
|  |             head.forceStrong(calcDepth) | ||||||
|  |             opts = [] | ||||||
|  |             if len(head.childs) == 0: | ||||||
|  |                 break | ||||||
|  |             for c in head.childs: | ||||||
|  |                 opts.append((c, c.getStrongFor(head.curPlayer))) | ||||||
|  |             if firstNRandom: | ||||||
|  |                 firstNRandom -= 1 | ||||||
|  |                 ind = int(random.random()*len(opts)) | ||||||
|  |             else: | ||||||
|  |                 opts.sort(key=lambda x: x[1]) | ||||||
|  |                 if exacity >= 10: | ||||||
|  |                     ind = 0 | ||||||
|  |                 else: | ||||||
|  |                     ind = int(pow(random.random(), exacity)*(len(opts)-1)) | ||||||
|  |             head = opts[ind][0] | ||||||
|  |         self.killWorker() | ||||||
|  |         if verbose: | ||||||
|  |             print(head) | ||||||
|  |         print(' => '+['O', 'X', 'No one'][head.getWinner()] + ' won!') | ||||||
|  |         return head | ||||||
|  | 
 | ||||||
|  |     def timelineIterSingle(self, term): | ||||||
|  |         for i in self.timelineIter(self, [term]): | ||||||
|  |             yield i | ||||||
|  | 
 | ||||||
|  |     def timelineIter(self, terms, altChildPerNode=-1): | ||||||
|  |         batch = len(terms) | ||||||
|  |         heads = terms | ||||||
|  |         while True: | ||||||
|  |             empty = True | ||||||
|  |             for b in range(batch): | ||||||
|  |                 head = heads[b] | ||||||
|  |                 if head == None: | ||||||
|  |                     continue | ||||||
|  |                 empty = False | ||||||
|  |                 yield head | ||||||
|  |                 if len(head.childs): | ||||||
|  |                     if altChildPerNode == -1:  # all | ||||||
|  |                         for child in head.childs: | ||||||
|  |                             yield child | ||||||
|  |                     else: | ||||||
|  |                         for j in range(min(altChildPerNode, int(len(head.childs)/2))): | ||||||
|  |                             yield random.choice(head.childs) | ||||||
|  |                 if head.parent == None: | ||||||
|  |                     head = None | ||||||
|  |                 else: | ||||||
|  |                     head = head.parent | ||||||
|  |                 heads[b] = head | ||||||
|  |             if empty: | ||||||
|  |                 return | ||||||
|  | 
 | ||||||
|  |     def timelineExpandUncertain(self, term, secs): | ||||||
|  |         self.rootNode.universe.clearPQ() | ||||||
|  |         self.rootNode.universe.activateEdge(self.rootNode) | ||||||
|  |         self.spawnWorker() | ||||||
|  |         for s in range(secs): | ||||||
|  |             time.sleep(1) | ||||||
|  |             print('.', end='', flush=True) | ||||||
|  |         self.rootNode.universe.clearPQ() | ||||||
|  |         self.killWorker() | ||||||
|  |         print('') | ||||||
|  | 
 | ||||||
|  |     def trainModel(self, model, lr=0.00005, cut=0.01, calcDepth=4, exacity=5, terms=None, batch=16): | ||||||
|  |         loss_func = nn.MSELoss() | ||||||
|  |         optimizer = optim.Adam(model.parameters(), lr) | ||||||
|  |         if terms == None: | ||||||
|  |             terms = [] | ||||||
|  |             for i in range(batch): | ||||||
|  |                 terms.append(self.buildDatasetFromModel( | ||||||
|  |                     model, depth=calcDepth, exacity=exacity)) | ||||||
|  |         print('[*] Conditioning Brain') | ||||||
|  |         for r in range(64): | ||||||
|  |             loss_sum = 0 | ||||||
|  |             lLoss = 0 | ||||||
|  |             zeroLen = 0 | ||||||
|  |             for i, node in enumerate(self.timelineIter(terms)): | ||||||
|  |                 for p in range(self.rootNode.playersNum): | ||||||
|  |                     inp = node.state.getTensor(player=p) | ||||||
|  |                     gol = torch.tensor( | ||||||
|  |                         [node.getStrongFor(p)], dtype=torch.float) | ||||||
|  |                     out = model(inp) | ||||||
|  |                     loss = loss_func(out, gol) | ||||||
|  |                     optimizer.zero_grad() | ||||||
|  |                     loss.backward() | ||||||
|  |                     optimizer.step() | ||||||
|  |                     loss_sum += loss.item() | ||||||
|  |                     if loss.item() == 0.0: | ||||||
|  |                         zeroLen += 1 | ||||||
|  |                 if zeroLen == 5: | ||||||
|  |                     break | ||||||
|  |             print(loss_sum/i) | ||||||
|  |             if r > 16 and (loss_sum/i < cut or lLoss == loss_sum): | ||||||
|  |                 return loss_sum | ||||||
|  |             lLoss = loss_sum | ||||||
|  |         return loss_sum | ||||||
|  | 
 | ||||||
|  |     def main(self, model=None, gens=1024, startGen=0): | ||||||
|  |         newModel = False | ||||||
|  |         if model == None: | ||||||
|  |             print('[!] No brain found. Creating new one...') | ||||||
|  |             newModel = True | ||||||
|  |             model = self.rootNode.state.getModel() | ||||||
|  |         self.universe.scoreProvider = ['neural', 'naive'][newModel] | ||||||
|  |         model.train() | ||||||
|  |         for gen in range(startGen, startGen+gens): | ||||||
|  |             print('[#####] Gen '+str(gen)+' training:') | ||||||
|  |             loss = self.trainModel(model, calcDepth=min( | ||||||
|  |                 4, 3+int(gen/16)), exacity=int(gen/3+1), batch=4) | ||||||
|  |             print('[L] '+str(loss)) | ||||||
|  |             self.universe.scoreProvider = 'neural' | ||||||
|  |             self.saveModel(model, gen) | ||||||
|  | 
 | ||||||
|  |     def trainFromTerm(self, term): | ||||||
|  |         model, gen = self.loadModel() | ||||||
|  |         self.universe.scoreProvider = 'neural' | ||||||
|  |         self.trainModel(model, calcDepth=4, exacity=10, term=term) | ||||||
|  |         self.saveModel(model) | ||||||
|  | 
 | ||||||
|  |     def train(self): | ||||||
|  |         if os.path.exists(self.getModelFileName()): | ||||||
|  |             model, gen = self.loadModel() | ||||||
|  |             self.main(model, startGen=gen+1) | ||||||
|  |         else: | ||||||
|  |             self.main() | ||||||
							
								
								
									
										21
									
								
								vacuumDecay/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vacuumDecay/utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | def choose(txt, options): | ||||||
|  |     while True: | ||||||
|  |         print('[*] '+txt) | ||||||
|  |         for num, opt in enumerate(options): | ||||||
|  |             print('['+str(num+1)+'] ' + str(opt)) | ||||||
|  |         inp = input('[> ') | ||||||
|  |         try: | ||||||
|  |             n = int(inp) | ||||||
|  |             if n in range(1, len(options)+1): | ||||||
|  |                 return options[n-1] | ||||||
|  |         except: | ||||||
|  |             pass | ||||||
|  |         for opt in options: | ||||||
|  |             if inp == str(opt): | ||||||
|  |                 return opt | ||||||
|  |         if len(inp) == 1: | ||||||
|  |             for opt in options: | ||||||
|  |                 if inp == str(opt)[0]: | ||||||
|  |                     return opt | ||||||
|  |         print('[!] Invalid Input.') | ||||||
|  | 
 | ||||||
							
								
								
									
										58
									
								
								vacuumDecay/visualizer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								vacuumDecay/visualizer.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | |||||||
|  | import threading | ||||||
|  | import time | ||||||
|  | import networkx as nx | ||||||
|  | from flask import Flask, render_template, jsonify | ||||||
|  | from flask_socketio import SocketIO, emit | ||||||
|  | 
 | ||||||
|  | class Visualizer: | ||||||
|  |     def __init__(self, universe): | ||||||
|  |         self.universe = universe | ||||||
|  |         self.graph = nx.DiGraph() | ||||||
|  |         self.app = Flask(__name__) | ||||||
|  |         self.socketio = SocketIO(self.app) | ||||||
|  |         self.init_flask() | ||||||
|  | 
 | ||||||
|  |     def init_flask(self): | ||||||
|  |         @self.app.route('/') | ||||||
|  |         def index(): | ||||||
|  |             return render_template('index.html') | ||||||
|  | 
 | ||||||
|  |         @self.app.route('/data') | ||||||
|  |         def data(): | ||||||
|  |             nodes_data = [] | ||||||
|  |             edges_data = [] | ||||||
|  |             for node in self.universe.iter(): | ||||||
|  |                 nodes_data.append({ | ||||||
|  |                     'id': id(node), | ||||||
|  |                     'image': node.state.getImage().tobytes() if node.state.getImage() else None, | ||||||
|  |                     'value': node.getScoreFor(node.state.curPlayer), | ||||||
|  |                     'last_updated': node.last_updated | ||||||
|  |                 }) | ||||||
|  |                 for child in node.childs: | ||||||
|  |                     edges_data.append({'source': id(node), 'target': id(child)}) | ||||||
|  |             return jsonify(nodes=nodes_data, edges=edges_data) | ||||||
|  | 
 | ||||||
|  |         @self.socketio.on('connect') | ||||||
|  |         def handle_connect(): | ||||||
|  |             print('Client connected') | ||||||
|  | 
 | ||||||
|  |     def send_update(self): | ||||||
|  |         nodes_data = [] | ||||||
|  |         edges_data = [] | ||||||
|  |         for node in self.universe.iter(): | ||||||
|  |             nodes_data.append({ | ||||||
|  |                 'id': id(node), | ||||||
|  |                 'image': node.state.getImage().tobytes() if node.state.getImage() else None, | ||||||
|  |                 'value': node.getScoreFor(node.state.curPlayer), | ||||||
|  |                 'last_updated': node.last_updated | ||||||
|  |             }) | ||||||
|  |             for child in node.childs: | ||||||
|  |                 edges_data.append({'source': id(node), 'target': id(child)}) | ||||||
|  |         self.socketio.emit('update', {'nodes': nodes_data, 'edges': edges_data}) | ||||||
|  | 
 | ||||||
|  |     def run(self): | ||||||
|  |         self.socketio.run(self.app, debug=True, use_reloader=False) | ||||||
|  | 
 | ||||||
|  |     def start(self): | ||||||
|  |         self.thread = threading.Thread(target=self.run) | ||||||
|  |         self.thread.start() | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user