1 準備と講義概要
小テスト❷ を実施します。鉛筆と消しゴムを準備してください。
GoogleColab にログインしておいてください。
PG1-第06回講義.ipynbという名前で「GoogleColabノートブック」を作成しておいてください。
今回の講義では、ここまでに学んだ「変数」「条件分岐構文」「繰返し構文」「f文字列」などを総合した演習課題に取り組んでもらいます。自身の定着度合いを測る指標としてください。
- 学習内容が十分に定着していないと感じた場合は、学習方法を見直してください。
14:20 になったらHR教室に戻り、その後「歯科検診」に向かってください。この部屋には荷物を置いていかないようにしてください。
1.1 「課題02 タートルグラフィックス🐢」の作品集
- 2025年度 (New!)
- 2024年度
- 2023年度
2 課題03
以下の「演習1」から「演習4」について、今回の講義で作成したノートブック
PG1-第06回講義.ipynb
のなかで取組み、共有設定の上、Teams
の指定位置に提出してください。.ipynb
ファイルを提出するわけではないので注意してください。
提出に際しては、必ず、ノートブックの
先頭
に以下の内容のテキストセルを作成し、それにつづけて各演習のコードセルを配置してください。各コードセルの先頭には
%reset -f を配置してください。
▼ テキストセルに記述する内容
# **プログラミング1 課題03**
- 氏名: 高専太郎
- 出席番号: 99
- この課題の取り組み時間: **180分**
必ず、課題に要した時間を明記してください。
- 評価 : 標準
(=演習1~4の全てに取り組んでいる)
を「8点」として 10点満点
で評価します。
- 課題の採点後は、修正版の再提出・追提出があっても原則として再評価はしません。
- Colabノートブックの共有設定は第03回講義で解説済みです。
- 適切に共有されていない場合 (提出された共有リンクからプログラムを確認できない場合) 0点で評価します。再評価はしません。
- 取り組み時間の目安 :
180分以上
- 本課題は
生成AIを直接的に利用すること(AIにコードを生成させること)を想定しない演習
です。
- 自分の理解や解釈が適切かを生成AIに確認してもらう → OK👍 (これは問題なし、学びの効果大)
- 自分の疑問を生成AIに尋ねる → OK👍 (これも問題なし、学びの効果大)
- 直接的にプログラムコードを書いてもらう(あるいは修正してもらう)
→ NG🙅 (学習フェーズではNG。今回は原則として使用しない。)
- 本課題は
生成AIを直接的に利用すること(AIにコードを生成させること)を想定しない演習
です。
- 期限 :
2025年5月28日(水) 23時59分
- 提出後も、Teamsで採点がフィードバックされるまではノートブックの内容を変更して問題ありません。
3 演習1
ソフトウェアエンジニアを目指すW君(高専2年生)は、プログラミングのスキルアップのために友人と共にゲームづくりに取り組んでいる。
いま、見下ろし型のフィールド画面を持ったRPGを作成している。いま、ゲームアイテムとして、使用時に…
💀「西に125、南に92進めば幽霊船にであえるだろう」
…のように、自キャラの現在位置から幽霊船までの距離を表示するアイテムを実装する必要が生じた。
3.1 仕様
- ゲームのフィードとして、\(0 \le X \le 511\)、\(0 \le Y \le 511\) の大きさのXYの2次元平面を考える。
- X軸は右方向を正として、その方位を東とする。
- \(X=511\)から東に1だけ動くと \(X=0\) に移動し、\(X=0\)から西に1だけ動くと \(X=511\) に移動する。
- Y軸は下方向を正とする、その方位を南とする。
- \(Y=511\)から南に1だけ動くと \(Y=0\) に移動し、\(Y=0\)から北に1だけ動くと \(Y=511\) に移動する。
- 自キャラの座標は、変数
x1,y1に格納されているものとする。 - 幽霊船の座標は
x2,y2に格納されているものとする。 - 座標と移動量は、いずれも整数値のみを扱うものとする。
- 東西のどちらからでも移動量が同じ場合は、東移動を優先してメッセージを出力する。
- 南北のどちらからでも移動量が同じ場合は、南移動を優先してメッセージを出力する。
3.2 実行例
- 自キャラの座標が \((20,10)\)、幽霊船の座標が \((30,5)\) のとき
💀「東に10、北に5進めば幽霊船にであえるだろう」
- 自キャラの座標が \((2,510)\)、幽霊船の座標が \((511,1)\) のとき
💀「西に3、南に3進めば幽霊船にであえるだろう」
- 自キャラの座標が \((100,40)\)、幽霊船の座標が \((100,60)\) のとき
💀「南に20進めば幽霊船にであえるだろう」
- 自キャラの座標が \((10,150)\)、幽霊船の座標が \((300,150)\) のとき
💀「西に222進めば幽霊船にであえるだろう」
- 自キャラの座標と幽霊船の座標が同じときに使用すれば、以下を表示する。
💀「幽霊船にであえたようじゃな」
3.3 ヒントと注意点
- 南北のみに移動するケース、東西のみに移動するケースにおいて句点「、」の出力に注意してください。
- 基本的なプログラムなので、Geminiなどの生成AIは、十分に悩んで手を尽くしてから使用してください。最初から生成AIに頼ると、演習を通じて得られる学びと経験は半分からゼロになってしまいます。
- 自分が理解していないコードは、絶対に解答しないでください。以下の「演習2」から「演習4」についても同じです。
- まずは、以下のコードをつづけて記述してみてください。
%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 仕様
- キャラの現在位置 (座標) を (
x1、y1) とする。 - キャラの移動目標の位置 (座標) を
(
x2、y2) とする。 t秒後のキャラ位置 (座標) を (x3、y3) とする。- 座標
xyは任意の実数、時間tは非負の実数とする。 - 移動速度は 1単位距離/秒 とする。
- 2秒間に2単位距離、8秒間に8単位距離だけ移動する。
- キャラが目標位置に到達したら、その位置に留まるものとする。
- キャラの位置と目標位置が同じ場合であっても、適切に処理できること。
ZeroDivisionErrorが発生しないようにすること。
4.2 実行例
- キャラの座標が \((0,0)\)、移動目標の座標が \((10,10)\) 、時間 \(t\) が \(8\) であれば…
(0.0, 0.0) から (10.0, 10.0) を目標に、
速度1で移動するキャラの 8.0 秒後の位置は (5.7, 5.7)
- キャラの座標が \((-7,2)\)、移動目標の座標が \((5,-8)\) 、時間 \(t\) が \(2\) であれば…
(-7.0, 2.0) から (5.0, -8.0) を目標に、
速度1で移動するキャラの 2.0 秒後の位置は (-5.5, 0.7)
- キャラの座標が \((2,10)\)、移動目標の座標が \((-3,11)\) 、時間 \(t\) が \(8\) であれば…
(2.0, 10.0) から (-3.0, 11.0) を目標に、
速度1で移動するキャラの 8.0 秒後の位置は (-3.0, 11.0)
- キャラの座標が \((4,4)\)、移動目標の座標が \((4,4)\) 、時間 \(t\) が \(2.3\) であれば…
(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」の図形)
5.1 仕様
- 任意の2点を与えたとき、それを両端とする線分 (図の青線)
を描き、各端点から長さが \(1\)
の垂直線(図内の2本の赤線)を描くための 4点の座標
(
x1a,y1a)、(x1b,y1b)、(x2a,y2a)、(x2b,y2b) を計算する処理を実装したい。- 任意の2点を、点1 (
x1,y1) および点2 (x2,y2) とする。 - 点1(
x1,y1)を中点として長さ \(1\) の垂直線の端点を点1a(x1a,y1a)と点1b(x1b,y1b)とする。 - 点2(
x2,y2)を中点として長さ \(1\) の垂直線の端点を点2a(x2a,y2a)と点2b(x2b,y2b)とする。
- 任意の2点を、点1 (
- 点1 と 点2 には、異なる座標が与えられることが保証されているものとする。
期待する出力の例
点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
(3,4)と(1,0)を与えたとき
点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)
(参考) 図の描画は、提供したコードをそのまま利用してください。
5.3 実行例2
(2,3)と(-5,3)を与えたとき…
点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)
(参考)
5.4 ヒントと注意点
- 現在、「線形代数」で学んでいるベクトルの知識を十分に利用して実装してください。
- 点1から点2に向かうベクトルを考え、それに 垂直な単位ベクトル を求めて利用することがポイントです (線形代数の教科書 p.31)。
- まずは、次のコードを GoogleColab に貼り付けて動作させることからはじめてください。
%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.」の文字列を出力したい。
- XYの2次元平面を考える。
- X軸は右方向を正とする。
- Y軸は上方向を正とする。
- 方向(方位)は、一般的な2Dゲームと同様に画面の上方向を北としてそれを \(0^\circ\)、東を \(90^\circ\)、西を\(-90^\circ\)、南を \(-180^\circ\) or \(180^\circ\) とする。
- 武器には「射程距離」「攻撃方向」「拡がり角」の3つのパラメータが設定される。
- 扇形の攻撃範囲内に敵キャラが存在する場合は「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.
(参考)
6.1 仕様
- キャラの位置 (座標) を (
x1,y1) とする。 - 敵キャラの位置 (座標) を (
x2,y2) とする。 - XとYの座標は \(-128\) 以上 \(127\) 以下の実数で与えられる。
- 射程距離 (
bw_range) は \(0.5\) 以上 \(50\) 以下の実数で与えられる。 - 攻撃方向 (
bw_direction) は \(-180\) 以上 \(180\) 以下の度数法(deg)の実数で与えられる。 - 拡がり角 (
bw_spread_angle) は \(5\) 以上 \(270\) 以下の度数法(deg)の実数で与えられる。
6.2 実行例
例2:「Hit!」
例3:「Hit!」
例4:「Miss.」
6.3 ヒントと注意点
紙面上で問題を理解したうえで、コーディングに取り組んでください。
扇形の攻撃範囲内に敵が存在するか否かは「自キャラと敵キャラとの距離」、「自キャラから敵キャラに向かうベクトルの角度」「扇形の開始角度と終了角度」の情報を使って判定可能です (紙面上で考えてみてください)。
print関数を使って変数の内容を出力し、それが紙面上での手計算と一致するかを確認しながら進めることをお勧めします (そのようにすることで、結果的には短時間で開発できます)。敵方向が \(-170^\circ\) で、攻撃方向が \(175^\circ\)、拡がり角が \(60^\circ\) のようなケースに注意してください。
まずは、次のコードを GoogleColab に貼り付けて動作させることからはじめてください。
- あらかじめ独立したコードセルで
!pip install japanize_matplotlibを実行しておいてください。
- あらかじめ独立したコードセルで
%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()