Implement AI

This commit is contained in:
nemunaire 2020-03-15 11:28:11 +01:00
parent 9bbcedeb59
commit 14a8e8d546
4 changed files with 176 additions and 10 deletions

View File

@ -18,6 +18,7 @@ class Card():
class Chasseresse(Card):
color = 91
importance = 6
def __init__(self, score):
Card.__init__(self, score)
@ -26,6 +27,7 @@ class Chasseresse(Card):
class DragonCorail(Card):
color = 92
importance = 7
def __init__(self, score):
Card.__init__(self, score)
@ -34,6 +36,7 @@ class DragonCorail(Card):
class Pearl(Card):
color = 93
importance = 10
def __init__(self, score):
Card.__init__(self, score)
@ -42,6 +45,7 @@ class Pearl(Card):
class Witch(Card):
color = 94
importance = 7
def __init__(self, score):
Card.__init__(self, score)
@ -50,6 +54,7 @@ class Witch(Card):
class DragonPetrified(Card):
color = 95
importance = 9
def __init__(self, score):
Card.__init__(self, score)
@ -58,6 +63,7 @@ class DragonPetrified(Card):
class Golem(Card):
color = 91
importance = 8
def __init__(self, score):
Card.__init__(self, score)
@ -66,6 +72,7 @@ class Golem(Card):
class Guardian(Card):
color = 94
importance = 5
def __init__(self, score):
Card.__init__(self, score)
@ -74,6 +81,7 @@ class Guardian(Card):
class Horser(Card):
color = 92
importance = 7
def __init__(self, score):
Card.__init__(self, score)
@ -82,6 +90,7 @@ class Horser(Card):
class City(Card):
color = 96
importance = 6
def __init__(self):
Card.__init__(self, 0)

134
opale/computer.py Normal file
View File

@ -0,0 +1,134 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Mar 4 22:32:32 2020
@author: AlexandreMouchel
"""
DEBUG = False
import functools
from opale.carte import *
from opale.player import Player
class Computer(Player):
def __init__(self, board):
super().__init__("OpaleIA")
self.board = board
self.witch_played = 0
def base_importance(t):
return t.importance
def importance(self):
return {
Chasseresse: Computer.base_importance(Chasseresse) - len(self.board.chasseresse),
DragonCorail: Computer.base_importance(DragonCorail) + functools.reduce(lambda a,b: a + b, [0] + [p.score for p in self.board.pearl]),
Pearl: Computer.base_importance(Pearl) - len(self.board.pearl) // 2,
Witch: Computer.base_importance(Witch),
DragonPetrified: Computer.base_importance(DragonPetrified) - len(self.board.dragonPetrified) // 2,
Golem: Computer.base_importance(Golem) - len(self.board.golem) // 2,
Guardian: Computer.base_importance(Guardian) - len(self.board.guardian),
Horser: Computer.base_importance(Horser),
City: Computer.base_importance(City) - len(self.board.city),
}
def play_turn(self, play_round):
if DEBUG: print("HAND: %s" % self.hand)
cards = self.search_play()
if DEBUG: print("after search_play: %s" % cards)
if type(cards[0]) == Witch:
self.witch_played += len(cards)
play_round(*cards)
return cards
def search_play(self):
importance = self.importance()
# On regroupe les cartes identiques
cardgroup = {}
for card in self.hand:
if type(card) not in cardgroup:
cardgroup[type(card)] = []
cardgroup[type(card)].append(card)
# On trie les cartes, de sorte de toujours jouer les plus faibles d'abord
for t in cardgroup.keys():
cardgroup[t].sort(key=lambda c: c.score)
# Cas spécial pour les Witches : on veut récupérer les dragons à tout prix !
if Witch in cardgroup and not self.dragonPetrified:
if len(self.board.dragonPetrified) > 0:
if DEBUG: print("search_play RESOLUTION: taking the dragon is my priority")
return [cardgroup[Witch][0]]
# Cas spécial pour les Witches : on garde la dernière Witch pour récupérer un dragon
if self.witch_played >= 3 and Witch in cardgroup:
del cardgroup[Witch]
# On recherche le(s) groupe(s) de carte(s) qui rapporte(nt) le plus de points
scores = {}
score_max = None
for t in cardgroup.keys():
score = functools.reduce(lambda a,b: a + b, [0] + [c.score for c in self.board.try_cards(*cardgroup[t])])
if score > 0:
scores[t] = score
if score_max is None or scores[t] > scores[score_max]:
score_max = t
# Try by reducing the number of cards plays
if score_max is not None:
if DEBUG: print("search_play reduce with score=%d" % scores[score_max])
min_cards = 9
for t in [ty for ty in cardgroup.keys() if ty in scores and scores[ty] == scores[score_max]]:
if DEBUG: print("search_play reducing starting with %s" % cardgroup[t])
ok = len(cardgroup[t])
while ok > 1:
score = functools.reduce(lambda a,b: a + b, [0] + [c.score for c in self.board.try_cards(*cardgroup[t][0:ok - 1])])
if DEBUG: print("search_play reducing score=%d with %s" % (score, cardgroup[t][0:ok-1]))
if score >= scores[score_max]:
ok -= 1
else:
break
if ok != len(cardgroup[t]):
if t == Guardian:
cardgroup[t] = cardgroup[t][len(cardgroup[t]) - ok:]
else:
cardgroup[t] = cardgroup[t][0:ok]
if DEBUG: print("search_play reduce to %d %s" % (ok, cardgroup[t]))
if min_cards > ok:
min_cards = ok
# Return the first match
for t in sorted([ty for ty in cardgroup.keys() if ty in scores and scores[ty] == scores[score_max] and len(cardgroup[ty]) == min_cards], key=lambda t: importance[t], reverse=True):
if DEBUG: print("search_play RESOLUTION: play for score %d" % scores[score_max])
return cardgroup[t]
print("WHY am I here? score_max=%d min_cards=%d groups=[%s] sorted=%s" % (
scores[score_max],
min_cards,
','.join(["%s: %s" % (t.__name__, cardgroup[t]) for t in sorted([ty for ty in cardgroup.keys()], key=lambda t: importance[t], reverse=True)]),
[t.__name__ for t in sorted([ty for ty in cardgroup.keys() if ty in scores and scores[ty] == scores[score_max] and len(cardgroup[ty]) == min_cards], key=lambda t: importance[t], reverse=True)]
))
# Return the first match
for t in sorted(cardgroup.keys(), key=lambda t: importance[t], reverse=True):
if len(cardgroup[t]) > 1:
if t == Pearl and cardgroup[t][0].score + cardgroup[t][0].score < 4:
if DEBUG: print("search_play RESOLUTION: Pearl cards less than 4 points")
return cardgroup[t][0:2]
elif t == Pearl and len(cardgroup[t]) > 2:
if DEBUG: print("search_play RESOLUTION: too much Pearl cards in hand")
return cardgroup[t][0:2]
elif len(cardgroup[t]) > 4:
if DEBUG: print("search_play RESOLUTION: too much card type in hand")
return cardgroup[t][0:2]
if DEBUG: print("search_play RESOLUTION: fallback to default move")
return [cardgroup[t][0]]

View File

@ -10,6 +10,7 @@ import random
from opale.carte import *
from opale.board import Board
from opale.computer import Computer
from opale.player import Player
class Game():
@ -20,7 +21,10 @@ class Game():
self.players = []
for pname in player_names:
self.players.append(Player(pname))
if pname is not None:
self.players.append(Player(pname))
else:
self.players.append(Computer(self.board))
random.shuffle(self.players)

View File

@ -8,9 +8,11 @@ import random
import sys
import uuid
from opale.computer import Computer
import opale.computer
from opale.game import Game
opale.computer.DEBUG = True
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
@ -58,6 +60,11 @@ while True:
watchers.append(connP1)
connP1.send(("Welcome! You'll watch futher games. Wait for one... :)\n").encode())
continue
elif nameP1 == "computer":
nameP2 = None
connP1.send(("Welcome! You'll play against the computer. Please choose your name:\n> ").encode())
nameP1 = connP1.recv(1024).decode().strip()
connP1.send("\n\nWould you like to load a saved game? (leave blank and press ENTER to load a new one)\n> ".encode())
else:
connP1.send(("Welcome \033[93m%s\033[0m! We are waiting for a second player...\n" % nameP1).encode())
@ -90,7 +97,8 @@ while True:
break
connP1.send((chr(27) + "[2J").encode())
connP2.send((chr(27) + "[2J").encode())
if nameP2 is not None:
connP2.send((chr(27) + "[2J").encode())
if choice not in games:
if len(choice) == 0:
choice = str(uuid.uuid1())
@ -98,7 +106,8 @@ while True:
game = Game(nameP1, nameP2)
games[choice] = game
connP1.send(("\n\nYou entered a new game.\nSave the id of the room if you loose the connection for any reason: %s\n" % choice).encode())
connP2.send(("\n\nYou entered a new game.\nSave the id of the room if you loose the connection for any reason: %s\n" % choice).encode())
if nameP2 is not None:
connP2.send(("\n\nYou entered a new game.\nSave the id of the room if you loose the connection for any reason: %s\n" % choice).encode())
print("Play new game between %s and %s: %s" % (nameP1, nameP2, choice))
else:
@ -109,15 +118,21 @@ while True:
game.player1.name = nameP1
game.player2.name = nameP2
connP1.send(("You entered a saved game (%s); \033[93m%s\033[0m takes the place of \033[94m%s\033[0m, \033[93m%s\033[0m the place of \033[94m%s\033[0m.\n" % (choice, nameP1, game.player1.name, nameP2, game.player2.name)).encode())
connP2.send(("You entered a saved game (%s); \033[93m%s\033[0m takes the place of \033[94m%s\033[0m, \033[93m%s\033[0m the place of \033[94m%s\033[0m.\n" % (choice, nameP1, game.player1.name, nameP2, game.player2.name)).encode())
if nameP2 is not None:
connP2.send(("You entered a saved game (%s); \033[93m%s\033[0m takes the place of \033[94m%s\033[0m, \033[93m%s\033[0m the place of \033[94m%s\033[0m.\n" % (choice, nameP1, game.player1.name, nameP2, game.player2.name)).encode())
connP1.send(("\n\nWelcome back! We are at round %d. \033[96m%d citie(s) discovered.\033[0m \033[93m%s\033[0m collected %d cards, you collected %d card(s).\n" % (game.round // 2, game.board.roundCity, game.player2.name, len(game.player2.défausse), len(game.player1.défausse))).encode())
connP2.send(("\n\nWelcome back! We are at round %d. \033[96m%d citie(s) discovered.\033[0m \033[93m%s\033[0m collected %d cards, you collected %d card(s).\n" % (game.round // 2, game.board.roundCity, game.player1.name, len(game.player1.défausse), len(game.player2.défausse))).encode())
if nameP2 is not None:
connP2.send(("\n\nWelcome back! We are at round %d. \033[96m%d citie(s) discovered.\033[0m \033[93m%s\033[0m collected %d cards, you collected %d card(s).\n" % (game.round // 2, game.board.roundCity, game.player1.name, len(game.player1.défausse), len(game.player2.défausse))).encode())
game.player1.print = functools.partial(writeSocket, connP1)
game.player2.print = functools.partial(writeSocket, connP2)
game.player1.input = functools.partial(readSocket, connP1)
game.player2.input = functools.partial(readSocket, connP2)
if type(game.player2) == opale.computer.Computer:
game.player2.print = lambda *args: True
game.player2.input = lambda *args: True
else:
game.player2.print = functools.partial(writeSocket, connP2)
game.player2.input = functools.partial(readSocket, connP2)
def printWatchers(*args):
for w in watchers:
@ -136,6 +151,9 @@ while True:
printWatchers(*args)
def play_turn():
if type(game.current_player) == opale.computer.Computer:
return game.current_player.play_turn(game.play_round)
printWatchers("\n\033[1mCurrent player:\033[0m \033[93m%s\033[0m \033[95m%s\033[0m" % (game.current_player.name, "DRAGON" if game.current_player.dragonPetrified else ""))
game.current_player.print("\n\033[1mCurrent player:\033[0m \033[93m%s\033[0m \033[95m%s\033[0m" % (game.current_player.name, "DRAGON" if game.current_player.dragonPetrified else ""))
game.current_partner.print("\n\033[1mCurrent player:\033[0m \033[96m%s\033[0m \033[95m%s\033[0m" % (game.current_player.name, "DRAGON" if game.current_player.dragonPetrified else ""))
@ -229,9 +247,10 @@ while True:
del games[choice]
connP1.send(("\n\nFinished! See you next time " + nameP1 + "!\n\n\n").encode())
connP2.send(("\n\nFinished! See you next time " + nameP2 + "!\n\n\n").encode())
connP1.close()
connP2.close()
if nameP2 is not None:
connP2.send(("\n\nFinished! See you next time " + nameP2 + "!\n\n\n").encode())
connP2.close()
except BrokenPipeError as e:
print(e)
finally: