Smalltalkで三目並べを500文字プログラミングしようとして挫折した


Smalltalk でチャレンジして同じく挫折しましたのでこちらに晒します。今回はいつもの Squeak/Pharo ではなく自分としては珍しく GNU Smalltalk で書きました。GNU Smalltalk は Smalltalk 処理系として変わり種(逆を言えばそこらへんの普通の言語に近いw)という以外にも節操なく拡張されている Squeak に比べて API が貧弱で書きたいように書けないのが難点で個人的にはあまり好きではないのですが、入出力まわりの処理を Kotlin版に合わせやすいということで選びました。

挙動はリンク先とほぼ同じですが、出力のタイミングとかはちょっと変えています。

@enu7さんの Kotlin版は、勝利判定にビット列を使って凝っていたのですが、こちらは文字列をそのまま使っています。ただ、副作用をバリバリ使ってちょっと変態チックな勝利判定にしてみました。

あと GNU Smalltalk にはループからの脱出機構がないので、冒頭で BlockClosure >>#valueWithExit として追加しています。

手元の環境に GNU Smalltalk をインストールするのは(自分を含め^^;)面倒だと思うので Coding Groundideone.com にもコードを置いておきましたので動作を試したり改変できます。

BlockClosure extend [ valueWithExit [ self value: [^self] ] ]

| board showBoard patterns get message |

board := #(('.' '.' '.') ('.' '.' '.') ('.' '.' '.')) deepCopy.
showBoard := [board do: [:line | line do: [:cell | cell display]. stdout nl]].

patterns := board,
   ({[:i | 1]. [:i | 2]. [:i | 3]. [:i | i]. [:i | 4-i]} collect: [:spec |
      (1 to: 3) collect: [:idx | (board at: idx) at: (spec value: idx)]]).

get := [:xy |
   | v |
   [(xy, '?') display. (v := stdin nextLine asInteger) between: 1 and: 3] whileFalse.
   v
].

message := '[draw]'.
[:exit |
   | rest |
   [(rest := (board gather: [:line | line]) count: [:each | each = '.']) > 0] whileTrue: [
      | player x y |
      player := #('x' 'o') at: rest \\ 2 + 1.
      ('[<', player asString, '>''s turn]') displayNl.
      showBoard value.
      [  x := get value: 'x'.
         y := get value: 'y'.
         ((board at: y) at: x) ~= '.'] whileTrue.
      ((board at: y) at: x) at: 1 put: player first.
      (patterns anySatisfy: [:pat | (pat count: [:each | each = player]) = 3]) ifTrue: [
         message := '[<', player asString, '> won!]'.
         exit value].
   ]
] valueWithExit.

message displayNl.
showBoard value.
'[game over]' displayNl

追記:
参考まで Kotlin のビットで処理するのを比較的忠実に Smalltalk で再現した版も書きました。こちらも Coding Groundideone.com に置いてあります。

BlockClosure extend [ valueWithExit [ self value: [^self] ] ]

Integer extend [
   << x [ ^ self bitShift: x ]
   >> x [ ^ self bitShift: x negated ]
]

| readline player board pattern |

readline := [
   | ax a |
   ax := stdin nextLine.
   a := 0.
   (ax ~= nil and: [ax size = 1]) ifTrue: [a := '123' indexOf: ax first].
   a
].

player := 0.
board := 0.
pattern := #(86016 1344 21 66576 16644 4161 65793 4368).

[:exit | [((board radix: 2) count: [:x | x = $1]) < 9] whileTrue: [
   | pnt point |
   ('[', player printString, ' turn]') displayNl.

   8 to: 0 by: -1 do: [:i |
      ('.ox' at: ((board >> (i * 2)) bitAnd: 2r11) + 1) display. "2r00 = $. / 2r01 = $o / 2r11 = $x"
      i \\ 3 = 0 ifTrue: ['' displayNl].
   ].

   pnt := 0 @ 0.
   [pnt x < 1] whileTrue: ['x?' display. pnt := readline value @ pnt y].
   [pnt y < 1] whileTrue: ['y?' display. pnt := pnt x @ readline value].

   point := (2r1 << (6 * (3 - pnt y)) << ((3 - pnt x) * 2)) << player.
   ((board bitAnd: point) = 0 and: [(board >> (1 - player) bitAnd: point >> player) = 0]) ifTrue: [
      board := board bitOr: point.
      (pattern findFirst: [:x | (x bitAnd: (board >> player)) = x]) ~= 0 ifTrue: [('', player printString, ' won!') displayNl. exit value].
      player := player bitXor: 1
   ]
]] valueWithExit.

'game end' displayNl