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 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

View file

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

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
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))

View file

@ -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)

View file

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