File indexing completed on 2024-04-14 03:59:10
0001 # -*- coding: utf-8 -*- 0002 0003 """ 0004 Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de> 0005 0006 SPDX-License-Identifier: GPL-2.0 0007 0008 """ 0009 0010 import csv 0011 import subprocess 0012 import datetime 0013 0014 from enum import IntEnum 0015 from functools import total_ordering 0016 0017 from common import Options, StrMixin 0018 from player import Player, Players 0019 0020 class CsvWriter: 0021 """how we want it""" 0022 def __init__(self, filename, mode='w'): 0023 self.outfile = open(filename, mode) 0024 self.__writer = csv.writer(self.outfile, delimiter=Csv.delimiter) 0025 0026 def writerow(self, row): 0027 """write one row""" 0028 self.__writer.writerow([str(cell) for cell in row]) 0029 0030 def __del__(self): 0031 """clean up""" 0032 self.outfile.close() 0033 0034 0035 class Csv: 0036 """how we want it""" 0037 0038 delimiter = ';' 0039 0040 @staticmethod 0041 def reader(filename): 0042 """return a generator for decoded strings""" 0043 return csv.reader(open(filename, 'r', encoding='utf-8'), delimiter=Csv.delimiter) 0044 0045 @total_ordering 0046 class CsvRow(StrMixin): 0047 """represent a row in kajongg.csv""" 0048 0049 fields = IntEnum('Field', 'RULESET AI COMMIT PY_VERSION GAME TAGS PLAYERS', start=0) 0050 0051 commitDates = dict() 0052 0053 def __init__(self, row): 0054 self.row = row 0055 self.ruleset, self.aiVariant, self.commit, self.py_version, self.game, self.tags = row[:6] 0056 self.winner = None 0057 rest = row[6:] 0058 players = [] 0059 while rest: 0060 name, balance, wonCount, winner = rest[:4] 0061 player = Player(None, name) 0062 player.balance = balance 0063 player.wonCount = wonCount 0064 players.append(player) 0065 if winner: 0066 self.winner = player 0067 rest = rest[4:] 0068 self.players = Players(players) 0069 0070 @property 0071 def commitDate(self): 0072 """return datetime""" 0073 if self.commit not in self.commitDates: 0074 try: 0075 self.commitDates[self.commit] = datetime.datetime.fromtimestamp( 0076 int(subprocess.check_output( 0077 'git show -s --format=%ct {}'.format(self.commit).split(), stderr=subprocess.DEVNULL))) 0078 except subprocess.CalledProcessError: 0079 self.commitDates[self.commit] = datetime.datetime.fromtimestamp(0) 0080 return self.commitDates[self.commit] 0081 0082 @property 0083 def game(self): 0084 """return the game""" 0085 return self.row[self.fields.GAME] 0086 0087 @game.setter 0088 def game(self, value): 0089 self.row[self.fields.GAME] = value 0090 0091 @property 0092 def ruleset(self): 0093 """return the ruleset""" 0094 return self.row[self.fields.RULESET] 0095 0096 @ruleset.setter 0097 def ruleset(self, value): 0098 self.row[self.fields.RULESET] = value 0099 0100 @property 0101 def aiVariant(self): 0102 """return the AI used""" 0103 return self.row[self.fields.AI] 0104 0105 @aiVariant.setter 0106 def aiVariant(self, value): 0107 self.row[self.fields.AI] = value 0108 0109 @property 0110 def commit(self): 0111 """return the git commit""" 0112 return self.row[self.fields.COMMIT] 0113 0114 @commit.setter 0115 def commit(self, value): 0116 self.row[self.fields.COMMIT] = value 0117 0118 @property 0119 def py_version(self): 0120 """return the python version""" 0121 return self.row[self.fields.PY_VERSION] 0122 0123 @py_version.setter 0124 def py_version(self, value): 0125 self.row[self.fields.PY_VERSION] = value 0126 0127 @property 0128 def tags(self): 0129 """return the tags""" 0130 return self.row[self.fields.TAGS] 0131 0132 @tags.setter 0133 def tags(self, value): 0134 self.row[self.fields.TAGS] = value 0135 0136 def result(self): 0137 """return a tuple with the fields holding the result""" 0138 return tuple(self.row[self.fields.PLAYERS:]) 0139 0140 def write(self): 0141 """write to Options.csv""" 0142 writer = CsvWriter(Options.csv, mode='a') 0143 writer.writerow(self.row) 0144 del writer 0145 0146 def __eq__(self, other): 0147 return self.row == other.row 0148 0149 def sortkey(self): 0150 """return string for comparisons""" 0151 result = [self.game, self.ruleset, self.aiVariant, 0152 self.commitDate or datetime.datetime.fromtimestamp(0), self.py_version] 0153 result.extend(self.row[self.fields.TAGS:]) 0154 return result 0155 0156 def __lt__(self, other): 0157 return self.sortkey() < other.sortkey() 0158 0159 def __getitem__(self, field): 0160 """direct access to row""" 0161 return self.row[field] 0162 0163 def __hash__(self): 0164 return hash(tuple(self.row)) 0165 0166 def data(self, field): 0167 """return a string representing this field for messages""" 0168 result = self.row[field] 0169 if field == self.fields.COMMIT: 0170 result = '{}({})'.format(result, self.commitDate) 0171 return result 0172 0173 def differs_for(self, other): 0174 """return the field names for the source attributes causing a difference. 0175 Possible values are commit and py_version. If both rows are identical, return None.""" 0176 if self.row[self.fields.PLAYERS:] != other.row[self.fields.PLAYERS:]: 0177 differing = [] 0178 same = [] 0179 for cause in (self.fields.COMMIT, self.fields.PY_VERSION): 0180 if self.row[cause] != other.row[cause]: 0181 _ = '{} {} != {}'.format(cause.name, self.data(cause), other.data(cause)) 0182 differing.append(_) 0183 else: 0184 _ = '{} {}'.format(cause.name, self.data(cause)) 0185 same.append(_) 0186 return ', '.join(differing), ', '.join(same) 0187 return None 0188 0189 def neutralize(self): 0190 """for comparisons""" 0191 for idx, field in enumerate(self.row): 0192 field = field.replace(' ', '') 0193 if field.startswith('Tester ') or field.startswith('Tüster'): 0194 field = 'Tester' 0195 if 'MEM' in field: 0196 parts = field.split(',') 0197 for part in parts[:]: 0198 if part.startswith('MEM'): 0199 parts.remove(part) 0200 field = ','.join(parts) 0201 self.row[idx] = field 0202 0203 def __str__(self): 0204 return 'Game {} {} AI={} commit={}({}) py={} {}'.format( 0205 self.game, self.ruleset, self.aiVariant, self.commit, self.commitDate, self.py_version, self.tags)