From 87562402d96a68127f3d38a47005d0625f7b0a49 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Wed, 3 Dec 2025 11:35:04 +0100 Subject: [PATCH] tic80 example for bomberman skeleton --- tic80/bomberman.lua | 235 ++++++++++++++++++++++++++++++++++++++++++++ tic80/bomberman.rb | 226 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 461 insertions(+) create mode 100644 tic80/bomberman.lua create mode 100644 tic80/bomberman.rb diff --git a/tic80/bomberman.lua b/tic80/bomberman.lua new file mode 100644 index 0000000..55e3c51 --- /dev/null +++ b/tic80/bomberman.lua @@ -0,0 +1,235 @@ +-- title: Simple Bomberman +-- author: Claude +-- script: lua + +-- constants +TILE_SIZE=16 +PLAYER_SIZE=12 +BOMB_TIMER=90 +EXPLOSION_TIMER=30 + +EMPTY=0 +SOLID_WALL=1 +BREAKABLE_WALL=2 + +-- player +playerX=16 +playerY=16 + +-- game objects +bombs={} +explosions={} + +-- enemy +enemy={ + x=208, + y=112, + dir=0, + moveTimer=0 +} + +-- 1=solid wall, 2=breakable wall +map={ + {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, + {1,0,0,2,2,2,0,2,0,2,2,2,0,0,1}, + {1,0,1,2,1,2,1,2,1,2,1,2,1,0,1}, + {1,2,2,2,0,2,2,0,2,2,0,2,2,2,1}, + {1,2,1,0,1,0,1,0,1,0,1,0,1,2,1}, + {1,2,2,2,0,2,2,0,2,2,0,2,2,2,1}, + {1,0,1,2,1,2,1,2,1,2,1,2,1,0,1}, + {1,0,0,2,2,2,0,2,0,2,2,2,0,0,1}, + {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, +} + +function TIC() + cls(0) + + -- handle input + newX=playerX + newY=playerY + + if btn(0) then newY=playerY-1 end + if btn(1) then newY=playerY+1 end + if btn(2) then newX=playerX-1 end + if btn(3) then newX=playerX+1 end + + if not isSolid(newX,playerY) then playerX=newX end + if not isSolid(playerX,newY) then playerY=newY end + + -- place bomb + if btnp(4) then + bombX=math.floor(playerX/TILE_SIZE)*TILE_SIZE + bombY=math.floor(playerY/TILE_SIZE)*TILE_SIZE + table.insert(bombs,{x=bombX,y=bombY,timer=BOMB_TIMER}) + end + + -- update bombs + for i=#bombs,1,-1 do + bombs[i].timer=bombs[i].timer-1 + if bombs[i].timer<=0 then + explode(bombs[i].x,bombs[i].y) + table.remove(bombs,i) + end + end + + -- update explosions + for i=#explosions,1,-1 do + explosions[i].timer=explosions[i].timer-1 + if explosions[i].timer<=0 then + table.remove(explosions,i) + end + end + + -- draw map + for row=1,9 do + for col=1,15 do + tile=map[row][col] + drawX=(col-1)*TILE_SIZE + drawY=(row-1)*TILE_SIZE + if tile==SOLID_WALL then + rect(drawX,drawY,TILE_SIZE,TILE_SIZE,8) + elseif tile==BREAKABLE_WALL then + rect(drawX,drawY,TILE_SIZE,TILE_SIZE,4) + end + end + end + + -- draw bombs + for i=1,#bombs do + bomb=bombs[i] + circ(bomb.x+8,bomb.y+8,6,2) + end + + -- draw explosions + for i=1,#explosions do + expl=explosions[i] + rect(expl.x,expl.y,TILE_SIZE,TILE_SIZE,6) + end + + -- update enemy + updateEnemy() + + -- draw player + rect(playerX,playerY,PLAYER_SIZE,PLAYER_SIZE,12) + + -- draw enemy + rect(enemy.x,enemy.y,PLAYER_SIZE,PLAYER_SIZE,2) + + -- check player death by explosion + for i=1,#explosions do + expl=explosions[i] + if playerXexpl.x and + playerYexpl.y then + playerX=16 + playerY=16 + end + end + + -- check player death by enemy + if playerXenemy.x and + playerYenemy.y then + playerX=16 + playerY=16 + end + + -- check enemy death by explosion + for i=1,#explosions do + expl=explosions[i] + if enemy.xexpl.x and + enemy.yexpl.y then + enemy.x=208 + enemy.y=112 + end + end + + print("ARROWS:MOVE A:BOMB",50,2,15) +end + +function isSolid(x,y) + gridLeft=math.floor(x/TILE_SIZE)+1 + gridTop=math.floor(y/TILE_SIZE)+1 + gridRight=math.floor((x+PLAYER_SIZE-1)/TILE_SIZE)+1 + gridBottom=math.floor((y+PLAYER_SIZE-1)/TILE_SIZE)+1 + + if gridLeft<1 or gridTop<1 or gridRight>15 or gridBottom>9 then + return true + end + if map[gridTop][gridLeft]>=SOLID_WALL then return true end + if map[gridTop][gridRight]>=SOLID_WALL then return true end + if map[gridBottom][gridLeft]>=SOLID_WALL then return true end + if map[gridBottom][gridRight]>=SOLID_WALL then return true end + return false +end + +function updateEnemy() + enemy.moveTimer=enemy.moveTimer+1 + if enemy.moveTimer<3 then return end + enemy.moveTimer=0 + + -- try current direction + dirs={{0,-1},{0,1},{-1,0},{1,0}} + dx=dirs[enemy.dir+1] and dirs[enemy.dir+1][1] or 0 + dy=dirs[enemy.dir+1] and dirs[enemy.dir+1][2] or 0 + + newEnemyX=enemy.x+dx + newEnemyY=enemy.y+dy + + if not isSolidForEnemy(newEnemyX,newEnemyY) and math.random()>0.1 then + enemy.x=newEnemyX + enemy.y=newEnemyY + else + enemy.dir=math.random(0,3) + end +end + +function isSolidForEnemy(x,y) + gridLeft=math.floor(x/TILE_SIZE)+1 + gridTop=math.floor(y/TILE_SIZE)+1 + gridRight=math.floor((x+PLAYER_SIZE-1)/TILE_SIZE)+1 + gridBottom=math.floor((y+PLAYER_SIZE-1)/TILE_SIZE)+1 + + if gridLeft<1 or gridTop<1 or gridRight>15 or gridBottom>9 then + return true + end + if map[gridTop][gridLeft]>=SOLID_WALL then return true end + if map[gridTop][gridRight]>=SOLID_WALL then return true end + if map[gridBottom][gridLeft]>=SOLID_WALL then return true end + if map[gridBottom][gridRight]>=SOLID_WALL then return true end + return false +end + +function explode(bombX,bombY) + table.insert(explosions,{x=bombX,y=bombY,timer=EXPLOSION_TIMER}) + + -- horizontal explosion + for dir=-1,1,2 do + explX=bombX+dir*TILE_SIZE + gridX=math.floor(explX/TILE_SIZE)+1 + gridY=math.floor(bombY/TILE_SIZE)+1 + if gridX>=1 and gridX<=15 then + tile=map[gridY][gridX] + if tile==EMPTY then + table.insert(explosions,{x=explX,y=bombY,timer=EXPLOSION_TIMER}) + elseif tile==BREAKABLE_WALL then + map[gridY][gridX]=EMPTY + table.insert(explosions,{x=explX,y=bombY,timer=EXPLOSION_TIMER}) + end + end + end + + -- vertical explosion + for dir=-1,1,2 do + explY=bombY+dir*TILE_SIZE + gridX=math.floor(bombX/TILE_SIZE)+1 + gridY=math.floor(explY/TILE_SIZE)+1 + if gridY>=1 and gridY<=9 then + tile=map[gridY][gridX] + if tile==EMPTY then + table.insert(explosions,{x=bombX,y=explY,timer=EXPLOSION_TIMER}) + elseif tile==BREAKABLE_WALL then + map[gridY][gridX]=EMPTY + table.insert(explosions,{x=bombX,y=explY,timer=EXPLOSION_TIMER}) + end + end + end +end diff --git a/tic80/bomberman.rb b/tic80/bomberman.rb new file mode 100644 index 0000000..aa8a42a --- /dev/null +++ b/tic80/bomberman.rb @@ -0,0 +1,226 @@ +# title: Simple Bomberman +# author: Claude +# script: ruby + +# constants +TILE_SIZE = 16 +PLAYER_SIZE = 12 +BOMB_TIMER = 90 +EXPLOSION_TIMER = 30 + +EMPTY = 0 +SOLID_WALL = 1 +BREAKABLE_WALL = 2 + +# player +$playerX = 16 +$playerY = 16 + +# game objects +$bombs = [] +$explosions = [] + +# enemy +$enemy = { + x: 208, + y: 112, + dir: 0, + moveTimer: 0 +} + +# 1=solid wall, 2=breakable wall +$map = [ + [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], + [1,0,0,2,2,2,0,2,0,2,2,2,0,0,1], + [1,0,1,2,1,2,1,2,1,2,1,2,1,0,1], + [1,2,2,2,0,2,2,0,2,2,0,2,2,2,1], + [1,2,1,0,1,0,1,0,1,0,1,0,1,2,1], + [1,2,2,2,0,2,2,0,2,2,0,2,2,2,1], + [1,0,1,2,1,2,1,2,1,2,1,2,1,0,1], + [1,0,0,2,2,2,0,2,0,2,2,2,0,0,1], + [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] +] + +def TIC + cls(0) + + # handle input + newX = $playerX + newY = $playerY + + newY = $playerY - 1 if btn(0) + newY = $playerY + 1 if btn(1) + newX = $playerX - 1 if btn(2) + newX = $playerX + 1 if btn(3) + + $playerX = newX unless solid?(newX, $playerY) + $playerY = newY unless solid?($playerX, newY) + + # place bomb + if btnp(4) + bombX = ($playerX / TILE_SIZE).floor * TILE_SIZE + bombY = ($playerY / TILE_SIZE).floor * TILE_SIZE + $bombs << { x: bombX, y: bombY, timer: BOMB_TIMER } + end + + # update bombs + $bombs.reverse_each do |bomb| + bomb[:timer] -= 1 + if bomb[:timer] <= 0 + explode(bomb[:x], bomb[:y]) + $bombs.delete(bomb) + end + end + + # update explosions + $explosions.reverse_each do |expl| + expl[:timer] -= 1 + $explosions.delete(expl) if expl[:timer] <= 0 + end + + # draw map + (0..8).each do |row| + (0..14).each do |col| + tile = $map[row][col] + drawX = col * TILE_SIZE + drawY = row * TILE_SIZE + if tile == SOLID_WALL + rect(drawX, drawY, TILE_SIZE, TILE_SIZE, 8) + elsif tile == BREAKABLE_WALL + rect(drawX, drawY, TILE_SIZE, TILE_SIZE, 4) + end + end + end + + # draw bombs + $bombs.each do |bomb| + circ(bomb[:x] + 8, bomb[:y] + 8, 6, 2) + end + + # draw explosions + $explosions.each do |expl| + rect(expl[:x], expl[:y], TILE_SIZE, TILE_SIZE, 6) + end + + # update enemy + update_enemy + + # draw player + rect($playerX, $playerY, PLAYER_SIZE, PLAYER_SIZE, 12) + + # draw enemy + rect($enemy[:x], $enemy[:y], PLAYER_SIZE, PLAYER_SIZE, 2) + + # check player death by explosion + $explosions.each do |expl| + if $playerX < expl[:x] + TILE_SIZE && $playerX + PLAYER_SIZE > expl[:x] && + $playerY < expl[:y] + TILE_SIZE && $playerY + PLAYER_SIZE > expl[:y] + $playerX = 16 + $playerY = 16 + end + end + + # check player death by enemy + if $playerX < $enemy[:x] + PLAYER_SIZE && $playerX + PLAYER_SIZE > $enemy[:x] && + $playerY < $enemy[:y] + PLAYER_SIZE && $playerY + PLAYER_SIZE > $enemy[:y] + $playerX = 16 + $playerY = 16 + end + + # check enemy death by explosion + $explosions.each do |expl| + if $enemy[:x] < expl[:x] + TILE_SIZE && $enemy[:x] + PLAYER_SIZE > expl[:x] && + $enemy[:y] < expl[:y] + TILE_SIZE && $enemy[:y] + PLAYER_SIZE > expl[:y] + $enemy[:x] = 208 + $enemy[:y] = 112 + end + end + + print("ARROWS:MOVE A:BOMB", 50, 2, 15) +end + +def solid?(x, y) + gridLeft = (x / TILE_SIZE).floor + gridTop = (y / TILE_SIZE).floor + gridRight = ((x + PLAYER_SIZE - 1) / TILE_SIZE).floor + gridBottom = ((y + PLAYER_SIZE - 1) / TILE_SIZE).floor + + return true if gridLeft < 0 || gridTop < 0 || gridRight > 14 || gridBottom > 8 + return true if $map[gridTop][gridLeft] >= SOLID_WALL + return true if $map[gridTop][gridRight] >= SOLID_WALL + return true if $map[gridBottom][gridLeft] >= SOLID_WALL + return true if $map[gridBottom][gridRight] >= SOLID_WALL + false +end + +def update_enemy + $enemy[:moveTimer] += 1 + return if $enemy[:moveTimer] < 3 + $enemy[:moveTimer] = 0 + + # try current direction + dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]] + dir = $enemy[:dir] + dx = dirs[dir] ? dirs[dir][0] : 0 + dy = dirs[dir] ? dirs[dir][1] : 0 + + newEnemyX = $enemy[:x] + dx + newEnemyY = $enemy[:y] + dy + + if !solid_for_enemy?(newEnemyX, newEnemyY) && rand > 0.1 + $enemy[:x] = newEnemyX + $enemy[:y] = newEnemyY + else + $enemy[:dir] = rand(4) + end +end + +def solid_for_enemy?(x, y) + gridLeft = (x / TILE_SIZE).floor + gridTop = (y / TILE_SIZE).floor + gridRight = ((x + PLAYER_SIZE - 1) / TILE_SIZE).floor + gridBottom = ((y + PLAYER_SIZE - 1) / TILE_SIZE).floor + + return true if gridLeft < 0 || gridTop < 0 || gridRight > 14 || gridBottom > 8 + return true if $map[gridTop][gridLeft] >= SOLID_WALL + return true if $map[gridTop][gridRight] >= SOLID_WALL + return true if $map[gridBottom][gridLeft] >= SOLID_WALL + return true if $map[gridBottom][gridRight] >= SOLID_WALL + false +end + +def explode(bombX, bombY) + $explosions << { x: bombX, y: bombY, timer: EXPLOSION_TIMER } + + # horizontal explosion + [-1, 1].each do |dir| + explX = bombX + dir * TILE_SIZE + gridX = (explX / TILE_SIZE).floor + gridY = (bombY / TILE_SIZE).floor + if gridX >= 0 && gridX <= 14 + tile = $map[gridY][gridX] + if tile == EMPTY + $explosions << { x: explX, y: bombY, timer: EXPLOSION_TIMER } + elsif tile == BREAKABLE_WALL + $map[gridY][gridX] = EMPTY + $explosions << { x: explX, y: bombY, timer: EXPLOSION_TIMER } + end + end + end + + # vertical explosion + [-1, 1].each do |dir| + explY = bombY + dir * TILE_SIZE + gridX = (bombX / TILE_SIZE).floor + gridY = (explY / TILE_SIZE).floor + if gridY >= 0 && gridY <= 8 + tile = $map[gridY][gridX] + if tile == EMPTY + $explosions << { x: bombX, y: explY, timer: EXPLOSION_TIMER } + elsif tile == BREAKABLE_WALL + $map[gridY][gridX] = EMPTY + $explosions << { x: bombX, y: explY, timer: EXPLOSION_TIMER } + end + end + end +end