動作画面
説明:とりあえず動けば良いということで
- ボールは、対角斜め(45度・135度・225度・315度)のみ動きます。
- ブロックを全部消しても、ゲームクリアーにならないです。
- ゲームオーバーも無し。
[version 0.9]
- 白棒(バー)は、[a]を押すと左方向、[s]を押すと右方向に動きます。
- スタート画面はないです。コンパイルすると、いきなりゲーム開始。
- キー入力はライブラリの「mattn/go-tty」を使用した。
- 「キー入力」と「画面表記」の非同期はゴルーチンを使用した。
[version 2.0]
- 白棒(バー)は、[←]を押すと左方向、[→]を押すと右方向に動きます。
- スタート画面を追加しました。
- キー入力はWin32の「GetAsyncKeyState」を使用した。
- Win32なので「キー入力」と「画面表記」の非同期は不要となり、ゴルーチンを削除した。
車輪の再発明
↓先人が2016年に今回と同じテーマを投稿しており、車輪(タイヤ)の再発名ですが、私個人として「色」と「日本語や絵文字」がGo言語の定番ライブラリ「termbox-go」だと思い通りに使用できなかったので改めて挑戦しました。
創意工夫した点
- 背景やブロックはANSIエスケープシーケンスを使って着色🟥🟨🟦しています。
- ボールは絵文字の「⚪」です。
- キーボード入力イベントは、Go言語のライブラリ「github.com/mattn/go-tty」を使ってます。
- キーボード入力イベントで検索すると、Go言語の定番TUI(Text User Interface) ライブラリ「termbox-go」がヒットしますが、上記で記載の通り、色や、日本語・絵文字をうまく表示してくれなかったので使用しませんでした。
苦労した点
[version 0.9]
- 画面表示はfor文の永久ループ+ゴルーチンを使ってますが、キーイベントを途中に入れ込むことが出来ず苦労しました。
- 最終的にミリ秒の等間隔で、キーイベントを実行させてむりやり入れ込んでます。
- もっとエレガントな方法があれば、良かったんですが限界でした。
[version 2.0]
- Win32の「GetAsyncKeyState」を使ったら多少エレガントになりました。
- そもそも「Win32」って何?、「GetAsyncKeyState」って何?という話になりますが、長くなるので割愛。
パソコン環境
- Windows10
- WindowsTerminal 1.19.2831
- go 1.21.3
参考にしたサイト(ブロック崩し)
参考にしたサイト(ANSIエスケープシーケンス)
参考にしたサイト(キー入力)
プログラム本文[Version0.9]
157行
約4100文字
package main
import (
"fmt"
"log"
"time"
"github.com/mattn/go-tty"
)
type Bar struct {
Left int
Top int
W int
H int
}
type Ball struct {
Left float64
Top float64
Speed_x float64
Speed_y float64
}
type Block struct {
Left int
Top int
Color string
Exists int
}
const tick_time_str time.Duration = 200
const tick_time_tty time.Duration = 10
const white string = "\033[48;2;255;255;255m \033[0m" //⬜
const red string = "\033[48;2;255;0;0m \033[0m" //🟥
const blue string = "\033[48;2;0;0;255m \033[0m" //🟦
const yellow string = "\033[48;2;255;255;0m \033[0m" //🟨
func main() {
fmt.Println("\033[2J") //画面全体を消去
ball := Ball{
Left: 15, Top: 5, Speed_x: 1, Speed_y: 1,
}
bar := Bar{
Left: 7, Top: 27, W: 5, H: 1,
}
block_data := []Block{
{2, 2, red, 1}, {4, 2, red, 1}, {6, 2, red, 1}, {8, 2, red, 1}, {10, 2, red, 1}, {12, 2, red, 1}, {14, 2, red, 1},
{3, 3, blue, 1}, {4, 3, blue, 1}, {5, 3, blue, 1}, {6, 3, blue, 1}, {8, 3, blue, 1}, {9, 3, blue, 1}, {10, 3, blue, 1}, {11, 3, blue, 1}, {13, 3, blue, 1},
{2, 4, yellow, 1}, {3, 4, yellow, 1}, {4, 4, yellow, 1}, {5, 4, yellow, 1}, {6, 4, yellow, 1}, {7, 4, yellow, 1}, {8, 4, yellow, 1}, {9, 4, yellow, 1}, {10, 4, yellow, 1},
}
chan_Print_str := make(chan string)
go go_Print_str(chan_Print_str)
defer close(chan_Print_str)
tty, err := tty.Open()
if err != nil {
log.Fatal(err)
}
defer tty.Close()
go func() {
for range time.Tick(tick_time_str * time.Millisecond) {
chan_Print_str <- creat_str(ball, bar, block_data)
ball, block_data = ballHitCheck(ball, bar, block_data)
}
}()
for range time.Tick(tick_time_tty * time.Millisecond) {
r, err := tty.ReadRune()
if err != nil {
log.Fatal(err)
}
//aが左に、sが右に
if string(r) == "s" { //r=115
bar.Left++
} else if string(r) == "a" { //r=97
bar.Left--
}
} //for range time.Tick(tick_time_tty * time.Millisecond) {
}
func go_Print_str(chan_Print_str chan string) {
fmt.Println("\033[2J\033[0;0H")
for {
//ゲーム画面を描画する
fmt.Println(<-chan_Print_str)
time.Sleep(tick_time_str * time.Millisecond)
}
}
func creat_str(ball Ball, bar Bar, block []Block) string {
str := "\033[0;0H" //カーソルを原点に戻す
var pixel [22][32]string
for pixel_top := 0; pixel_top < 32; pixel_top++ {
for pixel_left := 0; pixel_left < 22; pixel_left++ {
if float64(pixel_left) <= ball.Left && ball.Left < float64(pixel_left+1) && float64(pixel_top) <= ball.Top && ball.Top < float64(pixel_top+1) {
pixel[pixel_left][pixel_top] = "\033[48;2;100;100;100m⚪\033[0m"
} else if bar.Left < pixel_left && bar.Left >= pixel_left-bar.W && bar.Top < pixel_top && bar.Top >= pixel_top-bar.H {
pixel[pixel_left][pixel_top] = white
} else {
pixel[pixel_left][pixel_top] = "\033[48;2;100;100;100m \033[0m"
}
for i, _ := range block {
if pixel_top == block[i].Top && pixel_left == block[i].Left && block[i].Exists == 1 {
pixel[pixel_left][pixel_top] = block[i].Color
}
}
}
}
for pixel_top := 0; pixel_top < 32; pixel_top++ {
for pixel_left := 0; pixel_left < 22; pixel_left++ {
str += pixel[pixel_left][pixel_top]
}
str += "\n"
}
return str
}
func ballHitCheck(ball Ball, bar Bar, block []Block) (Ball, []Block) {
ball.Left += ball.Speed_x
ball.Top += ball.Speed_y
//左右の壁と接触判定
if ball.Left < 1 || ball.Left > 20 {
ball.Speed_x = -ball.Speed_x
}
//上下の壁と接触判定
if ball.Top < 1 || ball.Top > 30 {
ball.Speed_y = -ball.Speed_y
}
//棒とボールの接触判定
if ball.Top > float64(bar.Top) && ball.Top <= float64(bar.Top+bar.H) && ball.Left > float64(bar.Left) && ball.Left <= float64(bar.Left+bar.W) {
ball.Speed_y = -ball.Speed_y
}
//ボールとブロックの接触判定
for i, _ := range block {
if ball.Top > float64(block[i].Top) && ball.Top <= float64(block[i].Top+1) && ball.Left > float64(block[i].Left) && ball.Left <= float64(block[i].Left+1) {
ball.Speed_y = -ball.Speed_y
block[i].Exists = 0
}
}
return ball, block
}
プログラム本文[Version2.0]
160行
約4700文字
package main
import (
"fmt"
"time"
"golang.org/x/sys/windows"
)
type Bar struct {
Left int
Top int
W int
H int
}
type Ball struct {
Left float64
Top float64
Speed_x float64
Speed_y float64
}
type Block struct {
Left int
Top int
Color string
Exists int
}
var game_start = 0
const game_time = 100
const white string = "\033[48;2;255;255;255m \033[0m" //⬜
const red string = "\033[48;2;255;0;0m \033[0m" //🟥
const blue string = "\033[48;2;0;0;255m \033[0m" //🟦
const yellow string = "\033[48;2;255;255;0m \033[0m" //🟨
const start_screen string = `*--------------------*
|Go言語でブロック崩し|
*--------------------*
*開始するには「スペース」ボタンを押してください*`
var (
moduser32 = windows.NewLazyDLL("user32.dll")
procGetAsyncKeyState = moduser32.NewProc("GetAsyncKeyState")
)
func main() {
//fmt.Println("\033[2J") //画面全体を消去
fmt.Println("\033[2J\033[0;0H")
fmt.Println(start_screen) //画面全体を消去
ball := Ball{
Left: 15, Top: 5, Speed_x: 1, Speed_y: 1,
}
bar := Bar{
Left: 7, Top: 27, W: 5, H: 1,
}
block_data := []Block{
{2, 2, red, 1}, {4, 2, red, 1}, {6, 2, red, 1}, {8, 2, red, 1}, {10, 2, red, 1}, {12, 2, red, 1}, {14, 2, red, 1}, {16, 2, red, 1}, {18, 2, red, 1},
{3, 3, blue, 1}, {4, 3, blue, 1}, {5, 3, blue, 1}, {6, 3, blue, 1}, {8, 3, blue, 1}, {9, 3, blue, 1}, {10, 3, blue, 1}, {11, 3, blue, 1}, {13, 3, blue, 1}, {14, 3, blue, 1}, {15, 3, blue, 1},
{2, 4, yellow, 1}, {3, 4, yellow, 1}, {4, 4, yellow, 1}, {5, 4, yellow, 1}, {6, 4, yellow, 1}, {7, 4, yellow, 1}, {8, 4, yellow, 1}, {9, 4, yellow, 1}, {10, 4, yellow, 1}, {11, 4, yellow, 1}, {12, 4, yellow, 1}, {13, 4, yellow, 1}, {14, 4, yellow, 1},
}
for {
asynch32, _, _ := procGetAsyncKeyState.Call(uintptr(32)) //space
if asynch32 != 0 {
game_start = 1
break
}
}
if game_start == 1 {
fmt.Println("\033[2J\033[0;0H")
for {
//ゲーム画面を描画する
ball, block_data = ballHitCheck(ball, bar, block_data)
asynch37, _, _ := procGetAsyncKeyState.Call(uintptr(37)) //←
if asynch37 != 0 {
bar.Left--
}
asynch39, _, _ := procGetAsyncKeyState.Call(uintptr(39)) //→
if asynch39 != 0 {
bar.Left++
}
fmt.Println("asynch37", asynch37, asynch37&0x1)
fmt.Println("asynch39", asynch39, asynch39&0x1)
fmt.Println(creat_str(ball, bar, block_data))
time.Sleep(game_time * time.Millisecond)
}
}
}
func creat_str(ball Ball, bar Bar, block []Block) string {
str := "\033[0;0H" //カーソルを原点に戻す
var pixel [22][32]string
for pixel_top := 0; pixel_top < 32; pixel_top++ {
//玉と空間の位置情報をデータに格納
for pixel_left := 0; pixel_left < 22; pixel_left++ {
if float64(pixel_left) <= ball.Left && ball.Left < float64(pixel_left+1) && float64(pixel_top) <= ball.Top && ball.Top < float64(pixel_top+1) {
pixel[pixel_left][pixel_top] = "\033[48;2;100;100;100m⚪\033[0m"
} else if bar.Left < pixel_left && bar.Left >= pixel_left-bar.W && bar.Top < pixel_top && bar.Top >= pixel_top-bar.H {
pixel[pixel_left][pixel_top] = white
} else {
pixel[pixel_left][pixel_top] = "\033[48;2;100;100;100m \033[0m"
}
//ブロックの位置情報をデータ格納
for i, _ := range block {
if pixel_top == block[i].Top && pixel_left == block[i].Left && block[i].Exists == 1 {
pixel[pixel_left][pixel_top] = block[i].Color
}
}
}
}
//strデータに、全位置情報を格納
for pixel_top := 0; pixel_top < 32; pixel_top++ {
for pixel_left := 0; pixel_left < 22; pixel_left++ {
str += pixel[pixel_left][pixel_top]
}
str += "\n"
}
return str
}
// 接触判定
func ballHitCheck(ball Ball, bar Bar, block []Block) (Ball, []Block) {
ball.Left += ball.Speed_x
ball.Top += ball.Speed_y
//左右の壁と接触判定
if ball.Left < 1 || ball.Left > 20 {
ball.Speed_x = -ball.Speed_x
}
//上下の壁と接触判定
if ball.Top < 1 || ball.Top > 30 {
ball.Speed_y = -ball.Speed_y
}
//棒とボールの接触判定
if ball.Top > float64(bar.Top) && ball.Top <= float64(bar.Top+bar.H) && ball.Left > float64(bar.Left) && ball.Left <= float64(bar.Left+bar.W) {
ball.Speed_y = -ball.Speed_y
}
//ボールとブロックの接触判定
for i, _ := range block {
if block[i].Exists == 1 && ball.Top > float64(block[i].Top) && ball.Top <= float64(block[i].Top+1) && ball.Left > float64(block[i].Left) && ball.Left <= float64(block[i].Left+1) {
ball.Speed_x = -ball.Speed_x
ball.Speed_y = -ball.Speed_y
block[i].Exists = 0
}
}
return ball, block
}