about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/freedom.html
blob: 49a5d04c5da5058ae5145deaf7ec25a31a3e4412 (plain) (blame)
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
<HTML>
<HEAD>
<TITLE>Using Computers for Educational Freedom</TITLE>
</HEAD>
<BODY>
<H1>Using Computers for Educational Freedom</H1>
<CITE>Brian Harvey<BR>University of California, Berkeley</CITE>

<P>[This article is adapted from a talk given by the author at the Second
Annual Computer Conference at Lesley College, May 3, 1980.]

<P>The computer is fast becoming an educational cure-all; depending on which
expert you consult, it can teach problem-solving skills, teach basic
arithmetic by making drill fun, replace the teacher, augment the teacher,
or provide experiential learning.  Given all these possibilities, it's
hard to establish priorities when setting up a computer facility.  The way
to choose what to do first, from among the many exciting possibilities, is
to start with a clear idea of your overall goals.

<P>I would like to suggest one possible goal, and consider its practical
implications.  The goal is summed up in this statement by Ted Nelson:
<EM>``The purpose of computers is human freedom.''</EM>
[From <CITE>The Computer Lib Pledge</CITE> (c) 1978 Ted Nelson.]
That's pretty vague, as it stands.  Let me say first that it doesn't mean
<EM>not</EM> to think about other goals.  It does, though, establish priorities
in buying equipment and in spending time on development of the facility.  We
have set up a computer facility at Lincoln-Sudbury Regional High School
(we bought a Digital Equipment Corporation PDP-11/70 in 1979), and
our choices will help explain what I understand by this goal.

<P>The word ``freedom'' means many different things in different contexts.  For
the purposes of this discussion, though, I want to consider two fairly
narrow components of freedom: <EM>variety</EM> and <EM>initiative</EM>.

<H2>Variety: Activities vs. Tools</H2>

<P>It makes no sense to talk about freedom for students unless they have
choices to make.  And as Jonathan Kozol points out, they have to be
<EM>significant</EM> choices--deciding between tuna fish and peanut butter
in the cafeteria doesn't count.

<P>Probably the first thing which comes to mind under the heading of variety
is a variety of activities, as in the open classroom approach.  In the
context of computer education, we can provide a variety of game programs
for student use, and a variety of suggested programming projects.  This
kind of variety is an obviously worthwhile step, but I think that the
computer allows a much more profound step toward freedom: a variety of
<EM>tools</EM>.

<P>You can go to the store and buy a ``computer game'' with a name like
Electronic Football.  The game implies one specific activity.  If it's
a good game, you may play it often.  But if you get bored with the
activity, the device is useless to you.  Alternatively, you can buy a
screwdriver.  This tool is not limited to one activity; in fact, it
doesn't suggest an activity at all.  That is, you don't say ``I think
I'll go play with my screwdriver now.''  Instead, you say ``I think I'll
fix that loose hinge now,'' and you reach for your screwdriver without
thinking about it.

<P>It may be overstating the case to say that the Electronic Football game
actually decreases its owner's freedom, but certainly a good assortment
of tools is much more conducive to free behavior.  The computer lends
itself to creating such a toolkit.  Here
are some of the tools we offer:

<UL>
<LI>The <EM>turtle</EM> is a small robot which moves on the
floor under computer control.  It also has headlights and a horn.  It
lends itself to many different
activities: it can be taught to dance; it can draw pictures with the pen
in its belly; it can be sent across the room to attack one's friend at another
terminal; using its touch sensors, it can be taught to maneuver around
obstacles or to escape from mazes.

<LI>We have a Diablo printer, a typewriter-like device which allows variable
character spacing.  With suitable software tools, it can be used to print
papers with justified margins, boldface, underlining, and automatic
hyphenation.  This tool was originally envisioned as an aid to the writing
of English or history papers.  However, students have come up with several
unanticipated uses for this tool.  Our school newspaper, the <EM>Promethean</EM>,
is using it to typeset their articles.  This activity is one in which a
group of students with no direct interest in computers is using the machine
to further what <EM>is</EM> their direct interest: putting out a
newspaper.  Another activity is the creation of computer graphics, by
printing closely spaced dots.  The first such project was done by a student who
typed in the necessary sequence of
dots and spaces by hand, with no programming involved; later projects have
become more sophisticated.

<LI>The primary means of communication with the computer are our VT-100 display
terminals.  These are not graphics terminals, in the sense that it is not
possible to draw a smooth curve on their screens.  They were intended simply
as text display terminals, and one of our most important software tools is a
display-oriented text editor which exploits their capabilities.  The chore of
entering information into the computer is made infinitely easier with good
display software, compared to the more common line-oriented hardcopy editors.
But the VT-100 also has a ``graphics character set'' which allows lines, blocks,
and a few other special symbols to be displayed in place of text.  I've been
spoiled by the powerful graphics terminals at university research centers,
and the limited graphics of the VT-100 was beneath my notice.  But several
students have used the VT-100 to program PDP-11 timesharing versions of some
of the standard personal computer video games, such as Breakout and Asteroids,
and Conway's mathematical game of Life.  Writing such a game is both more
educational and more fun than simply playing one which somebody else programmed.
But even for the other students who play these games, it is better that they
are programmed by a fellow student and not by a wizard off in a distant
castle.  The program is available for inspection, and the authors are available
for questioning.

<LI>We have five Atari 800 personal computers, which we use as graphics
terminals.  The same commands which move the robot turtles across the
floor can be used to draw pictures by moving a ``display turtle'' across
the TV screen.  A special 6502 machine language program for the Atari
processes display commands sent from the central PDP-11 system.  The
Ataris are used as terminals, rather than as independent computers, so
that students can use the powerful Logo programming language, which is
not available for the Atari itself.

<LI>The Unix
operating system we use on the PDP-11 provides literally hundreds
of software tools, small and large.  At the small end are things like a
program to print a file, going over each line twice, to get readable output
from a hardcopy terminal with a weak ribbon.  At the large end are the
programming languages, of which more later.  In the middle are the document
formatter, an automatic spelling checker with a large dictionary, the text
editor mentioned earlier, a sort program, and a utility program which extracts
from a data file all lines containing a user-specified text string.  As an
example of the importance of these tools, one of the computer center ``regulars''
two years ago was
also very interested in the newly-formed school radio station.  He wanted
to use the computer to maintain a catalog of the radio station's record
collection.  He envisioned a major programming project to develop programs to
allow typein of record titles, recording artists, and so on; to produce lists
sorted by title, by artist, or by record company; and to find out whether a
particular record, or any record by a particular artist, is in the catalog.  I
pointed out that
the existing text editor, sort program, and text-matching utility are
sufficient without additional programming.  A sequence of two or three commands
to the existing programs can be written in a minute or two.  Also, the operating
system allows such a sequence of commands to be written in a file, and given
a name, defining a new command.  This facility makes it easy for other students
to use the record catalog without knowing the details of operation of the
utility programs.

<P>The example is important because it illustrates the point that a student
with a well-equipped toolkit can accomplish tasks of practical interest, which
might otherwise seem impossible to non-wizards.  <EM>Good tools expand kids'
view of the possible.</EM>  This point ties the technical issue of a variety of
tools to the more political, or psychological, question of initiative.  The
connection will be discussed further below.
</UL>

<P>The most powerful of software tools is the programming language.  A student
who can program is truly free to use the computer in ways not anticipated by
a teacher or operating system designer.  The choice of programming language
has a profound effect on the range of problems within the student's grasp;
some languages are more powerful than others, and also some are more
conducive than others to a programming style which will make large problems
comprehensible to mere human beings.  For beginning programming students, we
use the Logo language.  This language, developed specifically as a teaching
language at MIT and at Bolt Beranek and Newman, Inc., is simple and interactive,
like BASIC, but also allows the power of list processing and recursive
procedures, like the LISP language from which many of its ideas came.  The
beginning programmer can type in a simple command for immediate execution
(PRINT 2+2) or store a sequence
of commands as a named procedure for later
use.  A complex problem can be divided naturally into sub-problems, each
solved by a sub-procedure of the main program.  A procedure can also use
<EM>itself</EM> as a sub-procedure.  The language contains provisions for
interesting problem domains like graphics (through the turtle commands
mentioned earlier) and language processing (for example, translating a
sentence into Pig Latin).

<P>Other languages we use are APL, Pascal, C, and LISP.  APL is used by the
Mathematics Department not to teach programming <EM>per se</EM>, but to provide
as a tool to students what amounts to a calculator which understands algebra.
The language hides many of the problems of control structure which are
prominent in more conventional languages, and emphasizes instead mathematical
concepts like functions, vectors, and matrices.  Pascal is quickly becoming
a very popular teaching language because it is available on many microcomputers
and is designed to foster the Structured Programming style.  I think it suffers
as an initial teaching language from the fact that it is not interactive; the
student must learn to cope with details of text editors, files, and operating
systems before writing even the simplest Pascal program.  However, it is a
marvelous <EM>second</EM> language for the student who has mastered these details,
because it calls attention to issues of data types and storage allocation which
are hidden in an interactive language with dynamic allocation, like Logo.  [1994
addendum:  I can't believe I said that!] The
C language is much like Pascal in its design, but it has the added benefit that
most of the Unix operating system software itself is written in C, so a student
who is curious about the inner workings of the software can read the actual
programs after learning C.  Finally, LISP is one of the most powerful of
languages, used widely in Computer Science research.  It provides a worthwhile
challenge to our advanced students, and has been used in one formal course on
Computational Linguistics.

<P>Finally, an important role for the teacher in all this is as a sort of human
tool; he is a consultant on ways and means, rather than an initiator of
activities for students.  I spend my time helping individual students debug
their programs, rather than lecturing to a large group.  I also encourage
students to use one another as consultants and as tutors.

<H2>Initiative: a Political Issue</H2>

<P>Educational freedom means, first of all, that students can make significant
choices from a variety of alternatives.  But if the choices are always made
from a list invented by a teacher, the freedom is of a very limited sort.  The
example of using the computer to typeset the <EM>Promethean</EM> illustrates
a very different sort of choice, in which
students meet <EM>their own
needs</EM> (the newspaper is an extracurricular activity, not a course) using
the computer as a tool.  That's what initiative means.

<P>There is a clear relationship between this notion of initiative and the
availability of a variety of tools.  The more traditional variety of
activities encourages what might be called ``passive freedom''; students are
free to choose, but not free to initiate.  In Paulo Freire's terms, students
are still <EM>objects</EM> of an education provided by their teachers.  But a
variety of tools encourages students to become the <EM>subjects</EM>--the
actors rather than the acted-upon--of their own education.

<P>Any attempt to make initiative a guiding principle in teaching will
confront two psychological barriers: first, it is hard for <EM>adults</EM> to
<EM>permit</EM> student initiative; second, it is hard for <EM>students</EM>
to <EM>accept</EM> the
burden, an unusual one in a high school, if we encourage them to take
initiative.

<P>Many of the experts who write articles or talk at conferences about the use
of computers in education give the impression that simply introducing
computers to the classroom will automatically lead to increased freedom for
learners.  The truth, I think, is that the use of computers can go either
way.  When Ted Nelson says ``The purpose of computers is human freedom,'' he
really means that that is what the purpose <EM>should be</EM>.  In practice, most
computers are better described as dedicated to human slavery!  The computers
at the IRS check up on income tax cheaters; the ones at the bank send you
bills (or your paycheck, which is more pleasant than a bill but a more
important form of economic slavery).  More sophisticated research computers
at the universities are used to study pictures of Vietnamese jungles to help
figure out where to drop the napalm.  Similarly, many
computers in schools are still used exclusively for administrative computing;
students don't get near them.  If students do use the computers, it is often
only for teacher-directed drill and practice, no matter how cleverly disguised
as a game.  Better uses of the technology are possible,
but they aren't inevitable.

<P>Consider an analogy.  Most teachers probably agree, in principle, with the
idea of educational freedom.  Students learn best through intrinsic motivation,
not through force.  What you learn under pressure doesn't last past the exam.
Everyone says these things, and yet almost all teachers continue to give
grades.  Why?  ``It's required''; ``The colleges need grades''; ``The parents
wouldn't stand for it''; ``It's the way things are.''  In short, the reasons
for grades are political.  The same political reasons make educational
freedom through computers a difficult goal.  If students are left to their
own devices to initiate projects, how do we evaluate them?  How do we know
they aren't just wasting time?  Remember, many school computers are funded
through federal grants, and the feds always insist on evaluation of the
program.  That means coopting the computer into the usual school routine
of assignments initiated and evaluated by teachers.

<P>An even more frustrating barrier is that the students themselves are not
accustomed to being without instructions from an adult.  Many students
will find valuable projects on their own, but many more will have to be
weaned away slowly from dependence on explicit assignments.  One of my early
students taught himself four different programming
languages, and learned a great deal about issues of programming
style and structure in his senior year.  He'll probably
learn less about computers in four
years of college.  But he told me every day that I'm a terrible teacher,
because I didn't <EM>make</EM> him learn anything.  I didn't stand in front of
the room and impart information, I didn't send in skip slips if he didn't
show up, and I didn't punish him when he acted obnoxious.  Well, it's not
much fun to hear all this.  It was tempting to say ``OK, if that's what you
want, sit down and shut up!''  But I doubt if the most effective classroom
manager in the world could teach this student as much in a year as he
learned on his own--he would start directing his efforts into a
power struggle.

<P>What does all this mean as a guide to action?  Well, our computer was
installed for a full year before I started working on curriculum materials
or organizing a course structure.  I spent that year collecting and
building tools, and kids spent the year learning on their own, or by
asking questions.  Two years later, we have a computer course in operation
based on self-paced curriculum units, with no grades and with many
different options in the actual course content.  And about 50 kids have
keys to the computer center, and use it evenings and weekends without
adult supervision.  The path from there to here was far from smooth,
but it's been exciting.


<P><ADDRESS>
<A HREF="index.html"><CODE>www.cs.berkeley.edu/~bh</CODE></A>
</ADDRESS>
</BODY>
</HTML>
21:02:31 -0700 stop tracking wallclock time' href='/akkartik/view.love/commit/source_edit.lua?id=0e0f36f8b4a57bd9a13925c9f7e92f7bd8c59a81'>0e0f36f ^
e1c5a42 ^






0e0f36f ^
e1c5a42 ^






ae429cd ^

e1c5a42 ^


e0448d7 ^
e1c5a42 ^
8399c42 ^
637e28f ^
c29be0f ^
e1c5a42 ^



637e28f ^










e1c5a42 ^
528c64d ^

73fefa7 ^







2b3e09c ^
637e28f ^
73fefa7 ^


8c373fd ^



637e28f ^
528c64d ^

69c88da ^
528c64d ^


e0448d7 ^
637e28f ^
528c64d ^
e1c5a42 ^

637e28f ^
bd6f7d4 ^
637e28f ^


9b5a78d ^
e1c5a42 ^

2b3e09c ^
528c64d ^
637e28f ^
8399c42 ^
528c64d ^
2b3e09c ^
528c64d ^



c7c54a0 ^
73fefa7 ^
637e28f ^
bd6f7d4 ^
91a08ee ^
bd6f7d4 ^



73fefa7 ^


637e28f ^
8c373fd ^



637e28f ^
bd6f7d4 ^

73fefa7 ^


bd6f7d4 ^

9b5a78d ^
bd6f7d4 ^
637e28f ^
528c64d ^
e1c5a42 ^

bd6f7d4 ^













99faf61 ^

91a08ee ^
bafc45b ^
99faf61 ^


a6dcfc5 ^
f2299cb ^
bafc45b ^
99faf61 ^





2b3e09c ^
a3e5a1f ^
e1c5a42 ^

e1c5a42 ^
a3e5a1f ^
528c64d ^




e1c5a42 ^
1d27d59 ^

2b3e09c ^
1d27d59 ^
e1c5a42 ^



2b3e09c ^
73fefa7 ^




19615ea ^
f98cdd1 ^
73fefa7 ^
e1c5a42 ^


e1c5a42 ^


5a74b69 ^
e1c5a42 ^

e1c5a42 ^




1609d79 ^


e1c5a42 ^
1693f1f ^
67e3cbe ^
1693f1f ^

e1c5a42 ^
6975b8b ^
e1c5a42 ^




8c373fd ^

e1c5a42 ^
e1c5a42 ^




81ebc6a ^



e1c5a42 ^




e1c5a42 ^




73fefa7 ^
e1c5a42 ^
528c64d ^

5a74b69 ^
e1c5a42 ^


e1c5a42 ^




73fefa7 ^
e1c5a42 ^
528c64d ^

5a74b69 ^
e1c5a42 ^


44aa822 ^

8c373fd ^
e1c5a42 ^


7e97a2a ^
e1c5a42 ^

f98cdd1 ^
e1c5a42 ^
7e97a2a ^
e1c5a42 ^


e1c5a42 ^



7e97a2a ^
e1c5a42 ^










e1c5a42 ^
c7c54a0 ^
528c64d ^

528c64d ^


2b3e09c ^
528c64d ^








a3e5a1f ^
528c64d ^











a3e5a1f ^





528c64d ^


e1c5a42 ^
2b3e09c ^
e1c5a42 ^


2b3e09c ^
e1c5a42 ^



0f4aea6 ^
e1c5a42 ^
e1c5a42 ^



06c784b ^

e1c5a42 ^
06c784b ^
e1c5a42 ^





06c784b ^
0f4aea6 ^
bd2179d ^
e1c5a42 ^


2b3e09c ^
e1c5a42 ^
2b3e09c ^



e1c5a42 ^
35f81e5 ^
e1c5a42 ^


2b3e09c ^
1dbd734 ^


e1c5a42 ^
35f81e5 ^
e1c5a42 ^




e0448d7 ^
9b5a78d ^
e1c5a42 ^
2b3e09c ^
e1c5a42 ^
35f81e5 ^
e1c5a42 ^




e0448d7 ^
e1c5a42 ^
35f81e5 ^
e1c5a42 ^




2b3e09c ^
e1c5a42 ^
35f81e5 ^
e1c5a42 ^

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614


                                            
                                                


                                                                                      

                                                                    
                                         


                                                                             




                 





                                                                                      


                                   
                                                                                                                               
                  

                                         

                        

                                 












                                                                                                                             
                                                        





                                                                                                              
                                                                                                                 




                                                                                                                            





                                                                                 

                                                   
                                                                                        
                                                                             
 




                                                                            
 



                                   


                                                                                                            
                
                              
                              

              
                                                                                          
                              

                       
                         







                      


                                                                    
                             
 



                                                                        
                                    


                                                            
                                   
                                            



                                   



                                                                                                

   









                                                                                                




                                                        
                                                 





                                            

           


     
                                                         
                            
                                   
                       

                                                                                                                                                                                                                   





                                                         
                                           
                                                               
                               

                                           
                                                  
                                        
         

                                       



                                                                            
                                                                             







                                                                                                                              
                                                                                                                           
                                           
                                           


                         
                                                                                   





                                                                          
                                                               
       






                               
                           
                                                            






                                
                                                                                                         






                                      

                                                            


     
                                                   
                                      
                                 
                                                                                                        
                                                                      



                                                                










                                             
                                               

                                                  







                                                                                           
                                                                 
                                                                              


                                                 



                                                             
              

                                      
                                                                                 


                                                      
                                                                 
              
         

       
 
                                                      


                                           
                                                         

   
                                                     
                                      
                                                                                                          
                        
                                     
                                                   



                                                                                                                 
                        
      
                                                               
                         
                                                 



                                      


                                                    
                                                                                  



                                                               
                                                                                                                   

                                          


           

                                                          
                                                        
                                    
                                                                                                                                                                                                  
     

   













                                                                       

                                            
                                               
                                            


                             
                    
                                              
                                            





                              
                                  
                            

                                            
                           
                                                                                  




                                                                                                       
      

                                                                 
                               
       



                      
                                               




                                                                                                     
                                                                                                                                                                                        
                                                      
     


                             


                                                        
                                                                                                  

                                 




                                                                         


                                                                 
                               
                                    
                         

                               
                             
                                 




                            

                                                                          
     




                                                         



                                                           




                                        




                                                  
                                                
                                                   

                                                
                                                                                                  


                            




                                                  
                                                
                                                   

                                                
                                                                                                  


                          

                                      
                                                                                       


                                   
                          

                            
                                                             
             
                          


                            



                                                                             
                                              










                                                                          
                                                                                                     
                        

                                                         


                                                                 
                                          








                                                                                     
                                                                                  











                                                                                                           





                                                                                                             


                        
      
                                     


     
                                               



                                                      
                                                       
                                                 



                           

                                                                          
                     
                     





                                                         
                                           
                              
         


                         
                                            
                                       



                                            
                          
                       


                  
                              


                                                   
                          
                       




                                                             
                                            
                  
                                           
                                              
                          
                       




                                                             
                                            
                          
                       




                                                               
                                              
                          
                       

                  
-- some constants people might like to tweak
Text_color = {r=0, g=0, b=0}
Cursor_color = {r=1, g=0, b=0}
Hyperlink_decoration_color = {r=0.4, g=0.4, b=1}
Stroke_color = {r=0, g=0, b=0}
Current_stroke_color = {r=0.7, g=0.7, b=0.7}  -- in process of being drawn
Current_name_background_color = {r=1, g=0, b=0, a=0.1}  -- name currently being edited
Focus_stroke_color = {r=1, g=0, b=0}  -- what mouse is hovering over
Highlight_color = {r=0.7, g=0.7, b=0.9}  -- selected text
Line_number_color = {r=0.6, g=0.6, b=0.6}
Icon_color = {r=0.7, g=0.7, b=0.7}  -- color of current mode icon in drawings
Help_color = {r=0, g=0.5, b=0}
Help_background_color = {r=0, g=0.5, b=0, a=0.1}

Margin_top = 15
Margin_left = 25
Margin_right = 25

Drawing_padding_top = 10
Drawing_padding_bottom = 10
Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottom

Same_point_distance = 4  -- pixel distance at which two points are considered the same

edit = {}

-- run in both tests and a real run
function edit.initialize_state(top, left, right, font, font_height, line_height)  -- currently always draws to bottom of screen
  local result = {
    -- a line is either text or a drawing
    -- a text is a table with:
    --    mode = 'text',
    --    string data,
    -- a drawing is a table with:
    --    mode = 'drawing'
    --    a (h)eight,
    --    an array of points, and
    --    an array of shapes
    -- a shape is a table containing:
    --    a mode
    --    an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)
    --    an array vertices for mode 'polygon', 'rectangle', 'square'
    --    p1, p2 for mode 'line'
    --    center, radius for mode 'circle'
    --    center, radius, start_angle, end_angle for mode 'arc'
    -- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide
    -- The field names are carefully chosen so that switching modes in midstream
    -- remembers previously entered points where that makes sense.
    lines = {{mode='text', data=''}},  -- array of lines

    -- Lines can be too long to fit on screen, in which case they _wrap_ into
    -- multiple _screen lines_.

    -- rendering wrapped text lines needs some additional short-lived data per line:
    --   startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen
    --   screen_line_starting_pos: optional array of codepoint indices if it wraps over more than one screen line
    line_cache = {},

    -- Given wrapping, any potential location for the text cursor can be described in two ways:
    -- * schema 1: As a combination of line index and position within a line (in utf8 codepoint units)
    -- * schema 2: As a combination of line index, screen line index within the line, and a position within the screen line.
    --
    -- Most of the time we'll only persist positions in schema 1, translating to
    -- schema 2 when that's convenient.
    --
    -- Make sure these coordinates are never aliased, so that changing one causes
    -- action at a distance.
    --
    -- On lines that are drawings, pos will be nil.
    screen_top1 = {line=1, pos=1},  -- position of start of screen line at top of screen
    cursor1 = {line=1, pos=1},  -- position of cursor; must be on a text line

    selection1 = {},
    -- some extra state to compute selection between mouse press and release
    old_cursor1 = nil,
    old_selection1 = nil,
    mousepress_shift = nil,

    -- cursor coordinates in pixels
    cursor_x = 0,
    cursor_y = 0,

    current_drawing_mode = 'line',
    previous_drawing_mode = nil,  -- extra state for some ephemeral modes like moving/deleting/naming points

    font = font,
    font_height = font_height,
    line_height = line_height,

    top = top,
    left = math.floor(left),  -- left margin for text; line numbers go to the left of this
    right = math.floor(right),
    width = right-left,

    filename = 'run.lua',
    next_save = nil,

    -- undo
    history = {},
    next_history = 1,

    -- search
    search_term = nil,
    search_backup = nil,  -- stuff to restore when cancelling search
  }
  return result
end  -- edit.initialize_state

function edit.check_locs(State)
  -- if State is inconsistent (i.e. file changed by some other program),
  --   throw away all cursor state entirely
  if edit.invalid1(State, State.screen_top1)
      or edit.invalid_cursor1(State)
      or not edit.cursor_on_text(State)
      or not Text.le1(State.screen_top1, State.cursor1) then
    State.screen_top1 = {line=1, pos=1}
    State.cursor1 = {line=1, pos=1}
    edit.put_cursor_on_next_text_line(State)
  end
end

function edit.invalid1(State, loc1)
  if loc1.line > #State.lines then return true end
  local l = State.lines[loc1.line]
  if l.mode ~= 'text' then return false end  -- pos is irrelevant to validity for a drawing line
  return loc1.pos > #State.lines[loc1.line].data
end

-- cursor loc in particular differs from other locs in one way:
-- pos might occur just after end of line
function edit.invalid_cursor1(State)
  local cursor1 = State.cursor1
  if cursor1.line > #State.lines then return true end
  local l = State.lines[cursor1.line]
  if l.mode ~= 'text' then return false end  -- pos is irrelevant to validity for a drawing line
  return cursor1.pos > #State.lines[cursor1.line].data + 1
end

function edit.cursor_on_text(State)
  return State.cursor1.line <= #State.lines
      and State.lines[State.cursor1.line].mode == 'text'
end

function edit.put_cursor_on_next_text_line(State)
  local line = State.cursor1.line
  while line < #State.lines do
    line = line+1
    if State.lines[line].mode == 'text' then
      State.cursor1.line = line
      State.cursor1.pos = 1
      break
    end
  end
end

function edit.draw(State, hide_cursor, show_line_numbers)
  State.button_handlers = {}
  love.graphics.setFont(State.font)
  App.color(Text_color)
  assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines))
  assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos))
  State.cursor_x = nil
  State.cursor_y = nil
  local y = State.top
--?   print('== draw')
  for line_index = State.screen_top1.line,#State.lines do
    local line = State.lines[line_index]
--?     print('draw:', y, line_index, line)
    if y + State.line_height > App.screen.height then break end
    if line.mode == 'text' then
--?       print('text.draw', y, line_index)
      local startpos = 1
      if line_index == State.screen_top1.line then
        startpos = State.screen_top1.pos
      end
      if line.data == '' then
        -- button to insert new drawing
        local buttonx = State.left-Margin_left+4
        if show_line_numbers then
          buttonx = 4  -- HACK: position draw buttons at a fixed x on screen
        end
        button(State, 'draw', {x=buttonx, y=y+4, w=12,h=12, bg={r=1,g=1,b=0},
          icon = icon.insert_drawing,
          onpress1 = function()
                       Drawing.before = snapshot(State, line_index-1, line_index)
                       table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})
                       table.insert(State.line_cache, line_index, {})
                       if State.cursor1.line >= line_index then
                         State.cursor1.line = State.cursor1.line+1
                       end
                       record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})
                       Drawing.before = nil
                       schedule_save(State)
                     end,
        })
      end
      y = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
--?       print('=> y', y)
    elseif line.mode == 'drawing' then
      y = y+Drawing_padding_top
      Drawing.draw(State, line_index, y)
      y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
    else
      assert(false, ('unknown line mode %s'):format(line.mode))
    end
  end
  if State.search_term then
    Text.draw_search_bar(State)
  end
end

function edit.update(State, dt)
  Drawing.update(State, dt)
  if State.next_save and State.next_save < Current_time then
    save_to_disk(State)
    State.next_save = nil
  end
end

function schedule_save(State)
  if State.next_save == nil then
    State.next_save = Current_time + 3  -- short enough that you're likely to still remember what you did
  end
end

function edit.quit(State)
  -- make sure to save before quitting
  if State.next_save then
    save_to_disk(State)
    -- give some time for the OS to flush everything to disk
    love.timer.sleep(0.1)
  end
end

function edit.mouse_press(State, x,y, mouse_button)
  if State.search_term then return end
  State.mouse_down = mouse_button
--?   print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))
  if mouse_press_consumed_by_any_button(State, x,y, mouse_button) then
    -- press on a button and it returned 'true' to short-circuit
    return
  end

  if y < State.top then
    State.old_cursor1 = State.cursor1
    State.old_selection1 = State.selection1
    State.mousepress_shift = App.shift_down()
    State.selection1 = {
        line=State.screen_top1.line,
        pos=State.screen_top1.pos,
    }
    return
  end

  for line_index,line in ipairs(State.lines) do
    if line.mode == 'text' then
      if Text.in_line(State, line_index, x,y) then
        -- delicate dance between cursor, selection and old cursor/selection
        -- scenarios:
        --  regular press+release: sets cursor, clears selection
        --  shift press+release:
        --    sets selection to old cursor if not set otherwise leaves it untouched
        --    sets cursor
        --  press and hold to start a selection: sets selection on press, cursor on release
        --  press and hold, then press shift: ignore shift
        --    i.e. mouse_release should never look at shift state
--?         print_and_log(('edit.mouse_press: in line %d'):format(line_index))
        State.old_cursor1 = State.cursor1
        State.old_selection1 = State.selection1
        State.mousepress_shift = App.shift_down()
        State.selection1 = {
            line=line_index,
            pos=Text.to_pos_on_line(State, line_index, x, y),
        }
        return
      end
    elseif line.mode == 'drawing' then
      if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then
        State.lines.current_drawing_index = line_index
        State.lines.current_drawing = line
        Drawing.before = snapshot(State, line_index)
        Drawing.mouse_press(State, line_index, x,y, mouse_button)
        return
      end
    end
  end

  -- still here? mouse press is below all screen lines
  State.old_cursor1 = State.cursor1
  State.old_selection1 = State.selection1
  State.mousepress_shift = App.shift_down()
  State.selection1 = Text.final_text_loc_on_screen(State)
end

function edit.mouse_release(State, x,y, mouse_button)
  if State.search_term then return end
--?   print_and_log(('edit.mouse_release: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))
  State.mouse_down = nil
  if State.lines.current_drawing then
    Drawing.mouse_release(State, x,y, mouse_button)
    if Drawing.before then
      record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
      Drawing.before = nil
    end
    schedule_save(State)
  else
--?     print_and_log('edit.mouse_release: no current drawing')
    if y < State.top then
      State.cursor1 = deepcopy(State.screen_top1)
      edit.clean_up_mouse_press(State)
      return
    end

    for line_index,line in ipairs(State.lines) do
      if line.mode == 'text' then
        if Text.in_line(State, line_index, x,y) then
--?           print_and_log(('edit.mouse_release: in line %d'):format(line_index))
          State.cursor1 = {
              line=line_index,
              pos=Text.to_pos_on_line(State, line_index, x, y),
          }
--?           print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos))
          edit.clean_up_mouse_press(State)
          return
        end
      end
    end

    -- still here? mouse release is below all screen lines
    State.cursor1 = Text.final_text_loc_on_screen(State)
    edit.clean_up_mouse_press(State)
--?     print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))
  end
end

function edit.clean_up_mouse_press(State)
  if State.mousepress_shift then
    if State.old_selection1.line == nil then
      State.selection1 = State.old_cursor1
    else
      State.selection1 = State.old_selection1
    end
  end
  State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
  if eq(State.cursor1, State.selection1) then
    State.selection1 = {}
  end
end

function edit.mouse_wheel_move(State, dx,dy)
  if dy > 0 then
    State.cursor1 = deepcopy(State.screen_top1)
    edit.put_cursor_on_next_text_line(State)
    for i=1,math.floor(dy) do
      Text.up(State)
    end
  elseif dy < 0 then
    State.cursor1 = Text.screen_bottom1(State)
    edit.put_cursor_on_next_text_line(State)
    for i=1,math.floor(-dy) do
      Text.down(State)
    end
  end
end

function edit.text_input(State, t)
--?   print('text input', t)
  if State.search_term then
    State.search_term = State.search_term..t
    Text.search_next(State)
  elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then
    local before = snapshot(State, State.lines.current_drawing_index)
    local drawing = State.lines.current_drawing
    local p = drawing.points[drawing.pending.target_point]
    p.name = p.name..t
    record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
  else
    local drawing_index, drawing = Drawing.current_drawing(State)
    if drawing_index == nil then
      Text.text_input(State, t)
    end
  end
  schedule_save(State)
end

function edit.keychord_press(State, chord, key)
  if State.selection1.line and
      not State.lines.current_drawing and
      -- printable character created using shift key => delete selection
      -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
      (not App.shift_down() or utf8.len(key) == 1) and
      chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(key) then
    Text.delete_selection_and_record_undo_event(State)
  end
  if State.search_term then
    if chord == 'escape' then
      State.search_term = nil
      State.cursor1 = State.search_backup.cursor
      State.screen_top1 = State.search_backup.screen_top
      State.search_backup = nil
      Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
    elseif chord == 'return' then
      State.search_term = nil
      State.search_backup = nil
    elseif chord == 'backspace' then
      local len = utf8.len(State.search_term)
      local byte_offset = Text.offset(State.search_term, len)
      State.search_term = string.sub(State.search_term, 1, byte_offset-1)
      State.cursor = deepcopy(State.search_backup.cursor)
      State.screen_top = deepcopy(State.search_backup.screen_top)
      Text.search_next(State)
    elseif chord == 'down' then
      if #State.search_term > 0 then
        Text.right(State)
        Text.search_next(State)
      end
    elseif chord == 'up' then
      Text.search_previous(State)
    end
    return
  elseif chord == 'C-f' then
    State.search_term = ''
    State.search_backup = {
      cursor={line=State.cursor1.line, pos=State.cursor1.pos},
      screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},
    }
  -- zoom
  elseif chord == 'C-=' then
    edit.update_font_settings(State, State.font_height+2)
    Text.redraw_all(State)
  elseif chord == 'C--' then
    if State.font_height > 2 then
      edit.update_font_settings(State, State.font_height-2)
      Text.redraw_all(State)
    end
  elseif chord == 'C-0' then
    edit.update_font_settings(State, 20)
    Text.redraw_all(State)
  -- undo
  elseif chord == 'C-z' then
    local event = undo_event(State)
    if event then
      local src = event.before
      State.screen_top1 = deepcopy(src.screen_top)
      State.cursor1 = deepcopy(src.cursor)
      State.selection1 = deepcopy(src.selection)
      patch(State.lines, event.after, event.before)
      -- invalidate various cached bits of lines
      State.lines.current_drawing = nil
      Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
      schedule_save(State)
    end
  elseif chord == 'C-y' then
    local event = redo_event(State)
    if event then
      local src = event.after
      State.screen_top1 = deepcopy(src.screen_top)
      State.cursor1 = deepcopy(src.cursor)
      State.selection1 = deepcopy(src.selection)
      patch(State.lines, event.before, event.after)
      -- invalidate various cached bits of lines
      State.lines.current_drawing = nil
      Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
      schedule_save(State)
    end
  -- clipboard
  elseif chord == 'C-a' then
    State.selection1 = {line=1, pos=1}
    State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}
  elseif chord == 'C-c' then
    local s = Text.selection(State)
    if s then
      App.set_clipboard(s)
    end
  elseif chord == 'C-x' then
    local s = Text.cut_selection_and_record_undo_event(State)
    if s then
      App.set_clipboard(s)
    end
    schedule_save(State)
  elseif chord == 'C-v' then
    -- We don't have a good sense of when to scroll, so we'll be conservative
    -- and sometimes scroll when we didn't quite need to.
    local before_line = State.cursor1.line
    local before = snapshot(State, before_line)
    local clipboard_data = App.get_clipboard()
    for _,code in utf8.codes(clipboard_data) do
      local c = utf8.char(code)
      if c == '\n' then
        Text.insert_return(State)
      else
        Text.insert_at_cursor(State, c)
      end
    end
    if Text.cursor_out_of_screen(State) then
      Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
    end
    record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
    schedule_save(State)
  -- dispatch to drawing or text
  elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
    local drawing_index, drawing = Drawing.current_drawing(State)
    if drawing_index then
      local before = snapshot(State, drawing_index)
      Drawing.keychord_press(State, chord)
      record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})
      schedule_save(State)
    end
  elseif chord == 'escape' and not App.mouse_down(1) then
    for _,line in ipairs(State.lines) do
      if line.mode == 'drawing' then
        line.show_help = false
      end
    end
  elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then
    if chord == 'return' then
      State.current_drawing_mode = State.previous_drawing_mode
      State.previous_drawing_mode = nil
    else
      local before = snapshot(State, State.lines.current_drawing_index)
      local drawing = State.lines.current_drawing
      local p = drawing.points[drawing.pending.target_point]
      if chord == 'escape' then
        p.name = nil
        record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
      elseif chord == 'backspace' then
        local len = utf8.len(p.name)
        if len > 0 then
          local byte_offset = Text.offset(p.name, len-1)
          if len == 1 then byte_offset = 0 end
          p.name = string.sub(p.name, 1, byte_offset)
          record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
        end
      end
    end
    schedule_save(State)
  else
    Text.keychord_press(State, chord)
  end
end

function edit.key_release(State, key, scancode)
end

function edit.update_font_settings(State, font_height)
  State.font_height = font_height
  State.font = love.graphics.newFont(State.font_height)
  State.line_height = math.floor(font_height*1.3)
end

--== some methods for tests

-- Insulate tests from some key globals so I don't have to change the vast
-- majority of tests when they're modified for the real app.
Test_margin_left = 25
Test_margin_right = 0

function edit.initialize_test_state()
  -- if you change these values, tests will start failing
  return edit.initialize_state(
      15,  -- top margin
      Test_margin_left,
      App.screen.width - Test_margin_right,
      love.graphics.getFont(),
      14,
      15)  -- line height
end

-- all text_input events are also keypresses
-- TODO: handle chords of multiple keys
function edit.run_after_text_input(State, t)
  edit.keychord_press(State, t)
  edit.text_input(State, t)
  edit.key_release(State, t)
  App.screen.contents = {}
  edit.update(State, 0)
  edit.draw(State)
end

-- not all keys are text_input
function edit.run_after_keychord(State, chord, key)
  edit.keychord_press(State, chord, key)
  edit.key_release(State, key)
  App.screen.contents = {}
  edit.update(State, 0)
  edit.draw(State)
end

function edit.run_after_mouse_click(State, x,y, mouse_button)
  App.fake_mouse_press(x,y, mouse_button)
  edit.mouse_press(State, x,y, mouse_button)
  edit.draw(State)
  App.fake_mouse_release(x,y, mouse_button)
  edit.mouse_release(State, x,y, mouse_button)
  App.screen.contents = {}
  edit.update(State, 0)
  edit.draw(State)
end

function edit.run_after_mouse_press(State, x,y, mouse_button)
  App.fake_mouse_press(x,y, mouse_button)
  edit.mouse_press(State, x,y, mouse_button)
  App.screen.contents = {}
  edit.update(State, 0)
  edit.draw(State)
end

function edit.run_after_mouse_release(State, x,y, mouse_button)
  App.fake_mouse_release(x,y, mouse_button)
  edit.mouse_release(State, x,y, mouse_button)
  App.screen.contents = {}
  edit.update(State, 0)
  edit.draw(State)
end