以前作った Ruby でのオブジェクト指向じゃんけんゲームを、iOS アプリの RubyPico 向けに書き直しました。操作性を iPad 用に変えただけで、あとはほぼもとのままです。iPad で Ruby スクリプト(正確には mruby)が動くとはじつに楽しいですね。RubyPico、もっと遊んでみたいと思います。
コードは Gist に置いておきました。
m_jankenasobi.rb · GitHub
iPad mini で確認しました。
以前 C# や Ruby で OOP を使ったじゃんけんゲームを作ってみましたが、同等の機能をもったそれを C言語のお勉強として作ってみました。C言語だから手続き型プログラミングということになりますが、C 初心者のためひどいコードでしょう。なるたけ改良してみたいと思っています。
Linux Mint 18 で確認しました。コンパイラは gcc です。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> struct { char *name; int wincount; } pc, user; char* show_hand(int hd_num) { char *hd_name[] = {"グー", "チョキ", "パー"}; return hd_name[hd_num - 1]; } void judge(int cp_hand, int hand) { printf("%s 対 %s で ", show_hand(cp_hand), show_hand(hand)); if (cp_hand == hand) { printf("引き分けです。\n"); return; } if ((3 + cp_hand - hand) % 3 == 2) { printf("%s", pc.name); pc.wincount++; } else { printf("%s", user.name); user.wincount++; } printf("の勝ちです。\n"); } void game(int n) { char a[20]; int hand, cp_hand; printf("*** %d回戦 ***\n", n); do { printf("%sの手を入力して下さい(1:グー, 2:チョキ, 3:パー):", user.name); scanf( "%s", a); if (!strcmp(a, "1") || !strcmp(a, "2") || !strcmp(a, "3")) hand = atoi(a); else hand = 0; } while (!hand); cp_hand = rand() % 3 + 1; judge(cp_hand, hand); } void final_winner() { printf("\n*** 最終結果 ***\n"); printf("%d 対 %d で ", pc.wincount, user.wincount); if (pc.wincount == user.wincount) { printf("引き分けです。\n"); return; } if (pc.wincount > user.wincount) printf("%s", pc.name); else printf("%s", user.name); printf("の勝ちです。\n"); } int main() { srand((unsigned)time(NULL)); /* 乱数のシードを time で与える */ pc.wincount = 0; user.wincount = 0; pc.name = "Computer"; char st[30]; printf("あなたの名前を入力して下さい:"); scanf("%s", st); user.name = st; printf("%s 対 %s :じゃんけん開始\n\n", pc.name, user.name); for (int n = 1; n <= 5; n++) game(n); final_winner(); return 0; }
上の sda が本体の PC で、下の sdb は外付けHDD です。全部で 6つの Linux ディストリビューションがインストールされているのがわかるかと思います。
sdb3 の Kubuntu がいらない子なので、ここに Ubuntu 17.04 を入れてみようと思います。
まず、ubuntu-17.04-desktop-amd64.iso を Ubuntu の公式サイトからダウンロードして下さい。
それを Brasero などで DVD-ROM にイメージとして焼いて(ファイルのコピーではありません)インストールするのがふつうですが、僕の中古PC は DVDドライブの調子が悪いので、USBメモリにディスクイメージを焼きました。
$ su # cat ubuntu-17.04-desktop-amd64.iso > /dev/sdX # sync
sdX の X には実際の USBメモリのデバイス名を入れて下さい。ここを間違えると簡単にシステムがぶっ壊れますので、慎重に。なお、USBメモリ内のすべてのデータが上書きされて失われますので、気をつけて下さい。
PC を再起動し、USBメモリをブートデバイスに選択して(自分の PC では PC 起動時に [F12] キーで起動デバイスが選択できます)インストールを開始します。USBメモリからのブートはどの PC でも可能なわけではないようです。無理な場合はふつうに DVD-ROM からブートして下さい。
インストールの言語に [日本語] を選びます。
[それ以外] を選びます。マルチブートはこれでやる必要があります。
今回は /dev/sdb3 にインストールします。
[利用方法] は ext4 を、[マウントポイント] は「/」を選択します。[初期化] はどちらでもよいでしょう。
ブートローダをどこに入れるかですが、HDD の先頭に入れればこの OS がデフォルトのそれになります。ここでは外付けHDD に GRUB をいれたくないので、/dev/sdb3 に入れることにします。ただし、これを選択するとそのままではインストールしたこの OS が GRUB の起動メニューに現れないので、あとでフォローが必要になります。それで取り敢えず [インストール] を押せばあとはさほど問題ないでしょう(すべてデフォルトのままでいけると思います)。
さて、インストールが終了して PC を再起動させますが、このままでは入れた Ubuntu 17.04 が GRUB の起動メニューにありません。なので他のインストール済の OS から Grub Customizer を立ち上げます。Grub Customizer が入っていなければ、
$ sudo add-apt-repository ppa:danielrichter2007/grub-customizer $ sudo apt-get update && sudo apt-get install grub-customizer
で入れます。
このように、インストールした Ubuntu 17.04 がメニューに出ているので、[保存] を押せば修正おわりです。
あとはもう一度再起動してみて、きちんと Ubuntu 17.04 が立ち上がるか確認して下さい。お疲れ様でした。
なお、以上はレガシーBIOS の場合です。最近の UEFI BIOS の場合はこれとは多少ちがうので注意して下さい。
ひとつ前の記事で迷路を生成したので、その中を歩いてみるプログラムを書きました。OpenGL を使っています。Ruby 2.3.3, Linux Mint 18.3 で動作確認。
赤い床のマスがゴールです。"←" で左回転、"→" で右回転、"↑" で前進します。迷路は実行のたびに新たに生成されます。
ぐぐってみても意外とこんな単純なゲーム(?)の実装がないのですよねえ。
walk_maze.rb
require_relative 'miniopengl' require_relative 'maze' module WalkMaze def self.start(width, height, yokokabe, tatekabe) MiniOpenGL.app width: 400, height: 400, depth_buffer: :on, title: "maze" do px, pz = width - 1, height - 1 #迷路のスタート位置 dir = [0, -1] #最初の向きの設定 draw do glEnable(GL_BLEND) #混合処理を可能にします glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) self.color_alpha = 0.9 glEnable(GL_DEPTH_TEST) #隠面消去 clear color(0, 1, 1) (height + 1).times {|z| line3(0, 0, z, width, 0, z)} (width + 1).times {|x| line3(x, 0, 0, x, 0, height)} color(0, 0.8, 0) (height + 1).times do |z| width.times do |x| next if yokokabe[x + z * width].zero? vtx = [x, 0, z, x + 1, 0, z, x + 1, 1, z, x, 1, z] draw_vertex(GL_QUADS, 3, vtx) end end (width + 1).times do |x| height.times do |z| next if tatekabe[z + x * height].zero? vtx = [x, 0, z, x, 0, z + 1, x, 1, z + 1, x, 1, z] draw_vertex(GL_QUADS, 3, vtx) end end color(1, 0, 0) draw_vertex(GL_QUADS, 3, [0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1]) #ゴールの床 display end set_eye = ->{ init_modelview x = px + 0.5 z = pz + 0.5 look_at(x, 0.5, z, x + dir[0], 0.5, z + dir[1], 0, 1, 0) } reshape do |w, h| viewport(0, 0, w, h) init_projection perspective(120, w / h.to_f, 0.2, 20) set_eye.() end check = ->{ if dir[0].zero? z = (dir[1] < 0) ? pz : pz + 1 f = yokokabe[z * width + px] else x = (dir[0] < 0) ? px : px + 1 f = tatekabe[x * height + pz] end f.zero? } end_f = false key_in2 do |key| exit if end_f case key when GLUT_KEY_LEFT #左 dir = [dir[1], -dir[0]] when GLUT_KEY_RIGHT #右 dir = [-dir[1], dir[0]] when GLUT_KEY_UP #前 if check.() #前へ進めるかどうか px += dir[0] pz += dir[1] end end set_eye.() redisplay if px.zero? and pz.zero? puts "Goal!" end_f = true end end end end end Width, Height = 10, 10 yokokabe, tatekabe = Maze.new(Width, Height).generate #Left, Top = 20, 20 #CellWidth = 20 # #fork { show_maze(Width, Height, yokokabe, tatekabe) } WalkMaze.start(Width, Height, yokokabe, tatekabe)
maze.rb はひとつ前の記事です。miniopengl.rb はこちらを参照。
なお、Linux の OpenGL についてはこちらも参照。
※追記
これの立体版も作ってみました。
obelisk.hatenablog.com
http://apollon.issp.u-tokyo.ac.jp/~watanabe/tips/maze.html
ここのリンク先のアルゴリズムを使って、迷路のジェネレーターを Ruby で書いてみました。リンク先でも Ruby での実装がありますが、自分でやってみました。
20×20の迷路です。
コードは以下です。迷路の作成と描画は別にしてあります。描画は自作の Gem 'oekaki' を使っています。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
(※追記 以前は下のコードは自作のライブラリ 'utils' に依存していましたが、それを使わないように変更しました。(10/9))
maze.rb
require 'oekaki' class Maze def initialize(width, height) @width, @height = width, height @yokokabe = Array.new(@width * (@height + 1), 1) @tatekabe = Array.new(@height * (@width + 1), 1) @left_wall = (@width..((a = @yokokabe.size) - @width - 1)).to_a @left_wall += ((a + @height)..(a + @tatekabe.size - @height - 1)).to_a @yokokabe_num = a @cells = Array.new(@width) {Array.new(@height)} #@cells にすべてちがう値の数を与える (@width * @height).times {|i| @cells[i % @width][i / @width] = i} end def generate break_wall until finish [@yokokabe, @tatekabe] end def wall_position(num) #flag, a flag = (num < @yokokabe_num) #横壁なら true、縦壁なら false a = if flag @width else num -= @yokokabe_num @height end [flag, num % a, num / a - 1] end def replace(s, t) @width.times do |x| @height.times {|y| @cells[x][y] = t if @cells[x][y] == s} end end def break_wall #num, flag, x, y, a, b num = @left_wall[rand(@left_wall.size)] #残された壁から壊す壁をランダムに選択する flag, x, y = wall_position(num) #壊す壁の情報を得る @left_wall.delete(num) if flag #横壁 return if (a = @cells[x][y]) == (b = @cells[x][y + 1]) @yokokabe[num] = 0 #実際に壁を壊す else #縦壁 return if (a = @cells[y][x]) == (b = @cells[y + 1][x]) @tatekabe[num - @yokokabe_num] = 0 #実際に壁を壊す end a, b = b, a if a < b replace(a, b) #壁を壊したあとに通路をつなげる end def finish #@cell の値がすべて等しくなれば終了 a = @cells[0][0] @cells.flatten.each {|b| return false if b != a} true end end def show_maze(w, h, yokokabe, tatekabe) wi = Left * 2 + w * (CellWidth + 1) he = Top * 2 + h * (CellWidth + 1) Oekaki.app width: wi, height: he, title: "maze" do draw do clear color(65535, 65535, 65535) (h + 1).times do |y| w.times do |x| next if yokokabe[x + y * w].zero? x1 = Left + x * (CellWidth + 1) y1 = Top + y * (CellWidth + 1) line(x1, y1, x1 + CellWidth + 1, y1) end end (w + 1).times do |x| h.times do |y| next if tatekabe[y + x * h].zero? x1 = Left + x * (CellWidth + 1) y1 = Top + y * (CellWidth + 1) line(x1, y1, x1, y1 + CellWidth + 1) end end #save_pic(get_pic(0, 0, wi, he), "maze.png") #画像ファイルの作成 end end end if __FILE__ == $0 Width, Height = 30, 20 yokokabe, tatekabe = Maze.new(Width, Height).generate Left, Top = 20, 20 CellWidth = 20 show_maze(Width, Height, yokokabe, tatekabe) end
メソッド Maze#generate で迷路を生成します。返り値は yokokabe と tatekabe で、それぞれ横方向と縦方向の壁をあらわす配列です(1 なら壁が存在し、0 なら存在しない。初期値はすべて 1)。メソッド Maze#break_wall は、壁をランダムにひとつ選んで、壊してもよい壁なら壊します。メソッド show_maze で迷路を表示します。ここで自家製の Gem 'oekaki' を使っています。png 画像ファイルに落とすことも出来ます。
※追記
これで作った迷路を実際に歩いてみるプログラムを書きました。
これを見てどうも Java や C++ の話のように思ったのだが、最初の「『等値』と『等価』の違いを説明してください」というのがよくわからなかった。僕は Ruby しか知らない素人初心者プログラマなのだが、Ruby だとどういうことなのだろう。
ただ、Ruby でも「==」と「equal?」はちがうよね。
irb(main):001:0> a = "hoge" => "hoge" irb(main):002:0> b = "hoge" => "hoge" irb(main):003:0> a == b => true irb(main):004:0> a.equal?(b) => false irb(main):005:0> a.object_id => 47028457345780 irb(main):006:0> b.object_id => 47028458255160 irb(main):007:0> a = b = "hoge" => "hoge" irb(main):008:0> a.equal?(b) => true
上を見ればわかると思うけれども、「==」はオブジェクトが同じ内容なら true、「equal?」は同じオブジェクトID でなければ true にならない。上の「等値」と「等価」というのは、これと関係があるのかな。