Skip to content

Commit

Permalink
this is it (?)
Browse files Browse the repository at this point in the history
  • Loading branch information
codingWhale13 committed Mar 11, 2018
1 parent f4ca4ed commit b7a7adf
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 96 deletions.
17 changes: 15 additions & 2 deletions board.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,26 @@ def __init__(self, width: int = 7, height: int = 6) -> None:
# generate a new board with given width and height by creating according entries in database
self.__db_interface.generate_board(width, height)

def do_move(self, column: int, player_number: int) -> None:
def do_move(self, column: int, player_id: int) -> None:
# find field on which the token will "fall", starting from the bottom of the given column
# it is guaranteed at this point that the move is valid
y = 0
while self.__db_interface.get_field(column, y) != 0:
y += 1
# let "db_interface" update the database
self.__db_interface.set_field(column, y, player_number)
self.__db_interface.set_field(column, y, player_id)
# save this most recent move as an (x, y) position in database - this makes it easier to check for a win
self.__db_interface.add_to_history(column, y)

# when a bot does a move to figure out if it's a good one, the move needs to be resettable because bot only "thinks"
def undo_move(self) -> None:
# find out what move needs to be reset
undo_x, undo_y = self.__db_interface.get_last_move()
# actually reset it
self.__db_interface.set_field(undo_x, undo_y, 0)
# delete the latest move from history
self.__db_interface.clear_last_move()

def get_board(self) -> dict:
# simply return the dictionary requested from "db_interface"
return self.__db_interface.get_board(self.__width)
Expand All @@ -31,15 +40,19 @@ def clear_board(self) -> None:
self.__db_interface.clear_board()

def get_last_move(self) -> tuple:
# instruct "db_interface" to return the last move
return self.__db_interface.get_last_move()

def get_history(self) -> list:
# instruct "db_interface" to get the entire move history as a list of tuples
return self.__db_interface.get_history()

def clear_history(self) -> None:
# let "db_interface" delete game history
self.__db_interface.clear_history()

# "@property" decorator makes variables accessible from outside this class even though they are private
# if width or height need to be changed by "game", new Board object must be created
@property
def width(self) -> int:
return self.__width
Expand Down
77 changes: 41 additions & 36 deletions bot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from random import choice
from board import Board
from player import Player
from rules import Rules

Expand All @@ -8,60 +9,64 @@ def __init__(self, id: int, default_symbol: str) -> None:
# set variables (without help of user)
self.__id = id
self.__score = 0
self.__name = self.__choose_name()
self.__symbol = default_symbol
self.__depth = 5
self.__rules = Rules()

def __choose_name(self) -> str:
# bot will always pick a random name from "NAMES" (inherited from super class "Player")
return choice(self.NAMES)

def __negamax(self, board, depth, alhpa, beta, multiplier) -> tuple:
pass
"""
if depth == 0 or self.__rules.check_game_over(board):
return (multiplier * score)
best_value = -9999
for i in range(height):
value = -1 * negamax()
best_value = max(best_value, value)
self.__name = choice(self.NAMES)
self.__symbol = default_symbol

alpha = max(alpha, value)
if alhpa >= beta:
break
def get_move(self, board: Board, player_id: int, rules: Rules) -> int:
# find out which moves are valid
possible_moves = []
for x in range(board.width):
if rules.check_move(board.get_board(), board.height, x):
possible_moves += [x]

return (best_value, -1)
"""
# this simple bot checks if it can win by playing a certain column first
for x in possible_moves:
board.do_move(x, player_id)
if rules.check_win(board.get_board(), board.width, board.height, board.get_last_move(), player_id):
# even though a winning move has been detected, undo it - "game" will handle further process
board.undo_move()
return x
# if bot cannot win with this move, undo it
board.undo_move()

def get_move(self, board) -> int:
pass
"""
# "negamax" algorithm determines best move
return self.__negamax(board, self.__depth, -999, 999, 1)[1]
"""
# if bot cannot win in this round, it checks if opponent can win and blocks his move
opponent_player_id = 1 if player_id == 2 else 2
for x in possible_moves:
board.do_move(x, opponent_player_id)
if rules.check_win(board.get_board(), board.width, board.height, board.get_last_move(),
opponent_player_id):
# make sure to undo the latest move - "game" will handle further process
board.undo_move()
return x
# if bot cannot prevent a win by the opponent with this move, undo it
board.undo_move()
# if no victory or defeat can be detected with this very limited search depth, play randomly
return choice(possible_moves)

# "@property" decorator makes variables accessible from outside this class even though they're private
@property
def name(self):
return self.__name

@name.setter
def name(self, name):
self.__name = name
@property
def id(self):
return self.__id

@property
def symbol(self):
return self.__symbol

@symbol.setter
def symbol(self, symbol):
self.__symbol = symbol

@property
def score(self):
return self.__score

# in case of a symbol collision, the symbol of a bot may be changed from outside the class
@symbol.setter
def symbol(self, symbol):
self.__symbol = symbol

# since the score needs to be updated from outside this class, it can be modified even though it's private
@score.setter
def score(self, score):
self.__score = score
Binary file modified db.sqlite
Binary file not shown.
6 changes: 5 additions & 1 deletion db_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def get_board(self, width: int) -> dict:
self.__cursor.execute("SELECT state FROM board ORDER BY y, x;")
for element in self.__cursor.fetchall():
board[(x, y)] = element[0]
# if the end of a row on the board is reached, continue in the next row; if not, increase x by 1
# if the end of a row on the board is reached, continue in the row above; if not, increase x by 1
if x == width - 1:
x = 0
y += 1
Expand Down Expand Up @@ -78,6 +78,10 @@ def get_last_move(self) -> tuple:
self.__cursor.execute("SELECT x, y FROM history ORDER BY rowid DESC LIMIT 1;")
return self.__cursor.fetchone()

def clear_last_move(self) -> None:
# this command deletes the latest entry in table "history"
self.__cursor.execute("DELETE FROM history WHERE rowid = (SELECT MAX(rowid) FROM history);")

def clear_history(self) -> None:
# delete all entries from table "history"
self.__cursor.execute("DELETE FROM history;")
Expand Down
6 changes: 3 additions & 3 deletions fancy_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ class FancyPrint:

# string "message" will be displayed in the corresponding color
# when highlighting a single character, "line_end" can be modified so that no line break is added after the message
def blue(self, message: str, line_end: str) -> None:
def text_blue(self, message: str, line_end: str) -> None:
print(type(self).__BLUE + message + type(self).__END, end=line_end)

def red(self, message: str, line_end: str) -> None:
def text_red(self, message: str, line_end: str) -> None:
print(type(self).__RED + message + type(self).__END, end=line_end)

def yellow(self, message: str, line_end: str) -> None:
def text_yellow(self, message: str, line_end: str) -> None:
print(type(self).__YELLOW + message + type(self).__END, end=line_end)
Loading

0 comments on commit b7a7adf

Please sign in to comment.