Whatever computer programming language you learn, the first program you make is by tradition the one-liner with the output "Hello world". The experimental economics equivalent of "Hello world" is the prisoner's dilemma, the most studied game in behavioral science. To highlight the most important steps, we make a very simple experiment: repeated partners design with a symmetric payoff matrix. The sequence of the experiment is: login, instructions (several pages), {numberperiods} times the sequence decision-wait-results, questionnaire and payoff screen.
Elements of the experiment that need to be flexible like the number of players, the payoff structure etc are in the database in the table "commonparameters" and the data of the subjects are in the tables "ppnumbers" (per subject) and "decision" (one record per decision).
Download all the files on this page in one zip-file. Remember that you have to change the common.inc file (step 3). You can make your own tables in phpmyadmin (a good exercise) or only make the database and import the tables by importing "peep.sql" in phpmyadmin.
Download and install the combined (free): XAMPP package (windows, OSX and linux). If you use a mac an alternative for XAMPP is the MAMP package (I advise against using the default Apache installation of OSX). The packages includes phpmyadmin which is a very nice interface with mysql (of course in a webpage and of course using php).
Learn the basics html and php (for example on w3schools.com). I suggest you first play an afternoon or so with html before you continue to php.
This is called the 0 step because I assume you have already done this.
Make a directory "PDG" in the "htdocs" directory in the XAMP directory. Here we will put all the programming files.
Start localhost/phpmyadmin/ and make a new database. To keep things tractable, we use the name PDG for this database. Because the layout phpmyadmin regularly changes, your version may look different.
We make a table "commonparameters" were we will put all variables that have to be available in many pages and which can be changed at the start of the session. For example, if you want to change the payoff matrix or the number of players you only have to change that in the database, not in the php-program.
We add the following variables to the table: numberplayers, numberperiods, payoffCC, payoffCD, payoffDC, payoffDD, exchangerate (number of points equal to one euro).
We now have to fill in the parameters, for each a name and a value (I show only the last two):
Now we have to make two more tables in the database, one with information about subjects "ppnumbers" and one with their decisions "decisions". In the ppnumbers table we would like to have the following variables:
The variable tafelnummer is the code for the table in the lab and is a string (VARCHAR) of length 3 (in the CREED-lab the tables have codes like A01, B03, etc). I use the Dutch word and not the English "table" because that word is also used in html, php and mysql. In principle I could use it, but it is not practical because it increases the possibility of hard-to-find bugs! However, I am not very consistent in this in practice ;).
The "pagina" (Dutch for page) is a long string variable because it will contain the path of the page the subject is currently looking at (and these paths can be long if you ever put your program on an external server). The variable "earnings" contains the earnings in points, and (not surprisingly) the variable "euros" contains the earnings in euros.
Finally we need a table "decisions". Here we need the following variables:
in which "time" is the time in seconds. We could have made the decision itself a string but here I have explicitly forbidden to have any other value than "C" and "D".
<?php define ("HOST","localhost"); // fill in correct name of database define ("DBNAME","PDG"); // fill in correct loginname/password define ("ADMIN","root"); define ("WWOORD","yourownpassword"); //For this simple experiment we read all common parameters we will ever need. $table_name="commonparameters"; $connection = @mysql_connect(HOST,ADMIN, WWOORD) or die(mysql_error()); $db = @mysql_select_db(DBNAME,$connection)or die(mysql_error()); $sql="SELECT * FROM $table_name"; $result=@mysql_query($sql,$connection) or die("Couldn't execute query ".$sql); while ($row=mysql_fetch_array($result)) { $name=$row['name']; $value=$row['value']; $$name=$value; } />
<?php include("common.inc"); //Only for use in CREEDlab, when the client is redirected in beginexp.html to this file. Otherwise the table will be empty $table=$_REQUEST['table']; //Automatically a number is asigned and also the table (A2, A3 etc) is included in ppnummers $table_name="ppnumbers"; $connection = @mysql_connect(HOST,ADMIN, WWOORD) or die(mysql_error()); $db = @mysql_select_db(DBNAME,$connection)or die(mysql_error()); $sql="SELECT * FROM $table_name ORDER BY ppnr DESC"; $result=@mysql_query($sql,$connection) or die("Couldn't execute query ".$sql); if ($row=mysql_fetch_array($result)) { $ppnr=$row['ppnr']+1; } else { $ppnr=1; } $numberplayers=readCommonParameter(numberplayers); if ($ppnr>$numberplayers){ //the numberplayers is read by the common.inc file, like all commonparameters header("Location: relogin.html"); exit(); } else { setcookie("beheerder", $ppnr); $table_name="ppnumbers"; $connection = @mysql_connect(HOST,ADMIN, WWOORD) or die(mysql_error()); $db = @mysql_select_db(DBNAME,$connection)or die(mysql_error()); $sql2="INSERT INTO $table_name (ppnr, tafelnummer) VALUES (\"$ppnr\", \"$table\")"; $result=@mysql_query($sql2,$connection) or die("Couldn't execute query ".$sql2); header("Location: instruction.php"); exit(); } />
<?php include("common.inc"); Header("Cache-Control: must-revalidate"); if (!$_COOKIE['beheerder']){ header("Location: begin.html"); exit(); } else { $koek=readcookie("beheerder"); $ppnr=$koek[0]; } updateTableOne("ppnumbers","ppnr=$ppnr","pagina",$_SERVER['PHP_SELF']); /> <HTML> <HEAD> </HEAD> <BODY> Instructions... <p align=center><a Href="decision.php">To the first period</a> </BODY> </HTML>
In this stage we don't spend energy on a nice layout, we keep it simple. Note however that we take the numbers in the payoff matrix from the database so if we change the payoffs for another treatment we don't have to change the program. In a real experiment we would very likely use more neutral labels than "C" and "D"!
We measure the time the player takes in seconds by comparing the time when the server sends the page to the player [$begintijd=time();
] with the time when an answer arrives [$tijd=time()-$begintijd;
]. This is not extremely precise, especially if the network is slow, but good enough for us now (if needed, you could measure it much more precise at the computer of the player, using javascript).
<?php Header("Cache-Control: no-cache, must-revalidate"); include("common.inc"); if (!$_COOKIE['beheerder']){ header("Location: begin.html"); exit(); } $koek=readcookie("beheerder"); $ppnr=$koek[0]; $period=lookUp("ppnumbers","ppnr='$ppnr'","period"); updateTableOne("ppnumbers","ppnr=$ppnr","pagina",$_SERVER['PHP_SELF']); if ($period==0) { $period=1; updateTableOne("ppnumbers","ppnr=$ppnr","period",$period); } if (isset($_REQUEST['OK'])) { $keus=$_REQUEST['keus']; $begintijd=$_REQUEST['begintijd']; $tijd=time()-$begintijd; //we first check whether the decision is already saved; this because of relaoding of page $check=lookUp("decisions","period='$period' and ppnr='$ppnr'","decision"); if ($check==""){ insertRecord("decisions","ppnr, period, decision, time","'$ppnr', '$period', '$decision', '$tijd'"); } header("Location: wait.php"); exit(); } $begintijd=time(); /> <HTML> <HEAD> </HEAD> <BODY> <!--This javascript function checks if a decision was made before the submit button was clicked--> <script language="JavaScript"> function confirm_box() { if (eval(document.forms['form1'].keus[0].checked)) { } else if (eval(document.forms['form1'].keus[1].checked)) { } else { alert("Please take a decision!"); return false; } return true; } </script> <H1 align=center>Your decision for period <?php echo $period; ?></H1> <font size=+2> <table border="1" align="center"> <tr> <td colspan=2 rowspan=2></td> <td colspan=2><font color=blue><b>Other</b></font></td> </tr> <tr> <td align=center><font color=blue><b>C</b></font></td> <td align=center><font color=blue><b>D</b></font></td> </tr> <tr> <td rowspan=2><font color=red><b>You</b></font></td> <td><font color=red><b>C</b></font></td> <td align=center><font color=red><b><?php echo $payoffCC; ?></b></font>, <font color=blue><b><?php echo $payoffCC; ?></b></font></td> <td align=center><font color=red><b><?php echo $payoffDC; ?></b></font>, <font color=blue><b><?php echo $payoffDC; ?></b></font></td> </tr> <tr> <td><font color=red><b>D</b></font></td> <td align=center><font color=red><b><?php echo $payoffCD; ?></b></font>, <font color=blue><b><?php echo $payoffDC; ?></b></font></td> <td align=center><font color=red><b><?php echo $payoffDD; ?></b></font>, <font color=blue><b><?php echo $payoffDD; ?></b></font></td> </tr> </table> </font> <form name="form1" action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" onsubmit='return confirm_box(this)'> <input type='hidden' name='begintijd' id='hiddenField2' value='<?php echo $begintijd; ?>'> <table align=center> <tr> <td align=right><font color=red><b>Your decision:</b></font></td> <td><input type="radio" name="keus" value="C">C<br><input type="radio" name="keus" value="D">D</td> </tr> <tr><td></td><td><button type="submit" name="OK" value="OK">OK</button></td> </tr> </table> </form> </BODY> </HTML>
After a subject makes a decision, he is referred to a waiting page. This page checks whether all relevant decisions (in this case: only the decision of the partner) are in the database. If so, we go to the results page, if not, a message is displayed that we have to wait. The waiting page is refreshed automatically every few seconds, until all relevant decisions are made and we can proceed to the results.
In our little experiment, we let each pair of subjects (1-2, 3-4, etc) continu when their decisions are complete, they don't wait for the other pairs.
<?php include("common.inc"); Header("Cache-Control: must-revalidate"); if (!$_COOKIE['beheerder']){ header("Location: begin.html"); exit(); } $koek=readcookie("beheerder"); $ppnr=$koek[0]; $period=lookUp("ppnumbers","ppnr='$ppnr'","period"); updateTableOne("ppnumbers","ppnr=$ppnr","pagina",$_SERVER['PHP_SELF']); //we look up whether the partner has made a decision in this period if ($ppnr % 2 == 0){ $partner=$ppnr-1; } else { $partner=$ppnr+1; } $decisionpartner=lookUp("decisions","period='$period' and ppnr='$partner'","decision"); if ($decisionpartner!="") { header("Location: result.php"); exit(); } ?> <html> <meta http-equiv="Refresh" content="5"> <body> We wait for the decision of your partner... </body> </html>
The results page displays decisions of both players, and their payoffs. The players can click to go the next period, or, if it was the last one, to go to the questionnaire.
<?php include("common.inc"); Header("Cache-Control: must-revalidate"); if (!$_COOKIE['beheerder']){ header("Location: begin.html"); exit(); } $koek=readcookie("beheerder"); $ppnr=$koek[0]; $period=lookUp("ppnumbers","ppnr='$ppnr'","period"); updateTableOne("ppnumbers","ppnr=$ppnr","pagina",$_SERVER['PHP_SELF']); if ($ppnr % 2 == 0){ $partner=$ppnr-1; } else { $partner=$ppnr+1; } $decisionother=lookUp("decisions","period='$period' and ppnr='$partner'","decision"); $owndecision=lookUp("decisions","period='$period' and ppnr='$ppnr'","decision"); $earnings=${"payoff".$owndecision.$decisionother}; $payoffpartner=${"payoff".$decisionother.$owndecision}; //feedback $tekst="<p align=center><b>Results period ".$period.": </b><br>Your decision was ".$owndecision.", the decision of the orther player was ".$decisionother."<br>Your earnings are ".$earnings." and the earnings of the other player are ".$payoffpartner; //link depending on whether it is the last period or not if ($period<$numberperiods){ $link="<p align=center><a href=\"decision.php\">To the next period</a>"; } else { $link="<p align=center><a href=\"period.php\">To the questionairre</a>"; } $check=lookUp("decisions","period='$period' and ppnr='$ppnr'","decisionother"); if ($check==""){ //saving the data in the decision table and increasing the period, but only one (not when the page is reloaded) updateTableMore("decisions","period='$period' and ppnr='$ppnr'","decisionother='$decisionother', earnings='$earnings'"); $period=$period+1; updateTableOne("ppnumbers","ppnr=$ppnr","period",$period); } ?> <html> <body> <?php echo $tekst; ?> <br> <?php echo $link; ?> </body> </html>
If you like you can also display the results of all previous periods (and you could also do that on the decision page of course). See the code at the earningspage below.
It is common to end with a questionnaire; here we will only ask for gender (radio-buttons), age (input field), major (drop down list) and an open form question (to show how a textfield works). We will save this data in the "ppnumbers" table, so we have to add these variables to the table. Go in phpmyadmin to the table "ppnumbers", tab "Structure" and choose to add 4 variables at the end of the list. We make the variables strings: varchar of limited length for the first three variables, and text for the open form question (unlimited size string).
<?php $dezepagina=$_SERVER['PHP_SELF']; include("common.inc"); Header("Cache-Control: must-revalidate"); if (!$_COOKIE['beheerder']){ header("Location: begin.html"); exit(); } $koek=readcookie("beheerder"); $ppnr=$koek[0]; if (isset($_REQUEST['verzend'])) { $age=$_REQUEST['age']; $gender=$_REQUEST['gender']; $study=$_REQUEST['study']; $openvraag=$_REQUEST['openvraag']; updateTableMore("ppnumbers","ppnr=\"$ppnr\"","age=\"$age\", gender=\"$gender\", study=\"$study\", comment=\"$openvraag\""); header("Location: earnings.php"); exit(); } ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE> New Document </TITLE> <META NAME="Generator" CONTENT="EditPlus"> <META NAME="Author" CONTENT=""> <META NAME="Keywords" CONTENT=""> <META NAME="Description" CONTENT=""> </HEAD> <BODY> <H1>Questionnaire</H1> <p>Please fill in this short questionnaire<br> <form name="Show" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"> <table> <tr> <td>Age:</td> <td align=left> <input type="text" name="age" size=5></td> </tr> <tr> <td>Gender:</td> <td align=left colspan="9"> <input type="radio" name="gender" value="male"> Male <br> <input type="radio" name="gender" value="female"> Female </td> </tr> <tr> <td>I study at:</td> <td align=left colspan="9"> <select name='study'> <option value="">(choose from list)</option> <option value="econ">UVA - Economics and Business</option> <option value="psy">UVA - Social sciences - Psychology</option> <option value="FMG">UVA - Social sciences - not psychology</option> <option value="NWI">UVA - Science</option> <option value="betagamma">UVA - IIS: beta gamma bachelor</option> <option value="Rechten">UVA - Law School</option> <option value="Geestes">UVA - Humanities</option> <option value="Genees">UVA - Medical School</option> <option value="Tand">UVA - Dentistry</option> <option value="andU">Another University</option> <option value="andH">A profesional school</option> <option value="anders">Otherwise</option> </select> </SELECT> </td> </tr> <tr> <td>What is your impression of the other player?</td> <td><textarea rows="4" cols="50" name="openvraag"></textarea></td> </tr> <tr> <td colspan="10" align="center"><br> <input name="verzend" type="submit" value="Send"></td> </tr> </table> </form> </BODY> </HTML>
We can make answering the questions obligatory by using a javascript function like the one in the decision page, for example because we really need to know the gender, but for other questions is may be better not to force an answer, because no answer is better than an unreliable answer. In this example we don't force subjects to give answers.
At the end of the experiment the participant will get an overview of her earnings. The total is an amount in points that is exchanged for euros. During the payout participants leave the lab one by one to get paid. We don't want them to be able to see the earnings on the screens of other participants. Of course, we could use a very small font size, but a more elegant solution is that the participant can hide the page (link to "hide.php" on line 35). The hide page gives a link back to the earningspage.
<?php include("common.inc"); Header("Cache-Control: must-revalidate"); if (!$_COOKIE['beheerder']){ header("Location: begin.html"); exit(); } $koek=readcookie("beheerder"); $ppnr=$koek[0]; $period=lookUp("ppnumbers","ppnr='$ppnr'","period"); updateTableOne("ppnumbers","ppnr=$ppnr","pagina",$_SERVER['PHP_SELF']); if ($ppnr % 2 == 0){ $partner=$ppnr-1; } else { $partner=$ppnr+1; } $resultstable="<table border=1 align=center><tr><td align=center><strong>Period</strong></td><td align=center><strong>Your decision</strong></td><td align=center><strong>Decision Other</strong></td><td align=center><strong>Earnings</strong></td></tr>"; for ($p=1;$p<=$numberperiods;$p++){ $owndecision=lookUp("decisions","period='$p' and ppnr='$ppnr'","decision"); $decisionother=lookUp("decisions","period='$p' and ppnr='$ppnr'","decisionother"); $earnings=lookUp("decisions","period='$p' and ppnr='$ppnr'","earnings"); $resultstable.="<tr><td>".$p."</td><td align=center>".$owndecision."</td><td align=center>".$decisionother."</td><td align=center>".$earnings."</td></tr>"; } $totalearnings=lookUp("ppnumbers","ppnr='$ppnr'","earnings"); $euros=$totalearnings/($exchangerate); $resultstable.="<tr><td colspan=3>Total points:</td><td align=center><strong>".$totalearnings."</strong></td></tr></table>"; ?> <html> <body> <h1 align=center>Your earnings</h1> <?php echo $resultstable; ?> <p align=center> Your earnings in euros are: <?php echo $euros; ?>. <p align=center><a Href="hide.php">Hide earnings</a></p> </body> </html>
In many experiments the instructions will be more than one page, and we also want to check understanding. It is good practice to use a menu at top of instruction pages. The subjects than know if they are almost finished or not. In the menu only the pages that are already visited are clickable. We will make 5 pages: general instruction "instruction1.php", explanation of the payoff matrix "instruction2.php", two pages with questions to check understanding "instructionquestion1.php" and "instructionquestion2.php" and finally a summary "instructiesummary.php". We have to make a table in our database named "instructions" by running in phpmyadmin the following query:
CREATE TABLE `instructions` ( `part` int(11) NOT NULL, `pagenumber` int(11) default NULL, `filename` varchar(80) NOT NULL, `nameinmenu` varchar(80) NOT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1; INSERT INTO `instructions` VALUES (1, 0, 'instruction1.php', 'Introduction'); INSERT INTO `instructions` VALUES (1, 1, 'instruction2.php', 'Payoff table'); INSERT INTO `instructions` VALUES (1, 2, 'instructionquestion1.php', 'Question 1'); INSERT INTO `instructions` VALUES (1, 3, 'instructionquestion2.php', 'Question 2'); INSERT INTO `instructions` VALUES (1, 4, 'instructionsummary.php', 'Summary');
Accidents will happen. However, even if a computer of a participant completely breaks down, you can repair this very easily. If all your pages have the line:
updateTableOne("ppnumbers","ppnr=$ppnr","pagina",$_SERVER['PHP_SELF']);
which updates in the database the present page the participant is looking at, you just put the participant behind another computer (or restart the same one) and open firefox. In the setup of the CREEDlab you will go automatically to the beginauto.php page. Here the server finds that we have already "numberplayers" (as in the table "commonparameters") logged in, and it redirects to "relogin.php". Here you fill in the number of the subject, and it will be redirected to the last page seen.
<?php include "common.inc"; //check for required fields if (isset($_REQUEST['reloginpp'])) { $ppnr=$_REQUEST['reloginpp']; if (lookUp("ppnumbers","ppnr='$ppnr'","ppnr")==""){ echo "This number is not in de database!"; exit(); } else { writecookie("beheerder",$ppnr); //send to last page $period=lookUp("ppnumbers","ppnr='$ppnr'","period"); $pagina=lookUp("ppnumbers","ppnr='$ppnr'","pagina"); header("Location: ".$pagina); } } ?> <html> <head> <title>Relogin</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head><div align="center"> <form name="form2" method="post" action="relogin.php"> <table width="90%" border="0" bgcolor="#B6FFD9"> <tr> <td width="40%"> <div align="right"><b>Relogin</b> Participant Number:</div> </td> <div align="left"> <td width="56%"><input type="text" name="reloginpp"> (existing number) </div> </td> </tr> <tr> <td> <div align="right"></div></td> <td> <div align="left"> <input type="submit" name="Proceed" value="Proceed"> </div></td> </tr> </table> </form> <p> </center> </div> </body> </html>
During the experiment the experimenter wants to know what page the subjects are looking at, and at the end of the session the receipts have to be filled in. You could use phpmyadmin of course, but a self-reloading page with only the info you want is easy to make (or in practice: to copy from another experiment and to adapt). Note that in the top is also a link to start the experiment ("startdeel1.php") which does nothing else than changing the common parameter "startexperiment"" to 1 and than return to the monitor page. Also this could be done in phpmyadmin, but a link in the monitorpage is more convenient. See the section improvements below.
<?php include("common.inc"); $table_name="ppnumbers"; $connection = @mysql_connect(HOST,ADMIN, WWOORD) or die(mysql_error()); $db = @mysql_select_db(DBNAME,$connection) or die(mysql_error()); $sql3="SELECT * FROM $table_name ORDER BY `ppnr` ASC"; $result=@mysql_query($sql3,$connection) or die("Couldn't execute query ".$sql3); $tabel="<table border=1><tr><td><b>ppnr<b></td><td><b>table<b></td><td><b>period<b></td><td><b>earnings<b></td><td><b>euros<b></td><td><b>pagina<b></td><tr>"; while ($row=mysql_fetch_array($result)) { $ppnr=$row['ppnr']; $tafelnummer=$row['tafelnummer']; $period=$row['period']; $earnings=$row['earnings']; $euros=$row['euros']; $pagina=$row['pagina']; $tabel .= "<tr><td>".$ppnr."</td><td align=center>".$tafelnummer."</td><td align=center>".$period."</td><td align=center>".$earnings."</td><td align=center>".$euros."</td><td align=center>".$pagina."</td><tr>"; } $tabel .= "</table>"; ?> <html> <meta http-equiv="Refresh" content="5"> <body> <a href="startdeel1.php">Start experiment</a> <?php echo $tabel; ?> </body> </html>
So far we have focussed on the functionality of the pages. By using css files (which defines styles to use) you can make the layout much nicer. Even more importantly, reading the same css file at each page makes it easier to change layouts for all pages together by adapting the css file. For example, if you layout the payoff table in this experiment by using css, you can change the colors red/blue that I used to other colors you like better. Because you used the same css-sheet in the instructions and the decision page, you have to change it only once.
One of the nice things of css is that they are easy to "borrow". If you see a webpage with a very nice combination of colors, you "view source" in Chrome or "page source" in Firefox and you can find the link to css files, which you can download and adapt to your own liking.
In the example above we employed a partner design: the same two players interacted repeatedly. We can easily change that to a strangers design. We have to make several adaptions to the program: