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.
The Android project code is in ./OthelloDemo/
, which is the directory examples/android/OthelloDemo
inside the LispWorks distribution. You need to make a project with this code.
The following sub-sections provide the detail for Eclipse and Android studio projects respectively.
This procedure was checked with ADT build: v22.6.2:
examples/android/OthelloDemo
directory 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
Finish
.
This procedure was checked with Android studio 0.4.6:
examples/android/OthelloDemo
directory 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.lisp
or 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/")
You will then use your edited copy of the build script as the -build
command line argument to LispWorks.
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 examples/android/run-lw-android.sh
.
run-lw-android.sh -build /path/to/deliver-android-othello.lisp
See deliver-to-android-project for details.
If you cannot access the directories that you use in your project from the ARM machine:
*project-path*
to some temporary directory, and add :no-sub-dir t
to the call to deliver-to-android-project. For example, if you use deliver-android-othello.lisp
, the call should be:
(deliver-to-android-project nil *project-path* 5 :no-sub-dir t)
This will create two files in the temporary directory called libLispWorks.so
and LispWorks.so.lwheap
.
libLispWorks.so
to the directory libs/armeabi-v7a
inside your project, and copy LispWorks.so.lwheap
to the assets
directory 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.
The display has two elements in addition to the board:
It also has a menu (which maybe partly displayed on the action bar), with these items:
Restart the game.
Undo the last move. You can undo repeatedly to the beginning of the game.
When the computer plays, each undo undoes to the state before your last move.
When the computer does not play, it undoes one move.
When delivering "with Lisp" the menu also has these items:
Takes you to the Lisp Panel screen, which allows you to evaluate Lisp forms. See below in the description of the Lisp Panel.
Takes you a list of the forms that that you evaluated. It is initialized by a few demo forms. See below about the History list.
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.
When delivering "without Lisp" the menu also has these items:
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.
Send the current text in the input pane to Lisp by a direct call to eval-for-android
.
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.
In the history list, when you touch an item it is inserted into the input pane, and the application switches to the Lisp Panel. It does not evaluate the form at that point.
You can also reach the history list from the menu in the Othello screen.
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 input pane below the buttons is just a passive text view, in which you can type Lisp forms, and evaluate by touching the Evaluate the string button.
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.
The idea is that you can try these forms, and then modify them to check and perform things that you need to do when debugging your application.
(mp:ps)
Shows the Lisp processes. Initially there are at least the idle process and the GUI process which displays as "created by foreign code".
(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.
(defun eval-and-print (form)
(let ((res (eval form)))
(lw-ji:send-message-to-java-host
(princ-to-string res) :reset)))
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.
(eval-and-print '(mp:get-current-process))
Use the function defined above to print the process in the current thread. That is the GUI process.
(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.
(progn
(defun loop-executing-events ()
(loop
(let ((event (mp:process-wait-for-event)))
(lw-ji:format-to-java-host "~%got event ~s" event)
(let ((res (mp:general-handle-event event)))
(lw-ji:format-to-java-host
"~%Handling got ~s" res)))))
(setq loop-executing-events-process
(mp:process-run-function "Loop Execute Events" ()
'loop-executing-events)))
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.
(mp:process-run-function
"multiplier" ()
#'(lambda()
(setq *finish-multiply* nil)
(dotimes (x 100)
(sleep 1)
(when *finish-multiply* (return))
(lw-ji:format-to-java-host
\"~%~d * ~d = ~d\"
x x (* x x)))))
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)
Tell the "multiplier" process (see above) to stop.
(mp:process-run-function
"Error"
() #'(lambda () (open "junk;;file::name")))
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.
(raise-alert-dialog
"What do you want to eat?" +
:ok-title "Chicken "
:ok-callback '(raise-alert-dialog "Here is some chicken") +
:cancel-title "Salad "
:cancel-callback '(raise-alert-dialog "We do not have salad"))
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)
Raises an Android "toast" at the middle of the left side, using raise-a-toast
which is defined in toast.lisp
.
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 does not know anything about the game that is being played, and does not keep a record of the state of the game. That is all done in Lisp.
The Java code processes user gestures concerning the game (touching the board, and touching any of the buttons and items
Computer plays
,
undo move
,
restart
) 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 setupServer
.
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.
To actually respond to moves, the Othello
class exports 3 methods ("updateState"
, "signalBadMove"
and "change"
) which are called directly from Lisp to change the board and the status text.
When an 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.
When LispWorks is ready, setupAndInit
sets up the server by calling setupServer
and initializes the game by calling mOthelloServer.init
.
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 mOthelloServer
to ErrorOthelloServer
, and then shows the Lisp Panel which will be displaying the error.
There is also an onCreateOptionsMenu
method which checks whether Lisp is working and can evaluate forms (using LispPanel.canEvaluate
), and accordingly decides which menu to use.
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 TextView
.
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 TextView
.
Other usage of the com.lispworks
package in LispPanel
are:
cl:*debugger-hook*
, uncaught errors will end up calling this reporter.onResume
and onPause
to allow Lisp code to raise dialogs when LispPanel
is visible. This is needed to allow the raise-alert-dialog
form 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.
(example-edit-file "misc/othello")
is a generic implementation of the playing Othello part, and has nothing to do with Java or Android.
The Lisp code that interacts with Java and Android to play Othello and evaluate the forms is in
(example-edit-file "android/android-othello-user")
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.
The function 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.
(example-edit-file "android/dialog")
(example-edit-file "android/toast")
define the functions raise-alert-dialog
and raise-a-toast
respectively, to demonstrate using Android code directly from Lisp. See the comments in these files.
LispWorks User Guide and Reference Manual - 20 Sep 2017