Theme: Grab Bag
Topic: Stocking Stuffers
Keywords: simulation design
Presenter | James Powell james@dutc.io |
Date | Friday, December 11, 2020 |
Time | 12:00 PM PST |
print("Let's go!")
The game ”fizzbuzz” is played as follows:
A sample game may look like this:
Task: Write a function that gives the first N values from such a game.
divisors = {
3: 'fizz',
5: 'buzz',
}
def fizzbuzz(n, divisors=divisors):
rv = []
for x in range(1, n+1):
terms = []
for div, term in divisors.items():
if x % div == 0:
terms.append(term)
rv.append((x, terms))
return rv
for num, word in fizzbuzz(10, divisors=divisors):
print(f'{"".join(word).upper() = }')
print(f'{num = :.2f}')
Task: Write a test for the above.
Task: Extend the above to generalise the divisors. e.g., allow the game to be played with ‘fizz’, ‘buzz’, and ‘quux’ for 3, 5, and 7.
Task: Write search
below to search within the fizzbuzz sequence for
particular patterns.
# NOTE: feel free to redesign the grammar for your pattern.
divisors = {
3: 'fizz',
5: 'buzz',
}
def fizzbuzz(n, divisors=divisors):
rv = []
for x in range(1, n+1):
terms = []
for div, term in divisors.items():
if x % div == 0 or str(div) in str(x):
terms.append(term)
rv.append((x, terms))
return rv
for num, terms in fizzbuzz(30):
print(f'{num = :<5} {"".join(terms) = }')
def search(pattern, *, max_value=100_000):
''' search for a pattern within the fizzbuzz sequence up to max_value
Use the following (sample) grammar:
- word: match this exact word (e.g., fizz, buzz, fizzbuzz)
- word₁|word₂: match word₁ OR word₂
- *: match any word
- #: match only a number
'''
pass
# look for four sequential plays that are
# fizz, then fizz OR buzz, then anything, then buzz
pattern = ['fizz', 'fizz|buzz', '*', 'fizzbuzz']
prev_val1 = None
prev_val2 = None
for curr_val in range(10):
if prev_val1 is None:
prev_val1, prev_val2 = prev_val2, curr_val
continue
if prev_val2 is None:
prev_val1, prev_val2 = prev_val2, curr_val
continue
print(f'{prev_val1, prev_val2, curr_val = }')
prev_val1, prev_val2 = prev_val2, curr_val
def pairs_of_three(xs):
return zip(xs, xs[1:], xs[2:])
for prev_val1, prev_val2, curr_val in pairs_of_three(range(10)):
print(f'{prev_val1, prev_val2, curr_val = }')
for x, y in zip('lion tiger bear'.split(), 'yellow orange brown'.split()):
print(f'{x, y = }')
xs = [0, 1, 2, 3, 4]
for x, y, z in zip(xs, xs[1:], xs[2:]):
print(f'{x, y, z = }')
from itertools import count
divisors = {
3: 'fizz',
5: 'buzz',
}
def fizzbuzz(divisors=divisors):
for num in count(1):
terms = []
for div, term in divisors.items():
if num % div == 0 or str(div) in str(num):
terms.append(term)
yield num, terms
from itertools import islice, tee
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
def search(pattern, *, max_value=100_000):
''' search for a pattern within the fizzbuzz sequence up to max_value
Use the following (sample) grammar:
- word: match this exact word (e.g., fizz, buzz, fizzbuzz)
- word₁|word₂: match word₁ OR word₂
- *: match any word
- #: match only a number
'''
for win in nwise(islice(fizzbuzz(), max_value), len(pattern)):
if all(p == ''.join(t) for p, (_, t) in zip(pattern, win)):
return win
# look for four sequential plays that are
# fizz, then fizz OR buzz, then anything, then buzz
pattern = ['buzz', 'buzz', 'fizzbuzz', 'buzz']
print(f'{search(pattern) = }')
The game “Rock, Paper, Scissors” is played as follows:
Task: write a function to evaluate the rules of the game.
# NOTE: for naming & design purposes, you may assume the players are directional
# i.e., `a` is the Player
# `b` is the Challenger
# e.g., `rules` could return "player wins" or "player loses"
# or it could "player wins" vs "challenger wins"
# QUESTION: how do you represent ties?
def rules(a, b):
''' return who wins, given shapes played by two players a and b '''
pass
Task: write a framework that can evaluate a strategy and play the game for 10,000 rounds given a pairing of strategies.
from random import choice
def random_strategy():
''' randomly select a shape '''
return choice(['rps'])
# other sample strategies…
# QUESTION: how do we track "history" here?
def beat_previous_play():
''' select the shape that would beat the opponent's previous play '''
pass
def most_common_play(n=3):
''' select the most common shape from the opponent's previous N plays '''
pass
games = [(random_strategy(), random_strategy()) for _ in range(10_000)]
results = [rules(a, b) for a, b in games]
# High-Card
# Two-of-a-Kind
# Three-of-a-Kind
# Full-House
# Straight
# Flush . . . . . all same suit
# Straight-Flush 10 9 8 7 6
# Royal-Flush A K Q J 10
from enum import Enum, auto
from functools import total_ordering
from collections import namedtuple, Counter
from itertools import product, islice, tee, combinations
from random import shuffle
@total_ordering
class Suits(Enum):
Clubs = 0
Diamonds = auto()
Hearts = auto()
Spades = auto()
def __lt__(self, other):
return self.value < other.value
def __eq__(self, other):
return self.value == other.value
def __hash__(self):
return hash(self.value)
def __str__(self):
return ['\N{black club suit}',
'\N{black diamond suit}',
'\N{black spade suit}',
'\N{black heart suit}',][self.value]
@total_ordering
class Faces(Enum):
Two = 0
Three = auto()
Four = auto()
Five = auto()
Six = auto()
Seven = auto()
Eight = auto()
Nine = auto()
Ten = auto()
Jack = auto()
Queen = auto()
King = auto()
Ace = auto()
def __lt__(self, other):
return self.value < other.value
def __eq__(self, other):
return self.value == other.value
def __hash__(self):
return hash(self.value)
def __str__(self):
return '2 3 4 5 6 7 8 9 10 J Q K A'.split()[self.value]
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
class HandType(namedtuple('HandType', 'predicate')):
__call__ = lambda s, *a, **kw: s.predicate(*a, **kw)
@total_ordering
class Hands(Enum):
RoyalFlush = HandType(
lambda cs: Hands.StraightFlush(cs) and max(cs, key=lambda c: c.face).face == Faces.Ace
)
StraightFlush = HandType(
lambda cs: Hands.Straight(cs) and Hands.Flush(cs)
)
Flush = HandType(
lambda cs: Counter(c.suit for c in cs).most_common(1)[0][-1] == 5
)
FourOfAKind = HandType(
lambda cs: Counter(c.face for c in cs).most_common(1)[0][-1] == 4
)
Straight = HandType(
lambda cs: all(y.face.value - x.face.value == 1
for x, y in nwise(sorted(cs, key=lambda c: c.face)))
)
FullHouse = HandType(
lambda cs: [c for _, c in Counter(c.face for c in cs).most_common(2)] == [3, 2]
)
TwoPair = HandType(
lambda cs: [c for _, c in Counter(c.face for c in cs).most_common(2)] == [2, 2]
)
ThreeOfAKind = HandType(
lambda cs: Counter(c.face for c in cs).most_common(1)[0][-1] == 3
)
TwoOfAKind = HandType(
lambda cs: Counter(c.face for c in cs).most_common(1)[0][-1] == 2
)
HighCard = HandType(
lambda cs: True
)
def __call__(self, *args, **kwargs):
return self.value(*args, **kwargs)
def __lt__(self, other):
return self.value < other.value
def __eq__(self, other):
return self.value == other.value
def __hash__(self):
return hash(self.value)
class Card(namedtuple('Card', 'face suit')):
def __str__(self):
return f'{self.face!s}{self.suit!s}'
class Hand(namedtuple('HandBase', 'cards best_hand')):
@classmethod
def from_cards(cls, *cards):
best_hand = [hand_type for hand_type in Hands if hand_type(cards)]
return cls(cards, best_hand)
deck = [Card(f, s) for f, s in product(Faces, Suits)]
shuffle(deck)
flop = [deck.pop(), deck.pop(), deck.pop()]
megan_hand = [deck.pop(), deck.pop()]
lauren_hand = [deck.pop(), deck.pop()]
turn = [deck.pop()]
river = [deck.pop()]
h = [*flop, *lauren_hand, *turn, *river]
print(f'{Hand.from_cards(*flop, *lauren_hand, *turn, *river).best_hand = }')
# from contextlib import contextmanager
# from time import perf_counter
# @contextmanager
# def timed():
# try:
# start = perf_counter()
# yield
# finally:
# stop = perf_counter()
# print(f'Elapsed (\N{greek capital letter delta}t): {stop - start:.2f}s')
# with timed():
# all_possible_hands = Counter(Hand.from_cards(*cs).best_hand
# for cs in islice(combinations(deck, 5), 10_000_000))
# all_possible_hands = Counter()
# for idx, cs in islice(combinations(deck, 5)):
# all_possible_hands[Hand.from_cards(*cs).best_hand] += 1
# print(f'{all_possible_hands = }')
We have two datafiles in “Portable Game Notation” representing a series of chess games played in recent years.
We will use a third-party toolkit python-chess to load these and generate simpler files for our consumption.
NOTE: you do not need to install python-chess
for this exercise!
from chess import WHITE, BLACK, PAWN, ROOK, KNIGHT, BISHOP, QUEEN, KING
from chess.pgn import read_game
from itertools import product
from collections import namedtuple
from enum import Enum
from pathlib import Path
class Colors(Enum):
Black = BLACK
White = WHITE
class Pieces(Enum):
Pawn = PAWN
Rook = ROOK
Knight = KNIGHT
Bishop = BISHOP
Queen = QUEEN
King = KING
if __name__ == '__main__':
for input_filename in (Path(x) for x in {'carlsen.pgn', 'croatian.pgn'}):
output_filename = input_filename.with_suffix('.csv')
games = []
with open(input_filename) as f:
while (g := read_game(f)):
games.append(g)
class Row(namedtuple('RowBase', 'game move color piece positions')):
def to_csv(self):
return f'{self.game}, {self.move}, {self.color.name}, {self.piece.name}, {", ".join(f"{x}" for x in self.positions)}\n'
rows = []
for game_no, g in enumerate(games):
b = g.board()
for move_no, m in enumerate(g.mainline_moves()):
b.push(m)
for col, pc in product(Colors, Pieces):
if b.pieces(pc.value, col.value):
r = Row(game_no, move_no, col, pc, [*b.pieces(pc.value, col.value)])
rows.append(r)
with open(output_filename, 'w') as f:
for r in rows:
if r.positions:
f.write(r.to_csv())
These files contain information about these games in the following CSV format (with no header):
[0, 64)
where position 63 is the rightmost Black Rook and position 0 is the leftmost White Rook)Read in the above data using pandas
, numpy
, or xarray
(or some combination thereof) and determine the following things:
from pandas import read_csv
df = read_csv('carlsen.csv',
header=None,
names='game move side piece pos0 pos1 pos2 pos3 pos4 pos5 pos6 pos7'.split())
print(df.sample(3))