Monday, October 31, 2016

FreeDOS 1.2 Release Candidate 1

You may know that I am involved in many open source software projects. Aside from my usability work with GNOME, I am probably best known as the founder and project coordinator of the FreeDOS Project.

I started the FreeDOS Project back in 1994, when MS-DOS was still the platform of choice for many people. You can read the history of FreeDOS on our website, but the short version is this: I announced the FreeDOS Project in June 1994 as a way to replace the functionality of MS-DOS. The idea of a free DOS quickly caught on, and soon developers across the world came together to contribute to and improve FreeDOS.

It's 2016, and FreeDOS is still going strong. In fact, we are planning a new release this year: FreeDOS 1.2 should be available on December 25, 2016.

You can get FreeDOS 1.2 RC1 at our Download page www.freedos.org/download

You can help us make FreeDOS 1.2 a reality! We just released the FreeDOS 1.2 RC1 ("Release Candidate 1") for folks to try out. Please download and test this latest version of FreeDOS! Report any bugs or problems to the freedos-devel email list.

Saturday, October 29, 2016

Solitaire in a Bash script

I like to write Bash scripts. It stems from my time as a Unix and Linux systems administrator, years ago. I used to automated everything. So I got very good at writing shell scripts. Even today when managing a personal server, I write Bash scripts to automate various jobs so I don't have to keep logging into the server all the time. For example, I have a job that parses an RSS news feed with Bash.

I admit that my Bash scripts aren't always for automation. Some of my scripts are just for fun. Like the Bash script to fill out my March Madness basketball brackets. It can be an interesting diversion to write a Bash script to do something innovative.

And lately, I've started writing another such Bash script. Let me tell you about it.

We all know the classic Klondike Solitaire card game. There have been countless computer implementations of Solitaire on every platform. We even had a simple Solitaire game on our old Apple IIe computer in the 1980s. If you run Linux, you may be familiar with AisleRiot, which supports multiple card solitaire games, including the classic Klondike Solitaire. More recently, Google now provides a browser-based version of Klondike Solitaire; just search for the term "solitaire" and you'll get an option to "Click to play" the web version.

I wanted to write my own version of Klondike Solitaire as a Bash script. Sure, I could grab another shell script implementation of Solitaire called Shellitaire but I liked the challenge of writing my own.

And I did. Or rather, I mostly did. I have run out of free time to work on it. So I'm sharing it here in case others want to build on it. I have implemented most of the game, except for the card selection. You might think that's the toughest part, but I don't think so; I'll explain at the end.

So, how do you write a solitaire card game in a shell script?

I found it was easiest to leverage the strength of shell scripts: files. I started with all 52 cards in a single "deck" file, shuffled it, then "drew" cards from the deck into piles on the tableau.

Let's start with the basics. Creating 52 cards, comprised of thirteen cards of four different suits, is straightforward:

for s in c d h s ; do
  for n in $(seq 1 13) ; do
    echo "$s$n"
  done
done

You can direct the output to a file, and you have a file containing an ordered representation of all 52 cards. On a Linux system, you can use GNU shuf(1) to randomize ("shuffle") an input file, which can also be a pipe. So to define a shuffled deck of 52 cards, you do this:

deck=/tmp/sol/deck

for s in c d h s ; do
  for n in $(seq 1 13) ; do
    echo "$s$n"
  done
done | shuf > $deck

Drawing cards from the deck requires a little more work, but not much more. I wrote a simple function popn() that takes ("pops") the first n lines of a file and returns those lines, and shortens the file at the same time. Usually, this will be one at a time, but we'll need the flexibility later.

On top of that function, I wrote another simple function drawcard() that draws a single card from the deck and assigns it to the end of a "drawn cards" pile. This function also needs a little extra logic to deal with an empty deck; if the deck is empty, we return the drawn cards to the deck and start over. This is why it's important to pop from the start of the deck and append drawn cards to the end of the "drawn cards" pile; you can easily reset the deck using the drawn cards:

function popn() {
  # pop the first n($2) items from a file($1) and print it
  head -$2 $1
  cp $1 /tmp/tempfile
  awk "NR>$2 {print}" /tmp/tempfile > $1
}

function drawcard() {
  if [ $(cat $deck | wc -l) -eq 0 ] ; then
    cp $draw $deck
    >$draw
  fi

  popn $deck 1 >> $draw
}

In Klondike Solitaire, the play area is a tableau of seven piles of cards, where the first n-1 cards are piled "face down" and the last card is placed "face up." So for the first column, there are no "face down" cards, and only one "face up" card. On the seventh column, you start with six "face down" cards, and only one "face up" card. The player must move cards on these seven piles, eventually transferring the cards to four separate "foundation" piles, where each pile is dedicated to a separate suit: clubs, diamonds, hearts, and spades.

Creating the play area requires the use of Bash arrays. I rarely use arrays in Bash, but they are a very useful feature. Bash supports both indexed arrays (0, 1, 2, …) and associative arrays (where the index is a string value). You define a variable as an indexed array using the declare -a directive, and as an associative array with the declare -A directive.

With this, it's simple enough to define the tableau card piles as separate files, then use the popn() function to deal cards from the deck into these files.

declare -a tabdraw
for n in $(seq 1 7) ; do tabdraw[$n]="/tmp/sol/tab.draw.$n" ; done

declare -a pile
for n in $(seq 1 7) ; do tab[$n]="/tmp/sol/tab.$n" ; done

declare -A found
for s in c d h s ; do found[$s]="/tmp/sol/found.$s" ; done

# deal cards from deck into tableau {curly brackets needed on array refs}

for n in $(seq 1 7) ; do popn $deck 1 >> ${tab[$n]} ; done

for n in $(seq 1 7) ; do popn $deck $((n - 1)) >> ${tabdraw[$n]} ; done

And that's the guts of a Solitaire game in Bash! When assembled, my Bash script looked like this:

#!/bin/bash

# create work directory

tmpdir="/tmp/sol.tmp.$RANDOM"
[ ! -d $tmpdir ] && mkdir $tmpdir

# create variables

deck="$tmpdir/deck"
draw="$tmpdir/draw"
tmpf="$tmpdir/tmpf"

declare -a tabdraw
for n in $(seq 1 7) ; do tabdraw[$n]="$tmpdir/tab.draw.$n" ; done

declare -a pile
for n in $(seq 1 7) ; do tab[$n]="$tmpdir/tab.$n" ; done

declare -A found
for s in c d h s ; do found[$s]="$tmpdir/found.$s" ; done

# create functions

function popn() {
  # pop the first n($2) items from a file($1) and print it
  head -$2 $1
  cp $1 $tmpf
  awk "NR>$2 {print}" $tmpf > $1
}

function drawcard() {
  if [ $(cat $deck | wc -l) -eq 0 ] ; then
    cp $draw $deck
    >$draw
  fi

  popn $deck 1 >> $draw
}

function showcards() {
  # show the cards again - repaint the screen

  clear

  for n in $(seq 1 7) ; do cat ${tabdraw[$n]} | wc -l > $tmpdir/wc.$n ; done

  paste $tmpdir/wc.[1-7]
  paste ${tab[*]}

  echo '___  ___  ___  ___'

  paste ${found[*]}

  echo '___'

  cat $deck | wc -l
  tail -1 $draw
}

# build and shuffle initial deck

for s in c d h s ; do
  echo "${s}0" > ${found[$s]}

  for n in $(seq 1 13) ; do
    echo "$s$n"
  done
done | shuf > $deck

# deal cards from deck into tableau {curly brackets needed on array refs}

for n in $(seq 1 7) ; do popn $deck 1 >> ${tab[$n]} ; done

for n in $(seq 1 7) ; do popn $deck $((n - 1)) >> ${tabdraw[$n]} ; done

# loop until quit ('q')

input='n'
while [ "$input" != "q" ] ; do
  case "$input" in
  'n')
    # draw next card
    drawcard
    ;;
  *)
    # parse into card1,card2
    # is this a valid request?
    # is this a valid move?
    ;;
  esac

  showcards

  echo -n '?> '
  read input
done

# cleanup

rm -rf $tmpdir

I have left unfinished the logic to move cards around on the tableau. This might seem like the most difficult part, but not really. Since every pile on the tableau is a file, it's easy enough to seek a requested card in each of the "face up" piles, then extract all lines (cards) from that "face up" pile and append them onto another "face up" pile. You'd need a little extra logic in there to move Kings to an empty space on the tableau, but that's not very difficult.

I envisioned splitting the logic into three parts:

1. Verifying that this is a valid request
For example, the user is not allowed to move a black card onto a black card. Also, the user can only move cards in descending order on the tableau, and ascending order on the foundation. That's easy logic.
2. Confirming that the cards are there to move
This should be a fairly straightforward process using fgrep(1) to locate the requested cards on the tableau or in the foundation.
3. Moving the cards
I believe this should be easy, if you remember that cards are only entries in files. You can easily write a function that outputs all lines starting with card A, and appends them to the end of another file. At the same time, the function can truncate the first file starting at card A.

When you start the script, you should see output similar to this:

0       1       2       3       4       5       6
s12     c11     s2      h7      s11     h11     d3
___     ___     ___     ___
c0      d0      h0      s0
___
23
c13
?> 

The output is intentionally functional, and uses paste(1) to merge the output from several tableau files. What you're seeing:
  • The first line shows the number of cards remaining in each of the tableau "face down" piles.
  • The second line shows the "face up" cards for each pile on the tableau. This sample output indicates: 12 (Queen) of Spades, 11 (Jack) of Clubs, 2 of Spades, 7 of Hearts, 11 (Jack) of Spades, 11 (Jack) of Hearts, and 3 of Diamonds.
  • The third line shows the empty foundation piles. I initialized these as the "zero" cards so the logic to transfer cards to the foundation could remain simple; you only move cards of the same suit in ascending order.
  • The fourth line shows the number of cards remaining to be drawn on the deck.
  • The fifth line shows the "face up" card from the deck. In this case, it is the 13 (King) of Clubs.
  • The last line shows a prompt (?>) for the user to enter a command.

This Bash script was a lot of fun to write, but I don't have time to finish it. The script took an afternoon to write, and an hour to tweak. Even so, I think this is a solid start to play Solitaire in a Bash script.

Feel free to finish this script. Please assume Creative Commons Attribution. So if you use it somewhere else, such as to write an article and publish it, you should credit me as the original author.

Sunday, October 2, 2016

Looking ahead: Usability of open source software

This Spring semester, I look forward to teaching CSCI 4609 Processes, Programming, and Languages: Usability of Open Source Software. This is the second time I will teach the class at the University of Minnesota Morris, although it's more like the fifth time because I structured the course outline to be very similar to the Outreachy internships I've mentored now for three cycles.

Interested in a preview of the course? Here's a quick breakdown of the syllabus:
CSCI 4609: Usability of Open Source Software

Introduction to usability studies and how users interact with systems using open source software as an example. Students learn usability methods, then explore and contribute to open source software by performing usability tests, presenting their analysis of these tests, and making suggestions or changes that may improve the usability.

Course objectives:
  • To understand what is usability and apply basic principles for how test usability
  • Design and develop personas, scenarios, and other artifacts for usability testing
  • Create an execute a usability test against an actual product
  • To identify and reflect on the value and presentation of usability test results

Each student will work with the professor and other students to choose an individual project to complete during the second half of the term.

Requirements:
  • Class engagement (discussions, presentations, feedback)
  • Projects (small group project, and larger individual project)
  • Final paper to document your individual project

Each discussion will be worth 5 points. This is graded on a scale: no points for no discussion posted, and 1 to 5 points based on the quality of your discussion. For example: a well thought-out discussion will be given 5 points; a sketched out discussion post will earn 1 point.

Course outline:
  1. Introduction
  2. What is usability?
  3. How do we test usability?
  4. Personas
  5. Scenarios
  6. Scenario tasks
  7. User interfaces
  8. Mini project (two weeks)
  9. Final project (four weeks)
  10. Final paper
Based on what I learned from teaching the class last time, I'll be sure to arrange the weeks to leave more time for the final project, and to spread discussion throughout the week. For example, in the first half of the course, there's a lot of research and practice: learn about a topic and post a summary, then apply what you have learned towards a specific assignment. This time, I'll have the first discussion assignment due around Thursday each week, and the practice assignment due on Sunday, assuming each week starts on a Monday and ends on Sunday night.

I will also change the points. Last time, I had a 60/40 split for discussion points and final paper. I totaled your discussion, and that was 60% of your grade; your final paper was the other 40%. This year, I plan to make the points cumulative. If you assume 20 discussions at 5 points each, that's 100 points for discussion. The paper is an additional 50 points. That makes it clear you cannot skip the discussion and hope for a strong paper; neither can you punt the paper and rely on your discussion points. You need to participate every week and you need to make an effort on the final paper to get a good grade in the class.
image: University of Minnesota Morris