2025-2I プログラミング1 第06回 講義資料

2025年05月15日(木)5・6時限

1 準備と講義概要

1.1 「課題02 タートルグラフィックス🐢」の作品集

2 課題03

以下の「演習1」から「演習4」について、今回の講義で作成したノートブック PG1-第06回講義.ipynb のなかで取組み、共有設定の上、Teams の指定位置に提出してください。.ipynb ファイルを提出するわけではないので注意してください。

提出に際しては、必ず、ノートブックの 先頭 に以下の内容のテキストセルを作成し、それにつづけて各演習のコードセルを配置してください。各コードセルの先頭には %reset -f を配置してください。

▼ テキストセルに記述する内容

# **プログラミング1 課題03**

- 氏名: 高専太郎 
- 出席番号: 99
- この課題の取り組み時間: **180分**

必ず、課題に要した時間を明記してください。

3 演習1

ソフトウェアエンジニアを目指すW君(高専2年生)は、プログラミングのスキルアップのために友人と共にゲームづくりに取り組んでいる。

いま、見下ろし型のフィールド画面を持ったRPGを作成している。いま、ゲームアイテムとして、使用時に…

💀「西に125、南に92進めば幽霊船にであえるだろう」

…のように、自キャラの現在位置から幽霊船までの距離を表示するアイテムを実装する必要が生じた。

3.1 仕様

3.2 実行例

💀「東に10、北に5進めば幽霊船にであえるだろう」
💀「西に3、南に3進めば幽霊船にであえるだろう」
💀「南に20進めば幽霊船にであえるだろう」
💀「西に222進めば幽霊船にであえるだろう」
💀「幽霊船にであえたようじゃな」

3.3 ヒントと注意点

%reset -f
x1, y1 = 20, 10
x2, y2 = 30, 5

assert 0 <= x1 <= 511 and 0 <= y1 <= 511
assert 0 <= x2 <= 511 and 0 <= y2 <= 511
print(f'キャラ位置 -> X:{x1:3}  Y:{y1:3}')
print(f'幽霊船位置 -> X:{x2:3}  Y:{y2:3}')
print()

# ▼▼▼ 基本的な書き換え範囲(ここから)

中級者向け

上記では、参考として「基本的な書き換え範囲」を示していますが、それにとらわれずに、関数、クラス、例外処理、条件式(三項演算子)などを利用して記述してください。テキストセルに明記すれば、仕様や設定を拡張変更しても問題ありません (以降の「演習2」~「演習4」についても同様)。

4 演習2

ソフトウェアエンジニアを目指すW君(高専2年生)は、プログラミングのスキルアップのために友人とゲームづくりに取り組んでいる。

いま、見下ろし型の2Dゲームを作成している。そのなかで「キャラの現在位置」、「キャラの移動目標の位置」が与えられたとき、目標に向かって 等速(=1単位距離/秒)で直線移動 したときの \(t\) 秒後のキャラ位置 を求める必要が生じた。

4.1 仕様

4.2 実行例

(0.0, 0.0) から (10.0, 10.0) を目標に、
速度1で移動するキャラの 8.0 秒後の位置は (5.7, 5.7)
(-7.0, 2.0) から (5.0, -8.0) を目標に、
速度1で移動するキャラの 2.0 秒後の位置は (-5.5, 0.7)
(2.0, 10.0) から (-3.0, 11.0) を目標に、
速度1で移動するキャラの 8.0 秒後の位置は (-3.0, 11.0)
(4.0, 4.0) から (4.0, 4.0) を目標に、
速度1で移動するキャラの 2.3 秒後の位置は (4.0, 4.0)

4.3 ヒントと注意点

%reset -f
import math

# キャラの現在位置(座標)
x1, y1 = 0, 0

# キャラの移動目標の位置(座標)
x2, y2 = 10, 10

# t秒数の位置を計算
t= 8

# ▼▼▼ 基本的な書き換え範囲(ここから)

x3, y3 = 5.7, 5.7

# ▲▲▲ 基本的な書き換え範囲(ここまで)

# 出力
print(f'({x1:.1f}, {y1:.1f}) から ({x2:.1f}, {y2:.1f}) を目標に、')
print(f'速度1で移動するキャラの {t:.1f} 秒後の位置は ({x3:.1f}, {y3:.1f})')

5 演習3

ソフトウェアエンジニアを目指すW君(高専2年生)は、プログラミングのスキルアップのために友人とゲームづくりに取り組んでいる。

いま、2Dゲームを作成しているなかで、任意の2点 (点1と点2) の座標を与えたときに、それを基に次のような「H」型の図形を描画するための「座標計算」が必要となった。

例: 点1として \((1,-4)\)、点2として \((-5,-1.5)\) を与えたときに画面に描画したい「H」の図形)

img

5.1 仕様

img

期待する出力の例

点1の座標は (1.00,-4.00)
  これを中点とする長さ1の垂直線の両端座標は (1.19,-3.54) と (0.81,-4.46)
点2の座標は (-5.00,-1.50)
  これを中点とする長さ1の垂直線の両端座標は (-4.81,-1.04) と (-5.19,-1.96)

5.2 実行例1

点1の座標は (3.00,4.00)
  これを中点とする長さ1の垂直線の両端座標は (2.55,4.22) と (3.45,3.78)
点2の座標は (1.00,0.00)
  これを中点とする長さ1の垂直線の両端座標は (0.55,0.22) と (1.45,-0.22)

(参考) 図の描画は、提供したコードをそのまま利用してください。

img

5.3 実行例2

点1の座標は (2.00,3.00)
  これを中点とする長さ1の垂直線の両端座標は (2.00,3.50) と (2.00,2.50)
点2の座標は (-5.00,3.00)
  これを中点とする長さ1の垂直線の両端座標は (-5.00,3.50) と (-5.00,2.50)

(参考)

img

5.4 ヒントと注意点

%reset -f
import math
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patheffects as pe
from matplotlib_inline.backend_inline import set_matplotlib_formats
set_matplotlib_formats('svg')
plt.rcParams['mathtext.fontset'] = 'cm'

# ▼▼▼ 基本的な書き換え範囲(ここから)

# 任意の2点(点1と点2)の座標を与える
x1, y1 =   1,   -4
x2, y2 =  -5, -1.5

# 点1と点2が同一座標ではないことを簡易チェック
assert x1 != x2 or y1 != y2, '点1と点2は異なる座標である必要があります(仕様違反)'

# 次の 4点(点1a、点1b、点2a、点2b)の座標をプログラムにより求める
x1a, y1a = 1.19,-3.54
x1b, y1b = 0.81,-4.46
x2a, y2a = -4.81,-1.04
x2b, y2b = -5.19,-1.96

# ▲▲▲ 基本的な書き換え範囲(ここまで)

print(f'点1の座標は ({x1:.2f},{y1:.2f})')
print(f'  これを中点とする長さ1の垂直線の両端座標は ({x1a:.2f},{y1a:.2f}) と ({x1b:.2f},{y1b:.2f})')
print(f'点2の座標は ({x2:.2f},{y2:.2f})')
print(f'  これを中点とする長さ1の垂直線の両端座標は ({x2a:.2f},{y2a:.2f}) と ({x2b:.2f},{y2b:.2f})')


# 以下、適切な座標計算ができているかを確認するための処理です。
# 現時点では深く内容を理解する必要はありません。

def lower_int(x):
  return int(x) - (0 if x == int(x) else 1)

def upper_int(x):
    return int(x) + (0 if x == int(x) else 1)

def render_graph():

  fig, axes=plt.subplots(ncols=2, figsize=(8,4), facecolor='white')
  for i,ax in enumerate(axes) :

    ax.axvline(0,c='black',lw=0.5)
    ax.axhline(0,c='black',lw=0.5)

    ax.plot([x1,x2],[y1,y2],c='tab:blue')

    if i==1 :
      ax.plot([x1a,x1b],[y1a,y1b],c='tab:red')
      ax.plot([x2a,x2b],[y2a,y2b],c='tab:red')

    ax.set_aspect('equal')
    margin = 1
    ax.set_xlim( lower_int(min(x1a,x1b,x2a,x2b)-margin),
                upper_int(max(x1a,x1b,x2a,x2b)+margin ))
    ax.set_ylim( lower_int(min(y1a,y1b,y2a,y2b)-margin),
                upper_int(max(y1a,y1b,y2a,y2b)+margin ))
    ax.set_axisbelow(True)
    ax.set_xticks(np.arange(ax.get_xlim()[0],ax.get_xlim()[1]+1))
    ax.set_yticks(np.arange(ax.get_ylim()[0],ax.get_ylim()[1]+1))

    ax.tick_params(direction='inout')
    ax.grid()
  plt.tight_layout()
  plt.show()

render_graph()

中級者向け

図は Matplotlib というライブラリを利用して描画しています。本授業では第11回講義以降で正式に扱います。

この演習で要求されている処理は、NumPy の ndarray という行列 (ベクトル) を扱うオブジェクトを使用することで、極めてスマートに実装できます。本授業では、NumPy は第17回講義以降で正式に取り上げる予定です。意欲のある学生は、ぜひ NumPy を利用して本演習に取り組んでください。

6 演習4

ソフトウェアエンジニアを目指すW君(高専2年生)は、プログラミングのスキルアップのために友人と共にゲームづくりに取り組んでいる。

いま、見下ろし型の2Dシューティングゲームを作成している。自キャラは、自分の位置から扇形の範囲攻撃ができる武器を装備しており、その攻撃に対する敵キャラの当たり判定をして「Hit!」あるいは「Miss.」の文字列を出力したい。

6.0.1 例1:

▼ 入力 ▼

# 自キャラ位置(下図の青点)
x1 = 5.0
y1 = -10.0

# 敵キャラ位置(下図の赤点)
x2 = 15
y2 = 10

# 射程距離、攻撃方向、拡がり角
bw_range = 15
bw_direction = 45
bw_spread_angle = 70

▼ 期待する出力 ▼

Miss.

(参考)

img

6.1 仕様

6.2 実行例

例2:「Hit!

img

例3:「Hit!

img

例4:「Miss.

img

6.3 ヒントと注意点

%reset -f
import math
import japanize_matplotlib
import matplotlib.pyplot as plt
import matplotlib.patheffects as pe
from matplotlib import patches
from matplotlib_inline.backend_inline import set_matplotlib_formats
set_matplotlib_formats('svg')
plt.rcParams['mathtext.fontset'] = 'cm'

# ▼▼▼ 基本的な書き換え範囲(ここから)

# 自キャラ位置
x1 = 5.5
y1 = -10.0

# 敵キャラ位置
x2 = 15.2
y2 = 10.6

# 射程距離、攻撃方向、拡がり角
bw_range = 15
bw_direction = 30
bw_spread_angle = 70

# 判定処理
print('Miss.')

# ▲▲▲ 基本的な書き換え範囲(ここまで)

assert -128 <= x1 <= 127  
assert -128 <= y1 <= 127  
assert -128 <= x2 <= 127  
assert -128 <= y2 <= 127  
assert 0.5 <= bw_range <= 50
assert -180 <= bw_direction <= 180
assert 5 <= bw_spread_angle <= 270

# 以下、適切に「当たり判定」ができているかを確認するための処理です。
# 現時点では深く理解する必要はありません

def render_graph():

  xp = x1+math.cos(math.radians(-bw_direction+90))*bw_range
  yp = y1+math.sin(math.radians(-bw_direction+90))*bw_range

  fig,ax=plt.subplots(figsize=(6,6), facecolor='white', dpi=96)
  ax.scatter(x1,y1,c='tab:blue',s=80)

  ax.plot([x1,xp],[y1,yp],ls='--',lw=0.75)
  ax.set_aspect('equal', adjustable='box')
  margin = 10
  ax.set_xlim( min(x1,x2)-margin, max(x1,x2)+margin )
  ax.set_ylim( min(y1,y2)-margin, max(y1,y2)+margin )

  sector = patches.Wedge((x1,y1),bw_range,
                        -bw_direction - bw_spread_angle/2 + 90,
                        -bw_direction + bw_spread_angle/2 + 90,
                        width=bw_range, alpha=0.4)
  ax.add_patch(sector)

  ax.scatter(x2,y2,c='tab:red')
  t = ax.text(0.02, 0.98, f'射程距離 : {bw_range}\n攻撃方向 : {bw_direction}°\n拡がり角 : {bw_spread_angle}°',
          va='top', ha='left', transform=ax.transAxes)
  t.set_path_effects([pe.Stroke(linewidth=4,foreground='white'),pe.Normal()])

  ax.grid()
  ax.set_axisbelow(True)
  ax.tick_params(direction='inout')
  plt.show()

render_graph()