Sunday, December 24, 2017

Dollhouse from Recycled CD Cases

The wife saw this neat design on Thingiverse and wanted to make one for the little bit. I printed a joint in PLA and tried it out. While I'm sure it works just fine, I thought it was a tad delicate for the hands of a toddler. One of the several designers to remix it added a small angle to the ends of the legs for grip. I thought that feature plus thickening up the whole thing would make a sturdy design. Here's my remix on Thingiverse.

While examining the CD case, I found the case itself has a little trim feature along the corners. I ended up drawing a profile that included this feature in Fusion 360. A straight piece I test printed went on a CD case with a satisfying snap. All the joints are extrusions of this profile.

I realized during assembly that the joints need cutouts where the trim pieces interfere with the extruded profile. Nonetheless, I was able to get the whole house assembled only needing to reprint 1 piece. However, there are one or two that might be a little stressed because of the force required to insert the CD's. If I where to build another of these, I would model the CD case in Fusion 360 and remove interfering material. I think that would make the whole thing easier to assemble.

I printed it in Taulman Bridge nylon, which is stronger but more flexible than PLA. (I learned about nylon filament during the boy's science fair project, but that is a different story.) Everyone is happy with the final result.




Wednesday, August 16, 2017

Sparkfun 8x7 LED Array Now With Vintage 8-bit ATASCII Font

Just a quick blog post since I've been quiet all summer (working, traveling, and riding roller coasters). I received an 8x7 LED array the wife bought as a mystery item on Sparkfun's Almost Free Day last year. I soldered it up to a Nano and loaded the library and demo code. Worked right out of the gate. The library has a nice scrolling text demo, but the the provided character set is kind of boring.

So, I decided to add the ATASCII character set to the code. This is a hand-tweaked version of the original, which I obtained from the ROM using an emulator and a BASIC program to print out the values. The display is 8 rows x 7 columns and most of the ATASCII characters are 6 x 6. The ATASCII data are stored row-by-row because the ANTIC graphics chip pushes the bits to the GTIA display chip as television raster lines. I had to transpose the individual character bitmaps because the demo software from Sparkfun displays as columns for easy horizontal scrolling. I eliminated some blank columns on the narrow characters to make it a bit more proportional, which improves readability on the scroll. I also nudged the lower case characters up or down a line to look better. Finally, I replaced the Sparkfun logo bitmap with a Fuji. The code is on my GitHub.

Here's the final product:

The music is by Adam Gilmore from the Atari 8-bit game Zybex.


Sunday, June 4, 2017

Mars, Middle School, and 3D Models

"Dad, can you print out a 3D model of NE Syrtis Major?"
"Where's that?"
"On Mars. NASA might land the next rover there. Can't you just find a model and print it? I need it for science tomorrow. We're doing group projects."

A quick check on Thingiverse revealed no such model - not surprising. This presents quite the dilemma: Let the boy learn a lesson not to procrastinate or work on a really cool 3D printing project? The project was too cool to pass up but I have a tiny bit of guilt enabling the boy's poor planning.

Two problems to tackle first. Where is Syrtis Major and where do I get elevation data? The latter turns out to be really easy. I remember when NASA mapped Mars with a laser altimeter. A little googling and I find a digital elevation model downloadable as a TIFF file. Next problem is locating Syrtis Major. Some more googling and I download a nice paper with an image and latitude/longitude values. The candidate landing site in NE Srytis is topographically boring (i.e., flat) for obvious reasons. But the whole Srytis Major province is perched 3 miles above Isidis Planitia and a 2000-mile diagonal map is dramatic. That's what we'll target. 

I wrote a Matlab script (see below) to read in the TIFF file, subset out the area, smooth and decimate the surface down to something manageable. Someone wrote a nice mesh to STL exporter. My first crack at it would take 32 hours to print and since this is a fire fighting exercise, I smoothed it down even more and made the model only 6" on a side to get a 14 hour predicted print time. Right about when the school bus comes. The horizontal scale is 16,000,000:1 and the vertical is 220,000:1, which is about a 70x exaggeration in the elevation.

In my experience, many 3D models have errors so they are not manifold. Windows 10 has a built in app called 3D Builder that is great at correcting errors. I usually run my STL files through it before trying to slice them for printing. Here's a rendering of the model in 3D Builder:
The STL file is up on Thingiverse. I sliced it with the 0.2-mm normal setting in Prusa 3D Sli3er, loaded to my Octoprint server and started the job. Here's the outcome. Not bad!


Matlab script to generate the source STL file:

%%
a=imread('Mars_MGS_MOLA_DEM_mosaic_global_463m.tiff');;
%% maps
% https://astrogeology.usgs.gov/search/details/Mars/GlobalSurveyor/MOLA/Mars_MGS_MOLA_DEM_mosaic_global_463m/cub
% Minimum Latitude -90
% Maximum Latitude 90
% Minimum Longitude -180
% Maximum Longitude 180
% Direct Spatial Reference Method Raster
% Object Type Pixel
% Lines (pixels) 23040
% Samples (pixels) 46080
% Bit Type 16
% Radius A 3396000
% Radius C 3396000
% Bands 1
% Pixel Resolution (meters/pixel) 463.0836
% Scale (pixels/degree) 128
% Horizontal Coordinate System Units Meters
% Map Projection Name Simple Cylindrical
% Latitude Type Planetocentric
% Longitude Direction Positive East
% Longitude Domain -180 to 180
imshow(a(1:100:end,1:100:end))
%% subset
% http://onlinelibrary.wiley.com/doi/10.1029/2003JE002143/abstract
% http://www.planetary.brown.edu/pdfs/2763.pdf
% Figure 1: The map covers an area from -10S to 30N and from 270W to 315W.
% 270 W is -90 E and 315 W is -45 E
lat=linspace(-90,90,size(a,1));
lon=linspace(-180,180,size(a,2));
x=interp1(lat,1:size(a,1),-[30 -10],'nearest');
y=interp1(lon,1:size(a,2),-[-45 -90],'nearest');
b=a(x(1):x(2),y(1):y(2));
%%
figure
N=5;
pcolor(flipud(b(1:N:end,1:N:end)))
shading flat
%% smooth
S=25;
c = double(imgaussfilt(b,S));
%% plot
figure
N=2*S;
mesh((c(1:N:end,1:N:end)))
shading flat
%% make surface to export
N=2*S;
d=c(1:N:end,1:N:end);
d=d-min(d(:));
d(d<0)=0;
d(1:end,1)=0;
d(1:end,end)=0;
d(1,1:end)=0;
d(end,1:end)=0;
d=d*35/max(d(:));
[u,v]=meshgrid(1:size(d,2),1:size(d,1));
u=u*150/max(u(:));
v=v*150/max(v(:));
%%
mesh(u,v,d); axis equal
%%  export
% https://www.mathworks.com/matlabcentral/fileexchange/20922-stlwrite-filename--varargin-
stlwrite('mars4.stl',u,v,d)

Saturday, May 13, 2017

ADC IIR LPF SPI TFT LCD GUI - Part 2

Since I posted ADC IIR LPF SPI TFT LCD GUI - Part 1, I thought I should finally post a Part 2. In the meantime, I found the NodeMCU wifi microcontrollers and have been learning to use those with the esp8266 Arduino framework. I built a simple demo that uses websockets to print the ADC value in the browser. I think this is a great way to put an interactive display in a project. So, I've decided not to spend energy on programming a TFT LCD GUI that doesn't really have an outlet beyond the project. Instead I can spend the time learning just enough HTML5, CSS and JavaScript to be dangerous.

This experience has taught me never again to put "Part 1" in a title.


Sketch


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <Arduino.h>
#include <Hash.h>

#include <WebSocketsServer.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <ESP8266mDNS.h>

// declarations
WiFiServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);
uint16_t adcreading = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println();
  connect();
  server.begin();
  if(MDNS.begin("adc")) {
        Serial.println("MDNS responder started");
    }
  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
  Serial.printf("Web server started, open %s in a web browser\n", WiFi.localIP().toString().c_str());
}

//connect to wifi network
void connect() {
  Serial.printf("Connecting ");
  WiFi.begin("yourssid", "password");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" connected");
}

// server side
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
    switch(type) {
        case WStype_DISCONNECTED:
            break;
        case WStype_CONNECTED:
            webSocket.sendTXT(num, "connected");
            delay(500);
            break;
        case WStype_TEXT:
            String temp=String(adcreading);
            webSocket.sendTXT(num, temp);
            delay(10);
            break;
     }
}

// client side
// prepare a web page to be send to a client (web browser)
String htmlHeader()
{
  String htmlPage =
     String("HTTP/1.1 200 OK\r\n") +
            "Content-Type: text/html\r\n" +
            "Connection: close\r\n" +  // the connection will be closed after completion of the response
            "\r\n" +
            "<!DOCTYPE HTML>\r\n";
     return htmlPage;
}

String styleSheet()
{
  String stylePage =
    String("<head>\r\n") +
    "<script>\r\n" +
    "var connection = new WebSocket('ws://adc.local:81');\r\n"+
    "connection.onmessage = function (e) {\r\n" +
    "document.getElementById('adcval').innerHTML = e.data;\r\n" +
    "connection.send('anything');\r\n" +
    "}\r\n" +
    "</script>\r\n" +
    "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n" +
    "<style>\r\n" +
    "h1 { \r\n" +
        "font-family: verdana;\r\n" +
        "text-align: center;\r\n" +
        "font-size: 30px;\r\n" +
    "}\r\n" +
    "</style>\r\n" +
    "</head>\r\n";
  return stylePage;
}

// main loop
void loop()
{

  WiFiClient client = server.available();
  // wait for a client (web browser) to connect
  if (client)
  {
    Serial.println("\n[Client connected]");
    while (client.connected())
    {
      // read line by line what the client (web browser) is requesting
      if (client.available())
      {
        String line = client.readStringUntil('\r');
        Serial.println(line);
        // wait for end of client's request, that is marked with an empty line
        if (line.length() == 1 && line[0] == '\n')
        {
          client.println(htmlHeader());
          client.println("<html>");
          client.println(styleSheet());
          client.println("<h1 id='adcval'>");
          client.println("waiting ...");
          client.println("</h1>");
          client.println("</html>");
          break;
        }
      }
    }
    delay(100); // give the web browser time to receive the data
    // and wait a little to close the connection:
    client.stop();
    Serial.println("[Client disonnected]");
  }
  webSocket.loop();
  adcreading = analogRead(A0); delay(10); // need delay to stop analogRead from blocking the web server
}

Friday, April 28, 2017

Time Warp - BBS'ing with an Atari 8-bit Emulator

I heard "Bill's Modern Segment" on ANTIC Episode 40 about BBS's in the middle of the BASIC 10-Liner game competition. Now that it's over, I was able to spend a little time exploring the 1980's era online world. There are a slew of BBS's created or resurrected as telnet servers. This wasn't my scene as a teenager. I didn't have a modem, although I always wanted one. I was fascinated by the idea of the Atari 850 interface and being able to hook up other devices to my computer. Alas.

My latest quest was to use the emulated 850 and Hayes modem to connect to an Atari BBS. It took me a while to figure out so I thought I'd post a step-by-step guide. If you're more interested in using the BBS's from your modern computer, I suggest using a native terminal program. But, the point here is to (re)experience the 1980's online world available through one's Atari 8-bit computer.

I use the Altirra emulator. It can emulate an attached 850 interface and Hayes modem. To set it up, selected "Devices" from the "System" menu.
The device list will appear. You might already have the Hosted disk and Printer devices.
Click Add... and select the 850 Interface Module and click OK.
Some options will appear.
You can click OK without changing anything. The Devices list will now have the 850 Interface Module listed as a device.
This next part took me a while to figure out. Select the 850 Interface Module and click Add again.
Select Modem and click OK. More options will appear. You can click OK.
Now the devices list will have an 850 with a modem. Finally, click OK.
You have an Atari with a modem! Next is software. There are four terminal programs I had success using, In age order:
  • APX Chameleon CRT TTY Emulator
  • Disk Version 3.1 by 
  • John Howard Palevich (1982). Palevich was interviewed for the ANTIC Podcast.
  • 850 Express! v1.1 by Keith Ledbetter (1986). 
  • Bob Term by Robert Puff (1990). Download an ATR file at archive.org. The original ARC compressed files are available at the legacy CSS site.
  • Ice-T XE by itaych on Atariage. This is an evolving modern terminal program that runs on the XE series (requires extra RAM) and has a software driven 80 column display.

Boot one up, change the mode to ASCII so you can talk to the modem, switch to terminal mode, type "AT" and it should respond "OK." There you go!
To connect to a BBS you need the URL and port number. You "dial" the URL instead of phone number: "ATDI url port." Once connected, change back to ATASCII mode if the BBS supports it.
You can find tons of telnet sites on the BBSTELNETGUIDE, although the search tool isn't useful. Even a Google search for Atari of the site is tough. I did find the YYZ-BBS.Here is its menu page in all its ATASCII glory.


Tuesday, April 18, 2017

FLTSIM2D - A longitudinal 3-DOF flight simulator in 10 lines of BASIC

My final entry to the 2017 BASIC 10-Liners game competition is a two-dimensional flight simulator. It simulates the longitudinal flight of an aircraft using three degrees-of-freedom (3DOF) equations of motion. This means the aircraft can fly forward, pitch, ascend and descend, but does not turn, yaw or roll. You will also see that it is not possible to invert the aircraft because I made some math approximations. But that's what one gets in less than 10 lines of BASIC code that can run realtime on the right machine! This project became a wee bit of an obsession and I am glad it is finally finished. The code and instructions for both Atari 800XL and BBC Micro Model B are over on GitHub.

While finishing last year's 10-liner second place winner GRAVITEN, I learned what game developers have known since the beginning: simulating physics with little bit of math can yield complex play action in only a few lines of code. The first digital video game exploited real physics: Spacewar! I wanted to apply this technique to a game for 2017. Listening to two Atari podcasts also helped lead to me to choose to write FLTSIM2D. First, Kevin Savetz on ANTIC described how he wanted to write a text adventure for the 2016 10-liner and produced CABBAGE. After hearing this, I started to think about what kind of game could be done for the contest that was completely different from typical arcade or puzzle style games. Second, Rob McMullen made an episode on Atari flight simulators for his Player Missle Podcast. It was fun to hear about SubLOGIC's Flight Simulator II, which I played for endless hours on my Atari 800XL as a teenager. These thoughts came together (physics, text, and flight simulator), which led to FLTSIM2D.

Why 2-D? My college professor always said, "when you face a problem, simplify it down to something you know how to solve, and then start adding back in the details a little at a time." I had no idea how to do a full motion simulator and it would surely be too slow in BASIC. So 2-D should be easy, right? Lift, drag, thrust, and gravity - should be pretty simple. Well, it was in the end, but it took a while to get there. I learned enough to be dangerous from NASA; however, my airplane cartwheeled and the floating-point overflowed in the first version I wrote. After another try in BASIC, I resorted to a spreadsheet to try to first get the math right. I drew a free-body diagram (after recalling my statics and dynamics professors saying, "always start with a free-body diagram.") Ah! I had a sign wrong. Switch a positive to a negative, and voila, I had a stable system. I didn't know when I started this project that an aircraft is a second-order system, which means it can oscillate. At this point, I was not completely satisfied my math was correct. I'm pretty sure I had two different coordinate systems mixed together. I also tried not to get too discouraged when I found someone had made this the topic of their masters thesis. After all, I did have something kind of working. As I said ... obsession.

After much googling, I stumbled across a lecture on 6-DOF differential equations of motion. This lecture was a turning point for me. Here's the code with some description of each command and equation with links to online resources as best as I can remember.

Code and Comments

'-----------------------------------------------------------
'
'                          FLTSIM2D
'
'Longitudinal (2-D: Z & X) flight simulator 
'for Atari 800XL 2017 8-bit BASIC 10-Liner
'
'Jeff Piepmeier
'March 4, 2017
'http://jeffpiepmeier.blogspot.com/
'http://github.com/jeffpiep/
'
'Parsed with TurboBASIC XL Parser Tool 
$options +optimize, optimize=-convert_percent-const_replace, optimize=+const_folding
'Tested on Altirra
'-----------------------------------------------------------

First, a bit about platforms. I wrote this in TurboBASIC XL using the parser tool noted above. I started on Atari because, well, it's Atari. Unfortunately, my beloved machine was just too slow. I was able to run it in real time using an 65816 accelerator chip clocked at 7 MHz. This is easy to try out using the Altirra emualtor but is not legal for the contest. After the recommendation from an online community to check out the BBC Micro, I found it could run at real time with a co-processor. The BBC Micro text mode is several times faster than Atari, the 6502 is clocked faster, and a second processor off loads the display and I/O functions. That made it work. I put together a line-by-line comparison of the TBXL source and Atari and BBC optimized codes. The rest of this description is made using the TBXL source because it keeps the original variable names.

DIM K$(1)

Need a string to hold the keyboard input. As seen below, I used the original controls from Flight Simulator II.

DT = .1 : REM (S) TIME STEP 

The time step value is a critical parameter in numerical solutions to nonlinear differential equations. I had to adjust it to keep the aircraft stable. Unfortunately, 0.1 seconds is the largest I could go. At 0.4 seconds, the stock Atari would have run in realtime. Alas...

CLS : REM CLEAR THE SCREEN
?
?,"FLIGHT SIMULATOR 2D"
POKE 752,1 : REM TURN OFF CURSOR

This sets up the screen. It's in glorious 40-column text mode! I hope you are an instrument-rated simulator pilot because I can't afford the clock cycles to draw the horizon.

REPEAT

Here begins the game loop.

BASETIME = TIME

I record the start time of each loop iteration so I can display the loop rate relative to real time below.

'ATMOSPHERE 
SIGMA=(1-Z*8.0E-05) : REM LINEAR APPROX FOR RELATIVE AIR DENSITY

Every flight simulator needs an atmosphere model. Mine is a linear approximation of the relative density of the US Standard Atmosphere. Note, all my calculations are in SI units, so Z is in meters.

STALL = ((U+2*FLAPS)>29) ! (Z<1) : REM DETERMINE IF NOT STALLED

FLTSIM2D is based on a Cessna 172 because I assumed I could find the most information online about this general aviation aircraft. Fortunately, a consulting firm published a free analysis with many specifications and parameters on the Cessna 172. Among the specs is the stall speed with flaps up and down. This equation is close enough for the simulator. The flag STALL is in negative logic: 0 if stalled; 1 if not stalled. My aircraft's stall speed is 29 m/s (56 knots) with flaps up and 23 m/s ( 51 knots) with flaps fully extended. (The variable FLAPS is in units of 10 degrees.) Also, if the aircraft is very close to the ground, it is not stalled. 

QSW = 8.1*(U*U+W*W)*1.225*SIGMA : REM DYNAMIC PRESSURE * WING AREA

The engineering of aircraft depends upon many coefficients (lift, drag, moment) that are multiplied by dynamic pressure and wing area to compute forces, which is computed by this equation. The velocity computes U and W are squared, but it is faster to self-multiply them. (I just realized while writing this that I did not combine the 8.1*1.225 into 9.923. Some wasted compute cycles!) 

'ANGLE OF ATTACK(S)
UNOSING = 1/(U + (U=0)) : REM U != 0
SLOPE = W*UNOSING : REM SLOPE OF WIND
SLOPE = -(SLOPE<=-1) + (SLOPE>-1)*SLOPE : REM LIMIT SLOPE TO <+/-1
SLOPE = (SLOPE>=1) + (SLOPE<1)*SLOPE : REM LIMIT SLOPE TO <+/-1
ALPHA = SLOPE - .22 * SLOPE^3 : REM WING ANGLE OF ATTACK WRT WIND

This group of statements computes the angle-of-attack (AOA) of the wing. As seen below, the lift and drag coefficients vary with AOA. The AOA is computed by taking the arc-tangent of the slope of the wind velocity with respect to the wing. (This is done in the aircraft reference coordinate frame, where U and W are the longitudinal and transverse velocity components, respectively). The first line avoids a singularity (divide by zero) when U=0. I just force it to be 1, if necessary. This works fine on the ground while waiting to take off. It really wreaks havoc in the air if you find yourself is a severe stall. The next three lines find the slope and limit it to +/- 1. The limiting is done because I use a cubic polynomial approximation of arc-tangent instead of ATN() in the last line for speed. The ability to loop the loop was something I had to give up early on to make a fast simulator possible.

ALPHAT = ALPHA + OMEGA*UNOSING*4.3 + 8.33E-3*DLTA + .0863*CL - .0873 : REM TAILPLANE AOA. INCLUDES ELEVATOR AND DOWNWASH TERMS

This statement computes the AOA of the tailplane and packs in a lot of physics. There are four adjustments made to the wing AOA. In reverse order: the tailplane on the Cessna 172 is pitched 5 degrees (0.0873 radians) relative to the aircraft. Second, the wing creates a downwash on the tailplane, which acts to change the AOA. It turns out this term is incredibly important for aircraft stability. I don't know if this stability is real-life or simply numerical, but I tested the simulator with and without it and had to have it in here. I found the equation in another lecture and collapsed all the constants into the 0.0863 coefficient. The variable CL is the coefficient of lift computed on the previous loop iteration. The next term is the effect of the elevator position on tailplane AOA. This term lets the elevator control change the pitch of the aircraft and I found the equation in the Elevator Design chapter of an online book on aircraft design. This simulator is brought to you by the online generosity of university professors everywhere. The last term takes into account the relative motion of the tailplane with respect to the wind due to aircraft pitch rate. The tailplane is a stabilizing element of the aircraft - the wind is always trying to push it back to a neutral position.

'LIFT & DRAG COEFFICIENTS
CLT = .4*ALPHAT -.24*ALPHAT^3 : REM TAILPLANE LIFT COEFFICIENT WITH AREA RATIO. 

This section begins computation of the all important lift and drag coefficients. First, the tailplane lift coefficient is modeled using a cubic polynomial valid for a wide range of AOA. The Cessna 172 uses a NACA 0012 airfoil, which is also used in wind turbines. The turbine engineers are concerned with the entire 360 degree range of AOA's. I noticed a sine function is a nice fit, although it does not include details of how the airfoil stalls at about 20 degree. But, hey, it's only a BASIC aircraft. I also factor in the wing-to-tailplane area ratio, which allows me to simplify calculation of the total lift and drag and pitching moment later on in the program.

CL = 0.3+0.16*FLAPS+4.8*ALPHA+12*ALPHA*ABS(ALPHA)-46*ALPHA^3 : REM WING LIFT COEFFICIENT. CUBIC FIT TO GET "DOUBLE HOOKS"
CL = CL * STALL : REM IF STALLED NO WING LIFT

Here, the wing lift coefficient is computed. It is a bit more complicated than the tailplane because the Cessna 172 wing is a NACA 2412 asymmetric airfoil. A completed third-order polynomial models this nicely. The sign of the squared term is flipped with negative AOA's using the ABS() function. The effect of flap position is also included with an additional constant term. The lift is finally nullified if the wing is below stall speed. I broke this up into two statements at one point to fit it in 10 lines and never removed it. It works. 

CLL = CL+CLT : REM TOTAL A/C LIFT COEFFICIENT
CD = .025 + .0575*CLL*CLL : REM DRAG COEFFICIENT USING OSWALD EFFICIENCY

The total lift is the sum of the wing and tailplane lift. The drag coefficient is computed using the Oswald efficiency concept. Fortunately, I found this theory when I was needing to compact the code. Otherwise, I would need two more polynomial equations for the wing and tail drag coefficients. Oswald lets me use a single quadratic for both. 

'ENGINE WITH DROPOFF DUE TO ALTITUDE
THRUST=(SIGMA-.05)*THROTTLE*UNOSING*1100 : REM INVERT PROPULSIVE POWER EQUATION
THRUST=THRUST*(THRUST<=2000)+2000*(THRUST>2000) : REM LIMIT THRUST TO 2000 N

The propulsion system was almost more difficult to figure out than the aerodynamic forces. At the start of the development, the thrust was just a scaled value of the throttle setting. This model, it turns out, is like having a jet turbine powered aircraft. It was convenient to have something that just worked, although I knew it was not a good model when the aircraft was at 25,000 ft and still climbing! (The Cessna 172 has a ceiling of about 14,000 ft.) I read and read about propeller and combustion engine propulsion. Most of the treatments are thermodynamic in nature. Work = Force x Distance; differentiating with respect to time yields Power = Force x Speed. To determine the thrust from applied power, just divide by the speed. Of course this equation blows up when the aircraft is stopped at the end of the runway waiting to take off. I learned that real flight simulators have dynamic models instead of thermodynamic models for propulsion . Engine throttle creates torque, which accelerates the propeller, which moves air and creates thrust. I found a great page on propeller efficiency vs. rotation rate (RPM), but was never able to arrive at a model that would determine RPM. My disposable time was running short and the contest deadline was approaching. Fortunately, the aircraft simulator book gave me "permission" to make power proportional to throttle (not entirely accurate), to invoke the thermodynamic model, and to limit the thrust arbitrarily. That is what the above two statements do. Oh, and the aircraft ceiling? That is made possible by engine power dropoff vs. altitude, the equation for which was gleaned from equation 7 on the propeller performance page: the engine torque (but I approximate with power) is proportional to relative air density. In the end, the parameters here could use some adjustment, but we'll just go with it. This model has 2000 N (450 lbs) of static thrust for acceleration down the runway and an altitude ceiling of a about 14,000 ft. There is probably too much power available because at low altitudes the aircraft can climb at 1000 fpm, which is too fast. Oh well. It works. Whew.

'TIME STEP EQUATIONS OF MOTION
MY = -QSW*(.0308 + CL*(.28-0.1*FLAPS) + CLT*4.3) : REM COMPUTE PITCHING MOMENT, + IS NOSE UP

Here begins the group of statements that compute the motion of the aircraft. This statement computes and sums the moments. The most difficult part was keeping track of the signs of the three terms - get one wrong and the aircraft is unstable and tumbles to a numerical death. The pitching moment calculation was made clear by equation 3.1 in this lecture on static longitudinal stability and control. I hope I'm applying it appropriately.  The three terms are the pitching moment caused by air flowing around the wing, the wing lift acting at the end of a short moment arm (whose length is changed by the flap position) and the lift of the tailplane acting at the end of the tail. These terms are multiplied by the dynamic-pressure wing-area product to compute the total moment in units of Newton meters. 

OMEGA = OMEGA + MY*5.48e-5 : REM UPDATE PITCHING RATE WITH PITCHING MOMENT AND IYY
OMEGA = OMEGA * ((Z>.01) ! (OMEGA>0)) : REM NO ROTATION UNLESS POSITIVE WHEN ON GROUND

The moment causes an acceleration in the pitch direction and changes the angular velocity OMEGA about the pitch axis. The first statement combines DT and the moment of inertia IYY into a single constant. The IYY value was provided by these guys, which was really nice of them because I couldn't find it anywhere else. The second statement limits the rotation direction to nose up when the aircraft is on the ground. The next group of statements implement the force calculations and equations of motion for linear translation.

U = U + ( (THRUST-QSW*CD)*1E-3 - SINTH*9.81 - OMEGA*W ) * DT : REM UPDATE LONGITUDINAL VELOCITY
W = W + (   -QSW*CLL*1E-3 + COSTH*9.81 + OMEGA*U ) * DT : REM UPDATE TRANSVERSE VELOCITY
W = W * ((Z>.01) ! (W<0)) : REM NO VERTICAL VELOCITY IF ON GROUND, UNLESS ITS UP

The calculations are performed in the aircraft reference frame: U is the longitudinal velocity (along the axis of the aircraft) and W is the transverse (vertical with respect to the aircraft) velocity. There are four forces effecting U: thrust, drag, gravity and Coriolis. There are three forces accelerating the aircraft along W: lift, gravity and Coriolis. Gravity is projected onto U and W by the sine and cosine of the pitch (computed below). The Coriolis forces are present because the forces and velocities are computed in the rotating aircraft reference frame. The transverse velocity is nearly vertical and limited to the up direction when the aircraft is on the ground.

'INERTIAL FRAME UPDATE
T=T+DT : REM STEP TIME FORWARD
THETA = THETA + OMEGA*DT : REM UPDATE PITCH ANGLE
COSTH = 1 - 0.49*THETA*THETA : REM QUADRATIC APPROX TO COSINE
SINTH = THETA -.15*THETA^3 : REM CUBIC APPROX TO SINE
VX = (U*COSTH + W*SINTH) : REM UPDATE VELOCITY OVER GROUND
VZ = (U*SINTH - W*COSTH) : REM UPDATE VERTICAL RATE
X = X + VX*DT : REM UPDATE GROUND TRACK POSITION
Z = Z + VZ*DT : REMO UPDATE ALTITUDE
Z = Z * (Z>0) : REM RESTRICT Z TO ABOVE GROUND

This group of statements computes states in the inertial (Earth) reference frame. First time is stepped forward. The pitch angle THETA is updated. The cosine and sine values of pitch are computed using polynomial approximations. This code executes in about half the speed of calling the COS() and SIN() functions. With the sine and cosine values, the U and W velocities are rotated into X and Z coordinates, which are then updated with the time step. Finally, the altitude is limited to positive values only. 

OKTOLAND = OKTOLAND ! (Z>9) : REM BE A LITTLE FORGIVING ON THE TAKEOFF

This flag is used to end the game; however, it is not set until the aircraft is at least 9 meters off the ground. The plane has a tendency to bounce on takeoff. To avoid the bounce being counted as a landing, you must get surely off the ground.

'PILOT INPUT. USE KEY INPUTS SAME AS SUBLOGIC FLIGHT SIM II
K$=INKEY$
THROTTLE = THROTTLE + 5*((K$="\1F")*(THROTTLE<100)-(K$="\1E")*(THROTTLE>0))
DLTA = DLTA + 0.5*((K$="T")*(DLTA<23)-(K$="B")*(DLTA>-28)) : REM ELEVATOR UP/DOWN
FLPIN = FLPIN + ((K$="N")*(FLPIN<3)-(K$="Y")*(FLPIN>0)) : REM FLAPS IN/OUT
FLAPS = 0.05*FLPIN+0.95*FLAPS : REM A LITTLE IIR EXPONENTIAL RESPONSE TO MODEL THE FLAP DRIVE MOTOR

The above is the input logic. FLTSIM2D uses a subset of the control keys used in Flight Simulator II. Fortunately, the manual was preserved on Atari Mania (among others), so I didn't have to go by a fading memory. The throttle uses left and right arrow keys. (The BBC Micro version uses ,< and .> keys). The elevator is adjusted using T and B and the flaps using Y and N. To slow the flap actuation and not cause a huge step function in lift and drag, the flap value is smoothed by an exponential filter. I tried smoothing other inputs, but it only seemed to exasperate the phugoid.

POSITION 2,3
?"KTAS",INT(U*1.94)," " : REM KNOTS
?"PITCH",INT(THETA*573)*.1," " : REM DEGREES WITH 1 DECIMAL PLACE
?"ALT.",INT(Z*3.28)," " : REM FEET
?"VRATE",INT(VZ*197)," " : REM FEET/MINUTE
?
?"ELEV.", DLTA," " : REM ELEVATOR POSITION
?"POWER", THROTTLE," " : REM % POWER
?"FLAPS ", INT(FLAPS*10+0.5)," " : REM FLAP POSITION
?
?"STALL ", CHR$(161-116*STALL); : REM STALL INDICATOR
?CHR$(253-221*STALL) : REM BEEP IF STALLING!
?
?"DIST.", INT(X*.9)," " : REM DISTANCE TRAVELED OVER GROUND
?"TIME",T,,INT(600/(TIME-BASETIME));"% " : REM TIME AND SIMULATOR RATE RELATIVE TO REAL TIME

Obviously, this is the output section with all the print statements. I grouped the outputs to try to follow the dashboard from Flight Sim. II: speed, pitch, altitude, vertical rate. he internal calculations are done in SI units, but the output are in customary imperial units. So meters per second are turned into knots or feet per minute. Meters are turned into feet or yards. Radians are turned into degrees. The next group are the control surface position readouts: elevator, throttle and flaps. Then there's a stall indicator, which uses the BELL character to sound the alarm. Finally, distance (GPS?) and time are displayed. The execution rate of the simulator with respect to real time is displayed as a percentage. The TIME function returns 1/60 second ticks (for NTSC video) on the Atari. The BBC Micro returns centisecond (0.01 second) ticks. Values >100% are faster than real time. 

UNTIL OKTOLAND & (Z=0) : REM END GAME IF BACK ON GROUND
?
IF VZ >-2 : REM CHECK TO SEE IF VERTICAL RATE IS SLOW ENOUGH
? "TOUCHDOWN"
ELSE
? "YOU CRASHED!\FD\FD\FD" : REM OH NO! VERTICAL TOO FAST!
ENDIF

This section handles the end of the game. Once the plan returns to the ground after reaching at least 9 meters altitude, the game is over. If you hit the ground going slower than 2 m/s (394 fpm) vertical rate, it counts as a safe landing. Otherwise, its a crash! 

Have a safe flight!

Wednesday, March 1, 2017

Interceptor: another 10-Liner BASIC Game

My second entry to the 2017 10-Liner BASIC Game competition is an homage to Interceptor developed by Tomohiro Nishikado (of Space Invaders fame) and produced by Taito in 1976. I first learned of Interceptor while researching the games listed in the Chronology from the Ernst Cline novel Armada. I was fascinated to learn Interceptor is almost lost to time. There might be a couple machines still floating around, but no one has yet to capture the schematics needed for MAME emulation. In the original Interceptor, you are flying a fighter aircraft chasing down enemy planes. The planes appear to come in singles or pairs and can move in depth as well as x-y coordinates. You use the joystick to move them into your targeting retical and fire. The original machine uses a blue-and-white display with a plastic overlay for the cockpit framing. I hope to capture this look in the BASIC version, although the planes come in singles and there's no animation for the projectiles. Find it over on GitHub.

If you've ever played the original Interceptor arcade game, please leave a comment below about your experience.


Usually when I write these BASIC games, I save the detailed graphics for last. I start with just blocks for the sprites and don't spend too much time initially on the appearance. Rather, I try to nail down the game logic first, then polish it up. This time, I did it backwards. The look was most important to get right first; then, the logic would fit in the rest of the allowed code space. This turned out to be a challenge - at one point 6 lines were dedicated to set up, which is too much. I finally got the setup (screen drawing, sprite definition, initialization, etc.) down to four lines in the final version.


From an online video, it appears the game is the familiar blue and white motif of Atari's graphics 0 and 8 modes where pixels are one television scan line tall and one-half color clock wide. The plastic overlay is a darker blue or black color. The timer and score are displayed in large text at the bottom with a dark blue background. So in my version, I wanted three colors: light blue field, dark blue border, and white sprites and text. This led me to choose graphics mode 7, which is half the resolution (two scan lines by one color clock). I tried graphics 15, which has one-scan-line vertical resolution and is available on the 800XL, early on but concluded the extra resolution didn't add to the visual quality of my limited version. I also use double-line double-wide player sprites, which match the graphics 7 resolution. Only using half the vertical resolution in graphics 7 vs. 15 also saved on characters and commands in the code.

To simplify the game, all the enemy planes fly solo with no wingmen There are up to 3 planes on the screen at one time, one each flying left, right and vertical. This uses three of the four sprites. The fourth player is an explosion graphic that is switched in when you score a hit. To create the explosion, I took a technique from Yars Revenge to create a random pattern without storing the data: point to a location in memory that already has data (e.g., into the code itself). I point to location 95 for 17 bytes that contain a bunch of OS registers. (This technique is described in the great book Racing the Beam about the Atari VCS platform.) The biggest sacrifice made, due to both line count and speed limitations of BASIC, is the drawing of the projectiles. In the original, these are rapidly moving small lines. In mine they are solid lines. I tried some animation, but it took up too much code space. There might be some optimization that can be done, but I feel it works. After all, the BASIC version is humble nod to the original.





Monday, February 13, 2017

SPLATARI - 2017 BASIC 10-Liners Game Competition Entry #1

It's that time of year: there are games to be written instead of doing taxes! My first entry for this year's competition is a take off on Splatoon for the Wii U that I call SPLATARI. It is written in TurboBASIC XL using the TBXL-parser to run on an  Atari 800 XL emulated by Altirra. To grab it, head on over to it's GitHub page or download the ATR file directly. Just boot the ATR on Altirra in NTSC mode - the game will autorun.


Directions

SPLATARI requires two joysticks to play. Move your squid around the screen and splat ink by pressing fire. You have 180 seconds to beat your opponent by inking more of the screen than them. Run out of ink? Swim or rest in your own color. But be careful! You lose health when you swim through your opponent's ink color. Lose all your health and you get sent back to your wall. If you and your opponent collide, you both get knocked back to your respective walls. There's no time to rest because you don't know the score until the time runs out and the counting is done. Press fire to play it again.
Splat ink around the playfield with your squid.
Cover more area than you opponent to win.
Watch your health and ink levels!

Design

The original Splatoon is a first person shooter. One of the Boy's favorite game modes is team vs. team. I took that idea to make this top down version two players. I considered making a single player version, but there's no room left in the 10 lines! The two player version is more fun I think. There's lots of opportunity for trash talking your opponent. The Boy was instrumental in making this game work. He suggested two key features: running out of ink and filling up in your own color; and losing health in your opponent's color. I think those are in the original and they really make the game work. It would be boring without them. The Girl designed the pixel graphic for the player. It's a squid like in the original game. Squid ink. Took me a while to get that.

Code

Well, it's written in BASIC. It fits in 10 lines with lots of abbreviations. Thanks to the TBXL-parser, it wasn't too difficult to optimize in length. It gets tricky at the end of writing, especially when changing a 4-digit number to a 5-digit number (for the colors of the players/sprites) ripples through and knocks half of line 10 onto line 11. Ack! Fortunately, reordering some statements on the first two lines brought it back in spec.

Here's the obfuscated 10 lines in ATASCII:

There's a new requirement this year to provide a version with one-statement-per-line. I suppose that makes things much easier to interpret. My program is 157 lines including white space and comments:
'-----------------------------------------------------------
'
'     SPLATARI
'
'Simplistic Splatoon for Atari 800XL 8-Bit BASIC 10-Liner
'Two-player game with joysticks
'
'Jeff Piepmeier
'February 12, 2017
'http://jeffpiepmeier.blogspot.com/splatari
'http://github.com/jeffpiep/splatari
'
'Parsed with TurboBASIC XL Parser Tool 
'http://github.com/dmsc/tbxl-parser
$options +optimize, optimize=-convert_percent-const_replace
'
'-----------------------------------------------------------

'SET UP MEMORY
DIM X(1),Y(1),INK(1),HEALTH(1)
PLAYER=ADR("\00\00\00\00\18\3C\7E\B7\7E\4C\44\22\00\00\00\00\18\3C\7E\DB\7E\24\24\42\00\00\00\00\18\3C\7E\ED\7E\12\16\24\00\00\00\00")

'SET UP DISPLAY
GR.8:REM CLEAR OUT 8 KBYTES
GR.5:REM USE 80 X 40 PIXELS, 4 COLORS, 4 LINES OF TEXT

'SET UP SPRITES
PM=$AC00:REM THIS SHOULD BE CLEARED OUT BY THE GR.8:GR.5 TRICK
DPOKE 704,$5274:REM COLOR OF PLAYER 1 & 0      
POKE 53277,3:REM ENABLE PM DISPLAY
POKE 559,46:REM ENABLE PM DMA WITH 2-LINE RES
POKE 623,1:REM SET PLAYER PRIORITY OVER PLAYFIELD
POKE 54279,PM/256:REM TELL ANTIC WHERE PM RAM IS

'DRAW A BOX AROUND PLAYFIELD  
C.3
PLOT0,0
DRAWTO79,0
DRAWTO79,39
DRAWTO0,39
DRAWTO0,0

'SET UP LOCATIONS, INK AND HEALTH FILLS
MINX=3
MAXX=76
MINY=3
MAXY=36:REM LIMITS OF PLAYFIELD
X(0)=MINX
Y(0)=MINY:REM LOCATION OF PLAYER 1
X(1)=MAXX
Y(1)=MAXY:REM LOCATION OF PLAYER 2
MAXINK=9:REM FULL INK AMOUNT
INK(0)=MAXINK
INK(1)=MAXINK
MAXHEALTH=9:REM FULL HEALTH AMOUNT
HEALTH(0)=MAXHEALTH
HEALTH(1)=MAXHEALTH

'INITIALIZE SCOREBOX
POKE 752,1:REM TURN OF CURSOR
?"PLAYER","HEALTH","INK","FINAL"
?"1",,,"-"
?"2",,,"-"
?," TIMER:";

'START THE GAME TIMER FOR 180 SECOND GAME
BASETIME=TIME DIV 60 + 180

'GAME LOOP
REPEAT
P=NOT P:REM ALTERNATE PLAYER PROCESSING EVERY PASS OF LOOP

'PROCESS JOYSTICK INPUT
S=STICK(P)
LR=(S&4=4)-(S&8=8)
UD=(S&1=0)-(S&2=0):REM   CREATE +/1 VALUES FOR LEFT/RIGHT AND UP/DOWN 

'FIND THE COLOR YOU ARE SITTING IN AND TAKE ACTION
LOC.X(P),Y(P),PC
SPEED=(1+(PC=P+1)):REM GO FASTER IF YOU ARE IN YOUR OWN INK
HEALTH(P)=HEALTH(P)-(PC=(NOT P)+1):REM LOSE HEALTH IF YOU ARE IN OTHER INK
INK=INK(P)
INK(P)=(INK<MAXINK)*(INK+SPEED-1)+MAXINK*(INK>=MAXINK):REM GET INK IF IN YOUR OWN COLOR

'MOVE THE PLAYER
U=X(P)
V=Y(P):REM USE FEWER SPACES IN LINE BY COPYING VARIABLE OVER
X(P)=U*((U>MINX)&(U<MAXX))+MINX*(U<=MINX)+MAXX*(U>=MAXX)+LR*SPEED:REM LIMIT X MOVEMENT AND MOVE LEFT/RIGHT
Y(P)=V*((V>MINY)&(V<MAXY))+MINY*(V<=MINY)+MAXY*(V>=MAXY)-UD*SPEED:REM LIMIT Y AND MOVE UP/DOWN - AXIS FLIPPED

'SPLATTER INK
IF (STRIG(P)=0)&(INK(P)>0)
 C.P+1:REM SET COLOR
 INK(P)=INK(P)-1:REM USE UP SOME INK
 SPLATX=X(P)+8*LR:REM SPLATTER AHEAD OF PLAYER DIRECTION
 SPLATY=Y(P)-4*UD
 FOR I=1 TO 2:REM CREATE SPLAT
  CIRCLE SPLATX*(SPLATX>=0),SPLATY*(SPLATY>=0),I
 NEXT I
ENDIF

'IF AT ZERO HEALTH, GET KNOCKED BACK TO YOUR SIDE
IF PEEK(53260)=2
 HEALTH(0)=0
 HEALTH(1)=0
 POKE 53278,1:REM CLEAR THE COLLISION REGISTER
ENDIF
IF HEALTH(P)<=0
 X(P)=MINX*(NOT P)+MAXX*P
 HEALTH(P)=MAXHEALTH
ENDIF

'UPDATE POSITION OF SPRITES
MOVE PLAYER+12+12*LR,PM+$200+$80*P+Y(P)*2+9,16:REM SET THE Y POSITION BY MOVING PLAYER
POKE $D000+P,X(P)*2+45: REM SET THE X POSITION OF THE PLAYER

'UPDATE SCOREBOX
POKE 656,1+P:REM SET LINE NUMBER BASED ON PLAYER NUMBER
POKE 657,12
?HEALTH(P),INK(P);

'UPDATE GAME TIMER
XTIME=BASETIME-(TIME DIV 60)
POKE 656,3
POKE 657,19
?XTIME;" ";

'KEEP PLAYING UNTIL OUT OF TIME
UNTIL XTIME=0

'FIGURE OUT SCORE
X(0)=0
X(1)=0:REM INITIALIZE SOME COUNTERS. REUSE X POSITION BECAUSE NO LONGER NEED IT
FOR J=1 TO 38
 FOR I=1 TO 78
  LOC.I,J,PC:REM GET COLOR OF PIXEL
  C.PC+3*(PC=0):REM CHANGE TO A DIFFERENT COLOR TO FILL UP SCREEN
  PLOT I,J:REM FILL IN PIXEL WITH NEW COLOR
  IF PC>0:REM IF NOT BACKGROUND, INCREMENT SCORE
   X(PC-1)=X(PC-1)+1
   POKE 656,PC:REM UPDATE SCORE IN SCOREBOX
   POKE 657,32
   ?X(PC-1);
  ENDIF
 N.I
N.J

'FIGURE OUT THE WINNER
WINNER=(X(1)>X(0))+1-(X(1)=X(0))
C.WINNER+3*(WINNER=0)
TEXT 17,9,WINNER
TEXT 24,9," WINS"

'ALL DONE - WAIT FOR FIRE BUTTON TO RESTART
WHILE STRIG(0)
WEND
RUN
I hope you give it a try! Leave me a comment if you do and let me know what you think. Thanks for reading.