Created
January 5, 2014 01:24
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
;;;;; made on PLT Racket | |
;;;;; Racket: | |
;;;;; http://racket-lang.org/ | |
; Racket 実装依存 | |
; 他の処理系の場合、SRFI(Schemeの共有ライブラリ)を呼び出すには | |
; Gauche: http://practical-scheme.net/gauche/index-j.html | |
; (use srfi-1) ... | |
; Guile: http://www.gnu.org/software/guile/ | |
; (srfi srfi-1) ... | |
; 等に書き換える | |
(require srfi/1 srfi/11 srfi/13 srfi/27) | |
;;;;; 乱数の初期化 | |
(random-source-randomize! (make-random-source)) | |
;;;;; ダミーデータ | |
; | |
;(define player 5000) | |
;(define comp 5000) | |
;;;;; Game Over | |
(define game-over 30000) ; ゲームオーバーの条件 | |
;;;;; トーフのデータ | |
(define tofu '((price . 50) ; トーフの値段 | |
(cost . 40) ; トーフの製造コスト | |
(sunny . 500) ; 晴れの時に売れる最大個数 | |
(cloudy . 300) ; 曇りの時に売れる最大個数 | |
(rainy . 100))) ; 雨の時に売れる最大個数 | |
(define messages | |
; ゲームで使われる文字列のデータ | |
'((yen . " 円 ") | |
(1000-yen . #\■) | |
(empty-yen . #\□) | |
(space . #\space) | |
(introduction . "イスカンダルのトーフ屋ゲーム(Scheme 関数プログラミング 版)\n | |
Copyright (c) 1978 - 2014 by Nobuhide Tsuda\n\n | |
ルール説明しますか?[y/n]") | |
(instruction . "ここはイスカンダル星。あなたはここでトーフ屋を経営し、\n | |
地球への帰還費用を作り出さなくてはいけません。\n | |
でもお向かいには、コンピュータが経営するトーフ屋があります。。。\n\n | |
トーフの原価は1個40円、販売価格は50円です。\n | |
1日に売れる個数は天候に左右されます。\n | |
晴れると500個、くもりだと300個、雨のときは100個まで売れます。\n | |
トーフは日持ちしないので、売れ残った分はすべて廃棄します。\n | |
そこで、次の日の天気予報をよく見て、何個作るか決心してください。\n | |
所持金5千円からはじめて早く3万円を超えた方が勝ちです。\n\n | |
いいですか?[y/n]\n") | |
(money . "\n所持金:\n") | |
(player . "あなた") | |
(comp . #("わたし" "わたしは ")) | |
(sunny . #("\n明日の天気予報: 晴れ " #\◎ " 晴れ \\(^o^)/ ")) | |
(cloudy . #( "% くもり " #\・ " くもり (~_~) ")) | |
(rainy . #("% 雨 " #\● " 雨 (;_;) ")) | |
(percent . "%") | |
(howmany-tofus? . "\nトーフを何個作りますか?(1~") | |
(kokka . ")") | |
(makes . "個 作ります。\n") | |
(next-day . "***** 次の日 *****") | |
(weather . "今日の天気は") | |
(period . ".") | |
(is . " です。") | |
(you-win . "\nあなたの勝ちです。") | |
(even . "\n引き分けです。") | |
(you-lose . "\nコンピュータの勝ちです。") | |
(play-again? . "\nplay again ? [y/n]") | |
)) | |
(define (show-money player comp data) | |
; プレイヤーとコンピュータの持ち金の文字列整形 | |
; 本当はSRFIのformatを使えばもっと簡単に書ける | |
(letrec ((calc | |
; プレイヤーとコンピュータの持ち金の表示用文字列整形 | |
(lambda (name gold) | |
(letrec ((format | |
; 数値用文字列整形 | |
(lambda () | |
(cond ((> gold 9999) 1) | |
((> gold 999) 2) | |
((> gold 99) 3) | |
(else 4)))) | |
(spaces | |
; 数値を揃える為のスペース整形 | |
(lambda (num) | |
(make-string num (cdr (assq 'space data))))) | |
(calc | |
; 持ち金のグラフィック部分の整形 | |
(lambda () | |
(let ((x (quotient gold 1000))) | |
(let ((y (- 30 x))) | |
(string-append | |
(make-string x (cdr (assq '1000-yen data))) | |
(make-string y (cdr (assq 'empty-yen data))))))))) | |
; calc の body | |
(string-append (spaces 2) | |
(let ((n (cdr (assq name data)))) | |
(if (vector? n) | |
(vector-ref n 0) | |
n)) | |
(spaces 1) | |
(spaces (format)) | |
(number->string gold) | |
(cdr (assq 'yen data)) | |
(calc) | |
"\n"))))) | |
; body | |
(string-append (cdr (assq 'money data)) | |
(calc 'player | |
player) | |
(calc 'comp | |
comp)))) | |
(define (show-weather-report wr data) | |
; 天気予報表示の文字列整形 | |
; 本当はSRFIのformat を使うともっと簡単に書ける | |
(letrec ((calc0 | |
; パーセンテージ表示用 | |
(lambda (keys) | |
(let ((s0 (map (lambda (k) | |
(vector-ref | |
(cdr (assq k data)) 0)) keys)) | |
(s1 (map (lambda (k) | |
(number->string | |
(cdr (assq k wr)))) | |
keys))) | |
(string-append (string-concatenate | |
(map string-concatenate (zip s0 s1))) | |
(cdr (assq 'percent data)) | |
"\n")))) | |
(calc1 | |
; グラフィック表示用 | |
(lambda (keys) | |
(let ((nums (map (lambda (k) | |
(quotient | |
(* (cdr (assq k wr)) 10) 25)) | |
keys))) | |
(string-concatenate | |
(map (lambda (k n) | |
(make-string n | |
(vector-ref (cdr (assq k data)) 1))) | |
keys nums)))))) | |
; body | |
(let ((keys '(sunny cloudy rainy))) | |
(string-append (calc0 keys) (calc1 keys))))) | |
(define (ask-howmany-tofu num data) | |
; いくつトーフを作るのか尋ねる | |
(string-append (cdr (assq 'howmany-tofus? data)) | |
(number->string num) | |
(cdr (assq 'kokka data)))) | |
(define (computer-reply num data) | |
; コンピュータの反応表示 | |
(string-append (vector-ref (cdr (assq 'comp data)) 1) | |
(number->string num) | |
(cdr (assq 'makes data)))) | |
(define (show-weather-is sym data) | |
; 実際の天気を表示 | |
(let ((keys `(weather period period period ,sym is))) | |
(map (lambda (k) | |
(let ((v (cdr (assq k data)))) | |
(if (vector? v) | |
(vector-ref v 2) | |
v))) keys))) | |
(define (show-winner key data) | |
; 勝者を表示 | |
(cdr (assq key data))) | |
(define (print phase player comp wr pn cn) | |
; 表示だけじゃなくって、 Eval から渡された6つの引数、 | |
; phase、 player、 comp、 wr、 pn、 cn を | |
; そのまま返す | |
(for-each (lambda (x) | |
(sleep 0.5) ; Racket 実装依存 | |
(display x)) | |
(case phase | |
((input-integer) `(,(string-append (show-money player comp messages) | |
(show-weather-report wr messages) | |
(ask-howmany-tofu cn messages)))) | |
((opponent-turn) `(,(computer-reply cn messages))) | |
((next-day) (show-weather-is cn messages)) | |
((show-winner) `(,(show-winner cn messages))) | |
(else `(,(cdr (assq phase messages)))))) | |
(values phase player comp wr pn cn)) | |
;;;;; Eval | |
(define (comp-calc comp weather-report) | |
; コンピュータが作るトーフの個数を天気予報から算出 | |
(letrec ((calc | |
(lambda () | |
(cond ((>= (cdr (assq 'sunny weather-report)) 80) 500) | |
((>= (cdr (assq 'rainy weather-report)) 20) 100) | |
(else 300))))) | |
; body | |
(min (quotient comp (cdr (assq 'cost tofu))) (calc)))) | |
(define (weather-report prob1 prob2) | |
; 天気予報を計算する関数 | |
(let ((sunny (- 100 (max prob1 prob2))) | |
(rainy (min prob1 prob2))) | |
(let ((cloudy (- 100 sunny rainy))) | |
`((sunny . ,sunny) | |
(cloudy . ,cloudy) | |
(rainy . ,rainy))))) | |
(define (actual-weather weather-report tofu r) | |
; 実際の天気を計算する関数 | |
; 天気予報を参照して計算する | |
(cond ((< r (cdr (assq 'rainy weather-report))) | |
(values 'rainy (cdr (assq 'rainy tofu)))) | |
((< r (+ (cdr (assq 'rainy weather-report)) | |
(cdr (assq 'cloudy weather-report)))) | |
(values 'cloudy (cdr (assq 'cloudy tofu)))) | |
(else | |
(values 'sunny (cdr (assq 'sunny tofu)))))) | |
(define (calc seller n sold tofu) | |
; トーフの売上から損益を計算する | |
(+ seller (- (* (min sold n) (cdr (assq 'price tofu))) | |
(* n (cdr (assq 'cost tofu)))))) | |
(define (test player comp game-over) | |
; ゲームの勝者を計算する | |
(letrec ((calc | |
; プレイヤーの持ち金とコンピュータの持ち金から | |
; 勝者を計算する | |
(lambda () | |
(cond ((> player comp) 'you-win) | |
((= player comp) 'even) | |
(else 'you-lose))))) | |
; body | |
(and (or (>= player game-over) (>= comp game-over)) | |
(calc)))) | |
(define (interp phase player comp wr pn cn) | |
; Eval | |
; Read から与えられた6つの引数に従って評価を行うこのプログラムの心臓部 | |
; phase に対しては次のフェーズをセット | |
; player と comp はそれぞれの持ち金を表す | |
; wr は天気予報計算の結果を入れる変数 | |
; pn にはプレイヤーが入力した情報 | |
; cn には eval の評価値(もしあれば) | |
; と、それぞれ束縛される | |
; これらはその後、多値として Print に渡される | |
(let-syntax ((initialize | |
; コード中で初期化に必要なトコが3箇所あり、 | |
; メンドいんで initialize とローカルマクロとして | |
; まとめた | |
(syntax-rules () | |
((_ player comp) | |
(let ((prob0 (random-integer 100)) | |
(prob1 (random-integer 100))) | |
(values 'input-integer player comp | |
(weather-report prob0 prob1) pn | |
(quotient player (cdr (assq 'cost tofu))))))))) | |
; body | |
(case phase | |
((introduction) (if pn | |
(values 'instruction player comp wr pn cn) | |
(initialize player comp))) | |
((instruction) (if pn | |
(initialize player comp) | |
(values phase player comp wr pn cn))) | |
((input-integer) (let ((c (comp-calc comp wr))) | |
(values 'opponent-turn player comp wr pn c))) | |
((opponent-turn) (let ((prob (random-integer 100))) | |
(let-values (((sym sold) (actual-weather wr tofu prob))) | |
(values 'next-day (calc player pn sold tofu) (calc comp cn sold tofu) wr pn sym)))) | |
((next-day) (let ((fact (test player comp game-over))) | |
(if fact | |
(values 'show-winner player comp wr pn fact) | |
(initialize player comp)))) | |
((show-winner) (values 'play-again? player comp wr cn pn)) | |
((play-again?) (if pn | |
(initialize 5000 5000) | |
(exit))) ; exit はRacket 実装依存 | |
(else (values 'introduction player comp wr pn cn))))) | |
;;; Read | |
(define (y-or-n? arg) | |
; yes か no かに類する入力に対して | |
; yes の場合は #t、 no の場合は #f を返す | |
; 入力違反に対しては空リストを返す | |
(letrec ((test | |
; 入力されたシンボルを大文字シンボルに変換 | |
; yes に類する場合は #t | |
; no に類する場合は #f | |
; それら以外の場合は '() を返す | |
(lambda (sym) | |
(let ((upsym (string->symbol | |
(string-upcase | |
(symbol->string sym))))) | |
(case upsym | |
((Y YES) #t) | |
((N NO) #f) | |
(else '())))))) | |
; body | |
(if (symbol? arg) | |
(test arg) | |
'()))) | |
(define (input-integer arg limit) | |
; 整数の入力しか受け付けない関数 | |
; 入力違反には空リストを返す | |
(letrec ((test | |
; 入力された整数が0からトーフを作れる | |
; 最大値の間にあるか調べる | |
; 最大値は環境を参照する | |
; 入力違反には '() を返す | |
(lambda (num) | |
(if (<= 0 num limit) | |
num | |
'())))) | |
; body | |
(if (integer? arg) | |
(test arg) | |
'()))) | |
(define (parser phase player comp wr pn cn) | |
; Read | |
; 入力を受け取らなくても基本的には6つの値 | |
; phase、 player、 comp、 wr、 pn、 cn | |
; を自動的に返し、これらは環境を参照してる | |
; 入力は pn に束縛する | |
(letrec-syntax ((iter | |
; 出力用のループはローカルマクロでまとめてある | |
(syntax-rules () | |
((_ (arg ...)) | |
(let loop ((i (arg ...))) | |
(if (null? i) | |
(loop (arg ...)) | |
(values phase player comp wr i cn))))))) | |
(case phase | |
((introduction instruction play-again?) (iter (y-or-n? (read)))) | |
((input-integer) (iter (input-integer (read) cn))) | |
(else (values phase player comp wr pn cn))))) | |
;;;;; REPL (Read-Eval-Print-Loop) | |
(define (repl phase player comp wr pn cn) | |
(let-values (((r0 r1 r2 r3 r4 r5) (parser phase player comp wr pn cn))) | |
(let-values (((e0 e1 e2 e3 e4 e5) (interp r0 r1 r2 r3 r4 r5))) | |
(let-values (((p0 p1 p2 p3 p4 p5) (print e0 e1 e2 e3 e4 e5))) | |
(repl p0 p1 p2 p3 p4 p5))))) | |
(repl #f 5000 5000 #f 0 0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment