Software Design and Development – Assignment 3

Creating a Software Solution [P6] [D2]

Analysis

Questionnaires, Surveys and Market Analysis

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.

Requirements

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:

Design

Storyboard and User Experience

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.

Data Flow

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.

A DFD, or data flow diagram, showing how data may move around a Blackjack program.
Image by me; see license.

Process Flow

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:

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.

  1. A message being outputted to the user.
  2. A decision being made based on user input.
  3. A decision being made based on the value of a stored variable.
  4. A decision being made based on user input, potentially causing the program to loop.
  5. The end of the program being reached, resulting in it quitting.
A flow chart showing the steps undertaken by a Blackjack game program. The program was built, and is shown further down the page.
Image by myself; see license.

Development and Testing [M2]

Data

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.

Integers

Booleans

Strings

File Objects

Importance of Data Types

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.

Pseudo-Code

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]

Building

Choice of Tools

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.

Testing

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.

Plan

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.

Results and Response

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.

Quality and Readability of Source Code [M1] [D1]

Features of Good Source Code

Comments

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 Conventions and Standards

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

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.

Documentation

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.

Data Types

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.

Results of Good Source Code

By ensuring that the code one writes displays the attributes of good source code given above, the following results are commonly noticed.

Reliability

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

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.

Robustness

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.

Usability

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

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

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.

Blackjack Python Script

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"))