スカッシュゲームを作ろう

# スカッシュゲームとは

スカッシュゲームとは、画面の端に当たって跳ね返ってきたボールを、マウスを使って打ち返して遊ぶゲームのことです。

---

# 作成方法


# Step1：ウィンドウとキャンバスを作成しよう

```python
# coding:utf-8
import tkinter as tk

# ウィンドウの作成
win = tk.Tk()
# キャンバスの作成
cv = tk.Canvas(win, width=640, height=480 ,bg="white")
# キャンバスの表示
cv.pack()

# ウィンドウの表示
win.mainloop()
```
---
# Step2：ボールを描いて動かそう
ボールを動かす➝ ボールを表示たり消したりする

ボールを消す 
 1. 背景と同じ色のボールを表示
 2. ボールを表示した後に画面をすべて消す ☜ 今回はこっち

---

## 手順１：変数の設定
```python
# ゲームの初期化
ball_ichi_x = 300 
ball_ichi_y = 150
ball_idou_x = 30
ball_idou_y = -30
ball_size = 10
```


##手順２：画面を描く
```python
# 画面の描画
def draw_screen():
    # 画面クリア
    cv.delete('all') ←　画面を消せという命令
    # キャンパス(画面)の作成
    cv.create_rectangle(0, 0, 640, 480, fill="white", width=0)
```

##手順３：ボールを描く
```python
# ボールを描く
def draw_ball():
    cv.create_oval(ball_ichi_x - ball_size, ball_ichi_y - ball_size, ball_ichi_x + ball_size, ball_ichi_y + ball_size, fill="red", width=0)
```
---

##手順４：ボールを動かす
```python
# ボールの移動
def move_ball():
    global ball_ichi_x, ball_ichi_y, ball_idou_x, ball_idou_y

    # 左右の壁に当たったかの判定
    if ball_ichi_x + ball_idou_x < 0 or ball_ichi_x + ball_idou_x > 640:
        ball_idou_x *= -1
               ↓
     ball_idou_x = ball_idou_x * -1 ということ

    # 天井か床に当たったかの判定
    if ball_ichi_y + ball_idou_y < 0 or ball_ichi_y + ball_idou_y > 480:
        ball_idou_y *= -1
    
    # ボールの位置を移動
    if 0 <= ball_ichi_x + ball_idou_x <= 640:
        ball_ichi_x = ball_ichi_x + ball_idou_x
    if 0 <= ball_ichi_y + ball_idou_y <= 480:
        ball_ichi_y = ball_ichi_y + ball_idou_y
    
# ゲームの繰り返し処理の指令
def game_loop():
    draw_screen()
    draw_ball()
    move_ball()
    win.after(50, game_loop) # タイマーの設定

# ゲームのメイン処理
game_loop() ←　関数を呼び出す
```
関数の中で設定した変数を関数の外でも使うときに「global」を設定します
タイマーの時間の単位はミリ(1/1000)秒

---

##実行結果
![tk-2020-07-08-11-44-27.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638210/f71be2c0-2a33-216f-8754-0f5907ada977.gif)

---

#Step3：スカッシュゲームに改造しよう
##手順１：変数の設定
```python
# ゲームの初期化
def init_game():
    global is_gameover, ball_ichi_x, ball_ichi_y
    global ball_idou_x, ball_idou_y, ball_size
    global racket_ichi_x, racket_size, point, speed
    is_gameover = False # ゲーム終了の確認(終了でないときはFalse/終了のときはTrue)
    ball_ichi_x = 0
    ball_ichi_y = 250
    ball_idou_x = 15 
    ball_idou_y = -15 
    ball_size = 10
    racket_ichi_x = 0
    racket_size = 100
    point = 0 # ポイント(初期は0点/10点ずつ増える)
    speed = 50 # ゲームのスピードを調整するタイマー(単位はミリ秒)
    win.title("スカッシュゲーム:スタート!") # タイトルバーに表示
```
変数の初期値を「init_game( )」という名前の関数でまとめると、ゲームをまとめて初期化することができます

---

##手順２：ラケットを描く
```python
# ラケットを描く
def draw_racket ():
    cv.create_rectangle(racket_ichi_x, 470, racket_ichi_x + racket_size, 480, fill="yellow")
```
##手順３：ボールを動かす
```python
# ボールの移動
def move_ball():
    global is_gameover, point, ball_ichi_x, ball_ichi_y, ball_idou_x, ball_idou_y
    if is_gameover: return
    
    # 左右の壁に当たったかの判定
    if ball_ichi_x + ball_idou_x < 0 or ball_ichi_x + ball_idou_x > 640:
        ball_idou_x *= -1

    # 天井か床に当たったかの判定
    if ball_ichi_y + ball_idou_y < 0: ←　0の後除く。そうしないとミスの判定ができない。
        ball_idou_y *= -1
    
    # ラケットに当たったかの判定
    if ball_ichi_y + ball_idou_y > 470 and (racket_ichi_x <= (ball_ichi_x + ball_idou_x) <= (racket_ichi_x + racket_size)):
        ball_idou_y *= -1
        if random.randint(0, 1) == 0:
            ball_idou_x *= -1
        point += 10
        win.title("得点＝" + str(point)) # 得点の表示
    
    # ミスしたときの判定
    if ball_ichi_y + ball_idou_y > 480:
        is_gameover = True
        win.title("得点＝" + str(point)) # 得点の表示
    
    # ボールの位置を移動
    if 0 <= ball_ichi_x + ball_idou_x <= 640:
        ball_ichi_x = ball_ichi_x + ball_idou_x
    if 0 <= ball_ichi_y + ball_idou_y <= 480:
        ball_ichi_y = ball_ichi_y + ball_idou_y
```
---

##手順４：マウスの処理
```python
# マウスの動きの処理
def motion(event): # マウスポインタの位置確認
    global racket_ichi_x 
    racket_ichi_x = event.x
    
def click(event): # クリックで再スタート
    if event.num == 1:
        init_game()

# マウスの動きとクリックの確認
win.bind('<Motion>', motion)
win.bind('<Button>', click)
```
「<　>」内の命令は「イベント」といってマウスの動きを調べられます
＜Motion＞ ← イベント名(先頭が大文字)
「motion」← 関数名

- マウスのイベント
    - Button または ButtonPress ← マウスのボタンを押した
    - ButtonRelease ← マウスのボタンを放した
    - Mottion ← マウスの動き(座標の取得)
 
---

# Step4：オプション
##音とメッセージを出そう
```python
    # 左右の壁に当たったかの判定
    if ball_ichi_x + ball_idou_x < 0 or ball_ichi_x + ball_idou_x > 640:
        ball_idou_x *= -1
        winsound.Beep(1300, 50)
    
    # 天井か床に当たったかの判定
    if ball_ichi_y + ball_idou_y < 0:
        ball_idou_y *= -1
        winsound.Beep(1300, 50)

    # ラケットに当たったかの判定
    if ball_ichi_y + ball_idou_y > 470 and (racket_ichi_x <= (ball_ichi_x + ball_idou_x) <= (racket_ichi_x + racket_size)):
        ball_idou_y *= -1
        if random.randint(0, 1) == 0:
            ball_idou_x *= -1
        winsound.Beep(2000, 50)
        mes = random.randint(0, 3)
        if mes == 0:
            message = "うまい!"
        if mes == 1:
            message = "グッド!"
        if mes == 2:
            message = "ナイス!"
        if mes == 3:
            message = "すごい!"
        point += 10
        win.title(message + "得点＝" + str(point)) # 得点とメッセージの表示
        
    # ミスしたときの判定
    if ball_ichi_y + ball_idou_y > 480:
        mes = random.randint(0, 3)
        if mes == 0:
            message = "ミスしたね!"
        if mes == 1:
            message = "ドンマイ!"
        if mes == 2:
            message = "ヘタくそ!"
        if mes == 3:
            message = "あーあ、見てられないね!"
        win.title(message + "得点＝" + str(point)) # 得点とメッセージの表示
        winsound.Beep(240, 800)
        is_gameover = True
```
「winsound.Beep(音の周波数, 音を鳴らす時間)」で音を出すことができます

---

##ボールのスピードを変えよう
```python
ball_idou_x = 15
ball_idou_y = -15
``` 
```python
 speed = 50 # ゲームのスピードを調整するタイマー(単位はミリ秒)
```
これらの値を変えることによってボールのスピードを変えられます

---

#ソースコード
```python
# coding:utf-8

# スカッシュゲーム(壁打ちテニス)
# モジュールのインポート
import tkinter as tk
import random
import winsound

# ウィンドウの作成
win = tk.Tk()
# キャンバスの作成
cv = tk.Canvas(win, width=640, height=480 ,bg="white")
# キャンバスの表示
cv.pack()

# ゲームの初期化
def init_game():
    global is_gameover, ball_ichi_x, ball_ichi_y
    global ball_idou_x, ball_idou_y, ball_size
    global racket_ichi_x, racket_size, point, speed
    is_gameover = False # ゲーム終了の確認(終了でないときはFalse/終了のときはTrue)
    ball_ichi_x = 0
    ball_ichi_y = 250
    ball_idou_x = 15
    ball_idou_y = -15
    ball_size = 10
    racket_ichi_x = 0
    racket_size = 100
    point = 0 # ポイント(初期は0点/10点ずつ増える)
    speed = 50 # ゲームのスピードを調整するタイマー(単位はミリ秒)
    win.title("スカッシュゲーム:スタート!") # タイトルバーに表示

# 画面の描画
def draw_screen():
    # 画面クリア
    cv.delete('all') # 画面を消せという命令 
    # キャンパス(画面)の作成
    cv.create_rectangle(0, 0, 640, 480, fill="white", width=0)

# ボールを描く
def draw_ball():
    cv.create_oval(ball_ichi_x - ball_size, ball_ichi_y - ball_size, ball_ichi_x + ball_size, ball_ichi_y + ball_size, fill="red", width=0)

# ラケットを描く
def draw_racket ():
    cv.create_rectangle(racket_ichi_x, 470, racket_ichi_x + racket_size, 480, fill="yellow")

# ボールの移動
def move_ball():
    global is_gameover, point, ball_ichi_x, ball_ichi_y, ball_idou_x, ball_idou_y
    if is_gameover: return
    
    # 左右の壁に当たったかの判定
    if ball_ichi_x + ball_idou_x < 0 or ball_ichi_x + ball_idou_x > 640:
        ball_idou_x *= -1
        winsound.Beep(1300, 50)
    
    # 天井か床に当たったかの判定
    if ball_ichi_y + ball_idou_y < 0:
        ball_idou_y *= -1
        winsound.Beep(1300, 50)

    # ラケットに当たったかの判定
    if ball_ichi_y + ball_idou_y > 470 and (racket_ichi_x <= (ball_ichi_x + ball_idou_x) <= (racket_ichi_x + racket_size)):
        ball_idou_y *= -1
        if random.randint(0, 1) == 0:
            ball_idou_x *= -1
        winsound.Beep(2000, 50)
        mes = random.randint(0, 3)
        if mes == 0:
            message = "うまい!"
        if mes == 1:
            message = "グッド!"
        if mes == 2:
            message = "ナイス!"
        if mes == 3:
            message = "すごい!"
        point += 10
        win.title(message + "得点＝" + str(point)) # 得点とメッセージの表示
        
    # ミスしたときの判定
    if ball_ichi_y + ball_idou_y > 480:
        mes = random.randint(0, 3)
        if mes == 0:
            message = "ミスしたね!"
        if mes == 1:
            message = "ドンマイ!"
        if mes == 2:
            message = "ヘタくそ!"
        if mes == 3:
            message = "あーあ、見てられないね!"
        win.title(message + "得点＝" + str(point)) # 得点とメッセージの表示
        winsound.Beep(240, 800)
        is_gameover = True

    # ボールの位置を移動
    if 0 <= ball_ichi_x + ball_idou_x <= 640:
        ball_ichi_x = ball_ichi_x + ball_idou_x
    if 0 <= ball_ichi_y + ball_idou_y <= 480:
        ball_ichi_y = ball_ichi_y + ball_idou_y

# マウスの動きの処理
def motion(event): # マウスポインタの位置確認
    global racket_ichi_x 
    racket_ichi_x = event.x
    
def click(event): # クリックで再スタート
    if event.num == 1:
        init_game()

# マウスの動きとクリックの確認
win.bind('<Motion>', motion)
win.bind('<Button>', click)

# ゲームの繰り返し処理の指令
def game_loop():
    draw_screen()
    draw_ball()
    draw_racket()
    move_ball()
    win.after(speed, game_loop) # タイマーの設定

# ゲームのメイン処理
init_game()
game_loop() 
# ウィンドウの表示
win.mainloop()

```
---

##実行結果
![ミスしたね_得点＝10-2020-07-08-17-30-12.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638210/3db4ee1e-2767-864f-71c5-f93f38b7f92b.gif)

---

![スカッシュゲーム (3).PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638210/03ee39f0-1793-39d5-1d3d-58787dc3d701.png)

---

# 気づいたこと

- 実行したウィンドウを消去しないと次のウィンドウを表示できないこと
⇒これには結構焦りました。実行が上手くいかないので、Pythonに問題があるのかと思い、アンインストールしたりしてしまいました。

- ウィンドウやキャンバスの作成などの単純なプログラムでも多様な書き方があるということ
⇒省略できるものや書き方の異なるものが多くて驚きました。

---

#疑問
```python
import tkinter as tk 
```
を参考文献[1]では、

```python
from tkinter import *
```
と書いてあったので、これで実行してみると実行できるのですが、fromの下に黄色い波線が引かれ、問題が100となります。これはなぜですか？

# 参考文献
[1] すがやみつる「ゲームセンターあらしと学ぶプログラミング入門まんが版こんにちはPython」(2020) 日経BP
[2] 大澤文孝「いちばんやさしいPython入門教室」(2019) 株式会社ソーテック社