Last active
September 29, 2016 15:32
-
-
Save dcousens/fb51f9f2196cd6399135 to your computer and use it in GitHub Desktop.
Adapted from a StackOverflow answer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections import namedtuple | |
#=========================================================================== | |
class Rule: | |
""" The rule interface. | |
""" | |
def checkForAction(self, player, move): | |
pass | |
class MillRule(Rule): | |
""" Checks for any mills in the game. | |
""" | |
def __init__(self, board): | |
self.board = board | |
self.existing_mills = set() | |
def __find_mills(self): | |
""" finds the set of all existing mills on the board by checking adjacencies or something. | |
""" | |
return set() | |
def checkForAction(self, player, move): | |
all_mills = self.__find_mills() | |
new_mills = all_mills - self.existing_mills | |
if len(new_mills) >= 1: | |
return "REMOVE_ACTION_REQUIRED" | |
return None | |
class TwoLeftAndYouLoseRule(Rule): | |
""" If a player has two pieces or less, return PLAYER_LOSE. | |
""" | |
def __init__(self, board): | |
self.board = board | |
def checkForAction(self, player, move): | |
n_player_pieces = len(self.board.get_player_vertices(player)) | |
if n_player_pieces <= 2: | |
return "PLAYER_LOSE" | |
return None | |
class PlacementRules(Rule): | |
""" Ensures no placements occur during the first phase of the game | |
""" | |
def __init__(self, board): | |
self.board = board | |
self.pieces_placed = 0 | |
def checkForAction(self, player, move): | |
# have all pieces been placed, if yes, this rule no longer counts | |
if self.pieces_placed == 18: | |
return None | |
# ensure destination is a valid and empty position on the board | |
if not (self.board.exists(move.destination) and self.board.owner(move.destination) is None): | |
return "INVALID_MOVE" | |
# ensure move.source is None | |
# pieces are being placed at the moment, you can't move them, no source, only destination | |
if move.source is not None: | |
return "INVALID_MOVE" | |
# a valid placement then, record it and lets move on | |
self.pieces_placed += 1 | |
self.board.claim(move.destination, player) | |
class MovementRules(Rule): | |
""" Ensures no move is illegal by comparing it against the game state | |
""" | |
def __init__(self, board): | |
self.board = board | |
def checkForAction(self, player, move): | |
# ensure destination is a valid and empty position on the board | |
if not (self.board.exists(move.destination) and self.board.owner(move.destination) is None): | |
return "INVALID_MOVE" | |
# now check the source exists and that the player owns it | |
if self.board.exists(move.source) and (self.board.owner(move.source) is player): | |
n_player_pieces = len(self.board.get_player_vertices(player)) | |
# if player has more than 3 pieces left, adjacency checking rules still apply (Phase 3) | |
if n_player_pieces > 3: | |
if not self.board.check_connected(move.source, move.destination): | |
return "INVALID_MOVE" | |
# player claims new position, and relinquishes previous | |
self.board.claim(move.destination, player) | |
self.board.relinquish(move.source) | |
# otherwise, maybe this was a placement (Phase 1) move, if so, ignore it | |
elif move.source is None: | |
return None | |
# not sure how you got here, but its not valid! | |
return "INVALID_MOVE" | |
#=========================================================================== | |
Vertex = namedtuple('Vertex', ['edges', 'owner']) | |
class Board: | |
""" Responsible for maintaining the game state. | |
The vertices on the graph (ie board) are all that represent the game state in this game, the rest is governed by rules in the game itself. | |
A rule may dictate, that on a certain move, a player owns or relinquishes ownership of a node on the graph. | |
""" | |
def __init__(self): | |
self.graph = self.__create_graph() | |
def __create_graph(self): | |
""" | |
a b c | |
d e f | |
g h i | |
j k l m n o | |
p q r | |
s t u | |
v w x | |
""" | |
return { | |
'a': Vertex(['b', 'j'], None), | |
'b': Vertex(['c', 'e'], None), | |
# ... | |
'x': Vertex(['o', 'w'], None) | |
} | |
def check_connected(self, a, b): | |
""" Checks if the points a and b are connected by an edge | |
""" | |
# this is a bi-directional graph, so only need to check one way | |
return a in self.graph[b].edges | |
def claim(self, vertex, player): | |
self.graph[vertex].owner = player | |
def exists(self, vertex): | |
""" Checks if vertex exists in this graph | |
""" | |
return (vertex in self.graph) | |
def get_player_vertices(self, player): | |
""" Gets all vertices owned by a player | |
""" | |
return [vertex.owner is player for vertex in self.graph.values()] | |
def owner(self, vertex): | |
""" Returns the owner of a vertex | |
""" | |
return self.graph[vertex].owner | |
def relinquish(self, vertex): | |
self.graph[vertex].owner = None | |
#=========================================================================== | |
class Game: | |
""" The game. | |
Manages players and rules. | |
Ensures players don't break the rules. | |
""" | |
def __init__(self, players): | |
self.board = Board() | |
self.finished = False | |
self.history = [] | |
self.players = players | |
# ordered list of rules (rules have priority, but shhh, the rules don't know that) | |
self.rules = [ | |
PlacementRules(self.board), | |
MovementRules(self.board), | |
TwoLeftAndYouLoseRule(self.board), | |
MillRule(self.board) | |
] | |
def update(self): | |
for player in self.players: | |
move = player.askMove(self) | |
self.history.append(move) | |
for rule in self.rules: | |
action = rule.checkForAction(player, move) | |
print("Action?: ", action) | |
if (action is not None): | |
# do the action... if invalid move, ask for another move etc, probably need recursively call this function | |
# if remove action, player.askRemove etc etc etc | |
pass | |
#=========================================================================== | |
Move = namedtuple('Move', ['source', 'destination']) | |
class Player: | |
""" The player interface. | |
""" | |
def askMove(self, game): | |
pass | |
def askRemove(self, game): | |
pass | |
class HumanPlayer(Player): | |
def askMove(self, game): | |
""" Please input the source position (or click it, or something) | |
Please input the destination ... | |
Tada, you made a move | |
""" | |
return Move(None, None) | |
class AIPlayer(Player): | |
def askMove(self, game): | |
""" Some fancy AI algorithm to go here | |
""" | |
return Move(None, None) | |
#=========================================================================== | |
class View: | |
""" A view of the game, a renderer. | |
2d, 3d, text, whatever. Only needed for Humans. | |
""" | |
def __init__(self, game): | |
pass | |
def render(self): | |
pass | |
#============================================================================== | |
if __name__ == "__main__": | |
p1 = HumanPlayer() | |
p2 = AIPlayer() | |
players = (p1, p2) | |
game = Game(players) | |
view = View(game) | |
while not game.finished: | |
game.update() | |
view.render() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Control.Monad | |
import Data.List | |
import Data.Maybe | |
-- Cales magical functions | |
pick 0 xs = [[]] | |
pick n xs = [(x : xs) | (x : ys) <- tails xs, xs <- pick (n - 1) ys] | |
setPairs l = [(a, b) | (a : bs) <- tails l, b <- bs] | |
-- A list of all NxN board positions | |
boardPositions n = [(x, y) | x <- [0 .. n - 1], y <- [0 .. n - 1]] | |
-- Find all distinct combinations for n positions | |
boardCombinations n = pick n (boardPositions n) | |
checkSolution n combination = (valid, combination) | |
where | |
valid = not $ any (\x -> x) [queenLOS a b | (a : bs) <- tails combination, b <- bs] | |
findSolution n = Just solution | |
where | |
Just (_, solution) = find fst allSolutions | |
allSolutions = map (checkSolution n) $ boardCombinations n | |
queenLOS (x1, y1) (x2, y2) = (dx == 0) || (dy == 0) || ((abs dx) == (abs dy)) | |
where dx = x2 - x1 | |
dy = y2 - y1 | |
-- program | |
main :: IO () | |
main = do | |
let n = 7 -- Good luck going higher | |
let solution = fromJust $ findSolution n | |
showResult n solution | |
showResult :: Int -> [(Int, Int)] -> IO () | |
showResult n solution = do | |
let newLines = [0 .. ((n * n) - 1)] | |
let board = zipWith (\i p -> fmtTile i p) newLines $ boardPositions n | |
putStrLn $ join board | |
where fmtTile :: Int -> (Int, Int) -> String | |
fmtTile i xy | |
| (mod i n == 0) = "\n" ++ s | |
| otherwise = s | |
where s = if (elem xy solution) then "Q" else "-" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Data.Array.IArray (array, (//), (!)) | |
import Data.Array.Unboxed (UArray) | |
import Data.Set (Set, fromList, toList, delete) | |
import Control.Monad | |
-- contains sets of unoccupied columns and lookup arrays for both diagonals | |
data BoardState = BoardState (Set Int) (UArray Int Bool) (UArray Int Bool) | |
-- an empty board | |
board :: Int -> BoardState | |
board n | |
= BoardState (fromList [0 .. n-1]) (truearr 0 (2 * (n - 1))) (truearr (1 - n) (n - 1)) | |
where truearr a b = array (a, b) [(i, True) | i <- [a .. b]] | |
-- modify board state if queen gets placed | |
occupy :: BoardState -> (Int, Int) -> BoardState | |
occupy (BoardState c s d) (a,b) | |
= BoardState (delete b c) (tofalse s (a+b)) (tofalse d (a-b)) | |
where tofalse arr i = arr // [(i, False)] | |
-- get free fields in a row | |
freefields :: BoardState -> Int -> [(Int, Int)] | |
freefields (BoardState c s d) a = filter freediag candidates | |
where candidates = [(a,b) | b <- toList c] | |
freediag (a,b) = (s ! (a+b)) && (d ! (a-b)) | |
-- try all positions for a queen in row n-1 | |
place :: BoardState -> Int -> [[(Int, Int)]] | |
place _ 0 = [[]] | |
place b n = concatMap place_ (freefields b (n - 1)) | |
where place_ p = map (p:) (place (occupy b p) (n - 1)) | |
-- all possibilities to place n queens on a n*n board | |
queens :: Int -> [[(Int, Int)]] | |
queens n = place (board n) n | |
main = do | |
let n = 7 | |
let solutions = queens n | |
mapM_ (showResult n) solutions | |
-- A list of all NxN board positions | |
boardPositions n = [(x, y) | x <- [0 .. n - 1], y <- [0 .. n - 1]] | |
showResult :: Int -> [(Int, Int)] -> IO () | |
showResult n solution = do | |
let newLines = [0 .. ((n * n) - 1)] | |
let board = zipWith (\i p -> fmtTile i p) newLines $ boardPositions n | |
putStrLn $ join board | |
where fmtTile :: Int -> (Int, Int) -> String | |
fmtTile i xy | |
| (mod i n == 0) = "\n" ++ s | |
| otherwise = s | |
where s = if (elem xy solution) then " Q" else " -" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment