Before designing or building a piece of software it is important that the developers are aware of the problem they are building a solution to and understand what they must do in order to provide a successful product. One way to understand the market of the product better is to distribute questionnaires and surveys via the internet or in person. By asking the people that the product will be built for what exactly they want, efforts can be much more accurately targeted. Using social media websites such as Twitter and Facebook, and by asking for support from organisations that the target market would likely follow on such websites, a development team can quickly gauge what parts of a solution are most important to the users, and how they can build a product that will cover those aspects correctly.
Developers can also look to other similar projects and products for ways in which to improve. By looking at popular solutions' help forum, issue tracking and discussion websites, the features most often missed from a piece of software can be quickly determined. If users often feel the need for a tool that isn't available in their current software packages, a new product that has this tool can easily gain attention.
As we had been tasked to create the popular card game Blackjack in a programming language of our choice – I chose Python – I looked online for previously-build versions of the game, in the hopes of identifying common errors in such programs. I found that when dealing cards, many programs would generate a random integer between one and eleven, inclusive. This is because royal cards in Blackjack all count as ten points and an ace as either one or eleven points. In order to accurately emulate a real game of Blackjack, however, one must generate a random integer with a maximum value of thirteen. This will give every card the correct probability of being dealt, which has a very significant effect on the game's outcome.
During and after the process of collecting information about the pitfalls of previous solutions and the wants of the target market, the development team will build a list of requirements. This list will be consulted throughout the project's life-cycle, in order that the programmers to not make the same mistakes others have made. The list of requirements will likely outline the defining attributes of the solution, as well as the more specific, new features that will set the program apart from its competition.
For my Blackjack program I created the following list of requirements:
Before developing the diagrams to show the flow of data and execution in Blackjack, I created a representation of a typical run through the program.
Firstly, a banner made with box-drawing characters is shown, and the user is welcomed to the game:
+-----------------+
| BLACKJACK |
+-----------------+
Welcome to the game!
Secondly, the game deals two cards to the player, telling them which they have been dealt and what the cards are worth. If an ace is dealt, the user will be asked to specify one or eleven points.
You have been dealt an Ace of Hearts and a Four of Clubs.
Would you like the Ace to count as 1 point or 11 points? [1/11]: 11
Your score is now 15.
The game then asks the player, as long as they haven't already exceed twenty-one points, whether they would like to stick or twist. If they stick, the game will complete the game as the dealer, otherwise another card will be dealt and the new score shown. Answers can be in lower case or upper case; either will work.
Would you like to stick or twist? [S/t]: t
You have been dealt a Jack of Diamonds.
This makes your score 25.
Based on the new score, whether or not it is greater than twenty-one, and the number of times the player has already twisted, the question will be asked again or the game will be ended.
You have scored more than 21 and gone bust. The Dealer wins!
OR
You have twisted three times, and are now stuck.
Your final score is 20.
OR
Your score is now 20.
If the game has not ended, it will end once the player has stuck and the AI dealer has finished playing. The victorious participant will be announced, and the wins score-table shown. If the participants both finish with the same score, no wins will be awarded.
Would you like to stick or twist? [S/T]: s
You have stuck. Your final score is 20.
You scored higher than the dealer. You win!
+--------+---+
| You | 1 |
+--------+---+
| Dealer | 0 |
+--------+---+
Once the game has ended, the user will be asked if they'd like to play again. If they enter nothing, the game will default to "Y". If the player chooses to play another round, the banner will not be shown again, but a line dividing the round will be outputted.
Would you like to play another round? [Y/n]: n
Thank-you for playing!
At any point during the game, the player can force the program to quit by pressing Ctrl + C. The program will detect this, and exit without an error code. If a round is in progress, neither player will be credited with a win.
During the design stage of a software project, data flow diagram, of DFDs, are often created by the developers to plan the movement of information round the program during operation. Having this information in mind when designing the other aspects of the program can improve the efficiency of the final product, make development easier, and save the developers time that would otherwise be spend re-writing code. In order to better understand the flow of data around the Blackjack program, I designed the data flow diagram shown below.
game()
).As well as creating diagrams to show the flow of data around a program and between modules of a solution, developers often create diagrams to show the flow of the actual program. Using different shapes to represent different operations in a program, one can quickly prototype an element of a program. In a flow diagram, standardised shapes represent certain stages of a process:
Rounded rectangles are used to signify the beginning and end of a process. In most flow diagrams there are only two, and there can only ever be one "start" shape. This is the first and last shape that an interpreter of the diagram will read, and cannot occur elsewhere in the process.
Rectangles represent commands or sub-processes that must be undertaken during the execution of the main process. The accuracy and level of detail of these commands is something that can vary. Thanks to abstraction in programming, a large and potentially tedious part of a solution can be condensed into a single command. Addition detail about the process that must be completed at such a stage could be given in a separate diagram.
Parallelograms are used to represent the commands and sub-processes that require with input or output. In the context of programming, this is most often interaction with the user, which could occur in the form of text, sound or possibly movement. This can be combined with a rhombus shape to perform a decision based on input, and the shape is often used to represent input and output in one stage.
Rhombi are used to show decision-making. Often compared to the if
statement found in almost all programming languages, a decision will typically have two outward routes, or potentially more. Decisions can appear in the form of a wide variety of questions, but most often only require a yes or no answer, simplifying the process of interpreting the diagram.
Arrows are used throughout flow diagrams to show the direction of travel around a process. These are particularly important when a diagram begins to grow, and also when decision blocks are used to create loops; a line carrying the interpreter back to an earlier part or forward to a later part of the program could be mistaken for an alternative route at its endpoint, were it not for the arrow-heads indicating direction.
In order to understand how the Blackjack game would work when programmed, I decided to create a flow diagram of the process. The list details what happens at each of the labelled stages.
In order to understand the data that the Blackjack program would use, I wrote a list of variables that I would create, along with their data types.
card
— In fact an array storing two integers, this contains the indices of a card's value and suit. It is generated by the card dealing function, and given as an argument to the value-adding function.
suit
— Used temporarily too hold the index of a cards suit in the suit name string array.
value
— As above, but for storing the face value of a card.
card_new
— Used when the player twists during a round. This temporarily stores the new card dealt by the card-dealing function, and is then passed to the value adding function.
game_over
— Potentially set during the main game process to later determine whether or not to compare scores. If either participant goes bust, for example, it will be set to true.
dealer_cont
— Enables the Dealer to continue the game even after the player has become stuck.
windows
— Set to true when the program is running on Windows. This is used by both the text highlighting code and the ASCII/box-drawing art, as Windows' Command Prompt does not support ANSI escape codes or Unicode characters.
wins_string
— Holds the data retrieved from a previous game's win table file.
wins_split
— The wins_string
variable, broken into an array and with its values parsed as integers. This becomes the global wins variable.
choice
— The data collected from the user when they are asked if they would like an ace to count as 1 point or eleven points.
action
— As above, but used when asking the user if they wish to stick or twist.
card_name
— Used both in the card naming function and in the main game process when calling upon it, this holds the string name of a card.
play_again
— As choice
and action
, but used when asking the user if they would like to play another round.
wins
— A global variable used to track the number of victories held by each participants.
wins_file
— The object used to handle the wins save file, both when saving the new wins table to disk and when loading it from a previous game.In order to efficiently and quickly develop software and to make programming more practical, a huge number of different data types are available to programmers. Although there are differences between certain languages, a handful of data types are shared by the vast majority of languages. Among these are integers; floats, doubles and decimals; booleans; strings; and, if considered another data-type, arrays. The majority of languages offer further types in addition to these, perhaps tailored to the language's typical usage.
Before writing the application in Python, I wrote some snippets of pseudo-code to further understand some particular parts of the program.
The card dealing function will work roughly as follows:
function cardDeal() // no arguments
card = randomInt(0,12) // range of 0-12 rather than 1-13, so that this
// can be used as an index
suit = randomInt(0,3) // spades, diamonds, clubs, hearts
return [card, suit]
The function to find the full name of a card will be based on this:
function formattedCardName(card) // array of two integers; return value
// of cardDeal
if card is 8 or ace // so that, e.g., "an eight", not "a eight"
a = "an"
else
a = "a"
cards = ["Ace", "Two", "Three", "Four", ...]
suits = ["Spades", "Diamonds", "Clubs", "Hearts"]
return a + cards[card[0]] + "of" + suits[card[1]]
// e.g., "an Eight of Clubs"
I also planned the text highlighting function:
highlight(colour, text) // integer, string
colours = ["*normal*", "*colour_1*", "*colour_2*", ...]
colour_hl = colours[colour] // use "colour" as an index
return colour_hl + text + colours[0]
When it came to the actual building stage of the project, I made a few decisions relating to the tools I would use.
Before choosing the actual programming language that I would write the game in, I decided to use the procedural paradigm, with elements of event driven programming used as well. I felt this was the most appropriate paradigm for the project, as the game is relatively simple in comparison to many others. I felt the game did not warrant the use of object-oriented programming, although the language I chose does fully support it, and that using event-driven programming to a fuller extent than I did was not suiting. I did consider the use of object-orientation when the project was being built, but felt that the source code may become over-complicated. As an example, a Blackjack game could use a Card
class. The class would hold the suit and face value of a random card, as well as methods to calculate its full name, point value, etc.
The language which I had the most experience in, had enjoyed using in the past, and most importantly fit my previously-decided criteria was Python. The language has pleasant syntax and is popular among other developers, meaning that support for the language is easier to come by on programming Q&A and forum websites. Python is also easy to port from one platform to another, if any modifications are needed to do so. The language is easy to read as a human, and uses a small amount of punctuation in its syntax. I also like to encourage the use of Python as a new programmer's first language, as it forces the use of good indentation, for example. Indenting code consistently and well is something that any programmer should do, and making it mandatory in order for the language to work ingrains good habit.
As I had decided to use Python and had opted not to use object-oriented programming features, I did not use any kind of IDE in the creation of my Blackjack program. Instead, I used the popular text editor, Sublime Text 2. This program does not offer all of the integration with compilation, version control, documentation, objective lists, etc. that a full IDE would, I didn't feel the need for these when building such a small application. Sublime Text is able to greatly improve the speed at which one can write code, by highlighting language syntax; providing completions based both on the detected language and previously used variable names; and scanning source code for errors each time the file is saved. For some parts of the development I also used the command-line editor Vim with the YouCompleteMe plugin and the Flatland colour scheme. This allows Vim to offer much the same functionality as Sublime Text, while offering convenient access to a command shell.
Using common techniques for testing, I was able to verify the functionality of the Blackjack program continuously. Based on the findings from my testing, I was able to improve the program and fix bugs.
I will test the software simply by playing the game. I will purposely make varying decisions in order that different parts of the game code are tested. For example, I will consistently stick after my first cards have been dealt, allowing the Dealer to easily win. This will test the code which handles the Dealer going bust or winning with a higher score. I may also modify small parts of code that I can be sure will not cause errors in order to skew the game results. As an example, I could prevent the cards dealt by the game from ever exceeding five, allowing me to check that the game successfully prevents more than three player twists in a round.
I found a few errors occurring during execution of the program. These were mostly trivial issues such as confused variable names and incorrectly remembering the names of standard Python functions and methods. These errors were easily fixed, but the majority were detected before runtime by the Python linter (syntax error detector) present in Sublime Text.
Commenting parts of source code is very important when writing pieces of software that are intended to be used for a long period of time, distributed widely or developed collaboratively with other developers. Using comments to explain pieces of code allows another developer – another set of eyes, although it could easily be oneself after some time has passed – to easily understand what's happening in the program. This will mean that any additions they make to the program will be more consistent with the styles and standards already used in the project. It will also make this process of contribution easier, and mean that otherwise plain source code can be used as an education resource.
Different programming languages implement commenting in different ways. The most common method is to use a delimiting character at the beginning of a line, which will comment out the rest of the line. The character, or occasionally characters, can usually be positioned anywhere on the line as well, although not always. Common delimiters include #
, //
and "
. For larger comments spanning multiple sentences, block commenting syntax also exists. These most often have a starting and ending delimiter, and do not end when text moves down to a new line. For example, in SGML-derived languages such as XML and HTML, <!--
and -->
are used, and in many other languages /*
and */
.
In my own Blackjack program, I made sure to add comments where I felt they were needed. This provided a level of documentation to a user or developer that was appropriate for the project's scale.
Naming convention and standards are also very important when programming. Standards a most commonly defined by the organisations behind a programming language, and tell programmers how they should format code. A common issue covered by standardisation documents is the naming of different program elements; variables, constant variables, compiler definitions, functions, classes, etc. For a variable that would store the score of a player in a game, one should name the variable in several ways; ScorePlayer
, scorePlayer
, scoreplayer
, score_player
or potentially score-player
. The differences may seem insignificant, but by declaring that all classes should be named in camel-case (e.g., ClassName
), code is consistent. Without needing to search through different files or other parts of code, one can instantly identify what an object being referred to is by its name.
Although many languages insist primarily that one remains consistent among one's own projects, others define standards language-wide. In the JavaScript and Java communities, variables are typically formatted in camel-case, with a lower-case first letter (e.g. variableName
). In contrast, the Python community prefers words to be divided by an underscore, and only lower-case to be used (e.g. variable_name
). This is partially outlined in the PEP 8 document, written by the original creator of the language and two other organisation members.
The variables that I created my own Blackjack program were compliant with the community's standard naming conventions. Had I created classes or used other programming structures, I would have used the corresponding standard format.
Indentation and spacing is also used to improve the readability of source code. By placing spaces either side of mathematical operators such as +
and =
, code can be made easier to read. When assigning values to variables, for instance, the spaces divide the name of variable and the equals sign. This makes identifying variables, particularly when several are declared at once, considerably easier. Indentation is useful to show when a program enters a conditional block, loop, class, function definition or similar. If a programmer writes an if
statement, the code to be executed if the condition equates to true will usually be padded from the left side of the window using a tab or a number of spaces. Developers are greatly divided on the matter of using spaces versus tabs, but both are usually accepted.
Indentation is not always purely for readability, however. As briefly mentioned before, Python requires code blocks to be correctly indented. The programming language runtime is capable of detecting the method by which the developer has indented their code (two spaces, four spaces, tabs, etc.), but this must be consistent throughout a script. The reason Python requires this is because its syntax does not require code blocks to be wrapped in brackets or braces as they are in many other languages. The indentation is therefore required in order to see where a block begins and ends. The PEP 8 document mentioned before specifies that Python developers should ideally use four spaces to indent their code.
I feel that I made sure to follow the standards of Python formatting in my project. I followed the guidelines of PEP 8, using spaces for indentation when I tend to prefer tabs, and ensuring that my lines of code did not exceed 79 characters in length.
Reading and writing good documentation about a language as well as a solution is important. While particularly useful for those using the software who are curious about its workings or wish to modify it, documentation is also useful when working in teams, so that everyone is able to understand each other's work. Documentation should explain parts of the code that are not already explained in enough detail in source code comments, detail the reasoning behind decisions made by the development team, and tell users how they can use and extend the software or contribute to the project. Documentation is particularly useful when made easily accessible in the software or on the web, making it easy for users and contributors to find the information they need quickly. As well as giving users plain information about a solution, documentation can also give examples that promote the user of a tool in a certain way. This is particularly relevant for programming languages, which are both a development tool and a solution themselves.
The PHP documentation is available online, and can be downloaded from the official website easily. The documentation is easily navigated and is formatted in a consistent way. This means that, as a developer, one is able to find specific pieces of information very quickly, such as the arguments accepted by a function. The examples provided in the documentation also use the naming conventions and standards preferred by the community, which almost subliminally encourages new developers to follow them.
I feel my Blackjack project may have fallen short in this respect. Although I feel there are adequate comments in the source code for a developer with reasonable Python experience to understand the program, I can understand that a novice may still be confused. I also did not write any other dedicated documentation.
Having a variety of data types to use also makes code more readable to another person. Particularly when using languages which require variables' types to be explicitly declared, such as C, data types can reveal a lot to a new developer. If a developer sees a variable named win_count
with the type integer, they could safely guess that the variable held the number a wins gained by a player in the program. If, however, the variable was of the type char
(used in C to represent strings) or float
(used to represent non-integer numbers), the developer would likely decide to look further into the use of the variable to understand it. In contrast, implicit-declaration languages can lead to new developers being confused as to the use of particular variables. Although one can usually guess the type of a variable by its use and declaration, this is not always the case.
My use of Python in the Blackjack project made full use of a number of a data types. I feel that they were used as efficiently as possible, and were used appropriately. I feel that only a more complex programming solution would have warranted the use of more complex data types.
By ensuring that the code one writes displays the attributes of good source code given above, the following results are commonly noticed.
Reliability of a program is improved by the use of good standards and conventions by the developers, as the likelihood of bugs arising is naturally decreased. If code is well formatted, members of the community and users of the software with an interest in the source code of it are more likely to validate the work of others and scan the code written but others for obvious flaws. Commenting code is particularly important in this scenario, and having people voluntarily look over one's source code is a valuable asset. Software being reliable is very important in certain "mission-critical" situations. Software that controls the hardware in factories and vehicles and that which is deployed on embedded systems not designed to ever be upgraded must be able to run for extended periods of time. Some systems may be expected to run flawlessly for several years without being restarted or upgraded, meaning that reliability is very important. Extensive testing and review of code are generally considered the most effective ways of achieving this.
Efficiency is important in many IT solutions. When the need to process a large amount of data, for example, is presented, software must be designed to produce as small a memory footprint and as few CPU cycles as possible, without drastically reducing the functionality of the solution. By reading documentation of a programming language, justifying choices made in the form of comments and creating and using variables and classes responsibly, the efficiency of code can be improved and the code reviewers made aware of reasoning behind otherwise questionable decisions. Not only is it important to create efficient software for processing data sets, but when creating time-critical software. The systems controlling fighter aircraft, for instance, must be able to interpret information collected by the various sensors aboard the plane and make changes to the position of rudders and valves in response, all within fractions of a second.
Software should also be robust, and be able to handle imperfect input from a user. If the environment that the software is running in not ideal, it should be able to either correct the situation, run with limited functionality or exit cleanly while warning the user. The program should not crash, potentially causing damage to other processes running on the system, while not providing the user with any useful information regarding why the program failed to execute. Robust software should also be able to withstand potentially unexpected peaks in traffic, or being assigned particularly large tasks. For example, the famed C10k problem arose as the web grew but server and router software was not capable of handling several concurrent (simultaneous) connections. A new wave of projects were started to tackle the problem of handling ten-thousand connections at once without crashing, and web servers in use today, such as nginx, are fully capable of this. The robustness of a software solution often correlates strongly with the efficiency of it.
A software solution must be usable, as well. Providing good documentation accounts for part of this, but making the software intuitive to use and following common software design patterns is more important. In order for users to feel they are able to use a software package well and be likely to recommend the solution to others, a piece of software mustn't make common tasks difficult to complete. By maintaining an online community that users are able to post questions and problems to – and importantly receive feedback to – a development team can improve the user experience of customers. As an example, it's important for a Linux command-line tool to respond as expected to common flags. If user writes -h
or --help
when calling a program, it is expected of software to return information regarding the use of the command. If a program fails to do this, information about the program and its command-line tools should have been added to the man
, or manual, pages upon installation.
Portability is also worth considering when building software. Even when using a language such as Python, which is almost entirely portable between different operating systems, one must occasionally make changes to software when it is required to run on both Linux and Windows, for example. Portability is even more of an issue when writing programs in languages such as C and C++. For many typical problems faced by developers when writing software, there are several solutions available. In many cases, there is a method which will allow the program to compile on all three major platforms – Windows, OS X and Linux – but which may have reduced performance. Alternatives will often be available, provided by Microsoft, Apple and the Linux community, which offer better performance, but are locked to the particular platform. Writing code that navigates around these issues, or handles all three platforms automatically, is a valuable skill.
Maintainability is important, particularly for projects with a long intended lifespan and a large community of developers and contributors. Writing code in a modular way, breaking parts of a program out into several files and directories, and building the software so that it reads configuration from files, for example, rather than hard-coded data, are all common ways of making a solution more maintainable. By making source code more manageable and easily customised, one increases the chances of community developers willingly taking control of a project when and if the original authors resign. A maintainable, and extendible, piece of software will naturally encourage people to make their own additions to the software and distribute them on the web, which in turn makes the solution more attractive to newcomers. Overall, maintainability of a software solution is very important in order for it long-term success.
In order to show the use of algorithms, functions, variables, user interaction and more, I build Blackjack digitally, using Python. The code I wrote is shown below, which will work optimally on both Linux/OS X and Windows. When using a terminal emulator that properly supports ANSI escape codes and Unicode characters, such are used. On Windows, escape sequences are disabled and DOS characters are used in place of their Unicode counterparts. You can also use Right click > "Save link/target as..." on this link to download the script.
#!/usr/bin/python3
# Blackjack
# Written by Blieque Mariguan
# as part of an IT BTEC.
# GPLv3 license applies
import sys
import platform
import os.path
import random
def load(path):
try:
if os.path.isfile(path):
wins_file = open(path, "r")
wins_string = wins_file.read()
wins_file.close()
wins_split = wins_string.split()
wins_split = [int(wins_split[0]), int(wins_split[1])]
return wins_split
else:
return [0, 0]
except IOError:
print(highlight(1, "Failed to open win table."))
except ValueError:
print(highlight(1, "Corrupted wins table file.") +
" Will overwrite (" +
highlight(4, "press Ctrl+C to prevent") +
").")
def save(path, wins):
try:
wins_file = open(path, "w+")
wins_file.write(str(wins[0]) + " " + str(wins[1]))
wins_file.close()
except IOError:
print(highlight(1, "Failed to save win table."))
return 1
finally:
return 0
def randomCard ():
card = random.randint(0, 12)
suit = random.randint(0, 3)
return [card, suit]
def cardName(card):
suit_prefix = "a" if (card[0] != 0 and card[0] != 7) else "an"
card_name = ["Ace",
"Two",
"Three",
"Four",
"Five",
"Six",
"Seven",
"Eight",
"Nine",
"Ten",
"Jack",
"Queen",
"King"][card[0]]
suit_name = ["Spades",
"Diamonds",
"Clubs",
"Hearts"][card[1]]
return suit_prefix + " " + card_name + " of " + suit_name
def valueOf(cards, player, score_dealer = 0):
to_add = 0
for i in range(0, len(cards)):
value = cards[i][0]
if value == 0:
if player == 0:
# user has an ace
choice = input("You were dealt an Ace; should it count as 1" +
" point or 11 points? [" +
highlight(4, "11") +
"/1] ")
# keep asking until a valid answer is given
while choice != "1" and choice != "11" and choice != "":
choice = input("You must enter either 11 or 1: ")
# default to 11
if choice == "":
choice = "11"
print("Defaulting to 11.")
to_add += int(choice)
else:
# dealer has an ace
# go for 11 if it would be beneficial
if (score_dealer < 3 or
(score_dealer > 7 and score_dealer < 11)):
to_add += 11
else:
to_add += 1
elif value > 9:
to_add += 10
else:
to_add += value + 1
return to_add
def scoreTable(wins):
wins_str = [str(wins[0]), str(wins[1])]
col_width = max([len(wins_str[0]), len(wins_str[1])])
for i in range(0, 2):
wins_str[i] = " " * (col_width - len(wins_str[i])) + wins_str[i]
index_of_bigger = 0 if wins[0] < wins[1] else 1
index_of_smaller = (index_of_bigger - 1) * (index_of_bigger - 1)
wins_str[index_of_bigger] = highlight(5, wins_str[index_of_bigger])
wins_str[index_of_smaller] = highlight(6, wins_str[index_of_smaller])
if not windows:
# unicode box-drawing
print("Wins table:\n" +
"┌────────┬─" + col_width * "─" + "─┐\n" +
"│ Dealer │ " + wins_str[1] + " │\n" +
"├────────┼─" + col_width * "─" + "─┤\n" +
"│ You │ " + wins_str[0] + " │\n" +
"└────────┴─" + col_width * "─" + "─┘" )
else:
# dos box-drawing
print("Wins table:\n" +
"┌────────┬─" + col_width * "─" + "─┐\n" +
"│ Dealer │ " + wins_str[1] + " │\n" +
"├────────┼─" + col_width * "─" + "─┤\n" +
"│ You │ " + wins_str[0] + " │\n" +
"└────────┴─" + col_width * "─" + "─┘" )
def highlight(colour, string):
# no colours is the output is a file or similar
# no colours for Windows as they aren't supported
if sys.stdout.isatty() and not windows:
colour_codes = ["\033[m", # normal
"\033[31m", # red/error
"\033[32m", # green/correct
"\033[33m", # gold/warning
"\033[34m", # blue/emphasis
"\033[91m", # light red/error
"\033[92m", # light green/correct
"\033[93m", # light gold/warning
"\033[94m"] # light blue/emphasis
return colour_codes[colour] + string + colour_codes[0]
else:
return string
def game(firstRound):
# ========================== introduce the game ===========================
welcome = ""
if not windows:
welcome = "\n " + "─" * 33 # unicode box-drawing lines
welcome += highlight(5, " ╺╸ ") # highlight bolder lines in the centre
welcome += highlight(1, "╺╸")
welcome += highlight(5, " ╺╸ ")
welcome += "─" * 33 + "\n\n"
else:
# dos box-drawing
welcome = "\n " + "─" * 33 + " ╣ ╬ ╠ " + "─" * 33 + "\n\n"
if firstRound: # if the game has just been launched
welcome += "Welcome to the table"
else: # if the user is playing another round
welcome += "Welcome back"
print(welcome + ". Let's play.\n") # send it out in one call
# ================== initialise scores, deal two cards ===================
scores = [0, 0] # player, dealer
hand_player = [randomCard(), randomCard()] # deal four initial cards
print("You have been dealt two cards; " + # show the player theirs
highlight(4, cardName(hand_player[0])) +
" and " +
highlight(4, cardName(hand_player[1])) +
".")
# ============================= tot up scores =============================
scores[0] += valueOf(hand_player, 0) # pass hand array to valueOf, add
# the result to scores variable
print("This makes your score " + # prettify and print score
highlight(4, str(scores[0])) +
".")
hand_dealer = [randomCard(), randomCard()] # deal dealer's card
scores[1] += valueOf(hand_dealer, 1, scores[1]) # add card worth to score
# ========================== natural win/draw =============================
if scores[0] == 21:
if scores[1] != 21: # only the player has 21
print("You scored " +
highlight(4, "21") +
" instantly. Natural " +
highlight(2, "win") +
"!\n")
wins[0] += 1 # give win to player
else: # both player and dealer have 21
print("You scored " +
highlight(4, "21") +
" instantly, as did the Dealer. It's a " +
highlight(8, "draw") +
".\n")
game_over = True # prevent main game loop from running
# ========================== main portion loop ============================
# initialise some variables
twist_count = [0, 0]
stuck = [False, False]
game_over = False
dealer_cont = False
# while neither player is stuck, and the game is not over
while not (stuck[0] and stuck[1]) and not game_over:
if not stuck[0]: # if the player is in game
action = input("\nDo you wish to " +
highlight(8, "stick") +
" or " +
highlight(8, "twist") +
"? [S/T]: ").upper()
while action != "S" and action != "T":
action = input("You must enter either S or T: ").upper()
if action == "S":
print("You " +
highlight(8, "stuck") +
". You cannot make any more moves.\n" +
"Your final score is " +
highlight(4, str(scores[0])) +
".\n")
stuck[0] = True
if not stuck[1]:
dealer_cont = True
if action == "T":
card_new = randomCard();
scores[0] += valueOf([card_new], 0)
card_name = cardName(card_new)
print("\nYou were dealt " +
highlight(4, card_name) +
", bringing your score to " +
highlight(4, str(scores[0])) +
".")
twist_count[0] += 1
if scores[0] > 21:
print("You've gone " +
highlight(8, "bust") +
"! You " +
highlight(5, "lose") +
".\n")
wins[1] += 1
game_over = True
elif twist_count[0] == 3:
print("You have twisted three times so you are " +
highlight(8, "stuck") +
". Your score is final.\n")
stuck[0] = True
if not stuck[1]:
dealer_cont = True
if not stuck[1] and not game_over: # is dealer is in the game
# dealer twist
if scores[1] < 18:
card_new = randomCard();
scores[1] += valueOf([card_new], 1, scores[1])
twist_count[1] += 1
if scores[1] > 21:
print("The Dealer decided to " +
highlight(8, "twist") +
" and went " +
highlight(8, "bust") +
"! " +
highlight(6, "You win") +
".\n")
wins[0] += 1
game_over = True
elif twist_count[1] == 3:
print("The Dealer has twisted three times and is now " +
highlight(8, "stuck") +
".")
stuck[1] = True
else:
print("The Dealer decided to " +
highlight(8, "twist") +
".")
# dealer stick
else:
print("The Dealer decided to " +
highlight(8, "stick") +
".")
stuck[1] = True
if not game_over:
if dealer_cont:
print("")
if scores[0] > scores[1]:
print("You finished with a higher score than the Dealer. " +
highlight(6, "You win") +
".\n")
wins[0] += 1
elif scores[0] < scores[1]:
print("The Dealer finished with a higher score than you. You " +
highlight(5, "lose") +
".\n")
wins[1] += 1
else:
print("Both the Dealer and you have the same score. It's a " +
highlight(7, "draw") +
".\n")
scoreTable(wins)
# ========================= ask to repeat game ============================
play_again = input("\nWould you like to play another round? [" +
highlight(4, "Y") +
"/n]: ")
if play_again.upper() == "Y" or play_again == "":
game(False)
else:
print("See you next time.\n")
windows = platform.system() == 'Windows'
if not windows:
# unicode box-drawing
print(highlight(4, "\n ╔" + "═" * 76 + "╗ \n ║" + " " * 16) +
highlight(1, "┳━┓ ┳ ┏━━┓ ┏━━┓ ┳ ┏ ╺┳ ┏━━┓ ┏━━┓ ┳ ┏ ") +
highlight(4, " " * 17 + "║ \n ║" + " " * 16) +
highlight(1, "┣━┻┓ ┃ ┣━━┫ ┃ ┣━┻┓ ┃ ┣━━┫ ┃ ┣━┻┓") +
highlight(4, " " * 17 + "║ \n ║" + " " * 16) +
highlight(1, "┻━━┛ ┻━━┛ ┻ ┻ ┗━━┛ ┻ ┻ ┗━┛ ┻ ┻ ┗━━┛ ┻ ┻") +
highlight(4, " " * 17 + "║ \n ╚" + "═" * 76 + "╝ "))
else:
# dos box-drawing
print("\n ╔" + "═" * 75 + "╗ \n ║" + " " * 15 +
"┬─┐ ┬ ┌──┐ ┌──┐ ┬ ┌ ─┬ ┌──┐ ┌──┐ ┬ ┌ " +
" " * 15 + "║ \n ║" + " " * 15 +
"├─┴┐ │ ├──┤ │ ├─┴┐ │ ├──┤ │ ├─┴┐" +
" " * 15 + "║ \n ║" + " " * 15 +
"┴──┘ ┴──┘ ┴ ┴ └──┘ ┴ ┴ └──┘ ┴ ┴ └──┘ ┴ ┴" +
" " * 15 + "║ \n ╚" + "═" * 75 + "╝ ")
path = "blackjack-wins.txt"
wins = load(path)
if wins != [0, 0]:
print("")
scoreTable(wins)
try:
game(True)
save(path, wins)
# ========================== pick up ^C, cleanly exit =========================
except KeyboardInterrupt:
print(highlight(3, "\n\n" +
"The Blackjack table collapses dramatically, scattering cards and" +
"chips across\nthe scarlet carpet. The Dealer rushes to tidy the" +
"casino floor, with customers\nlooking on in distaste. The game is" +
"forced to close without a conclusion.\n"))