The Othello demo is a simple Android app showing the basics of using LispWorks for Android Runtime. It is a full Android project that can be imported into an Android development environment, for example Eclipse with ADT and Android Studio.
The application plays the Othello game as an example of an application. When delivering "with Lisp" (see Delivering LispWorks to the project below), it also allows the user to type and evaluate Lisp forms. This is useful during development.
To try the demo, you need to do these steps:
These steps are described in detail in the following sections.
examples/android/OthelloDemodirectory inside the LispWorks distribution directory, select it and press Ok .
The new project name defaults to "LispWorksRuntimeDemo", and this, relative to the workspace directory, is the project path that you will need to set the
*project-path* to (below). You can change that before clicking
examples/android/OthelloDemodirectory in the LispWorks distribution and press OK . That should raise a dialog called Import Project from ADT (Eclipse Android) .
*project-path*variable to (below).
To deliver LispWorks, copy one of the build script files
deliver-android-othello-with-lisp.lisp from the
examples/android directory in the LispWorks distribution. In the copied file change the value of the variable
*project-path* to point to the directory of the project that you created above. For example:
(defvar *project-path* "~/my-workspace/LispWorksRuntimeDemo/")
The Android delivery image is called
lispworks-7-1-0-arm-linux-android. This must be run on an ARM architecture, currently that means ARM Linux or an emulator. To run this image under the QEMU emulator, use the script
run-lw-android.sh -build /path/to/deliver-android-othello.lisp
See deliver-to-android-project for details.
*project-path*to some temporary directory, and add
:no-sub-dir tto the call to deliver-to-android-project. For example, if you use
deliver-android-othello.lisp, the call should be:
libLispWorks.soto the directory
libs/armeabi-v7ainside your project, and copy
assetsdirectory inside your project (assuming you are using Eclipse/ADT).
The two deliver scripts,
deliver-android-othello.lisp ("without Lisp" ) and
deliver-android-othello-with-lisp.lisp ("with Lisp") differ in what the application contains. The "with Lisp" script keeps a lot of Lisp in the application, and hence allows evaluation of Lisp forms when running on Android. The "without Lisp" script delivers at the maximum delivery level, and hence cannot evaluate forms. As a result the runtime image is much smaller.
Once you have the project with the LispWorks files, you can build, install it on the device and run it as any other Android project. When it runs, It first shows a splash screen (the LispWorks splash screen image) and then the first screen displays an Othello board, where you can play against the computer (you play black), by touching the square where you want to add your piece.
Restart the game.
Undo the last move. You can undo repeatedly to the beginning of the game.
Takes you to the Lisp Panel screen, which allows you to evaluate Lisp forms. See below in the description of the Lisp Panel.
Raises a submenu with three items: Java server , full proxy and lazy proxy . Switching between these changes the mechanism by which Java calls into Lisp. The behavior of the game is exactly the same, only the output to the Lisp Panel or Output is different. This feature is for demonstrating different techniques of calling from Java to Lisp. See discussion of the code for details.
Takes you to the "output" screen.
The Lisp Panel contains a row of buttons, a text view for input, and the bottom is a text view for output. This screen is available only when delivering "with Lisp". When delivering "without Lisp", there is the Output screen instead.
Clears all the output from the output pane.
eval-for-android is defined in
(example-edit-file "android/android-othello-user"). It reads the string and evaluates it. If it is successful, it prints to the output pane the form, anything the form printed, and the result(s). If there is an error, it logs the error and prints the error message to the output pane.
Takes you to another screen which displays a list of the forms that were evaluated. The list is initialized by some forms which demonstrate some features of the multiprocessing on Android. See below in the section Prepared forms. Whenever you evaluate a form by pressing Evaluate the string , it adds the form to the history in the beginning of it. If the form matches exactly a form which is already in the list, the old item is removed.
Invokes com.lispworks.Manager.showBugFormLogs. This shows another screen with a list of the logged errors displaying the error string for each item. Touching an item opens another screen with bug form log of this error.
Clears all the bug form logs, including removing the files.
The bottom part of the Lisp Panel, in the Output screen when delivering "without Lisp", is the output pane. It prints the output of evaluation as above. It also prints whenever you touch a square in the Othello board. When the Full or Lazy proxy is used for communication, it also prints this fact.
Initially, the History list contains the forms described below. When using forms, note that evaluating a form moves it to the top of the list. When you should evaluate more than one of these forms in order, you will need to look down the list for each one in turn.
(setq *computer-plays-waste-time-in-seconds* 2)
That causes the computer to pretend that it takes it time to compute a move. When playing against the computer after setting this, you will see that after your move, the display says "Computer to play" for two seconds before it actually plays. Set
*computer-plays-waste-time-in-seconds* back to
nil to make it behave normally.
Defines a function to be used by the next two forms. Note that it uses send-message-to-java-host to print, which comes in the output and works on any thread. When it is on the current thread it will end up printing before the printing of the evaluation, but on another thread it is random which output comes first.
(mp:funcall-async 'eval-and-print '(mp:get-current-process))
Use the function
eval-and-print defined above to print the process on which funcall-async executes the function. This will be one of the Background Execute processes.
Create a process called "Loop Execute Events" and set
loop-executing-events-process to it. The process has a process function
loop-executing-events which read events and handles them using process-wait-for-event and format-to-java-host. It prints "got event <event>" and then "handling got <result of handling>". Note the usage of format-to-java-host, which prints to the output pane too (it actually calls send-message-to-java-host).
(mp:process-send loop-executing-events-process '(mp:get-current-process))
Sends to the "Loop Execute Events" process (that started in the previous step) an event, which cause get-current-process to be called, and hence return the process. You should see "got event (MP:GET-CURRENT-PROCESS)" and "Handling got <process name>"
(othello-user-change-a-square 5 2)
Changes square 5 (sixth from the left in the top row) to color 2 (black). This function is defined in
(example-edit-file "android/android-othello-user") and is part of the "interface" that the Lisp Othello code uses to tell Java to change the board.
Starts a process that performs "a lengthy computation" (simulated by using
(sleep 1)) and prints results while doing it. In each "step in the computation" (the
cl:dotimes iteration) it prints the square of the iteration number. To stop it, evaluate the next form.
(setq *finish-multiply* t)
Starts another process that gets an error (because the argument to
cl:open is an illegal pathname). It prints that it got the error, and you can use the
Bug form logs
button to look at the bug form log.
Raises an alert dialog using
raise-alert-dialog which is defined in
dialog.lisp. Note that this works because the
LispPanel class uses com.lispworks.Manager.setCurrentActivity to set the current activity.
(raise-a-toast "Bla Bla Bla" :gravity :left)
The Othello Demo Java code is in the package
com.lispworks.example.othellodemo. LispWorks interfaces in Java are all in the package
com.lispworks. The methods appear in full, to make it is easy to see where there is a call to the LispWorks interface.
Othello is a subclass of
Activity that displays the screen with the Othello board. The display is all in standard Java. The board is made of a grid of 64
ImageView panes, each one displaying one of three images (blank, white, black). Each view has an
OnClickListener(SquareListener) that remembers its index and passes it when clicked.
The Java code processes user gestures concerning the game (touching the board, and touching any of the buttons and items
) by calling methods on an object that implements the nested interface
OthelloServer, which is kept in
mOthelloServer. The object can be either a Lisp proxy, or of the nested class
JavaOthelloServer. All of these objects do exactly the same thing (calling the Lisp functions defined in
(example-edit-file "misc/othello")), and the purpose of having all these options is to demonstrate different techniques to call into Lisp. There is also a nested class
ErrorOthelloServer in case LispWorks does not work, which displays the error.
mOthelloServer is set by the method
The nested class
JavaOthelloServer is plain Java with methods that call into Lisp using the Direct calls interface (com.lispworks.LispCalls.callIntV and com.lispworks.LispCalls.callVoidV). This has the advantage that on the Lisp side all you have to do is to ensure that the functions are not shaken, which you can do with
hcl:deliver-keep-symbols (see the
LispWorks Delivery User Guide
). It has the disadvantage that you hardwire Lisp function names in Java (though the names can be variables too).
The other two possible implementations of the
OthelloServer are Lisp proxies which are defined in Lisp (in
examples/android/android-othello-user.lisp). See the discussion of the Lisp code for more details. The code in
setupServer demonstrates two techniques of using the proxy definitions: either calling a Lisp function that makes a proxy (using com.lispworks.LispCalls.callObjectV to call
create-lisp-othello-server), or using com.lispworks.LispCalls.createLispProxy with the name of the proxy definition (
lisp-othello-server-lazy) to create a proxy.
Othello instance is created, it calls
setupAndInit to do anything with Lisp (mainly call
mOthelloServer.init). Before doing anything that may interact with Lisp, it checks the status of Lisp using com.lispworks.Manager.status. If Lisp is not ready and there was no error, it calls com.lispworks.Manager.init to initialize LispWorks, passing it a
Runnable that with call
setupAndInit again to actually do the initialization. In the Demo the Lisp side will already be initialized, because it is done by the
LispWorksRuntimeDemo activity, but the
Othello class avoids relying on it.
If there is an error,
setupAndInit gets the error details using com.lispworks.Manager.mInitErrorString, and com.lispworks.Manager.init_result_code and adds a message, set
ErrorOthelloServer, and then shows the Lisp Panel which will be displaying the error.
LispPanel is a subclass of
Activity that displays the Lisp panel, or just the output when delivering "without Lisp" (see Delivering LispWorks to the project).
The main purpose of the Lisp Panel is to evaluate Lisp forms, which it does by calling the Lisp function
eval-for-android using com.lispworks.LispCalls.callIntV. That can work only if
eval-for-android is defined, so LispPanel has a method
canEvaluate that works by checking if
eval-for-android is defined using com.lispworks.LispCalls.checkLispSymbol. If
eval-for-android is fbound,
LispPanel displays in full, otherwise it shows only output
LispPanel is also responsible for displaying messages in its output
TextView. To achieve that, it uses com.lispworks.Manager.setTextView. Once it sets the
TextView, all calls to com.lispworks.Manager.addMessage and calls to the Lisp functions send-message-to-java-host and format-to-java-host put their output in this
cl:*debugger-hook*, uncaught errors will end up calling this reporter.
onPauseto allow Lisp code to raise dialogs when
LispPanelis visible. This is needed to allow the
raise-alert-dialogform to work.
MyApplication is not actually used in the demo. It is a demonstration of how to initialize LispWorks when the application starts, by calling com.lispworks.Manager.init in the
onCreate of the application. The demo itself does not use this mechanism. Instead the
SplashScreen activity does it, and the
Othello activity also checks using com.lispworks.Manager.status, and if LispWorks needs initializing does it.
Display a splash screen and initialize the Lisp side, by checking com.lispworks.Manager.status and using com.lispworks.Manager.init if needed. The purpose of this class is just to give an example of displaying a splash screen while initializing Lisp. It is not really needed, because the
Othello class checks too (in
setupAndInit). On Eclipse the name of this class is the default project name.
The Java callers to update the game are defined by a define-java-caller form. All these methods need to be called on the GUI thread (because they interact with GUI elements), so the actual functions that are called from the Othello code are defined to call the Java callers using android-funcall-in-main-thread.
eval-for-android is what the Java code uses to evaluate Lisp forms. The function has no Java-specific features, but it has error handling and binding of some of the top-level variables like
cl:* to make it more usable in repeated calls from "outside".
The code also defines two proxy definitions that implement the
Othello.OthelloServer interface which responds to user gestures. To demonstrate the various features of proxies, there are two definitions which achieve exactly the same thing. The full proxy definition (
lisp-othello-server-full) specifies functions for all the methods that the interface defines. The lazy (programmer) proxy definition does not define any method. Instead it has a default function that decides what to do based on the method name.
LispWorks User Guide and Reference Manual - 20 Sep 2017