From e8fb679f5e5abfa4a4d81ceafc86749e77007708 Mon Sep 17 00:00:00 2001 From: Francesco Mecca Date: Tue, 20 Aug 2019 16:39:33 +0200 Subject: [PATCH] check if it works --- hosaka/main.ml | 18 +++-- hosaka/table.ml | 7 +- ono_sendai/debug.json | 1 + ono_sendai/prova.py | 169 ++++++++++++++++++++++++++---------------- ono_sendai/state.py | 53 +++++++------ ono_sendai/widgets.py | 9 ++- 6 files changed, 161 insertions(+), 96 deletions(-) create mode 100644 ono_sendai/debug.json diff --git a/hosaka/main.ml b/hosaka/main.ml index 14d76af..3bfe1a2 100644 --- a/hosaka/main.ml +++ b/hosaka/main.ml @@ -9,6 +9,7 @@ open Yojson.Basic.Util;; let read_json () = let json = Yojson.Basic.from_channel stdin in let open Yojson.Basic.Util in + (* let json = Yojson.Basic.from_file "../ono_sendai/debug.json" in let open Yojson.Basic.Util in *) let make_card l = let hd = List.hd l |> to_string in let tl = List.tl l |> List.hd |> to_int in @@ -29,9 +30,14 @@ let to_json (table:Table.table) = cards_to_json tc.cards in `List (List.map (fun tcl -> tcards_to_json tcl) table.cards);; -let open Yojson.Basic.Util in -let hand, table = read_json () in -let tn = Table.make (table.cards@hand) in -let res, _ = Table.alg ~maxiter:14 tn void_printer in -(* Printf.printf "%a\n" print_table res;; *) -to_json res |> Yojson.Basic.to_channel stdout +let main maxiter = + let open Yojson.Basic.Util in + let hand, table = read_json () in + let tn = Table.make (table.cards@hand) in + let res, _ = Table.alg ~maxiter:maxiter tn void_printer in + (* Printf.printf "%a\n" print_table res;; *) + to_json res |> Yojson.Basic.to_channel stdout + +let () = + let n = Sys.argv.(1) |> int_of_string in + main n diff --git a/hosaka/table.ml b/hosaka/table.ml index 10c91a1..c1e5a94 100644 --- a/hosaka/table.ml +++ b/hosaka/table.ml @@ -56,8 +56,9 @@ let neighbors (tcs:tcards) table : card list= let constraints start eend = let hand = List.filter (fun ts -> ts.strategy = Single) start.cards in - let res = List.filter (fun (e:tcards) -> e.strategy = Single && not (List.mem e hand)) eend.cards in - (List.length res) = 0 + let res = eend.cards |> List.filter (fun (e:tcards) -> e.strategy = Single && not (List.mem e hand)) in + let invs = eend.cards |> List.filter (fun (e:tcards) -> e.strategy <> Single && e.tag = Invalid) in + (List.length res) = 0 && (List.length invs) = 0 let doesnt_improve n scores = if List.length scores < n then @@ -104,7 +105,7 @@ let alg ?maxiter original (dbg: int -> int -> table -> unit) = should_exit := is_best_outcome table; Hashtbl.add set cur_hash (); dbg n cur_score table ; - if constraints original table && cur_score > !max_score then + if cur_score > !max_score && constraints original table then (max_score := cur_score ; best := table ) ; if !should_exit || n > maxiter || doesnt_improve (maxiter/2) uscores then diff --git a/ono_sendai/debug.json b/ono_sendai/debug.json new file mode 100644 index 0000000..b2992bd --- /dev/null +++ b/ono_sendai/debug.json @@ -0,0 +1 @@ +{"hand": [["Hearts", 2], ["Tiles", 2], ["Clovers", 3], ["Pikes", 3], ["Hearts", 5], ["Tiles", 5], ["Clovers", 7], ["Hearts", 8], ["Tiles", 8], ["Tiles", 9], ["Tiles", 11], ["Clovers", 12], ["Clovers", 13]], "table": [[["Hearts", 11], ["Hearts", 12], ["Hearts", 13]], [["Clovers", 10], ["Hearts", 10], ["Tiles", 10]]]} diff --git a/ono_sendai/prova.py b/ono_sendai/prova.py index 5eca63b..8c2c5bf 100644 --- a/ono_sendai/prova.py +++ b/ono_sendai/prova.py @@ -4,17 +4,19 @@ from picotui.screen import Screen from time import sleep import os import json +import logging from widgets import * from state import Table, Hand from metro_holografix.cardtypes import * import state +logging.basicConfig(level=logging.INFO, filename='game.log', filemode='w', format='%(levelname)s - %(message)s') +logging.info("START") + action = '' exit = False -ID = "tui" - -game = state.State(["bot1", ID]) +ID = "you" class FrameFactory: titles = ['0x10', '0x11', '0x12', '0x13', '0x14', '0x15', '0x16', '0x17', @@ -92,7 +94,7 @@ class FrameFactory: def newHandFrame(self, cards): assert type(cards) is list, type(cards) h = 27 # height ? - self.d.add(1, 1, WColoredFrame(12, h, 'HAND: '+str(len(cards)), blue)) + self.d.add(1, 1, WColoredFrame(12, h, 'HAND: '+str(len(cards)-2), blue)) coloredCards = [f'{Fore.BLUE}'+cards[0]] + cards[1:-1] + [cards[-1]+f'{Style.RESET_ALL}'] w = WCardRadioButton(coloredCards, -1, self.constrainAllWidgets, isHand=True) self.d.add(2, 2, w) @@ -115,7 +117,7 @@ class FrameFactory: src = i, w.choice-2 return src, dst -def makeButtons(d): +def makeButtons(dialog, stats): buttonSend = WColoredButton(7, "SND", C_RED) dialog.add(108, 28, buttonSend) buttonSend.finish_dialog = ACTION_OK @@ -144,6 +146,9 @@ def makeButtons(d): global action; action = "DRAW" buttonDraw.on_click = btnDraw + buttonStats = WColoredButton(13, stats, C_BLACK) + dialog.add(15, 28, buttonStats) + buttonAbort = WColoredButton(13, f'{Fore.BLACK}'+" ABRT "+f'{Style.RESET_ALL}', C_WHITE) dialog.add(4, 28, buttonAbort) buttonAbort.finish_dialog = ACTION_OK @@ -164,7 +169,7 @@ def wrong_play(): print(f'{Fore.RED}'+'Wrong play. Retry...'+f'{Style.RESET_ALL}') sleep(2) -def spawn_and_wait(str): +def spawn_and_wait(tstr, difficulty): import sys from subprocess import PIPE, Popen from animation.octopus import animate @@ -174,8 +179,9 @@ def spawn_and_wait(str): if pid == 0: # child os.close(r) - res = Popen(["../hosaka/_build/default/main.exe"], stdout=PIPE, stdin=PIPE) - out, err = res.communicate(str.encode('utf-8')) + res = Popen(["../hosaka/_build/default/main.exe", str(difficulty)], stdout=PIPE, stdin=PIPE) + out, err = res.communicate(tstr.encode('utf-8')) + print(out, err) res.stdin.close() w = os.fdopen(w, 'w') w.write(out.decode('utf-8')) @@ -184,7 +190,7 @@ def spawn_and_wait(str): os.close(w) p = os.waitpid(pid, os.WNOHANG) while p == (0, 0): - animate(10) + # animate(10) p = os.waitpid(pid, os.WNOHANG) r = os.fdopen(r) output = r.read() @@ -207,14 +213,16 @@ def validate_auto_play(original, nl): return table, hand -def make_auto_move(original, game): +def make_auto_move(original, game, difficulty): table, hand = game.last() tstr = state.toJson(table, hand) - output = spawn_and_wait(tstr) + output = spawn_and_wait(tstr, difficulty) res = validate_auto_play(original, json.loads(output)) if type(res) is str: + logging.info(f"BOT-DRAW ({game.nrounds}): {game.cur_player} = {game.last()}") game.draw() elif type(res) is tuple: + logging.info(f"BOT-MOVE ({game.nrounds}): {game.cur_player} = {res}") game.advance(*res) game.done() else: @@ -222,66 +230,99 @@ def make_auto_move(original, game): game.next_turn() -game.next_turn() -while not exit and not game.hasEnded: - while game.cur_player != ID: - make_auto_move(game.last()[0], game) +def main(difficulty): + global exit, action + + game = state.State(["bot1", ID]) + + game.next_turn() + + while not exit and not game.hasEnded: + while game.cur_player != ID: + make_auto_move(game.last()[0], game, difficulty) + if game.hasEnded == True: + break if game.hasEnded == True: break - if game.hasEnded == True: - break - - with Context(): - table, hand = game.last() - Screen.attr_color(C_WHITE, C_GREEN) - Screen.cls() - Screen.attr_reset() - dialog = Dialog(1, 1, 120, 30) - f = FrameFactory(dialog) + with Context(): - makeButtons(dialog) + table, hand = game.last() + Screen.attr_color(C_WHITE, C_GREEN) + Screen.cls() + Screen.attr_reset() + dialog = Dialog(1, 1, 120, 30) + f = FrameFactory(dialog) - #### FRAMES #### - f.emptyFrame() - for cards in table.widget_repr(): - f.newFrame(cards) - f.newHandFrame(hand.widget_repr()) + stats = f' Round: {game.nrounds} - ' + for idp, h in game.players.items(): + stats += f'{idp}: {len(h.cards)}, ' + stats = stats[:-2] + ' ' # remove last comma + makeButtons(dialog, stats) - dialog.redraw() - res = dialog.loop() - if res == 1001 or res == 9: # or res == KEY_END or res == KEY_ESC: # 1001 is exit? # 9 is ctrl-c - exit = True - else: - if action == 'EXIT': + #### FRAMES #### + f.emptyFrame() + for cards in table.widget_repr(): + f.newFrame(cards) + f.newHandFrame(hand.widget_repr()) + + dialog.redraw() + res = dialog.loop() + if res == 1001 or res == 9: # or res == KEY_END or res == KEY_ESC: # 1001 is exit? # 9 is ctrl-c exit = True - elif action == 'MOVE': - # TODO: transition effect - src, dst = f.getChoices() - if src[0] is not None and src[1] is not None and dst is not None: - game.move_and_advance(src, dst) # get them from next - else: - continue - elif action == 'DRAW': - game.draw() - game.next_turn() - elif action == 'RESET': - while game.size() > 1: - game.backtrack() - elif action == 'SEND': - try: - game.done() - game.next_turn() - except state.WrongMoveException as e: - wrong_play() - elif action == 'BACK': - game.backtrack() else: - assert False + if action == 'EXIT': + exit = True + elif action == 'MOVE' or res == KEY_ENTER or res == b'm': + # TODO: transition effect + src, dst = f.getChoices() + if src[0] is not None and src[1] is not None and dst is not None: + game.move_and_advance(src, dst) # get them from next + logging.info(f"MOVE ({game.nrounds}): {game.cur_player} = {src}:{dst}") + else: + continue + elif action == 'DRAW' or res == b'd': + logging.info(f"DRAW ({game.nrounds}): {game.cur_player} = {game.last()}") + game.draw() + game.next_turn() + elif action == 'RESET': + while game.size() > 1: + game.backtrack() + logging.info(f"RESET ({game.nrounds}): {game.cur_player}") + elif action == 'SEND' or res == b's': + try: + th = game.last() + pl = game.cur_player + game.done() + logging.info(f"DONE ({game.nrounds}): {pl}' = {th}") + game.next_turn() + except state.WrongMoveException as e: + wrong_play() + logging.info(f"WRONGPLAY ({game.nrounds}): {game.cur_player} = {game.last()}") + elif action == 'BACK': + game.backtrack() + logging.info(f"BACK ({game.nrounds}): {game.cur_player}") + else: + pass -if game.hasEnded: - print(f'{Fore.RED}' + "Game has ended, player '" + game.winner + "' has won"+f'{Style.RESET_ALL}') + if game.hasEnded: + print(f'{Fore.RED}' + "Game has ended, player '" + game.winner + "' has won"+f'{Style.RESET_ALL}') -print('TODO: ordina per bene KQ12, mostra le carte in mano agli altri') -print('LOGGER') -print('magari perche` la mossa e` sbagliata') + +if __name__ == '__main__': + from sys import argv + + print(argv[1]) + diff = argv[1] + if diff == 'easy': + main(7) + elif diff == 'medium': + main(14) + elif diff == 'hard': + main(21) + else: + try: + diff = int(diff) + except: + print('Wrong argument for difficulty') + main(int(diff)) diff --git a/ono_sendai/state.py b/ono_sendai/state.py index 15b391d..3832e68 100644 --- a/ono_sendai/state.py +++ b/ono_sendai/state.py @@ -19,11 +19,12 @@ class Table(Tavolo): def widget_repr(self): for ts in self.cards: + ocards = list(sorted(ts.cards, key=lambda c: c.value)) yi = [sym.big['hat']] - seed = ts.cards[0][0].lower() + seed = ocards[0][0].lower() yi.append(sym.big[seed]) - yi.append(sym.big[ts.cards[0][1]]) - for card in ts.cards[1:]: + yi.append(sym.big[ocards[0][1]]) + for card in ocards[1:]: seed = card[0].lower() yi.append(sym.sym[seed][card[1]]) yield yi @@ -37,8 +38,6 @@ class Table(Tavolo): return True else: return False - - class Hand: def __init__(self, cards): @@ -46,6 +45,9 @@ class Hand: return -1 if (a[1],a[0]) < (b[1],b[0]) else 1 self.cards = list(sorted(cards, key=cmp_to_key(sortc))) + def __repr__(self): + return 'Hand<'+ str(self.cards) + '>' + def widget_repr(self): yi = [sym.big['hat']] seed = self.cards[0][0].lower() @@ -61,8 +63,9 @@ def make_deck(): def make_set(seed): for i in range(1, 14): yield Card(seed, i) - odeck = [m for seed in ['Pikes', 'Hearts', 'Clovers', 'Tiles'] for m in make_set(seed)] + odeck = [m for seed in ['Pikes', 'Hearts', 'Clovers', 'Tiles'] for m in make_set(seed)] * 2 shuffle(odeck) + assert len(odeck) == 104 return odeck class WrongMoveException(Exception): @@ -75,9 +78,10 @@ class State: self.hasEnded = False self.players = dict() self.table = Table([]) - self.round = None + self.turn = None self.ids = ids self.cur_player = ids[0] + self.nrounds = 0 for i in ids: cards = [self.deck.pop() for i in range(11)] self.players[i] = Hand(cards) @@ -87,48 +91,54 @@ class State: nhand = Hand(hand.cards + [self.deck.pop()]) self.players[self.cur_player] = nhand assert len(self.players[self.cur_player].cards) == len(hand.cards) + 1 - self.round = None + self.turn = None def next_turn(self): - assert self.round is None + assert self.turn is None next_player = self.ids[(self.ids.index(self.cur_player) + 1) % len(self.ids)] self.cur_player = next_player - self.round = [(copy(self.table), copy(self.players[self.cur_player]))] + self.turn = [(copy(self.table), copy(self.players[self.cur_player]))] + self.nrounds = self.nrounds + 1 if self.cur_player == self.ids[0] else self.nrounds def done(self): - assert self.round is not None + assert self.turn is not None original = self.table table, hand = self.last() - if not table.is_valid() or len(set(original.flatten()) - set(table.flatten())) != 0: + + if original.equality(table): + raise WrongMoveException() + elif not table.is_valid() or len(set(original.flatten()) - set(table.flatten())) != 0: + if self.cur_player != 'you': + fuck() # debug raise WrongMoveException() else: self.table, self.players[self.cur_player] = table, hand - self.round = None + self.turn = None if len(hand.cards) == 0: # won self.hasEnded = True self.winner = self.cur_player def last(self): - return self.round[-1] + return self.turn[-1] def backtrack(self): - if len(self.round) >= 2: - return self.round.pop() + if len(self.turn) >= 2: + return self.turn.pop() else: - return self.round[0] + return self.turn[0] def size(self): - return len(self.round) + return len(self.turn) def move_and_advance(self, src, dst): table, hand = self.last() t, h = gioca(table, hand, src, dst) - self.round.append((t, h)) + self.turn.append((t, h)) return t, h def advance(self, table, hand): - self.round.append((table, hand)) + self.turn.append((table, hand)) return table, hand def fromJson(j): @@ -146,12 +156,12 @@ def toJson(table, hand): j['table'].append(tc.cards) return json.dumps(j) - # TODO: refactor language def gioca(tavolo, hand, src, dst): giocata = [] if dst == 'Empty' else tavolo.cards[dst] da_muovere = hand.cards[src[1]] if src[0] == 'Hand' else tavolo.cards[src[0]].cards[src[1]] hcards = hand.cards[:src[1]] + hand.cards[src[1]+1:] if src[0] == 'Hand' else hand.cards + assert src[0] != 'Hand' or len(hcards) == len(hand.cards) - 1 assert type(dst) is int or dst == 'Empty' assert type(src[0]) is int or src[0] == 'Hand' @@ -173,4 +183,3 @@ def gioca(tavolo, hand, src, dst): else: news.append(t) return Table(news), Hand(hcards) - diff --git a/ono_sendai/widgets.py b/ono_sendai/widgets.py index 27e1465..24a8c29 100644 --- a/ono_sendai/widgets.py +++ b/ono_sendai/widgets.py @@ -30,6 +30,8 @@ class WColoredButton(WButton): return r def handle_key(self, inp): + if inp in [KEY_ENTER, b's', b'd', b'm']: + return inp pass def on_click(self, *args, **kwargs): @@ -42,7 +44,10 @@ class WColoredFrame(WFrame): title = color.decode('utf-8') + title + self.rst.decode('utf-8') super().__init__(w, h, title) self.color = color + def handle_key(self, inp): + if inp in [KEY_ENTER, b's', b'd', b'm']: + return inp pass def draw_box(self, left, top, width, height): @@ -86,7 +91,9 @@ class WCardRadioButton(WRadioButton): self.id = id def handle_key(self, inp): - pass # TODO: maybe enable keyboard + if inp in [KEY_ENTER, b's', b'd', b'm']: + return inp + pass def redraw(self): i = 0