Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/DavidHowlett/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Iain-Cheverton committed May 1, 2017
2 parents 33bf00d + 00aaf86 commit 7dd743e
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 13 deletions.
47 changes: 45 additions & 2 deletions David_AI_v9.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
A board is represented by 128 char array
ToDo:
- change positional scoring according to the game's phase
- score moves towards enemy king more highly
- score repeated positions differently as they lead to draws
- create isCheck function
Expand Down Expand Up @@ -117,7 +116,42 @@
total_moves = 0
time_out_point = now() + 100
history = []
position_value = None

initialPosition = '''
r n b q k b n r
p p p p p p p p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R'''
initialPosition = initialPosition.replace(' ', '').split().__reversed__()
initialPosition = array('u', ''.join(row+'_'*8 for row in initialPosition))


def is_check(board, for_white):
"""If for_white is true this returns whether white is in check."""
return any(abs(diff) > 1000 for _, diff in moves(board, not for_white))


def is_checkmate(board, whites_turn):
"""If for_white is true this returns whether white is in checkmate."""
return (
is_check(board, whites_turn) and
all(is_check(move_, whites_turn) for move_, _ in moves(board, whites_turn)))


# todo this should be built on legal move generation
def is_stalemate(board):
"""Returns true if either side has no leagal moves"""
white_cant_move = (
(not is_check(board, True)) and
all(is_check(move_, True) for move_, _ in moves(board, True)))
black_cant_move = (
(not is_check(board, False)) and
all(is_check(move_, False) for move_, _ in moves(board, False)))
return white_cant_move or black_cant_move


def evaluate(board)->float:
Expand Down Expand Up @@ -149,6 +183,9 @@ def recalculate_position_values(board):
position_value[piece_.lower()].extend(
[-PIECE_VALUE[piece_]-value for value in row.__reversed__()]+[None]*8)

# it is better that the position values are given some sensible default values
recalculate_position_values(initialPosition)


def move(board, pos1, pos2):
global total_moves
Expand Down Expand Up @@ -653,4 +690,10 @@ def main(given_history, white_time, black_time):
315452 5 1.253 60681
211089 moves made per second
changed piece square tables
42 1 0.000 0
254 2 0.002 67
8371 3 0.067 2075
64724 4 0.225 4808
339102 5 1.388 63499
201605 moves made per second
'''
129 changes: 129 additions & 0 deletions David_AI_v9_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import David_AI_v9 as ai
from array import array
from time import perf_counter as now

boards = {
'initialPosition': '''
r n b q k b n r
p p p p p p p p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R''',
'difficultPosition': '''
r . b q . . . r
p p p p n k p p
. . n b . p . .
. . . . p . . .
. . P . N . . .
P . . P B N . .
. P . . P P P P
R . . Q K B . R''',
'promotionPosition': '''
r . . . . . . .
. P . P . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . p . p .
. . . . . . . R''',
'pawnTakePosition1': '''
. . . . . . . .
. . . p . . . .
. . . . P . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .''',
'pawnTakePosition2': '''
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . p . . . .
. . . . P . . .
. . . . . . . .''',
'kingSavePosition': '''
r . . . . . . .
p . . . . . . .
P . . k p . . .
. . . . r . . .
. . . . Q . . .
. . . p . . P .
. . . . . . . P
. . . . K . N R''',
'kingThreat': '''
r . . . . . . .
p . . . . . . .
P . . . p . . .
. . . . k . . .
. . . . Q . . .
. . . p . . P .
. . . . . . . P
. . . . K . N R''',
'castlingPosition': '''
r . . . k . . r
p . . . . . . p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P . . . . . . P
R . . . K . . R''',
'currentBug': '''
. . . . . r k r
. p . . . . . p
p . . . . . . .
. . . . . p . .
. . n N . B . .
. . N . . . . P
P K . . . P P .
. . . R . . . R'''
}

for key in boards:
board = boards[key].replace(' ', '').split().__reversed__()
board = array('u', ''.join(row+'_'*8 for row in board))
assert len(board) == 128
boards[key] = board
# print(len(list(ai.moves(position, True))))
assert abs(ai.evaluate(boards['initialPosition'])) < 0.000001
assert len(list(ai.moves(boards['initialPosition'], True))) == 20
assert len(list(ai.moves(boards['difficultPosition'], True))) == 42
assert len(list(ai.moves(boards['pawnTakePosition1'], True))) == 2
assert len(list(ai.moves(boards['pawnTakePosition1'], False))) == 3
assert len(list(ai.moves(boards['pawnTakePosition2'], True))) == 3
assert len(list(ai.moves(boards['pawnTakePosition2'], False))) == 2
assert len(list(ai.moves(boards['castlingPosition'], True))) == 16
assert len(list(ai.moves(boards['castlingPosition'], False))) == 16
assert not ai.is_check(boards['kingSavePosition'], True)
assert not ai.is_check(boards['kingSavePosition'], False)
assert not ai.is_check(boards['kingThreat'], True)
assert ai.is_check(boards['kingThreat'], False)
assert ai.position_value['N'][3+4*16] == ai.position_value['N'][4+3*16]
assert ai.position_value['N'][3+4*16] == -ai.position_value['n'][3+4*16]
assert ai.position_value['P'][16] < ai.position_value['P'][5*16]
assert ai.position_value['p'][6*16] > ai.position_value['p'][2*16]
assert ai.position_value['P'][4*16] < ai.position_value['P'][4+4*16]


def performance_test():
ai.total_moves = 0
_board = boards['difficultPosition']
test_start_time = now()
for depth in range(1, 6):
start_time = now()
best_move, _ = ai.search(_board, depth, ai.evaluate(_board), True, -99999, 99999)
print('{}\t\t\t{}\t\t{:.3f}\t{}'.format(ai.total_moves, depth, now() - start_time, len(ai.transpositionTable)))
# print('\n'.join(' '.join(piece for piece in row) for row in best_move.__reversed__()) + '\n')
print('{} moves made per second'.format(int(ai.total_moves/(now()-test_start_time))))


ai.transpositionTable = dict()
performance_test()

5 changes: 2 additions & 3 deletions results printer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import hashlib
import inspect
from runner import competitors, make_file_name
from runner import competitors, make_file_name, source_hash


competitorNames = [
Expand Down Expand Up @@ -47,8 +47,7 @@
file = open(make_file_name(white, black, repeat))
previous_versions = file.readline()
current_versions = (
f'{hashlib.sha256(inspect.getsource(white).encode()).hexdigest()} vs '
f'{hashlib.sha256(inspect.getsource(black).encode()).hexdigest()} repeat {repeat}\n')
f'{source_hash(white)} vs {source_hash(black)} repeat {repeat}\n')
if current_versions != previous_versions:
continue
except (FileNotFoundError, SyntaxError):
Expand Down
28 changes: 20 additions & 8 deletions runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def print_state(_turn, board, run_time, white_time_remaining, black_time_remaini
print('white time: {:.3f}'.format(white_time_remaining))
print('black time: {:.3f}'.format(black_time_remaining))
print('score: {:.1f}'.format(ai.evaluate(ai.to_array(board))))
if ai.is_check(ai.to_array(board), True):
print('white is in check')
if ai.is_check(ai.to_array(board), False):
print('black is in check')
print()


Expand All @@ -73,6 +77,12 @@ def make_file_name(white, black, repeat):
return rf'results/{white.__name__} vs {black.__name__} repeat {repeat}.txt'


def source_hash(player):
# this line makes things the same on windows and unix
normalised_source = '\n'.join(inspect.getsource(player).split())
return hashlib.sha256(normalised_source.encode()).hexdigest()


def match(white, black, repeat):
"""This plays a single match between the white and black players and records the result."""
print(f'\nMatch {repeat} between {white.__name__} on white and {black.__name__} on black')
Expand Down Expand Up @@ -113,18 +123,21 @@ def match(white, black, repeat):
black_time = initialTime + black_moves * (timePerMove + (repeat - 1) * extraRepeatTime) - black_time_taken
black_moves += 1
print_state(turn, chosen_move, run_time, white_time, black_time, white, black, repeat)
if ai.is_checkmate(ai.to_array(chosen_move), True):
to_record = {'score': 0, 'cause': 'Black won by checkmate'}
break
if ai.is_checkmate(ai.to_array(chosen_move), False):
to_record = {'score': 1, 'cause': 'White won by checkmate'}
break
if ai.is_stalemate(ai.to_array(chosen_move)):
to_record = {'score': 0.5, 'cause': 'Draw due to stalemate'}
break
if white_time < 0:
to_record = {'score': 0, 'cause': 'Black won due to white running out of time'}
break
if black_time < 0:
to_record = {'score': 1, 'cause': 'White won due to black running out of time'}
break
if not any(any(piece == 'K' for piece in row) for row in chosen_move):
to_record = {'score': 0, 'cause': 'Black won by taking the king'}
break
if not any(any(piece == 'k' for piece in row) for row in chosen_move):
to_record = {'score': 1, 'cause': 'White won by taking the king'}
break
if chosen_move not in legal_moves(history, player_is_white):
if player_is_white:
to_record = {'score': 0, 'cause': 'Black won because white made an illegal move'}
Expand All @@ -149,8 +162,7 @@ def match(white, black, repeat):
continue
file_name = make_file_name(white, black, repeat)
current_versions = (
f'{hashlib.sha256(inspect.getsource(white).encode()).hexdigest()} vs '
f'{hashlib.sha256(inspect.getsource(black).encode()).hexdigest()} repeat {repeat}\n')
f'{source_hash(white)} vs {source_hash(black)} repeat {repeat}\n')
try:
file = open(file_name)
previous_versions = file.readline()
Expand Down

0 comments on commit 7dd743e

Please sign in to comment.