<commit_before>"""
A Machine Detector that can detect machines we would like
to connect to.  Can detect multiple machines by passing
in a list of machine names.  We search through their machine
profiles for their VID/PID information, use makerbot's pyserial
library to find any/all ports with those values, and compare iSerial
values to find all current, added and removed ports.  All ports
are kept track of in a python dict named "ports".
"""

import logging
try:
  import serial.tools.list_ports  as lp
  list_ports_generator = lp.list_ports_by_vid_pid
except ImportError:
  import warnings
  warnings.warn("No VID/PID detection in this version of PySerial; Automatic machine detection disabled.")
  # We're using legacy pyserial. For now, return an empty iterator.
  def list_ports_generator():
    return
    yield

#import profile

g_machineDetector = None

def get_gMachineDetector(everSeenCacheFile=None):
  """ always returns a singleton MachineDetector."""
  if g_machineDetector == None :
    g_machineDetector = MachineDetector()
  if everSeenCacheFile != None :
    g_machineDetector.updateEverSeen(everSeenCacheFile)

# Data structure containing bot connection classess by VID/PID, as
# well as what kinds of MakerBot may be constructed with those
botClasses = {
 'The Replicator':{'tool_count':1,'vid':0x23C1, 'pid':0xD314,'botProfiles':'.*ReplicatorSingle.*'},
 'The Replicator Dual':{'tool_count':2,'vid':0x23C1, 'pid':0xD314, 'botProfiles':'.*ReplicatorDual.*'},
 'MightyBoard':{'tool_count':1,'vid':0x23C1, 'pid':0xB404, 'botProfiles':'.*ReplicatorSingle.*'},
 'The MightBoard Dual':{'tool_count':2,'vid':0x23C1, 'pid':0xB404, 'botProfiles':'.*ReplicatorDual.*'},
}

class MachineDetector(object):
  """ Class used to detect machines, and query basic information from
  them. This is used to use MakerBot's pyserial to detect bots. """

  def __init__(self):
    self._log = logging.getLogger(self.__class__.__name__)
    self.everSeen = {} #dictionary of bots ever seen, UUID sorted. only 5.6 or newer bots
    self.botsOpen= {} #dictionary of bots currently open as best we know, by port
    self.botsRecentlySeen={}  #dictionary of bots seen in this running of the
            #environment (ie since singleton MachineDetector was created) by port
    self.botsJustSeen={} # seen in the last scan, by port{}

    #We save this func as a variable for testing purposes,
    #otherwise we would have to do hacky things, like reload
    #libraries during testing, etc
    self.list_ports_by_vid_pid = list_ports_generator

  def scan(self,botTypes=None):
    """ scans for connected bots, updates internal list of bots
    based on scan results
    @param botTypes. This can be an individual botClass name, or a list
    of bot class names
    """
    # clear our just seen list
    self.botsJustSeen.clear()

    # scan for all bot types
    scanNameList = []
    if botTypes == None:
        scanNameList.extend(botClasses.keys())
    elif isinstance(botTypes, str) or isinstance(botTypes, unicode):
        scanNameList.append(botTypes)
    else :
        scanNameList.extend(botTypes)

    for botClass in scanNameList:
        self._log.info( "scanning for BotClass " + botClass )
        try:
            vid = botClasses[botClass]['vid']
            pid = botClasses[botClass]['pid']
            newBots = list(self.list_ports_by_vid_pid(vid, pid))
            for p in newBots:
                self.botsJustSeen[p['port']] = p
                self.botsRecentlySeen[p['port']] = p
        except KeyError:
            continue #bot name isn't one we recognize

  def register_open(self, botPort):
    """ Register a port as an open bot. If that bot has been scanned,
    the dictionary value will be bot-data. If the bot was never found in a scan
    the bot info is empty.
    @param botPort Name of the port to register as open
    @return True if a bot registered has been 'seen', false otherwise.
    """
    botInfo = {}
    if botPort in self.botsRecentlySeen.keys():
        botInfo = self.botsRecentlySeen[botPort]
        self.botsOpen[botPort] = botInfo
        return True
    self.botsOpen[botPort] = botInfo
    return False

  def is_open(self, botPort):
    """ test to see if a bot is registered as open.
    @return True if the specified port is registered as open. False otherwise
    """
    return botPort in self.botsOpen.keys()

  def register_closed(self, botPort):
    """ register that a port is 'closed'
    @param botPort Name of the port to register as open
    """
    if botPort in self.botsOpen.keys():
        del self.botsOpen[botPort]
        return True
    return False

  def get_vid_pid_by_class(self, botType):
    """@returns a tuple of vid/pid if botType passed matches any class"""
    if botType in botClasses.keys():
      return botClasses[botType]['vid'], botClasses[botType]['pid']
    return None,None

  def get_first_bot_available(self, botTypes= None):
    """ returns the bot dict for the very first bot avaiable"""
    botsAll = self.get_bots_available(botTypes)
    for port in botsAll.keys():
        return botsAll[port]
    return None

  def get_bots_available(self, botTypes=None):
    """
    Returns any currently connected bots given a type/multiple types
    @param botClass a single bot class or list of valid bot classes. If None,
        all bot classes are considered
    @returns a dict of bots we have see that match types, keyed by port name.
        None if no bot matches
    """
    self.scan(botTypes)
    return self.botsJustSeen
<commit_msg>Fix global variable and return for the MachineDetector singleton.<commit_after>"""
A Machine Detector that can detect machines we would like
to connect to.  Can detect multiple machines by passing
in a list of machine names.  We search through their machine
profiles for their VID/PID information, use makerbot's pyserial
library to find any/all ports with those values, and compare iSerial
values to find all current, added and removed ports.  All ports
are kept track of in a python dict named "ports".
"""

import logging
try:
  import serial.tools.list_ports  as lp
  list_ports_generator = lp.list_ports_by_vid_pid
except ImportError:
  import warnings
  warnings.warn("No VID/PID detection in this version of PySerial; Automatic machine detection disabled.")
  # We're using legacy pyserial. For now, return an empty iterator.
  def list_ports_generator():
    return
    yield

#import profile

g_machineDetector = None

def get_gMachineDetector(everSeenCacheFile=None):
  """ always returns a singleton MachineDetector."""
  global g_machineDetector
  if g_machineDetector == None :
    g_machineDetector = MachineDetector()
  if everSeenCacheFile != None :
    g_machineDetector.updateEverSeen(everSeenCacheFile)
  return g_machineDetector

# Data structure containing bot connection classess by VID/PID, as
# well as what kinds of MakerBot may be constructed with those
botClasses = {
 'The Replicator':{'tool_count':1,'vid':0x23C1, 'pid':0xD314,'botProfiles':'.*ReplicatorSingle.*'},
 'The Replicator Dual':{'tool_count':2,'vid':0x23C1, 'pid':0xD314, 'botProfiles':'.*ReplicatorDual.*'},
 'MightyBoard':{'tool_count':1,'vid':0x23C1, 'pid':0xB404, 'botProfiles':'.*ReplicatorSingle.*'},
 'The MightBoard Dual':{'tool_count':2,'vid':0x23C1, 'pid':0xB404, 'botProfiles':'.*ReplicatorDual.*'},
}

class MachineDetector(object):
  """ Class used to detect machines, and query basic information from
  them. This is used to use MakerBot's pyserial to detect bots. """

  def __init__(self):
    self._log = logging.getLogger(self.__class__.__name__)
    self.everSeen = {} #dictionary of bots ever seen, UUID sorted. only 5.6 or newer bots
    self.botsOpen= {} #dictionary of bots currently open as best we know, by port
    self.botsRecentlySeen={}  #dictionary of bots seen in this running of the
            #environment (ie since singleton MachineDetector was created) by port
    self.botsJustSeen={} # seen in the last scan, by port{}

    #We save this func as a variable for testing purposes,
    #otherwise we would have to do hacky things, like reload
    #libraries during testing, etc
    self.list_ports_by_vid_pid = list_ports_generator

  def scan(self,botTypes=None):
    """ scans for connected bots, updates internal list of bots
    based on scan results
    @param botTypes. This can be an individual botClass name, or a list
    of bot class names
    """
    # clear our just seen list
    self.botsJustSeen.clear()

    # scan for all bot types
    scanNameList = []
    if botTypes == None:
        scanNameList.extend(botClasses.keys())
    elif isinstance(botTypes, str) or isinstance(botTypes, unicode):
        scanNameList.append(botTypes)
    else :
        scanNameList.extend(botTypes)

    for botClass in scanNameList:
        self._log.info( "scanning for BotClass " + botClass )
        try:
            vid = botClasses[botClass]['vid']
            pid = botClasses[botClass]['pid']
            newBots = list(self.list_ports_by_vid_pid(vid, pid))
            for p in newBots:
                self.botsJustSeen[p['port']] = p
                self.botsRecentlySeen[p['port']] = p
        except KeyError:
            continue #bot name isn't one we recognize

  def register_open(self, botPort):
    """ Register a port as an open bot. If that bot has been scanned,
    the dictionary value will be bot-data. If the bot was never found in a scan
    the bot info is empty.
    @param botPort Name of the port to register as open
    @return True if a bot registered has been 'seen', false otherwise.
    """
    botInfo = {}
    if botPort in self.botsRecentlySeen.keys():
        botInfo = self.botsRecentlySeen[botPort]
        self.botsOpen[botPort] = botInfo
        return True
    self.botsOpen[botPort] = botInfo
    return False

  def is_open(self, botPort):
    """ test to see if a bot is registered as open.
    @return True if the specified port is registered as open. False otherwise
    """
    return botPort in self.botsOpen.keys()

  def register_closed(self, botPort):
    """ register that a port is 'closed'
    @param botPort Name of the port to register as open
    """
    if botPort in self.botsOpen.keys():
        del self.botsOpen[botPort]
        return True
    return False

  def get_vid_pid_by_class(self, botType):
    """@returns a tuple of vid/pid if botType passed matches any class"""
    if botType in botClasses.keys():
      return botClasses[botType]['vid'], botClasses[botType]['pid']
    return None,None

  def get_first_bot_available(self, botTypes= None):
    """ returns the bot dict for the very first bot avaiable"""
    botsAll = self.get_bots_available(botTypes)
    for port in botsAll.keys():
        return botsAll[port]
    return None

  def get_bots_available(self, botTypes=None):
    """
    Returns any currently connected bots given a type/multiple types
    @param botClass a single bot class or list of valid bot classes. If None,
        all bot classes are considered
    @returns a dict of bots we have see that match types, keyed by port name.
        None if no bot matches
    """
    self.scan(botTypes)
    return self.botsJustSeen
<|endoftext|>


<commit_before>#
#  Copyright 2019 The FATE Authors. All Rights Reserved.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
from copy import deepcopy

import requests

from fate_arch.common.log import schedule_logger
from fate_flow.controller.engine_operation.base import BaseEngine
from fate_flow.entity.runtime_config import RuntimeConfig
from fate_flow.entity.types import LinkisJobStatus, KillProcessStatusCode
from fate_flow.settings import LINKIS_SPARK_CONFIG, LINKIS_EXECUTE_ENTRANCE, LINKIS_SUBMIT_PARAMS, LINKIS_RUNTYPE, \
    LINKIS_LABELS, LINKIS_QUERT_STATUS, LINKIS_KILL_ENTRANCE, detect_logger

class LinkisSparkEngine(BaseEngine):
    @staticmethod
    def run(job_id, component_name, task_id, task_version, role, party_id, task_parameters_path, run_parameters,
            task_info, user_name, **kwargs):
        linkis_execute_url = "http://{}:{}{}".format(LINKIS_SPARK_CONFIG.get("host"),
                                                     LINKIS_SPARK_CONFIG.get("port"),
                                                     LINKIS_EXECUTE_ENTRANCE)
        headers = {"Token-Code": LINKIS_SPARK_CONFIG.get("token_code"),
                   "Token-User": user_name,
                   "Content-Type": "application/json"}
        schedule_logger(job_id).info(f"headers:{headers}")
        python_path = LINKIS_SPARK_CONFIG.get("python_path")
        execution_code = 'import sys\nsys.path.append("{}")\n' \
                         'from fate_flow.operation.task_executor import TaskExecutor\n' \
                         'task_info = TaskExecutor.run_task(job_id="{}",component_name="{}",' \
                         'task_id="{}",task_version={},role="{}",party_id={},' \
                         'run_ip="{}",config="{}",job_server="{}")\n' \
                         'TaskExecutor.report_task_update_to_driver(task_info=task_info)'. \
            format(python_path, job_id, component_name, task_id, task_version, role, party_id, RuntimeConfig.JOB_SERVER_HOST,
                   task_parameters_path, '{}:{}'.format(RuntimeConfig.JOB_SERVER_HOST, RuntimeConfig.HTTP_PORT))
        schedule_logger(job_id).info(f"execution code:{execution_code}")
        params = deepcopy(LINKIS_SUBMIT_PARAMS)
        schedule_logger(job_id).info(f"spark run parameters:{run_parameters.spark_run}")
        for spark_key, v in run_parameters.spark_run.items():
            if spark_key in ["spark.executor.memory", "spark.driver.memory", "spark.executor.instances",
                             "wds.linkis.rm.yarnqueue"]:
                params["configuration"]["startup"][spark_key] = v
        data = {
            "method": LINKIS_EXECUTE_ENTRANCE,
            "params": params,
            "executeApplicationName": "spark",
            "executionCode": execution_code,
            "runType": LINKIS_RUNTYPE,
            "source": {},
            "labels": LINKIS_LABELS
        }
        schedule_logger(job_id).info(f'submit linkis spark, data:{data}')
        task_info["engine_conf"]["data"] = data
        task_info["engine_conf"]["headers"] = headers
        res = requests.post(url=linkis_execute_url, headers=headers, json=data)
        schedule_logger(job_id).info(f"start linkis spark task: {res.text}")
        if res.status_code == 200:
            if res.json().get("status"):
                raise Exception(f"submit linkis spark failed: {res.json()}")
            task_info["engine_conf"]["execID"] = res.json().get("data").get("execID")
            task_info["engine_conf"]["taskID"] = res.json().get("data").get("taskID")
            schedule_logger(job_id).info('submit linkis spark success')
        else:
            raise Exception(f"submit linkis spark failed: {res.text}")
        return True

    @staticmethod
    def kill(task):
        linkis_query_url = "http://{}:{}{}".format(LINKIS_SPARK_CONFIG.get("host"),
                                                   LINKIS_SPARK_CONFIG.get("port"),
                                                   LINKIS_QUERT_STATUS.replace("execID",
                                                                               task.f_engine_conf.get("execID")))
        headers = task.f_engine_conf.get("headers")
        response = requests.get(linkis_query_url, headers=headers).json()
        schedule_logger(task.f_job_id).info(f"querty task response:{response}")
        if response.get("data").get("status") != LinkisJobStatus.SUCCESS:
            linkis_execute_url = "http://{}:{}{}".format(LINKIS_SPARK_CONFIG.get("host"),
                                                         LINKIS_SPARK_CONFIG.get("port"),
                                                         LINKIS_KILL_ENTRANCE.replace("execID",
                                                                                task.f_engine_conf.get("execID")))
            schedule_logger(task.f_job_id).info(f"start stop task:{linkis_execute_url}")
            schedule_logger(task.f_job_id).info(f"headers: {headers}")
            kill_result = requests.get(linkis_execute_url, headers=headers)
            schedule_logger(task.f_job_id).info(f"kill result:{kill_result}")
            if kill_result.status_code == 200:
                pass
        return KillProcessStatusCode.KILLED

    @staticmethod
    def is_alive(task):
        process_exist = True
        try:
            linkis_query_url = "http://{}:{}{}".format(LINKIS_SPARK_CONFIG.get("host"),
                                                       LINKIS_SPARK_CONFIG.get("port"),
                                                       LINKIS_QUERT_STATUS.replace("execID", task.f_engine_conf.get("execID")))
            headers = task.f_engine_conf["headers"]
            response = requests.get(linkis_query_url, headers=headers).json()
            detect_logger(job_id=task.f_job_id).info(response)
            if response.get("data").get("status") == LinkisJobStatus.FAILED:
                process_exist = False
        except Exception as e:
            detect_logger(job_id=task.f_job_id).exception(e)
            process_exist = False
        return process_exist<commit_msg>fix log bug<commit_after>#
#  Copyright 2019 The FATE Authors. All Rights Reserved.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
from copy import deepcopy

import requests

from fate_arch.common.log import schedule_logger
from fate_flow.controller.engine_operation.base import BaseEngine
from fate_flow.entity.runtime_config import RuntimeConfig
from fate_flow.entity.types import LinkisJobStatus, KillProcessStatusCode
from fate_flow.settings import LINKIS_SPARK_CONFIG, LINKIS_EXECUTE_ENTRANCE, LINKIS_SUBMIT_PARAMS, LINKIS_RUNTYPE, \
    LINKIS_LABELS, LINKIS_QUERT_STATUS, LINKIS_KILL_ENTRANCE, detect_logger

class LinkisSparkEngine(BaseEngine):
    @staticmethod
    def run(job_id, component_name, task_id, task_version, role, party_id, task_parameters_path, run_parameters,
            task_info, user_name, **kwargs):
        linkis_execute_url = "http://{}:{}{}".format(LINKIS_SPARK_CONFIG.get("host"),
                                                     LINKIS_SPARK_CONFIG.get("port"),
                                                     LINKIS_EXECUTE_ENTRANCE)
        headers = {"Token-Code": LINKIS_SPARK_CONFIG.get("token_code"),
                   "Token-User": user_name,
                   "Content-Type": "application/json"}
        schedule_logger(job_id).info(f"headers:{headers}")
        python_path = LINKIS_SPARK_CONFIG.get("python_path")
        execution_code = 'import sys\nsys.path.append("{}")\n' \
                         'from fate_flow.operation.task_executor import TaskExecutor\n' \
                         'task_info = TaskExecutor.run_task(job_id="{}",component_name="{}",' \
                         'task_id="{}",task_version={},role="{}",party_id={},' \
                         'run_ip="{}",config="{}",job_server="{}")\n' \
                         'TaskExecutor.report_task_update_to_driver(task_info=task_info)'. \
            format(python_path, job_id, component_name, task_id, task_version, role, party_id, RuntimeConfig.JOB_SERVER_HOST,
                   task_parameters_path, '{}:{}'.format(RuntimeConfig.JOB_SERVER_HOST, RuntimeConfig.HTTP_PORT))
        schedule_logger(job_id).info(f"execution code:{execution_code}")
        params = deepcopy(LINKIS_SUBMIT_PARAMS)
        schedule_logger(job_id).info(f"spark run parameters:{run_parameters.spark_run}")
        for spark_key, v in run_parameters.spark_run.items():
            if spark_key in ["spark.executor.memory", "spark.driver.memory", "spark.executor.instances",
                             "wds.linkis.rm.yarnqueue"]:
                params["configuration"]["startup"][spark_key] = v
        data = {
            "method": LINKIS_EXECUTE_ENTRANCE,
            "params": params,
            "executeApplicationName": "spark",
            "executionCode": execution_code,
            "runType": LINKIS_RUNTYPE,
            "source": {},
            "labels": LINKIS_LABELS
        }
        schedule_logger(job_id).info(f'submit linkis spark, data:{data}')
        task_info["engine_conf"]["data"] = data
        task_info["engine_conf"]["headers"] = headers
        res = requests.post(url=linkis_execute_url, headers=headers, json=data)
        schedule_logger(job_id).info(f"start linkis spark task: {res.text}")
        if res.status_code == 200:
            if res.json().get("status"):
                raise Exception(f"submit linkis spark failed: {res.json()}")
            task_info["engine_conf"]["execID"] = res.json().get("data").get("execID")
            task_info["engine_conf"]["taskID"] = res.json().get("data").get("taskID")
            schedule_logger(job_id).info('submit linkis spark success')
        else:
            raise Exception(f"submit linkis spark failed: {res.text}")
        return True

    @staticmethod
    def kill(task):
        linkis_query_url = "http://{}:{}{}".format(LINKIS_SPARK_CONFIG.get("host"),
                                                   LINKIS_SPARK_CONFIG.get("port"),
                                                   LINKIS_QUERT_STATUS.replace("execID",
                                                                               task.f_engine_conf.get("execID")))
        headers = task.f_engine_conf.get("headers")
        response = requests.get(linkis_query_url, headers=headers).json()
        schedule_logger(task.f_job_id).info(f"querty task response:{response}")
        if response.get("data").get("status") != LinkisJobStatus.SUCCESS:
            linkis_execute_url = "http://{}:{}{}".format(LINKIS_SPARK_CONFIG.get("host"),
                                                         LINKIS_SPARK_CONFIG.get("port"),
                                                         LINKIS_KILL_ENTRANCE.replace("execID",
                                                                                task.f_engine_conf.get("execID")))
            schedule_logger(task.f_job_id).info(f"start stop task:{linkis_execute_url}")
            schedule_logger(task.f_job_id).info(f"headers: {headers}")
            kill_result = requests.get(linkis_execute_url, headers=headers)
            schedule_logger(task.f_job_id).info(f"kill result:{kill_result}")
            if kill_result.status_code == 200:
                pass
        return KillProcessStatusCode.KILLED

    @staticmethod
    def is_alive(task):
        process_exist = True
        try:
            linkis_query_url = "http://{}:{}{}".format(LINKIS_SPARK_CONFIG.get("host"),
                                                       LINKIS_SPARK_CONFIG.get("port"),
                                                       LINKIS_QUERT_STATUS.replace("execID", task.f_engine_conf.get("execID")))
            headers = task.f_engine_conf["headers"]
            response = requests.get(linkis_query_url, headers=headers).json()
            detect_logger.info(response)
            if response.get("data").get("status") == LinkisJobStatus.FAILED:
                process_exist = False
        except Exception as e:
            detect_logger.exception(e)
            process_exist = False
        return process_exist<|endoftext|>


<commit_before>from datetime import datetime, timedelta

from django.core.management.base import NoArgsCommand, CommandError
from django.conf import settings

import logging
logger = logging.getLogger(__name__)

from machiavelli import models

class Command(NoArgsCommand):
	"""
This script checks in every active game if the current turn must change. This happens either
when all the players have finished OR the time limit is exceeded

Of all the live games, it will adjudicate any existing fast games and only one of the normal
games (to prevent a long running time). To select only one game, it will use the oldest
value of 'Game.last_phase_change'.
	"""

	help = 'This script checks in every active game if the current turn must change. \
	This happens either when all the players have finished OR the time limit is exceeded.'

	def handle_noargs(self, **options):
		if settings.MAINTENANCE_MODE:
			logger.warning("App is in maintenance mode. Exiting.")
			return
		try:
			game = models.LiveGame.objects.filter(fast=False).order_by('last_phase_change')[0]
		except IndexError: # no live games
			pass
		else:
			game.check_finished_phase()
		fast_games = models.LiveGame.objects.filter(fast=True)
		for game in fast_games:
			try:
				game.check_finished_phase()
			except Exception, e:
				print "Error while checking if phase is finished in game %s\n\n" % game.pk
				print e
				continue
		## check for fast games that have not yet started and are older than
		## one hour
		fast_games = models.Game.objects.filter(slots__gt=0, fast=True)
		term = timedelta(0, settings.FAST_EXPIRATION)
		for f in fast_games:
			expiration = f.created + term
			if datetime.now() > expiration:
				print "Deleting game %s\n" % f
				f.delete()
<commit_msg>changed output of check_turns.py<commit_after>from datetime import datetime, timedelta

from django.core.management.base import NoArgsCommand, CommandError
from django.conf import settings

import logging
logger = logging.getLogger(__name__)

from machiavelli import models

class Command(NoArgsCommand):
	"""
This script checks in every active game if the current turn must change. This happens either
when all the players have finished OR the time limit is exceeded

Of all the live games, it will adjudicate any existing fast games and only one of the normal
games (to prevent a long running time). To select only one game, it will use the oldest
value of 'Game.last_phase_change'.
	"""

	help = 'This script checks in every active game if the current turn must change. \
	This happens either when all the players have finished OR the time limit is exceeded.'

	def handle_noargs(self, **options):
		self.stdout.write("Checking phase changes.")
		if settings.MAINTENANCE_MODE:
			logger.warning("App is in maintenance mode. Exiting.")
			self.stderr.write("App is in maintenance mode. Exiting.")
			return
		try:
			game = models.LiveGame.objects.filter(fast=False).order_by('last_phase_change')[0]
		except IndexError: # no live games
			self.stderr.write("There are no live games.")
		else:
			game.check_finished_phase()
		fast_games = models.LiveGame.objects.filter(fast=True)
		for game in fast_games:
			try:
				game.check_finished_phase()
			except Exception, e:
				self.stderr.write("Error while checking if phase is finished in game %s\n\n" % game.pk)
				self.stderr.write(e)
				continue
		## check for fast games that have not yet started and are older than
		## one hour
		fast_games = models.Game.objects.filter(slots__gt=0, fast=True)
		term = timedelta(0, settings.FAST_EXPIRATION)
		for f in fast_games:
			expiration = f.created + term
			if datetime.now() > expiration:
				self.stdout.write("Deleting game %s\n" % f)
				f.delete()
<|endoftext|>


<commit_before><commit_msg>Show message sign when signing<commit_after><|endoftext|>


<commit_before>#!/usr/bin/env python
#
# This program is a warper for the isct_dice_coefficient binary
#
# ---------------------------------------------------------------------------------------
# Copyright (c) 2013 Polytechnique Montreal <www.neuro.polymtl.ca>
# Authors: Sara Dupont
# Modified: 2015-05-20
#
# About the license: see the file LICENSE.TXT
#########################################################################################
from msct_parser import Parser
import sys
import sct_utils as sct

def get_parser():
    parser = Parser(__file__)
    parser.usage.set_description('Compute the Dice Coefficient. Note: indexing (in both time and space) starts with 0 not 1! Inputting -1 for a size will set it to the full image extent for that dimension.')
    parser.add_option(name='-i',
                      type_value='image_nifti',
                      description='First input image.',
                      mandatory=True,
                      example='t2_seg.nii.gz')
    parser.add_option(name='-d',
                      type_value='image_nifti',
                      description='Second input image.',
                      mandatory=True,
                      example='t2_manual_seg.nii.gz')
    parser.add_option(name='-2d-slices',
                      type_value='multiple_choice',
                      description='Compute DC on 2D slices in the specified dimension',
                      mandatory=False,
                      example=['0', '1', '2'])
    parser.add_option(name='-b',
                      type_value=[[','], 'int'],
                      description='Bounding box with the coordinates of the origin and the size of the box as follow: x_origin,x_size,y_origin,y_size,z_origin,z_size',
                      mandatory=False,
                      example='5,10,5,10,10,15')
    parser.add_option(name='-bmax',
                      type_value='multiple_choice',
                      description='Use maximum bounding box of the images union to compute DC',
                      mandatory=False,
                      example=['0', '1'])
    parser.add_option(name='-bzmax',
                      type_value='multiple_choice',
                      description='Use maximum bounding box of the images union in the "Z" direction to compute DC',
                      mandatory=False,
                      example=['0', '1'])
    parser.add_option(name='-bin',
                      type_value='multiple_choice',
                      description='Binarize image before computing DC. (Put non-zero-voxels to 1)',
                      mandatory=False,
                      example=['0', '1'])
    parser.add_option(name='-o',
                      type_value='file_output',
                      description='Output file with DC results (.txt)',
                      mandatory=False,
                      example='dice_coeff.txt')
    parser.add_option(name='-v',
                      type_value='multiple_choice',
                      description='Verbose.',
                      mandatory=False,
                      default_value='1',
                      example=['0', '1'])

    return parser

if __name__ == "__main__":
    parser = get_parser()
    arguments = parser.parse(sys.argv[1:])

    fname_input1 = arguments['-i']
    fname_input2 = arguments['-d']

    if '-bin' in arguments:
        fname_input1_bin = sct.add_suffix(fname_input1, '_bin')
        sct.run('sct_maths -i '+fname_input1+' -bin -o '+fname_input1_bin)
        fname_input1 = fname_input1_bin
        fname_input2_bin = sct.add_suffix(fname_input2, '_bin')
        sct.run('sct_maths -i '+fname_input2+' -bin -o '+fname_input2_bin)
        fname_input2 = fname_input2_bin

    cmd = 'isct_dice_coefficient '+fname_input1+' '+fname_input2

    if '-2d-slices' in arguments:
        cmd += ' -2d-slices '+arguments['-2d-slices']
    if '-b' in arguments:
        bounding_box = ' '.join(arguments['-b'])
        cmd += ' -b '+bounding_box
    if '-bmax' in arguments and arguments['-bmax'] == '1':
        cmd += ' -bmax'
    if '-bzmax' in arguments and arguments['-bzmax'] == '1':
        cmd += ' -bzmax'
    if '-o' in arguments:
        cmd += ' -o '+arguments['o']

    verbose = arguments['-v']
    if verbose == '0':
        cmd += ' -v '

    status, output = sct.run(cmd, verbose)
    sct.printv(output, verbose)
<commit_msg>BUG: fix use of flag -o<commit_after>#!/usr/bin/env python
#
# This program is a warper for the isct_dice_coefficient binary
#
# ---------------------------------------------------------------------------------------
# Copyright (c) 2013 Polytechnique Montreal <www.neuro.polymtl.ca>
# Authors: Sara Dupont
# Modified: 2015-05-20
#
# About the license: see the file LICENSE.TXT
#########################################################################################
from msct_parser import Parser
import sys
import sct_utils as sct

def get_parser():
    parser = Parser(__file__)
    parser.usage.set_description('Compute the Dice Coefficient. Note: indexing (in both time and space) starts with 0 not 1! Inputting -1 for a size will set it to the full image extent for that dimension.')
    parser.add_option(name='-i',
                      type_value='image_nifti',
                      description='First input image.',
                      mandatory=True,
                      example='t2_seg.nii.gz')
    parser.add_option(name='-d',
                      type_value='image_nifti',
                      description='Second input image.',
                      mandatory=True,
                      example='t2_manual_seg.nii.gz')
    parser.add_option(name='-2d-slices',
                      type_value='multiple_choice',
                      description='Compute DC on 2D slices in the specified dimension',
                      mandatory=False,
                      example=['0', '1', '2'])
    parser.add_option(name='-b',
                      type_value=[[','], 'int'],
                      description='Bounding box with the coordinates of the origin and the size of the box as follow: x_origin,x_size,y_origin,y_size,z_origin,z_size',
                      mandatory=False,
                      example='5,10,5,10,10,15')
    parser.add_option(name='-bmax',
                      type_value='multiple_choice',
                      description='Use maximum bounding box of the images union to compute DC',
                      mandatory=False,
                      example=['0', '1'])
    parser.add_option(name='-bzmax',
                      type_value='multiple_choice',
                      description='Use maximum bounding box of the images union in the "Z" direction to compute DC',
                      mandatory=False,
                      example=['0', '1'])
    parser.add_option(name='-bin',
                      type_value='multiple_choice',
                      description='Binarize image before computing DC. (Put non-zero-voxels to 1)',
                      mandatory=False,
                      example=['0', '1'])
    parser.add_option(name='-o',
                      type_value='file_output',
                      description='Output file with DC results (.txt)',
                      mandatory=False,
                      example='dice_coeff.txt')
    parser.add_option(name='-v',
                      type_value='multiple_choice',
                      description='Verbose.',
                      mandatory=False,
                      default_value='1',
                      example=['0', '1'])

    return parser

if __name__ == "__main__":
    parser = get_parser()
    arguments = parser.parse(sys.argv[1:])

    fname_input1 = arguments['-i']
    fname_input2 = arguments['-d']

    if '-bin' in arguments:
        fname_input1_bin = sct.add_suffix(fname_input1, '_bin')
        sct.run('sct_maths -i '+fname_input1+' -bin -o '+fname_input1_bin)
        fname_input1 = fname_input1_bin
        fname_input2_bin = sct.add_suffix(fname_input2, '_bin')
        sct.run('sct_maths -i '+fname_input2+' -bin -o '+fname_input2_bin)
        fname_input2 = fname_input2_bin

    cmd = 'isct_dice_coefficient '+fname_input1+' '+fname_input2

    if '-2d-slices' in arguments:
        cmd += ' -2d-slices '+arguments['-2d-slices']
    if '-b' in arguments:
        bounding_box = ' '.join(arguments['-b'])
        cmd += ' -b '+bounding_box
    if '-bmax' in arguments and arguments['-bmax'] == '1':
        cmd += ' -bmax'
    if '-bzmax' in arguments and arguments['-bzmax'] == '1':
        cmd += ' -bzmax'
    if '-o' in arguments:
        cmd += ' -o '+arguments['-o']

    verbose = arguments['-v']
    if verbose == '0':
        cmd += ' -v '

    status, output = sct.run(cmd, verbose)
    sct.printv(output, verbose)
<|endoftext|>


<commit_before>from coverage.html import escape
from jinja2 import Environment, PackageLoader
from mangrove.datastore.entity import get_all_entities
from mangrove.form_model.field import field_attributes, SelectField, UniqueIdField
from mangrove.form_model.form_model import FormModel

env = Environment(loader=PackageLoader('mangrove.transport.xforms'), trim_blocks=True)
date_appearance = {
    'mm.yyyy': 'month-year',
    'yyyy': 'year'
}

field_xmls = {
    field_attributes.DATE_FIELD: env.get_template(name='date_field.xml', globals={'date_appearance': date_appearance}),
    field_attributes.SELECT_FIELD: env.get_template('select_field.xml'),
    field_attributes.MULTISELECT_FIELD: env.get_template('select_field.xml'),
    field_attributes.LOCATION_FIELD: env.get_template('geo_code_field.xml'),
    field_attributes.UNIQUE_ID_FIELD: env.get_template('unique_id_field.xml'),
}

field_types = {
    field_attributes.LOCATION_FIELD: 'geopoint',
    field_attributes.TEXT_FIELD: 'string',
    field_attributes.INTEGER_FIELD: 'decimal',
}

def list_all_forms(form_tuples, xform_base_url):
    template = env.get_template('form_list.xml')
    form_tuples = [(escape(form_name), form_id) for form_name, form_id in form_tuples]
    return template.render(form_tuples=form_tuples, xform_base_url=xform_base_url)

class UniqueIdUIField(UniqueIdField):
    def __init__(self, field, dbm):
        super(UniqueIdUIField, self).__init__(unique_id_type=field.unique_id_type, name=field.name, code=field.code, label=field.label, instruction=field.instruction, constraints=field.constraints)
        self.dbm = dbm

    @property
    def options(self):
        return [(entity.short_code, escape(entity.data['name']['value'])) for entity in
                    get_all_entities(self.dbm, [self.unique_id_type])]

def xform_for(dbm, form_id, reporter_id):
    questionnaire = FormModel.get(dbm, form_id)
    xform = questionnaire.xform
    if xform:
        return xform

    _escape_special_characters(questionnaire)
    ui_fields = []
    for field in questionnaire.fields:
        if isinstance(field, UniqueIdField):
            ui_fields.append(UniqueIdUIField(field,dbm))
        else:
            ui_fields.append(field)
    template = env.get_template('reporter_entity_form.xml')
    return template.render(questionnaire=questionnaire, fields=ui_fields, field_xmls=field_xmls, reporter_id=reporter_id,
                           field_types=field_types, default_template=env.get_template('text_field.xml'))

def _escape_special_characters(questionnaire):
    questionnaire.name = escape(questionnaire.name)
    for question in questionnaire.fields:
        question.set_label(escape(question.label))
        question.set_instruction(escape(question.instruction))
        if type(question) == SelectField:
            question.escape_option_text()
<commit_msg>Pooja | #3765 | Repeat questions will have one group by default on smartphones<commit_after>import re
from coverage.html import escape
from jinja2 import Environment, PackageLoader
from mangrove.datastore.entity import get_all_entities
from mangrove.form_model.field import field_attributes, SelectField, UniqueIdField
from mangrove.form_model.form_model import FormModel

env = Environment(loader=PackageLoader('mangrove.transport.xforms'), trim_blocks=True)
date_appearance = {
    'mm.yyyy': 'month-year',
    'yyyy': 'year'
}

field_xmls = {
    field_attributes.DATE_FIELD: env.get_template(name='date_field.xml', globals={'date_appearance': date_appearance}),
    field_attributes.SELECT_FIELD: env.get_template('select_field.xml'),
    field_attributes.MULTISELECT_FIELD: env.get_template('select_field.xml'),
    field_attributes.LOCATION_FIELD: env.get_template('geo_code_field.xml'),
    field_attributes.UNIQUE_ID_FIELD: env.get_template('unique_id_field.xml'),
}

field_types = {
    field_attributes.LOCATION_FIELD: 'geopoint',
    field_attributes.TEXT_FIELD: 'string',
    field_attributes.INTEGER_FIELD: 'decimal',
}

def list_all_forms(form_tuples, xform_base_url):
    template = env.get_template('form_list.xml')
    form_tuples = [(escape(form_name), form_id) for form_name, form_id in form_tuples]
    return template.render(form_tuples=form_tuples, xform_base_url=xform_base_url)

class UniqueIdUIField(UniqueIdField):
    def __init__(self, field, dbm):
        super(UniqueIdUIField, self).__init__(unique_id_type=field.unique_id_type, name=field.name, code=field.code, label=field.label, instruction=field.instruction, constraints=field.constraints)
        self.dbm = dbm

    @property
    def options(self):
        return [(entity.short_code, escape(entity.data['name']['value'])) for entity in
                    get_all_entities(self.dbm, [self.unique_id_type])]

def xform_for(dbm, form_id, reporter_id):
    questionnaire = FormModel.get(dbm, form_id)
    xform = questionnaire.xform
    if xform:
        xform_cleaned = re.sub(r"\s+", " ", re.sub(r"\n", "", xform))
        #so that in the smartphone repeat questions have atleast one group pre added
        return re.sub('ns2:template=""',"",xform_cleaned)

    _escape_special_characters(questionnaire)
    ui_fields = []
    for field in questionnaire.fields:
        if isinstance(field, UniqueIdField):
            ui_fields.append(UniqueIdUIField(field,dbm))
        else:
            ui_fields.append(field)
    template = env.get_template('reporter_entity_form.xml')
    return template.render(questionnaire=questionnaire, fields=ui_fields, field_xmls=field_xmls, reporter_id=reporter_id,
                           field_types=field_types, default_template=env.get_template('text_field.xml'))

def _escape_special_characters(questionnaire):
    questionnaire.name = escape(questionnaire.name)
    for question in questionnaire.fields:
        question.set_label(escape(question.label))
        question.set_instruction(escape(question.instruction))
        if type(question) == SelectField:
            question.escape_option_text()
<|endoftext|>


<commit_before>import logging
import re
from urllib.parse import urlparse

from twisted.internet import defer
from twisted.internet.threads import deferToThread
import requests

from cardinal.decorators import command, event, help
from cardinal.exceptions import EventRejectedMessage
from cardinal.util import F

class SearchCache:
    def __init__(self, max_length):
        self.max_length = max_length

        self._cache = dict()
        self._keys = list()

    def add(self, channel, results):
        # don't duplicate channel in list
        try:
            self._keys.remove(channel)
        except ValueError:
            pass

        self._keys.append(channel)
        self._cache[channel] = results

        # remove oldest item in list
        while len(self._keys) > self.max_length:
            key = self._keys.pop(0)
            del self._cache[key]

    def get(self, channel):
        return self._cache[channel]

def get_imdb_link(id):
    return "https://imdb.com/title/{}".format(id)

def format_data_short(data):
    if data['imdbRating'] == "N/A":
        rating = ""
    else:
        rating = "Rating: {} | ".format(float(data['imdbRating']))

    return "[ IMDb: {title} ({year}) - {runtime} | {maybe_rating}Plot: {plot} | {link} ]".format(  # noqa: E501
        title=data['Title'],
        year=data['Year'],
        runtime=data['Runtime'],
        maybe_rating=rating,
        plot=data['Plot'],
        link=get_imdb_link(data['imdbID']),
    )

def format_data_full(data):
    if data['imdbRating'] == "N/A":
        maybe_rating = ""
    else:
        rating = float(data['imdbRating'])
        stars = '*' * round(rating)
        stars += '.' * (10 - round(rating))

        maybe_rating = "{}: {} [{}]  ".format(
            F.bold("Rating"), data['imdbRating'], stars
        )

    link = get_imdb_link(data['imdbID'])
    return [
        "[IMDb] {title} ({year}) - {link}"
        .format(
            title=F.bold(data['Title']),
            year=data['Year'],
            link=link
        ),
        "{}{}: {}  {}: {}  {}: {}".format(
            maybe_rating,
            F.bold("Runtime"), data['Runtime'],
            F.bold("Genre"), data['Genre'],
            F.bold("Released"), data['Released'],
        ),
        "{}: {}  {}: {}".format(
            F.bold("Director"), data['Director'],
            F.bold("Cast"), data['Actors'],
        ),
        "{}: {}".format(F.bold("Plot"), data['Plot']),
    ]

class MoviePlugin:
    def __init__(self, cardinal, config):
        self.logger = logging.getLogger(__name__)
        self.cardinal = cardinal

        if config is None:
            raise Exception("Movie plugin requires configuration")

        self.api_key = config.get('api_key', None)
        self.default_output = config.get('default_output', 'short')
        self.private_output = config.get('private_output', 'full')
        self.channels = config.get('channels', {})
        self.max_search_results = config.get('max_search_results', 5)

        # Stores results for quick lookup
        self._search_cache = SearchCache(5)

    def search_allowed(self, channel):
        chantypes = self.cardinal.supported.getFeature("CHANTYPES") or ('#',)
        if channel[0] not in chantypes:
            return True

        return self.channels.get(channel, {}) \
            .get('allow_search', False)

    def get_output_format(self, channel):
        chantypes = self.cardinal.supported.getFeature("CHANTYPES") or ('#',)
        if channel[0] not in chantypes:
            return self.private_output

        # Fetch channel-specific output format, or default
        return self.channels.get(channel, {}) \
            .get('output', self.default_output)

    @command('movie')
    @help('Get the first movie IMDb result for a given search')
    @help('Syntax: .movie <search query>')
    @defer.inlineCallbacks
    def movie(self, cardinal, user, channel, msg):
        yield self.imdb(cardinal, user, channel, msg, result_type='movie')

    @command('show')
    @help('Get the first TV show IMDb result for a given search')
    @help('Syntax: .show <search query>')
    @defer.inlineCallbacks
    def show(self, cardinal, user, channel, msg):
        yield self.imdb(cardinal, user, channel, msg, result_type='series')

    @command(['omdb', 'imdb'])
    @help('Get the first IMDb result for a given search')
    @help('Syntax: .imdb <search query>')
    @defer.inlineCallbacks
    def imdb(self, cardinal, user, channel, msg, result_type=None):
        # Before we do anything, let's make sure we'll be able to query omdb.
        if self.api_key is None:
            cardinal.sendMsg(
                channel,
                "Movie plugin is not configured correctly. Please set API key."
            )
            return

        try:
            search_query = msg.split(' ', 1)[1].strip()
        except IndexError:
            command = 'imdb'
            if result_type == 'series':
                command = 'show'
            elif result_type == 'movie':
                command = 'movie'
            cardinal.sendMsg(channel, f"Syntax: .{command} <search query>")
            return

        # first, check if this is just an IMDB id
        imdb_id = None
        if re.match(r'^tt\d{7,8}$', search_query):
            imdb_id = search_query
        # next, check if this is is a search selection
        elif (match := re.match(r'^(\d+)$', search_query)):
            res_id = int(match.group(1))
            imdb_id = self._search_cache.get(channel)[res_id - 1]['imdbID']

        # otherwise, try to find the best match
        if not imdb_id:
            try:
                results = yield self._search(search_query, result_type)
                imdb_id = results[0]['imdbID']
            except RuntimeError as e:
                cardinal.sendMsg(channel, str(e))
                return
            except Exception:
                self.logger.exception("Unknown error while searching")
                return

        try:
            yield self._send_result(cardinal, channel, imdb_id)
        except Exception:
            cardinal.sendMsg(channel, "Error fetching movie info.")
            self.logger.exception("Failed to parse info for %s", imdb_id)
            return

    @command('search')
    @help('Return IMDb search results (use .imdb for a single title)')
    @help('Syntax: .search <search query>')
    @defer.inlineCallbacks
    def search(self, cardinal, user, channel, msg):
        if self.api_key is None:
            cardinal.sendMsg(
                channel,
                "Movie plugin is not configured correctly. Please set API key."
            )
            return

        if not self.search_allowed(channel):
            cardinal.sendMsg(channel, "nope")
            return

        try:
            search_query = msg.split(' ', 1)[1].strip()
        except IndexError:
            cardinal.sendMsg(channel, "Syntax: .search <search query>")
            return

        results = yield self._search(search_query)
        if not results:
            cardinal.sendMsg(channel, "No results found.")
            return

        # Store these for quick lookup in imdb command
        self._search_cache.add(channel, results)

        i = 0
        for result in results:
            i += 1
            type_ = result['Type'].capitalize()
            link = get_imdb_link(result['imdbID'])
            cardinal.sendMsg(
                channel,
                f"{i}. {result['Title']} ({result['Year']})  "
                f"[{type_}] - {link}"
            )
            if i >= self.max_search_results:
                break

    @defer.inlineCallbacks
    def _search(self, search_query, result_type=None):
        params = {}
        if result_type:
            params['type'] = result_type

        # separate out year if search ends in 4 digits
        if len(search_query) > 5 and search_query[-5] == " " \
                and search_query[-4:].isnumeric():
            params['y'] = search_query[-4:]
            search_query = search_query[:-5]
        params['s'] = search_query

        try:
            result = yield self._form_request(params)
        except Exception:
            self.logger.exception("Unable to connect to OMDb")
            raise RuntimeError("Failed to connect to OMDb")

        if result['Response'] == 'False':
            if "Error" in result:
                self.logger.error(
                    "Error attempting to search OMDb: %s" % result['Error']
                )

            raise RuntimeError("Error searching OMDb: %s" % result['Error'])

        return result['Search']

    @defer.inlineCallbacks
    def _send_result(self, cardinal, channel, imdb_id):
        params = {
            "i": imdb_id,
            "plot": self.get_output_format(channel),
        }

        result = yield self._form_request(params)

        for message in self._format_data(channel, result):
            cardinal.sendMsg(channel, message)

    @defer.inlineCallbacks
    def _form_request(self, payload):
        payload.update({
            'apikey': self.api_key,
            'v': 1,
            'r': 'json',
        })

        return (yield deferToThread(
            requests.get,
            'https://www.omdbapi.com',
            params=payload
        )).json()

    def _format_data(self, channel, data):
        if self.get_output_format(channel) == 'short':
            return [format_data_short(data)]
        else:
            return format_data_full(data)

    @event('urls.detection')
    @defer.inlineCallbacks
    def imdb_handler(self, cardinal, channel, url):
        if self.api_key is None:
            raise EventRejectedMessage

        o = urlparse(url)

        if o.netloc not in ('imdb.com', 'www.imdb.com'):
            raise EventRejectedMessage

        match = re.match(r'^/title/(tt\d{7,8})(?:$|/.*)', o.path)
        if not match:
            raise EventRejectedMessage

        try:
            yield self._send_result(cardinal, channel, match.group(1))
        except Exception:
            self.logger.exception("Error parsing IMDB ID %s", match.group(1))
            raise EventRejectedMessage

entrypoint = MoviePlugin
<commit_msg>Select movie search results with numerals<commit_after>import logging
import re
from urllib.parse import urlparse

from twisted.internet import defer
from twisted.internet.threads import deferToThread
import requests

from cardinal.decorators import command, event, help
from cardinal.exceptions import EventRejectedMessage
from cardinal.util import F

_numerals = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e'}
_indexes = {v: k for k, v in _numerals.items()}

class SearchCache:
    def __init__(self, max_length):
        self.max_length = max_length

        self._cache = dict()
        self._keys = list()

    def add(self, channel, results):
        # don't duplicate channel in list
        try:
            self._keys.remove(channel)
        except ValueError:
            pass

        self._keys.append(channel)
        self._cache[channel] = results

        # remove oldest item in list
        while len(self._keys) > self.max_length:
            key = self._keys.pop(0)
            del self._cache[key]

    def get(self, channel):
        return self._cache[channel]

def get_imdb_link(id):
    return "https://imdb.com/title/{}".format(id)

def format_data_short(data):
    if data['imdbRating'] == "N/A":
        rating = ""
    else:
        rating = "Rating: {} | ".format(float(data['imdbRating']))

    return "[ IMDb: {title} ({year}) - {runtime} | {maybe_rating}Plot: {plot} | {link} ]".format(  # noqa: E501
        title=data['Title'],
        year=data['Year'],
        runtime=data['Runtime'],
        maybe_rating=rating,
        plot=data['Plot'],
        link=get_imdb_link(data['imdbID']),
    )

def format_data_full(data):
    if data['imdbRating'] == "N/A":
        maybe_rating = ""
    else:
        rating = float(data['imdbRating'])
        stars = '*' * round(rating)
        stars += '.' * (10 - round(rating))

        maybe_rating = "{}: {} [{}]  ".format(
            F.bold("Rating"), data['imdbRating'], stars
        )

    link = get_imdb_link(data['imdbID'])
    return [
        "[IMDb] {title} ({year}) - {link}"
        .format(
            title=F.bold(data['Title']),
            year=data['Year'],
            link=link
        ),
        "{}{}: {}  {}: {}  {}: {}".format(
            maybe_rating,
            F.bold("Runtime"), data['Runtime'],
            F.bold("Genre"), data['Genre'],
            F.bold("Released"), data['Released'],
        ),
        "{}: {}  {}: {}".format(
            F.bold("Director"), data['Director'],
            F.bold("Cast"), data['Actors'],
        ),
        "{}: {}".format(F.bold("Plot"), data['Plot']),
    ]

class MoviePlugin:
    def __init__(self, cardinal, config):
        self.logger = logging.getLogger(__name__)
        self.cardinal = cardinal

        if config is None:
            raise Exception("Movie plugin requires configuration")

        self.api_key = config.get('api_key', None)
        self.default_output = config.get('default_output', 'short')
        self.private_output = config.get('private_output', 'full')
        self.channels = config.get('channels', {})
        self.max_search_results = config.get('max_search_results', 5)
        if self.max_search_results > 5:
            raise Exception("max_search_results must be between 1-5")

        # Stores results for quick lookup
        self._search_cache = SearchCache(5)

    def search_allowed(self, channel):
        chantypes = self.cardinal.supported.getFeature("CHANTYPES") or ('#',)
        if channel[0] not in chantypes:
            return True

        return self.channels.get(channel, {}) \
            .get('allow_search', False)

    def get_output_format(self, channel):
        chantypes = self.cardinal.supported.getFeature("CHANTYPES") or ('#',)
        if channel[0] not in chantypes:
            return self.private_output

        # Fetch channel-specific output format, or default
        return self.channels.get(channel, {}) \
            .get('output', self.default_output)

    @command('movie')
    @help('Get the first movie IMDb result for a given search')
    @help('Syntax: .movie <search query>')
    @defer.inlineCallbacks
    def movie(self, cardinal, user, channel, msg):
        yield self.imdb(cardinal, user, channel, msg, result_type='movie')

    @command('show')
    @help('Get the first TV show IMDb result for a given search')
    @help('Syntax: .show <search query>')
    @defer.inlineCallbacks
    def show(self, cardinal, user, channel, msg):
        yield self.imdb(cardinal, user, channel, msg, result_type='series')

    @command(['omdb', 'imdb'])
    @help('Get the first IMDb result for a given search')
    @help('Syntax: .imdb <search query>')
    @defer.inlineCallbacks
    def imdb(self, cardinal, user, channel, msg, result_type=None):
        # Before we do anything, let's make sure we'll be able to query omdb.
        if self.api_key is None:
            cardinal.sendMsg(
                channel,
                "Movie plugin is not configured correctly. Please set API key."
            )
            return

        try:
            search_query = msg.split(' ', 1)[1].strip()
        except IndexError:
            command = 'imdb'
            if result_type == 'series':
                command = 'show'
            elif result_type == 'movie':
                command = 'movie'
            cardinal.sendMsg(channel, f"Syntax: .{command} <search query>")
            return

        # first, check if this is just an IMDB id
        imdb_id = None
        if re.match(r'^tt\d{7,8}$', search_query):
            imdb_id = search_query
        # next, check if this is is a search selection
        elif search_query in _indexes:
            try:
                res_id = _indexes[search_query]
                res = self._search_cache.get(channel)[res_id - 1]
            except KeyError:
                # no search results in this channel yet
                pass
            else:
                imdb_id = res['imdbID']

        # otherwise, try to find the best match
        if not imdb_id:
            try:
                results = yield self._search(search_query, result_type)
                imdb_id = results[0]['imdbID']
            except RuntimeError as e:
                cardinal.sendMsg(channel, str(e))
                return
            except Exception:
                self.logger.exception("Unknown error while searching")
                return

        try:
            yield self._send_result(cardinal, channel, imdb_id)
        except Exception:
            cardinal.sendMsg(channel, "Error fetching movie info.")
            self.logger.exception("Failed to parse info for %s", imdb_id)
            return

    @command('search')
    @help('Return IMDb search results (use .imdb for a single title)')
    @help('Syntax: .search <search query>')
    @defer.inlineCallbacks
    def search(self, cardinal, user, channel, msg):
        if self.api_key is None:
            cardinal.sendMsg(
                channel,
                "Movie plugin is not configured correctly. Please set API key."
            )
            return

        if not self.search_allowed(channel):
            cardinal.sendMsg(channel, "nope")
            return

        try:
            search_query = msg.split(' ', 1)[1].strip()
        except IndexError:
            cardinal.sendMsg(channel, "Syntax: .search <search query>")
            return

        try:
            results = yield self._search(search_query)
        except RuntimeError as e:
            cardinal.sendMsg(channel, str(e))
            return
        if not results:
            cardinal.sendMsg(channel, "No results found.")
            return

        # Store these for quick lookup in imdb command
        self._search_cache.add(channel, results)

        i = 0
        for result in results:
            i += 1
            n = _numerals[i]
            type_ = result['Type'].capitalize()
            link = get_imdb_link(result['imdbID'])
            cardinal.sendMsg(
                channel,
                f"{n}. {result['Title']} ({result['Year']})  "
                f"[{type_}] - {link}"
            )
            if i >= self.max_search_results:
                break

        cardinal.sendMsg(channel, "Use .imdb <numeral> to view more.")

    @defer.inlineCallbacks
    def _search(self, search_query, result_type=None):
        params = {}
        if result_type:
            params['type'] = result_type

        # separate out year if search ends in 4 digits
        if len(search_query) > 5 and search_query[-5] == " " \
                and search_query[-4:].isnumeric():
            params['y'] = search_query[-4:]
            search_query = search_query[:-5]
        params['s'] = search_query

        try:
            result = yield self._form_request(params)
        except Exception:
            self.logger.exception("Unable to connect to OMDb")
            raise RuntimeError("Failed to connect to OMDb")

        if result['Response'] == 'False':
            if "Error" in result:
                self.logger.error(
                    "Error attempting to search OMDb: %s" % result['Error']
                )

            raise RuntimeError("Error searching OMDb: %s" % result['Error'])

        return result['Search']

    @defer.inlineCallbacks
    def _send_result(self, cardinal, channel, imdb_id):
        params = {
            "i": imdb_id,
            "plot": self.get_output_format(channel),
        }

        result = yield self._form_request(params)

        for message in self._format_data(channel, result):
            cardinal.sendMsg(channel, message)

    @defer.inlineCallbacks
    def _form_request(self, payload):
        payload.update({
            'apikey': self.api_key,
            'v': 1,
            'r': 'json',
        })

        return (yield deferToThread(
            requests.get,
            'https://www.omdbapi.com',
            params=payload
        )).json()

    def _format_data(self, channel, data):
        if self.get_output_format(channel) == 'short':
            return [format_data_short(data)]
        else:
            return format_data_full(data)

    @event('urls.detection')
    @defer.inlineCallbacks
    def imdb_handler(self, cardinal, channel, url):
        if self.api_key is None:
            raise EventRejectedMessage

        o = urlparse(url)

        if o.netloc not in ('imdb.com', 'www.imdb.com'):
            raise EventRejectedMessage

        match = re.match(r'^/title/(tt\d{7,8})(?:$|/.*)', o.path)
        if not match:
            raise EventRejectedMessage

        try:
            yield self._send_result(cardinal, channel, match.group(1))
        except Exception:
            self.logger.exception("Error parsing IMDB ID %s", match.group(1))
            raise EventRejectedMessage

entrypoint = MoviePlugin
<|endoftext|>


<commit_before>"""Dominus Blottleships AI"""
import const
import base_player
from random import randint

def getPlayer():
    """ MUST NOT be changed, used to get a instance of the class."""
    return Player()

def getRandCell():
    """Get a random piece on the board."""

    row = randint(0, 11)
    # Board is a weird L shape
    col = randint(0, 5 if row < 6 else 11)
    # Return move in row (letter) + col (number) grid reference
    # e.g. A3 is represented as 0,2
    return (row, col)

def isValidCell(cell):
    """Check that a generated cell is valid on the board.

    Keyword arguments:
    cell -- piece on the board to check

    """
    assert type(cell) == tuple
    if cell[0] < 0 or cell[1] < 0:
        return False
    if cell[0] > 11 or cell[1] > 11:
        return False
    if cell[0] < 6 and cell[1] > 5:
        return False

    return True

def getRotationFactor(rotation, i):
    """Rotate a piece (around (0, 0)).

    Keyword arguments:
    rotation -- arbitrary rotation factor
    i -- piece of the board to rotate

    """
    if rotation == 0:
        return i
    if rotation == 1:
        return (i[1], i[0])
    if rotation == 2:
        return (-i[0], -i[1])
    if rotation == 3:
        return (i[1], -i[0])
    raise IndexError  # It's sort of an index error

def rotateShip(rotation, ship, base=(0, 0)):
    """Rotate a ship.

    Keyword arguments:
    rotation -- arbitrary rotation factor
    ship -- ship to rotate
    base -- (default: (0, 0)

    """
    rotShip = []
    for cx, cy in ship:
        rx, ry = getRotationFactor(rotation, (cx, cy))
        rotShip.append((base[0] + rx, base[1] + ry))

    return frozenset(rotShip)

def circleCell(piece):
    """Get a list of pieces that are adjacent to another piece.

    Keyword arguments:
    piece -- piece on the board

    """
    assert type(piece) == tuple
    rotate = [(-1, 0), (0, 1), (1, 0), (0, -1)]
    return [(piece[0] + offset[0], piece[1] + offset[1]) for offset in rotate]

class Player(base_player.BasePlayer):
    """Dominus Blottleships AI implementation."""

    def __init__(self):
        base_player.BasePlayer.__init__(self)
        self._playerName = "Dominus"
        self._playerYear = "1"
        self._version = "Theta"
        self._playerDescription = ("\"Dominus\" is Latin for Master."
                                   "Good luck.\nBy Charles Pigott and"
                                   "Nathan van Doorn")

        self._moves = []  # Previous moves
        self.shapes = []

    def makeShip(self, base, shape):
        """Place a ship on the board.

        Keyword arguments:
        base -- base piece to try placing the ship on
        shape -- ship to try placing

        """
        rotShip = rotateShip(randint(0, 3), shape, base)

        successful = []
        for cell in rotShip:
            success = True
            success = success and isValidCell(cell)
            success = success and self._playerBoard[cell[0]][cell[1]] == const.EMPTY
            if not success:
                return False

            # Try not to connect ships together
            count = 0
            for adjCell in circleCell(cell):
                success = success and (not isValidCell(adjCell) or
                                       adjCell in successful or
                                       self._playerBoard[adjCell[0]][adjCell[1]] == const.EMPTY)
                count += 1

            # Don't bother trying to separate ships if it's too hard
            if not success and count < 200:
                return False

            successful.append(cell)
        for cx, cy in successful:
            self._playerBoard[cx][cy] = const.OCCUPIED
        return True

    def deployFleet(self):
        """Place ship fleet on the board. """
        self._initBoards()

        # Reset some variables each game
        self._moves = []
        self.ships = [
            frozenset([(-1, 0), (0, 0), (0, -1), (0, 1), (1, -1), (1, 1)]),  # Hovercraft
            frozenset([(-1, -1), (1, -1), (0, -1), (0, 0), (0, 1), (0, 2)]),  # Aircraft Carrier
            frozenset([(0, 0), (0, 1), (0, 2), (0, 3)]),  # Battleship
            frozenset([(0, 0), (0, 1), (0, 2)]),  # Cruiser
            frozenset([(0, 0), (1, 0)])  # Destroyer
        ]

        for ship in self.ships:
            while True:
                sp = getRandCell()
                if self.makeShip(sp, ship):
                    break

        return self._playerBoard

    def countPossibilities(self, cell, shape):
        """Count the number of possible ways the given shape could overlap
        with the given cell.

        cell -- piece on the board to check
        shape -- ship to try placing
        """
        count = 0
        for rotation in xrange(4):
            for px, py in shape:
                shape2 = rotateShip(rotation, {(x - px, y - py) for x, y in shape}, cell)
                valid = True
                for x, y in shape2:
                    valid = valid and isValidCell((x, y))
                    valid = valid and self._opponenBoard[x][y] == const.EMPTY
                    if not valid:
                        break
                if valid:
                    count += 1
        return count

    def analyzeHitRegion(self, remCells, toTestShips, toDelShips):
        """Gets a list of ships that precisely cover a set of points.

        Keyword arguments:
        remCells -- remaining cells to test
        toTestShips -- ships still to test
        toDelShips -- ships already used in the solution

        """
        if not remCells:
            # We've got there :)
            return [toDelShips]

        if toTestShips:
            thisShip0 = toTestShips.pop()

            res = [(remCells, toTestShips[:], toDelShips[:])]

            for direction in range(4):
                thisShip = rotateShip(direction, thisShip0)

                for fx, fy in remCells:
                    for ox, oy in thisShip:
                        willBeTaken = {(fx - ox + px, fy - oy + py) for px, py in thisShip}

                        if willBeTaken <= remCells:
                            nextToDelShips = toDelShips[:]
                            nextToDelShips.append(thisShip0)
                            res.append((remCells - willBeTaken,
                                        toTestShips[:], nextToDelShips[:]))
            return [fin for state in res for fin in self.analyzeHitRegion(*state)]

        else:
            return []

    def coverWithSingleShip(self, hitRegion, border):
        """Chooses the most likely cell in the border to be a hit, assuming
        that there is only one ship, where no others are adjacent to it.

        Keyword arguments:
        hitRegion -- set of known hits
        border -- set of points adjacent to these hits

        """
        borderScores = dict.fromkeys(border, 0)

        for cell in hitRegion:
            for shapePreRot in self.ships:
                for px, py in shapePreRot:
                    for orientation in range(4):
                        shape = rotateShip(orientation, {(x - px, y - py) for x, y in shapePreRot}, base=cell)

                        if hitRegion <= shape:

                            valid = True
                            for cx, cy in shape:
                                valid = valid and isValidCell((cx, cy))
                                valid = valid and self._opponenBoard[cx][cy] != const.EMPTY or (cx, cy) in hitRegion

                            if valid:
                                for coord in shape & border:
                                    borderScores[coord] += 1

        try:
            best = max(borderScores.items(), key=lambda kv: kv[1])
            if best[1]:
                return best[0]

        except ValueError:
            pass

    def coverWithMultipleShips(self, hitRegion, border):
        """Chooses the most likely cell in the border to be a hit, assuming
        there are ships adjacent to each other.

        Keyword arguments:
        hitRegion -- set of known hits
        border -- set of points adjacent to these hits

        """
        borderScores = dict.fromkeys(border, 0)

        def helperFunction(toCover, covered, remaining):
            """Recursive helper function to calculate the best point on the
            board to hit.

            Keyword arguments:
            toCover -- set of coords to cover
            covered -- set of coords already covered
            remaining -- remaining ships to check

            """
            if not (toCover and remaining):
                # FLAWLESS VICTORY!
                # Update the weightings
                for coord in covered & border:
                    borderScores[coord] += 1

            else:
                for shapePreOffset in self.ships:
                    for pivx, pivy in shapePreOffset:
                        for orientation in range(4):
                            coord = next(iter(toCover))
                            shape = rotateShip(orientation, [(x - pivx, y - pivy) for x, y in shapePreOffset], base=coord)

                            # make sure it fits
                            valid = True
                            for cx, cy in shape:
                                valid = valid and isValidCell((cx, cy))
                                valid = valid and self._opponenBoard[cx][cy] == const.EMPTY or (cx, cy) in toCover
                                valid = valid and (cx, cy) not in covered
                                if not valid:
                                    break

                            if valid:
                                helperFunction(toCover - shape, covered | shape, remaining[:].remove(shapePreOffset))

        helperFunction(hitRegion, frozenset(), self.ships[:])
        try:
            best = max(borderScores.items(), key=lambda kv: kv[1])
            if best[1]:
                return best[0]
        except ValueError:
            pass

    def chooseMove(self):
        """Decide what move to make based on current state of opponent's
        board and return it.
        """
        decMv = (-1, -1)

        hitRegion = {x[0] for x in reversed(self._moves) if x[1] == const.HIT}

        if hitRegion:
            # Most likely situation is that these all form a single ship.
            # However, there is an unavoidable possibility that they do not.
            # We should first check to see whether it is possible to cover all
            # these cells with a single ship. If we can, we should base
            # solutions on that. If we cannot, or we can't move based on that,
            # (because, say, the hits form the exact shape of a larger ship)
            # we should do something else.

            # Check previous moves for unchecked cells
            border = set()

            for decMv in [c for x in hitRegion for c in circleCell(x)]:
                if (isValidCell(decMv) and
                        self._opponenBoard[decMv[0]][decMv[1]] == const.EMPTY):
                    border.add(decMv)

            oneShipCase = self.coverWithSingleShip(hitRegion, border)
            if oneShipCase is not None:
                return oneShipCase

            borderScores = dict.fromkeys(border, 0)

            # otherwise it's time to check the "More than one ship case"

            multiShipCase = self.coverWithMultipleShips(hitRegion, border)
            if multiShipCase is not None:
                return multiShipCase

            # Otherwise stop looking for those shapes
            for toDel in self.analyzeHitRegion(hitRegion, self.ships[:], [])[0]:
                self.ships.remove(toDel)

            # Reset _moves because we've checked all the hits we care about.
            self._moves = []

        # Failing that, it's probability distribution time.
        bestProb = 0
        for x in range(12):
            for y in range(len(self._opponenBoard[x])):
                thisProb = 0
                for shape in self.ships:
                    thisProb += self.countPossibilities((x, y), shape)
                if thisProb > bestProb:
                    bestProb = thisProb
                    decMv = (x, y)

        # Failing that, get a random cell (in a diagonal pattern)
        count = 0
        while (not isValidCell(decMv) or
                self._opponenBoard[decMv[0]][decMv[1]] != const.EMPTY):
            decMv = getRandCell()
            if count < 50 and (decMv[0] + decMv[1]) % 2 != 0:
                decMv = (-1, -1)

        assert(isValidCell(decMv) and
               self._opponenBoard[decMv[0]][decMv[1]] == const.EMPTY)
        return decMv

    def setOutcome(self, entry, row, col):
        """Update the opponent board with the outcome of our previous move.

        Keyword arguments:
        entry -- the outcome of your shot onto your opponent, expected value
                 is const.HIT for hit and const.MISSED for missed
        row -- the board row number (e.g. row A is 0)
        col -- the board column (e.g. col 2 is represented by value 3)

        """
        if entry == const.HIT:
            Outcome = const.HIT
        elif entry == const.MISSED:
            Outcome = const.MISSED
        else:
            raise Exception("Invalid input!")
        self._opponenBoard[row][col] = Outcome
        self._moves.append(((row, col), Outcome))

    def getOpponentMove(self, row, col):
        """Keep track of where the opponent is hitting."""
        if ((self._playerBoard[row][col] == const.OCCUPIED)
                or (self._playerBoard[row][col] == const.HIT)):
            # They may hit the same square twice so check for occupied or hit
            self._playerBoard[row][col] = const.HIT
            result = const.HIT
        else:
            # Acknowledge that the opponent missed
            # Todo? Keep track of misses
            result = const.MISSED
        return result

<commit_msg>LordAro doesn't know his greek alphabet.<commit_after>"""Dominus Blottleships AI"""
import const
import base_player
from random import randint

def getPlayer():
    """ MUST NOT be changed, used to get a instance of the class."""
    return Player()

def getRandCell():
    """Get a random piece on the board."""

    row = randint(0, 11)
    # Board is a weird L shape
    col = randint(0, 5 if row < 6 else 11)
    # Return move in row (letter) + col (number) grid reference
    # e.g. A3 is represented as 0,2
    return (row, col)

def isValidCell(cell):
    """Check that a generated cell is valid on the board.

    Keyword arguments:
    cell -- piece on the board to check

    """
    assert type(cell) == tuple
    if cell[0] < 0 or cell[1] < 0:
        return False
    if cell[0] > 11 or cell[1] > 11:
        return False
    if cell[0] < 6 and cell[1] > 5:
        return False

    return True

def getRotationFactor(rotation, i):
    """Rotate a piece (around (0, 0)).

    Keyword arguments:
    rotation -- arbitrary rotation factor
    i -- piece of the board to rotate

    """
    if rotation == 0:
        return i
    if rotation == 1:
        return (i[1], i[0])
    if rotation == 2:
        return (-i[0], -i[1])
    if rotation == 3:
        return (i[1], -i[0])
    raise IndexError  # It's sort of an index error

def rotateShip(rotation, ship, base=(0, 0)):
    """Rotate a ship.

    Keyword arguments:
    rotation -- arbitrary rotation factor
    ship -- ship to rotate
    base -- (default: (0, 0)

    """
    rotShip = []
    for cx, cy in ship:
        rx, ry = getRotationFactor(rotation, (cx, cy))
        rotShip.append((base[0] + rx, base[1] + ry))

    return frozenset(rotShip)

def circleCell(piece):
    """Get a list of pieces that are adjacent to another piece.

    Keyword arguments:
    piece -- piece on the board

    """
    assert type(piece) == tuple
    rotate = [(-1, 0), (0, 1), (1, 0), (0, -1)]
    return [(piece[0] + offset[0], piece[1] + offset[1]) for offset in rotate]

class Player(base_player.BasePlayer):
    """Dominus Blottleships AI implementation."""

    def __init__(self):
        base_player.BasePlayer.__init__(self)
        self._playerName = "Dominus"
        self._playerYear = "1"
        self._version = "Gamma"
        self._playerDescription = ("\"Dominus\" is Latin for Master."
                                   "Good luck.\nBy Charles Pigott and"
                                   "Nathan van Doorn")

        self._moves = []  # Previous moves
        self.shapes = []

    def makeShip(self, base, shape):
        """Place a ship on the board.

        Keyword arguments:
        base -- base piece to try placing the ship on
        shape -- ship to try placing

        """
        rotShip = rotateShip(randint(0, 3), shape, base)

        successful = []
        for cell in rotShip:
            success = True
            success = success and isValidCell(cell)
            success = success and self._playerBoard[cell[0]][cell[1]] == const.EMPTY
            if not success:
                return False

            # Try not to connect ships together
            count = 0
            for adjCell in circleCell(cell):
                success = success and (not isValidCell(adjCell) or
                                       adjCell in successful or
                                       self._playerBoard[adjCell[0]][adjCell[1]] == const.EMPTY)
                count += 1

            # Don't bother trying to separate ships if it's too hard
            if not success and count < 200:
                return False

            successful.append(cell)
        for cx, cy in successful:
            self._playerBoard[cx][cy] = const.OCCUPIED
        return True

    def deployFleet(self):
        """Place ship fleet on the board. """
        self._initBoards()

        # Reset some variables each game
        self._moves = []
        self.ships = [
            frozenset([(-1, 0), (0, 0), (0, -1), (0, 1), (1, -1), (1, 1)]),  # Hovercraft
            frozenset([(-1, -1), (1, -1), (0, -1), (0, 0), (0, 1), (0, 2)]),  # Aircraft Carrier
            frozenset([(0, 0), (0, 1), (0, 2), (0, 3)]),  # Battleship
            frozenset([(0, 0), (0, 1), (0, 2)]),  # Cruiser
            frozenset([(0, 0), (1, 0)])  # Destroyer
        ]

        for ship in self.ships:
            while True:
                sp = getRandCell()
                if self.makeShip(sp, ship):
                    break

        return self._playerBoard

    def countPossibilities(self, cell, shape):
        """Count the number of possible ways the given shape could overlap
        with the given cell.

        cell -- piece on the board to check
        shape -- ship to try placing
        """
        count = 0
        for rotation in xrange(4):
            for px, py in shape:
                shape2 = rotateShip(rotation, {(x - px, y - py) for x, y in shape}, cell)
                valid = True
                for x, y in shape2:
                    valid = valid and isValidCell((x, y))
                    valid = valid and self._opponenBoard[x][y] == const.EMPTY
                    if not valid:
                        break
                if valid:
                    count += 1
        return count

    def analyzeHitRegion(self, remCells, toTestShips, toDelShips):
        """Gets a list of ships that precisely cover a set of points.

        Keyword arguments:
        remCells -- remaining cells to test
        toTestShips -- ships still to test
        toDelShips -- ships already used in the solution

        """
        if not remCells:
            # We've got there :)
            return [toDelShips]

        if toTestShips:
            thisShip0 = toTestShips.pop()

            res = [(remCells, toTestShips[:], toDelShips[:])]

            for direction in range(4):
                thisShip = rotateShip(direction, thisShip0)

                for fx, fy in remCells:
                    for ox, oy in thisShip:
                        willBeTaken = {(fx - ox + px, fy - oy + py) for px, py in thisShip}

                        if willBeTaken <= remCells:
                            nextToDelShips = toDelShips[:]
                            nextToDelShips.append(thisShip0)
                            res.append((remCells - willBeTaken,
                                        toTestShips[:], nextToDelShips[:]))
            return [fin for state in res for fin in self.analyzeHitRegion(*state)]

        else:
            return []

    def coverWithSingleShip(self, hitRegion, border):
        """Chooses the most likely cell in the border to be a hit, assuming
        that there is only one ship, where no others are adjacent to it.

        Keyword arguments:
        hitRegion -- set of known hits
        border -- set of points adjacent to these hits

        """
        borderScores = dict.fromkeys(border, 0)

        for cell in hitRegion:
            for shapePreRot in self.ships:
                for px, py in shapePreRot:
                    for orientation in range(4):
                        shape = rotateShip(orientation, {(x - px, y - py) for x, y in shapePreRot}, base=cell)

                        if hitRegion <= shape:

                            valid = True
                            for cx, cy in shape:
                                valid = valid and isValidCell((cx, cy))
                                valid = valid and self._opponenBoard[cx][cy] != const.EMPTY or (cx, cy) in hitRegion

                            if valid:
                                for coord in shape & border:
                                    borderScores[coord] += 1

        try:
            best = max(borderScores.items(), key=lambda kv: kv[1])
            if best[1]:
                return best[0]

        except ValueError:
            pass

    def coverWithMultipleShips(self, hitRegion, border):
        """Chooses the most likely cell in the border to be a hit, assuming
        there are ships adjacent to each other.

        Keyword arguments:
        hitRegion -- set of known hits
        border -- set of points adjacent to these hits

        """
        borderScores = dict.fromkeys(border, 0)

        def helperFunction(toCover, covered, remaining):
            """Recursive helper function to calculate the best point on the
            board to hit.

            Keyword arguments:
            toCover -- set of coords to cover
            covered -- set of coords already covered
            remaining -- remaining ships to check

            """
            if not (toCover and remaining):
                # FLAWLESS VICTORY!
                # Update the weightings
                for coord in covered & border:
                    borderScores[coord] += 1

            else:
                for shapePreOffset in self.ships:
                    for pivx, pivy in shapePreOffset:
                        for orientation in range(4):
                            coord = next(iter(toCover))
                            shape = rotateShip(orientation, [(x - pivx, y - pivy) for x, y in shapePreOffset], base=coord)

                            # make sure it fits
                            valid = True
                            for cx, cy in shape:
                                valid = valid and isValidCell((cx, cy))
                                valid = valid and self._opponenBoard[cx][cy] == const.EMPTY or (cx, cy) in toCover
                                valid = valid and (cx, cy) not in covered
                                if not valid:
                                    break

                            if valid:
                                helperFunction(toCover - shape, covered | shape, remaining[:].remove(shapePreOffset))

        helperFunction(hitRegion, frozenset(), self.ships[:])
        try:
            best = max(borderScores.items(), key=lambda kv: kv[1])
            if best[1]:
                return best[0]
        except ValueError:
            pass

    def chooseMove(self):
        """Decide what move to make based on current state of opponent's
        board and return it.
        """
        decMv = (-1, -1)

        hitRegion = {x[0] for x in reversed(self._moves) if x[1] == const.HIT}

        if hitRegion:
            # Most likely situation is that these all form a single ship.
            # However, there is an unavoidable possibility that they do not.
            # We should first check to see whether it is possible to cover all
            # these cells with a single ship. If we can, we should base
            # solutions on that. If we cannot, or we can't move based on that,
            # (because, say, the hits form the exact shape of a larger ship)
            # we should do something else.

            # Check previous moves for unchecked cells
            border = set()

            for decMv in [c for x in hitRegion for c in circleCell(x)]:
                if (isValidCell(decMv) and
                        self._opponenBoard[decMv[0]][decMv[1]] == const.EMPTY):
                    border.add(decMv)

            oneShipCase = self.coverWithSingleShip(hitRegion, border)
            if oneShipCase is not None:
                return oneShipCase

            borderScores = dict.fromkeys(border, 0)

            # otherwise it's time to check the "More than one ship case"

            multiShipCase = self.coverWithMultipleShips(hitRegion, border)
            if multiShipCase is not None:
                return multiShipCase

            # Otherwise stop looking for those shapes
            for toDel in self.analyzeHitRegion(hitRegion, self.ships[:], [])[0]:
                self.ships.remove(toDel)

            # Reset _moves because we've checked all the hits we care about.
            self._moves = []

        # Failing that, it's probability distribution time.
        bestProb = 0
        for x in range(12):
            for y in range(len(self._opponenBoard[x])):
                thisProb = 0
                for shape in self.ships:
                    thisProb += self.countPossibilities((x, y), shape)
                if thisProb > bestProb:
                    bestProb = thisProb
                    decMv = (x, y)

        # Failing that, get a random cell (in a diagonal pattern)
        count = 0
        while (not isValidCell(decMv) or
                self._opponenBoard[decMv[0]][decMv[1]] != const.EMPTY):
            decMv = getRandCell()
            if count < 50 and (decMv[0] + decMv[1]) % 2 != 0:
                decMv = (-1, -1)

        assert(isValidCell(decMv) and
               self._opponenBoard[decMv[0]][decMv[1]] == const.EMPTY)
        return decMv

    def setOutcome(self, entry, row, col):
        """Update the opponent board with the outcome of our previous move.

        Keyword arguments:
        entry -- the outcome of your shot onto your opponent, expected value
                 is const.HIT for hit and const.MISSED for missed
        row -- the board row number (e.g. row A is 0)
        col -- the board column (e.g. col 2 is represented by value 3)

        """
        if entry == const.HIT:
            Outcome = const.HIT
        elif entry == const.MISSED:
            Outcome = const.MISSED
        else:
            raise Exception("Invalid input!")
        self._opponenBoard[row][col] = Outcome
        self._moves.append(((row, col), Outcome))

    def getOpponentMove(self, row, col):
        """Keep track of where the opponent is hitting."""
        if ((self._playerBoard[row][col] == const.OCCUPIED)
                or (self._playerBoard[row][col] == const.HIT)):
            # They may hit the same square twice so check for occupied or hit
            self._playerBoard[row][col] = const.HIT
            result = const.HIT
        else:
            # Acknowledge that the opponent missed
            # Todo? Keep track of misses
            result = const.MISSED
        return result

<|endoftext|>