#!/usr/bin/env python
# NOTE: The whole database could also be downloaded from website:
# https://www.atnf.csiro.au/research/pulsar/psrcat/download.html
import os
import csv
import logging
import argparse
import requests
from bs4 import BeautifulSoup

logging.basicConfig(level=logging.INFO, format="%(filename)s: %(message)s")

def request_data(coor1, coor2, r):
    baseurl = "https://www.atnf.csiro.au/research/pulsar/psrcat/proc_form.php"
    parameters = {
        "version": "1.62",
        "JName": "JName",
        "Dist": "Dist",
        "Age": "Age",
        "Edot": "Edot",
        "RaJD": "RaJD",
        "DecJD": "DecJD",
        "startUserDefined": "true",
        "sort_attr": "jname",
        "sort_order": "asc",
        "condition": "",
        "ephemeris": "short",
        "coords_unit": "rajd/decjd",
        "radius": r,
        "coords_1": coor1,
        "coords_2": coor2,
        "style": "Short csv without errors",
        "no_value": "*",
        "fsize": "3",
        "x_axis": "",
        "x_scale": "linear",
        "y_axis": "",
        "y_scale": "linear",
        "state": "query",
        "table_bottom.x": "29",
        "table_bottom.y": "21"
    }

    try:
        res = requests.get(baseurl, params=parameters)
    except (requests.exceptions.ConnectionError,
            requests.exceptions.ConnectTimeout):
        logging.error("Connection error, please re-run the script")
        exit(1)

    if res:
        soup = BeautifulSoup(res.content, 'html.parser')
        rawdata = soup.find("pre").string.strip() + '\n'
    else:
        logging.error("Download failed, please re-run the script")
        exit(1)

    return rawdata

def save_data(rawdata):
    rawdata_file = os.path.join(resultdir, "rawdata.csv")
    data_file = os.path.join(resultdir, "data.csv")
    with open(rawdata_file, "w") as f:
        f.write(rawdata)

    with open(rawdata_file) as rawdata, open(data_file, "w") as f:
        count = 0
        fields = ['JName', 'Age', 'Dist', 'Edot', 'RaJD', 'DecJD']
        writer = csv.DictWriter(f, fieldnames=fields)
        writer.writeheader()
        for data in csv.DictReader(rawdata, delimiter=';'):
            if not data['#']:
                continue
            count += 1
            RaJD = float(data['RAJD'])
            DecJD = float(data['DECJD'])
            if data['DIST'] != '*' and data['AGE'] != '*' \
                    and data['EDOT'] != '*':
                # convert unit from erg/s to m_e c^2/s
                Edot = float(data['EDOT']) * 1221432.8760283517
                # convert unit from kpc to pc
                Dist = float(data['DIST']) * 1e3
                # convert unit from yr to s
                Age = float(data['AGE']) * 365.25 * 24 * 60 * 60

                writer.writerow({'JName': data['PSRJ'],
                                 'Age': Age, 'Dist': Dist, 'Edot': Edot,
                                 'RaJD': RaJD, 'DecJD': DecJD})
            else:
                Edot = data['EDOT']
                Dist = data['DIST']
                Age = data['AGE']
                writer.writerow({'JName': data['PSRJ'],
                                 'Age': Age, 'Dist': Dist, 'Edot': Edot,
                                 'RaJD': RaJD, 'DecJD': DecJD})
        logging.info("Data saved!")
        logging.info("Total: %d" % (count))

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-c', '--coor', nargs=2, metavar=("Ra/deg", "Dec/deg"))
    parser.add_argument('-r', '--radius', nargs=1, metavar="Radius/deg")
    args = parser.parse_args()

    if not 0 < float(args.coor[0]) < 360:
        logging.error("<ra> should be within (0, 360) degrees")
        exit(1)
    if not -90 < float(args.coor[1]) < 90:
        logging.error("<dec> should be within (-90, 90) degrees")
        exit(1)

    currentdir = os.path.dirname(__file__)
    resultdir = os.path.join(currentdir, "result")
    if not os.path.exists(resultdir):
        os.mkdir(resultdir)

    rawdata = request_data(args.coor[0], args.coor[1], args.radius)
    save_data(rawdata)


import numpy as np
from sklearn.metrics import precision_recall_curve, average_precision_score

def calc_tp_tn_fp_fn(label, cmat, cmat_labels):
    """
    Return the number of true positives (TPs), true negatives (TNs),
    false positives (FPs) and false negatives (FNs) for a given class label.

    Args:
        label: The class label for which to calculate the TPs, TNs, FPs and FNs.
        cmat: The confusion matrix. The rows should correspond to the true labels,
            the columns should correspond to the predicted labels.
        cmat_labels: The class labels in the confusion matrix.
    """
    label_idx = get_label_idx(label, cmat_labels)

    tps = cmat[label_idx, label_idx]
    fps = np.sum(cmat[:, label_idx]) - tps
    fns = np.sum(cmat[label_idx, :]) - tps
    tns = np.sum(cmat) - fps - fns - tps

    return tps, tns, fps, fns

def calc_precision(label, cmat, cmat_labels):
    """
    Return the precision for a given class label.

    Args:
        label: The class label for which to calculate the precision.
        cmat: The confusion matrix. The rows should correspond to the true labels,
            the columns should correspond to the predicted labels.
        cmat_labels: The class labels in the confusion matrix.
    """
    label_idx = get_label_idx(label, cmat_labels)

    tps = cmat[label_idx, label_idx]
    all_positives = np.sum(cmat[:, label_idx])

    return tps / all_positives

def calc_recall(label, cmat, cmat_labels):
    """
    Return the recall for a given class label.

    Args:
        label: The class label for which to calculate the recall.
        cmat: The confusion matrix. The rows should correspond to the true labels,
            the columns should correspond to the predicted labels.
        cmat_labels: The class labels in the confusion matrix.
    """
    label_idx = get_label_idx(label, cmat_labels)

    tps = cmat[label_idx, label_idx]
    all_true = np.sum(cmat[label_idx, :])

    return tps / all_true

def calc_pr_curve(label, sim_mat, gallery_labels, query_labels):
    """
    Return the precision-recall curve for a given class label. The PR-curve is returned
    as three arrays: precision, recall, thresholds. Doing this correctly, however, is not
    trivial when we have multiple classes. The following text goes through it step by step.

    Each query will be classified as the label with which it has the highest similarity. For
    a certain class, queries that are classified as that class are (true or false) positives
    and queries that are classified as another class are (true or false) negatives. As such,
    we can compute the precision and recall for each class.

    Since each classification also has a similarity score, we can choose to put a *threshold*
    on the similarity scores. Setting a threshold value for a certain class means that we
    classify all queries with a score lower than the threshold as *negative* and those with a
    score higher than or equal to the threshold as *positive*. The lowest (resp. highest)
    possible threshold is thus the lowest (resp. highest) similarity score.

    By increasing the threshold value for a certain class from the lowest to the highest
    possible value, we will get less and less queries that are classified as that class. Hence,
    the recall of the classifier will decrease (or stay the same, but never increase). However,
    meanwhile, the precision might increase since we are raising the bar on which scores we
    consider high enough. A PR-curve visualizes this trade-off between precision and recall for
    all possible thresholds.

    The PR-curve that evaluates the classification of a certain class is drawn based on a list
    of scores (`y_score`) and a binary list (`y_true`) that indicates which of the queries truly
    belonged to the chosen class. The scores are sorted from high to low and at each rank the
    precision and recall are evaluated by classifying the samples up until the rank as *positive*
    and those after the rank as *negative*. This is equivalent to choosing thresholds and
    classifying the queries under the threshold as negative and those equal to or above the
    threshold as positive.

    An important issue is how to compose `y_score` and `y_true` correctly for the evaluation of
    the classification of a certain class label, given the similarity matrix. As described above,
    the scores in `y_score` will be sorted and will be considered as a predicted positive
    classification at certain ranks. There are scores in the similarity matrix, however, that will
    *never* lead to a positive classification for the chosen class. First, the similarity scores
    with other classes will obviously never lead to a classification as the chosen class. Therefore,
    these scores should be excluded from `y_score`. Second, the similarity scores with the chosen
    class that are *not* the maximum similarity score of the query, will also *never* lead to a
    positive classification for the chosen class. This is because, when classifying a query, we
    only take the class with maximum similarity score into consideration.

    From the above reasoning, it follows that `y_score` should only consist of scores that have
    a maximum value for the class label that we are evaluating. `y_true` then indicates which of
    the queries corresponding to the scores in `y_scores` truely belong to that class.

    The problem with this is, however, that **we will miss a constant number of false negatives**.
    This is because the queries that *do* belong to the chosen class but that will never be classified
    *as* the chosen class (because their score is not the maximum similarity), will also not
    show up in `y_scores` and `y_true`. Hence, the denominator of the recall will be too small and
    the recall will be overestimated. Luckily, the denominator of the recall is constant as it is
    equal to the number of queries that truely have the chosen label. Hence, we can easily correct
    the recalls by multiplying away the wrong denominator and dividing it again with the correct
    denominator.

    One might suggest to add the non-maximum queries that do belong to the chosen class to `y_scores`
    and `y_true` as well. This, however, will lead to erroneous results, since those scores are not
    guaranteed to be lower than the other scores in `y_scores`, while they *should* be. A more valid
    suggestion would be to assign a score of $-\infty$ to these queries, which illustrates the fact
    that these queries will never be classified as positive. Similarity scores of infinite value
    might not be supported, however (e.g. in `sklearn`).

    Args:
        label: The class label for which to calculate the precision-recall curve.
        sim_mat: The similarity matrix. It should have the same amount of rows as
            the number of queries and the same amount of columns as the number of
            class labels in the gallery.
        gallery_labels: The labels that correspond to the columns in `sim_mat`,
            i.e. the labels in the gallery.
        query_labels: The labels that correspond to the rows in `sim_mat`, i.e.
            the true label of each query.
    """
    # y_score should only contain scores that have a maximum value
    # for `label`
    match_idxs = sim_mat.argmax(axis=1)
    label_idx = get_label_idx(label, gallery_labels)
    row_idxs, = np.where(match_idxs == label_idx)
    y_score = sim_mat[row_idxs, match_idxs[row_idxs]]

    # y_score tells which of these truely correspond to `label`
    y_true = query_labels[row_idxs] == label

    # If no queries has a betch match for the given label,
    # return a PR-curve with a single point at p = 1., r = 0.
    if len(y_true) == 0:
        return (np.array([0.]),
                np.array([1.]),
                np.array([0.]))

    # Calculate the PR-curve from y_true and y_score
    p, r, t = precision_recall_curve(y_true, y_score)

    # Compensate for the underestimation of false negatives
    true_num_tp_fn = (query_labels == label).sum()
    selected_num_tp_fn = y_true.sum()
    r *= selected_num_tp_fn / true_num_tp_fn

    return p, r, t

def calc_ap(label, sim_mat, gallery_labels, query_labels):
    """
    Return the average precision for a given class label.

    Args:
        label: The class label for which to calculate the precision-recall curve.
        sim_mat: The similarity matrix. It should have the same amount of rows as
            the number of queries and the same amount of columns as the number of
            class labels in the gallery.
        gallery_labels: The labels that correspond to the columns in `sim_mat`,
            i.e. the labels in the gallery.
        query_labels: The labels that correspond to the rows in `sim_mat`, i.e.
            the true label of each query.
    """
    precision, recall, _ = calc_pr_curve(label, sim_mat, gallery_labels,
                                         query_labels)
    return -np.sum(np.diff(recall) * np.array(precision)[:-1])

def get_label_idx(label, cmat_labels):
    if label not in cmat_labels:
        raise ValueError(f'Label {label} not found')

    idxs = np.where(cmat_labels == label)[0]

    if len(idxs) > 1:
        raise ValueError(f'Label {label} should only occur once')

    return idxs[0]


from itertools import combinations
import copy
def solution(n, info):
    answer = []
    answer_score = -55
    info = info[::-1]
    li = []
    apach_got = []

    for i in range(11):
        if info[i] > 0: apach_got.append(i)
        # (필요 화살, 점수)
        li.append((info[i] + 1, i))

    apach_score = sum(apach_got)

    for i in range(1, 12):
        combi = list(combinations(li, i))
        print(combi)

        for j in combi:
            lion_score = 0
            apa_score = apach_score
            summ = 0

            for num_n, score in j:
                summ += num_n

                if score in apach_got:
                    apa_score -= score

                lion_score += score

            if summ <= n and apa_score < lion_score:
                li_j = list(j)

                for _ in range(n - summ):
                    li_j.append((1, 0))

                if answer_score == lion_score:
                    answer.append(copy.deepcopy(li_j))

                elif answer_score < lion_score:
                    answer_score = lion_score
                    answer = [copy.deepcopy(li_j)]

    if len(answer) == 0: return [-1]

    new_answer = []

    for i in answer:
        a = [0] * 11
        for num_n, score in i:
            a[score] += num_n
        new_answer.append(a)
    new_answer.sort()
    return new_answer[-1][::-1]

solution(10, [0,0,0,0,0,0,0,0,3,4,3])


import numpy as np
import cv2
import os
import time

class Model:

    def __init__(self,WEIGHTS_PATH,CONFIG_PATH,LABELS_PATH):
        self.WEIGHTS_PATH = WEIGHTS_PATH
        self.CONFIG_PATH = CONFIG_PATH
        self.LABELS_PATH = LABELS_PATH

        self.LABELS = []
        self.USE_GPU = True

        try:
            with open(self.LABELS_PATH, "r") as f:
                for line in f.readlines():
                    self.LABELS.append(line.strip("\n"))  # her bir class adından '\n' i sildik
            print("LABELS :",self.LABELS)
            self.__colors = np.random.randint(0, 255, size=(len(self.LABELS), 3), dtype='uint8')
        except:
            print("[LOAD LABELS ERROR]")
            exit(1)

    def load_yolo(self):

        self.net = cv2.dnn.readNet(self.WEIGHTS_PATH, self.CONFIG_PATH)  # load YOLO algorithm.

        if self.USE_GPU:
            self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
            self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
            print('Using GPU')
        else:
            self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_DEFAULT)
            self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
            print('Using CPU')

        self.layer_names = self.net.getLayerNames()

        print(f"Layers Names [{len(self.layer_names)}] :", self.layer_names)  # CNN modelinin katmanları

        self.output_layers = []  # Output layerlarin isimleri
        for i in range(len(self.layer_names)):
            if i + 1 in self.net.getUnconnectedOutLayers():  # Output layerlarin indisini doner
                self.output_layers.append(self.layer_names[i])

        print("Output Layers : ", self.output_layers)

        print("[WAIT] Model is loading...")

    def __extract_boxes_confidences_classids(self,outputs, confidence, width, height):
        boxes = []
        confidences = []
        classIDs = []

        for output in outputs:
            for detection in output:
                # Extract the scores, classid, and the confidence of the prediction
                scores = detection[5:]
                classID = np.argmax(scores)
                conf = scores[classID]

                # Consider only the predictions that are above the confidence threshold
                if conf > confidence:
                    # Scale the bounding box back to the size of the image
                    box = detection[0:4] * np.array([width, height, width, height])
                    centerX, centerY, w, h = box.astype('int')

                    # Use the center coordinates, width and height to get the coordinates of the top left corner
                    x = int(centerX - (w / 2))
                    y = int(centerY - (h / 2))

                    boxes.append([x, y, int(w), int(h)])
                    confidences.append(float(conf))
                    classIDs.append(classID)

        return boxes, confidences, classIDs

    def make_prediction(self, image, confidence=0.5, threshold=0.3):
        height, width = image.shape[:2]

        # Create a blob and pass it through the model
        blob = cv2.dnn.blobFromImage(image, 1 / 255.0, (416, 416), swapRB=True, crop=False)
        self.net.setInput(blob)
        outputs = self.net.forward(self.output_layers)

        # Extract bounding boxes, confidences and classIDs
        boxes, confidences, classIDs = self.__extract_boxes_confidences_classids(outputs, confidence, width, height)

        # Apply Non-Max Suppression
        idxs = cv2.dnn.NMSBoxes(boxes, confidences, confidence, threshold)

        return boxes, confidences, classIDs, idxs

    def draw_bounding_boxes(self,image, boxes, confidences, classIDs, idxs):
        if len(idxs) > 0:
            for i in idxs.flatten():
                # extract bounding box coordinates
                x, y = boxes[i][0], boxes[i][1]
                w, h = boxes[i][2], boxes[i][3]

                # draw the bounding box and label on the image
                color = [int(c) for c in self.__colors[classIDs[i]]]
                cv2.rectangle(image, (x, y), (x + w, y + h), color, 2)
                text = "{}: {:.4f}".format(self.LABELS[classIDs[i]], confidences[i])
                cv2.putText(image, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)

        return image

if __name__ == "__main__":
    img = cv2.imread("./test_images/image7.jpg")
    model = Model("./model/yolov4-tiny-custom_best.weights","./model/yolov4-tiny-custom.cfg","./model/obj.names")
    model.load_yolo()
    boxes, confidences, classIDs, idxs = model.make_prediction(img)
    print(boxes,confidences,classIDs)
    img = model.draw_bounding_boxes(img,boxes,confidences,classIDs,idxs)

    cv2.imshow("window",img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


import logging
import os
import pickle
import random
import json

import numpy as np
import torch
import glob
import re
import shutil

try:
    from torch.utils.tensorboard import SummaryWriter
except:
    from tensorboardX import SummaryWriter

from torch import nn
from torch.distributions import Categorical
from torch.utils.data import DataLoader, Sampler, SequentialSampler, RandomSampler
from torch.utils.data.distributed import DistributedSampler

from collections import defaultdict, deque
from tqdm import tqdm, trange
from transformers import AdamW, get_linear_schedule_with_warmup
from apex import amp

from .util import set_seed, load_and_cache_examples
from .trainer import Trainer

logger = logging.getLogger(__name__)

class DomSampler(nn.Module):
    def __init__(self, model):
        super().__init__()
        self.domain_embedding = model.domain_cls.predictions.decoder
        self.sim_fn = nn.CosineSimilarity(-1)
        self.domain_cnts = defaultdict(int)

    def forward(self):
        with torch.no_grad():
            # laptop uses 0.1, restaurant uses 0.13
            tp = 0.1 # 0.13
            logits = self.sim_fn(self.domain_embedding.weight.data[0], self.domain_embedding.weight.data) / tp
            # logits = torch.matmul(self.domain_embedding.weight.data[0], self.domain_embedding.weight.data.transpose(1, 0))
            probs = torch.softmax(logits.float(), 0)
            self.sampler = Categorical(probs)

class DomainSampler(Sampler):
    def __init__(self, dataset, domain_sampler):
        self.domain_data_dict = defaultdict(list)
        for ex_idx, ex in enumerate(dataset):
            self.domain_data_dict[ex[0].item()].append(ex_idx)
        logger.info("total number of target domain examples: %s", str(len(self.domain_data_dict[0])))
        for domain_idx in self.domain_data_dict:
            random.shuffle(self.domain_data_dict[domain_idx])
        self.domain_sampler = domain_sampler
        self.domain_offsets = {key: 0 for key in self.domain_data_dict}

    def __iter__(self):
        ex_cnt = 0
        while ex_cnt < len(self.domain_data_dict[0]):
            domain_idx = self.domain_sampler.sampler.sample().item()
            while len(self.domain_data_dict[domain_idx]) == 0:
                domain_idx = self.domain_sampler.sampler.sample().item()
            self.domain_sampler.domain_cnts[domain_idx] += 1
            yield self.domain_data_dict[domain_idx][self.domain_offsets[domain_idx]]
            ex_cnt += 1
            self.domain_offsets[domain_idx] += 1
            if self.domain_offsets[domain_idx] >= len(self.domain_data_dict[domain_idx]):
                random.shuffle(self.domain_data_dict[domain_idx])
                self.domain_offsets[domain_idx] = 0

    def __len__(self):
        return len(self.domain_data_dict[0])

class DomTrainer(Trainer):
    def _build_train_data_loader(self, args, train_dataset, model):
        with open("data/pt/domain_tag/doi_domain.json") as f:
            domain_dict = json.load(f)
        self.id_to_domain = {domain_dict[domain]: domain for domain in domain_dict}

        self.meta_model = DomSampler(model)
        self.meta_model()

        self.train_sampler = DomainSampler(train_dataset, self.meta_model)
        train_dataloader = DataLoader(train_dataset, sampler=self.train_sampler, batch_size=args.train_batch_size)
        return self.train_sampler, train_dataloader

    def _post_step(self, args, outputs):
        # outputs: 0 is the total loss, 1 is the lm_loss, 2 is the domain_cls loss.
        if args.gradient_accumulation_steps > 1:
            lm_loss = outputs[1] / args.gradient_accumulation_steps
            domain_cls_loss = outputs[2] / args.gradient_accumulation_steps
        self.lm_loss += lm_loss.item()
        self.domain_cls_loss += domain_cls_loss.item()

    def _post_training(self):
        self.meta_model() # update the sampler

    def _init_logging(self):
        super()._init_logging()
        self.lm_loss, self.logging_lm_loss, self.domain_cls_loss, self.logging_domain_cls_loss = 0.0, 0.0, 0.0, 0.0

    def _train_writer(self, logging_steps):
        super()._train_writer(logging_steps)
        self.tb_writer.add_scalar("lm_loss", (self.lm_loss - self.logging_lm_loss)/logging_steps, self.global_step)
        self.tb_writer.add_scalar("domain_cls_loss", (self.domain_cls_loss - self.logging_domain_cls_loss)/logging_steps, self.global_step)
        self.logging_lm_loss = self.lm_loss
        self.logging_domain_cls_loss = self.domain_cls_loss

        cnt_rank = [self.id_to_domain[k]+": "+str(v) for k_id, (k, v) in enumerate(sorted(self.meta_model.domain_cnts.items(), key=lambda item: item[1], reverse=True)) if k_id < 30]
        logger.info("total sampled examples: %s", str(sum(list(self.meta_model.domain_cnts.values()))))
        logger.info("top-30 examples (domains : counts) \n%s\n", "\n".join(cnt_rank))
        self.meta_model.domain_cnts = defaultdict(int) # reset

        with torch.no_grad():
            probs, domain_ids = torch.topk(self.meta_model.sampler.probs, 30)
            domain_rank = [self.id_to_domain[domain_id.item()]+": "+str(float(probs[rank_id])) for rank_id, domain_id in enumerate(domain_ids)]
        logger.info("top-30 probs (domains: probs) \n%s\n", "\n".join(domain_rank))


import streamlit as st
import numpy as np
import nltk
import pdb
import pickle
import string
from os import getcwd
from utils import cosine_similarity

def nearest_neighbor(v, candidates, k=1, cosine_similarity=cosine_similarity):
    """
    Input:
      - v, the vector you are going find the nearest neighbor for
      - candidates: a set of vectors where we will find the neighbors
      - k: top k nearest neighbors to find
    Output:
      - k_idx: the indices of the top k closest vectors in sorted form
    """
    similarity_l = []

    # for each candidate vector...
    for row in candidates:
        # get the cosine similarity
        cos_similarity = cosine_similarity(v, row)

        # append the similarity to the list
        similarity_l.append(cos_similarity)

    # sort the similarity list and get the indices of the sorted list
    sorted_ids = np.argsort(np.array(similarity_l))

    # Reverse the order of the sorted_ids array
    sorted_ids = np.flip(sorted_ids)

    # get the indices of the k most similar candidate vectors
    k_idx = sorted_ids[:k]
    return k_idx

# add folder, tmp2, from our local workspace containing pre-downloaded corpora files to nltk's data path
filePath = f"{getcwd()}/tmp2/"
nltk.data.path.append(filePath)

# Load english and french word embedding
en_embeddings_subset = pickle.load(open("./data/en_embeddings.p", "rb"))
fr_embeddings_subset = pickle.load(open("./data/fr_embeddings.p", "rb"))

st.title("Machine Translation English to French")
st.write("The full dataset for English embeddings is about 3.64 gigabytes, and the French embeddings are about 629 megabytes. To safety for memory, in this project I will use subset of the embeddings for the words that we'll use in this assignment. So with some word in English you may not translate it")

with open("R.npy", "rb") as f:
    R_train = np.load(f)

with open("Y.npy", "rb") as f:
    Y_subset = np.load(f)

english_word = st.text_input("Your english word: ")

if english_word in en_embeddings_subset:
    english_word_embedding = en_embeddings_subset[english_word]
    french_word_embedding = np.dot(english_word_embedding, R_train)
    idx = nearest_neighbor(french_word_embedding, Y_subset, 1)
    for i in fr_embeddings_subset.keys():
        if (np.array_equal(fr_embeddings_subset[i], Y_subset[idx.item()])):
            st.write("French word is: ", i)
else:
    st.write("Can't translate")


from absl.testing import absltest
import jax
from learned_optimization import eval_training
from learned_optimization.learned_optimizers import base as lopt_base
from learned_optimization.optimizers import base as opt_base
from learned_optimization.tasks import quadratics

class EvalTrainingTest(absltest.TestCase):

  def test_single_task_training_curves_no_data(self):
    key = jax.random.PRNGKey(0)
    task = quadratics.QuadraticTask(10)
    opt = opt_base.SGD()
    res = eval_training.single_task_training_curves(
        task, opt, 10, key, eval_every=2)
    self.assertIn("train/xs", res)
    self.assertIn("train/loss", res)
    self.assertEqual(res["train/loss"].shape, (11,))

  def test_single_task_training_curves_with_data(self):
    key = jax.random.PRNGKey(0)
    task = quadratics.BatchQuadraticTask()
    opt = opt_base.SGD()
    res = eval_training.single_task_training_curves(
        task, opt, 10, key, eval_every=2)
    self.assertIn("train/xs", res)
    self.assertIn("train/loss", res)
    self.assertEqual(res["train/loss"].shape, (11,))

  def test_multi_task_training_curves(self):
    key = jax.random.PRNGKey(0)
    task_family = quadratics.FixedDimQuadraticFamilyData(10)
    learned_opt = lopt_base.LearnableSGD()
    theta = learned_opt.init(key)

    res = eval_training.multi_task_training_curves(
        task_family,
        learned_opt,
        theta,
        n_tasks=5,
        key=key,
        eval_every=2,
        steps_per_jit=2,
        steps=10,
        with_aux_values=("l2",),
    )
    self.assertIn("train/xs", res)
    self.assertIn("train/loss", res)
    self.assertIn("eval/train/loss", res)
    self.assertEqual(res["train/loss"].shape, (
        5,
        10,
    ))

    # 10 / 2 and then 1 additional due to eval-ing at the end.
    self.assertEqual(res["eval/train/loss"].shape, (
        5,
        6,
    ))

    self.assertIn("eval/train/aux/l2", res)
    self.assertNotIn("eval/train/aux/l1", res)

if __name__ == "__main__":
  absltest.main()


# Problem Statement: https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/

from typing import List

class Solution:
    def findMaximumXOR(self, nums: List[int]) -> int:
        """
        let ni and nj be the numbers such that xor between them produces maximum
        value among other pairs. let m be the max value.

        m = ni ^ nj
        => m ^ nj = (ni ^ nj) ^ nj
        =>        = ni ^ (nj ^ nj) # xor is associative
        =>        = ni ^ 0
        =>        = ni

        so, m ^ nj = ni

        We note that m is 31-bit integer so we guess bits of m and and with each nj,
        we check if the combination of m and nj will produce ni.

        So time complexity becomes O(32N) = O(N)
        :param nums:
        :return:
        """
        m, mask = 0, 0

        for i in range(32)[::-1]:
            mask |= 1 << i
            prefixes = {n & mask for n in nums}

            tmp = m | (1 << i)

            if any(prefix ^ tmp in prefixes for prefix in prefixes):
                m = tmp

        return m

Medium/421. Maximum XOR of Two Numbers in an Array.py


# -*- coding: utf-8 -*-
import os
import time
import datetime
import xml.etree.ElementTree as ET
from caches import check_databases, clean_databases
from caches.trakt_cache import clear_trakt_list_contents_data
from apis.trakt_api import trakt_sync_activities
from modules import kodi_utils, settings
from modules.nav_utils import sync_MyAccounts
from modules.settings_reader import get_setting, set_setting, make_settings_dict

logger = kodi_utils.logger
ls, monitor, path_exists, translate_path, is_playing = kodi_utils.local_string, kodi_utils.monitor, kodi_utils.path_exists, kodi_utils.translate_path, kodi_utils.player.isPlaying
get_property, set_property, clear_property, get_visibility = kodi_utils.get_property, kodi_utils.set_property, kodi_utils.clear_property, kodi_utils.get_visibility

class InitializeDatabases:
	def run(self):
		logger('EZRA', 'InitializeDatabases Service Starting')
		check_databases()
		return logger('EZRA', 'InitializeDatabases Service Finished')

class CheckSettingsFile:
	def run(self):
		logger('EZRA', 'CheckSettingsFile Service Starting')
		clear_property('ezra_settings')
		profile_dir = translate_path('special://profile/addon_data/plugin.video.ezra/')
		if not path_exists(profile_dir): kodi_utils.make_directorys(profile_dir)
		settings_xml = translate_path('special://profile/addon_data/plugin.video.ezra/settings.xml')
		if not path_exists(settings_xml):
			__addon__ = kodi_utils.addon()
			addon_version = __addon__.getAddonInfo('version')
			__addon__.setSetting('version_number', addon_version)
			monitor.waitForAbort(0.5)
		make_settings_dict()
		set_property('ezra_kodi_menu_cache', get_setting('kodi_menu_cache'))
		return logger('EZRA', 'CheckSettingsFile Service Finished')

class SyncMyAccounts:
	def run(self):
		logger('EZRA', 'SyncMyAccounts Service Starting')
		sync_MyAccounts(silent=True)
		return logger('EZRA', 'SyncMyAccounts Service Finished')

class ReuseLanguageInvokerCheck:
	def run(self):
		logger('EZRA', 'ReuseLanguageInvokerCheck Service Starting')
		addon_xml = translate_path('special://home/addons/plugin.video.ezra/addon.xml')
		tree = ET.parse(addon_xml)
		root = tree.getroot()
		current_addon_setting = get_setting('reuse_language_invoker', 'true')
		refresh, text = True, '%s\n%s' % (ls(33021), ls(33020))
		for item in root.iter('reuselanguageinvoker'):
			if item.text == current_addon_setting: refresh = False; break
			item.text = current_addon_setting
			tree.write(addon_xml)
			break
		if refresh and kodi_utils.confirm_dialog(text=text): kodi_utils.execute_builtin('LoadProfile(%s)' % kodi_utils.get_infolabel('system.profilename'))
		return logger('EZRA', 'ReuseLanguageInvokerCheck Service Finished')

class ViewsSetWindowProperties:
	def run(self):
		logger('EZRA', 'ViewsSetWindowProperties Service Starting')
		kodi_utils.set_view_properties()
		return logger('EZRA', 'ViewsSetWindowProperties Service Finished')

class AutoRun:
	def run(self):
		logger('EZRA', 'AutoRun Service Starting')
		if settings.auto_start_ezra(): kodi_utils.execute_builtin('RunAddon(plugin.video.ezra)')
		return logger('EZRA', 'AutoRun Service Finished')

class DatabaseMaintenance:
	def run(self):
		time = datetime.datetime.now()
		current_time = self._get_timestamp(time)
		due_clean = int(get_setting('database.maintenance.due', '0'))
		if current_time >= due_clean:
			logger('EZRA', 'Database Maintenance Service Starting')
			monitor.waitForAbort(10)
			clean_databases(current_time, database_check=False, silent=True)
			next_clean = str(int(self._get_timestamp(time + datetime.timedelta(days=3))))
			set_setting('database.maintenance.due', next_clean)
			return logger('EZRA', 'Database Maintenance Service Finished')

	def _get_timestamp(self, date_time):
		return int(time.mktime(date_time.timetuple()))

class TraktMonitor:
	def run(self):
		logger('EZRA', 'TraktMonitor Service Starting')
		trakt_service_string = 'TraktMonitor Service Update %s - %s'
		update_string = 'Next Update in %s minutes...'
		if not kodi_utils.get_property('ezra_traktmonitor_first_run') == 'true':
			clear_trakt_list_contents_data('user_lists')
			kodi_utils.set_property('ezra_traktmonitor_first_run', 'true')
		while not monitor.abortRequested():
			while is_playing() or get_visibility('Container().isUpdating') or get_property('ezra_pause_services') == 'true': monitor.waitForAbort(10)
			if not kodi_utils.get_property('ezra_traktmonitor_first_run') == 'true': monitor.waitForAbort(5)
			value, interval = settings.trakt_sync_interval()
			next_update_string = update_string % value
			status = trakt_sync_activities()
			if status == 'success':
				logger('EZRA', trakt_service_string % ('EZRA TraktMonitor - Success', 'Trakt Update Performed'))
				if settings.trakt_sync_refresh_widgets():
					kodi_utils.widget_refresh()
					logger('EZRA', trakt_service_string % ('EZRA TraktMonitor - Widgets Refresh', 'Setting Activated. Widget Refresh Performed'))
				else: logger('EZRA', trakt_service_string % ('EZRA TraktMonitor - Widgets Refresh', 'Setting Disabled. Skipping Widget Refresh'))
			elif status == 'no account':
				logger('EZRA', trakt_service_string % ('EZRA TraktMonitor - Aborted. No Trakt Account Active', next_update_string))
			elif status == 'failed':
				logger('EZRA', trakt_service_string % ('EZRA TraktMonitor - Failed. Error from Trakt', next_update_string))
			else:# 'not needed'
				logger('EZRA', trakt_service_string % ('EZRA TraktMonitor - Success. No Changes Needed', next_update_string))
			monitor.waitForAbort(interval)
		return logger('EZRA', 'TraktMonitor Service Finished')


import librosa, librosa.display
import matplotlib.pyplot as plt
import glob,os
import numpy as np
from scipy.ndimage.interpolation import shift
import warnings
import cv2 as cv
import pandas as pd

class Data_augmentation:
    def __init__(self,labels,root_path='./data/dataset_aug/'):
        super(Data_augmentation, self).__init__()

        self.root_path = root_path
        self.labels = labels
        self.folders = ['images/time','images/mel']
        self.dir_v1 = './data/dataset_v1/audio/'

        self.cnt = 0

    # Generate aug folders
    def make_aug_folders(self,status=False):
        try:
            if status == True:
                for folder in self.folders:
                    for label in self.labels:
                        os.makedirs(os.path.join(self.root_path,folder,label))
                print("Complete folders generation")
        except:
            print("You already have folders")

    # Data Augmentation methods
    def m_Scaling(self,X,f_range_1=0.5, f_range_2=2.0):
        X = np.expand_dims(X,axis=1)
        scalingFactor = np.random.uniform(low=f_range_1, high=f_range_2, size=(1,X.shape[1]))
        myNoise = np.matmul(np.ones((X.shape[0],1)), scalingFactor)
        scaled_X = np.squeeze(X*myNoise,axis=1)
        return scaled_X

    def m_Jitter(self,X,low=0,high=0.01):
        sigma = np.random.uniform(low, high)
        myNoise = np.random.normal(loc=0, scale=sigma, size=X.shape)
        return X+myNoise

    def m_Shifting(self,X,range_1=-300,range_2=1000):
        shift_inv = np.random.randint(range_1,range_2)
        fill_time_noise = np.random.normal(loc=0, scale=0.003, size=abs(shift_inv))
        shift_X = shift(X, shift_inv, cval=np.nan)
        where_nan = np.isnan(shift_X)
        shift_X[where_nan] = fill_time_noise
        return shift_X

    def data_aug(self,sound_samples, label, n):
        aug_samples = []
        cnt = 0
        for i in range(n):
            if label == 'OH':
                Sc_sound_samples = self.m_Scaling(sound_samples,0.5,2.0)
                J_sound_samples = self.m_Jitter(Sc_sound_samples,0,0.01)
                ch_sound_samples = J_sound_samples
            elif label == 'CH' or label == 'R':
                Sc_sound_samples = self.m_Scaling(sound_samples,0.2,1.5)
                J_sound_samples = self.m_Jitter(Sc_sound_samples,0,0.03)
                ch_sound_samples = J_sound_samples
            elif label == 'rest':
                Sc_sound_samples = self.m_Scaling(sound_samples,0.5,2.0)
                J_sound_samples = self.m_Jitter(Sc_sound_samples,0,0.01)
                Sh_sound_samples = self.m_Shifting(J_sound_samples,-1000,0)
                ch_sound_samples = Sh_sound_samples
            elif label == 'B+CH' or label == 'B' or label == 'B+OH' or label == 'B+R':
                Sc_sound_samples = self.m_Scaling(sound_samples,1.5,2.5)
                J_sound_samples = self.m_Jitter(Sc_sound_samples,0,0.01)
                Sh_sound_samples = self.m_Shifting(J_sound_samples,-300,1000)
                ch_sound_samples = Sh_sound_samples
            else:
                Sc_sound_samples = self.m_Scaling(sound_samples,0.5,2.0)
                J_sound_samples = self.m_Jitter(Sc_sound_samples,0,0.01)
                Sh_sound_samples = self.m_Shifting(J_sound_samples,-300,1000)
                ch_sound_samples = Sh_sound_samples
            aug_samples.append(ch_sound_samples)
        return aug_samples

    def export_data(self,aug_n,label_n):
        label = self.labels[label_n]
        s_dir = os.path.join(self.dir_v1,label)
        for s in os.listdir(s_dir):
            sample_dir = os.path.join(s_dir,s)
            sample, sr = librosa.load(sample_dir)

            aug_samples = self.data_aug(sample, label, aug_n)

            for sample in aug_samples:
                warnings.filterwarnings(action='ignore')
                # time
                plt.figure(figsize=(6.898,4.719))
                plt.plot(sample)
                plt.xlim(0, len(sample))
                plt.ylim(-0.7,0.7)
                plt.axis('off')
                plt.savefig(fname= self.root_path + self.folders[0] + '/'                             + label + '/' + str(self.cnt) + '_aug.jpg', bbox_inches='tight', pad_inches=0)
                plt.close()
                # mel
                plt.figure(figsize=(6.898,4.719))
                f_sample=librosa.feature.melspectrogram(sample)
                librosa.display.specshow(librosa.power_to_db(f_sample), cmap='jet', y_axis='mel')
                plt.clim(-70,20)
                plt.axis('off')
                plt.savefig(fname= self.root_path + self.folders[1] + '/'                             + label + '/' + str(self.cnt) + '_aug.jpg', bbox_inches='tight', pad_inches=0)
                plt.close()

                self.cnt += 1

        print("Complete data augementation")

class Dataset_overview:
    def __init__(self,labels,data_v,root_path='./data'):
        super(Dataset_overview, self).__init__()
        self.root_path = root_path
        self.img_path = 'images/time'

        self.labels = labels
        self.folders = data_v

    def overview(self):
        data_frame = []
        for folder in self.folders:
            data_cnt = []
            for label in self.labels:
                data_files = glob.glob(os.path.join(self.root_path,folder,self.img_path,label,'*'))
                data_cnt.append(len(data_files))
            data_frame.append(data_cnt)
        df = pd.DataFrame(data_frame,index=self.folders, columns=self.labels)
        df = df.transpose()
        return df

    def confirm_size(self):
        error_cnt = 0
        for folder in self.folders:
            for label in self.labels:
                data_files = glob.glob(os.path.join(self.root_path,folder,self.img_path,label,'*'))
                for data_file in data_files:
                    img = cv.imread(data_file)
                    img_size = img.shape
                    if img_size != (256,384,3):
                        error_cnt += 1
        if error_cnt == 0:
            print("All images size is (256,384,3)")
        else:
            print("Number of wrong image size :" + int(error_cnt) )


# encoding: UTF-8
# refer: https://nga.178.com/read.php?tid=11838545
# python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python pyautogui numpy

import re
import pyautogui
import cv2
import numpy as np
import time

global LOGFILE, encoding, preImg
pyautogui.FAILSAFE = False
LOGFILE = "C:\\Program Files (x86)\\Hearthstone\\Logs\\Power.log"
encoding = "UTF-8"
preImg = [cv2.imread(f"./hs/{i}.png") for i in range(3)]

def screenshot(x=376, y=292 + 30, w=530, h=430 - 30):
    img = pyautogui.screenshot(region=[x, y, w, h])  # x,y,w,h
    img = cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
    return img

def imgSave(i=9):
    img = screenshot()
    cv2.imwrite(f"./hs/{i}.png", img)
    cv2.imshow(str(i), img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

def ORB_img_similarity(img1, img2):
    try:
        orb = cv2.ORB_create()
        kp1, des1 = orb.detectAndCompute(img1, None)
        kp2, des2 = orb.detectAndCompute(img2, None)
        bf = cv2.BFMatcher(cv2.NORM_HAMMING)
        matches = bf.knnMatch(des1, trainDescriptors=des2, k=2)
        good = [m for (m, n) in matches if m.distance < 0.75 * n.distance]
        similary = len(good) / len(matches)
        return similary
    except:
        return float(0)

def compare(img):
    result = 9
    p = 0.4
    for i in range(3):
        res = float(ORB_img_similarity(preImg[i][277:380, 0:530], img[277:380, 0:530]))
        if res > p:
            p = res
            result = i
    print(f"res = {result}, p = {p}")
    return result

def movClick(x, y, t=0.7):
    pyautogui.moveTo(x, y, duration=t, tween=pyautogui.easeInOutQuad)
    pyautogui.click()

def gaming():
    flagGameover = 0
    flagTimesleep = 0
    with open(LOGFILE, 'r', encoding=encoding) as log:
        while log.readline():
            pass
        while True:
            line = log.readline()
            if line:
                flagTimesleep = 0
                if re.findall(r"tag=STEP value=MAIN_ACTION", line):
                    time.sleep(10)
                    logLine = log.readline()
                    while logLine:
                        if re.findall(r"tag=STEP value=FINAL_GAMEOVER", logLine):
                            flagGameover += 1
                        logLine = log.readline()
                    movClick(572, 501)
                    movClick(632, 436)
                    movClick(861, 506)
                    time.sleep(1)
                    movClick(861, 506)
                elif re.findall(r"tag=STEP value=FINAL_GAMEOVER", line):
                    time.sleep(3)
                    logLine = log.readline()
                    while logLine:
                        if re.findall(r"tag=STEP value=FINAL_GAMEOVER", logLine):
                            flagGameover += 1
                        logLine = log.readline()
                    flagGameover += 1
            else:
                time.sleep(1)
                flagTimesleep += 1
                if flagTimesleep % 30 == 0:
                    print(flagTimesleep/30)
            if flagTimesleep > 90:
                flagTimesleep = 0
                movClick(572, 501)
                movClick(632, 436)
                time.sleep(1)
                movClick(632, 436)
                movClick(861, 506)
                time.sleep(1)
                movClick(563, 514)
                movClick(708, 641)
                movClick(563, 514)
                movClick(708, 641)
                time.sleep(5)
                movClick(586, 512)
                movClick(511, 522)
                movClick(481, 514)
                movClick(455, 521)
                movClick(828, 650)
                movClick(828, 650, 0.1)
                time.sleep(3)
                movClick(861, 506)
                movClick(828, 650)
            if flagGameover >= 1:
                print("game over")
                return

if __name__ == '__main__':
    print(time.time())
    while True:
        time0= time.time()
        img = screenshot()
        res = compare(img)
        movClick(466, 460)
        time.sleep(1)
        movClick(828, 650)
        time.sleep(3)
        movClick(443, 453)
        movClick(828, 650)
        movClick(597, 553)
        movClick(828, 650)
        time.sleep(2)
        for _ in range(3):
            movClick(586, 512)
            movClick(511, 522)
            movClick(481, 514)
            movClick(455, 521)
            movClick(828, 650, 0.1)
            movClick(828, 650, 0.1)
            gaming()
            time.sleep(13)  # 1
            movClick(828, 650, 0.1)
            time.sleep(3)
            movClick(828, 650, 0.1)
            time.sleep(3)
            movClick(563, 514)
            movClick(708, 641)
            movClick(563, 514)
            movClick(708, 641)
            time.sleep(5)
        movClick(828, 650)
        movClick(565, 400)
        movClick(828, 650)
        gaming()
        time.sleep(13)
        movClick(828, 650, 0.1)
        time.sleep(3)
        movClick(828, 650, 0.1)
        time.sleep(13)
        movClick(828, 650, 0.1)
        movClick(510, 602, 0.2)
        movClick(650, 450, 0.2)
        movClick(782, 627, 0.2)
        movClick(510, 602, 0.2)
        movClick(650, 450, 0.2)
        movClick(782, 627, 0.2)
        time.sleep(2)
        movClick(654, 549)
        time.sleep(8)
        movClick(631, 647)
        time.sleep(7)
        print(str((time.time()-time0)/60.0)+"\tdone")


#!/usr/bin/env python3
import pyautogui
from socket import *
import time
import json

[screenWidth, screenHeight] = pyautogui.size()
mouseVX = -1

while True:
    # 1. configure socket dest.
    serverName = '127.0.0.1'
    serverPort = 12345
    clientSocket = socket(AF_INET, SOCK_STREAM)
    try:
        clientSocket.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1)
        clientSocket.connect((serverName, serverPort))
        print(clientSocket.getsockopt(SOL_SOCKET, SO_KEEPALIVE))
    except ConnectionRefusedError as e:
        print("Server refused connection. retrying")
        time.sleep(1)
        continue

    # 2. communication routine
    while (1):
        [mouseX, mouseY] = pyautogui.position()
        if mouseX < (screenWidth - 2) and mouseVX > 0:
            mouseVX -= 1
            data = json.dumps({"x": mouseVX, "y": mouseY})
            try:
                clientSocket.send(bytes(data, "utf8"))
            except ConnectionResetError as e:
                print("Server connection closed")
                break
            pyautogui.moveTo(screenWidth - 2, mouseY)
        if mouseX > (screenWidth - 2):
            mouseVX += 1
            data = json.dumps({"x": mouseVX, "y": mouseY})
            try:
                clientSocket.send(bytes(data, "utf8"))
            except ConnectionResetError as e:
                print("Server connection closed")
                break
            pyautogui.moveTo(screenWidth - 2, mouseY)

    # 3. proper closure
    clientSocket.shutdown(SHUT_RDWR)
    clientSocket.close()


from typing import List

class Solution:
    def trap(self, height: List[int]) -> int:
        stack = []
        volume = 0

        for i, h in enumerate(height):
            while stack and height[stack[-1]] < h:
                top = stack.pop()

                if not stack:
                    break

                dist = i - stack[-1] - 1
                volume += (min(height[stack[-1]], h) - height[top]) * dist

            stack.append(i)

        return volume


import random,copy
import numpy as np
import tensorflow as tf
import json

class DuelingDQN:
    def __init__(
            self,
            n_features,
            n_actions,
            modelname,
            sess,
            learning_rate=1e-4,
            reward_decay=0.97,
            e_greedy=0.9,
            replace_target_iter=300,
            memory_size=3200,
            batch_size=128,
            e_greedy_increment=None,
            dueling=True,
            output_graph=False,
            BaseScore=0,
    ):
        self.n_actions = n_actions
        self.n_features = n_features
        self.modelname = modelname
        self.gamma = reward_decay
        self.epsilon_max = e_greedy
        self.replace_target_iter = replace_target_iter
        self.memory_size = memory_size
        self.batch_size = batch_size
        self.epsilon_increment = e_greedy_increment
        self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max
        self.BaseScore = BaseScore

        self.n_l1 = 512
        self.n_l2 = 512

        self.dueling = dueling
        self.lr = learning_rate

        # total learning step
        self.learn_step_counter = 0

        # initialize zero memory [s, a, r, s_]
        self.memory = []#Memory(capacity=memory_size)

        # consist of [target_net, evaluate_net]
        with tf.variable_scope(self.modelname):
            self._build_net()

        self.sess = sess

        if output_graph:
            # $ tensorboard --logdir=logs
            # tf.train.SummaryWriter soon be deprecated, use following
            tf.summary.FileWriter("logs/", self.sess.graph)

        self.cost_his = []

    #修改
    def _build_net(self):
        def build_layers(s, c_names, w_initializer, b_initializer):
            with tf.variable_scope('l1'):
                w1 = tf.get_variable('w1', [self.n_features, self.n_l1], initializer=w_initializer, collections=c_names)
                b1 = tf.get_variable('b1', [1, self.n_l1], initializer=b_initializer, collections=c_names)
                l1 = tf.nn.relu(tf.matmul(s, w1) + b1)

            with tf.variable_scope('l2'):
                w2 = tf.get_variable('w2', [self.n_l1, self.n_l2], initializer=w_initializer, collections=c_names)
                b2 = tf.get_variable('b2', [1, self.n_l2], initializer=b_initializer, collections=c_names)
                l2 = tf.nn.relu(tf.matmul(l1, w2) + b2)

            if self.dueling:
                # Dueling DQN
                with tf.variable_scope('Value'):
                    w3 = tf.get_variable('w3', [self.n_l2, 1], initializer=w_initializer, collections=c_names)
                    b3 = tf.get_variable('b3', [1, 1], initializer=b_initializer, collections=c_names)
                    self.V = tf.matmul(l2, w3) + b3

                with tf.variable_scope('Advantage'):
                    w3 = tf.get_variable('w3', [self.n_l2, self.n_actions], initializer=w_initializer, collections=c_names)
                    b3 = tf.get_variable('b3', [1, self.n_actions], initializer=b_initializer, collections=c_names)
                    self.A = tf.matmul(l2, w3) + b3
                with tf.variable_scope('Q'):
                    out = self.V + (self.A - tf.reduce_mean(self.A, axis=1, keepdims=True))     # Q = V(s) + A(s,a)

            else:
                with tf.variable_scope('l3'):
                    w3 = tf.get_variable('w3', [self.n_l2, self.n_actions], initializer=w_initializer, collections=c_names)
                    b3 = tf.get_variable('b3', [1, self.n_actions], initializer=b_initializer, collections=c_names)
                    out = tf.matmul(l2, w3) + b3

            return out

        # ------------------ all inputs ------------------------
        self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s')  # input State
        self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_')  # input Next State
        self.r = tf.placeholder(tf.float32, [None, ], name='r')  # input Reward
        self.a = tf.placeholder(tf.int32, [None, ], name='a')  # input Action

        self.action_possible = tf.placeholder(tf.float32, [None, self.n_actions], name='action_possible')  # input Action
        self.ISWeights = tf.placeholder(tf.float32, [None,], name='IS_weights')

        # ------------------ build evaluate_net ------------------
        with tf.variable_scope('eval_net'):
            # c_names(collections_names) are the collections to store variables
            c_names, w_initializer, b_initializer = \
                ['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], \
                tf.random_normal_initializer(0., 0.1), tf.constant_initializer(0.1)  # config of layers
            self.q_eval = build_layers(self.s, c_names, w_initializer, b_initializer)

        # ------------------ build target_net ------------------
        with tf.variable_scope('target_net'):
            # c_names(collections_names) are the collections to store variables
            c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]
            q_next = build_layers(self.s_, c_names, w_initializer, b_initializer)
            self.q_next = q_next + self.BaseScore
            #print(self.q_next.shape)

        with tf.variable_scope('q_target'):
            q_next_max = tf.reduce_max(self.q_next*self.action_possible, axis=1, name='Qmax_s_')
            #print(q_next_max.shape)
            q_target = self.r + self.gamma * (q_next_max - self.BaseScore)    # shape=(None, )
            self.q_target = tf.stop_gradient(q_target)
            #print(self.q_target.shape)

        with tf.variable_scope('q_eval'):
            a_one_hot = tf.one_hot(self.a, depth=self.n_actions, dtype=tf.float32)
            self.q_eval_wrt_a = tf.reduce_sum(self.q_eval * a_one_hot, axis=1)     # shape=(None, )
            #print(self.q_eval_wrt_a.shape)

        with tf.variable_scope('loss'):
            self.abs_errors = tf.abs(self.q_target - self.q_eval_wrt_a)    # for updating Sumtree
            #print(self.abs_errors.shape)
            self.loss = tf.reduce_mean(self.ISWeights * tf.squared_difference(self.q_target, self.q_eval_wrt_a, name='TD_error'))
        with tf.variable_scope('train'):
            self._train_op = tf.train.AdamOptimizer(self.lr).minimize(self.loss)

    def store_transition(self, s, a, r, s_, actions_one_hot,player=None,initr=None):
        #print((s,[a, r], s_, actions_one_hot))
        if player is None and initr is None:
            transition = np.hstack((s, [a, r], s_, actions_one_hot))
        else:
            transition = np.hstack((s, [a, r], [player,initr], s_, actions_one_hot))
        self.memory.store(transition)

    def get_action(self, netinput, actions_one_hot, norand=False, addNonZero=0):
        output = self.sess.run(self.q_eval,feed_dict={self.s:[netinput]})
        output = output.flatten() + self.BaseScore + addNonZero
        output[0] -= addNonZero
        legalOut = np.multiply(output, actions_one_hot)
        #print(legalOut)
        minval = np.min(legalOut)
        if minval < 0:
            legalOut -= (minval-1)
            legalOut = np.multiply(legalOut,actions_one_hot)
        #print(legalOut)
        allidx = [i for i,v in enumerate(actions_one_hot) if v > 1e-6]
        #print(allidx)
        randf = random.random()
        #print(randf)
        #print(self.epsilon)
        outidx = -1
        if norand or randf < self.epsilon:
            outidx = np.argmax(legalOut)
        else:
            outidx = random.choice(allidx)
        #print(outidx)
        return outidx, legalOut[outidx]

    def _replace_target_params(self):
        t_params = tf.get_collection('target_net_params')
        e_params = tf.get_collection('eval_net_params')
        self.sess.run([tf.assign(t, e) for t, e in zip(t_params, e_params)])

    def learn(self,iskickers=False):
        if self.learn_step_counter % self.replace_target_iter == 0:
            self._replace_target_params()
            print('\ntarget_params_replaced\n')

        tree_idx, batch_memory, ISWeights = self.memory.sample(self.batch_size)

        ins = batch_memory[:, :self.n_features]
        ina = batch_memory[:, self.n_features]
        inr = batch_memory[:, self.n_features+1]
        if not iskickers:
            ins_ = batch_memory[:, self.n_features+2:self.n_features+2+self.n_features]
            inaction_possible = batch_memory[:, self.n_features+2+self.n_features:]
        else:
            #print(inr)
            inr = inr + self.BaseScore*self.gamma
            ins_ = np.zeros((self.batch_size,self.n_features))
            inaction_possible = np.zeros((self.batch_size,self.n_actions))

        _,loss = self.sess.run([self._train_op,self.loss],
                          feed_dict={self.s: ins, self.a: ina, self.r: inr, self.s_: ins_,
                                     self.action_possible: inaction_possible, self.ISWeights: ISWeights})
        abs_errors = self.sess.run(self.abs_errors,
                                   feed_dict={self.s: ins, self.a: ina, self.r: inr, self.s_: ins_,
                                              self.action_possible: inaction_possible})

        #print(abs_errors)
        for i in range(len(tree_idx)):  # update priority
            idx = tree_idx[i]
            self.memory.update(idx, abs_errors[i]/50.0)

        self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max
        self.learn_step_counter += 1

        return loss

    #新增
    def save_model(self,path,ckptname):
        saver = tf.train.Saver()
        saver.save(self.sess, path+ckptname+".ckpt")

    #新增
    def load_model(self,path,ckptname):
        saver = tf.train.Saver()
        try:
            saver.restore(self.sess, path+ckptname+".ckpt")
        except Exception as err:
            print(err)
            print("Fail to restore!")

    def plot_cost(self):
        import matplotlib.pyplot as plt
        plt.plot(np.arange(len(self.cost_his)), self.cost_his)
        plt.ylabel('Cost')
        plt.xlabel('training steps')
        plt.show()

class PlayModel(DuelingDQN):

    def __init__(self,modelname,sess,player):
        self.player = player
        super(PlayModel, self).__init__(105+364,364,modelname,sess,memory_size=3200,batch_size=128,e_greedy_increment=0.9/2e5)
        self.episodeTemp = []

    @staticmethod
    def cards2NumArray(cards):
        point = Hand.getCardPoint(cards)
        res = np.zeros(15)
        for p in point:
            res[p] += 1
        return res

    @staticmethod
    def ch2input(playerID,myCards,publicCards,history,lastPlay,lastLastPlay,actions_one_hot):
        net_input = []
        net_input.append(PlayModel.cards2NumArray(myCards))
        net_input.append(PlayModel.cards2NumArray(publicCards))
        player = playerID
        for _ in range(3):
            tmphis = []
            for his in history[player]:
                tmphis.extend(his)
            net_input.append(PlayModel.cards2NumArray(tmphis))
            player = (player - 1) % 3
        net_input.append(PlayModel.cards2NumArray(lastPlay))
        net_input.append(PlayModel.cards2NumArray(lastLastPlay))
        net_input = np.array(net_input).flatten()
        net_input = np.hstack([net_input,actions_one_hot])
        return net_input + 1

    @staticmethod
    def getDisFromChain(chain,baseChain,maxNum):
        chaindis = chain - baseChain
        num = maxNum
        res = 0
        for _ in range(chaindis):
            res += num
            num -= 1
        return res

    # change cardPoints to the index of network output
    @staticmethod
    def cardPs2idx(cardPoints):
        if cardPoints and isinstance(cardPoints[0],list):
            tmphand = cardPoints[1:]
            lenh = len(tmphand)
            kickers = []
            if lenh % 3 == 0:
                kickers = cardPoints[0][:lenh//3]
            elif lenh % 4 == 0:
                kickers = cardPoints[0][:lenh//2]
            for k in kickers:
                tmphand.extend(k)
            cardPoints = tmphand
        hand = Hand([],cardPoints)
        lencp = len(cardPoints)
        if hand.type == "None" or hand.type == "Pass":
            return 0
        elif hand.type == "Solo":
            if hand.chain == 1:
                return 1 + hand.primal
            else:
                idx = 108
                idx += PlayModel.getDisFromChain(hand.chain,5,8)
                idx += hand.primal
                return idx
        elif hand.type == "Pair":
            if hand.chain == 1:
                return 16 + hand.primal
            else:
                idx = 144
                idx += PlayModel.getDisFromChain(hand.chain,3,10)
                idx += hand.primal
                return idx
        elif hand.type == "Trio":
            if hand.chain == 1:
                return 29 + hand.primal + hand.kickerNum * 13
            else:
                idx = 196
                idx += PlayModel.getDisFromChain(hand.chain,2,11)
                return idx + hand.primal + hand.kickerNum * 45
        elif hand.type == "Four":
            if hand.chain == 1:
                return 68 + hand.primal + (hand.kickerNum-1) * 13
            else:
                return 331 + hand.primal + hand.kickerNum * 11
        elif hand.type == "Bomb":
            return 94 + hand.primal
        elif hand.type == "Rocket":
            return 107
        else:
            return 0

    # change all possible hands to one hot tensor
    def hand2one_hot(self,allhands):
        res = np.zeros(self.n_actions)
        for hand in allhands:
            idx = PlayModel.cardPs2idx(hand)
            res[idx] = 1
        return res

    @staticmethod
    def getChainFromDis(dis,baseChain,maxNum):
        chain = baseChain
        num = maxNum
        while dis >= num:
            dis -= num
            num -= 1
            chain += 1
        return chain, dis

    # change index of one hot output to cardPoints
    # if possible hand has kickers, the first element will be dict
    @staticmethod
    def idx2CardPs(idx):
        res = []
        if idx == 0: # Pass
            res = []
        elif idx <= 15: # Solo
            res = [idx - 1]
        elif idx <= 28: # Pair
            res = [idx - 16]*2
        elif idx <= 41: # Trio without kickers
            res = [idx - 29]*3
        elif idx <= 54: # Trio with one kicker
            res.append({"kickerNum":1,"chain":1,"type":"Trio"})
            res.extend([idx-42]*3)
        elif idx <= 67: # Trio with two kickers
            res.append({"kickerNum":2,"chain":1,"type":"Trio"})
            res.extend([idx-55]*3)
        elif idx <= 80: # Four with one kicker
            res.append({"kickerNum":1,"chain":1,"type":"Four"})
            res.extend([idx-68]*4)
        elif idx <= 93: # Four with two kickers
            res.append({"kickerNum":2,"chain":1,"type":"Four"})
            res.extend([idx-81]*4)
        elif idx <= 106: # Bomb
            res = [idx - 94]*4
        elif idx == 107: # Rocket
            res = [13,14]
        elif idx <= 143: # Solo Chain
            chain,primal = PlayModel.getChainFromDis(idx-108,5,8)
            res = list(range(primal,primal+chain))
        elif idx <= 195: # Pair Chain
            chain,primal = PlayModel.getChainFromDis(idx-144,3,10)
            res = list(range(primal,primal+chain)) * 2
        elif idx <= 240: # Airplane without wings
            chain,primal = PlayModel.getChainFromDis(idx-196,2,11)
            res = list(range(primal,primal+chain)) * 3
        elif idx <= 285: # Airplane with small wings
            chain,primal = PlayModel.getChainFromDis(idx-241,2,11)
            res.append({"kickerNum":1,"chain":chain,"type":"Trio"})
            res.extend(list(range(primal,primal+chain)) * 3)
        elif idx <= 330: # Airplane with big wings
            chain,primal = PlayModel.getChainFromDis(idx-286,2,11)
            res.append({"kickerNum":2,"chain":chain,"type":"Trio"})
            res.extend(list(range(primal,primal+chain)) * 3)
        elif idx <= 341: # Shuttle without wings
            chain,primal = PlayModel.getChainFromDis(idx-331,2,11)
            res = list(range(primal,primal+chain)) * 4
        elif idx <= 352: # Shuttle with small wings
            chain,primal = PlayModel.getChainFromDis(idx-342,2,11)
            res.append({"kickerNum":1,"chain":chain,"type":"Four"})
            res.extend(list(range(primal,primal+chain)) * 4)
        elif idx <= 363: # Shuttle with big wings
            chain,primal = PlayModel.getChainFromDis(idx-353,2,11)
            res.append({"kickerNum":2,"chain":chain,"type":"Four"})
            res.extend(list(range(primal,primal+chain)) * 4)
        return res

class KickersModel(DuelingDQN):

    def __init__(self,modelname,sess):
        super(KickersModel, self).__init__(105+364+15,28,modelname,sess,memory_size=320,batch_size=16,e_greedy_increment=0.9/1e5)
        self.episodeTemp = []

    @staticmethod
    def ch2input(playmodel_input, primPoints):
        res = playmodel_input.tolist()
        prims = np.zeros(15)
        for p in primPoints:
            prims[p] += 1
        res.extend(prims)
        return np.array(res)

    def cardPs2idx(self,cardPoints):
        if len(cardPoints) == 1:
            return cardPoints[0]
        elif len(cardPoints) == 2 and cardPoints[0] < 13:
            return cardPoints[0] + 15
        else:
            return -1

    def allkickers2onehot(self,allkicers):
        res = np.zeros(self.n_actions)
        for k in allkicers:
            idx = self.cardPs2idx(k)
            res[idx] = 1
        return res

    def idx2CardPs(self,idx):
        if idx < 15:
            return [idx]
        else:
            return [idx-15]*2

class FTLSimulator:

    # Simulation Functions
    # Initialization
    def __init__(self, nowPlayer, nowTurn, publicCard):
        # Format Descriptions
        self.nowPlayer = 0 # player 0: Landlord, 1: Farmer A(Jia), 2: Farmer B(Yi)
        self.cardCnt = [0, 0, 0] # the number of card every player now holds
        self.myCards = [] # What cards do I have NOW?
        self.publicCard = [] # 3 public cards owned by the landlord when dealing
        self.nowTurn = -1 # the present gaming turn (3 plays (including pass) each turn), -1 for not played yet
        self.history = [[], [], []] # history[p][k]: the card played by player p in the k-th turn

        # Operation Accessories
        self.isCardPlayed = [0] * 54 # isCardPlayed[i]: is card serial i played by any player
        self.cardsToFollow = [] # which hand shall I follow now ? empty for my turn to choose hand type

        self.nowPlayer = nowPlayer
        self.cardCnt = [20, 17, 17]
        self.nowTurn = nowTurn
        self.publicCard = publicCard
        self.lastPlay = []
        self.lastLastPlay = []
        # If I'm the landlord, public card is already included in my dealing

    # Serve cards
    def deal(self, cards):
        self.myCards.extend(cards)

    # Play cards, record history by sequential order
    def play(self, player, cards):
        self.cardCnt[player] -= len(cards)
        for card in cards:
            self.isCardPlayed[card] = 0
        # It's me playing
        if player == self.nowPlayer:
            for card in cards: # play my cards
                self.myCards.remove(card)
        # Record this play
        self.history[player].append(cards)
        self.lastLastPlay = self.lastPlay
        self.lastPlay = cards

    def setCardsToFollow(self, cards):
        self.cardsToFollow = cards

    # set history directly & retrieve current status
    # no need to set cards to follow any more
    def setHistory(self, historyData):
        lenh = [len(h) for h in historyData]
        for t in range(lenh[0]):
            for player in range(3):
                if t >= lenh[player]:
                    continue
                self.play(player, historyData[player][t])
        self.cardsToFollow = self.lastPlay or self.lastLastPlay
        # after this, self.history will be identical to historyData

class Hand:
    # point:       0 1 2 3 4 5 6 7  8 9 10 11 12 13 14
    # actual card: 3 4 5 6 7 8 9 10 J Q K  A  2  Jb Jr
    @staticmethod
    def getCardPoint(card):
        if isinstance(card, list): # a card list
            return [Hand.getCardPoint(c) for c in card]
        # a single card
        if card == 52:
            return 13
        elif card == 53:
            return 14
        else:
            return card//4

    # distinguish cards as hand patterns
    def __init__(self, cards, cardPoints=None):
        if isinstance(cards, Hand): # Copy instance
            self.type = cards.type
            self.primal = cards.primal
            self.kickerNum = cards.kickerNum
            self.chain = cards.chain
            return

        # Assume that cards has been sorted:
        cards.sort()
        # And, is a LEGAL type
        #self.cards = cards
        self.type = "None"
        self.primal = -1 # the basic card type, used for judging greater hand
        self.kickerNum = 0 # cards added to the basic type, 0: None, 1: Solo, 2: Pair
        # kicker element format: (point, type), type = 1 or 2
        self.chain = 1 # consecutive count. e.g. trio + solo && chain == 3 : Airplane with wings

        if cardPoints is not None:
            point = cardPoints
        else:
            point = Hand.getCardPoint(cards) # the point representing card points
        point.sort()

        if len(point) == 0: # passed
            self.type = "Pass"
        elif len(point) == 1: # Solo
            self.type = "Solo"
            self.primal = point[0]
        elif len(point) == 2: # Pair or Rocket
            if point[0] == 13: # Rocket
                self.type = "Rocket"
            elif point[0] == point[1]: # Pair
                self.type = "Pair"
                self.primal = point[0]
        else: # Above, types can be regarded as kickers
            pointU = list(set(point)) # delete duplicate
            pointU.sort()
            pointUCnt = [point.count(p) for p in pointU] # count the number of each point
            pattern = list(set(pointUCnt)) # get the pattern of point
            pattern.sort()
            #print(pattern)
            # distinguish the pattern
            if pattern == [1]: # Solo chain
                self.type = "Solo"
                self.primal = pointU[0]
                self.chain = len(pointU)
            elif pattern == [2]: # Pair chain
                self.type = "Pair"
                self.primal = pointU[0]
                self.chain = len(pointU)
            elif pattern == [3]: # Trio, including airplane
                self.type = "Trio"
                self.primal = min(pointU)
                self.chain = len(pointU)
            elif len(pointU) % 2 == 0 and (pattern == [1, 3] or pattern == [2, 3]): # Trio + Solo/Pair, including airplane
                self.type = "Trio"
                self.primal = min([c for i, c in enumerate(pointU) if pointUCnt[i] == 3])
                self.chain = len(pointU) // 2
                self.kickerNum = pattern[0]
            elif pattern == [4]: # Bomb or Four chain (Shuttle)
                if len(pointU) == 1: # Only 1 point: Bomb
                    self.type = "Bomb"
                    self.primal = pointU[0]
                else: # Four chain
                    self.type = "Four"
                    self.primal = min(pointU)
                    self.chain = len(pointU)
            elif (pattern == [1, 4] or pattern == [2, 4]) and len(pointU) % 3 == 0: # Four + Dual Solo/Pair, including shuttle
                # originally, error when cards = [0,1,2,3,4,5]
                self.type = "Four"
                self.primal = min([c for i, c in enumerate(pointU) if pointUCnt[i] == 4])
                self.chain = len(pointU) // 3
                self.kickerNum = pattern[0]

    # get report string
    def report(self):
        return "%s From %d Len = %d KickerNum = %d" % (self.type, self.primal, self.chain, self.kickerNum)

    # compare two hands, is this pattern able to follow the other one : T / F
    def isAbleToFollow(self, other):
        if self.type == "Pass" and other.type == "Pass": # Must follow sth.
            return False
        if self.type == "Pass" or other.type == "Pass": # always can follow
            return True
        if other.type == "Rocket": # Nothing can follow
            return False
        if self.type == "Rocket": # surpasses every hand
            return True
        if self.type == "Bomb": # dispose bomb separately
            if other.type == "Bomb":
                return self.primal > other.primal
            return True # surpasses other hands
        if self.type != other.type: # mixed patterns
            return False
        return self.chain == other.chain and self.primal > other.primal

    def getHandScore(self):
        score = 0
        if self.type == "Pass":
            score = 0
        elif self.type == "Solo" and self.chain == 1:
            score = 1
        elif self.type == "Pair" and self.chain == 1:
            score = 2 #+ 1
        elif self.type == "Trio" and self.chain == 1:
            score = 4 #+ 2
        elif self.type == "Solo" and self.chain >= 5:
            score = 6 #+ self.chain
        elif self.type == "Pair" and self.chain >= 3:
            score = 6 #+ self.chain * 2
        elif self.type == "Trio" and self.chain >= 2:
            score = 8 #+ self.chain * 3
        elif self.type == "Four" and self.chain == 1:
            score = 8 #+ self.chain * 4
        elif self.type == "Bomb":
            score = 10 #+ 10
        elif self.type == "Four" and self.chain == 2:
            score = 10 #+ self.chain * 4
        elif self.type == "Rocket":
            score = 16 #+ 16
        elif self.type == "Four" and self.chain > 2:
            score = 20 #+ self.chain * 4
        return score * 2 #/ 100.0

class CardInterpreter:
    # get the written name of the card
    @staticmethod
    def getCardName(cid):
        if isinstance(cid, list): # a card list
            return str([CardInterpreter.getCardName(c) for c in cid])
        if cid == 52:
            return "Black Joker"
        elif cid == 53:
            return "Red Joker"
        else:
            return ("Heart","Diamond","Spade","Club")[cid%4] + " " + \
                ("10" if cid//4 == 7 else "3456789_JQKA2"[cid//4])

    # parse the written name into id, for debug only
    @staticmethod
    def getCardID(cardName):
        cardName.lower()
        cardName.replace(" ","")
        if cardName == "jb" or cardName == "blackjoker":
            return 52
        if cardName == "jr" or cardName == "redjoker":
            return 53
        lastNum = {"3":0,"4":1,"5":2,"6":3,"7":4,"8":5,"9":6,"0":7,"j":8,"q":9,"k":10,"a":11,"2":12}[cardName[-1]]
        colorNum = {"h":0,"d":1,"s":2,"c":3}[cardName[0]]
        return lastNum * 4 + colorNum

    # get all possible hands in cards, meanwhile get all solos and pairs
    # cards: [card_s], cardsToFollow: the card set to follow
    # assume that cards is already sorted
    # return the point representation, may NOT sorted
    # when the hand has kickers, like "Trio" or "Four", the first element will be kickers
    @staticmethod
    def splitCard(cards, cardsToFollow = []):
        allHands = [[]] # all legal cards in points, originally 'pass' is a legal actions
        # *** colors, kickers, difference between bomb and four are not considered
        lastHand = Hand(cardsToFollow)
        # solos records all solo kickers
        # pairs records all pair kickers
        if lastHand.type == "Rocket": # can't follow
            return allHands

        # deal with Rocket
        if 52 in cards and 53 in cards: # Rocket
            allHands.append([13,14])

        # No Joker from now on, deal with bombs
        point = Hand.getCardPoint(cards) # get the point expressions
        pointU = list(set(point)) # delete duplicate
        pointUCnt = [point.count(p) for p in pointU] # count the number of each point

        # including card 2 (point == 12)
        soloRec = pointU[:] # including joker cards
        pairRec = [p for i, p in enumerate(pointU) if pointUCnt[i] >= 2] # p >= 13: no pair/trio/four
        trioRec = [p for i, p in enumerate(pointU) if pointUCnt[i] >= 3]
        fourRec = [p for i, p in enumerate(pointU) if pointUCnt[i] >= 4] # Bombs or Four + Dual Solo/Pair

        if lastHand.type == "Bomb": # append specific bombs
            allHands.extend([[pr]*4 for pr in fourRec if pr > lastHand.primal])
            return allHands

        allHands.extend([[pr]*4 for pr in fourRec]) # append bombs

        if lastHand.type == "Four": # kickers appended
            # if lastHand is a shuttle, then here all bombs are listed
            for i,c in enumerate(fourRec):
                if lastHand.chain == 1 and c > lastHand.primal:
                    kickers = CardInterpreter.getKickers(cards, lastHand.kickerNum, [c])
                    if len(kickers) >= 2:
                        allHands.append([kickers,c,c,c,c])
            return allHands

        # relative to xxxRec lists
        soloChainCnt = [1]
        pairChainCnt = [1] if pairRec else []
        trioChainCnt = [1] if trioRec else [] # airplane with no wings
        # do not consider four chains (shuttle)

        # card 2 (point 12), joker cards only in chain 1
        for i, p in enumerate(soloRec):
            if i: # start from the second place
                soloChainCnt.append(soloChainCnt[i-1] + 1 if p == soloRec[i-1] + 1 and p < 12 else 1)
        for i, p in enumerate(pairRec):
            if i: # start from the second place
                pairChainCnt.append(pairChainCnt[i-1] + 1 if p == pairRec[i-1] + 1 and p < 12 else 1)
        for i, p in enumerate(trioRec):
            if i: # start from the second place
                trioChainCnt.append(trioChainCnt[i-1] + 1 if p == trioRec[i-1] + 1 and p < 12 else 1)

        # Record Chain
        if lastHand.type == "Solo":
            for i, c in enumerate(soloRec):
                if soloChainCnt[i] >= lastHand.chain and c-lastHand.chain+1 > lastHand.primal: # able to follow
                    allHands.append(list(range(c-lastHand.chain+1, c+1)))
            return allHands
        if lastHand.type == "Pair":
            for i, c in enumerate(pairRec):
                if pairChainCnt[i] >= lastHand.chain and c-lastHand.chain+1 > lastHand.primal: # able to follow
                    allHands.append(list(range(c-lastHand.chain+1, c+1))*2)
            return allHands
        if lastHand.type == "Trio":
            for i, c in enumerate(trioRec):
                if trioChainCnt[i] >= lastHand.chain and c-lastHand.chain+1 > lastHand.primal: # able to follow
                    if lastHand.kickerNum > 0:
                        kickers = CardInterpreter.getKickers(cards, lastHand.kickerNum, list(range(c-lastHand.chain+1, c+1)))
                        if len(kickers) >= lastHand.chain:
                            allHands.append([kickers])
                            allHands[-1].extend(list(range(c-lastHand.chain+1, c+1))*3)
                    else:
                        allHands.append(list(range(c-lastHand.chain+1, c+1))*3)
            return allHands

        # Here, lastHand.type = "Pass". you can play any type you want
        allHands.remove([]) # remove "Pass" actions, you must take out some cards
        for i, c in enumerate(soloRec): # record solo and solo chains (>=5)
            allHands.append([c])
            if soloChainCnt[i] >= 5: # able to play
                for length in range(5, soloChainCnt[i]+1):
                    allHands.append(list(range(c-length+1, c+1)))
        for i, c in enumerate(pairRec): # record pair and pair chains (>=3)
            allHands.append([c]*2)
            if pairChainCnt[i] >= 3: # able to play
                for length in range(3, pairChainCnt[i]+1):
                    allHands.append(list(range(c-length+1, c+1))*2)
        for i, c in enumerate(trioRec): # record trio and trio chains i.e. airplane (>=2)
            allHands.append([c]*3)
            for knum in range(1,3):
                kickers = CardInterpreter.getKickers(cards, knum, [c])
                if len(kickers) > 0:
                    allHands.append([kickers])
                    allHands[-1].extend([c]*3)
            if trioChainCnt[i] >= 2: # able to play
                for length in range(2, trioChainCnt[i]+1):
                    allHands.append(list(range(c-length+1, c+1))*3)
                    for knum in range(1,3):
                        kickers = CardInterpreter.getKickers(cards, knum, list(range(c-length+1, c+1)))
                        if len(kickers) >= length:
                            allHands.append([kickers])
                            allHands[-1].extend(list(range(c-length+1, c+1))*3)
        for i, c in enumerate(fourRec):
            for knum in range(1,3):
                kickers = CardInterpreter.getKickers(cards, knum, [c])
                if len(kickers) >= 2:
                    allHands.append([kickers])
                    allHands[-1].extend([c]*4)

        return allHands

    @staticmethod
    def getKickers(cards, kickerNum, primCards):
        point = Hand.getCardPoint(cards) # get the point expressions
        pointU = list(set(point)) # delete duplicate
        res = []

        # including card 2 (point == 12)
        if kickerNum == 1:
            res = [[p] for p in pointU] # including joker cards
            for p in primCards:
                if [p] in res:
                    res.remove([p])

        if kickerNum == 2:
            pointUCnt = [point.count(p) for p in pointU] # count the number of each point
            res = [[p]*2 for i, p in enumerate(pointU) if pointUCnt[i] >= 2] # p >= 13: no pair/trio/four
            for p in primCards:
                if [p,p] in res:
                    res.remove([p,p])
        return res

    # Given a hand of point set, select out a set of card of this pattern
    @staticmethod
    def selectCardByHand(cards, pointSet):
        cardsNotUsed = cards[:]
        pointNotUsed = Hand.getCardPoint(cards)
        cardsSelected = []
        for p in pointSet:
            index = pointNotUsed.index(p)
            cardsSelected.append(cardsNotUsed[index])
            cardsNotUsed.pop(index)
            pointNotUsed.pop(index)

        return cardsSelected

# Fight The Landlord executor

SoloPairScore = [[60,56,52,48,44,40,36,32,28,24,20,12,4,1,0],
                 [40,38,36,34,30,28,26,24,20,16,12,6,2,0,0]]

# Initialization using the JSON input
class FTLBot:
    def __init__(self, playmodel,kickersmodel, data, dataType = "Judge", norand=False, addHuman=False):
        self.dataType = dataType
        self.playmodel = playmodel
        #self.valuemodel = valuemodel
        self.kickersmodel = kickersmodel
        self.norand = norand
        self.addHuman = addHuman
        if dataType == "JSON": # JSON req
            rawInput = json.loads(data)
            rawRequest = rawInput["requests"]
            initInfo = rawRequest[0]
            initHistory = initInfo["history"]
            myBotID = 2 if len(initHistory[0]) else 1 if len(initHistory[1]) else 0
            myCards = initInfo["own"]
            myCards.sort()
            publicCard = initInfo["publiccard"]
            nowTurn = len(rawInput["responses"])

            # Construct game simulator
            sim = FTLSimulator(myBotID, nowTurn, publicCard)
            sim.deal(myCards)

            # Retrace the history plays
            # first request
            if myBotID == 1:
                sim.play(0, initHistory[1])
            if myBotID == 2:
                sim.play(0, initHistory[0])
                sim.play(1, initHistory[1])
            # following rounds
            roundN = len(rawInput["responses"])
            for rnd in range(roundN):
                otherPlayerHistory = rawRequest[rnd+1]["history"]
                plays = [rawInput["responses"][rnd], otherPlayerHistory[0], otherPlayerHistory[1]]
                for turn in range(3):
                    #print(str((myBotID+turn)%3)+" Plays "+Hand(plays[turn]).report())
                    sim.play((myBotID+turn)%3, plays[turn])

            # check if this is my turn
            lastHistory = rawRequest[roundN]["history"]
            sim.setCardsToFollow(lastHistory[1] or lastHistory[0])
            self.simulator = sim

        elif dataType == "Judge": # data from judgement
            sim = FTLSimulator(data["ID"], data["nowTurn"], data["publicCard"])
            sim.deal(data["deal"])
            sim.setHistory(data["history"])
            self.simulator = sim

    def makeData(self, data,val=0):
        if self.dataType == "JSON":
            return json.dumps({"response": data,"debug":{"val":val}})
        elif self.dataType == "Judge":
            return data

    @staticmethod
    def maxValueKickers(solos,pairs,sknum,pknum):
        initsolos = copy.deepcopy(solos)
        initpairs = copy.deepcopy(pairs)
        initsolos.sort()
        initpairs.sort()
        tmpsknum = copy.deepcopy(sknum)
        tmpsknum.sort(key=lambda x:x[0],reverse=True)
        tmppknum = pknum[:]
        bval = 0
        success = True
        while True:
            tmpsolos = copy.deepcopy(initsolos)
            tmppairs = copy.deepcopy(initpairs)
            #print(tmpsolos)
            #print(tmppairs)
            try:
                for pnum in tmppknum:
                    for _ in range(pnum):
                        tmpc = tmppairs[0]
                        tmppairs.remove(tmpc)
            except Exception as err:
                success = False
                break
            try:
                for snum,cset in tmpsknum:
                    lastc = -1
                    for _ in range(snum):
                        i = 0
                        tmpc = tmpsolos[0][i]
                        while tmpc == lastc or tmpc in cset:
                            i += 1
                            tmpc = tmpsolos[0][i]
                        lastc = tmpc
                        tmpsolos.remove([tmpc])
            except Exception as err:
                if len(initpairs) == 0:
                    success = False
                    break
                tmpp = initpairs[0]
                initsolos.extend([[tmpp[0]]]*2)
                initsolos.sort()
                initpairs.remove(tmpp)
                continue
            val = 1000
            for s in tmpsolos:
                val -= SoloPairScore[0][s[0]]
            for p in tmppairs:
                val -= SoloPairScore[1][p[0]]
            if val > bval:
                bval = val
            else:
                break
        if success:
            return success,bval,initsolos,initpairs
        else:
            return success,10,initsolos,initpairs

    @staticmethod
    def searchHuman(cards,sknum,pknum,bonus=0,selectHand=None):
        possiblePlays = CardInterpreter.splitCard(cards, [])
        solos = []
        pairs = []
        bombs = []
        twos = []
        for p in possiblePlays:
            lenp = len(p)
            if lenp == 1:
                solos.append(p)
            elif lenp == 2:
                if p[0] == 13:
                    bombs.append(p)
                else:
                    pairs.append(p)
            #elif lenp == 4 and not isinstance(p[0],list):
                #bombs.append(p)
            elif selectHand is None and isinstance(p[0],list) and p[1] == 12:
                twos.append(p)
            elif selectHand is None and lenp == 3 and p[0] == 12:
                twos.append(p)
        for p in solos:
            possiblePlays.remove(p)
        for p in pairs:
            possiblePlays.remove(p)
        for p in bombs:
           possiblePlays.remove(p)
        for p in twos:
            possiblePlays.remove(p)
        #print(possiblePlays)
        #print(solos)
        #print(pairs)
        #print(bombs)
        #print(twos)
        #print(bombs)
        if len(possiblePlays) == 0:
            for p in pairs:
                if p[0] == 12 and len(twos) != 0:continue
                solos.remove([p[0]])
            for p in bombs:
                if p[0] == 13: solos.remove([p[1]])
                solos.remove([p[0]])
            success,val,solos,pairs = FTLBot.maxValueKickers(solos,pairs,sknum,pknum)
            '''print(solos)
            print(pairs)
            print(bonus)'''
            val += bonus
            #print(val)
            #print(val)
            return success,val,[],solos,pairs,bombs
        maxsuccess = False
        maxval = 0
        maxlist = []
        maxsolos = []
        maxpairs = []
        maxchoice = []
        maxbombs = []
        for p in possiblePlays:
            if selectHand is not None and p != selectHand:
                continue
            nsknum = copy.deepcopy(sknum)
            npknum = pknum[:]
            nbonus = bonus
            if isinstance(p[0],list):
                tmpc = p[1:]
                lenc = len(tmpc)
                if lenc % 3 == 0:
                    chain = lenc // 3
                else:
                    chain = 2*(lenc // 4)
                if len(p[0][0]) == 1:
                    nsknum.append([chain,list(set(tmpc))])
                else:
                    npknum.append(chain)
            else:
                tmpc = p
            nextcards = cards[:]
            cardsc = CardInterpreter.selectCardByHand(nextcards, tmpc)
            for c in cardsc:
                nextcards.remove(c)
            hand = Hand(cardsc)
            if hand.type == "Bomb" and not isinstance(p[0],list):
                nbonus += 90
            elif hand.type == "Trio" and hand.chain > 1:
                nbonus += hand.chain*hand.chain*10
            elif hand.type == "Four" and hand.chain > 1:
                nbonus += hand.chain*hand.chain*10
            '''print(p)
            print(tmpc)
            print(nextcards)
            print(nsknum)
            print(npknum)
            print(nbonus)'''
            success,tval,tlist,tsolos,tparis,tbombs = FTLBot.searchHuman(nextcards,nsknum,npknum,nbonus)
            if tval > maxval:
                maxsuccess = success
                maxval = tval
                maxchoice = p
                maxlist = tlist
                maxsolos = tsolos
                maxpairs = tparis
                maxbombs = tbombs
        nchoice = len(maxchoice)
        if nchoice == 4 and not isinstance(maxchoice[0],list):
            maxbombs.append(maxchoice)
        elif nchoice == 5 and isinstance(maxchoice[0],list):
            maxbombs.append(maxchoice[1:])
        else:
            maxlist.append(maxchoice)
        return maxsuccess,maxval,maxlist,maxsolos,maxpairs,maxbombs

    def isAddHuman(self):
        if self.addHuman:
            return True

    # Return the decision based on type
    def makeDecision(self):
        sim = self.simulator
        lastHand = Hand(sim.cardsToFollow)
        #print(sim.cardsToFollow)
        possiblePlays = CardInterpreter.splitCard(self.simulator.myCards, lastHand)
        #print(len(possiblePlays))
        if self.addHuman and len(possiblePlays) > 1:
            # Human Policy
            possiblePlays = []
            success,maxval,pPlays,psolos,ppairs,pbombs = self.searchHuman(self.simulator.myCards,[],[])
            if success:
                remains = []
                remains.extend(pPlays)
                remains.extend(psolos)
                remains.extend(ppairs)
                if len(pbombs) >= 1 and len(remains) == 1:
                    pbombs.sort()
                    if lastHand.type != "Pass":
                        possiblePlays.append(pbombs[0])
                    else:
                        possiblePlays.append(remains[0])
                elif lastHand.type == "Pass":
                    possiblePlays = pPlays
                    if not (sim.nowPlayer == 1 and sim.cardCnt[2] <= 2) and possiblePlays:
                        possiblePlays.extend(psolos)
                        possiblePlays.extend(ppairs)
                elif len(sim.cardsToFollow) == 1 or len(sim.cardsToFollow) == 2:
                    if lastHand.type == "Solo":possiblePlays=psolos
                    else:possiblePlays=ppairs
                    ablelist = []
                    for p in possiblePlays:
                        nowHand = Hand([],p)
                        if nowHand.isAbleToFollow(lastHand):
                            ablelist.append(p)
                    possiblePlays = ablelist
                    if possiblePlays:
                        possiblePlays.append([])
                        possiblePlays.extend(pbombs)
            #print("Search Human!!!")
            #print(possiblePlays)
        if possiblePlays == []:
            possiblePlays = CardInterpreter.splitCard(self.simulator.myCards, lastHand)
        #print(possiblePlays)

        addNonZero = 0
        if self.addHuman:
            if sim.nowPlayer == 0 and (sim.cardCnt[1] <= 2 or sim.cardCnt[2] <= 2):
                addNonZero += 50
            elif sim.nowPlayer == 1 and sim.cardCnt[0] <= 2 and sim.lastPlay:
                addNonZero += 50
            elif sim.nowPlayer == 2 and sim.cardCnt[0] <= 2 and sim.lastPlay == []:
                addNonZero += 50
            if sim.nowPlayer != 0 and lastHand.type == "Bomb":
                if (sim.nowPlayer == 1 and sim.cardsToFollow == sim.lastLastPlay) or (sim.nowPlayer == 2 and sim.cardsToFollow == sim.lastPlay):
                    possiblePlays = [[]]
        #print(addNonZero)

        if not len(possiblePlays):
            return self.makeData([])

        one_hot_t = self.playmodel.hand2one_hot(possiblePlays)
        ori_one_hot_t = self.playmodel.hand2one_hot(CardInterpreter.splitCard(self.simulator.myCards, lastHand))
        net_input = self.playmodel.ch2input(sim.nowPlayer,sim.myCards,sim.publicCard,sim.history,sim.lastPlay,sim.lastLastPlay,ori_one_hot_t)
        actidx,val = self.playmodel.get_action(net_input, one_hot_t, self.norand, addNonZero)
        choice = self.playmodel.idx2CardPs(actidx)

        # Add kickers, if first element is dict, the choice must has some kickers
        # get kickers from kickers model
        if choice and isinstance(choice[0],dict):
            tmphand = choice[1:]
            allkickers = CardInterpreter.getKickers(sim.myCards, choice[0]["kickerNum"], list(set(tmphand)))
            num = choice[0]['chain']
            if choice[0]["type"] == "Four":
                num *= 2
            kickers = []
            if self.addHuman:
                tmpchoice = choice[:]
                tmpchoice[0] = allkickers
                success,maxval,pPlays,psolos,ppairs,_ = self.searchHuman(sim.myCards,[],[],0,tmpchoice)
                if success:
                    if choice[0]["kickerNum"] == 1:
                        allkickers = psolos
                    else:
                        allkickers = ppairs
                    pointU = list(set(tmphand))
                    for p in pointU:
                        if [p] in allkickers:
                            allkickers.remove([p])
                        if [p]*2 in allkickers:
                            allkickers.remove([p]*2)
                    for _ in range(num):
                        kickers.append(allkickers[0])
                        allkickers.remove(allkickers[0])
                    #print("Kickers Human")
            kickers_input = self.kickersmodel.ch2input(net_input,tmphand)
            if kickers == []:
                kickers_onehot = self.kickersmodel.allkickers2onehot(allkickers)
                for _ in range(num):
                    actidx,val = self.kickersmodel.get_action(kickers_input,kickers_onehot,self.norand)
                    kickers.append(self.kickersmodel.idx2CardPs(actidx))
                    kidx = self.kickersmodel.cardPs2idx(kickers[-1])
                    kickers_onehot[kidx] = 0
            for k in kickers:
                tmphand.extend(k)
            choice = tmphand
        cardChoice = CardInterpreter.selectCardByHand(self.simulator.myCards, choice)

        return self.makeData(cardChoice,val)

if __name__ == "__main__":
    config = tf.ConfigProto()
    config.graph_options.optimizer_options.global_jit_level = tf.OptimizerOptions.ON_1
    sess = tf.InteractiveSession(config=config)
    playmodel = [PlayModel("play"+str(i),sess,i) for i in range(3)]
    kickersmodel = KickersModel("kick",sess)
    tf.global_variables_initializer().run()
    kickersmodel.load_model("data/FTL/","DQN")
    player = FTLBot(playmodel[0], kickersmodel, input(), "JSON", True, True)
    id = player.simulator.nowPlayer
    #print(id)
    player.playmodel = playmodel[id]
    res = player.makeDecision()
    print(res)