commit 1e07310ed5be34303fdec00278b2d68032777047 Author: Rodolphe Breard Date: Thu Aug 20 12:44:32 2015 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e5a392 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Misc stuff +*~ +\#* +.\#* diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..324359e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright (c) 2015 Rodolphe Breard + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..906140d --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Happy Slot Machine + +This software is a python slot machine that cheats on the users. + + +## Usage + +```ShellSession +$ python happy_slot_machine.py +``` + +This software has been written for python 3 but should also work on python 2. + + +## Numbers selection + +Instead of drawing 3 random numbers, we draw 4 of them. If they are not all different or identical, two identical numbers are set on the first two positions and a different one in the third. Then, the three first are returned. Using 10 digits, this leads to a quite low chance of success and the probabilities to display a losing combination starting with 2 identical numbers are significantly high. + +A perspicacious player could suspect the machine is cheating by observing that, in case of two identical numbers, the two first are always the identical ones. In order to prevent that, in this particular case, we add an extra rule: there is 25% chances first and third numbers are permuted and 25% chances second and third numbers are permuted. + +* P(success) = 1/(10^3) = 0.001 +* P(failure) = 1 - P(success) = 0.999 +* P(all different) = (9*8*7) / 10^3 = 0.504 +* P(failure & two identical) = 1 - P(success) - P(all different) = 0.495 +* P(failure & first identical to second) = P(two identical) * 0.5 = 0.2475 +* P(failure & first identical to third) = P(two identical) * 0.25 = 0.12375 +* P(failure & second identical to third) = P(two identical) * 0.25 = 0.12375 + +In comparison, a non-manipulated machine would have the following probabilities: + +* P(success) = 1/(10^2) = 0.01 +* P(failure) = 1 - P(success) = 0.99 +* P(all different) = (9*8) / 10^2 = 0.72 +* P(failure & two identical) = 1 - P(success) - P(all different) = 0.27 +* P(failure & first identical to second) = P(two identical) * (1/3) = 0.09 +* P(failure & first identical to third) = P(two identical) * (1/3) = 0.09 +* P(failure & second identical to third) = P(two identical) * (1/3) = 0.09 diff --git a/assets/0.txt b/assets/0.txt new file mode 100644 index 0000000..ce38d5d --- /dev/null +++ b/assets/0.txt @@ -0,0 +1,6 @@ + ____ + .' '. +| .--. | +| | | | +| `--' | + '.____.' diff --git a/assets/1.txt b/assets/1.txt new file mode 100644 index 0000000..cf302b9 --- /dev/null +++ b/assets/1.txt @@ -0,0 +1,6 @@ + __ + / | + `| | + | | + _| |_ + |_____| diff --git a/assets/2.txt b/assets/2.txt new file mode 100644 index 0000000..62669a6 --- /dev/null +++ b/assets/2.txt @@ -0,0 +1,6 @@ + _____ + / ___ `. +|_/___) | + .'____.' +/ /_____ +|_______| diff --git a/assets/3.txt b/assets/3.txt new file mode 100644 index 0000000..ec773f0 --- /dev/null +++ b/assets/3.txt @@ -0,0 +1,6 @@ + ______ + / ____ `. + `' __) | + _ |__ '. +| \____) | + \______.' diff --git a/assets/4.txt b/assets/4.txt new file mode 100644 index 0000000..8ccddff --- /dev/null +++ b/assets/4.txt @@ -0,0 +1,6 @@ + _ _ +| | | | +| |__| |_ +|____ _| + _| |_ + |_____| diff --git a/assets/5.txt b/assets/5.txt new file mode 100644 index 0000000..a8e3cd1 --- /dev/null +++ b/assets/5.txt @@ -0,0 +1,6 @@ + _______ +| _____| +| |____ +'_.____''. +| \____) | + \______.' diff --git a/assets/6.txt b/assets/6.txt new file mode 100644 index 0000000..e4d5841 --- /dev/null +++ b/assets/6.txt @@ -0,0 +1,6 @@ + ______ +.' ____ \ +| |____\_| +| '____`'. +| (____) | +'.______.' diff --git a/assets/7.txt b/assets/7.txt new file mode 100644 index 0000000..0e68c80 --- /dev/null +++ b/assets/7.txt @@ -0,0 +1,6 @@ + _______ +| ___ | +|_/ / / + / / + / / + /_/ diff --git a/assets/8.txt b/assets/8.txt new file mode 100644 index 0000000..64bb05b --- /dev/null +++ b/assets/8.txt @@ -0,0 +1,6 @@ + ____ + .' __ '. + | (__) | + .`____'. +| (____) | +`.______.' diff --git a/assets/9.txt b/assets/9.txt new file mode 100644 index 0000000..3668762 --- /dev/null +++ b/assets/9.txt @@ -0,0 +1,6 @@ + ______ +.' ____ '. +| (____) | +'_.____. | +| \____| | + \______,' diff --git a/assets/failure.txt b/assets/failure.txt new file mode 100644 index 0000000..a4b8a23 --- /dev/null +++ b/assets/failure.txt @@ -0,0 +1,6 @@ + __ __ _ _ _ + \ \ / / | | | | | | + \ V /___ _ _ | | ___ ___| |_| | + \ // _ \| | | | | |/ _ \/ __| __| | + | | (_) | |_| | | | (_) \__ \ |_|_| + \_/\___/ \__,_| |_|\___/|___/\__(_) diff --git a/assets/reset.txt b/assets/reset.txt new file mode 100644 index 0000000..5541163 --- /dev/null +++ b/assets/reset.txt @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/slot.txt b/assets/slot.txt new file mode 100644 index 0000000..c5d7f2b --- /dev/null +++ b/assets/slot.txt @@ -0,0 +1,11 @@ + .----------------. +| .--------------. | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| '--------------' | + '----------------' diff --git a/assets/success.txt b/assets/success.txt new file mode 100644 index 0000000..3dc4144 --- /dev/null +++ b/assets/success.txt @@ -0,0 +1,6 @@ + __ __ _ + \ \ / / | | + \ V /___ _ _ __ _____ _ __ | | + \ // _ \| | | | \ \ /\ / / _ \| '_ \| | + | | (_) | |_| | \ V V / (_) | | | |_| + \_/\___/ \__,_| \_/\_/ \___/|_| |_(_) diff --git a/happy_slot_machine.py b/happy_slot_machine.py new file mode 100755 index 0000000..ff83ee5 --- /dev/null +++ b/happy_slot_machine.py @@ -0,0 +1,210 @@ +#!/bin/env python +# +# Copyright (c) 2015 Rodolphe Breard +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +""" +Happy Slot Machine + +This software is a python slot machine that cheats on the users. The numbers +selection function is designed so it is very unlikely to get three identical +numbers and increases the chances for the two firsts numbers to be the same. +""" + +from collections import Counter +from random import randint +import curses +import time +import os + + +def get_asset(asset_name): + ''' + Loads an asset in memory. + ''' + file_name = '{}.txt'.format(asset_name) + asset_path = os.path.join('assets', file_name) + with open(asset_path) as f: + return [l.rstrip('\n') for l in f.readlines()] + +slot_asset = get_asset('slot') +numbers_assets = [get_asset(n) for n in range(10)] +results_assets = { + 'success': get_asset('success'), + 'failure': get_asset('failure'), + 'reset': get_asset('reset'), +} + + +def get_numbers(): + ''' + Returns a list of 3 not-so-random numbers. + See the attached README.md for more details. + ''' + nbs = [randint(0, 9) for _ in range(4)] + set_len = len(set(nbs)) + if set_len in [2, 3]: + nbs.sort(key=Counter(nbs).get, reverse=True) + nbs[2], nbs[3] = nbs[3], nbs[2] + rand_position = randint(1, 100) + if rand_position < 50: + nbs[0], nbs[2] = nbs[2], nbs[0] + elif rand_position < 25: + nbs[1], nbs[2] = nbs[1], nbs[0] + return nbs[:3] + + +def draw_result(stdscr, status): + ''' + Draws the status of the current round. + + stdscr: curses window object + status: string, must be either "success", "failure" or "reset" + ''' + for i, line in enumerate(results_assets[status]): + stdscr.addstr(i + 13, 1, line) + stdscr.refresh() + + +def draw_slot(stdscr, offset): + ''' + Draws an empty slot. + + stdscr: curses window object + offset: integer representing the slot's position + ''' + off = offset * (len(slot_asset[0]) + 3) + 1 + for i, line in enumerate(slot_asset): + stdscr.addstr(i + 2, off, line) + + +def draw_slots(stdscr): + ''' + Draws the 3 empty slots. + + stdscr: curses window object + ''' + for i in range(3): + draw_slot(stdscr, i) + stdscr.refresh() + + +def draw_raw_number(stdscr, nb, offset): + ''' + Draws a number in a given slot. + + stdscr: curses window object + nb: integer representing number to display + offset: integer representing the slot's position + ''' + nb = numbers_assets[nb] + off = offset * (len(nb[0]) + 13) + 6 + for i, line in enumerate(nb): + stdscr.addstr(i + 4, off, line) + stdscr.refresh() + + +def random_excepted(nb): + ''' + Returns a random number that cannot be the one passed as a parameter. + + nb: integer representing the number to avoid + ''' + while True: + n = randint(0, 9) + if n != nb: + return n + + +def numbers_to_display(nb): + ''' + Yields a series of numbers that should be displayed on a slot. + + nb: integer representing the last number to be yielded + ''' + n = None + for _ in range(10): + time.sleep(0.15) + n = random_excepted(n) + yield n + yield nb + + +def draw_number(stdscr, nb, offset): + ''' + Draws a number in a given slot with an animation. + + stdscr: curses window object + nb: integer representing number to display + offset: integer representing the slot's position + ''' + for n in numbers_to_display(nb): + draw_raw_number(stdscr, n, offset) + + +def play(stdscr): + ''' + Plays a new round. + + stdscr: curses window object + ''' + nbs = get_numbers() + draw_slots(stdscr) + draw_result(stdscr, 'reset') + for i, nb in enumerate(nbs): + draw_number(stdscr, nb, i) + draw_result(stdscr, 'success' if len(set(nbs)) == 1 else 'failure') + + +def clean_input(stdscr): + ''' + Removes all unread data from the standard input. + ''' + stdscr.nodelay(1) + while stdscr.getch() != -1: + pass + stdscr.nodelay(0) + + +def main(stdscr): + ''' + Initialize the screen and the commands. + + stdscr: curses window object + ''' + height, width = stdscr.getmaxyx() + curses.curs_set(0) + stdscr.clear() + + stdscr.addstr(" SLOT MACHINE", curses.A_REVERSE) + stdscr.chgat(-1, curses.A_REVERSE) + + h = height - 1 + stdscr.addstr(h, 0, " Press Q to quit, P to play.", curses.A_REVERSE) + stdscr.chgat(h, 0, -1, curses.A_REVERSE) + draw_slots(stdscr) + + while True: + stdscr.refresh() + clean_input(stdscr) + key = stdscr.getch() + if key in [ord('q'), ord('Q')]: + break + elif key in [ord('p'), ord('P')]: + play(stdscr) + + +if __name__ == '__main__': + curses.wrapper(main)