ag.import("std") function resetPins() local head_x = 0 local head_y = 15 local spacing = 0.75 for i = 1, 10 do if (pins[i] ~= nil) then pins[i]:destroy() end end local n = 1 for row = 0, 3 do for j = 0, row do pins[n] = reference_pin:clone() pins[n]:setPosition(head_x - row * spacing / 2 + j * spacing, head_y + 0.866 * spacing * row, 1 - pin_minz) n = n + 1 end end end function setupStars() stars = ag.startList() local dist = 500 for i = 1, 500 do local rx = math.random() * math.pi * 2 local ry = (math.random() - 0.5) * math.pi * 0.8 local x = dist * math.cos(ry) * math.cos(rx) local y = dist * math.cos(ry) * math.sin(rx) local z = dist * math.sin(ry) local size = math.random(1, 6) ag.drawPoint(size, 1, 1, 1, x, y, z) end ag.endList() end function setupScore() current_frame = 1 current_ball = 1 score = {} for i = 1, 10 do local s = {} s[1] = -1 s[2] = -1 if (i == 10) then s[3] = -1 end s["tot"] = -1 score[i] = s end end function init_event() lane = ag.loadModelStatic("bowling_lane") local minx, miny, minz, maxx, maxy, maxz = lane:getAABB() local lane_bottom = minz local lane_top = maxz pins = {} reference_pin = ag.loadModel("bowling_pin", 1.0/4.3) minx, miny, minz, maxx, maxy, maxz = reference_pin:getAABB() pin_minz = minz pin_maxz = maxz pin_standing_position = lane_top - pin_minz local box = ag.createBoxStatic(1, 10, 0.1) box:setVisible(false) box:setPosition(0, 0, lane_bottom - (maxz - minz) - 0.4) reference_pin:setPosition(0, 0, lane_bottom - maxz - 0.1) reference_ball = ag.createSphere(0.25) reference_ball:setColor(0.7, 0.1, 0.9) reference_ball:setMass(10) reference_ball:setPosition(0, -2, -1.5) setupStars() setupScore() position_x = 0 target_x = 0 game_state = "waiting" mouse_moved = false newFrame() last_update_time = ag.elapsedTime() setCamera(last_update_time) end function setCamera(now) if (game_state == "launched") then local pct = (now - launch_time) / 2500 if (pct > 1) then pct = 1 end ag.setCamera(position_x + (-position_x * pct), -10 + 10 * pct, 5 + 5 * pct, target_x, 20, 0) else ag.setCamera(position_x, -10, 5, target_x, 20, 0) end end function update_event() local now = ag.elapsedTime() if (game_state == "waiting") then if (ag.isKeyDown("a") or ag.isKeyDown("left")) then position_x = position_x - (now - last_update_time) / 300 if (position_x < -2) then position_x = -2 end end if (ag.isKeyDown("d") or ag.isKeyDown("right")) then position_x = position_x + (now - last_update_time) / 300 if (position_x > 2) then position_x = 2 end end end last_update_time = now if (game_state == "launched") then if (now - launch_time > 7000) then endBall() end end setCamera(now) ag.callList(stars) end function mouse_motion_event(x, y, xrel, yrel) if (mouse_moved and game_state == "waiting") then target_x = target_x + xrel / 100 if (target_x < -2) then target_x = -2 elseif (target_x > 2) then target_x = 2 end end mouse_moved = true end function drawFrameScore(width, height, frame, fs) local size = 60 local ball_size = 24 local local_width = size if (frame == 10) then local_width = 80 end local base_x = size / 2 + size * (frame - 1) local base_y = height - size / 2 - size ag.drawRect(1, 0.6, 0, local_width, size, base_x + local_width / 2, base_y + size / 2) ag.drawRect(1, 0.6, 0, ball_size, ball_size, base_x + local_width - ball_size / 2, base_y + size - ball_size / 2) if (frame == 10) then ag.drawRect(1, 0.6, 0, ball_size, ball_size, base_x + local_width - ball_size * 1.5, base_y + size - ball_size / 2) end local balls = 2 if (frame == 10) then balls = 3 end for ball = 1, balls do if (fs[ball] ~= -1) then local ball_base_x = base_x + local_width - (1 + balls - ball) * ball_size + 7 local ball_base_y = base_y + size - ball_size + 7 ag.drawText(fs[ball], 1, 0.6, 0, 14, ball_base_x, ball_base_y) if (fs["tot"] ~= -1) then local w, h = ag.getTextSize(fs["tot"], 16) ag.drawText(fs["tot"], 1, 0.6, 0, 16, base_x + local_width - 10 - w, base_y + 10) end end end end function drawScore(width, height) ag.drawText("Score:", 1, 0.6, 0, 16, 30, height - 25) for i = 1, 10 do drawFrameScore(width, height, i, score[i]) end end function update_overlay_event(width, height) drawScore(width, height) if (game_state == "get_power" or game_state == "get_accuracy" or game_state == "launched") then drawPowerBar(width, height) end if (game_state == "get_accuracy" or game_state == "launched") then drawAccuracyBar(width, height) end if (game_state == "waiting") then local cursor_size = 8 ag.drawLine(0, 1, 0, width / 2, height / 2 - cursor_size, width / 2, height / 2 + cursor_size) ag.drawLine(0, 1, 0, width / 2 - cursor_size, height / 2, width / 2 + cursor_size, height / 2) end end function mousebutton_down_event(button, x, y) if (button == 1) then if (game_state == "waiting") then getPower() elseif (game_state == "get_power") then getAccuracy() elseif (game_state == "get_accuracy") then launchBall() end end end function getPower() game_state = "get_power" power = 0 power_last_time = ag.elapsedTime() power_direction = 1 end function getAccuracy() game_state = "get_accuracy" accuracy = 0 accuracy_last_time = ag.elapsedTime() accuracy_direction = 1 end function launchBall() game_state = "launched" launch_time = ag.elapsedTime() bowling_ball = reference_ball:clone() bowling_ball:setPosition(position_x, -10, 1.26) local fx = target_x + accuracy - position_x local fy = 30 -- should match setCamera()'s dy local d = math.sqrt(fx*fx + fy*fy) fx = fx / d fy = fy / d local force = 150000 force = force * 0.5 + force * 0.5 * power bowling_ball:addForce(fx * force, fy * force, 0) end function endBall() game_state = "waiting" bowling_ball:destroy() -- find all the pins that were knocked over local pin_count = 0 for i = 1, 10 do if (pins[i] ~= nil) then local x, y, z = pins[i]:getPosition() if (z < pin_standing_position - 0.05) then pins[i]:destroy() pins[i] = nil pin_count = pin_count + 1 end end end updateScore(pin_count) end function getNextBall(frame, ball) ball = ball + 1 if (ball > 2 and frame < 10) then frame = frame + 1 ball = 1 end return frame, ball end function getNextBallAfterStrike(frame, ball) if (frame == 10) then ball = ball + 1 else frame = frame + 1 end return frame, ball end function updateScore(pin_count) score[current_frame][current_ball] = pin_count -- update total score for f = 1, 10 do local prevscore = 0 if (f > 1) then prevscore = score[f-1]["tot"] end if (score[f][1] == 10) then -- strike local nf, nb = getNextBallAfterStrike(f, 1) if (score[nf][nb] == -1) then break else local nf2, nb2 if (score[nf][nb] == 10) then nf2, nb2 = getNextBallAfterStrike(nf, nb) else nf2, nb2 = getNextBall(nf, nb) end if (score[nf2][nb2] == -1) then break else score[f]["tot"] = prevscore + 10 + score[nf][nb] + score[nf2][nb2] end end elseif (score[f][1] == -1 or score[f][2] == -1) then break elseif (score[f][1] + score[f][2] == 10) then -- spare if (score[f+1][1] == -1) then break else score[f]["tot"] = prevscore + 10 + score[f+1][1] end else score[f]["tot"] = score[f][1] + score[f][2] end end if (pin_count == 10 and current_frame < 10) then current_frame = current_frame + 1 current_ball = 1 newFrame() elseif (current_frame == 10) then current_ball = current_ball + 1 if ((current_ball == 3 and (score[10][1] + score[10][2]) < 10) or (current_ball == 4)) then gameOver() end else current_ball = current_ball + 1 if (current_ball > 2) then current_ball = 1 current_frame = current_frame + 1 newFrame() end end end function newFrame() resetPins() end function gameOver() game_state = "gameover" end function drawPowerBar(width, height) local bar_width = 80 local bar_height = 400 if (game_state == "get_power") then local now = ag.elapsedTime() power = power + (now - power_last_time) / 250 * power_direction if (power > 1) then power = 1 power_direction = -1 elseif (power < 0) then power = 0 power_direction = 1 end power_last_time = now end ag.drawRect(1, 1, 1, bar_width, bar_height, width - 50 - bar_width / 2, height / 2) ag.fillRect(0.1, 1, 0.1, bar_width - 2, (bar_height - 2) * power, width - 50 - bar_width / 2, height / 2) end function drawAccuracyBar(width, height) local bar_width = 400 local bar_height = 60 if (game_state == "get_accuracy") then local now = ag.elapsedTime() accuracy = accuracy + (now - accuracy_last_time) / 250 * accuracy_direction if (accuracy > 1) then accuracy = 1 accuracy_direction = -1 elseif (accuracy < -1) then accuracy = -1 accuracy_direction = 1 end accuracy_last_time = now end ag.drawRect(1, 1, 1, bar_width, bar_height, width / 2, 50 + bar_height / 2) ag.drawLine(1, 1, 1, width / 2, 50, width / 2, 50 + bar_height) ag.fillRect(0.1, 1, 0.1, 3, bar_height - 2, width / 2 + (bar_width - 4) / 2 * accuracy, 50 + bar_height / 2) end