Smalltalk80LanguageImplementationKor:Chapter 19

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 19 장 펜

Object

    Magnitude
        Character
        Date
        Time

        Number
            Float
            Fraction
            Integer
                LargeNegativeInteger
                LargePositiveInteger
                SmallInteger

        LookupKey
            Association

    Link

        Process

    Collection

        SequenceableCollection
            LinkedList

                Semaphore

            ArrayedCollection
                Array

                Bitmap
                    DisplayBitmap

                RunArray
                String
                    Symbol
                Text
                ByteArray

            Interval
            OrderedCollection
                SortedCollection
        Bag
        MappedCollection
        Set
            Dictionary
                IdentifyDictionary

    Stream
        PositionableStream
            ReadStream
            WriteStream
                ReadWriteStream
                    ExternalStream
                        FileStream

        Random

    File
    FileDirectory
    FilePage

    UndefinedObject
    Boolean
        False
        True

    ProcessorScheduler
    Delay
    SharedQueue

    Behavior
        ClassDescription
            Class
            MetaClass

    Point
    Rectangle
    BitBit
        CharacterScanner

        Pen***

    DisplayObject
        DisplayMedium
            Form
                Cursor
                DisplayScreen
        InfiniteForm
        OpaqueForm
        Path
            Arc
                Circle
            Curve
            Line
            LinearFit
            Spline


앞장에서 설명했듯이 Forms는 이미지를 나타낸다. 두 지정된 점 사이 거리가 점차적으로 증가하는 Form 내의 여러 위치로 또 다른 Frm을 복사하면 직선이 생성된다. Pen 클래스의 인스턴스는 직선 그리기에 대한 고수준 접근을 제공한다.


Pen은 BitBIt의 서브클래스다. 이는 소스 및 목적지 Forms에 대한 홀더(holder)이다. Form 소스는 검정색이나 흰색, 또는 회색의 여러 톤으로 칠할 수 있으며, 조합 규칙, 하프톤 마스크가 서로 다르고, 클리핑 직사각형 또한 다른 방식으로 목적지 Form으로 복사된다. 소스 Form은 Pen의 쓰기 툴 또는 펜촉(nib)이다. 목적지 Form은 Pen의 쓰기 표면으로, 주로 디스플레이 화면을 나타내는 Form이다.


BitBIt에서 상속되는 구현 외에도 Pen은 디스플레이 화면에서의 위치를 나타내는 Point와 Pen이 이동하는 방향을 나타내는 Number를 갖고 있다. Pen은 그것이 위치 또는 방향을 변경하도록 만드는 메시지를 이해한다. 그 위치가 변경되면 Pen은 그 Form의 복사본을 이전 위치에 남겨둘 수 있다. Pen을 다른 화면 위치로 이동시키고 그 Form을 이러한 위치들 중 하나 이상의 장소에 복사하면 그래픽 디자인이 생성된다.


여러 프로그래밍 시스템이 직선 그리기에 대해 이러한 유형의 접근을 제공한다. 이러한 시스템에서 직선 드로어(line drawer)는 주로 MIT/BBN Logo 언어를 따라 "turtle"(터틀)이라고 부른다 (Seymour Papert, MindStorms: Children, Computers and Powerful Ideas, Basic Books, 1980; Harold Abelson and Andrea diSessa, Turtle Geometry: The Computer as a Medium for Exploring Mathematics, MIT Press, 1981). Pens에 대한 프로토콜은 Logo에서 제공되는 터틀 명령과 같은 메시지를 지원한다. 이는 터틀에게 얼만큼 거리는 이동하는지, 얼만큼 회전하는지, 펜을 아래 방향으로 아니면 윗방향으로 위치시키는지를 알려주는 명령으로 구성된다. 펜이 아래를 향하고 이동하면 터틀의 경로 추적이 생성된다. 그에 해당하는 Pen 메시지로는 go: distance, turn: amount, down, up이 있다.


다수의 Pens를 생성하고 화면에서 그 이동을 조정함으로써 그래픽한 디자인의 생성 과정 자체를 그래픽적으로 흥미롭게 만들 수도 있다. 다음 절에는 Pen 클래스에 제공되는 프로토콜을 포함한다. 이후로는 Pens로 메시지를 전송하여 생성되는 디자인의 예제를 제공하겠다.


Pen 클래스

Pen 클래스의 인스턴스는 Pen으로 new라는 메시지를 전송하면 생성된다. 이렇게 생성된 Pen은 디스플레이 화면 어디든 그릴 수 있고, 처음 위치는 화면의 중앙이며 화면 위를 향한다. Pen은 소스 Form 또는 1x1 검정색 점으로 된 펜촉으로 그려지도록 설정된다 (예: 아랫방향).


Pen의 소스 Form을 변경하는 방법에는 두 가지가 있다. 첫 번째는 Pen으로 defaultNib: widthInteger 메시지를 전송하는 방법이다. 다른 하나는 슈퍼클래스 BitBIt로부터 상속받는 메시지를 Pen에게 전송함으로써 소스 Form을 리셋하는 방법이다. 가령, sourceForm: 메시지는 소스 폼을 변경하고, mask: 메시지는 소스 폼을 표시하는 데 사용되는 하프톤 폼을 (마스크) 변경한다. (디스플레이에 기본 마스크는 검정색임을 주목한다.)

초기화-해제(initialize-release)
defaultNib: shape "펜촉" 은 펜의 끝을 의미한다. 이것은 기본 펜을 쉽게 설정하는 방법이다. 수신자에 대한 Form은 높이와 너비가 (1) 인자 shape와 동일하거나 (shape가 Integer일 경우), (2) shape의 좌표와 동일한 (shape가 Point일 경우) 직사각형 모양이다.
Pen 인스턴스 프로토콜


따라서 아래는

bic  Pen new defaultNib: 2


너비와 높이가 2 bits x2 bits인 검정색 Form의 Pen이 생성된다.


Pen에 대한 접근 프로토콜은 Pen의 현재 방향, 위치, 그림 영역으로 접근성을 제공한다. 그림 영역은 Pen의 프레임(frame)이라고 부른다.

접근하기(accessing)
direction 수신자의 현재 방향을 응답한다. 270은 화면의 최상단을 향한다.
location 수신자의 현재 위치를 응답한다.
frame 수신자가 그릴 수 있는 Rectangle을 응답한다.
frame: aRectangle 수신자가 그릴 수 있는 Rectangle을 인자 aRectangle이 되도록 설정한다.
Pen 인스턴스 프로토콜


bic 예제를 계속 사용하여 디스플레이 화면이 600 bits 너비에 800 bits 높이라고 가정하면 다음을 예로 들 수 있다.

표현식 결과
bic direction 270
bic location 300@400
bic frame:(50@50 extent: 200@200)
bic location 300@400


Pen 방향이 디스플레이 화면의 최상단을 향하면 각도는 270도가 된다는 점을 주목하자. 또 Pen은 현재 그림 영역의 외부에 있고, 어떤 마크도 눈에 띄기 전에 50@50 corner: 250@250의 Rectangle내에 위치할 것이라는 점도 주목한다.


"Turtle" 그리기 명령은 Pen의 그리기 상태를 변경하고, 그리기 방향을 바꾸며, 위치를 옮긴다.

이동하기(moving)
down 수신자 상태를 "하향"으로 설정하여 수신자가 이동하면 마크를 벗어나도록 한다.
up 수신자 상태를 "상향"으로 설정하여 수신자가 이동하면 마크를 벗어나도록 한다.
turn: degrees 수신자가 인자 degrees와 동일한 양만큼 접하도록 수신자의 방향을 변경한다.
north 수신자가 디스플레이 화면의 상단을 향해 접하도록 수신자의 방향을 설정한다.
go: distance 현재 방향에 있는 수신자를 인자 distance와 동일한 비트 수만큼 이동시킨다. 수신자 상태가 "하향"일 경우 수신자의 Form을 그리기 브러시 모양으로 사용하여 직선이 그려질 것이다.
goto: aPoint 수신자를 aPoint 위치로 이동시킨다. 수신자 상태가 "하향"일 경우, 수신자의 Form을 그리기 브러시 모양으로 이용하여 현재 위치에서 새로운 위치까지 직선이 그려질 것이다. 수신자의 방향은 변경되지 않는다.
place: aPoint 수신자를 위치 aPoint에 설정한다. 어떤 직선도 그려지지 않는다.
home 그릴 수 있는 영역 중앙에 수신자를 위치시킨다.
Pen 인스턴스 프로토콜


따라서 아래를 평가하면 bic을 프레임 중앙에 위치시킬 수 있다.

bic home


이후 아래를 질문하면

bic location


응답은 150 @150이 될 것이다.


Pen으로 직선을 그린 다음에 지우기로 결정했다고 가정하자. 직선이 검정색 Form으로 그려진 경우 적어도 동일한 크기로 된 흰색 Form으로 그 위에 다시 직선을 그리면 지워진다. 따라서 아래를 이용하면 검정색 직선이 그려진다.

bic go: 100


그리고 아래를 이용하면

bic white


드로잉 마스크를 모두 흰색으로 설정하고 (white 메시지는 BitBIt의 프로토콜에서 상속된다), 이후 아래를 사용하면

bic go: -100


원래 그렸던 직선 위에 새로운 직선이 그려져 기존 직선이 지워진다.


사각형 등 다양한 다각형을 생성하는 것은 Logo 예제에서 공통적으로 제시되는 연습이다.

4 timesRepeat: [bic go: 100. bic turn: 90]


아래 표현식은 면 개수의 함수로 회전 각도를 계산하여 어떤 다각형이든 생성한다. nSides가 원하는 다각형의 면 개수일 경우, 다음을 이용하면

nSides timesRepeat: [bic go: 100. bic turn: 360 // nSides]


다각형이 그려질 것이다. 면의 개수와 각 면의 길이를 참조하는 인스턴스를 가진 Polygon 클래스도 생성할 수 있다. 또 각 Polygon은 그림을 그리기 위한 고유의 Pen을 갖고 있다. 다음 정의에서 우리는 Polygon에게 디스플레이 화면에 draw를 그릴 것을 알리도록 명시할 것인데, 메서드는 앞서 설명한 것과 동일하다.

클래스명 Polygon
슈퍼클래스 Object
인스턴스 변수명 drawingPen
nSides
length
클래스 메서드
instance creation
    new
        super new default
인스턴스 메서드
drawing
    draw
        drawingPen black.
        nSides timesRepeat: [drawingPen go: length; turn: 360 // nSides]
accessing
    length: n
        length  n
    sides: n
        nSides  n
private
    default
        drawingPen  Pen new.
        self length: 100.
        self sides: 4


이후 Polygon을 생성할 수 있으며, 아래 표현식을 평가하여 다각형의 시퀀스가 그려진다.

poly  Polygon new.
3 to: 10 do: [ :sides | poly sides: sides. poly draw]


결과는 그림 19.1에 표시되어 있다.

그림 19-1


기하학적 디자인(Geometric Designs)

앞서 언급한 Logo 서적에서는 컴퓨터 디스플레이 화면에 이미지를 생성하기 위해 직선 그리기로 이러한 접근을 사용하는 방법과 관련해 광범위한 예를 제공한다. 우리는 어떤 Pen이든 그림 19.2-19.5에 표시된 것과 같은 기하학적 디자인을 그릴 수 있도록 Pen에 추가 가능한 메서드 예제를 몇 가지 제공하고자 한다. (주의: 이러한 메서드는 사용자가 기하학적 디자인을 생성하면서 갖고 놀 수 있도록 Pen의 설명 일부로 시스템에 위치한다.)

나선형(Spirals)

첫 번째 디자인은 spiral이라 부른다. 나선형은 Pen이 점점 길어지는 직선을 그리도록 만들어 생성되는데, 각 선이 그려지고 나면 Pen이 어느 정도 방향을 튼다. 시작 길이가 1로 그려진 직선이 spiral:angle: 메시지의 첫 번째 인자와 동일한 길이에 도달할 때까지 1씩 길이가 증가한다. 메시지의 두 번째 인자는 각 직선을 그리고 난 후 Pen이 회전하는 양이다.

spiral: n angle: a
    1 to: n do:
        [ :i | self go: i. self turn: a]


그림 19.2의 각 직선은 다음과 같이 spiral:angle: 메시지를 bic에 전송하여 그려진 것이다.

bic spiral: 150 angle: 89

그림 19-2a


bic spiral: 150 angle: 91

그림 19-2b


bic spiral: 150 angle: 121

그림 19-2c


bic home.
bic spiral: 150 angle: 89.
bic home.
bic spiral: 150 angle:91

그림 19-2d


드래곤 곡선(Dragon Curve)

그림 19.3은 다음의 표현식을 평가하여 화면 중앙에 그려진 order 8의 "드래곤 곡선" 이미지다.

bic  Pen new defaultNib: 4.
bic dragon: 9


Pen 클래스에서 dragon: 메시지와 연관된 메서드는 다음과 같다.

dragon: n
    n = 0
        ifTrue: [self go: 10]
        ifFalse:
            [n > 0
                ifTrue:
                    [self dragon: n - 1.
                        self turn: 90.
                        self dragon: 1 - n]
                ifFalse:
                    [self dragon: -1 - n.
                        self turn: -90.
                        self dragon: 1 + n]]

그림 19-3


드래곤 곡선은 Martin Gardner가 Scientific American에 기재한 수학 게임 컬럼에서 논한 바 있다 (1967년 3월, p. 124, 1967년 4월, p. 119). 또 Donald Knuth와 Chandler Davis, "Number Representations and Dragon Curves," Journal of Recreation Mathematics, Vol. 3, 1970, pp. 66-81와 133-149에서도 드래곤 곡선에 대한 논의가 이루어졌다.


힐버트 곡선(Hilbert Curve)

그림 19.4는 수학자 David Hilbert의 공이 큰 공간 채우기 곡선이다. 공간 채우기 곡선은 색인을 가지는데, 색인이 무한대로 증가하면서 곡선은 평면의 점을 덮는 경향을 보인다. 아래 표현식을 평가한 결과를 예로 들 수 있겠다.

Pen new hilbert: 5 side: 8


예제에서 색인은 5이며, 각 점에서 8 픽셀 길이의 직선이 그려진다. hilbert:side 메시지에 해당하는 메서드는 다음과 같다.

hilbert: n side: s
    | a m |
    n = 0 ifTrue: [self turn: 180].
    n > 0 ifTrue: [a  90.
                    m < - n - 1]
            ifFalse: [a  -90.
                        m  n + 1].
self turn: a.
self hilbert: 0 - m side: s.
self turn: a.
self go: s.
self hilbert: m side: s.
self turn: 0 - a.
self go: s.
self turn: 0 - a
self hilbert: m side: s.
self go: s.
self turn: a.
self hilbert: 0 - m side: s.
self turn: a

그림 19-4


소스 폼이 다른 모양으로 되어 있는 힐버트 곡선의 효과는 꽤 괜찮다. Form이 한 행에 있는 세 개의 점이라고 가정하면, 이는 wait라고 불리는 시스템 커서가 된다. 그림 19.5의 이미지는 아래 표현식을 평가하여 생성되었다.

bic  Pen new sourceForm: Cursor wait.
bic combinationRule: Form under.
bic hilbert: 4 side: 16

그림 19-5


Cursor wait 와 Form under 표현식은 시스템 내 상수이자 명명된 클래스에 알려져 있는 Form과 조합 규칙으로 각각 접근한다. 그 외 상수는 다음 장의 절에 열거되어 있다. sourceForm: 과 combinationRule: 메시지는 슈퍼클래스 BitBIt으로부터 Pens에 의해 상속된다.


Pen 명령자

다음 예제는 그림 19.6에 표시되어 있다. 디자인이 생성되는 과정은 표시할 수 없지만 독자가 시도해보면 좋은 예제가 된다. 여러 Pens를 제어하고 디자인 그리기를 조정하는 객체를 생성하는 것이 기본 개념이다. 이러한 객체 유형의 클래스를 Commander라고 부른다. Commander는 Pens의 배열이다. Commander가 각 Pen을 열거하고 Pen 명령을 포함하는 블록을 평가하도록 만들면 Commander가 제어하는 Pens에 방향을 제공할 수 있다. 따라서 Commander의 Pens가 가령 go: 100 이어야 한다면 Commander에게 다음과 같은 메시지를 전송할 수 있겠다.

do: [ :eachPen | eachPen go: 100]


Commander는 그 Pens를 배열하도록 메시지에 응답할 수도 있으므로 대칭을 기반으로 한 흥미로운 디자인을 생성할 수 있다. 다음으로 표시된 Commander의 설명에 주어진 두 메시지는 fanOut과 lineUpFrom: startPoint to: endPoint이다. 첫 번째 메시지는 Pens의 각이 360도에 걸쳐 균일하게 분포되도록 배열한다. Commander의 Pens는 인자가 직선의 양끝점을 정의하는 lineUpFrom:to: 메시지를 이용해 직선을 따라 균일하게 위치시킬 수 있다.


Commander에 대한 설명은 다음과 같다. new: 메시지가 재정의되어 Pens가 Array의 각 요소에 저장된다.

클래스명 Commander
슈퍼클래스 Array
클래스 메서드
instance creation
    new: numberOfPens
        | newCommander |
        newCommander  super new: numberOfPens.
        1 to: numberOfPens do:
            [ :index | newCommander at: index put: Pen new].
        newCommander
인스턴스 메서드
distributing
    fanOut
        1 to: self size do:
            [ :index |
                (self at: index) turn: (index -1) * (360 / self size)]
    lineUpFrom: startPoint to: endPoint
        1 to: self size do:
            [ :index |
                (self at: index)
                    place: startPoint + (stopPoint - startPoint * (index -1) / (self size - 1))]


메서드는 Point 클래스의 인스턴스로 메시지를 전송하는 유용한 예제이다. 그림 19.6에 실린 그림은 아래 표현식을 평가하여 그려진다.

bic  Commander new: 4.
bic fanOut
bic do: [ :eachPen | eachPen up. eachPen go: 100. eachPen down].
bic do: [ : eachPen | eachPen spiral: 200 angle: 121]


Commander로 전송되는 do: 메시지는 그 Collection 슈퍼클래스로부터 상속된다.

그림 19-6


Commander의 또 다른 사용 예는 그림 19.7에 제공된다. 이 그림은 lineUpFrom:to: 메시지를 이용해 생성되었다. 아래 표현식을 이용해 생성된 각도에서 직선을 따라 배열된 나선형의 간단한 시퀀스에 해당한다.

bic  Commander new: 6.
bic lineUpFrom: (300@150) to: (300@500).
bic do: [ :eachPen | eachPen spiral: 200 angle: 121]


❏ Pen 명령자에 대한 추가 프로토콜


행동이 위치 또는 방향을 변경하는 Pen 클래스의 프로토콜에 대한 메시지마다 Commander의 확장 설명이 Commander로 추가된다. 이러한 추가 프로토콜은 Pen의 프로토콜에 속하는 메시지를 Commander로 전송하는 기능을 지원한다. 그러한 메시지는 컬렉션의 요소로 메시지를 알리면서 구현된다. 이렇게 Commander로 전송되는 메시지는 do: 메시지보다는 Pen으로 전송되는 메시지와 동일한 형태를 취한다. 이런 방식으로 정의된 클래스의 경우 Commander에 대한 그리기 시퀀스는 Pen에 대한 그리기 시퀀스와 더 비슷하게 보인다. 또 Commander가 명령한 모든 Pens는 동시에 그려지는데, 그림 19.6 또는 그림 19.7의 모든 나선형이 한 번에 커지는 것을 예로 들 수 있겠다.

그림 19-7

down
    self do: [ :each | each down]
up
    self do: [ :each | each up]
turn: degrees
    self do: [ :each | each turn: degrees]
north
    self do: [ :each | each north]
go: distance
    self do: [ :each | each go: distance]
goto: aPoint
    self do: [ :each | each goto: aPoint]
place: aPoint
    self do: [ :each | each place: aPoint]
home
    self do: [ :each | each home]
spiral: n angle: a
    1 to: n do:
        [ :i | self go: i. self turn: a]


이러한 추가 프로토콜로 아래 표현식을 이용하면 그림 19.6을,

bic  Commander new: 4.
bic fanOut.
bic up.
bic go: 100.
bic down.
bic spiral: 200 angle: 121


아래 표현식을 이용하면 그림 19.7을 그릴 수 있다.

bic  Commander new: 6.
bic lineUpFrom: (300@150) to: (300@500).
bic spiral: 200 angle: 121


Notes