├── tests ├── __init__.py ├── test_hand.py ├── test_shoe.py ├── test_card.py ├── test_scoreboard.py └── test_ties.py ├── pybaccarat ├── __init__.py ├── compute_baccarat_odds.py ├── baccaratsystems.py ├── baccarat.py └── playingcards.py ├── requirements.txt ├── MANIFEST.in ├── pybaccarat.dox ├── LICENSE ├── .gitignore ├── setup.py ├── play_baccarat_interactive.py └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pybaccarat/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | getch 3 | readchar 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include docs/html/* 3 | include docs/html/search/* 4 | include bin/* 5 | include .gitignore 6 | include LICENSE 7 | include pybaccarat.dox 8 | include tests/* -------------------------------------------------------------------------------- /pybaccarat.dox: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = "PyBaccarat" 2 | INPUT_DIRECTORY = pybaccarat 3 | PROJECT_BRIEF = "visit the ministry of silly walks" 4 | OPTIMIZE_OUTPUT_JAVA = YES 5 | RECURSIVE = YES 6 | GENERATE_LATEX = NO 7 | FILE_PATTERNS = *.py 8 | OUTPUT_DIRECTORY = docs 9 | EXTRACT_PACKAGE = YES 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-8 George L Fulk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # git and IDEs (idea=PyCharm vs=VisualStudio) 104 | .git/ 105 | .idea/ 106 | .vs/ 107 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | try: 4 | from setuptools import setup 5 | except ImportError: 6 | from distutils.core import setup 7 | #-- 8 | # put our dev version of pybaccarat ahead of installed copies 9 | import os,sys,unittest 10 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))[:-6]) #=dev first 11 | from pybaccarat.baccarat import __version__ 12 | #-- 13 | 14 | 15 | def readme(filespec): 16 | '''Utility to return readme contents for long description''' 17 | with open(filespec) as f: 18 | return f.read() 19 | 20 | setup( 21 | name='pybaccarat', 22 | version=__version__, 23 | url='https://github.com/fulkgl/PyBaccarat', 24 | download_url='https://pypi.python.org/packages/source/P/pybaccarat/'+\ 25 | 'pybaccarat-%s.tar.gz' % __version__, 26 | description='Play the card game Baccarat', 27 | long_description=readme('README.md'), 28 | author='George L Fulk', 29 | author_email='fulkgl@gmail.com', 30 | maintainer='George L Fulk', 31 | maintainer_email='fulkgl@gmail.com', 32 | license='MIT', 33 | packages=['pybaccarat'], 34 | keywords=['baccarat','game','playing cards','cards','card game',], 35 | scripts=['play_baccarat_interactive.py',], 36 | zip_safe=False, 37 | classifiers=[ 38 | 'Development Status :: 3 - Alpha', 39 | #Development Status :: 4 - Beta', 40 | #Development Status :: 5 - Production/Stable', 41 | 'Environment :: Console', 42 | 'License :: OSI Approved :: MIT License', 43 | 'Natural Language :: English', 44 | 'Operating System :: OS Independent', 45 | 'Programming Language :: Python', 46 | 'Programming Language :: Python :: 2', 47 | 'Programming Language :: Python :: 2.7', 48 | 'Programming Language :: Python :: 3', 49 | 'Programming Language :: Python :: 3.6', 50 | 'Programming Language :: Python :: 3.7', 51 | 'Topic :: Games/Entertainment', 52 | 'Topic :: Software Development :: Libraries :: Python Modules', 53 | ], 54 | # create an executable on target install 55 | # play_baccarat [name of executable on target to create] 56 | # play_baccarat_interactive [name.py to execute] 57 | # play [play() function to call] 58 | entry_points={'console_scripts':[ 59 | 'play_baccarat = play_baccarat_interactive:play',],}, 60 | ) 61 | -------------------------------------------------------------------------------- /play_baccarat_interactive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import argparse 4 | from pybaccarat.playingcards import Shoe 5 | from pybaccarat.baccarat import Game 6 | from pybaccarat.baccaratsystems import Interactive, JustBoards 7 | 8 | 9 | def just_boards(): 10 | print("*** JustBoards ***") 11 | shoe = Shoe(8) 12 | shoe.shuffle() 13 | Game(shoe=shoe, system=JustBoards()).play() 14 | 15 | def play(shoe=None): 16 | print("Interactive play a normal single game of baccarat.") 17 | print("Press the P key for player, B for banker, Ctrl-C for fast exit.") 18 | print("Press ESC to skip to the end of the shoe. Spacebar for no bet hand.") 19 | print("Press 1 through 5 then P or B to specify the P or B size.") 20 | print("") 21 | Game(shoe=shoe, system=Interactive()).play() 22 | 23 | def str2bool(v): 24 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 25 | return True 26 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 27 | return False 28 | else: 29 | raise argparse.ArgumentTypeError('Boolean value expected.') 30 | 31 | if __name__ == "__main__": 32 | # command line entry point 33 | 34 | parser = argparse.ArgumentParser("Play a game of Baccarat interactively") 35 | parser.add_argument("--create", dest="create_filespec", 36 | help="instead of playing just create and save a random shoe") 37 | parser.add_argument("--use", dest="use_filespec", 38 | help="use a saved shoe instead of random generation") 39 | parser.add_argument("--just_boards", type=str2bool, nargs='?', const=True, 40 | default=False, 41 | help="Just use the program to display board results") 42 | args = parser.parse_args() 43 | 44 | if args.use_filespec is not None: 45 | if args.create_filespec is not None: 46 | raise ValueError("can not use both --create and --use at same time") 47 | # --use creates an empty shoe, then fill from a saved file 48 | shoe = Shoe(0) 49 | shoe.load_shoe(args.use_filespec) 50 | play(shoe) 51 | else: 52 | # generate a new random shoe 53 | if args.create_filespec is not None: 54 | shoe = Shoe(8) 55 | shoe.shuffle() 56 | # --create saves the new shoe and exists 57 | shoe.save_shoe(args.create_filespec) 58 | shoe = None 59 | else: 60 | if args.just_boards: 61 | just_boards() 62 | else: 63 | play() 64 | 65 | # END # 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyBaccarat 2 | Play the card game Baccarat 3 | 4 | To check out a copy of the source: 5 | git clone https://github.com/fulkgl/PyBaccarat.git 6 | 7 | This is an implementation of the card game Baccarat. It includes the code 8 | needed to play a game interactively, replay a historical game, or play a 9 | game with a particular system. 10 | 11 | This code has been built and tested with these versions: 12 | Python 2.7.13 13 | Python 3.6.4 14 | 15 |

Updating this code

16 |
    17 |
  1. GIT (source control library). Check out a copy of the code. 18 |
    To keep the origin and structure of the code obvious, I start 19 | with a $BASE (%BASE%) location. It can be a location of your choosing. 20 | Assign an environment variable to define the starting base location: 21 | in Linux export BASE=/home/george or Windows 22 | set BASE=\users\george. Windows will use %BASE% 23 | instead of $BASE and the back-slash for directory 24 | separator instead of forward slash. 25 |
    1. 26 |
    2. mkdir $BASE/github.com/fulkgl
    3. 27 |
    4. chdir $BASE/github.com/fulkgl
    5. 28 |
    6. git clone https://github.com/fulkgl/PyBaccarat.git
    7. 29 |
    30 | 31 |
    The command git clone https://github.com/fulkgl/PyBaccarat.git 32 | will make a copy of code from a remote host to local system, 33 | setting up 3 trees (working directory, index, and head). 34 | What you normally see is the working directory. The index and head 35 | trees are inside the .git directory. And you don't normally look at them. 36 |
    git status
    Shows you files missing from git and files 37 | that are changed. 38 |
    git diff README.md
    Git diff will show you 39 | changed files differences. 40 |
    git add README.md
    Use the git add 41 | command to move changes from the working directory to the index. 42 | Files that are new to the repo are initially checked in with 43 | the git add command. Files that have been changed are also 44 | checked in this way. You often use git add one file at a time since 45 | you specify each file with this command. Use "git reset file" 46 | to reverse the effects of a git add command. 47 |
    git commit -m "description"
    Use the git commit 48 | command to move changes from the index to the head. 49 | You will do this for all the git added files once. 50 | Use "git checkout" to reverse the effects of a git commit. 51 | The description should contain a reference to the defect or feature 52 | number that is addressing this code change. 53 |
    git push origin master
    Use the git push command to 54 | move changes from the head to the remote host. 55 |
    git init is used to create a new repo the first time. 56 | Also a git remote add origin is used the first time to 57 | associate a connection to the remote host. 58 |
    browser https://github.com/fulkgl/PyBaccarat 59 |
    See that the latest things look correct on the webpage. 60 | 61 |
  2. Clean up temp files.
  3. 62 |
      63 |
    1. del /s/q build dist pybaccarat.egg-info
    2. 64 |
    3. rmdir /s/q build dist pybaccarat.egg-info
    4. 65 |
    5. del /s/q tests\*.pyc pybaccarat\*.pyc
    6. 66 |
    7. rmdir pybaccarat\__pycache__
    8. 67 |
    68 |
  4. Build the code
  5. 69 |
    python setup.py build bdist sdist 70 |
  6. Run the unit tests
  7. 71 |
    72 | cd %BASE%\github.com\fulkgl\PyBaccarat
    73 | python tests\test_card.py
    74 | python tests\test_hand.py
    75 | python tests\test_scoreboard.py
    76 | python tests\test_shoe.py
    77 | python tests\test_ties.py
    78 | 
    79 | 83 |
84 | 85 | 97 | -------------------------------------------------------------------------------- /tests/test_hand.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """! 4 | Unit test for the Hand class. 5 | 6 | To execute the unit test from base dir location, enter: 7 | @code 8 | python tests\test_hand.py [-v] 9 | python tests\test_card.py TestHand.test_constructor 10 | @endcode 11 | 12 | self.assertIsNone(obj, "msg") 13 | self.assertIsNotNone(obj, "msg") 14 | self.assertTrue(boolean, "msg") 15 | self.assertFalse(boolean, "msg") 16 | self.assertEqual(expect, actual, "msg") 17 | self.assertNotEqual(expect, actual, "msg") 18 | with self.assertRaises(ValueError): causes_exception 19 | self.fail("msg") 20 | 21 | @author fulkgl@gmail.com 22 | """ 23 | 24 | import os,sys,unittest 25 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))[:-6]) #=dev first 26 | from pybaccarat.baccarat import Hand 27 | from pybaccarat.playingcards import Card,Shoe 28 | 29 | 30 | class TestHand(unittest.TestCase): 31 | ''' 32 | Unit test for the baccarat.Hand class. 33 | ''' 34 | 35 | def test_constructor(self): 36 | ''' 37 | test hand construction 38 | ''' 39 | 40 | # good value 41 | player = Hand() 42 | self.assertIsNotNone(player, "normal") 43 | 44 | # bad 45 | # anything to test? 46 | 47 | def test_empty(self): 48 | ''' 49 | test empty method 50 | ''' 51 | player = Hand() 52 | player.empty() 53 | 54 | def test_add(self): 55 | ''' 56 | add(card) 57 | ''' 58 | player = Hand() 59 | card6s = Card(6, 's') 60 | self.assertEqual(0, player.value(), "no cards value 0") 61 | self.assertEqual("[]", str(player), "no cards") 62 | 63 | player.add(card6s) 64 | self.assertEqual(6, player.value(), "1 six = 6 value") 65 | self.assertEqual("[6s]", str(player), "add 6s") 66 | 67 | player.add(card6s) 68 | self.assertEqual(2, player.value(), "2 sixes = 2 value") 69 | self.assertEqual("[6s,6s]", str(player), "2 cards") 70 | 71 | player.add(card6s) 72 | self.assertEqual(8, player.value(), "3 sixes = 8 value") 73 | self.assertEqual("[6s,6s,6s]", str(player), "3 cards") 74 | 75 | # add an illegal 4th card 76 | expected = "too many cards in hand, can not add more" 77 | try: 78 | player.add(card6s) 79 | self.fail("should have failed adding 4th card") 80 | except ValueError as ex: 81 | self.assertEqual(expected, str(ex), "adding 4th card") 82 | 83 | def test_value(self): 84 | pass 85 | 86 | def test_need_hit(self): 87 | ''' 88 | need_hit() 89 | need_hit(player) 90 | ''' 91 | player = Hand() 92 | banker = Hand() 93 | card6s = Card(6, 's') 94 | card7h = Card(7, 'h') 95 | cardAd = Card(1, 'd') 96 | 97 | # normal player hit uses other=None 98 | player.add(card6s) 99 | player.add(card7h) 100 | self.assertTrue(player.need_hit(None), 'player 6+7 needs a hit') 101 | 102 | player.empty() 103 | player.add(card6s) 104 | player.add(cardAd) 105 | self.assertFalse(player.need_hit(None), 'player 6+A no hit') 106 | 107 | # normal banker must pass other=player 108 | player.empty() 109 | banker.empty() 110 | player.add(card6s) 111 | player.add(cardAd) 112 | banker.add(card6s) 113 | banker.add(card6s) 114 | self.assertTrue(banker.need_hit(player), 115 | 'banker 6+6 v player 6+A; hit') 116 | 117 | player.empty() 118 | banker.empty() 119 | player.add(card6s) 120 | player.add(card7h) 121 | player.add(cardAd) 122 | banker.add(card7h) 123 | banker.add(card7h) 124 | self.assertFalse(banker.need_hit(player), 'banker 4 v. P6+7+1; no hit') 125 | 126 | player.empty() 127 | banker.empty() 128 | player.add(card6s) 129 | player.add(card7h) 130 | player.add(cardAd) 131 | banker.add(card7h) 132 | banker.add(card6s) 133 | self.assertTrue(banker.need_hit(player), 'banker 3 v. P6+7+1; hit') 134 | 135 | def test_get_card(self): 136 | ''' 137 | get_card(int) 138 | ''' 139 | player = Hand() 140 | 141 | self.assertIsNone(player.get_card(0), 'empty hand') 142 | card6s = Card(6, 's') 143 | card7h = Card(7, 'h') 144 | cardAd = Card(1, 'd') 145 | player.add(card6s) 146 | player.add(card7h) 147 | player.add(cardAd) 148 | returned_card = player.get_card(2) 149 | self.assertEqual(1, returned_card.get_rank(), 'get back Ad') 150 | 151 | def test_str(self): 152 | pass 153 | 154 | 155 | # 156 | # Command line entry point 157 | # 158 | if __name__ == '__main__': 159 | unittest.main() 160 | -------------------------------------------------------------------------------- /tests/test_shoe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """! 4 | Unit test for the Shoe class. 5 | 6 | To execute the unit test from base dir location, enter: 7 | @code 8 | python tests\test_shoe.py [-v] 9 | @endcode 10 | 11 | self.assertIsNone(obj, "msg") 12 | self.assertIsNotNone(obj, "msg") 13 | self.assertTrue(boolean, "msg") 14 | self.assertFalse(boolean, "msg") 15 | self.assertEqual(expect, actual, "msg") 16 | self.assertNotEqual(expect, actual, "msg") 17 | with self.assertRaises(ValueError): causes_exception 18 | self.fail("msg") 19 | 20 | @author fulkgl@gmail.com 21 | """ 22 | 23 | import os,sys,unittest 24 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))[:-6]) #=dev first 25 | from pybaccarat.playingcards import Shoe 26 | from pybaccarat.playingcards import Card 27 | 28 | 29 | def delete_file(filespec): 30 | '''! 31 | Delete a file if it exists. 32 | @param filespec file to delete 33 | ''' 34 | if os.path.exists(filespec): 35 | os.remove(filespec) 36 | 37 | 38 | class TestShoe(unittest.TestCase): 39 | ''' 40 | unit test Shoe class 41 | ''' 42 | def test_constructor(self): 43 | ''' 44 | Shoe() 45 | Shoe(8) 46 | ''' 47 | # test good construction 48 | try: 49 | shoe = Shoe(8) 50 | except Exception as ex: 51 | self.fail("ex(%s)" % str(ex)) 52 | self.assertIsNotNone(shoe, "not none Shoe(8)") 53 | 54 | # test bad construction 55 | expected = "number_decks(8) invalid value(8)" 56 | try: 57 | shoe = Shoe("8") 58 | self.fail("expected a failure because of string param") 59 | except Exception as ex: 60 | actual = str(ex) 61 | self.assertEqual(expected, actual, 'msg Shoe("8")(%s)'%actual) 62 | 63 | # default constructor 64 | shoe = Shoe() 65 | self.assertIsNotNone(shoe, "default blank shoe exists") 66 | 67 | # custom shoe 68 | shoe = Shoe([Card(43), Card(44), Card(45)]) 69 | self.assertIsNotNone(shoe, "small custom 3 card shoe exists") 70 | 71 | def test_reset(self): 72 | ''' 73 | test method reset() 74 | ''' 75 | shoe = Shoe(8) 76 | self.assertTrue(shoe.cut_card_seen(), "new shoe cut card 0") 77 | shoe.set_cut_card(1) 78 | self.assertFalse(shoe.cut_card_seen(), "new cut card 1") 79 | shoe.reset() 80 | self.assertTrue(shoe.cut_card_seen(), "after reset back to 0") 81 | 82 | def test_shuffle(self): 83 | ''' 84 | test method shuffle() 85 | ''' 86 | shoe = Shoe(8) 87 | self.assertEqual("Ac", str(shoe.deal()), "no shuffle Ace clubs first") 88 | shoe.reset() 89 | expected_clubs = "Ac2c3c4c5c6c7c8c9cTcJcQcKc" 90 | cards = "" 91 | for _ in range(13): 92 | cards += str(shoe.deal()) 93 | self.assertEqual(expected_clubs, cards, "pre-shuffle") 94 | shoe.shuffle() 95 | cards = "" 96 | for _ in range(13): 97 | cards += str(shoe.deal()) 98 | self.assertNotEqual(expected_clubs, cards, "post-shuffle") 99 | 100 | def test_set_cut_card(self): 101 | ''' 102 | test method set_cut_card(int) 103 | ''' 104 | # good test 105 | shoe = Shoe(8) 106 | shoe.set_cut_card(-14) 107 | # bad test 108 | # too big 109 | expected = "cut card position value too big" 110 | try: 111 | shoe.set_cut_card(987) 112 | self.fail("expected failure set_cut_card(987)") 113 | except Exception as ex: 114 | self.assertEqual(expected, str(ex), "too big") 115 | # too small 116 | expected = "cut card position value too small" 117 | try: 118 | shoe.set_cut_card(-987) 119 | self.fail("expected failure set_cut_card(-987)") 120 | except Exception as ex: 121 | self.assertEqual(expected, str(ex), "too small") 122 | 123 | def test_cut_card_seen(self): 124 | '''! 125 | test method cut_card_seen() 126 | ''' 127 | shoe = Shoe(8) 128 | self.assertTrue(shoe.cut_card_seen(), "new shoe, yes") 129 | shoe.set_cut_card(1) 130 | self.assertFalse(shoe.cut_card_seen(), "position 1 no deal, no") 131 | card1 = shoe.deal() 132 | self.assertTrue(shoe.cut_card_seen(), "after 1 dealt, then yes") 133 | 134 | def test_deal(self): 135 | '''! 136 | test method deal() 137 | ''' 138 | shoe = Shoe(8) 139 | card1 = shoe.deal() 140 | # no shuffle so Ac should start us out 141 | self.assertEqual("Ac", str(card1), "no shuffle Ac first card") 142 | 143 | # create a short custom shoe to test running out of cards 144 | shoe2 = Shoe([Card(43), Card(44), Card(45), ]) 145 | # only 3 cards in this shoe2 146 | card1 = shoe2.deal() 147 | self.assertIsNotNone(card1, "first of shoe2") 148 | card1 = shoe2.deal() 149 | self.assertIsNotNone(card1, "second of shoe2") 150 | card1 = shoe2.deal() 151 | self.assertIsNotNone(card1, "third of shoe2") 152 | card1 = shoe2.deal() 153 | self.assertIsNone(card1, "fourth of shoe2 (empty)") 154 | 155 | def test_save_shoe(self): 156 | '''! 157 | test method save_shoe() 158 | ''' 159 | shoe = Shoe() #default single deck 160 | #no shuffle so we can test a simple single deck in order 161 | temp_file = os.sep + 'tmp' + os.sep + 'ut1.shoe' 162 | delete_file(temp_file) 163 | shoe.save_shoe(temp_file) 164 | expect = "Ac 2c \n"+\ 165 | "3c 4c 5c 6c 7c \n"+\ 166 | "8c 9c Tc Jc Qc \n"+\ 167 | "Kc Ad 2d 3d 4d \n"+\ 168 | "5d 6d 7d 8d 9d \n"+\ 169 | "Td Jd Qd Kd Ah \n"+\ 170 | "2h 3h 4h 5h 6h \n"+\ 171 | "7h 8h 9h Th Jh \n"+\ 172 | "Qh Kh As 2s 3s \n"+\ 173 | "4s 5s 6s 7s 8s 9s Ts Js Qs Ks \n" 174 | with open(temp_file, 'r') as f: 175 | actual = f.read() 176 | self.assertEqual(expect, actual, "saved single unshuffled deck") 177 | 178 | def test_load_shoe(self): 179 | pass 180 | 181 | 182 | # 183 | # Command line entry point 184 | # 185 | if __name__ == '__main__': 186 | unittest.main() 187 | -------------------------------------------------------------------------------- /tests/test_card.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """! 4 | Unit test for the Card class. 5 | 6 | To execute the unit test from base dir location, enter: 7 | @code 8 | python tests\test_card.py [-v] 9 | python tests\test_card.py TestCard.test_constructor 10 | @endcode 11 | 12 | self.assertIsNone(obj, "msg") 13 | self.assertIsNotNone(obj, "msg") 14 | self.assertTrue(boolean, "msg") 15 | self.assertFalse(boolean, "msg") 16 | self.assertEqual(expect, actual, "msg") 17 | self.assertNotEqual(expect, actual, "msg") 18 | with self.assertRaises(ValueError): causes_exception 19 | self.fail("msg") 20 | 21 | @author fulkgl@gmail.com 22 | """ 23 | 24 | import os,sys,unittest 25 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))[:-6]) #=dev first 26 | from pybaccarat.playingcards import Card 27 | 28 | 29 | class TestCard(unittest.TestCase): 30 | ''' 31 | Unit test for the Card class. 32 | ''' 33 | 34 | def test_constructor(self): 35 | ''' 36 | test card construction 37 | ''' 38 | 39 | # simple good value test 40 | c5s = Card(5, 's') 41 | self.assertIsNotNone(c5s, "normal 5s") 42 | 43 | # bad rank value 44 | with self.assertRaises(ValueError): 45 | cbad = Card(0, 's') # rank 0 46 | with self.assertRaises(ValueError): 47 | cbad = Card(0, 'S') # upper suit 48 | with self.assertRaises(ValueError): 49 | cbad = Card('s', 5) # swap suit,rank 50 | with self.assertRaises(ValueError): 51 | cbad = Card(5, 5) # 2 ranks 52 | with self.assertRaises(ValueError): 53 | cbad = Card('s', 's') # 2 suits 54 | with self.assertRaises(ValueError): 55 | cbad = Card(5, '') # suit length 0 56 | with self.assertRaises(ValueError): 57 | cbad = Card(None, None) # Null 58 | cbad = None 59 | 60 | try: 61 | cbad = Card('s', 5) 62 | self.fail("s5 should have thrown a ValueError but did not") 63 | except ValueError as ex: 64 | actual = str(ex) 65 | expected = "invalid syntax new_ordinal(s) suit(5)" 66 | self.assertEqual(expected, actual, "check msg actual(%s)" % actual) 67 | 68 | # bad suit value 69 | with self.assertRaises(ValueError): 70 | cbad = Card(5, 'Clubs') 71 | 72 | # interate through all legal 52 cards 73 | count = 0 74 | for suit in "cdhs": 75 | for rank in range(1, 14): 76 | count += 1 77 | card = Card(rank, suit) 78 | self.assertIsNotNone(card, "52 legeal cards") 79 | card = None 80 | self.assertEqual(52, count, "make sure we did all 52") 81 | 82 | # try some illegal ranks 83 | illegal = [-1, 0, 14] 84 | suit = 's' 85 | for rank in illegal: 86 | expected = "new_ordinal(%d) not in rank range 1..13" % rank 87 | try: 88 | cbad = Card(rank, suit) 89 | self.fail("bad should have thrown a ValueError but did not") 90 | except ValueError as ex: 91 | self.assertEqual(expected, str(ex), "check msg") 92 | 93 | # try the new 3rd constructor syntax 94 | c3good = Card("As") 95 | self.assertEqual("As", str(c3good), "normal As") 96 | c3good = Card("aS") 97 | self.assertEqual("As", str(c3good), "odd cases As") 98 | # try bad syntax 99 | try: 100 | c3bad = Card("bad") 101 | actual = "" 102 | except Exception as ex: 103 | actual = str(ex) 104 | self.assertEqual("new_ordinal(bad) not legel length 2 string", 105 | actual, "msg(%s)"%actual) 106 | try: 107 | c3bad = Card("cT") 108 | actual = "" 109 | except Exception as ex: 110 | actual = str(ex) 111 | self.assertEqual("illegal rank part of new_ordinal(cT)", 112 | actual, "msg(%s)"%actual) 113 | try: 114 | c3bad = Card("TT") 115 | actual = "" 116 | except Exception as ex: 117 | actual = str(ex) 118 | self.assertEqual("new_suit(t) not a legal value('cdhs')", 119 | actual, "msg(%s)"%actual) 120 | 121 | def test_getrank(self): 122 | ''' 123 | test get_rank method 124 | ''' 125 | rank = 5 126 | c5s = Card(rank, 's') 127 | self.assertEqual(rank, c5s.get_rank(), "rank 5 from card5s") 128 | 129 | def test_getsuit(self): 130 | ''' 131 | test get_suit method 132 | ''' 133 | suit = 'h' 134 | c5h = Card(5, suit) 135 | self.assertEqual(suit, c5h.get_suit(), "rank h from card5h") 136 | 137 | def test_ordinal(self): 138 | ''' 139 | test ordinal method 140 | ''' 141 | c5s = Card(5, 's') 142 | # spades are 4th suit (3*13) = +39 143 | # five rank is 5-1 within spades range 144 | self.assertEqual((39 + 5 - 1), c5s.get_ordinal(), "card5s ordinal") 145 | # Ac = 0 146 | # 2c = 1 147 | # ... 148 | # Kc = 12 149 | # Ad = 13 150 | # ... 151 | # Ah = 26 152 | # ... 153 | # As = 39 154 | # ... 155 | # Ks = 51 156 | 157 | def test_tostring(self): 158 | ''' 159 | test __str__ method 160 | ''' 161 | c5s = Card(5, 's') 162 | self.assertEqual('5s', str(c5s), "card5s toString") 163 | 164 | def test_eq(self): 165 | ''' 166 | test __eq__ method 167 | ''' 168 | card1 = Card(5, 's') 169 | card2 = Card(7, 'h') 170 | card3 = Card(5, 's') 171 | class CardChild1(Card): 172 | pass 173 | card4 = CardChild1(5, 's') 174 | card5 = CardChild1(7, 'h') 175 | card6 = Card(5, 's') 176 | 177 | # test 3 fundamental tests of same class EQ 178 | self.assertTrue(card1 == card1, "reflexive") 179 | self.assertTrue(card1 is card1, "same instance") 180 | 181 | self.assertFalse(card1 == card2, "false A==B") 182 | self.assertFalse(card2 == card1, "false B==A") 183 | self.assertFalse(card1 is card2, "not same instance 1") 184 | self.assertFalse(card2 is card1, "not same instance 2") 185 | 186 | self.assertTrue(card1 == card3, "2 diff but same value") 187 | self.assertTrue(card3 == card1, "opposite of 1==3") 188 | self.assertTrue(card1 == card6, "transative test1") 189 | self.assertTrue(card6 == card1, "T test2") 190 | self.assertTrue(card3 == card6, "T test3") 191 | self.assertTrue(card6 == card3, "T test4") 192 | self.assertFalse(card1 is card3, "not same 3") 193 | self.assertFalse(card1 is card6, "not same 6") 194 | # basic same class tests done 195 | 196 | # test Card class against a Card subclass 197 | self.assertTrue(card1 == card4, "child class same value") 198 | self.assertTrue(card4 == card1, "opp child class same") 199 | self.assertFalse(card1 == card5, "child class diff value") 200 | self.assertFalse(card5 == card1, "opp child class diff") 201 | self.assertFalse(card1 is card4, "not same 4") 202 | self.assertFalse(card1 is card5, "not same 5") 203 | 204 | # set tests 205 | set_test = set([card1, card3]) 206 | actual = len(set_test) 207 | self.assertEqual(1, actual, "card1 and 3 EQ actual(%d)" % actual) 208 | set_test2 = set([card1, card3, card6]) # card6 is from child class 209 | actual = len(set_test2) 210 | self.assertEqual(1, actual, "card1,3,6 actual(%d)" % actual) 211 | set_test3 = set([card1, card3, card6, card5]) 212 | actual = len(set_test3) 213 | self.assertEqual(2, actual, "card5 is unique") 214 | 215 | def test_ne(self): 216 | '''! 217 | test __ne__ method 218 | ''' 219 | card1 = Card(5, 's') 220 | card2 = Card(7, 'h') 221 | card3 = Card(5, 's') 222 | class CardChild1(Card): 223 | pass 224 | card4 = CardChild1(5, 's') 225 | card5 = CardChild1(7, 'h') 226 | card6 = Card(5, 's') 227 | 228 | # test 3 fundamental tests of same class EQ 229 | self.assertFalse(card1 != card1, "reflexive") 230 | self.assertTrue(card1 is card1, "same instance") 231 | 232 | self.assertTrue(card1 != card2, "false A==B") 233 | self.assertTrue(card2 != card1, "false B==A") 234 | self.assertFalse(card1 is card2, "not same instance 1") 235 | self.assertFalse(card2 is card1, "not same instance 2") 236 | 237 | self.assertFalse(card1 != card3, "2 diff but same value") 238 | self.assertFalse(card3 != card1, "opposite of 1==3") 239 | self.assertFalse(card1 != card6, "transative test1") 240 | self.assertFalse(card6 != card1, "T test2") 241 | self.assertFalse(card3 != card6, "T test3") 242 | self.assertFalse(card6 != card3, "T test4") 243 | self.assertFalse(card1 is card3, "not same 3") 244 | self.assertFalse(card1 is card6, "not same 6") 245 | # basic same class tests done 246 | 247 | # test Card class against a Card subclass 248 | self.assertFalse(card1 != card4, "child class same value") 249 | self.assertFalse(card4 != card1, "opp child class same") 250 | self.assertTrue(card1 != card5, "child class diff value") 251 | self.assertTrue(card5 != card1, "opp child class diff") 252 | self.assertFalse(card1 is card4, "not same 4") 253 | self.assertFalse(card1 is card5, "not same 5") 254 | 255 | def test_lt(self): 256 | ''' 257 | __lt__ 258 | ''' 259 | card1 = Card(5, 's') 260 | card2 = Card(7, 'h') 261 | try: 262 | result = card1 < card2 263 | self.fail("should not continue") 264 | except NotImplementedError as ex: 265 | self.assertEqual("LT does not have meaning, so not permitted", 266 | str(ex)) 267 | 268 | def test_le(self): 269 | ''' 270 | __le__ 271 | ''' 272 | card1 = Card(5, 's') 273 | card2 = Card(7, 'h') 274 | try: 275 | result = card1 <= card2 276 | self.fail("should not continue") 277 | except NotImplementedError as ex: 278 | self.assertEqual("LE does not have meaning, so not permitted", 279 | str(ex)) 280 | 281 | def test_gt(self): 282 | ''' 283 | __gt__ 284 | ''' 285 | card1 = Card(5, 's') 286 | card2 = Card(7, 'h') 287 | try: 288 | result = card1 > card2 289 | self.fail("should not continue") 290 | except NotImplementedError as ex: 291 | self.assertEqual("GT does not have meaning, so not permitted", 292 | str(ex)) 293 | 294 | def test_ge(self): 295 | ''' 296 | __ge__ 297 | ''' 298 | card1 = Card(5, 's') 299 | card2 = Card(7, 'h') 300 | try: 301 | result = card1 >= card2 302 | self.fail("should not continue") 303 | except NotImplementedError as ex: 304 | self.assertEqual("GE does not have meaning, so not permitted", 305 | str(ex)) 306 | 307 | def test_hash(self): 308 | ''' 309 | __hash__ 310 | ''' 311 | card1 = Card(5, 's') 312 | card2 = Card(7, 'h') 313 | card3 = Card(5, 's') 314 | h1 = card1.__hash__() 315 | # print("h1(%s)" % str(h1)) 316 | self.assertTrue(h1 == card3.__hash__(), "card 1 and 3 same value") 317 | self.assertFalse(h1 == card2.__hash__(), "card 1 and 2 diff") 318 | 319 | def test_inherit(self): 320 | ''' 321 | inheritted things from object: 322 | 323 | c = playingcards.Card(5,'s') 324 | dir(c) 325 | ['__delattr__', '__getattribute__', '__setattr__', '__new__', 326 | '__dict__', '__dir__', '__format__', '__init_subclass__', 327 | '__subclasshook__', '__reduce__', '__reduce_ex__', '__weakref__'] 328 | 329 | @todo inherit tests 330 | ''' 331 | # check namespace values 332 | c5s = Card(5, 's') 333 | class_actual = str(c5s.__class__) 334 | self.assertEqual("", 335 | class_actual, 336 | ".class(%s)" % class_actual) 337 | module_actual = str(c5s.__module__) 338 | self.assertEqual("pybaccarat.playingcards", module_actual, 339 | ".module(%s)" % module_actual) 340 | docstring = c5s.__doc__ 341 | self.assertTrue(isinstance(docstring, str), "docstring is string") 342 | self.assertTrue(0 < len(docstring), "some string of >0 exists") 343 | 344 | # __repr__ () 345 | # __sizeof__() 346 | 347 | # @todo usage examples 348 | 349 | 350 | # 351 | # Command line entry point 352 | # 353 | if __name__ == '__main__': 354 | unittest.main() 355 | -------------------------------------------------------------------------------- /pybaccarat/compute_baccarat_odds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | '''! 4 | Program to compute the odds for the game of Baccarat. 5 | 6 | @author George L Fulk 7 | ''' 8 | 9 | 10 | def bacc_value(num1, num2): 11 | '''! 12 | Compute the baccarat value with 2 inputed integer rank values (0..12). 13 | ''' 14 | if num1 > 9: 15 | num1 = 0 16 | if num2 > 9: 17 | num2 = 0 18 | num1 += num2 19 | if num1 > 9: 20 | num1 -= 10 21 | return num1 22 | 23 | 24 | def comma(number): 25 | '''! 26 | Convert an integer to comma seperated string. 27 | ''' 28 | str_int = "" 29 | sign = "" 30 | quo = number 31 | if number < 0: 32 | sign = '-' 33 | quo = -number 34 | while quo > 999: 35 | rem = quo % 1000 36 | str_int = ",%03d%s" % (rem, str_int) 37 | quo = quo // 1000 38 | return "%s%d%s" % (sign, quo, str_int) 39 | 40 | 41 | class ComputeBaccaratOdds(object): 42 | '''! 43 | Compute the odds for the game of Baccarat. 44 | ''' 45 | 46 | def __init__(self, number_decks=8): 47 | '''! 48 | Compute Baccarat odds for the given number of decks of cards. 49 | 50 | The range of valid number of decks is limited to 12. The 12 limit 51 | is an attempt to prevent attacks or bad coding using up resources. 52 | 53 | @param numberDecks Number of decks to initialized the odds. 54 | The range of valid value is 1 at a minimum up to 12. 55 | @throws java.lang.IllegalArgumentException 56 | Input arguement numberDecks is not valid. 57 | ''' 58 | # validate args 59 | if not isinstance(number_decks, int) or \ 60 | (number_decks < 0) or (number_decks > 12): 61 | raise ValueError("number_decks(%s) not a legal value" % 62 | str(number_decks)) 63 | 64 | # create the shoe 65 | self.saved_shoe = 13 * [4 * number_decks] 66 | 67 | # save the dragon table 68 | self.dragon_pay_table = 3 * [None] 69 | self.dragon_natural_win = 10 70 | self.dragon_natural_tie = 11 71 | # 0, 1, 2, 3, 4, 5, 6, 7, 8 , 9,nat,nT 72 | self.dragon_pay_table[1-1] = [-1, -1, -1, -1, 1, 2, 4, 6, 10, 30, 1, 0] 73 | self.dragon_pay_table[2-1] = [-1, -1, -1, -1, 1, 3, 4, 7, 8, 20, 1, 0] 74 | self.dragon_pay_table[3-1] = [-1, -1, -1, -1, 2, 2, 4, 4, 10, 30, 1, 0] 75 | # ^ ^ 76 | 77 | # Number of hand combinations that result in Banker,Player,Tie wins. 78 | self.count_banker = 0 79 | self.count_player = 0 80 | self.count_tie = 0 81 | self.count_naturals = 0 82 | self.count_pair = 0 83 | self.count_nonpair = 0 84 | self.count_banker_3card7 = 0 85 | self.count_player_3card8 = 0 86 | self.count_banker_dragon = [0, 0, 0] 87 | self.freq_banker_dragon = [0, 0, 0] 88 | self.count_player_dragon = [0, 0, 0] 89 | self.freq_player_dragon = [0, 0, 0] 90 | 91 | # perform the math computation 92 | self.recompute(self.saved_shoe) 93 | 94 | def record(self, value_banker, value_player, count, 95 | is_naturals=True, 96 | is_banker_3cards=False, 97 | is_player_3cards=False): 98 | '''! 99 | Record the results of a hand combination. 100 | ''' 101 | diff = value_banker - value_player 102 | if value_player < value_banker: 103 | # Banker wins 104 | self.count_banker += count 105 | if is_banker_3cards and value_banker == 7: 106 | self.count_banker_3card7 += count 107 | 108 | if is_naturals: # and not a tie 109 | diff = self.dragon_natural_win 110 | for table_num in range(3): # various dragon tables 111 | dragon_pays = self.dragon_pay_table[table_num][diff] 112 | self.count_banker_dragon[table_num] += count * dragon_pays 113 | if dragon_pays >= 0: 114 | self.freq_banker_dragon[table_num] += count 115 | self.count_player_dragon[table_num] += -count 116 | 117 | elif value_player > value_banker: 118 | # Player wins 119 | self.count_player += count 120 | if is_player_3cards and value_player == 8: 121 | self.count_player_3card8 += count 122 | 123 | diff = -diff 124 | if is_naturals: # and not a tie 125 | diff = self.dragon_natural_win 126 | for table_num in range(3): # various dragon tables 127 | dragon_pays = self.dragon_pay_table[table_num][diff] 128 | self.count_player_dragon[table_num] += count * dragon_pays 129 | if dragon_pays >= 0: 130 | self.freq_player_dragon[table_num] += count 131 | self.count_banker_dragon[table_num] += -count 132 | 133 | else: 134 | # Tie wins 135 | self.count_tie += count 136 | 137 | if is_naturals: 138 | diff = self.dragon_natural_tie 139 | # special case, table 3 counts the pushes 140 | self.freq_banker_dragon[3 - 1] += count 141 | self.freq_player_dragon[3 - 1] += count 142 | for table_num in range(3): # various dragon tables 143 | dragon_pays = self.dragon_pay_table[table_num][diff] 144 | self.count_player_dragon[table_num] += count * dragon_pays 145 | self.count_banker_dragon[table_num] += count * dragon_pays 146 | 147 | def not_naturals(self, value_p, value_b, shoe_size, shoe, count4): 148 | '''! 149 | Handle the not a naturals situation. Look for a third player and 150 | third banker situation. 151 | ''' 152 | # = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13] 153 | draw_table = [3, 4, 4, 5, 5, 6, 6, 2, 3, 3, 3, 3, 3, 3] 154 | 155 | if value_p <= 5: 156 | # Player hits 157 | for p3 in range(len(shoe)): 158 | if shoe[p3] != 0: 159 | if value_b <= draw_table[p3]: 160 | # Banker hits 161 | value_p3 = bacc_value(value_p, 1 + p3) 162 | count5 = count4 * shoe[p3] 163 | shoe[p3] -= 1 164 | for b3 in range(len(shoe)): 165 | if shoe[b3] != 0: 166 | count6 = count5 * shoe[b3] 167 | value_b3 = bacc_value(value_b, 1 + b3) 168 | self.record(value_b3, value_p3, count6, 169 | False, # not natural 170 | True, # 3 card banker 171 | True) # 3 card player 172 | shoe[p3] += 1 173 | else: 174 | # Banker stands 175 | count6 = count4 * shoe[p3] * (shoe_size - 1) 176 | value_p3 = bacc_value(value_p, 1 + p3) 177 | self.record(value_b, value_p3, count6, 178 | False, # not natural 179 | False, # not 3 card banker 180 | True) # player 3 cards 181 | else: 182 | # Player stands 183 | if value_b <= 5: 184 | # Banker hits 185 | for b3 in range(len(shoe)): 186 | if shoe[b3] != 0: 187 | value_b3 = bacc_value(value_b, 1 + b3) 188 | count6 = count4 * shoe[b3] * (shoe_size - 1) 189 | self.record(value_b3, value_p, count6, 190 | False, # not natural 191 | True, # 3 card banker 192 | False) # no 3 card player 193 | else: 194 | # Banker stands 195 | count6 = count4 * shoe_size * (shoe_size - 1) 196 | self.record(value_b, value_p, count6, False) # False=!natural 197 | 198 | def recompute(self, shoe): 199 | '''! 200 | Recompute the math for the given shoe contents. 201 | The 13 indexed values will represent the number of each of the 13 202 | cards in a suit. The shoe[0] is the number of aces, shoe[1] is the 203 | number of twos, et cetera. Up to shoe[12] is the number of Kings. 204 | @param shoe integer array of length 13 205 | ''' 206 | 207 | # validate shoe and compute it's size 208 | if not isinstance(shoe, list) or (len(shoe) != 13): 209 | raise ValueError("int[13] required") 210 | shoe_size = 0 211 | for i in shoe: 212 | if not isinstance(i, int) or (i < 0) or (i > 50): 213 | raise ValueError("shoe does not contain valid values") 214 | shoe_size += i 215 | 216 | # init the counts 217 | self.count_banker = 0 218 | self.count_player = 0 219 | self.count_tie = 0 220 | self.count_naturals = 0 221 | self.count_pair = 0 222 | self.count_nonpair = 0 223 | self.count_banker_3card7 = 0 224 | self.count_player_3card8 = 0 225 | self.count_banker_dragon = [0, 0, 0] 226 | self.count_player_dragon = [0, 0, 0] 227 | self.freq_banker_dragon = [0, 0, 0] 228 | self.freq_player_dragon = [0, 0, 0] 229 | 230 | # Loop through all possible card combinations 231 | for p1 in range(len(shoe)): 232 | if shoe[p1] > 0: 233 | count1 = shoe[p1] 234 | shoe[p1] -= 1 235 | shoe_size -= 1 236 | for b1 in range(len(shoe)): 237 | if shoe[b1] != 0: 238 | count2 = count1 * shoe[b1] 239 | shoe[b1] -= 1 240 | shoe_size -= 1 241 | for p2 in range(len(shoe)): 242 | if shoe[p2] != 0: 243 | count3 = count2 * shoe[p2] 244 | shoe[p2] -= 1 245 | shoe_size -= 1 246 | for b2 in range(len(shoe)): 247 | if shoe[b2] != 0: 248 | count4 = count3 * shoe[b2] 249 | shoe[b2] -= 1 250 | shoe_size -= 1 251 | # ----- 252 | # First 2 cards dealt to each side. 253 | # 254 | # count the pair side bet 255 | if p1 == p2: 256 | self.count_pair += count4 257 | else: 258 | self.count_nonpair += count4 259 | # 260 | value_p = bacc_value(1 + p1, 1 + p2) 261 | value_b = bacc_value(1 + b1, 1 + b2) 262 | if (value_p >= 8) or (value_b >= 8): 263 | count6 = count4 * shoe_size * \ 264 | (shoe_size - 1) 265 | self.record(value_b, value_p, 266 | count6) 267 | self.count_naturals += count6 268 | else: # not natural 269 | self.not_naturals(value_p, value_b, 270 | shoe_size, shoe, 271 | count4) 272 | # ----- 273 | shoe_size += 1 274 | shoe[b2] += 1 275 | # if b2 276 | # for b2= 277 | shoe_size += 1 278 | shoe[p2] += 1 279 | # if p2 280 | # for p2= 281 | shoe_size += 1 282 | shoe[b1] += 1 283 | # if b1 284 | # for b1= 285 | shoe_size += 1 286 | shoe[p1] += 1 287 | # if p1 288 | # for p1= 289 | 290 | def __str__(self): 291 | '''! 292 | Return the string representation of this object. 293 | @return String 294 | ''' 295 | output = [] 296 | total = self.count_banker + self.count_player + self.count_tie 297 | 298 | line = "%5s=%22s%8.4f%%%8.4f%%%+9.4f%%" % ( 299 | 'B', comma(self.count_banker), 300 | self.count_banker * 100.0 / total, 301 | self.count_banker * 100.0 / (self.count_banker + self.count_player), 302 | (self.count_banker * 0.95 - self.count_player) * 100.0 / total) 303 | output.append(line) 304 | 305 | line = "%5s=%22s%8.4f%%%8.4f%%%+9.4f%%" % ( 306 | 'P', comma(self.count_player), 307 | self.count_player * 100.0 / total, 308 | self.count_player * 100.0 / (self.count_banker + self.count_player), 309 | (self.count_player - self.count_banker) * 100.0 / total) 310 | output.append(line) 311 | 312 | line = "%5s=%22s%8.4f%%%8.4fx%+9.4f%%" % ( 313 | 'T', comma(self.count_tie), 314 | self.count_tie * 100.0 / total, 315 | total * 1.0 / self.count_tie, 316 | (self.count_tie * 8.0 - self.count_banker - self.count_player) * 317 | 100.0 / total) 318 | output.append(line) 319 | 320 | line = "total=%22s" % comma(total) 321 | output.append(line) 322 | 323 | line = " #nat=%22s%8.4f%% T9x%+6.3f%%" % ( 324 | comma(self.count_naturals), 325 | self.count_naturals * 100.0 / total, 326 | 100.0 * (self.count_tie * (2 + 8.0) - total) / total) 327 | output.append(line) 328 | 329 | line = "%5s=%22s%8.4f%%%8.4f%%%+9.4f%%" % ( 330 | 'EZ-B', comma(self.count_banker - self.count_banker_3card7), 331 | (self.count_banker - self.count_banker_3card7) * 100.0 / total, 332 | (self.count_banker - self.count_banker_3card7) * 100.0 / 333 | (self.count_banker + self.count_player), 334 | (self.count_banker - self.count_banker_3card7 - self.count_player) * 335 | 100.0 / total) 336 | output.append(line) 337 | 338 | line = "%5s=%22s%8.4f%%%8.4fx%+9.4f%%" % ( 339 | 'B3C7', comma(self.count_banker_3card7), 340 | self.count_banker_3card7 * 100.0 / total, 341 | total * 1.0 / self.count_banker_3card7, 342 | (self.count_banker_3card7 * (1 + 40.0) - total) * 100.0 / total) 343 | output.append(line) 344 | 345 | line = "%5s=%22s%8.4f%%%8.4fx%+9.4f%%" % ( 346 | 'P3C8', comma(self.count_player_3card8), 347 | self.count_player_3card8 * 100.0 / total, 348 | total * 1.0 / self.count_player_3card8, 349 | (self.count_player_3card8 * (1 + 25.0) - total) * 100.0 / total) 350 | output.append(line) 351 | 352 | for table_num in range(3): # various dragon tables 353 | comment = "" 354 | if table_num == 2: 355 | comment = "w/T" 356 | line = "%5s=%22s%8.4f%% %3s %+9.4f%%" % ( 357 | "DB%d" % (1 + table_num), 358 | comma(self.count_banker_dragon[table_num]), 359 | self.freq_banker_dragon[table_num] * 100.0 / total, 360 | comment, 361 | self.count_banker_dragon[table_num] * 100.0 / total) 362 | output.append(line) 363 | for table_num in range(3): # various dragon tables 364 | comment = "" 365 | if table_num == 2: 366 | comment = "w/T" 367 | line = "%5s=%22s%8.4f%% %3s %+9.4f%%" % ( 368 | "DP%d" % (1 + table_num), 369 | comma(self.count_player_dragon[table_num]), 370 | self.freq_player_dragon[table_num] * 100.0 / total, 371 | comment, 372 | self.count_player_dragon[table_num] * 100.0 / total) 373 | output.append(line) 374 | 375 | output.append("%5s=%14s /%15s%8.4fx%+9.4f%%" % ( 376 | 'pair', comma(self.count_pair), 377 | comma(self.count_pair + self.count_nonpair), 378 | self.count_nonpair * 1.0 / self.count_pair, 379 | (self.count_pair * 11.0 - self.count_nonpair) * 100.0 / 380 | (self.count_pair + self.count_nonpair))) 381 | 382 | return "\n".join(output) 383 | 384 | 385 | if __name__ == "__main__": 386 | # command line entry point 387 | ODDS = ComputeBaccaratOdds() 388 | print(ODDS) 389 | -------------------------------------------------------------------------------- /pybaccarat/baccaratsystems.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | '''! 4 | @package pybaccarat.baccaratsystems 5 | This module is collection of classes used with 6 | systems for playing the game Baccarat. 7 | @author fulkgl@gmail.com 8 | ''' 9 | 10 | import readchar 11 | 12 | 13 | class Dragon(object): 14 | '''! 15 | TBD 16 | ''' 17 | 18 | # -------------------------------------------------------------------- 19 | def __init__(self): 20 | '''! 21 | TBD 22 | ''' 23 | self.dragon_play = " " 24 | self.dragon_count = 0 25 | self.dragon_dict = {} 26 | self.hand_number = 0 27 | 28 | # -------------------------------------------------------------------- 29 | def update_count(self, rank): 30 | '''! 31 | Add these ranks to the count. 32 | @param rank int card rank's to add to system count 33 | ''' 34 | if (rank == 1) or (rank == 2) or (rank == 3): 35 | self.dragon_count += 1 36 | if (rank == 8) or (rank == 9): 37 | self.dragon_count -= 1 38 | 39 | # -------------------------------------------------------------------- 40 | def new_shoe(self, burn_cards): 41 | '''! 42 | Begin a new shoe. 43 | @param burn_cards Card 44 | ''' 45 | ##!< dragon_count used to count my system 46 | self.dragon_count = 0 47 | self.update_count(burn_cards[0].get_rank()) 48 | ##!< dragon_play is used for display out 49 | self.dragon_play = " " 50 | ##!< dragon_dict tracks the side bet difference results 51 | self.dragon_dict = { 52 | 'B9': 0, 'B8': 0, 'B7': 0, 'B6': 0, 'B5': 0, 'B4': 0, 'B3': 0, 53 | 'B2': 0, 'B1': 0, 'P9': 0, 'P8': 0, 'P7': 0, 'P6': 0, 'P5': 0, 54 | 'P4': 0, 'P3': 0, 'P2': 0, 'P1': 0, 'Bn': 0, 'Pn': 0, 'Tn': 0, 55 | 'T0': 0} 56 | 57 | # -------------------------------------------------------------------- 58 | def hand_pre(self): 59 | self.hand_number += 1 60 | return "" 61 | 62 | # -------------------------------------------------------------------- 63 | def hand_post(self, win_diff, player, banker): 64 | '''! 65 | TBD 66 | ''' 67 | # 68 | # 01 >25 =0+25 69 | # 11 >26 =1+25 70 | # 33 >28 =3 71 | # 66 >31 =6 72 | # hand_number 77 >32 h//11=7+25 73 | # 74 | threshold = 25 + (self.hand_number // 11) 75 | if threshold < self.dragon_count: 76 | self.dragon_play = " <<<" 77 | self.dragon_dict[win_diff] += 1 78 | else: 79 | self.dragon_play = " " 80 | 81 | # update count 82 | for i in range(3): 83 | card = player.get_card(i) 84 | if card is not None: 85 | self.update_count(card.get_rank()) 86 | card = banker.get_card(i) 87 | if card is not None: 88 | self.update_count(card.get_rank()) 89 | return "dragon=%s<%02d%s" % \ 90 | (str(threshold), self.dragon_count, self.dragon_play) 91 | 92 | # -------------------------------------------------------------------- 93 | def end_shoe(self): 94 | '''! 95 | TBD 96 | ''' 97 | dragon_p_money = 0 98 | dragon_b_money = 0 99 | dragon_plays = 0 100 | for k, v in self.dragon_dict.items(): 101 | dragon_plays += v 102 | if 'P4' == k: dragon_p_money += v*1 103 | elif 'P5' == k: dragon_p_money += v*2 104 | elif 'P6' == k: dragon_p_money += v*4 105 | elif 'P7' == k: dragon_p_money += v*6 106 | elif 'P8' == k: dragon_p_money += v*10 107 | elif 'P9' == k: dragon_p_money += v*30 108 | elif 'Pn' == k: dragon_p_money += v*1 109 | elif 'Tn' == k: dragon_p_money += v*0 110 | else: dragon_p_money -= v 111 | if 'B4' == k: dragon_b_money += v*1 112 | elif 'B5' == k: dragon_b_money += v*2 113 | elif 'B6' == k: dragon_b_money += v*4 114 | elif 'B7' == k: dragon_b_money += v*6 115 | elif 'B8' == k: dragon_b_money += v*10 116 | elif 'B9' == k: dragon_b_money += v*30 117 | elif 'Bn' == k: dragon_b_money += v*1 118 | elif 'Tn' == k: dragon_b_money += v*0 119 | else: dragon_b_money -= v 120 | 121 | out = "" 122 | if False: 123 | out += "Tn" + "=" + str(self.dragon_dict['Tn']) 124 | out += " Bn" + "=" + str(self.dragon_dict['Bn']) 125 | for i in range(1, 10): 126 | out += " B"+str(i)+"="+str(self.dragon_dict['B'+str(i)]) 127 | out += "\nT0" + "=" + str(self.dragon_dict['T0']) 128 | out += " Pn" + "=" + str(self.dragon_dict['Pn']) 129 | for i in range(1, 10): 130 | out += " P"+str(i)+"="+str(self.dragon_dict['P'+str(i)]) 131 | out += '\n' 132 | print(self.dragon_dict) 133 | out += "dragon_p_money(%d,%d/%d)" % \ 134 | (dragon_p_money, dragon_b_money, dragon_plays) 135 | return out 136 | # -------------------------------------------------------------------- 137 | 138 | 139 | class EZDragon(object): 140 | '''! 141 | Card count system for EZ-Baccarat Dragon side bet. EZ-Baccarat is the 142 | shuffle master commission free game. The Dragon side bet pays 40 to 1 143 | for a banker win of 3 card totalling 7. 144 | ''' 145 | 146 | # -------------------------------------------------------------------- 147 | def __init__(self): 148 | '''! 149 | TBD 150 | ''' 151 | self.play_w = 0 152 | self.play_l = 0 153 | self.count = 0 154 | self.hand_number = 0 155 | 156 | # -------------------------------------------------------------------- 157 | def add_count(self, card_rank): 158 | '''! 159 | Local routine to add a card to the count 160 | ''' 161 | debug = False 162 | if debug: 163 | print("rank(%d) count(%d)" % (card_rank, self.count)) 164 | if (4 <= card_rank) and (card_rank <= 7): 165 | self.count += 1 166 | if debug: 167 | print(" plus 1") 168 | if (card_rank == 8) or (card_rank == 9): 169 | self.count -= 2 170 | if debug: 171 | print(" minus 2") 172 | if debug: 173 | print(" count(%d)" % self.count) 174 | 175 | # -------------------------------------------------------------------- 176 | def new_shoe(self, burn_cards): 177 | '''! 178 | Start of a new shoe. Start the new count. The burn card is exposed 179 | so we can record that as an initial value. 180 | @param burn_rank is the rank of the burn card. Add it to 181 | the count. 182 | ''' 183 | self.count = 0 184 | self.add_count(burn_cards[0].get_rank()) 185 | self.play_w = 0 186 | self.play_l = 0 187 | 188 | # -------------------------------------------------------------------- 189 | def hand_pre(self): 190 | self.hand_number += 1 191 | return "" 192 | 193 | def hand_post(self, win_diff, player, banker): 194 | '''! 195 | This is the result of a hand being played. 196 | @param hand_number int 197 | @param win_diff TBD? win('BPT') and diff('n' or '0'..'9') 198 | @param player Hand player hand just played 199 | @param banker Hand banker hand just played 200 | @return String for display purposes 201 | ''' 202 | # save the count for later use 203 | this_hand_count = self.count 204 | # add all the cards just played into the count 205 | for i in range(3): 206 | card = banker.get_card(i) 207 | if card is not None: 208 | self.add_count(card.get_rank()) 209 | card = player.get_card(i) 210 | if card is not None: 211 | self.add_count(card.get_rank()) 212 | # who won 213 | if win_diff[0] == 'B': 214 | pass 215 | else: 216 | pass 217 | # return string 218 | true_count = this_hand_count 219 | # burn 8 220 | # 416-8-14 = 394 / 80hand = 5cards per hand + 1for burn 221 | # hand_number = n... (n+1)*5 222 | # tc = rc / decks_to_go 223 | # 1 deck_to_go=hand#74-0 = 70 tc4=10*rc(04)/10 224 | # 2 64-0 = 60 tc4=10*rc(08)/20 225 | # 3 54-1 = 50 tc4=10*rc(12)/30 226 | # 4 44-1 = 40 tc4=10*rc(16)/40 227 | # 5 34-2 = 30 20 228 | # 6 24-2 = 20 24 229 | # 7 14-3 = 10 28 230 | # 8 04-3 = 0 32 231 | # dtg(h) = h=0>>8 h=10>>7 232 | # dtg(h) = 8-(h/10) 233 | # tc = 10 * rc / (80-h) 234 | # cut 14 235 | 236 | result = '-' 237 | true_count = this_hand_count / (8.65 - self.hand_number/10.0) 238 | next_tc = (self.count) / (8.650 - (self.hand_number + 1.0)/10.0) 239 | if true_count >= 4.0: 240 | if (win_diff[0] == 'B') and (banker.value() == 7) and \ 241 | (banker.get_card(2) is not None): 242 | result = 'WIN' 243 | self.play_w += 1 244 | else: 245 | result = 'LOST' 246 | self.play_l += 1 247 | ret = "EZdrag rc(%d)tc(%.1f)next(%.1f)[%s]" % (this_hand_count, 248 | true_count, next_tc, 249 | result) 250 | return ret 251 | 252 | # -------------------------------------------------------------------- 253 | def end_shoe(self): 254 | '''! 255 | End of shoe. Show results. 256 | ''' 257 | print("end_shoe() EZDragon %dW-%dL" % (self.play_w, self.play_l)) 258 | 259 | # -------------------------------------------------------------------- 260 | 261 | 262 | class BaccSys(object): 263 | '''! 264 | Class that is used to define a Baccarat System. 265 | Children of this class will be individual class definitions for 266 | specific Baccarat Systems. 267 | This parent class will provide the framework for writing your own 268 | system. 269 | 270 | Normal operation would require a call to the parent class method first, 271 | then perform any extensions to that method. 272 | ''' 273 | def __init__(self, system_name="", forced_win=None): 274 | '''! 275 | Create a new baccarat system. 276 | @param system_name String name of the new baccarat system 277 | ''' 278 | self._reset() 279 | self.special_forced_win = False 280 | if forced_win == "JustBoards" and system_name == "JustBoards": 281 | self.special_forced_win = True 282 | self.name = system_name 283 | 284 | def new_shoe(self, burn_cards, boards=None, system_data={}): 285 | '''! 286 | Start a new shoe. 287 | @param burn_cards Card[] an array of cards that contain the burn 288 | cards from a new shoe. An element of None means that card was 289 | not exposed. 290 | @param boards Array an array of the display boards 291 | @param system_data Dict dictionary of data used by the baccarat 292 | system 293 | ''' 294 | self._reset() 295 | self.scoreboards = boards 296 | self.system_data = system_data 297 | self.WLseq = [[0,0]] 298 | return self.special_forced_win 299 | 300 | def _reset(self): 301 | '''! 302 | Internal use only. 303 | Reset the data elements within the baccarat system object. 304 | ''' 305 | self.hand_number = 0 306 | self.play_on = None 307 | self.play_size = 0 308 | self.won = 0 309 | self.lost = 0 310 | self.tied = 0 311 | self.money = 0.00 312 | self.scoreboards = None 313 | self.WLseq = [] 314 | self.last_WLT = "" 315 | self.quit_shoe = False 316 | 317 | def set_tie_object(self, tie_track): 318 | '''! 319 | ''' 320 | self.tie_tracker = tie_track 321 | 322 | def set_bpt_object(self, bpt): 323 | '''! 324 | ''' 325 | self.bpt_tracker = bpt 326 | 327 | def hand_pre(self): 328 | '''! 329 | Called prior to the play of a Hand. 330 | A baccarat system should assign any plays in this method. 331 | The return is a string for display purposes only. 332 | @return display string 333 | ''' 334 | self.hand_number = 1 335 | self.play_on = None 336 | self.play_size = 0 337 | return "" 338 | 339 | def result_won(self, amount): 340 | '''! 341 | A method called when a hand wins. 342 | ''' 343 | self.won += 1 344 | self.money += amount 345 | seq = self.WLseq 346 | if seq[-1][1] > 0: 347 | seq.append( [0,0] ) 348 | seq[-1][0] += 1 349 | self.WLseq = seq 350 | self.last_WLT = "W" 351 | 352 | def result_lost(self, amount): 353 | '''! 354 | A method called when a hand Losses. 355 | ''' 356 | self.lost += 1 357 | seq = self.WLseq 358 | seq[-1][1] += 1 359 | self.WLseq = seq 360 | self.money -= amount 361 | self.last_WLT = "L" 362 | 363 | def hand_post(self, win_diff, player_hand, banker_hand): 364 | '''! 365 | This method is called at the end of a Hand. 366 | A baccarat system can update their own results. 367 | @param win_diff String[2] A length 2 string containing the winning 368 | side ("B" or "P" or "T") and the difference between the winning 369 | and lossing sides. A 6-6 tie will have "T0". A 6-4 banker win 370 | will have "B2". A natural will use "n" for the difference. 371 | An 8-8 tie will have "Tn". A 9-8 banker will would have "Bn". 372 | @param player_hand Hand a pointer to the Hand object containing the 373 | cards for the players hand. For read-only purposes. 374 | @param banker_hand Hand a pointer to the Hand object containing the 375 | cards for the bankers hand. For read-only purposes. 376 | ''' 377 | # record results of a play 378 | if self.play_on is not None and self.play_size > 0: 379 | if self.play_on == "B": 380 | if win_diff[0] == "B": 381 | self.result_won(0.95 * self.play_size) 382 | elif win_diff[0] == "T": 383 | self.tied += 1 384 | self.last_WLT = "T" 385 | elif win_diff[0] == "P": 386 | self.result_lost(self.play_size) 387 | else: 388 | pass#unknown 389 | elif self.play_on == "P": 390 | if win_diff[0] == "P": 391 | self.result_won(self.play_size) 392 | elif win_diff[0] == "T": 393 | self.tied += 1 394 | self.last_WLT = "T" 395 | elif win_diff[0] == "B": 396 | self.result_lost(self.play_size) 397 | else: 398 | pass#unknown? 399 | elif self.play_on == "T": 400 | if win_diff[0] == "T": 401 | self.result_won(8.0 * self.play_size) 402 | elif win_diff[0] == "B": 403 | self.result_lost(self.play_size) 404 | elif win_diff[0] == "P": 405 | self.result_lost(self.play_size) 406 | else: 407 | pass#unknown? 408 | else: 409 | pass#unknown? 410 | # clear this last play 411 | self.play_on = None 412 | self.play_size = 0 413 | return "" 414 | 415 | def end_shoe(self): 416 | '''! 417 | Method called at the end of a shoe, after all hands have been 418 | played. The return string should give a summary of the baccarat 419 | system play. 420 | @return String display system results 421 | ''' 422 | return "Sys(%s) %d-%d-%d=%+.2f, %s" % (self.name,self.won, \ 423 | self.lost,self.tied,self.money,self.print_WLseq()) 424 | 425 | def opposite_side(self, side): 426 | '''! 427 | Worker method provided that will return the opposite of "side". 428 | @param side String "B" or "P". 429 | @return opposite of "side" or None 430 | ''' 431 | if side == "P": 432 | return "B" 433 | if side == "B": 434 | return "P" 435 | return None 436 | 437 | def play(self, side, size=1): 438 | '''! 439 | A method called during hand_pre() to assign a play. 440 | @param side String "B" or "P" or "T" 441 | @param size integer 442 | ''' 443 | self.play_on = side 444 | self.play_size = size 445 | return side 446 | 447 | def quit_this_shoe(self): 448 | '''! 449 | Mark this shoe as no more plays. 450 | ''' 451 | self.quit_shoe = True 452 | 453 | def print_WLseq(self): 454 | '''! 455 | Return a string for display purposes of the Win/Lose sequences. 456 | ''' 457 | seq = "" 458 | for i in self.WLseq: 459 | for j in i: 460 | seq += "0123456789abcdefghijklmnopqrstuvwxyz"[j] 461 | seq += " " 462 | return seq 463 | 464 | def get_keystroke(self): 465 | '''! 466 | A method that will get a keystroke and return it as an integer 467 | value. 468 | @return integer keystroke value 469 | ''' 470 | keystroke = 0 471 | #keystroke = ord(readchar.readkey()) 472 | getch = readchar.readkey() 473 | for o in getch: 474 | keystroke = keystroke*256 + ord(o) 475 | return keystroke 476 | 477 | class Interactive(BaccSys): 478 | '''! 479 | Play Baccarat with interactive selection of hand plays. 480 | Press the letter P to play player. 481 | Press the letter B to play banker. 482 | Press the enter key to make no play on a hand. 483 | Press the ESC key to skip to the end of the shoe. 484 | Press the Ctrl-C for emergency fast exit. 485 | Press numbers 1 or 2 or 3 or 4 or 5 before the P or B to increase size. 486 | For instance, press 3 then P will play 3 units on player. 487 | If no number is pressed it's assumed to be 1. 488 | ''' 489 | def __init__(self, system_name=""): 490 | return super(Interactive,self).__init__("interactive") 491 | def hand_pre(self): 492 | ''' 493 | We are given a chance to choose a play, prior to dealing cards. 494 | @return String description of play, such as "P" or "B". 495 | The returned string is for display purposes only. 496 | The tracking of the bet made is in the inheritted object 497 | play_on and play_size. 498 | ''' 499 | parent_ret = super(Interactive,self).hand_pre() 500 | my_size = 1 501 | while not self.quit_shoe: 502 | keystroke = self.get_keystroke() 503 | #getch = readchar.readkey() 504 | #keystroke = 0 505 | #for o in getch: 506 | # keystroke = keystroke*256 + ord(o) 507 | if chr(keystroke & 223) in ("P","B","T"): #uppercase 508 | return self.play(chr(keystroke & 223),my_size) #make a play 509 | elif chr(keystroke) in ("1","2","3","4","5"): #bet size 510 | my_size = keystroke - ord("0") 511 | elif keystroke==3: # ctrl-C raise exception 512 | raise ValueError("Ctrl-C request to end game now") 513 | elif keystroke==27 or keystroke==6939: # ESC,ESC+ESC quit shoe 514 | self.quit_this_shoe() 515 | elif keystroke==13 or keystroke==32: # enter or space no play 516 | return "" 517 | else: 518 | print("unhandled key(%s)(%d)" % (chr(keystroke),keystroke)) 519 | return "" 520 | 521 | 522 | class JustBoards(BaccSys): 523 | def __init__(self, system_name=""): 524 | return super(JustBoards,self).__init__("JustBoards", 525 | forced_win="JustBoards") 526 | def hand_pre(self): 527 | parent_ret = super(JustBoards,self).hand_pre() 528 | while not self.quit_shoe: 529 | #keystroke = self.get_keystroke() #get int keystroke 530 | #keystroke = ord(readchar.readkey()) 531 | keystroke = self.get_keystroke() 532 | if chr(keystroke & 223) in ("P","B","T"): #uppercase 533 | #return self.play(chr(keystroke & 223)) #make a play 534 | return chr(keystroke & 223) 535 | elif chr(keystroke & 223) in ("X"): 536 | return chr(keystroke & 223) 537 | elif keystroke==3: # ctrl-C raise exception 538 | raise ValueError("Ctrl-C request to end game now") 539 | elif keystroke==27: # ESC quit shoe 540 | self.quit_this_shoe() 541 | elif keystroke==13 or keystroke==32: # enter or space no play 542 | pass#return "" 543 | else: 544 | print("unhandled key(%s)(%d)" % (chr(keystroke),keystroke)) 545 | return "" 546 | 547 | 548 | class George1(BaccSys): 549 | ''' 550 | George bacc system 1. 551 | ''' 552 | def __init__(self, system_name=""): 553 | parent = super(George1,self).__init__("George1") #call parent 554 | self.George1_size = 3 #post 5 bet 2, post 6 bet 3 555 | return parent 556 | def hand_pre(self): 557 | ''' 558 | Call out to this method before each hand is played. 559 | Our system is responsible for making plays prior to 560 | the play of the hand by calling self.play("B",1). 561 | Where the "B" or "P" is the side and 1 is the size. 562 | Before our method is called the default is no play. 563 | This method returns a string used in the output for 564 | this hand. 565 | ''' 566 | parent = super(George1,self).hand_pre() #call parent 567 | # get the array for board #2 ie: [..., ['C', 5]] 568 | b2_array = self.scoreboards[2].get_array() 569 | if len(b2_array) < 2: 570 | return "" #not on the board yet" 571 | if b2_array[-1][1] < 5: 572 | return "" #R2 < 5" 573 | b0_array = self.scoreboards[0].get_array() 574 | arrayB = self.scoreboards[0].get_peek_B_array(b0_array) 575 | peekB2 = self.scoreboards[2].get_cs_mark(arrayB) 576 | len_R2 = b2_array[-1][1] 577 | if len_R2 in (5,6): 578 | long_R2 = b2_array[-1][0] 579 | # We had 5 long_R2 plays in a row. We now bet opposite. 580 | # peekB2 is the next play for Banker. 581 | if peekB2 == long_R2: 582 | self.play("P",len_R2-self.George1_size) 583 | else: 584 | self.play("B",len_R2-self.George1_size) 585 | self.George1_size = 4 586 | return ("playG%d"%len_R2)+long_R2+"(%s)"%peekB2 587 | #elif b2_array[-1][1] == 6: 588 | # return "playG6"+b2_array[-1][0]+"(B=%s)"%peekB2 589 | #return "noplayG7"+b2_array[-1][0] 590 | return "playG7" 591 | 592 | class ValSys(BaccSys): 593 | def hand_post(self, win_diff, p_hand, b_hand): 594 | parent_ret = super(ValSys,self).hand_post(win_diff,p_hand,b_hand) 595 | return " %s" % self.last_WLT 596 | def hand_pre(self): 597 | '''! 598 | ValSystem rules: 599 | 1. if board 2 last entry is in row 1, play chop else play same 600 | 2. overrides rule 1. If 4+ in a row on board 0, play same 601 | ''' 602 | parent_ret = super(ValSys,self).hand_pre() 603 | # 604 | if self.scoreboards is not None: 605 | b0_array = self.scoreboards[0].get_array() 606 | if len(b0_array) > 1 and b0_array[-1][1] >= 4: 607 | rule = 2 608 | self.play_on = b0_array[-1][0] 609 | self.play_size = 1 610 | return "val(%d)=%s" % (rule,self.play_on) 611 | else: 612 | b2_array = self.scoreboards[2].get_array() 613 | if len(b2_array) > 1: 614 | rule = 1 615 | side = b0_array[-1][0] 616 | if b2_array[-1][1] == 1: 617 | side = self.opposite_side(side) 618 | self.play_on = side 619 | self.play_size = 1 620 | return "val(%d)=%s" % (rule,self.play_on) 621 | # 622 | return "" 623 | 624 | def end_shoe(self): 625 | seq2 = "" 626 | for i in self.WLseq: 627 | for j in i: 628 | seq2 += "0123456789abcdefghij"[j] 629 | seq2 += " " 630 | return "EndSys(%s) %d-%d-%d=%+.2f, %s" % (self.name,self.won, \ 631 | self.lost,self.tied,self.money,seq2) 632 | 633 | # END 634 | -------------------------------------------------------------------------------- /tests/test_scoreboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """! 4 | Unit test for the Scoreboard class. 5 | 6 | To execute the unit test from base dir location, enter: 7 | @code 8 | python tests\test_scoreboard.py [-v] 9 | python tests\test_scoreboard.py TestScoreboard.test_version 10 | @endcode 11 | 12 | self.assertIsNone(obj, "msg") 13 | self.assertIsNotNone(obj, "msg") 14 | self.assertTrue(boolean, "msg") 15 | self.assertFalse(boolean, "msg") 16 | self.assertEqual(expect, actual, "msg") 17 | self.assertNotEqual(expect, actual, "msg") 18 | with self.assertRaises(ValueError): causes_exception 19 | self.fail("msg") 20 | 21 | @author fulkgl@gmail.com 22 | """ 23 | 24 | import os,sys,unittest 25 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))[:-6]) #=dev first 26 | from pybaccarat.baccarat import Scoreboard,__version__ 27 | from pybaccarat.playingcards import Card 28 | 29 | 30 | class TestScoreboard(unittest.TestCase): 31 | ''' 32 | Unit test for the Card class. 33 | ''' 34 | 35 | def test_version(self): 36 | ''' 37 | test the version 38 | ''' 39 | self.assertEqual(0.21, __version__, "test target module version") 40 | 41 | def test_constructor(self): 42 | ''' 43 | test card construction 44 | ''' 45 | pass 46 | 47 | def test_basics(self): 48 | # 1. empty board 49 | b0 = Scoreboard(0) 50 | empty_board = \ 51 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 52 | " 0\n"+\ 53 | " 0\n"+\ 54 | " 0\n"+\ 55 | " 0\n"+\ 56 | " 0\n"+\ 57 | " 0\n" 58 | actual = b0.print_lines() 59 | self.assertEqual(empty_board, actual, "check empty board") 60 | 61 | # 2. simple P mark 62 | b0.mark('P') 63 | board1 = \ 64 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 65 | "P 1\n"+\ 66 | " 0\n"+\ 67 | " 0\n"+\ 68 | " 0\n"+\ 69 | " 0\n"+\ 70 | " 0\n" 71 | actual = b0.print_lines() 72 | self.assertEqual(board1, actual, "basic P\n%s"%actual) 73 | 74 | # 3. simple P mark (total PP) 75 | b0.mark('P') 76 | board2 = \ 77 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 78 | "P 1\n"+\ 79 | "P 1\n"+\ 80 | " 0\n"+\ 81 | " 0\n"+\ 82 | " 0\n"+\ 83 | " 0\n" 84 | actual = b0.print_lines() 85 | self.assertEqual(board2, actual, "basic PP\n%s"%actual) 86 | 87 | # 4. PPB total marks 88 | b0.mark('B') 89 | board3 = \ 90 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 91 | "PB 2\n"+\ 92 | "P 1\n"+\ 93 | " 0\n"+\ 94 | " 0\n"+\ 95 | " 0\n"+\ 96 | " 0\n" 97 | actual = b0.print_lines() 98 | self.assertEqual(board3, actual, "basic PP B\n%s"%actual) 99 | 100 | # 5. PPBBBBB total marks (up to the 5th row on B) 101 | b0.mark('B') 102 | b0.mark('B') 103 | b0.mark('B') 104 | b0.mark('B') 105 | board4 = \ 106 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 107 | "PB 2\n"+\ 108 | "PB 2\n"+\ 109 | " B 1\n"+\ 110 | " B 1\n"+\ 111 | " B 1\n"+\ 112 | " 0\n" 113 | actual = b0.print_lines() 114 | self.assertEqual(board4, actual, "basic 2P 5B\n%s"%actual) 115 | 116 | # 6. 2P 6B up to the 6th row with B's 117 | b0.mark('B') 118 | board46 = \ 119 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 120 | "PB 2\n"+\ 121 | "PB 2\n"+\ 122 | " B 1\n"+\ 123 | " B 1\n"+\ 124 | " B 1\n"+\ 125 | " B 1\n" 126 | actual = b0.print_lines() 127 | self.assertEqual(board46, actual, "basic 2P 6B\n%s"%actual) 128 | 129 | # 7. 2P 7B first slide 130 | b0.mark('B') 131 | board5 = \ 132 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 133 | "PB 2\n"+\ 134 | "PB 2\n"+\ 135 | " B 1\n"+\ 136 | " B 1\n"+\ 137 | " B 1\n"+\ 138 | " BB 1\n" 139 | actual = b0.print_lines() 140 | self.assertEqual(board5, actual, "basic PP BBBBBB B\n%s"%actual) 141 | 142 | # 7. 2P 8B second slide 143 | b0.mark('B') 144 | board6 = \ 145 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 146 | "PB 2\n"+\ 147 | "PB 2\n"+\ 148 | " B 1\n"+\ 149 | " B 1\n"+\ 150 | " B 1\n"+\ 151 | " BBB 1\n" 152 | actual = b0.print_lines() 153 | self.assertEqual(board6, actual, "basic 2P 8B\n%s"%actual) 154 | 155 | def test_extreme_slide(self): 156 | ''' 157 | test the condition of slide go too far to the right 158 | ''' 159 | b0 = Scoreboard(0) 160 | empty_board = \ 161 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 162 | " 0\n"+\ 163 | " 0\n"+\ 164 | " 0\n"+\ 165 | " 0\n"+\ 166 | " 0\n"+\ 167 | " 0\n" 168 | actual = b0.print_lines() 169 | self.assertEqual(empty_board, actual, "check empty board") 170 | # mark 64 straight Players (slide to pre-max) 171 | for i in range(64): 172 | b0.mark('P') 173 | board1 = \ 174 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 175 | "P 1\n"+\ 176 | "P 1\n"+\ 177 | "P 1\n"+\ 178 | "P 1\n"+\ 179 | "P 1\n"+\ 180 | "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP 1\n" 181 | actual64 = b0.print_lines() 182 | self.assertEqual(board1, actual64, "64P, pre-max\n%s"%actual64) 183 | # mark the 65th player to reach max condition 184 | b0.mark('P') 185 | board2 = \ 186 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 187 | "P 1\n"+\ 188 | "P 1\n"+\ 189 | "P 1\n"+\ 190 | "P 1\n"+\ 191 | "P 1\n"+\ 192 | "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP 1\n" 193 | self.assertEqual(board2, b0.print_lines(), "65P max") 194 | # mark the 66th player to exceed max condition 195 | b0.mark('P') 196 | board3 = \ 197 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 198 | "P 1\n"+\ 199 | "P 1\n"+\ 200 | "P 1\n"+\ 201 | "P 1\n"+\ 202 | "P 1\n"+\ 203 | "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP> 1\n" 204 | actual = b0.print_lines() 205 | self.assertEqual(board3, actual, "66P beyond max\n%s"%actual) 206 | 207 | def test_9116_slide(self): 208 | ''' 209 | test a specific slide condition, 9116 210 | ''' 211 | b0 = Scoreboard(0) 212 | empty_board = \ 213 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 214 | " 0\n"+\ 215 | " 0\n"+\ 216 | " 0\n"+\ 217 | " 0\n"+\ 218 | " 0\n"+\ 219 | " 0\n" 220 | actual = b0.print_lines() 221 | self.assertEqual(empty_board, actual, "check empty board") 222 | # mark 9115 223 | for i in range(9): 224 | b0.mark('P') 225 | b0.mark('B') 226 | b0.mark('P') 227 | for i in range(5): 228 | b0.mark('B') 229 | board1 = \ 230 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 231 | "PBPB 4\n"+\ 232 | "P B 2\n"+\ 233 | "P B 2\n"+\ 234 | "P B 2\n"+\ 235 | "P B 2\n"+\ 236 | "PPPP 1\n" 237 | actual = b0.print_lines() 238 | self.assertEqual(board1, actual, "pre 9116\n%s\n%s"%(actual,board1)) 239 | # now add that 6th B 240 | b0.mark('B') 241 | board2 = \ 242 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 243 | "PBPB 4\n"+\ 244 | "P B 2\n"+\ 245 | "P B 2\n"+\ 246 | "P B 2\n"+\ 247 | "P BB 2\n"+\ 248 | "PPPP 2\n" 249 | actual = b0.print_lines() 250 | self.assertEqual(board2, actual, "after 9116\n%s"%actual) 251 | 252 | def test_91176_slide(self): 253 | ''' 254 | test a specific slide condition, 91176 255 | ''' 256 | b0 = Scoreboard(0) 257 | empty_board = \ 258 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 259 | " 0\n"+\ 260 | " 0\n"+\ 261 | " 0\n"+\ 262 | " 0\n"+\ 263 | " 0\n"+\ 264 | " 0\n" 265 | actual = b0.print_lines() 266 | self.assertEqual(empty_board, actual, "check empty board") 267 | # mark 9115 268 | for i in range(9): 269 | b0.mark('P') 270 | b0.mark('B') 271 | b0.mark('P') 272 | for i in range(5): 273 | b0.mark('B') 274 | board1 = \ 275 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 276 | "PBPB 4\n"+\ 277 | "P B 2\n"+\ 278 | "P B 2\n"+\ 279 | "P B 2\n"+\ 280 | "P B 2\n"+\ 281 | "PPPP 1\n" 282 | actual = b0.print_lines() 283 | self.assertEqual(board1, actual, "pre 9116\n%s\n%s"%(actual,board1)) 284 | # now add that 6th B 285 | b0.mark('B') 286 | board2 = \ 287 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 288 | "PBPB 4\n"+\ 289 | "P B 2\n"+\ 290 | "P B 2\n"+\ 291 | "P B 2\n"+\ 292 | "P BB 2\n"+\ 293 | "PPPP 2\n" 294 | actual = b0.print_lines() 295 | self.assertEqual(board2, actual, "after 9116\n%s"%actual) 296 | # now add that 7th B 297 | b0.mark('B') 298 | board3 = \ 299 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 300 | "PBPB 4\n"+\ 301 | "P B 2\n"+\ 302 | "P B 2\n"+\ 303 | "P B 2\n"+\ 304 | "P BBB 2\n"+\ 305 | "PPPP 2\n" 306 | actual = b0.print_lines() 307 | self.assertEqual(board3, actual, "after 9117\n%s"%actual) 308 | # 4 Ps 309 | for i in range(4): 310 | b0.mark('P') 311 | board4 = \ 312 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 313 | "PBPBP 5\n"+\ 314 | "P BP 3\n"+\ 315 | "P BP 3\n"+\ 316 | "P BP 3\n"+\ 317 | "P BBB 2\n"+\ 318 | "PPPP 2\n" 319 | actual = b0.print_lines() 320 | self.assertEqual(board4, actual, "after 91174\n%s"%actual) 321 | # 5th P 322 | b0.mark('P') 323 | board5 = \ 324 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 325 | "PBPBP 5\n"+\ 326 | "P BP 3\n"+\ 327 | "P BP 3\n"+\ 328 | "P BPP 3\n"+\ 329 | "P BBB 3\n"+\ 330 | "PPPP 2\n" 331 | actual = b0.print_lines() 332 | self.assertEqual(board5, actual, "after 91175\n%s"%actual) 333 | # 6th P 334 | b0.mark('P') 335 | board6 = \ 336 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 337 | "PBPBP 5\n"+\ 338 | "P BP 3\n"+\ 339 | "P BP 3\n"+\ 340 | "P BPPP 3\n"+\ 341 | "P BBB 3\n"+\ 342 | "PPPP 3\n" 343 | actual = b0.print_lines() 344 | self.assertEqual(board6, actual, "after 91176\n%s"%actual) 345 | # 7th P 346 | b0.mark('P') 347 | board7 = \ 348 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 349 | "PBPBP 5\n"+\ 350 | "P BP 3\n"+\ 351 | "P BP 3\n"+\ 352 | "P BPPPP 3\n"+\ 353 | "P BBB 3\n"+\ 354 | "PPPP 3\n" 355 | actual = b0.print_lines() 356 | self.assertEqual(board7, actual, "after 91177\n%s"%actual) 357 | 358 | def test_same_long_strings(self): 359 | ''' 360 | test the condition of 2 same type long strings in parallel and 361 | a possible collision. 362 | ''' 363 | b0 = Scoreboard(0) 364 | empty_board = \ 365 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 366 | " 0\n"+\ 367 | " 0\n"+\ 368 | " 0\n"+\ 369 | " 0\n"+\ 370 | " 0\n"+\ 371 | " 0\n" 372 | actual = b0.print_lines() 373 | self.assertEqual(empty_board, actual, "check empty board") 374 | # mark 6 straight P, check the resulting board 375 | for i in range(6): 376 | b0.mark('P') 377 | board1 = \ 378 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 379 | "P 1\n"+\ 380 | "P 1\n"+\ 381 | "P 1\n"+\ 382 | "P 1\n"+\ 383 | "P 1\n"+\ 384 | "P 1\n" 385 | self.assertEqual(board1, b0.print_lines(), "after first 6 players") 386 | for i in range(3): 387 | b0.mark('P') 388 | board2 = \ 389 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 390 | "P 1\n"+\ 391 | "P 1\n"+\ 392 | "P 1\n"+\ 393 | "P 1\n"+\ 394 | "P 1\n"+\ 395 | "PPPP 1\n" 396 | self.assertEqual(board2, b0.print_lines(), "after first 9 players") 397 | b0.mark('B') 398 | b0.mark('P') 399 | b0.mark('P') 400 | b0.mark('P') 401 | b0.mark('P') 402 | # B, then 4 P. Pre-collision condition 403 | board2 = \ 404 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 405 | "PBP 3\n"+\ 406 | "P P 2\n"+\ 407 | "P P 2\n"+\ 408 | "P P 2\n"+\ 409 | "P 1\n"+\ 410 | "PPPP 1\n" 411 | self.assertEqual(board2, b0.print_lines(), "pre-collision condition") 412 | # run the collision mark 413 | b0.mark('P') 414 | board2 = \ 415 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 416 | "PBP 3\n"+\ 417 | "P P 2\n"+\ 418 | "P P 2\n"+\ 419 | "P P 2\n"+\ 420 | "P P 2\n"+\ 421 | "PP=P 1\n" 422 | self.assertEqual(board2, b0.print_lines(), "collision") 423 | 424 | def test_getarray(self): 425 | ''' 426 | test get_rank method 427 | ''' 428 | pass 429 | 430 | def test_getseq(self): 431 | ''' 432 | test getseq() method 433 | ''' 434 | pass 435 | 436 | def test_mark(self): 437 | ''' 438 | test mark() method 439 | ''' 440 | pass 441 | 442 | def test_get_cs_mark(self): 443 | ''' 444 | test get_cs_mark() method 445 | ''' 446 | pass 447 | 448 | def test_get_peek_B_array(self): 449 | pass 450 | 451 | def test_print_lines(self): 452 | pass 453 | 454 | @unittest.skip("don't test inherit yet") 455 | def test_inherit(self): 456 | ''' 457 | inheritted things from object: 458 | 459 | c = playingcards.Card(5,'s') 460 | dir(c) 461 | ['__delattr__', '__getattribute__', '__setattr__', '__new__', 462 | '__dict__', '__dir__', '__format__', '__init_subclass__', 463 | '__subclasshook__', '__reduce__', '__reduce_ex__', '__weakref__'] 464 | 465 | @todo inherit tests 466 | ''' 467 | # check namespace values 468 | c5s = Card(5, 's') 469 | class_actual = str(c5s.__class__) 470 | self.assertEqual("", 471 | class_actual, 472 | ".class(%s)" % class_actual) 473 | module_actual = str(c5s.__module__) 474 | self.assertEqual("pybaccarat.playingcards", module_actual, 475 | ".module(%s)" % module_actual) 476 | docstring = c5s.__doc__ 477 | self.assertTrue(isinstance(docstring, str), "docstring is string") 478 | self.assertTrue(0 < len(docstring), "some string of >0 exists") 479 | 480 | # __repr__ () 481 | # __sizeof__() 482 | 483 | # @todo usage examples 484 | 485 | 486 | # 487 | # Command line entry point 488 | # 489 | if __name__ == '__main__': 490 | unittest.main() 491 | -------------------------------------------------------------------------------- /pybaccarat/baccarat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | '''! 4 | @package pybaccarat.baccarat 5 | This module is collection of classes used with playing the game 6 | Baccarat. 7 |

Classes: 8 |

    9 |
  • Hand
  • 10 |
  • Scoreboard
  • 11 |
  • Ties
  • 12 |
  • Game
  • 13 |
14 | @author fulkgl@gmail.com 15 | @version 0.21 16 | 0.18 basic functions all working promperly 17 | 0.19 interactive script added to installation,remove hand_num param on hand_pre 18 | 0.20 added JustBoards 19 | 0.21 play script add, just_boards 20 | 0.22 save_shoe had small error, scoreboard no more debug 21 | ''' 22 | 23 | 24 | from pybaccarat.playingcards import Card,Shoe 25 | 26 | __version__ = 0.22 ##!<@version 0.22 27 | 28 | 29 | class Hand(object): 30 | '''! 31 | This class represents a hand in the game Baccarat. 32 | 33 | @see playingcards.Card 34 | @see playingcards.Shoe 35 | ''' 36 | __HIT_TABLE = [0, 3, 4, 4, 5, 5, 6, 6, 2, 3, 3, 3, 3, 3] 37 | # rank x A 2 3 4 5 6 7 8 9 T J Q K 38 | # index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 39 | 40 | # ------------------------------------------------------------------------- 41 | def __init__(self): 42 | '''! 43 | Create a new hand in Baccarat. 44 | 45 | Example usage: 46 | @code{.py} 47 | player = Hand() 48 | player.add(Card(5,'s') 49 | player.add(shoe.deal()) 50 | @endcode 51 | ''' 52 | # 53 | # save data 54 | # 55 | self.__cards = [None, None, None] # max 3 cards per hand 56 | self.__size = 0 57 | self.__bacc_value = 0 58 | self.empty() 59 | 60 | # ------------------------------------------------------------------------- 61 | def empty(self): 62 | '''! 63 | Reset the hand, that is, clear all cards 64 | ''' 65 | self.__size = 0 66 | self.__bacc_value = 0 67 | 68 | # ------------------------------------------------------------------------- 69 | def add(self, new_card): 70 | '''! 71 | Add a card to the hand 72 | @param new_card the card to add to the hand 73 | @exception ValueError 74 | Raised by either a non-type compatible Card, or 75 | too many cards already in this Hand. 76 | ''' 77 | if not isinstance(new_card, Card): 78 | raise ValueError("add() requires a Card but found type(%s)" % 79 | type(new_card)) 80 | size = self.__size 81 | if size >= 3: 82 | raise ValueError("too many cards in hand, can not add more") 83 | else: 84 | self.__cards[size] = new_card 85 | self.__size = size + 1 86 | rank = new_card.get_rank() # 1..13 87 | if rank > 9: 88 | rank = 0 89 | self.__bacc_value = (self.__bacc_value + rank) % 10 90 | 91 | # ------------------------------------------------------------------------- 92 | def __add__(self, right): 93 | '''! 94 | Use the "+" operator to also add a Card to this Hand. 95 | @param right Card 96 | ''' 97 | self.add(right) 98 | return self 99 | 100 | # ------------------------------------------------------------------------- 101 | def value(self): 102 | '''! 103 | Get the baccarat hand value 104 | @return value of the hand 105 | ''' 106 | return self.__bacc_value 107 | 108 | # ------------------------------------------------------------------------- 109 | def need_hit(self, other_hand): 110 | '''! 111 | Does this hand need to hit a third card? 112 | Checking if the player needs a third card, other_hand should be None. 113 | The player's hand does not depend on the banker's hand. Only on 114 | itself. 115 | Checking if the banker needs a third card, other_hand should be the 116 | player's hand. The banker's third card decision is depends on the 117 | player's hand and cards. 118 | 119 | @param other_hand baccarat.Hand or None. 120 | @return True if yes, False if no. 121 | ''' 122 | max_hit = 5 123 | if (other_hand is not None) and isinstance(other_hand, Hand): 124 | # other_hand should be the player, and this should be banker 125 | player3 = other_hand.get_card(2) # player's third card 126 | if player3 is not None: 127 | max_hit = self.__HIT_TABLE[player3.get_rank()] 128 | return self.value() <= max_hit 129 | 130 | # ------------------------------------------------------------------------- 131 | def get_card(self, index): 132 | '''! 133 | Return the specified card. 134 | @param index integer 0 to 2 indicates which Card of this 135 | Hand to return. If no such Card exists, then None is returned. 136 | ''' 137 | card = None 138 | if isinstance(index, int) and (0 <= index) and (index < self.__size): 139 | card = self.__cards[index] 140 | return card 141 | 142 | # ------------------------------------------------------------------------- 143 | def is_natural(self): 144 | '''! 145 | Is this hand a natural? 146 | @return True for yes, False for no. 147 | ''' 148 | return (self.__size == 2) and (self.value() >= 8) 149 | 150 | # ------------------------------------------------------------------------- 151 | def __str__(self): 152 | '''! 153 | Return the string representation for this hand. 154 | 155 | This method overrights the method of the same name in the object class. 156 | @return string with cards contained inside "[...]", comma separated 157 | ''' 158 | to_string = "" 159 | for i in range(self.__size): 160 | if i > 0: 161 | to_string += "," 162 | to_string += str(self.__cards[i]) 163 | return "[" + to_string + "]" 164 | 165 | # ------------------------------------------------------------------------- 166 | def __cmp__(self, other): 167 | '''! 168 | Compare this hand to other hand. 169 | @return 0 if the two hands are equal, >0 if other hand has a 170 | greater value, <0 if other hand has a lesser value. The 171 | difference in value between the hands is also reflected in 172 | the magnitude of the difference here. If this hand has a 173 | value of 3 and other hand has a value of 5, then this compare 174 | method will return +2. 175 | ''' 176 | if not isinstance(other, Hand): 177 | raise ValueError("other type(%s) not type Hand" % type(other)) 178 | return self.value() - other.value() 179 | # ------------------------------------------------------------------------- 180 | # end class Hand 181 | 182 | 183 | class Scoreboard(object): 184 | '''! 185 | TBD 186 | board0 = "big road" 187 | board1 = "big eye boy" 188 | board2 = "small road" 189 | board3 = "cockroach pig" 190 | ''' 191 | RED_SAME = 's' 192 | BLUE_CHOP = 'C' 193 | 194 | def __init__(self, type, table_size=6): 195 | self.board_type = type 196 | self.h_array = [ "R%d" % type ] 197 | self.horiz_count = table_size * [0] 198 | self.lines = (table_size+1) * [None] 199 | self.lines[0] = "....v....1....v....2....v....3....v....4" + \ 200 | "....v....5....v....6" + " R%d" % type 201 | for i in range(table_size): 202 | self.lines[i + 1] = " "*60 + " %2d" % self.horiz_count[i] 203 | 204 | def get_array(self): 205 | return self.h_array 206 | 207 | def get_horiz_count(self): 208 | return self.horiz_count 209 | 210 | def mark(self, marker): 211 | '''! 212 | Mark the scoreboard. For board type 0 marker should be "B" or "P" for 213 | Banker or Player. For board types 1,2,3 marker should be "C" or "s" 214 | for Chop or Same. 215 | @param self this instance of the class 216 | @param marker single char 217 | ''' 218 | # only mark the board if it was a legal marker value 219 | if (self.board_type == 0 and marker != "B" and marker != "P"): 220 | return 221 | if (self.board_type > 0 and marker != "C" and marker != 's'): 222 | return 223 | if (self.board_type < 0): 224 | return 225 | 226 | # Do we need to add a new column to the horizontal array? 227 | # "len<2" means this is the first mark on this board. 228 | # "[-1][0]!=marker" means this is a different marker from the 229 | # last one recorded, thus requires a new column. 230 | if len(self.h_array) < 2 or self.h_array[-1][0] != marker: 231 | # add a new column with a count of 0 (incremented later) 232 | self.h_array.append( [marker, 0] ) 233 | # increment the count in this column 234 | col = len(self.h_array) - 1 235 | self.h_array[col][1] += 1 236 | # time to mark the print lines 237 | row = self.h_array[col][1] 238 | # 239 | six = len(self.horiz_count) 240 | sixty = len(self.lines[0]) - 3 241 | # update the horiz count 242 | if row <= six: 243 | self.horiz_count[row - 1] += 1 244 | self.lines[row] = self.lines[row][:sixty + 1] + \ 245 | "%2d" % self.horiz_count[row - 1] 246 | # calculate the slide 247 | slide = 0 248 | for iRow in range(row - 1): 249 | if six <= iRow: 250 | slide = row - iRow 251 | break 252 | if marker != self.lines[iRow + 1][col - 1]: 253 | slide = row - iRow 254 | break 255 | if slide == 0: 256 | if six < row: 257 | slide = row - six 258 | else: 259 | if " " != self.lines[row][col - 1]: 260 | slide = 1 261 | # 262 | if sixty < col + slide: 263 | # slide too far to the right, add special mark 264 | self.lines[row - slide] = \ 265 | self.lines[row - slide][:sixty] + ">" + \ 266 | self.lines[row - slide][sixty + 1:] 267 | else: 268 | #check for same below us without slide, change to "=" 269 | if slide == 0 and row < six: 270 | if marker == self.lines[row + 1][col - 1]: 271 | self.lines[row + 1] = \ 272 | self.lines[row + 1][:col - 1] + "=" + \ 273 | self.lines[row + 1][col:] 274 | #mark it, it should be empty 275 | self.lines[row - slide] = \ 276 | self.lines[row - slide][:col + slide - 1] + marker + \ 277 | self.lines[row - slide][col + slide:] 278 | 279 | def get_cs_mark(self, main_array): 280 | '''! 281 | Get the CS (chop/same) (blue/red) marks for the boards. 282 | 283 | R2... 284 | if L0 == 1: 285 | if L1 != L3: BC 286 | if L0 > 1: 287 | if L0 == L2: BC 288 | Next chop side: 289 | if L0 != L2: BC 290 | ''' 291 | col = len(main_array) - 1 292 | row = main_array[-1][1] 293 | if row == 1: 294 | if 0 < col - 1 - self.board_type: 295 | len_type_col = main_array[col - 1 - self.board_type][1] 296 | len_col_1 = main_array[col - 1][1] 297 | if len_col_1 != len_type_col: 298 | return self.BLUE_CHOP 299 | return self.RED_SAME 300 | else: 301 | if 0 < col - self.board_type: 302 | len_type_col = main_array[col - self.board_type][1] 303 | if row - 1 == len_type_col: 304 | return self.BLUE_CHOP 305 | return self.RED_SAME 306 | return " " 307 | 308 | def get_peek_B_array(self,arr): 309 | temp = [] 310 | for i in list(arr): 311 | a = i[:] 312 | temp.append(a) 313 | # copy by value not reference 314 | last_col = len(arr) - 1 315 | if last_col < 1 or 'B' != arr[last_col][0]: 316 | temp.append(['B', 1]) 317 | else: 318 | temp[last_col][1] += 1 319 | return temp 320 | 321 | def print_lines(self): 322 | out = "" 323 | for line in self.lines: 324 | out += line + "\n" 325 | return out 326 | 327 | def remove_last(self): 328 | pass 329 | 330 | # end class Scoreboard 331 | 332 | 333 | class Ties(object): 334 | '''! 335 | This class tracks tie results. 336 | ''' 337 | 338 | # ------------------------------------------------------------------------- 339 | def __init__(self): 340 | '''! 341 | TBD 342 | ''' 343 | self.__tie_tracker = "" 344 | self.__prior1 = "x" 345 | self.__prior2 = "x" 346 | 347 | # ------------------------------------------------------------------------- 348 | def mark(self, winner): 349 | '''! 350 | Record the winning hand from a game. 351 | @param winner char 'B' or 'P' or 'T' 352 | ''' 353 | if winner == "T": 354 | if self.__prior1 == "x": 355 | #print("1st hand of new shoe is tie") 356 | new_tie = "X" 357 | elif self.__prior1 == "T": 358 | #print("T follow T") 359 | self.__prior1 = self.__prior2 360 | #new_tie = "T" 361 | #self.__tie_tracker = self.__tie_tracker[:-1] #remove tailing "?" 362 | self.__tie_tracker = self.__tie_tracker[:-1] + "T" 363 | new_tie = "?" 364 | else: 365 | #print("T at end of seq") 366 | new_tie = "?" 367 | if len(self.__tie_tracker)%(5 + 1) == 5: 368 | self.__tie_tracker += "-" 369 | self.__tie_tracker += new_tie 370 | else: 371 | #print("not a tie, look at last for CS?") 372 | if self.__prior1 == "T" and self.__prior2 != 'x': 373 | if winner == self.__prior2: 374 | self.__tie_tracker = self.__tie_tracker[:-1] + "s" 375 | else: 376 | self.__tie_tracker = self.__tie_tracker[:-1] + "C" 377 | # 378 | self.__prior2 = self.__prior1 379 | self.__prior1 = winner 380 | 381 | # ------------------------------------------------------------------------- 382 | def __str__(self): 383 | '''! 384 | Print the current status of the tie tracker. 385 | ''' 386 | return "Ties(%s)" % self.__tie_tracker 387 | # ------------------------------------------------------------------------- 388 | def remove_last(self): 389 | if len(self.__tie_tracker) > 0: 390 | self.__tie_tracker = self.__tie_tracker[:-1] 391 | 392 | # ------------------------------------------------------------------------- 393 | # end class Ties 394 | 395 | 396 | class Game(object): 397 | '''! 398 | This class plays a game of Baccarat 399 | ''' 400 | 401 | # ------------------------------------------------------------------------- 402 | def __init__(self, shoe=None, player=None, banker=None, system=None): 403 | '''! 404 | TBD 405 | ''' 406 | # 407 | if shoe is None: 408 | shoe = Shoe(8) 409 | shoe.shuffle() 410 | if player is None: 411 | player = Hand() 412 | if banker is None: 413 | banker = Hand() 414 | # 415 | self.__shoe = shoe 416 | self.__player = player 417 | ##!< banker is the Hand for banker 418 | self.__banker = banker 419 | ##!< system_play is the bacc system we are tracking 420 | self.system_play = system 421 | # 422 | self.count_d7 = 0 423 | self.count_p8 = 0 424 | 425 | # ------------------------------------------------------------------------- 426 | def play_hand(self): 427 | '''! 428 | play a single hand 429 | zparam hand_number int 430 | @return (win,diff) 431 | ''' 432 | bonus = " " 433 | # deal first 4 cards 434 | self.__player.add(self.__shoe.deal()) 435 | self.__banker.add(self.__shoe.deal()) 436 | self.__player.add(self.__shoe.deal()) 437 | self.__banker.add(self.__shoe.deal()) 438 | # if not naturals, then third hits? 439 | diff = "n" 440 | if (not self.__player.is_natural()) and \ 441 | (not self.__banker.is_natural()): 442 | diff = "0" 443 | if self.__player.need_hit(None): 444 | self.__player.add(self.__shoe.deal()) 445 | if self.__banker.need_hit(self.__player): 446 | self.__banker.add(self.__shoe.deal()) 447 | # get winning hand 448 | if self.__banker.value() < self.__player.value(): 449 | win = "P" 450 | if not self.__player.is_natural(): 451 | diff = str(self.__player.value() - self.__banker.value()) 452 | if (self.__player.value() == 8) and \ 453 | (self.__player.get_card(2) is not None): 454 | bonus = "P8" 455 | self.count_p8 += 1 456 | elif self.__banker.value() > self.__player.value(): 457 | win = "B" 458 | if not self.__banker.is_natural(): 459 | diff = str(self.__banker.value() - self.__player.value()) 460 | if (self.__banker.value() == 7) and \ 461 | (self.__banker.get_card(2) is not None): 462 | bonus = "D7" 463 | self.count_d7 += 1 464 | else: 465 | win = "T" 466 | return (win, diff, bonus) 467 | 468 | def side_count(self, hand, rc1, rc2): 469 | for i in range(3): 470 | c = hand.get_card(i) 471 | if c is not None: 472 | r = c.get_rank() 473 | if r == 1 or r == 2 or r == 3: 474 | rc1 += 1 475 | elif r == 4 or r == 5 or r == 6 or r == 7: 476 | rc2 += 1 477 | elif r == 8 or r == 9: 478 | rc1 += -1 479 | rc2 += -2 480 | return rc1,rc2 481 | 482 | # ------------------------------------------------------------------------- 483 | def play(self, display=True, show_burn_cards=False, cut_card=-14): 484 | '''! 485 | Play one game of Baccarat. 486 | ''' 487 | 488 | # start of new shoe procedure 489 | self.count_d7 = 0 490 | self.count_p8 = 0 491 | self.__shoe.reset() 492 | #self.__shoe.shuffle() 493 | self.__shoe.set_cut_card(cut_card) 494 | tie_track = Ties() 495 | boards = [Scoreboard(0), Scoreboard(1), Scoreboard(2), Scoreboard(3)] 496 | rc1 = 0 497 | rc2 = 0 498 | 499 | # burn procedure 500 | burn = self.__shoe.deal() 501 | if display: 502 | display_burn = "burn(%s)" % str(burn) 503 | burned_cards = [burn] 504 | burn = burn.get_rank() 505 | if burn > 9: 506 | burn = 10 507 | for _ in range(burn): 508 | burned_card = self.__shoe.deal() 509 | if show_burn_cards: 510 | display_burn += " " + str(burned_card) 511 | burned_cards.append(burned_card) 512 | else: 513 | display_burn += " XX" 514 | 515 | # prepare before playing entire shoe 516 | hand_number = 0 517 | last_hand = False 518 | bpt = {'B': 0, 'P': 0, 'T': 0} 519 | win = 'X' 520 | 521 | special_JustBoards = False 522 | if display: 523 | print(display_burn) 524 | if self.system_play is not None: 525 | special_JustBoards = self.system_play.new_shoe(burned_cards, boards) 526 | self.system_play.set_tie_object(tie_track) 527 | self.system_play.set_bpt_object(bpt) 528 | if special_JustBoards: 529 | special_card9s = Card(9, 's') 530 | special_cardJh = Card(11, 'h') 531 | # 532 | while not last_hand: 533 | # start of a hand 534 | hand_number += 1 535 | self.__player.empty() 536 | self.__banker.empty() 537 | last_hand = self.__shoe.cut_card_seen() 538 | system_hand_output = "" 539 | if self.system_play is not None: 540 | # 541 | print(79*"=") 542 | print(boards[0].print_lines()) 543 | print(boards[2].print_lines()) 544 | print(str(tie_track)) 545 | print(self.system_play.end_shoe()) 546 | # 547 | system_hand_output = self.system_play.hand_pre() 548 | if special_JustBoards: 549 | print("***\n*** special_JustBoards\n***") 550 | if system_hand_output == "B" or \ 551 | system_hand_output == "P" or \ 552 | system_hand_output == "T": 553 | next_card = self.__shoe._Shoe__next_card 554 | print("*** force a %s" % system_hand_output) 555 | special_1 = special_cardJh 556 | special_2 = special_cardJh 557 | if system_hand_output != "B": 558 | special_1 = special_card9s 559 | if system_hand_output != "P": 560 | special_2 = special_card9s 561 | self.__shoe._Shoe__cards[next_card+0] = special_cardJh 562 | self.__shoe._Shoe__cards[next_card+1] = special_cardJh 563 | self.__shoe._Shoe__cards[next_card+2] = special_1 #p2 564 | self.__shoe._Shoe__cards[next_card+3] = special_2 #b2 565 | elif system_hand_output == "X": 566 | print("*** backup") 567 | hand_number -= 1 568 | if hand_number < 0: 569 | hand_number = 0 570 | print("***win(%s)" % win) 571 | if win == 'X': 572 | print("can not clear") 573 | else: 574 | bpt[win] -= 1 575 | if win == 'T': 576 | tie_track.remove_last() 577 | win = 'X' 578 | boards[0].remove_last() 579 | h_array = boards[0].get_array() 580 | if 1 < len(h_array): 581 | #last_col = len(h_array 582 | h_a_index = len(h_array) - 1 583 | boards[0].h_array[h_a_index][1] -= 1 584 | for j in range(1,4): 585 | boards[j].remove_last() 586 | print("%s" % str(boards[j].get_array())) 587 | print("%s" % str(boards[j].get_horiz_count())) 588 | continue 589 | else: 590 | print("*** what is this??? (%s)" % system_hand_output) 591 | 592 | (win, diff, bonus) = self.play_hand() 593 | bpt[win] += 1 594 | tie_track.mark(win) 595 | boards[0].mark(win) 596 | for j in range(1,4): 597 | if win != "T": 598 | boards[j].mark(boards[j].get_cs_mark(boards[0].get_array())) 599 | 600 | # shoe hand results 601 | if self.system_play is not None: 602 | system_hand_output += self.system_play.hand_post(win+diff, 603 | self.__player, 604 | self.__banker) 605 | 606 | #running counts 607 | rc1,rc2 = self.side_count(self.__player, rc1, rc2) 608 | rc1,rc2 = self.side_count(self.__banker, rc1, rc2) 609 | 610 | if display: 611 | board0horiz_count = boards[0].get_horiz_count() 612 | # 613 | peekB = "" 614 | if True: 615 | board0arr = boards[0].get_array() 616 | arrayB = boards[0].get_peek_B_array(board0arr) 617 | peekB += boards[1].get_cs_mark(arrayB) 618 | peekB += boards[2].get_cs_mark(arrayB) 619 | peekB += boards[3].get_cs_mark(arrayB) 620 | # 621 | flag1 = " " 622 | flag2 = " " 623 | if (25 + hand_number // 11) < rc1: 624 | flag1 = ">" 625 | if (32 - 4 * (hand_number // 11)) < rc2: 626 | flag2 = "<" 627 | 628 | print(("%02d P%d%-10s B%d%-10s %s%s %s BPT=%02d-%02d-%02d" + \ 629 | " %02d/%02d %s%s%02d,%02d%s %s") % 630 | (hand_number, 631 | self.__player.value(), str(self.__player), 632 | self.__banker.value(), str(self.__banker), 633 | win, diff, bonus, bpt['B'], bpt['P'], bpt['T'], 634 | board0horiz_count[0], board0horiz_count[1], peekB, 635 | flag1, rc1, rc2, flag2, system_hand_output)) 636 | # notify the user 637 | if self.__shoe.cut_card_seen() and not last_hand: 638 | if display: 639 | print("last hand of this shoe") 640 | # 641 | 642 | # 643 | # end of shoe 644 | # 645 | if display: 646 | print("%-30s D7(%d) P8(%d)" % \ 647 | (str(tie_track), self.count_d7, self.count_p8)) 648 | print(boards[0].print_lines()) 649 | print(boards[2].print_lines()) 650 | 651 | if self.system_play is not None: 652 | print(self.system_play.end_shoe()) 653 | # ------------------------------------------------------------------------- 654 | # end class Game 655 | -------------------------------------------------------------------------------- /tests/test_ties.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """! 4 | Unit test for the Ties class. 5 | 6 | To execute the unit test from base dir location, enter: 7 | @code 8 | python tests\test_ties.py [-v] 9 | python tests\test_ties.py TestTies.test_basic 10 | @endcode 11 | 12 | self.assertIsNone(obj, "msg") 13 | self.assertIsNotNone(obj, "msg") 14 | self.assertTrue(boolean, "msg") 15 | self.assertFalse(boolean, "msg") 16 | self.assertEqual(expect, actual, "msg") 17 | self.assertNotEqual(expect, actual, "msg") 18 | with self.assertRaises(ValueError): causes_exception 19 | self.fail("msg") 20 | 21 | @author fulkgl@gmail.com 22 | """ 23 | 24 | import os,sys,unittest 25 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))[:-6]) #=dev first 26 | from pybaccarat.baccarat import Ties 27 | 28 | 29 | class TestTies(unittest.TestCase): 30 | ''' 31 | Unit test for the Ties class. 32 | ''' 33 | 34 | @unittest.skip("TBD") 35 | def test_constructor(self): 36 | ''' 37 | test card construction 38 | ''' 39 | pass 40 | 41 | def test_post_tt(self): 42 | t0 = Ties() 43 | actual = str(t0) 44 | self.assertEqual("Ties()", actual, "new shoe(%s)"%actual) 45 | t0.mark("P") 46 | actual = str(t0) 47 | self.assertEqual("Ties()", actual, "P(%s)"%actual) 48 | t0.mark("T") 49 | actual = str(t0) 50 | self.assertEqual("Ties(?)", actual, "PT(%s)"%actual) 51 | t0.mark("T") 52 | actual = str(t0) 53 | self.assertEqual("Ties(T?)", actual, "PTT(%s)"%actual) 54 | t0.mark("P") 55 | actual = str(t0) 56 | self.assertEqual("Ties(Ts)", actual, "PTTP(%s)"%actual) 57 | 58 | def test_basics(self): 59 | '''! 60 | TBD 61 | ''' 62 | t0 = Ties() 63 | actual = str(t0) 64 | self.assertEqual("Ties()", actual, "new shoe(%s)"%actual) 65 | t0.mark("P") 66 | actual = str(t0) 67 | self.assertEqual("Ties()", actual, "P(%s)"%actual) 68 | t0.mark("T") 69 | actual = str(t0) 70 | self.assertEqual("Ties(?)", actual, "PT(%s)"%actual) 71 | t0.mark("P") 72 | actual = str(t0) 73 | self.assertEqual("Ties(s)", actual, "PTP(%s)"%actual) 74 | t0.mark("T") 75 | actual = str(t0) 76 | self.assertEqual("Ties(s?)", actual, "PTPT(%s)"%actual) 77 | t0.mark("B") 78 | actual = str(t0) 79 | self.assertEqual("Ties(sC)", actual, "PTPTB(%s)"%actual) 80 | 81 | def test_first_tie(self): 82 | t0 = Ties() 83 | actual = str(t0) 84 | self.assertEqual("Ties()", actual, "new(%s)"%actual) 85 | t0.mark("T") 86 | actual = str(t0) 87 | self.assertEqual("Ties(X)", actual, "T(%s)"%actual) 88 | t0.mark("P") 89 | actual = str(t0) 90 | self.assertEqual("Ties(X)", actual, "TP(%s)"%actual) 91 | 92 | def test_triple_tie(self): 93 | '''! 94 | TBD 95 | ''' 96 | # first simple "same" 97 | t0 = Ties() 98 | t0.mark("P") 99 | t0.mark("P") 100 | t0.mark("P") 101 | t0.mark("B") 102 | t0.mark("B") 103 | actual = str(t0) 104 | self.assertEqual("Ties()", actual, "3p2b(%s)"%actual) 105 | t0.mark("T") 106 | actual = str(t0) 107 | self.assertEqual("Ties(?)", actual, "3p2bT(%s)"%actual) 108 | t0.mark("B") 109 | actual = str(t0) 110 | self.assertEqual("Ties(s)", actual, "3p2bTb(%s)"%actual) 111 | t0 = None 112 | # second simple "chop" 113 | t1 = Ties() 114 | t1.mark("P") 115 | t1.mark("P") 116 | t1.mark("P") 117 | t1.mark("B") 118 | t1.mark("B") 119 | actual = str(t1) 120 | self.assertEqual("Ties()", actual, "3p2b(%s)"%actual) 121 | t1.mark("T") 122 | actual = str(t1) 123 | self.assertEqual("Ties(?)", actual, "3p2bT(%s)"%actual) 124 | t1.mark("P") 125 | actual = str(t1) 126 | self.assertEqual("Ties(C)", actual, "3p2bTp(%s)"%actual) 127 | t1 = None 128 | # third simple tie follow tie 129 | t2 = Ties() 130 | t2.mark("P") 131 | t2.mark("P") 132 | t2.mark("P") 133 | t2.mark("B") 134 | t2.mark("B") 135 | actual = str(t2) 136 | self.assertEqual("Ties()", actual, "3p2b(%s)"%actual) 137 | t2.mark("T") 138 | actual = str(t2) 139 | self.assertEqual("Ties(?)", actual, "3p2bT(%s)"%actual) 140 | t2.mark("T") 141 | actual = str(t2) 142 | self.assertEqual("Ties(T?)", actual, "3p2bTT(%s)"%actual) 143 | t2 = None 144 | 145 | @unittest.skip("TBD") 146 | def test_basics2(self): 147 | # 1. empty board 148 | b0 = Scoreboard(0) 149 | #b0.set_debug() 150 | empty_board = \ 151 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 152 | " 0\n"+\ 153 | " 0\n"+\ 154 | " 0\n"+\ 155 | " 0\n"+\ 156 | " 0\n"+\ 157 | " 0\n" 158 | actual = b0.print_lines() 159 | self.assertEqual(empty_board, actual, "check empty board") 160 | 161 | # 2. simple P mark 162 | b0.mark('P') 163 | board1 = \ 164 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 165 | "P 1\n"+\ 166 | " 0\n"+\ 167 | " 0\n"+\ 168 | " 0\n"+\ 169 | " 0\n"+\ 170 | " 0\n" 171 | actual = b0.print_lines() 172 | self.assertEqual(board1, actual, "basic P\n%s"%actual) 173 | 174 | # 3. simple P mark (total PP) 175 | b0.mark('P') 176 | board2 = \ 177 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 178 | "P 1\n"+\ 179 | "P 1\n"+\ 180 | " 0\n"+\ 181 | " 0\n"+\ 182 | " 0\n"+\ 183 | " 0\n" 184 | actual = b0.print_lines() 185 | self.assertEqual(board2, actual, "basic PP\n%s"%actual) 186 | 187 | # 4. PPB total marks 188 | b0.mark('B') 189 | board3 = \ 190 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 191 | "PB 2\n"+\ 192 | "P 1\n"+\ 193 | " 0\n"+\ 194 | " 0\n"+\ 195 | " 0\n"+\ 196 | " 0\n" 197 | actual = b0.print_lines() 198 | self.assertEqual(board3, actual, "basic PP B\n%s"%actual) 199 | 200 | # 5. PPBBBBB total marks (up to the 5th row on B) 201 | b0.mark('B') 202 | b0.mark('B') 203 | b0.mark('B') 204 | b0.mark('B') 205 | board4 = \ 206 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 207 | "PB 2\n"+\ 208 | "PB 2\n"+\ 209 | " B 1\n"+\ 210 | " B 1\n"+\ 211 | " B 1\n"+\ 212 | " 0\n" 213 | actual = b0.print_lines() 214 | self.assertEqual(board4, actual, "basic 2P 5B\n%s"%actual) 215 | 216 | # 6. 2P 6B up to the 6th row with B's 217 | b0.mark('B') 218 | board46 = \ 219 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 220 | "PB 2\n"+\ 221 | "PB 2\n"+\ 222 | " B 1\n"+\ 223 | " B 1\n"+\ 224 | " B 1\n"+\ 225 | " B 1\n" 226 | actual = b0.print_lines() 227 | self.assertEqual(board46, actual, "basic 2P 6B\n%s"%actual) 228 | 229 | # 7. 2P 7B first slide 230 | b0.mark('B') 231 | board5 = \ 232 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 233 | "PB 2\n"+\ 234 | "PB 2\n"+\ 235 | " B 1\n"+\ 236 | " B 1\n"+\ 237 | " B 1\n"+\ 238 | " BB 1\n" 239 | actual = b0.print_lines() 240 | self.assertEqual(board5, actual, "basic PP BBBBBB B\n%s"%actual) 241 | 242 | # 7. 2P 8B second slide 243 | b0.mark('B') 244 | board6 = \ 245 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 246 | "PB 2\n"+\ 247 | "PB 2\n"+\ 248 | " B 1\n"+\ 249 | " B 1\n"+\ 250 | " B 1\n"+\ 251 | " BBB 1\n" 252 | actual = b0.print_lines() 253 | self.assertEqual(board6, actual, "basic 2P 8B\n%s"%actual) 254 | # 255 | b0.set_debug(False) 256 | @unittest.skip("TBD") 257 | def test_extreme_slide(self): 258 | ''' 259 | test the condition of slide go too far to the right 260 | ''' 261 | b0 = Scoreboard(0) 262 | empty_board = \ 263 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 264 | " 0\n"+\ 265 | " 0\n"+\ 266 | " 0\n"+\ 267 | " 0\n"+\ 268 | " 0\n"+\ 269 | " 0\n" 270 | actual = b0.print_lines() 271 | self.assertEqual(empty_board, actual, "check empty board") 272 | # mark 64 straight Players (slide to pre-max) 273 | #b0.set_debug(True) 274 | for i in range(64): 275 | #print("extreme i=%d" % i) 276 | b0.mark('P') 277 | b0.set_debug(False) 278 | board1 = \ 279 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 280 | "P 1\n"+\ 281 | "P 1\n"+\ 282 | "P 1\n"+\ 283 | "P 1\n"+\ 284 | "P 1\n"+\ 285 | "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP 1\n" 286 | actual64 = b0.print_lines() 287 | self.assertEqual(board1, actual64, "64P, pre-max\n%s"%actual64) 288 | # mark the 65th player to reach max condition 289 | b0.mark('P') 290 | board2 = \ 291 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 292 | "P 1\n"+\ 293 | "P 1\n"+\ 294 | "P 1\n"+\ 295 | "P 1\n"+\ 296 | "P 1\n"+\ 297 | "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP 1\n" 298 | self.assertEqual(board2, b0.print_lines(), "65P max") 299 | # mark the 66th player to exceed max condition 300 | b0.mark('P') 301 | board3 = \ 302 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 303 | "P 1\n"+\ 304 | "P 1\n"+\ 305 | "P 1\n"+\ 306 | "P 1\n"+\ 307 | "P 1\n"+\ 308 | "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP> 1\n" 309 | actual = b0.print_lines() 310 | self.assertEqual(board3, actual, "66P beyond max\n%s"%actual) 311 | @unittest.skip("TBD") 312 | def test_9116_slide(self): 313 | ''' 314 | test a specific slide condition, 9116 315 | ''' 316 | b0 = Scoreboard(0) 317 | #b0.set_debug() 318 | empty_board = \ 319 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 320 | " 0\n"+\ 321 | " 0\n"+\ 322 | " 0\n"+\ 323 | " 0\n"+\ 324 | " 0\n"+\ 325 | " 0\n" 326 | actual = b0.print_lines() 327 | self.assertEqual(empty_board, actual, "check empty board") 328 | # mark 9115 329 | for i in range(9): 330 | b0.mark('P') 331 | b0.mark('B') 332 | b0.mark('P') 333 | for i in range(5): 334 | b0.mark('B') 335 | board1 = \ 336 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 337 | "PBPB 4\n"+\ 338 | "P B 2\n"+\ 339 | "P B 2\n"+\ 340 | "P B 2\n"+\ 341 | "P B 2\n"+\ 342 | "PPPP 1\n" 343 | actual = b0.print_lines() 344 | self.assertEqual(board1, actual, "pre 9116\n%s\n%s"%(actual,board1)) 345 | # now add that 6th B 346 | b0.mark('B') 347 | board2 = \ 348 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 349 | "PBPB 4\n"+\ 350 | "P B 2\n"+\ 351 | "P B 2\n"+\ 352 | "P B 2\n"+\ 353 | "P BB 2\n"+\ 354 | "PPPP 2\n" 355 | actual = b0.print_lines() 356 | self.assertEqual(board2, actual, "after 9116\n%s"%actual) 357 | b0.set_debug(False) 358 | @unittest.skip("TBD") 359 | def test_91176_slide(self): 360 | ''' 361 | test a specific slide condition, 91176 362 | ''' 363 | b0 = Scoreboard(0) 364 | empty_board = \ 365 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 366 | " 0\n"+\ 367 | " 0\n"+\ 368 | " 0\n"+\ 369 | " 0\n"+\ 370 | " 0\n"+\ 371 | " 0\n" 372 | actual = b0.print_lines() 373 | self.assertEqual(empty_board, actual, "check empty board") 374 | # mark 9115 375 | for i in range(9): 376 | b0.mark('P') 377 | b0.mark('B') 378 | b0.mark('P') 379 | for i in range(5): 380 | b0.mark('B') 381 | board1 = \ 382 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 383 | "PBPB 4\n"+\ 384 | "P B 2\n"+\ 385 | "P B 2\n"+\ 386 | "P B 2\n"+\ 387 | "P B 2\n"+\ 388 | "PPPP 1\n" 389 | actual = b0.print_lines() 390 | self.assertEqual(board1, actual, "pre 9116\n%s\n%s"%(actual,board1)) 391 | # now add that 6th B 392 | b0.mark('B') 393 | board2 = \ 394 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 395 | "PBPB 4\n"+\ 396 | "P B 2\n"+\ 397 | "P B 2\n"+\ 398 | "P B 2\n"+\ 399 | "P BB 2\n"+\ 400 | "PPPP 2\n" 401 | actual = b0.print_lines() 402 | self.assertEqual(board2, actual, "after 9116\n%s"%actual) 403 | # now add that 7th B 404 | #b0.set_debug() 405 | b0.mark('B') 406 | #b0.set_debug(False) 407 | board3 = \ 408 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 409 | "PBPB 4\n"+\ 410 | "P B 2\n"+\ 411 | "P B 2\n"+\ 412 | "P B 2\n"+\ 413 | "P BBB 2\n"+\ 414 | "PPPP 2\n" 415 | actual = b0.print_lines() 416 | self.assertEqual(board3, actual, "after 9117\n%s"%actual) 417 | # 4 Ps 418 | for i in range(4): 419 | b0.mark('P') 420 | board4 = \ 421 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 422 | "PBPBP 5\n"+\ 423 | "P BP 3\n"+\ 424 | "P BP 3\n"+\ 425 | "P BP 3\n"+\ 426 | "P BBB 2\n"+\ 427 | "PPPP 2\n" 428 | actual = b0.print_lines() 429 | self.assertEqual(board4, actual, "after 91174\n%s"%actual) 430 | # 5th P 431 | #b0.set_debug() 432 | b0.mark('P') 433 | board5 = \ 434 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 435 | "PBPBP 5\n"+\ 436 | "P BP 3\n"+\ 437 | "P BP 3\n"+\ 438 | "P BPP 3\n"+\ 439 | "P BBB 3\n"+\ 440 | "PPPP 2\n" 441 | actual = b0.print_lines() 442 | self.assertEqual(board5, actual, "after 91175\n%s"%actual) 443 | # 6th P 444 | b0.mark('P') 445 | board6 = \ 446 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 447 | "PBPBP 5\n"+\ 448 | "P BP 3\n"+\ 449 | "P BP 3\n"+\ 450 | "P BPPP 3\n"+\ 451 | "P BBB 3\n"+\ 452 | "PPPP 3\n" 453 | actual = b0.print_lines() 454 | self.assertEqual(board6, actual, "after 91176\n%s"%actual) 455 | # 7th P 456 | b0.mark('P') 457 | board7 = \ 458 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 459 | "PBPBP 5\n"+\ 460 | "P BP 3\n"+\ 461 | "P BP 3\n"+\ 462 | "P BPPPP 3\n"+\ 463 | "P BBB 3\n"+\ 464 | "PPPP 3\n" 465 | actual = b0.print_lines() 466 | self.assertEqual(board7, actual, "after 91177\n%s"%actual) 467 | @unittest.skip("TBD") 468 | def test_same_long_strings(self): 469 | ''' 470 | test the condition of 2 same type long strings in parallel and 471 | a possible collision. 472 | ''' 473 | b0 = Scoreboard(0) 474 | empty_board = \ 475 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 476 | " 0\n"+\ 477 | " 0\n"+\ 478 | " 0\n"+\ 479 | " 0\n"+\ 480 | " 0\n"+\ 481 | " 0\n" 482 | actual = b0.print_lines() 483 | self.assertEqual(empty_board, actual, "check empty board") 484 | # mark 6 straight P, check the resulting board 485 | for i in range(6): 486 | b0.mark('P') 487 | board1 = \ 488 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 489 | "P 1\n"+\ 490 | "P 1\n"+\ 491 | "P 1\n"+\ 492 | "P 1\n"+\ 493 | "P 1\n"+\ 494 | "P 1\n" 495 | self.assertEqual(board1, b0.print_lines(), "after first 6 players") 496 | for i in range(3): 497 | b0.mark('P') 498 | board2 = \ 499 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 500 | "P 1\n"+\ 501 | "P 1\n"+\ 502 | "P 1\n"+\ 503 | "P 1\n"+\ 504 | "P 1\n"+\ 505 | "PPPP 1\n" 506 | self.assertEqual(board2, b0.print_lines(), "after first 9 players") 507 | b0.mark('B') 508 | b0.mark('P') 509 | b0.mark('P') 510 | b0.mark('P') 511 | b0.mark('P') 512 | # B, then 4 P. Pre-collision condition 513 | board2 = \ 514 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 515 | "PBP 3\n"+\ 516 | "P P 2\n"+\ 517 | "P P 2\n"+\ 518 | "P P 2\n"+\ 519 | "P 1\n"+\ 520 | "PPPP 1\n" 521 | self.assertEqual(board2, b0.print_lines(), "pre-collision condition") 522 | # run the collision mark 523 | b0.mark('P') 524 | board2 = \ 525 | "....v....1....v....2....v....3....v....4....v....5....v....6 R0\n"+\ 526 | "PBP 3\n"+\ 527 | "P P 2\n"+\ 528 | "P P 2\n"+\ 529 | "P P 2\n"+\ 530 | "P P 2\n"+\ 531 | "PP=P 1\n" 532 | self.assertEqual(board2, b0.print_lines(), "collision") 533 | @unittest.skip("TBD") 534 | def test_getarray(self): 535 | ''' 536 | test get_rank method 537 | ''' 538 | pass 539 | @unittest.skip("TBD") 540 | def test_getseq(self): 541 | ''' 542 | test getseq() method 543 | ''' 544 | pass 545 | @unittest.skip("TBD") 546 | def test_mark(self): 547 | ''' 548 | test mark() method 549 | ''' 550 | pass 551 | @unittest.skip("TBD") 552 | def test_get_cs_mark(self): 553 | ''' 554 | test get_cs_mark() method 555 | ''' 556 | pass 557 | @unittest.skip("TBD") 558 | def test_get_peek_B_array(self): 559 | pass 560 | @unittest.skip("TBD") 561 | def test_print_lines(self): 562 | pass 563 | @unittest.skip("don't test inherit yet") 564 | def test_inherit(self): 565 | ''' 566 | inheritted things from object: 567 | 568 | c = playingcards.Card(5,'s') 569 | dir(c) 570 | ['__delattr__', '__getattribute__', '__setattr__', '__new__', 571 | '__dict__', '__dir__', '__format__', '__init_subclass__', 572 | '__subclasshook__', '__reduce__', '__reduce_ex__', '__weakref__'] 573 | 574 | @todo inherit tests 575 | ''' 576 | # check namespace values 577 | c5s = Card(5, 's') 578 | class_actual = str(c5s.__class__) 579 | self.assertEqual("", 580 | class_actual, 581 | ".class(%s)" % class_actual) 582 | module_actual = str(c5s.__module__) 583 | self.assertEqual("pybaccarat.playingcards", module_actual, 584 | ".module(%s)" % module_actual) 585 | docstring = c5s.__doc__ 586 | self.assertTrue(isinstance(docstring, str), "docstring is string") 587 | self.assertTrue(0 < len(docstring), "some string of >0 exists") 588 | 589 | # __repr__ () 590 | # __sizeof__() 591 | 592 | # @todo usage examples 593 | 594 | 595 | # 596 | # Command line entry point 597 | # 598 | if __name__ == '__main__': 599 | unittest.main() 600 | -------------------------------------------------------------------------------- /pybaccarat/playingcards.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | '''! 4 | @package pybaccarat.playingcards 5 | This module is collection of classes used with playing Cards. 6 | The Card class is the representation of a single playing card. 7 | The Shoe class is the representation of a stack of playing cards. 8 | This stack could be a single deck (such as poker), or a multi-deck 9 | deck (such as Baccarat). 10 | 11 | To use: 12 | from playingcards import Card 13 | from playingcards import Stack 14 | 15 | @author fulkgl@gmail.com 16 | ''' 17 | 18 | # http://www.stack.nl/~dimitri/doxygen/index.html 19 | import random 20 | 21 | 22 | class Card(object): 23 | '''! 24 | The Card class represents a single playing card. 25 | 26 | It is immutable. That is, once created this Card can not be changed. 27 | This Card is a general purpose playing card that can be use by many 28 | different games. 29 | 30 | Example usage: 31 | @code{.py} 32 | import pybaccarat.playingcards 33 | #create a five of spades 34 | card5s = pybaccarat.playingcards.Card(43) #(5, 's') 35 | #print this card 36 | print(card5s) #displays '5s' 37 | @endcode 38 | @see Shoe 39 | ''' 40 | 41 | # -------------------------------------------------------------------- 42 | def __init__(self, new_ordinal, new_suit=None): 43 | '''! 44 | Create a new playing card. There are 3 syntaxes that can be used to 45 | create this new card. First, a single integer in the range 0 to 51. 46 | Where each integer maps to a particular rank and suit. The second 47 | syntax is to supply a rank and suit. Third, a string naming of the 48 | rank and suit. 49 | 50 | Example usage: 51 | @code{.py} 52 | import pybaccarat.playingcards 53 | #create a five of spades 54 | card5s = pybaccarat.playingcards.Card(43) #(5, 's') 55 | #create a jack of diamods 56 | cardJh = pybaccarat.playingcards.Card(11, 'd') 57 | #create an ace of clubs 58 | cardAs = pybaccarat.playingcards.Card("Ac") 59 | @endcode 60 | 61 | The first syntax is a single integer value 0 to 51. This would likely 62 | be used by a program that wants to create an array of cards quickly. 63 | Such as a complete deck of cards. To create a complete deck of cards 64 | use the following code. 65 | @code{.py} 66 | from pybaccarat.playingcards import Card 67 | deck = [] 68 | for c in range(52): 69 | deck.append(Card(c)) 70 | @endcode 71 | 72 | The second syntax would be the case of creating a single specific 73 | playing card. This would likely be a rare useage. But, it is available 74 | in case a single card is wanted. The syntax would be 2 arguments: 75 | rank and suit. The rank is an integer in the range 1 to 13. 76 | The suit is a single string character. 77 | @code{.py} 78 | from pybaccarat.playingcards import Card 79 | c5s = Card(5, 's') # create a five of spades 80 | @endcode 81 | 82 | The third syntax is similar to the second, only using the string 83 | representation of the Card. This is the same syntax used to write 84 | a Card object with the str() method. 85 | @code{.py} 86 | from pybaccarat.playingcards import Card 87 | c5s = Card('5s') # create a five of spades 88 | @endcode 89 | 90 | @param self this object pointer reference 91 | @param new_ordinal integer value 0 to 51 inclusive. 92 | If 2 arguements are given for the creation of a card the integer 93 | value will be the rank in the range 1 to 13. Or a string 94 | representation of the Card value. 95 | @param new_suit Used only when 2 arguments are given for creating a 96 | new card. The single string character will represent the suit. 97 | Accepted values are 's', 'h', 'd', or 'c'. 98 | @exception ValueError 99 | If the input parameter is not valid this exception 100 | will be raised. 101 | 102 | An example of an exception is: 103 | @code{.py} 104 | import pybaccarat.playingcards 105 | c = pybaccarat.playingcards.Card(66) 106 | ValueError: new_ordinal(66) not in legal range 0..51 107 | @endcode 108 | ''' 109 | 110 | # 111 | # validate data 112 | # 113 | if isinstance(new_ordinal, str) and new_suit is None: 114 | # 115 | # Third syntax, input string representation. Card('5s') 116 | # 117 | new_ordinal = new_ordinal.strip() 118 | if len(new_ordinal) != 2: 119 | raise ValueError("new_ordinal(%s) not legel length 2 string" % 120 | str(new_ordinal)) 121 | # look for legal rank 122 | new_rank = 'A23456789TJQK'.find(new_ordinal[0].upper()) 123 | if new_rank == -1: 124 | raise ValueError("illegal rank part of new_ordinal(%s)" % 125 | str(new_ordinal)) 126 | new_rank += 1 # 1..13 127 | # accept upper/lower suit name 128 | new_suit = new_ordinal[1].lower() 129 | suit_index = 'cdhs'.find(new_suit) 130 | if suit_index == -1: 131 | raise ValueError("new_suit(%s) not a legal value('cdhs')" % 132 | str(new_suit)) 133 | new_ordinal = (new_rank - 1) + (13 * suit_index) 134 | elif isinstance(new_ordinal, int) and new_suit is not None: 135 | # 136 | # The second syntax has been used. One integer (1 to 13) and 137 | # a single string character for the suit. Card(5, 's') 138 | # 139 | if (new_ordinal < 1) or (13 < new_ordinal): 140 | raise ValueError("new_ordinal(%s) not in rank range 1..13" % 141 | str(new_ordinal)) 142 | if (not isinstance(new_suit, str)) or (len(new_suit) != 1): 143 | raise ValueError("new_suit(%s) is not a single char" % 144 | str(new_suit)) 145 | suit_index = 'cdhs'.index(new_suit) 146 | if suit_index == -1: 147 | raise ValueError("new_suit(%s) not a legal value('cdhs')" % 148 | str(new_suit)) 149 | new_rank = new_ordinal 150 | new_ordinal = (new_rank - 1) + (13 * suit_index) 151 | elif isinstance(new_ordinal, int) and new_suit is None: 152 | # 153 | # The first syntax has been used. new_ordinal must be an 154 | # integer in the range 0 to 51. Card(43) 155 | # 156 | if (new_ordinal < 0) or (51 < new_ordinal): 157 | raise ValueError("new_ordinal(%s) not in legal range 0..51" % 158 | str(new_ordinal)) 159 | new_rank = 1 + (new_ordinal % 13) 160 | new_suit = 'cdhs'[new_ordinal // 13] 161 | else: 162 | raise ValueError("invalid syntax new_ordinal(%s) suit(%s)" % ( 163 | str(new_ordinal),str(new_suit))) 164 | # we have valid new_rank, new_suit, new_ordinal 165 | 166 | # 167 | # save data 168 | # 169 | 170 | # Since this class is immutable all values are computed here. 171 | # No need to compute values later when methods are called. 172 | self.__rank = new_rank 173 | self.__suit = new_suit 174 | self.__ordinal = new_ordinal 175 | self.__to_string = 'A23456789TJQK'[self.__rank - 1] + self.__suit 176 | # 177 | # Extending original design... 178 | # face_up True/False 179 | # images? No. They don't belong here. Images will be loaded from 180 | # different sources or formats. Furthermore, weighting down this simple 181 | # class is a poor idea. A separate class with the images is a better 182 | # idea. 183 | # 184 | #self.face_up = False 185 | 186 | # -------------------------------------------------------------------- 187 | def get_rank(self): 188 | '''! 189 | Return the rank of this card. 190 | 191 | @param self this object pointer reference 192 | @return integer 1..13 193 | 194 | 195 | 196 | 197 | 198 | 199 |
1ace
2 to 10are numbered cards 2 to 10
11jack
12queen
13king
200 | ''' 201 | return self.__rank 202 | 203 | # -------------------------------------------------------------------- 204 | def get_suit(self): 205 | '''! 206 | Return the suit of this card. 207 | 208 | @param self this object pointer reference 209 | @return single lower case character 210 | 211 | 212 | 213 | 214 | 215 |
'c'clubs
'd'diamonds
'h'hearts
's'spades
216 | ''' 217 | return self.__suit 218 | 219 | # ------------------------------------------------------------------------- 220 | def get_ordinal(self): 221 | '''! 222 | Return the ordinal value. 223 | The ordinal value is computed as a single integer value for each of 224 | the 52 cards. 225 | 226 | @param self this object pointer reference 227 | @return integer 0..51 228 | ''' 229 | return self.__ordinal 230 | 231 | # ------------------------------------------------------------------------- 232 | def __str__(self): 233 | '''! 234 | Return the string representation for this card. 235 | 236 | This method overwrites the object class method of this same name. 237 | 238 | Example usage: 239 | @code{.py} 240 | card5s = Card(43) #(5, 's') 241 | print(card5s) #displays '5s' 242 | @endcode 243 | @param self this object pointer reference 244 | @return 2 character long string; rank and suit. 245 | ''' 246 | return self.__to_string 247 | 248 | # ------------------------------------------------------------------------- 249 | def __eq__(self, other): 250 | '''! 251 | Returns the result of equals (==) between this 252 | Card and other Card. 253 | 254 | Overwrites the default object class behavior. The default object 255 | class compares instanciated Card's addresses. Thus two cards with the 256 | same rank and suit will not be equal. This method will cause two cards 257 | of the same rank and suit to be considered equal. 258 | 259 | Example usage: 260 | @code{.py} 261 | import pybaccarat.playingcards 262 | #create a five of spades 263 | first = pybaccarat.playingcards.Card(43) #(5, 's') 264 | #create a second five of spades 265 | second = pybaccarat.playingcards.Card(43) #(5, 's') 266 | #test if they are equal 267 | if first == second: 268 | print("they are equal") 269 | #print the message 270 | @endcode 271 | 272 | @param self this object pointer reference 273 | @param other object A second Card to compare with 274 | @return True is this Card equals other Card rank and suit, 275 | otherwise False. If other is not of Card class type then 276 | it will be passed to the object class, which will return False. 277 | ''' 278 | if isinstance(other, self.__class__): 279 | return (self.get_rank() == other.get_rank()) and \ 280 | (self.get_suit() == other.get_suit()) 281 | return NotImplemented # this sends the test to the parent object class 282 | 283 | # ------------------------------------------------------------------------- 284 | def __ne__(self, other): 285 | '''! 286 | Returns the result of not equals (!=) between this 287 | Card and other Card. 288 | 289 | Overwrites the default object class behavior. The default object 290 | class compares instanciated Card's addresses. Thus two cards with the 291 | same rank and suit will not be equal. I want two cards of the same 292 | rank and suit will be equal. 293 | 294 | @param self this object pointer reference 295 | @param other object A second Card to compare with 296 | @return True if this Card not equal to other Card, otherwise False. 297 | If other is not of Card class type then passed to the object class, 298 | which will return True. 299 | ''' 300 | if isinstance(other, self.__class__): 301 | return not self.__eq__(other) 302 | return NotImplemented 303 | 304 | # ------------------------------------------------------------------------- 305 | def __lt__(self, other): 306 | '''! 307 | Less than method. Less than does not have meaning for 308 | a general purpose playing card, so it is disabled. 309 | @param self this object pointer reference 310 | @param other object A second Card to compare with 311 | @exception NotImplmentedError 312 | ''' 313 | raise NotImplementedError("LT does not have meaning, so not permitted") 314 | 315 | # ------------------------------------------------------------------------- 316 | def __le__(self, other): 317 | '''! 318 | Less or equal than method. Less than or equal does not have meaning for 319 | a general purpose playing card, so it is disabled. 320 | @param self this object pointer reference 321 | @param other object A second Card to compare with 322 | @exception NotImplmentedError 323 | ''' 324 | raise NotImplementedError("LE does not have meaning, so not permitted") 325 | 326 | # ------------------------------------------------------------------------- 327 | def __gt__(self, other): 328 | '''! 329 | Greater than method. Greater than does not have meaning for 330 | a general purpose playing card, so it is disabled. 331 | @param self this object pointer reference 332 | @param other object A second Card to compare with 333 | @exception NotImplmentedError 334 | ''' 335 | raise NotImplementedError("GT does not have meaning, so not permitted") 336 | 337 | # ------------------------------------------------------------------------- 338 | def __ge__(self, other): 339 | '''! 340 | Greater than or equal method. Greater than or equal does not have 341 | meaning for a general purpose playing card, so it is disabled. 342 | @param self this object pointer reference 343 | @param other object A second Card to compare with 344 | @exception NotImplmentedError 345 | ''' 346 | raise NotImplementedError("GE does not have meaning, so not permitted") 347 | 348 | # ------------------------------------------------------------------------- 349 | def __hash__(self): 350 | """! 351 | Override the default hash behavior 352 | (that returns the id of the object). 353 | The new hash will be the two saved values that define which card 354 | this class represents. 355 | @param self this object pointer reference 356 | @return hash code 357 | """ 358 | return hash(tuple([self.get_rank(), self.get_suit()])) 359 | # ------------------------------------------------------------------------- 360 | def __bool__(self): 361 | """! 362 | This method is called when the card is used in a boolean expression. 363 | Since a playing card has no logical reason nor meaning in a boolean 364 | expression we are raising an exception to avoid an incorrect use of 365 | a card. 366 | """ 367 | raise ValueError("bool() not permitted") 368 | 369 | # ------------------------------------------------------------------------- 370 | # end class Card() 371 | 372 | 373 | class Shoe(object): 374 | '''! 375 | The Shoe class represents a playing card shoe. That is, a device designed 376 | to hold a large number of playing cards, and deliver them to a card game 377 | as requested. 378 | 379 | The Shoe is normally not changed. Once created, it can be reused many 380 | times. Normal operation would use the reset() method to start a new 381 | shoe process. The cards within the shoe are shuffled and reused. 382 | But once created, a shoe will normally remain until end of game(s). 383 | 384 | Example usage: 385 | @code{.py} 386 | # create an 8 deck shoe for playing Baccarat 387 | shoe = Shoe(8) 388 | shoe.shuffle() 389 | shoe.set_cut_card(-14) 390 | # deal one card to player and one card to banker 391 | player1 = shoe.deal() 392 | banker1 = shoe.deal() 393 | @endcode 394 | 395 | @see Card 396 | ''' 397 | 398 | # ------------------------------------------------------------------------- 399 | def __init__(self, number_decks=None): 400 | '''! 401 | Create a shoe of a specified number of decks of playing cards. 402 | 403 | The default value for number_decks is one. 404 | 405 | Different card games use a shoe composed of a different number of 406 | decks of cards. Normally a game of Baccarat would use 8 decks. 407 | Blackjack might use 1 or 2 or 6. War would use 6. 408 | 409 | Example usage: 410 | @code{.py} 411 | import pybaccarat.playingcards 412 | shoe = pybaccarat.playingcards.Shoe(8) #create 8 deck shoe 413 | shoe.reset() 414 | shoe.shuffle() 415 | @endcode 416 | 417 | An alternative syntax has been added. That allows you to create a shoe 418 | based on the constructor being passed an array of Cards. That is, each 419 | element of the array must be of type Card class. This will allow you 420 | to create your own unique shoe. 421 | 422 | Example usage: 423 | @code{.py} 424 | # Create a shoe that consists of only 6 cards specified below. 425 | # In this case we are running a blackjack tournament, so a special 426 | # deck with just these 6 cards are shuffled and dealt out to assign 427 | # tournament starting positions for a 6 spot tournament table. 428 | import pybaccarat.playingcards 429 | shoe = pybaccarat.playingcards.Shoe([ Card(1,'s'), Card(2,'s'), 430 | Card(3,'s'), Card(4,'s'), Card(5,'s'), Card(6,'s') ]) 431 | shoe.reset() 432 | shoe.shuffle() 433 | for i in range(6): 434 |   print(shoe.deal()) 435 | @endcode 436 | 437 | @param self this object pointer reference 438 | @param number_decks integer number of decks. 439 | The default value is 1. Legal range is 1 to 12. 440 | The alternate constructor will allow you to pass in an arraay of 441 | Cards instead of just a single integer for the number of decks. 442 | @exception ValueError 443 | If the input parameter number_decks is not a legal 444 | integer. 445 | @todo need a finite limit to number_decks 446 | 447 | Shoe() 448 | Shoe(8) 449 | Shoe("PBPPBBB") 450 | Shoe([Card(43),Card(37),]) 451 | Shoe(0) 452 | s.save_shoe("filespec") 453 | s.load_shoe("filespec") 454 | ''' 455 | # 456 | # validate params 457 | # 458 | self.__cards = [] 459 | if number_decks is None: 460 | number_decks = 1 461 | if not isinstance(number_decks, int): 462 | # 463 | # Not an integer. What about an array of cards? 464 | # 465 | self.__enable_shuffle = False # don't shuffle this custom shoe 466 | if isinstance(number_decks, list): 467 | for i in number_decks: 468 | if not isinstance(i, Card): 469 | raise ValueError("non-card type params(%s)" % type(i)) 470 | self.__cards.append(i) 471 | elif isinstance(number_decks, str): 472 | ten = Card(10, 's') 473 | nine = Card(9, 's') 474 | ace = Card(1, 's') 475 | for i in range(2): 476 | self.__cards.append(ace) 477 | for i in number_decks: 478 | if i == "B": 479 | self.__cards.append(ten) #p1 480 | self.__cards.append(ten) #b1 481 | self.__cards.append(ten) #p2 482 | self.__cards.append(nine) #b2 483 | elif i == "P": 484 | self.__cards.append(ten) #p1 485 | self.__cards.append(ten) #b1 486 | self.__cards.append(nine) #p2 487 | self.__cards.append(ten) #b2 488 | elif i == "T": 489 | self.__cards.append(nine) #p1 490 | self.__cards.append(nine) #b1 491 | self.__cards.append(nine) #p2 492 | self.__cards.append(nine) #b2 493 | else: 494 | raise ValueError("number_decks(%s) invalid value(%s)" % 495 | (str(number_decks), i)) 496 | # 7 = 14 - (4-6)(last hand) - (1-3)(pent-ultimate hand) 497 | for i in range(7): 498 | self.__cards.append(ace) 499 | # pass.... filespec? 500 | # pass.... BPT sequence 501 | #raise ValueError("number_decks(%s) not a valid syntax" % 502 | # str(number_decks)) 503 | # 504 | else: 505 | raise ValueError("number_decks(%s) not a valid syntax" % 506 | str(number_decks)) 507 | else: 508 | # 509 | # number_decks is an integer 510 | # 511 | # number_decks==0 is permitted so that a custom shoe can be 512 | # loaded. Do not enable shuffle for 0 deck shoe. 513 | if (number_decks < 0) or (12 < number_decks): 514 | raise ValueError("number_decks(%s) not a legal value" % 515 | str(number_decks)) 516 | self.__enable_shuffle = (0 < number_decks) 517 | for _ in range(number_decks): 518 | for _ in range(52): 519 | self.__cards.append(Card(_)) 520 | 521 | self.__next_card = 0 522 | self.__cut_card_position = 0 523 | self.reset() 524 | 525 | # ------------------------------------------------------------------------- 526 | def reset(self): 527 | '''! 528 | Reset the shoe to start a new shoe. 529 | This method will not shuffle the cards nor assign the cut card 530 | position. 531 | 532 | @code{.py} 533 | import playingcards 534 | shoe = playingcards.Shoe() 535 | shoe.reset() 536 | @endcode 537 | 538 | @param self this object pointer reference 539 | ''' 540 | self.__next_card = 0 541 | self.set_cut_card(0) 542 | 543 | # ------------------------------------------------------------------------- 544 | def shuffle(self): 545 | '''! 546 | Shuffle cards in the shoe. Uses the standard Python 547 | random package shuffle method. 548 | 549 | Example usage: 550 | @code{.py} 551 | import playingcards 552 | shoe = playingcards.Shoe() 553 | shoe.shuffle() 554 | @endcode 555 | 556 | @param self this object pointer reference 557 | @see 559 | random.shuffle() 560 | @todo Find a way to attach a user's shuffle method instead of our 561 | own default. 562 | ''' 563 | if self.__enable_shuffle: 564 | random.shuffle(self.__cards) 565 | 566 | # ------------------------------------------------------------------------- 567 | def set_cut_card(self, position): 568 | '''! 569 | Assign the cut card position in the shoe. 570 | 571 | Example usage: 572 | @code{.py} 573 | import playingcards 574 | shoe = playingcards.Shoe() 575 | shoe.set_cut_card(-14) 576 | @endcode 577 | 578 | @param self this object pointer reference 579 | @param position integer index position within the shoe. 580 | 0 means at the very start of the shoe. 581 | A position of 0 would mean that even before the first card has 582 | been dealt the cut card has been seen. 583 | The max value is the length of the shoe (the very end). 584 | For a 6 deck shoe that would mean a maximum value of 312 585 | (6 times 52). A cut card position at the very end of the shoe 586 | would mean the cut card would never be seen. 587 | A negative value is allowed and means position 588 | from the end of the shoe. 589 | So a value of -14 would mean count 14 from the end of the shoe. 590 | @exception ValueError 591 | If the input parameter position is not a legal integer 592 | value, then throw a ValueError exception. 593 | @see cut_card_seen() 594 | ''' 595 | # 596 | # validate param 597 | # 598 | if not isinstance(position, int): 599 | raise ValueError("position(%s) not an integer" % str(position)) 600 | if position < 0: 601 | # if negative position, then adjust it from the end of the shoe. 602 | position += len(self.__cards) 603 | if position < 0: 604 | raise ValueError("cut card position value too small") 605 | if len(self.__cards) < position: 606 | raise ValueError("cut card position value too big") 607 | # 608 | # save data 609 | # 610 | self.__cut_card_position = position 611 | 612 | # -------------------------------------------------------------------- 613 | def cut_card_seen(self): 614 | '''! 615 | Return has the cut card been seen? 616 | 617 | Example usage: 618 | @code{.py} 619 | import playingcards 620 | shoe = new playingcards.Shoe() 621 | shoe.set_cut_card(1) 622 | # Query before first card dealt 623 | print(shoe.cut_card_seen()) #False 624 | card1 = shoe.deal() 625 | # Query after first card dealt 626 | print(shoe.cut_card_seen()) #True 627 | @endcode 628 | 629 | @param self this object pointer reference 630 | @return True if yes, False if no. 631 | @see set_cut_card 632 | ''' 633 | return self.__cut_card_position <= self.__next_card 634 | 635 | # -------------------------------------------------------------------- 636 | def deal(self): 637 | '''! 638 | Deal a Card from the Shoe. 639 | 640 | Example usage: 641 | @code{.py} 642 | import playinycards 643 | shoe = playinycards.Shoe() 644 | #shoe.shuffle() #no shuffle here 645 | card = shoe.deal() 646 | print(card) #displays 'Ac' 647 | # Since a brand new shoe was not shuffled I know ace of clubs is first. 648 | @endcode 649 | 650 | @param self this object pointer reference 651 | @return the next card from the Shoe, or 652 | None if no card is available. 653 | ''' 654 | card = None 655 | if self.__next_card < len(self.__cards): 656 | # we still have cards to deal, so get the next one 657 | card = self.__cards[self.__next_card] 658 | self.__next_card += 1 659 | return card 660 | 661 | # ------------------------------------------------------------------------- 662 | def discard_adjust_baccarat(self, type): 663 | '''! 664 | The discard pile is our shoe prior to the next_card index. 665 | Some games will discard the used cards in a specific manor (i.e. 666 | Baccarat). They do that so that when a player complains about the 667 | last hand after the dealer has swept the cards away, the pit card 668 | back the cards out of the discard pile to show the prior hands. 669 | 670 | 2P2B: 671 | deal: p1 b1 p2 b2 = -4 -3 -2 -1 672 | ^--------^ swap -4 -1 673 | ^--^ swap -2 -1 674 | sweep: b2 b1 p1 p2(top) 675 | 3P2B: 676 | deal: p1 b1 p2 b2 p3 677 | ^--------^ swap -5 -2 678 | ^--^ swap -3 -2 679 | sweep: b2 b1 p1 p2 p3(top) 680 | 2P3B: 681 | deal: p1 b1 p2 b2 b3 682 | ^-----------^ swap -5 -1 683 | ^-----^ swap -4 -2 684 | ^--^ swap -3 -2 685 | ^--^ swap -2 -1 686 | sweep: b3 b2 b1 p1 p2(top) 687 | 3P3B: 688 | deal: p1 b1 p2 b2 p3 b3 689 | ^--------------^ swap -6 -1 690 | ^-----^ swap -5 -3 691 | ^--^ swap -4 -3 692 | ^-----^ swap -3 -1 693 | ^--^ swap -2 -1 694 | sweep: b3 b2 b1 p1 p2 p3(top) 695 | ''' 696 | if type=="2P2B": 697 | if 3 <= self.__next_card: 698 | card1 = self.__cards[self.__next_card - 1] 699 | card2 = self.__cards[self.__next_card - 2] 700 | #ard3 = - 3] 701 | card4 = self.__cards[self.__next_card - 4] 702 | self.__cards[self.__next_card - 4] = card1 703 | # - 3] = card3 704 | self.__cards[self.__next_card - 2] = card4 705 | self.__cards[self.__next_card - 1] = card2 706 | elif type=="3P2B": 707 | if 4 <= self.__next_card: 708 | #ard1 = - 1] 709 | card2 = self.__cards[self.__next_card - 2] 710 | card3 = self.__cards[self.__next_card - 3] 711 | #ard4 = - 4] 712 | card5 = self.__cards[self.__next_card - 5] 713 | self.__cards[self.__next_card - 5] = card2 714 | # - 4] = card4 715 | self.__cards[self.__next_card - 3] = card5 716 | self.__cards[self.__next_card - 2] = card3 717 | # - 1] = card1 718 | elif type=="2P3B": 719 | if 4 <= self.__next_card: 720 | card1 = self.__cards[self.__next_card - 1] 721 | card2 = self.__cards[self.__next_card - 2] 722 | card3 = self.__cards[self.__next_card - 3] 723 | card4 = self.__cards[self.__next_card - 4] 724 | card5 = self.__cards[self.__next_card - 5] 725 | self.__cards[self.__next_card - 5] = card1 726 | self.__cards[self.__next_card - 4] = card2 727 | self.__cards[self.__next_card - 3] = card4 728 | self.__cards[self.__next_card - 2] = card5 729 | self.__cards[self.__next_card - 1] = card3 730 | elif type=="3P3B": 731 | if 5 <= self.__next_card: 732 | # [-6] = card1 733 | # [-5] = card3 734 | card1 = self.__cards[self.__next_card - 1] 735 | card2 = self.__cards[self.__next_card - 2] 736 | card3 = self.__cards[self.__next_card - 3] 737 | card4 = self.__cards[self.__next_card - 4] 738 | card5 = self.__cards[self.__next_card - 5] 739 | card6 = self.__cards[self.__next_card - 6] 740 | self.__cards[self.__next_card - 6] = card1 741 | self.__cards[self.__next_card - 5] = card3 742 | self.__cards[self.__next_card - 4] = card5 743 | self.__cards[self.__next_card - 3] = card6 744 | self.__cards[self.__next_card - 2] = card4 745 | self.__cards[self.__next_card - 1] = card2 746 | else: 747 | pass # not a legal type 748 | 749 | # ------------------------------------------------------------------------- 750 | def save_shoe(self, filespec): 751 | '''! 752 | Save this shoe to a disk file specified. 753 | ''' 754 | with open(filespec, 'w') as f: 755 | i = 0 756 | # write a psuedo burn first 757 | line = "" 758 | burn_size = self.__cards[i].get_rank() 759 | if burn_size > 9: 760 | burn_size = 10 761 | while i < len(self.__cards) and i < (1 + burn_size): 762 | line += str(self.__cards[i])+" " 763 | i += 1 764 | f.write(line+"\n") 765 | # walk the shoe writing psuedo hands, 5 cards 766 | while i < len(self.__cards)-14: 767 | line = "" 768 | j = 0 769 | while j < 5: 770 | line += str(self.__cards[i])+" " 771 | j += 1 772 | i += 1 773 | f.write(line+"\n") 774 | # write a psuedo end of shoe bolt of what's left 775 | line = "" 776 | while i < len(self.__cards): 777 | line += str(self.__cards[i])+" " 778 | i += 1 779 | f.write(line+"\n") 780 | 781 | # -------------------------------------------------------------------- 782 | def load_shoe(self, filespec): 783 | '''! 784 | Load a shoe from a disk file specified. 785 | ''' 786 | with open(filespec, 'r') as f: 787 | lines = f.readlines() 788 | for line in lines: 789 | if line.startswith("#END"): 790 | break 791 | if line.startswith("#"): 792 | continue 793 | st = line.split() 794 | for new_card in st: 795 | if new_card.startswith("#"): 796 | break 797 | self.__cards.append(Card(new_card)) 798 | 799 | # ------------------------------------------------------------------------- 800 | # end class Shoe() 801 | --------------------------------------------------------------------------------