Skip to content

Instantly share code, notes, and snippets.

@dcousens
Last active September 29, 2016 15:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dcousens/fb51f9f2196cd6399135 to your computer and use it in GitHub Desktop.
Save dcousens/fb51f9f2196cd6399135 to your computer and use it in GitHub Desktop.
Adapted from a StackOverflow answer
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()
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 "-"
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