Sunday, May 6, 2018

How to: Run a super simple SQL query from inside Python, or even from the command line...


The reason people use SQL is because you can run short queries like this:

Example Query:

$ SELECT country customer order 'sum{{price*quantity}}' --FROM cust_order_d.py --WHERE 'price*quantity>0' --GROUP_BY country,customer,order --HAVING 'sum{{price*quantity}}>0' --ORDER_BY 'sum{{price*quantity}}'

And get the answer without writing too much code...

Example INPUT cust_order_d.py with python or simple JASON

[
  {'country': 'NZ', 'customer': 'Andrew', 'item': 'Apples', 'order': 1, 'price': 5.0, 'quantity': 2.0},
  {'country': 'NZ', 'customer': 'Andrew', 'item': 'Bananas', 'order': 1, 'price': 1.0, 'quantity': 5.0},
  {'country': 'NZ', 'customer': 'Andrew', 'item': 'Carrots', 'order': 2, 'price': 2.5, 'quantity': 2.0},
  {'country': 'NZ', 'customer': 'Brenda', 'item': 'Apples', 'order': 3, 'price': 5.0, 'quantity': 1.0},
  {'country': 'NZ', 'customer': 'Brenda', 'item': 'Banana', 'order': 3, 'price': 1.0, 'quantity': 1.0},
  {'country': 'NZ', 'customer': 'Brenda', 'item': 'Apples', 'order': 4, 'price': 5.0, 'quantity': 1.0},
  {'country': 'NZ', 'customer': 'Brenda', 'item': 'Carrots', 'order': 4, 'price': 2.5, 'quantity': 1.0},
  {'country': 'AU', 'customer': 'Carol', 'item': 'Apples', 'order': 5, 'price': 5.0, 'quantity': 3.0},
  {'country': 'AU', 'customer': 'Carol', 'item': 'Bananas', 'order': 5, 'price': 1.0, 'quantity': 6.0},
  {'country': 'AU', 'customer': 'Carol', 'item': 'Carrots', 'order': 6, 'price': 2.5, 'quantity': 4.0}
]

Example Output:

[[('country', 'NZ'), ('customer', 'Andrew'), ('order', 2), ("sum(agg['price*quantity'])", 5.0)],
 [('country', 'NZ'), ('customer', 'Brenda'), ('order', 3), ("sum(agg['price*quantity'])", 6.0)],
 [('country', 'NZ'), ('customer', 'Brenda'), ('order', 4), ("sum(agg['price*quantity'])", 7.5)],
 [('country', 'AU'), ('customer', 'Carol'), ('order', 6), ("sum(agg['price*quantity'])", 10.0)],
 [('country', 'NZ'), ('customer', 'Andrew'), ('order', 1), ("sum(agg['price*quantity'])", 15.0)],
 [('country', 'AU'), ('customer', 'Carol'), ('order', 5), ("sum(agg['price*quantity'])", 21.0)]]
 
Note that the order sub-total as correctly calculated and sorted on the right hand side... 

And that didn't even hurt... :-) 

This is only Alpha code, but I am happy to release the code under the following license...

Attribution-NonCommercial-NoDerivs 3.0 Australia(CC BY-NC-ND 3.0 AU) 

Here is the Code:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
end=fi=od=done=esac=yrt=htiw=fed=ssalc=lambda *skip: skip # ¢ replace Bourne's "done" with "od" & add 2¢
import re,time,os,sys,time,string,pprint,pdb
pp=pprint.pprint; width=132
debug=False

from itertools import *
from collections import *

def get_group_by_func(*field_l):
  def out(record_d): 
    return [ record_d[field] if isinstance(field,str) else field(record_d) for field in field_l ]
  return out 

def get_order_by_func(*field_l):
  key_func=get_group_by_func(*field_l)
  return lambda a,b: cmp(key_func(a), key_func(b))

def is_solo_arg(arg):
  return isinstance(arg,(str,unicode))

class PathDict(object):
  def __init__(self,local_d_l=[],global_d={}): 
    self.local_d_l=local_d_l # .copy()???
    if global_d: self.local_d_l+=global_d,

  def __getitem__(self,key):
    for d in self.local_d_l:
      try: return d[key]
      except KeyError as error: pass
    else: raise
  #get=__getitem__

re_var_name=re.compile("^[a-z_][a-z0-9_]*$",re.IGNORECASE)

def threaded_eval(code_l,global_d,local_d_l):
  #for code in code_l: yield eval(code,global_d, local_d_l)
  try: 
    return [ 
      (PathDict(local_d_l=local_d_l,global_d=global_d)[code] 
        if re_var_name.match(code) 
        else eval(code,global_d,PathDict(local_d_l=local_d_l))) 
          for code in code_l ]
    return [ eval(code,global_d,PathDict(local_d_l)) for code in code_l ]
  except Exception as error: 
    pp((error, code_l, local_d_l[:-2]), width=width)
    print('TRACE mode: enter "c" to continue');pdb.set_trace()

def get_agg_dict(key_l, value_l_g):
  if False and len(key_l)==1: # False? <= sometime the same agg is used in 2 of SHO :-(
    return {key_l[0]:(value_l[0] for value_l in value_l_g)}
  else: # ideally this next line would use pythreading
    return dict(zip(key_l,zip(*list(value_l_g))))

def trace_group_by(gen_d, group_func):
  out_l=[]
  for enum, d in enumerate(gen_d): 
    k=group_func(d)
    if not enum or k==prev_k:
      out_l+=d,
    else:
      yield prev_k, out_l
      out_l=[d]
    prev_k=k
  if out_l: yield prev_k, out_l
      
group_by=groupby

re_parse_middle_expr=re.compile(r"{{((?:[^{}]|}[^}]|{[^{])*)}}")  # {{...}}  to look up a agg

def parse_agg_expr(expr):
  tok_l=re_parse_middle_expr.split(expr)
  inner_l=tok_l[1::2]
  tok_l[1::2]=["(agg[%r])"%inner for inner in inner_l]
  inner_d=OrderedDict(zip(inner_l,tok_l[1::2]))
  outer="".join(tok_l)
  return outer,inner_d

def open_from(from_name):
  if not isinstance(from_name,(unicode,str)):
    return from_name
  else:
    if from_name.endswith(".py"):
      return eval("".join(open(from_name,"r")),globals())
    elif from_name=="-":
      return sys.stdin

def gen_d_from_csv(col_key_l, csv_str, sep=",", conv_d={}):
  for line in csv_str.splitlines():
    if line:
      out=OrderedDict(zip(col_key_l,line.split(sep)))
      for key_l,conv_t in conv_d.items():
        for key in key_l.split():
          out[key]=conv_t(out[key])
      yield out

def QUERY(SELECT=None,FROM=None,WHERE=None,GROUP_BY=None,HAVING=None,ORDER_BY=None):
  if is_solo_arg(SELECT): SELECT=SELECT.split(",")
  if is_solo_arg(GROUP_BY): GROUP_BY=GROUP_BY.split(",")

  if not HAVING: HAVING=[]
  if is_solo_arg(HAVING): HAVING=HAVING.split(",")

  if not ORDER_BY: ORDER_BY=[]
  if is_solo_arg(ORDER_BY): ORDER_BY=ORDER_BY.split(",")

  if not ORDER_BY: 
    ORDER_BY=[]
  else:
    shared_eval_d_l=[]

  SELECT=list(SELECT)
  FROM=list(open_from(FROM))
  if debug: pp((dict(
    SELECT=SELECT,
    FROM=FROM,
    WHERE=WHERE,
    GROUP_BY=GROUP_BY,
    HAVING=HAVING,
    ORDER_BY=ORDER_BY,
  )),width=width)

  if WHERE: record_d_where_g=( record_d for record_d in FROM if eval(WHERE,globals(), record_d) )
  else: record_d_where_g=FROM

  SHO=dict(SELECT=SELECT, HAVING=HAVING, ORDER_BY=ORDER_BY,)

  outer_SHO_code_d=OrderedDict()
  inner_SHO_agg_code_d=OrderedDict()

  for keyword,expr_l in SHO.items():
    for enum,expr in enumerate(expr_l):
      outer,inner_d=parse_agg_expr(expr)
      SHO[keyword][enum]=outer
      outer_SHO_code_d[outer]=expr
      inner_SHO_agg_code_d.update(inner_d)
  """
Wikipedia: The clauses of a query have a particular order of
execution[7], which is denoted by the number on the right hand
side. It is as follows:
* SELECT <columns>: 5
* FROM <table>: 1
* WHERE <predicate on rows>: 2
* GROUP BY <columns>: 3
* HAVING <predicate on groups>: 4
* ORDER BY <columns>: 6
  """
  if not GROUP_BY:
    out_g=record_d_where_g
  else:
    group_by_func=get_group_by_func(*GROUP_BY)
    for group_value_l, group_record_d_g in group_by(record_d_where_g, group_by_func):
      group_d=dict(zip(GROUP_BY,group_value_l))
# Finally!!! aggregate...
      inner_agg_val_l_g=(threaded_eval(code_l=inner_SHO_agg_code_d.keys(),global_d=globals(),local_d_l=[group_record_d]) 
        for group_record_d in group_record_d_g )
      inner_agg_val_l_of_agg_code=get_agg_dict(inner_SHO_agg_code_d.keys(), inner_agg_val_l_g)
      shared_eval_d=dict(zip(outer_SHO_code_d.keys(),
        threaded_eval(code_l=outer_SHO_code_d.keys(),
        global_d=globals(), local_d_l=[group_d, dict(agg=inner_agg_val_l_of_agg_code)]))
      )
      if HAVING and not shared_eval_d[HAVING[0]]: continue
      if ORDER_BY: shared_eval_d_l+=shared_eval_d, # note the ","
      else: yield[(select,shared_eval_d[select]) for select in SELECT]

  if ORDER_BY: 
    shared_eval_d_l.sort(get_order_by_func(*ORDER_BY))
    for shared_eval_d in shared_eval_d_l: 
      yield[(select,shared_eval_d[select]) for select in SELECT]
      #yield[shared_eval_d[select] for select in SELECT]

####################################################################
# Unit Test Section
####################################################################
col_key_l="customer country order item price quantity".split()

order_str="""
Andrew,NZ,1,Apples,5.00,2
Andrew,NZ,1,Bananas,1.00,5
Andrew,NZ,2,Carrots,2.50,2
Brenda,NZ,3,Apples,5.00,1
Brenda,NZ,3,Banana,1.00,1
Brenda,NZ,4,Apples,5.00,1
Brenda,NZ,4,Carrots,2.50,1
Carol,AU,5,Apples,5.00,3
Carol,AU,5,Bananas,1.00,6
Carol,AU,6,Carrots,2.50,4
"""
cust_order_product_detail_d_g=gen_d_from_csv(col_key_l, order_str, conv_d={"price quantity":float, "order":int})

OrderedDict=dict

def unittest_1a(): # an example of call from another module
  ans=QUERY(
    SELECT="country,customer,order,sum{{price*quantity}}",
    FROM=cust_order_product_detail_d_g,
    WHERE="price*quantity>0.1",
    GROUP_BY="country,customer,order",
    HAVING='sum{{price*quantity}}>0',
    ORDER_BY='sum{{price*quantity}}',
  )
  pp(list(ans),width=width)

if __name__=="__main__":
  # unittest_1a(); sys.exit()
# https://stackoverflow.com/questions/3217673/why-use-argparse-rather-than-optparse
  import argparse
  parser = argparse.ArgumentParser(description='Pull target_ls from an html file or URL.', epilog="Good luck.")
  parser.add_argument('SELECT', nargs='+', help="columns to select")
  parser.add_argument("--debug","-X", action="store_true", help="drop into debug if there is a problem")
  parser.add_argument("--FROM","-F", default="-", help="name of table to query")
  parser.add_argument("--WHERE","-W", default=None, help="predcate on rows")
  parser.add_argument("--GROUP_BY","-G", default=None, help="columns to group")
  parser.add_argument("--HAVING","-H", default=None, help="predcate on groups")
  parser.add_argument("--ORDER_BY","-O", default=None, help="columns to order")

  arg_d = parser.parse_args() 
  debug=arg_d.debug
  ans=QUERY(
    SELECT=arg_d.SELECT,
    # FROM=cust_order_product_detail_d_g,
    FROM=arg_d.FROM,
    WHERE=arg_d.WHERE,
    GROUP_BY=arg_d.GROUP_BY,
    HAVING=arg_d.HAVING,
    ORDER_BY=arg_d.ORDER_BY,
  )
  pp(list(ans),width=width)

Tuesday, April 24, 2018

Anzac: 1918 Military Duty and Death Duty...


This story was repeated right across AU+NZ... All a Mother's Sons we called up and sent, with their own horse and rifle, to fight in a far off land.
Only to receive a telegram months later that one son wont be coming home tonight.

 Farms lay idle "while the boys were away", the girls had to do a "man`s job" or the scrub soon replaced what was once grass. And then the boys that returned had a double battle of recovering from the fighting, and restoring the farm.
The small boy sitting is my Great Uncle Claude. He was killed on Saturday 30th March 1918 in Aaman and didn't get to return to Kaitieke.
One of the wars ironies was that every boy that was killed in action, the families had to pay "Death Duty" on any land the son had owned. So ⅓ of the bush farm that Great Uncle Claude had cut from the bush before joining the Wellington Mounted Rifles became property of the NZ Tax Office. Then in 1950s, my Great Grandmother Racheal (on the right) was required to pay a further £1000 in 1950s to cover a Claude`s death as Claude`s farm was worth more then originally assessed at the time of his death.
It seems that War can be very profitable for Wellington... even when your favourite sons are being killed.
To this day I dont get how a government can levy a tax on someone killed while serving their country. The world has changed soo much in 100 years. Today a widow would not be required to sell ⅓ of her home to pay "Death Duty" after her sweat heart was killed in Afghanistan.

Saturday, March 17, 2018

Palestine 5/3/18 My Dear Mum, I'll have to hurry up if I want to get written all all I want to before we start work again.


 Palestine
5/3/18
My Dear Mum,

I'll have to hurry up if I want to get written all all I want to before we start work
again. Plenty to do these days + very little time left for writing.
Labou the place I just spoke of is only a small Arab village at the junction of 2
main roads from Jaffare the(?) South but it was from there that the 2 thieves who where crucified with Christ came from. It is a little more than ½ way from Jaffa to Jerusalem on the main tourist road + from there you start to climb up into the hills.

My nord the road is steep for it is impossible to get a grade . In places it comes down in the fashion ⦚↯
Image may contain: one or more people, sky and outdoor
(Title: "Mounted New Zealand World War 1 troops in Palestine, moving towards the Jordan River.")
Kmjet el Guab is another valley. it is mostly a Jewish village + is rather a pretty place in the Jewish town lies on the side of a step Jace + there are quite a few fine buildings in it.
Page 1...
No automatic alt text available.
... Cont Page 2...

Wednesday, December 20, 2017

Great News... Samsung Gear S3 eSIM available for download.

Update: Featured Samsung and Spark bring Kiwis smartwatch with standalone connectivity

First you need a 3G or 4G compatible SmartWatch:

  • Do NOT get a Samsung Gear S3 (BlueTooth) - SM-R760
  • Do NOT get a Samsung Gear S3 (BlueTooth) - SM-R760X - This is a "Display Model"
  • If you are holidaying in a country over Xmas that only has a 4G mobile phone network, then get the appropriate 4G capability: Samsung Gear S3 (LTE) eg. Singapore's SM-R765F has 3G and 4G (cf: Wikipedia: Samsung Gear S3 comparisons)
    • Important: If you was international 4G compatibility, ask the sales person to confirm the watch supports LTE-4G preferably have "watch supports LTE" written on the receipt, or you run the chance the watch wont receive Texts or calls when you holiday overseas. 
  • Note that Spark New Zealand seems to have a special arrangement with Samsung (and Noel Leeming) to provide NZ with pre-downloaded Spark eSIMs for the SM-R765N series watch.
    • Model: SM-R765NDAATNZ - From Spark? 
    • Model: SM-R765NDACTNG - From Noel Leeming?  (And Samsung NZ?)
    • The big problem with the SM-R765N model is that it appears to only support 3G networks and will more receive Texts or phone calls in country that are already 4G. (Less then ideal when you receive calls while in the hotel pool-bar)
  • Note: Before you visit New Zealand downloading your eSIM.  This is because the NZ provider "Spark", has yet to traine it's shop front staff on how to download an eSIM using the international QR code.

 How to (download and) register your international eSIM:

International eSIM registration.

First you need an appropriate QR code from your mobile provider:
 

This QR code links to the "airon" eSIM download web site and from this web site a new eSIM profile is downloaded to your watch.
  • Download the Samsung Gear S3 app form your app store.
  • Once downloaded select: "Gear eSIM profile: Register Gear eSIM profile via phone"

  • Step-A) "Register an eSIM profile to connect to mobile networks via your Gear. Tap SCAN to read the QR code on your eSIM voucher."

  • Germany: Vodafone ...
    • https://prod.aironv4.gi-de.com/sr/rest$4E0B86696D8665E69BF35C4DF7A76D8D
    • https://prod.aironv4.gi-de.com/sr/rest$3528FF91251647CA130669E2B393584F
  
  • Step-B) "A new eSIM profile has been detected.  Tap REGISTER to download and install this eSIM profile on your Gear."

Trouble Shooting:

"Failed to register the eSIM profile". =>  "A Gear system error has occurred.  Try again later"
  • This indicate your eSIM has already been registered or ...
  • Your mobile phone provider may not have an active
    "airon" eSIM download web server.
  • (If the server is off-line, or your provider make not have signed an agreement with
  • other.... 

What to do if your mobile provider does not support the QR-Code with eSIM download?

  • Japan -  NTT Docomo picks G&D AirOn service for eSIM deployment
  • China - 捷德公司推出AirOn V4 eSIM管理解决方案
  • Europe - G&D presents wearable solutions for banks, mobile operators and OEMs at MWC 2017
  • Germany - eSIM Management: A New Version for A New Era
  • Australia - Vodafone eSIM download server maybe on-line before End March 2018.
  • New Zealand - Vodafone eSIM download server maybe on-line before End February 2018.
It is best to pick an Mobile Provider that provides eSIM downloads now.  In future all SIMs will be downloaded.

Other consumer devices that support eSIM:

Tuesday, April 5, 2016

Just for fun - the 6kb chess engine - NanoChess68 ...

Just for fun - Cut and paste the following Algol code at: Execute Algol online...
#!/usr/bin/a68g --script #
# -*- coding: utf-8 -*- #
 
COMMENT
# Toledo Nanochess (c)2009 Oscar Toledo G. #
# http://www.nanochess.org/ #
 
# Algol-68 translation (c)2010 Neville C. Dempsey #
# http://sourceforge.net/projects/algol68 #
 
# v0.4   - Tue Jul 9 21:23:49 EST 2013 #
# v0.4.1 - Tue Apr 5 20:54:20 NZST 2016 - fix []INT overflow #
END COMMENT
 
INT bb,i,y,u,b:=666;# b is used before it is defined! #
[0:666]INT ii;
FOR i FROM LWB ii TO UPB ii DO ii[i]:=0 OD;
INT gg:=120,x:=10,z:=15,mm:=10000;
 
[]INT l=[]INT(5,3,4,6,2,4,3,5,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,13,11,
12,14,10,12,11,13,0,99,0,306,297,495,846,-1,0,1,2,2,1,0,-1,-1,1,-10,
10,-11,-9,9,11,10,20,-9,-11,-10,-20,-21,-19,-12,-8,8,12,19,21)[@0];
 
PRIO <<=5;# Should be 5.5 to match C #
OP <<=(INT i,s)INT: ABS (SBIN i SHL s );
 
OP SBA=(INT i)BOOL: i NE 0;# Cast a INT into an BOOL #
 
OP SBIN = (INT i)BITS: (i>=0 | BIN i | NOT BIN ABS (i+1) ); # 2s compliment #
PRIO XOR=2;# 2.5 to match C #
OP XOR=(INT a,b)INT: ABS ((SBIN a OR SBIN b)&NOT (SBIN a&SBIN b) );
PRIO XORAB=1;
OP XORAB=(REF INT lhs,INT i)VOID: lhs:=lhs XOR i;
 
OP INC=(REF INT lhs)VOID: lhs+:=1;
OP DEC=(REF INT lhs)VOID: lhs-:=1;
OP POSTINC=(REF INT lhs)INT:(INT out=lhs;lhs+:=1;out);
OP POSTDEC=(REF INT lhs)INT:(INT out=lhs;lhs-:=1;out);
OP PREINC=(REF INT lhs)INT: lhs+:=1;
OP PREDEC=(REF INT lhs)INT: lhs-:=1;
 
PRIO BOR=2,BAND=3;# Caution: should be 3.2 and 3.3 #
OP BOR=(INT a,b)INT:ABS (SBIN a OR SBIN b);
OP BAND=(INT a,b)INT:ABS (SBIN a&SBIN b);
OP BOR=(INT a,BOOL b)INT: a BOR ABS b;
OP BOR=(BOOL a,INT b)INT: ABS a BOR b;
OP BAND=(INT a,BOOL b)INT: a BAND ABS b;
OP BAND=(BOOL a,INT b)INT: ABS a BAND b;
 
OP NOT=(INT i)INT: ABS(i=0);
 
PROC ww=VOID: BEGIN
  bb:=b;
# FOR # INT p:=21;WHILE p<99 DO
# Uncomment 'unicode' to show graphics in Linux, Jul/04/2011 O.T.G. #
    piece OF element[p]:=#unicode# pieces[ii[p] BAND z];
    moved OF element[p]:=p=bb;
    p+:=(SBA(p%*x-8)|1|3)
  OD
END;
 
PROC xx=(INT w,c,h,e,ss,s)INT: BEGIN
  INT out;
  INT t,o,ll,ee,d,oo:=e,
  INT nn:=-mm*mm,
  INT kk:=78-h<<x,p,g,n,m,aa,q,r,cc,jj,a:=(SBA y|-x|x);
 
  y XORAB 8;
  INC gg;
  d:=ABS(SBA w OREL ((SBA s ANDTH s>=h ) ANDTH xx(0,0,0,21,0,0)>mm ));
  WHILE (
    IF SBA(o:=ii[p:=oo])THEN
      q:=o BAND z XOR y;
      IF q<7 THEN
        aa:=(SBA(POSTDEC q BAND 2)|8|4);
        cc:=(SBA(o-9 BAND z)|(q+1|53,47,61,51,47,47)|57);
        WHILE (
          r:=ii[p+:=l[cc]];
          IF SBA(NOT w BOR p=w)THEN
            g:=(SBA(q BOR p+a-ss)|0|ss);
            IF ((SBA(NOT r BAND SBA(NOT NOT q BOR aa<3)) OREL SBA NOT NOT g) OREL ((r+1 BAND z XOR y)>9 ANDTH SBA(q BOR aa>2)))THEN
              IF SBA(m:=NOT (r-2 BAND 7))THEN y XORAB 8;ii[POSTDEC gg]:=oo;out:=kk;return FI;
              jj:=n:=o BAND z;
              ee:=ii[p-a] BAND z;
              t:=(SBA(q BOR ee-7)|n|n+:=2;6 XOR y);
              WHILE n<=t DO
                ll:=(SBA r|l[r BAND 7 BOR 32]-h-q|0);
                IF SBA s THEN ll+:=(SBA(1-q)|
                  l[(p-p%*x)%x+37]-l[(oo-oo%*x)%x+37]+ l[p%*x+38]*(SBA q|1|2)-l[oo%*x+38]+ (o BAND 16)%2|NOT NOT m*9)+ (SBA NOT q|
                NOT (ii[p-1] XOR n)+NOT (ii[p+1] XOR n)+ l[n BAND 7 BOR 32]-99+ NOT NOT g*99+ABS(aa<2)|0)+ NOT (ee XOR y XOR 9) FI;
                IF s>h OREL ((1<s #B#&s=h ) ANDTH SBA(ll>z BOR d)) THEN
                  ii[p]:=n;ii[oo]:=(SBA m|(ii[g]:=ii[m];ii[m]:=0)|(SBA g|ii[g]:=0|0));
                  ll-:=xx((SBA(s>h BOR d#?#)|0|p),ll-nn,h+1,ii[gg+1],jj:=(SBA(q BOR aa>1)|0|p),s);
                  IF SBA NOT ((((SBA h OREL SBA(s-1 BOR bb-oo) ) BOR i-n ) BOR p-b ) BOR ll<-mm) THEN ww;DEC gg;u:=jj;out:=u;return FI;
                  jj:=ABS(((SBA(q-1 BOR aa<7) OREL SBA m) OREL SBA(NOT s BOR d BOR r BOR o<z) ) OREL xx(0,0,0,21,0,0)>mm);
                  ii[oo]:=o;
                  ii[p]:=r;
                  (SBA m|(ii[m]:=ii[g];ii[g]:=0)|(SBA g|ii[g]:=9 XOR y|0) )
                FI;
                IF ll>nn OREL (((s>1 ANDTH ll=nn) ANDTH SBA NOT h) ANDTH next random<.4) THEN
                  ii[gg]:=oo;
                  IF s>1 THEN
                    IF SBA h ANDTH c-ll<0 THEN y XORAB 8;DEC gg;out:=ll;return FI;
                    IF SBA NOT h THEN i:=n;bb:=oo;b:=p FI
                  FI;
                  nn:=ll
                FI;
                n+:=ABS(SBA jj OREL SBA (g:=p;m:=(p<oo|g-3|g+2);((SBA(ii[m]<z BOR ii[ m+oo-p])) OREL SBA ii[p+:=p-oo])|1|0))
              OD
            FI
          FI;
        # WHILE # (SBA(NOT r BAND ABS(q>2)) OREL (p:=oo;SBA(q BOR aa>2 BOR o>z BAND NOT r) ANDTH SBA(PREINC cc * PREDEC aa)))
        ) DO ~ OD
      FI
    FI;
  # WHILE # SBA(PREINC oo>98|oo:=20|e-oo)
  ) DO ~ OD;
  y XORAB 8;DEC gg;out:=(SBA(nn+mm*mm) ANDTH SBA(ABS(nn>-kk+1924) BOR d)|nn|0);
  return: out
END;
bb:=i:=y:=u:=0;
WHILE POSTINC bb<120 DO
  ii[bb-1]:=(SBA(bb%*x)|
    (bb%x%*x<2 #B#OR bb%*x<2|7|
      (SBA(bb%x BAND 4)|0|l[POSTINC i] BOR 16) )
|7)
OD;
 
MODE ELEMENT=STRUCT (
  STRING piece,
  BOOL moved,
  INT index
);
 
[0:98#?#]ELEMENT element;
FOR i FROM LWB element TO UPB element DO # ~ Extra #
  element[i]:=("",FALSE,0)
OD;
 
PROC print page=VOID:(
  STRING a:=" ";
  FOR i TO 8 DO a+:=" "+REPR(ABS "a"+i-1) OD;
  a+:=REPR 10;
  FOR bb FROM 0 TO 8-1 DO
    a+:=whole(8-bb,-0);
    STRING sep:=" ";
    FOR i FROM 21 TO 29-1 DO
      INT id=bb*x+i;
      STRING sq:=piece OF element[id];
      ((ODD bb=ODD id)&sq=" "|sq:="+" );
      IF moved OF element[id] THEN
        a+:="["+sq+"]";sep:=""
      ELSE
        a+:=sep+sq;sep:=" "
      FI
    OD;
    a+:=sep+REPR 10
  OD;
  print(a); print(new line)
);
 
[]STRING pieces=[]STRING(" ","♟","♚","♞","♝","♜","♛",~,~,"♙","♔","♘","♗","♖","♕")[@0],
   ascii pieces=[]STRING(" ","p","k","n","b","r","q",~,~,"P","K","N","B","R","Q")[@0];

 
ww;
 
PROC yy=(INT s)VOID: BEGIN
  i:=(ii[s] XOR y) BAND z;
  IF i>8 THEN
    b:=s;
    ww
  ELIF SBA bb ANDTH i<9 THEN
    b:=s;
    i:=ii[bb] BAND z;
    IF (i BAND 7)=1 #B#& (b<29 #B#OR b>90) THEN i:=14-index OF element[0] XOR y FI;
    xx(0,0,0,21,u,1);
    IF SBA y THEN xx(0,0,0,21,u,3#ply#);xx(0,0,0,21,u,1) FI
  FI
END;
 
first random(ENTIER(seconds*1024));
 
WHILE
  [2]CHAR move;
  print page;
  print("Enter your move: ");
  read(move);print(new line);
# WHILE # NOT string in string(move,NIL,"win won lose loss draw stalemate quit exit") DO
  INT imove:=ABS move[1]-ABS "a"+(ABS "9" - ABS move[2])*10+11;
  IF LWB ii > imove ORF imove > UPB ii THEN
    print(("Enter moves as eg: g2<enter>g3<enter>..., or 'qu' to quit", new line))
  ELSE
    yy(imove)
  FI
OD