check if it works

This commit is contained in:
Francesco Mecca 2019-08-20 16:39:33 +02:00
parent 3beaec0cfd
commit e8fb679f5e
6 changed files with 161 additions and 96 deletions

View file

@ -9,6 +9,7 @@ open Yojson.Basic.Util;;
let read_json () = let read_json () =
let json = Yojson.Basic.from_channel stdin in let open Yojson.Basic.Util in 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 make_card l =
let hd = List.hd l |> to_string in let hd = List.hd l |> to_string in
let tl = List.tl l |> List.hd |> to_int 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 cards_to_json tc.cards in
`List (List.map (fun tcl -> tcards_to_json tcl) table.cards);; `List (List.map (fun tcl -> tcards_to_json tcl) table.cards);;
let open Yojson.Basic.Util in let main maxiter =
let hand, table = read_json () in let open Yojson.Basic.Util in
let tn = Table.make (table.cards@hand) in let hand, table = read_json () in
let res, _ = Table.alg ~maxiter:14 tn void_printer in let tn = Table.make (table.cards@hand) in
(* Printf.printf "%a\n" print_table res;; *) let res, _ = Table.alg ~maxiter:maxiter tn void_printer in
to_json res |> Yojson.Basic.to_channel stdout (* 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

View file

@ -56,8 +56,9 @@ let neighbors (tcs:tcards) table : card list=
let constraints start eend = let constraints start eend =
let hand = List.filter (fun ts -> ts.strategy = Single) start.cards in 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 let res = eend.cards |> List.filter (fun (e:tcards) -> e.strategy = Single && not (List.mem e hand)) in
(List.length res) = 0 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 = let doesnt_improve n scores =
if List.length scores < n then 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; should_exit := is_best_outcome table;
Hashtbl.add set cur_hash (); Hashtbl.add set cur_hash ();
dbg n cur_score table ; 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 ) ; (max_score := cur_score ; best := table ) ;
if !should_exit || n > maxiter || doesnt_improve (maxiter/2) uscores then if !should_exit || n > maxiter || doesnt_improve (maxiter/2) uscores then

1
ono_sendai/debug.json Normal file
View file

@ -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]]]}

View file

@ -4,17 +4,19 @@ from picotui.screen import Screen
from time import sleep from time import sleep
import os import os
import json import json
import logging
from widgets import * from widgets import *
from state import Table, Hand from state import Table, Hand
from metro_holografix.cardtypes import * from metro_holografix.cardtypes import *
import state import state
logging.basicConfig(level=logging.INFO, filename='game.log', filemode='w', format='%(levelname)s - %(message)s')
logging.info("START")
action = '' action = ''
exit = False exit = False
ID = "tui" ID = "you"
game = state.State(["bot1", ID])
class FrameFactory: class FrameFactory:
titles = ['0x10', '0x11', '0x12', '0x13', '0x14', '0x15', '0x16', '0x17', titles = ['0x10', '0x11', '0x12', '0x13', '0x14', '0x15', '0x16', '0x17',
@ -92,7 +94,7 @@ class FrameFactory:
def newHandFrame(self, cards): def newHandFrame(self, cards):
assert type(cards) is list, type(cards) assert type(cards) is list, type(cards)
h = 27 # height ? 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}'] coloredCards = [f'{Fore.BLUE}'+cards[0]] + cards[1:-1] + [cards[-1]+f'{Style.RESET_ALL}']
w = WCardRadioButton(coloredCards, -1, self.constrainAllWidgets, isHand=True) w = WCardRadioButton(coloredCards, -1, self.constrainAllWidgets, isHand=True)
self.d.add(2, 2, w) self.d.add(2, 2, w)
@ -115,7 +117,7 @@ class FrameFactory:
src = i, w.choice-2 src = i, w.choice-2
return src, dst return src, dst
def makeButtons(d): def makeButtons(dialog, stats):
buttonSend = WColoredButton(7, "SND", C_RED) buttonSend = WColoredButton(7, "SND", C_RED)
dialog.add(108, 28, buttonSend) dialog.add(108, 28, buttonSend)
buttonSend.finish_dialog = ACTION_OK buttonSend.finish_dialog = ACTION_OK
@ -144,6 +146,9 @@ def makeButtons(d):
global action; action = "DRAW" global action; action = "DRAW"
buttonDraw.on_click = btnDraw 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) buttonAbort = WColoredButton(13, f'{Fore.BLACK}'+" ABRT "+f'{Style.RESET_ALL}', C_WHITE)
dialog.add(4, 28, buttonAbort) dialog.add(4, 28, buttonAbort)
buttonAbort.finish_dialog = ACTION_OK buttonAbort.finish_dialog = ACTION_OK
@ -164,7 +169,7 @@ def wrong_play():
print(f'{Fore.RED}'+'Wrong play. Retry...'+f'{Style.RESET_ALL}') print(f'{Fore.RED}'+'Wrong play. Retry...'+f'{Style.RESET_ALL}')
sleep(2) sleep(2)
def spawn_and_wait(str): def spawn_and_wait(tstr, difficulty):
import sys import sys
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
from animation.octopus import animate from animation.octopus import animate
@ -174,8 +179,9 @@ def spawn_and_wait(str):
if pid == 0: if pid == 0:
# child # child
os.close(r) os.close(r)
res = Popen(["../hosaka/_build/default/main.exe"], stdout=PIPE, stdin=PIPE) res = Popen(["../hosaka/_build/default/main.exe", str(difficulty)], stdout=PIPE, stdin=PIPE)
out, err = res.communicate(str.encode('utf-8')) out, err = res.communicate(tstr.encode('utf-8'))
print(out, err)
res.stdin.close() res.stdin.close()
w = os.fdopen(w, 'w') w = os.fdopen(w, 'w')
w.write(out.decode('utf-8')) w.write(out.decode('utf-8'))
@ -184,7 +190,7 @@ def spawn_and_wait(str):
os.close(w) os.close(w)
p = os.waitpid(pid, os.WNOHANG) p = os.waitpid(pid, os.WNOHANG)
while p == (0, 0): while p == (0, 0):
animate(10) # animate(10)
p = os.waitpid(pid, os.WNOHANG) p = os.waitpid(pid, os.WNOHANG)
r = os.fdopen(r) r = os.fdopen(r)
output = r.read() output = r.read()
@ -207,14 +213,16 @@ def validate_auto_play(original, nl):
return table, hand return table, hand
def make_auto_move(original, game): def make_auto_move(original, game, difficulty):
table, hand = game.last() table, hand = game.last()
tstr = state.toJson(table, hand) tstr = state.toJson(table, hand)
output = spawn_and_wait(tstr) output = spawn_and_wait(tstr, difficulty)
res = validate_auto_play(original, json.loads(output)) res = validate_auto_play(original, json.loads(output))
if type(res) is str: if type(res) is str:
logging.info(f"BOT-DRAW ({game.nrounds}): {game.cur_player} = {game.last()}")
game.draw() game.draw()
elif type(res) is tuple: elif type(res) is tuple:
logging.info(f"BOT-MOVE ({game.nrounds}): {game.cur_player} = {res}")
game.advance(*res) game.advance(*res)
game.done() game.done()
else: else:
@ -222,66 +230,99 @@ def make_auto_move(original, game):
game.next_turn() game.next_turn()
game.next_turn() def main(difficulty):
while not exit and not game.hasEnded: global exit, action
while game.cur_player != ID:
make_auto_move(game.last()[0], game) 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: if game.hasEnded == True:
break break
if game.hasEnded == True:
break
with Context():
table, hand = game.last() with Context():
Screen.attr_color(C_WHITE, C_GREEN)
Screen.cls()
Screen.attr_reset()
dialog = Dialog(1, 1, 120, 30)
f = FrameFactory(dialog)
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 #### stats = f' Round: {game.nrounds} - '
f.emptyFrame() for idp, h in game.players.items():
for cards in table.widget_repr(): stats += f'{idp}: {len(h.cards)}, '
f.newFrame(cards) stats = stats[:-2] + ' ' # remove last comma
f.newHandFrame(hand.widget_repr()) makeButtons(dialog, stats)
dialog.redraw() #### FRAMES ####
res = dialog.loop() f.emptyFrame()
if res == 1001 or res == 9: # or res == KEY_END or res == KEY_ESC: # 1001 is exit? # 9 is ctrl-c for cards in table.widget_repr():
exit = True f.newFrame(cards)
else: f.newHandFrame(hand.widget_repr())
if action == 'EXIT':
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 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: 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: if game.hasEnded:
print(f'{Fore.RED}' + "Game has ended, player '" + game.winner + "' has won"+f'{Style.RESET_ALL}') 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') if __name__ == '__main__':
print('magari perche` la mossa e` sbagliata') 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))

View file

@ -19,11 +19,12 @@ class Table(Tavolo):
def widget_repr(self): def widget_repr(self):
for ts in self.cards: for ts in self.cards:
ocards = list(sorted(ts.cards, key=lambda c: c.value))
yi = [sym.big['hat']] yi = [sym.big['hat']]
seed = ts.cards[0][0].lower() seed = ocards[0][0].lower()
yi.append(sym.big[seed]) yi.append(sym.big[seed])
yi.append(sym.big[ts.cards[0][1]]) yi.append(sym.big[ocards[0][1]])
for card in ts.cards[1:]: for card in ocards[1:]:
seed = card[0].lower() seed = card[0].lower()
yi.append(sym.sym[seed][card[1]]) yi.append(sym.sym[seed][card[1]])
yield yi yield yi
@ -37,8 +38,6 @@ class Table(Tavolo):
return True return True
else: else:
return False return False
class Hand: class Hand:
def __init__(self, cards): def __init__(self, cards):
@ -46,6 +45,9 @@ class Hand:
return -1 if (a[1],a[0]) < (b[1],b[0]) else 1 return -1 if (a[1],a[0]) < (b[1],b[0]) else 1
self.cards = list(sorted(cards, key=cmp_to_key(sortc))) self.cards = list(sorted(cards, key=cmp_to_key(sortc)))
def __repr__(self):
return 'Hand<'+ str(self.cards) + '>'
def widget_repr(self): def widget_repr(self):
yi = [sym.big['hat']] yi = [sym.big['hat']]
seed = self.cards[0][0].lower() seed = self.cards[0][0].lower()
@ -61,8 +63,9 @@ def make_deck():
def make_set(seed): def make_set(seed):
for i in range(1, 14): for i in range(1, 14):
yield Card(seed, i) 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) shuffle(odeck)
assert len(odeck) == 104
return odeck return odeck
class WrongMoveException(Exception): class WrongMoveException(Exception):
@ -75,9 +78,10 @@ class State:
self.hasEnded = False self.hasEnded = False
self.players = dict() self.players = dict()
self.table = Table([]) self.table = Table([])
self.round = None self.turn = None
self.ids = ids self.ids = ids
self.cur_player = ids[0] self.cur_player = ids[0]
self.nrounds = 0
for i in ids: for i in ids:
cards = [self.deck.pop() for i in range(11)] cards = [self.deck.pop() for i in range(11)]
self.players[i] = Hand(cards) self.players[i] = Hand(cards)
@ -87,48 +91,54 @@ class State:
nhand = Hand(hand.cards + [self.deck.pop()]) nhand = Hand(hand.cards + [self.deck.pop()])
self.players[self.cur_player] = nhand self.players[self.cur_player] = nhand
assert len(self.players[self.cur_player].cards) == len(hand.cards) + 1 assert len(self.players[self.cur_player].cards) == len(hand.cards) + 1
self.round = None self.turn = None
def next_turn(self): 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)] next_player = self.ids[(self.ids.index(self.cur_player) + 1) % len(self.ids)]
self.cur_player = next_player 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): def done(self):
assert self.round is not None assert self.turn is not None
original = self.table original = self.table
table, hand = self.last() 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() raise WrongMoveException()
else: else:
self.table, self.players[self.cur_player] = table, hand self.table, self.players[self.cur_player] = table, hand
self.round = None self.turn = None
if len(hand.cards) == 0: if len(hand.cards) == 0:
# won # won
self.hasEnded = True self.hasEnded = True
self.winner = self.cur_player self.winner = self.cur_player
def last(self): def last(self):
return self.round[-1] return self.turn[-1]
def backtrack(self): def backtrack(self):
if len(self.round) >= 2: if len(self.turn) >= 2:
return self.round.pop() return self.turn.pop()
else: else:
return self.round[0] return self.turn[0]
def size(self): def size(self):
return len(self.round) return len(self.turn)
def move_and_advance(self, src, dst): def move_and_advance(self, src, dst):
table, hand = self.last() table, hand = self.last()
t, h = gioca(table, hand, src, dst) t, h = gioca(table, hand, src, dst)
self.round.append((t, h)) self.turn.append((t, h))
return t, h return t, h
def advance(self, table, hand): def advance(self, table, hand):
self.round.append((table, hand)) self.turn.append((table, hand))
return table, hand return table, hand
def fromJson(j): def fromJson(j):
@ -146,12 +156,12 @@ def toJson(table, hand):
j['table'].append(tc.cards) j['table'].append(tc.cards)
return json.dumps(j) return json.dumps(j)
# TODO: refactor language # TODO: refactor language
def gioca(tavolo, hand, src, dst): def gioca(tavolo, hand, src, dst):
giocata = [] if dst == 'Empty' else tavolo.cards[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]] 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 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(dst) is int or dst == 'Empty'
assert type(src[0]) is int or src[0] == 'Hand' assert type(src[0]) is int or src[0] == 'Hand'
@ -173,4 +183,3 @@ def gioca(tavolo, hand, src, dst):
else: else:
news.append(t) news.append(t)
return Table(news), Hand(hcards) return Table(news), Hand(hcards)

View file

@ -30,6 +30,8 @@ class WColoredButton(WButton):
return r return r
def handle_key(self, inp): def handle_key(self, inp):
if inp in [KEY_ENTER, b's', b'd', b'm']:
return inp
pass pass
def on_click(self, *args, **kwargs): def on_click(self, *args, **kwargs):
@ -42,7 +44,10 @@ class WColoredFrame(WFrame):
title = color.decode('utf-8') + title + self.rst.decode('utf-8') title = color.decode('utf-8') + title + self.rst.decode('utf-8')
super().__init__(w, h, title) super().__init__(w, h, title)
self.color = color self.color = color
def handle_key(self, inp): def handle_key(self, inp):
if inp in [KEY_ENTER, b's', b'd', b'm']:
return inp
pass pass
def draw_box(self, left, top, width, height): def draw_box(self, left, top, width, height):
@ -86,7 +91,9 @@ class WCardRadioButton(WRadioButton):
self.id = id self.id = id
def handle_key(self, inp): 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): def redraw(self):
i = 0 i = 0