C言語でじゃんけんゲーム

20170429023223
 

 
 
以前 C#RubyOOP を使ったじゃんけんゲームを作ってみましたが、同等の機能をもったそれを 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;
}

Linux のマルチブートについて(Ubuntu 17.04)

GParted の画像を見て下さい。


上の 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 の場合はこれとは多少ちがうので注意して下さい。

迷路の中を歩く(Ruby)

ひとつ前の記事で迷路を生成したので、その中を歩いてみるプログラムを書きました。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 はこちらを参照。

なお、LinuxOpenGL についてはこちらも参照。


※追記
これの立体版も作ってみました。
obelisk.hatenablog.com

Ruby で迷路作成

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 画像ファイルに落とすことも出来ます。

 
 
※追記
これで作った迷路を実際に歩いてみるプログラムを書きました。

Linux Mint(Ubuntu)でSDカードに書き込みできない

まず SDカードを PC に挿入しても認識しない。
これは何故か SDカードを LOCK してから挿入したら認識した。

もう一度 LOCK を解除して挿入。しかしフォルダをコピーしても「転送先は読み込み専用です」という表示が出る。アンマウントして再マウントしてもダメ。パーミッションを変更してもダメ(というか、Linuxファイルシステムでないので無意味)。ちなみにファイル・システムは msdos
よくわからずにダメ元で SDカードを挿入したままで再起動。そうしたら普通に認識された。何なんだ。

「等値」と「等価」は Ruby では?

これを見てどうも JavaC++ の話のように思ったのだが、最初の「『等値』と『等価』の違いを説明してください」というのがよくわからなかった。僕は 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 にならない。上の「等値」と「等価」というのは、これと関係があるのかな。

Ruby で関数型プログラミングもどき

上のリンク先で JavaScript を使って、オブジェクト指向プログラミングと関数型プログラミングの対比をやってあったので、Ruby に移植してみました。

 

課題:
唐揚げ弁当がいくつかあるとします。それぞれ唐揚げが複数入っています。
この中からx個の唐揚げをつまみ食いするプログラムを作りましょう。
つまみ食いはバレないようにするために、
その時点で最も唐揚げ数が多いお弁当から取るという仕様にします。

http://qiita.com/stkdev/items/5c021d4e5d54d56b927c

 

オブジェクト指向で。こんな感じですかね。

class Bento
  def initialize(dish, num)
    @dish = dish
    @num = num
  end
  attr_reader :num
  
  def eat
    @num -= 1
  end
  
  def show
    puts @dish + @num.to_s + ""
  end
end

order = [Bento.new("唐揚げ", 10), 
         Bento.new("唐揚げ",  8),
         Bento.new("唐揚げ",  6)]
         
5.times do
  max_bento = order.inject(order.first) {|r, bento| r = (bento.num > r.num) ? bento : r}
  max_bento.eat
  puts "-------"
  order.each {|x| x.show}
end

結果。

-------
唐揚げ9個
唐揚げ8個
唐揚げ6個
-------
唐揚げ8個
唐揚げ8個
唐揚げ6個
-------
唐揚げ7個
唐揚げ8個
唐揚げ6個
-------
唐揚げ7個
唐揚げ7個
唐揚げ6個
-------
唐揚げ6個
唐揚げ7個
唐揚げ6個

 
 
次は関数型プログラミングっぽく。元の JavaScript コードは上と同じ答えを返さないので、多少変更してあります。

order = [{dish: "唐揚げ", num: 10},
         {dish: "唐揚げ", num:  8},
         {dish: "唐揚げ", num:  6}]
         
show = ->(d) {puts d[:dish] + d[:num].to_s + ""}

show_all = ->(data) {data.each {|x| show[x]}}

select_eating_bento = ->(data) {
  data.inject(data.first) {|r, bento| r = (bento[:num] > r[:num]) ? bento : r}
}

eating_karaage = ->(data) {
  data.map do |bento|
    if bento.equal?(select_eating_bento[data])
      {dish: bento[:dish], num: (bento[:num] - 1)}
    else
      bento
    end
  end
}

eating = ->(num, data) {
  data = eating_karaage[data]
  puts "-------"
  show_all[data]
  eating[num - 1, data] if num > 1
}

eating[5, order]

「副作用」がないようにというのが目標ですね。
 
どちらがわかりやすいでしょうか。

元記事は色いろコメントで怒られていますね…。