├── 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 | - 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 |
26 | - mkdir $BASE/github.com/fulkgl
27 | - chdir $BASE/github.com/fulkgl
28 | - git clone https://github.com/fulkgl/PyBaccarat.git
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 | - Clean up temp files.
62 |
63 | - del /s/q build dist pybaccarat.egg-info
64 | - rmdir /s/q build dist pybaccarat.egg-info
65 | - del /s/q tests\*.pyc pybaccarat\*.pyc
66 | - rmdir pybaccarat\__pycache__
67 |
68 | - Build the code
69 |
python setup.py build bdist sdist
70 | - Run the unit tests
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 | | 1 | ace |
195 | | 2 to 10 | are numbered cards 2 to 10 |
196 | | 11 | jack |
197 | | 12 | queen |
198 | | 13 | king |
199 |
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 | | 'c' | clubs |
212 | | 'd' | diamonds |
213 | | 'h' | hearts |
214 | | 's' | spades |
215 |
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 |
--------------------------------------------------------------------------------