#Copyright (c) 2016 Fedor Kalugin #MIT License BEGIN { #USER SETTINGS #screen width and height, every "pixel" is 2 chars wide w=64 h=48 #default color mode, change at runtime by pressing 1-4 #1 = no color, chars only, fast drawing #2 = colored chars #3 = background color only #4 = background color with char textures colormode = 4 #INITIALIZATION srand() PROCINFO["sorted_in"] = "@ind_num_asc" buffer[w,h] ZBuffer[w] #ugly 2d array initialization sprite[0][0] delete sprite[0] reloadTimeLeft = 0; moveSpeed = 0.8 rotSpeed = 0.4 reloadTime = 10 score = 0 health = 100 moves = 1001 #key bindings EXIT_KEY = "q" MOVF_KEY = "w" MOVB_KEY = "s" MOVL_KEY = "a" MOVR_KEY = "d" ROTL_KEY = "j" ROTR_KEY = "l" FIRE_KEY = " " UWIN_KEY = "x" #initial player direction vector dirX = 0.0 dirY = -1.0 #camera plane perpendicular to direction vector planeX = -0.66 planeY = 0.0 #LEVEL DESIGN mapWidth=44 mapHeight=44 map =\ "55555555566666666665555566666666655556666666"\ "5........666.6.6.65.....6...66665....7666666"\ "5.8.8.8.............88..6...66667.8..7.....6"\ "5........66666.6.6888...6.7.6667..88.7.....6"\ "5.8.8.8......6.6.6668..66.7.667......7.....6"\ "5........555.666..668.....7.65...7555666.666"\ "5.8.8.8..5.5.6667.766555557.67..75...666.666"\ "5....5...5...667...766665...6..76677.666.666"\ "555.5566.555.67.....76665.666..56667.66..666"\ "668.8666.....67..8..76665.....766667.6...666"\ "67...77666666667...766665....7666667....6666"\ "67....7666666666555666666777..566667...66666"\ "7......6777777777777777666665..56667..666666"\ "7..77..5...............6666665..5667.6666666"\ "67776..5...............66666665..767.6666666"\ "6666...5..8.........8..666666665.567.6666666"\ "6....8.5...............66666666...67..666666"\ "6.66.8.5...............6666667.....7...66666"\ "666....5.....33.33.....6666668...........666"\ "66...665.....3...3.....6666667.....7576...66"\ "6...6665.....3...3.....66666666...66666...66"\ "6.666665.....3...3.....666666665.5655666.666"\ "6.66...5.....33333.....6665666.5.55.5666.666"\ "6.6....5...............655.556......5666.666"\ "6.6....5...............6.....55...556666.666"\ "6......5..8.........8..6..........66666...66"\ "6555...5...............6.....66...566666.666"\ "65.....5...............655.55666...56666.666"\ "65.....56666668.8666666665.5666656566666.666"\ "65...6666666668.8666666555.5555555566666.666"\ "5...66666666668.8666666...........56666...66"\ "7.7666666666668.8666666...........5666.....6"\ "7.7666666666668.8666666............566.....6"\ "7.7666666666668.8666666............566.....6"\ "7.76........666.6666666............5666...66"\ "7.76...88888666.666666655555.......56666.666"\ "7.73...8...8666.6666666....5..............66"\ "7..........8666.6666666....5.......56668.866"\ "7773...8...8666.6666666....5.......5668...86"\ "6676...88888666............3.......568.....8"\ "6676........66666666665...........7668.....8"\ "66666666666666666666665555537777776668.....8"\ "666666666666666666666666666666666666668...86"\ "66666666666666666666666666666666666666688866" ceilingTex = "==" ceilingColor = 1 ceilingIsBright = 1 floorTex = "__" floorColor = 4 floorIsBright = 0 monsterTex = "OOMMZZ[]FuLL" monsterColor = 2 bulletTex = "**" bulletColor = 3 bulletIsBright = 0 wallTex="kkrrggyybbmmccww" #initial player position posX = 37.5 posY = 9.5 for (i=0; i < 25; i++) spawnMonster() #ENTERING MAIN LOOP main() #EXITING print "\n" if(health <= 0) print "GAME OVER! YOU LOSE!" else if (moves == -1) print "YOU WIN! YOUR SCORE: " score else print "You quit. Progress was not saved." print "Credits: Fedor 'TheMozg' Kalugin" print "https://github.com/TheMozg/awk-raycaster" print "Gameplay testing - Alex 'Yakojo' & Danya 'bogych97'" print "Go away!" } function addSprite(x,y, dX,dY, tex, color, isBright, type, uDiv, vDiv, vMove) { n = length(sprite)+1 sprite[n]["dirX"]=dX sprite[n]["dirY"]=dY sprite[n]["posX"]=x sprite[n]["posY"]=y sprite[n]["tex"]=tex sprite[n]["color"]=color sprite[n]["isBright"]=isBright sprite[n]["type"]=type sprite[n]["vDiv"]=vDiv sprite[n]["uDiv"]=uDiv sprite[n]["vMove"]=vMove } function spawnMonster(){ do{ x = mapWidth*rand() y = mapHeight*rand() } while ((worldMap(x, y) != 0) || (distPP(x,y,posX,posY) < 10)) isBright = int(2*rand()) n = int(rand()*int(length(monsterTex)/2))+1 tex = substr(monsterTex, n*2-1, 2) addSprite(x, y, dirX, dirY, tex, monsterColor, isBright, "monster", 1.0, 1.0, 0.0) } function shoot() { addSprite(posX, posY, dirX, dirY, bulletTex, bulletColor, bulletIsBright, "bullet", 3.0, 3.0, 0) n = length(sprite) moveSprite(n, 0.1) } function worldMap(y, x) { y = int(y) x = int(x) tile = substr(map, mapWidth*y+x+1, 1) if (tile == ".") return 0 return tile } function abs(x) { if(x<0) return -x return x } function fillBackground(){ for(x = 0; x < w; x++){ for(y = 0; y < h/2; y++){ buffer[x,y] = getPixel(ceilingColor, ceilingIsBright, colormode, ceilingTex); } for(y = int(h/2); y < h; y++){ buffer[x,y] = getPixel(floorColor, floorIsBright, colormode, floorTex); } } } function redraw(){ printf "\033[H" for(y = 0; y < h-2; y++){ str = "" for(x = 0; x < w; x++){ str = str buffer[x,y] } print str } drawUI() printf "\033[J" } function drawUI(){ if(colormode == 1 || colormode == 2){ fg_color = getANSICode(0, 0, 0); bg_color = getANSICode(0, 0, 1); } if(colormode == 3 || colormode == 4){ fg_color = getANSICode(8, 1, 0); bg_color = getANSICode(5, 0, 1); } help = toupper(MOVF_KEY)\ toupper(MOVL_KEY)\ toupper(MOVB_KEY)\ toupper(MOVR_KEY)\ " - move" help = help ", " toupper(ROTL_KEY) "/" toupper(ROTR_KEY)\ "- turn left/right (shift = quicker)" if(FIRE_KEY == " ") help = help ", " "spacebar" " - shoot" else help = help ", " toupper(FIRE_KEY) " - shoot" help = help ", 1-4 - change color mode" info = "ELEVATOR COMING " moves " | HP " health " | SCORE " score " | GUN " if(reloadTimeLeft == 0) info = info "READY" else info = info "RELOADING" if(inPosition()) if (moves != 0) info = info " | WAIT FOR ELEVATOR" else info = info " | PRESS " toupper(UWIN_KEY) " TO WIN" else info = info " | find an elevator and press " toupper(UWIN_KEY) while(length(help) < w*2) help = help " " while(length(info) < w*2) info = info " " print buildPixel(bg_color, fg_color, info) print buildPixel(bg_color, fg_color, help) } function getWallTex(color, isBright){ tex = substr(wallTex, color*2-1, 2) if(isBright == 1) return toupper(tex) return tex } function getANSICode(color, isBright, isBG){ if(color == 0) color = 10 else if (isBright==1) color+=60 if(isBG==1) color+=10 color+=30-1 return color } function buildPixel(bg_color, fg_color, text){ pixel = "\033[" bg_color ";" fg_color "m" text "\033[0m"; return pixel; } function getPixel(basecolor, isBright, colormode, tex){ color = "??"; if (colormode==1) { color = tex; } else if (colormode==2) { fg_color = getANSICode(basecolor, isBright, 0); bg_color = getANSICode(0, isBright, 1); color = buildPixel(bg_color, fg_color, tex); } else if (colormode==3) { tex = " "; fg_color = getANSICode(0, isBright, 0); bg_color = getANSICode(basecolor, isBright, 1); color = buildPixel(bg_color, fg_color, tex); } else if (colormode==4){ bg_color = getANSICode(basecolor, isBright, 1); if (isBright == 0) isBright = 1; else isBright = 0; fg_color = getANSICode(basecolor, isBright, 0); color = buildPixel(bg_color, fg_color, tex); } return color; } function distSP(i, x, y){ return distPP(sprite[i]["posX"], sprite[i]["posY"], x, y) } function distSS(i, j){ return distPP(sprite[i]["posX"], sprite[i]["posY"], sprite[j]["posX"], sprite[j]["posY"]) } function distPP(x1, y1, x2, y2){ return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) } function inPosition(){ if(posX <= 22 && posX >= 19.5 && posY >= 13 && posY <= 17) return 1 return 0 } function moveSprite(n, speed) { newPosX = sprite[n]["posX"]+sprite[n]["dirX"]*speed newPosY = sprite[n]["posY"]+sprite[n]["dirY"]*speed if(worldMap(newPosX,sprite[n]["posY"]) == 0) sprite[n]["posX"] = newPosX if(worldMap(sprite[n]["posX"],newPosY) == 0) sprite[n]["posY"] = newPosY return (worldMap(newPosX,newPosY) == 0) } function compareSprites(i1, v1, i2, v2){ return (v2["dist"] - v1["dist"]) } function main() { while (1) { if(moves != 0) moves-- if(reloadTimeLeft != 0) reloadTimeLeft-- if(health <= 0) break; fillBackground(); for(x = 0; x < w; x++) { #calculate ray position and direction cameraX = 2 * x / w - 1; #x-coordinate in camera space rayPosX = posX; rayPosY = posY; rayDirX = dirX + planeX * cameraX; rayDirY = dirY + planeY * cameraX; #which box of the map we're in mapX = int(rayPosX); mapY = int(rayPosY); #length of ray from current position to next x or y-side sideDistX=0.0; sideDistY=0.0; #length of ray from one x or y-side to next x or y-side if(rayDirX != 0) deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX)); else deltaDistX=999999; if(rayDirY != 0) deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY)); else deltaDistY=999999; perpWallDist=0.0; #what direction to step in x or y-direction (either +1 or -1) stepX=0; stepY=0; hit = 0; #was there a wall hit? side = 0; #was a NS or a EW wall hit? #calculate step and initial sideDist if (rayDirX < 0) { stepX = -1; sideDistX = (rayPosX - mapX) * deltaDistX; } else { stepX = 1; sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX; } if (rayDirY < 0) { stepY = -1; sideDistY = (rayPosY - mapY) * deltaDistY; } else { stepY = 1; sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY; } #perform DDA while (hit == 0) { #jump to next map square, OR in x-direction, OR in y-direction if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0; } else{ sideDistY += deltaDistY; mapY += stepY; side = 1; } #Check if ray has hit a wall if (worldMap(mapX,mapY) > 0) hit = 1; } #Calculate distance projected on camera direction if (side == 0) perpWallDist = abs( (mapX - rayPosX + int((1 - stepX) / 2)) / rayDirX); else perpWallDist = abs( (mapY - rayPosY + int((1 - stepY) / 2)) / rayDirY); #Calculate height of line to draw on screen if(perpWallDist == 0) lineHeight = h else lineHeight = abs(int(h / perpWallDist)); #calculate lowest and highest pixel to fill in current stripe drawStart = int(int(h / 2)-int(lineHeight / 2) ); if(drawStart < 0) drawStart = 0; drawEnd = int(lineHeight / 2 + h / 2); if(drawEnd >= h) drawEnd = h - 1; #choose wall color tex = getWallTex(worldMap(mapX,mapY), side); color = getPixel(worldMap(mapX,mapY), side, colormode, tex); #draw the pixels of the stripe as a vertical line for(y = drawStart; y <= drawEnd; y++) { buffer[x,y] = color } #set ZBuffer for sprite casting ZBuffer[x] = perpWallDist; #perpendicular distance is used } #sort sprites from far to close for(i in sprite) { sprite[i]["dist"] = distSP(i, posX, posY) } asort(sprite, sprite, "compareSprites") #after sorting the sprites, do the projection and draw them for(i in sprite) { #translate sprite position to relative to camera spriteX = sprite[i]["posX"] - posX; spriteY = sprite[i]["posY"] - posY; #transform sprite with the inverse camera matrix #required for correct matrix multiplication invDet = 1.0 / (planeX * dirY - dirX * planeY); transformX = invDet * (dirY * spriteX - dirX * spriteY); #this is actually the depth inside the screen, that what Z is in 3D transformY = invDet * (-planeY * spriteX + planeX * spriteY); spriteScreenX = int((w / 2) * (1 + transformX / transformY)); #controls moving the sprite up or down vMoveScreen = int(sprite[i]["vMove"] / transformY); #calculate height of the sprite on screen #using "transformY" instead of the real distance prevents fisheye spriteHeight = abs(int((h / transformY) / sprite[i]["vDiv"])); #calculate lowest and highest pixel to fill in current stripe drawStartY = int(int(-spriteHeight/2) + h/2 + vMoveScreen); if(drawStartY < 0) drawStartY = 0; drawEndY = int(int(spriteHeight / 2) + h/2 + vMoveScreen); if(drawEndY >= h) drawEndY = h - 1; #calculate width of the sprite spriteWidth = abs(int((h /transformY) / sprite[i]["uDiv"])); drawStartX = int(spriteScreenX-int(spriteWidth / 2)); if(drawStartX < 0) drawStartX = 0; drawEndX = int(int(spriteWidth / 2) + spriteScreenX); if(drawEndX >= w) drawEndX = w - 1; #loop through every vertical stripe of the sprite on screen for(stripe = drawStartX; stripe <= drawEndX; stripe++){ if(transformY > 0 && stripe >= 0 && stripe < w && transformY < ZBuffer[stripe]) for(y = drawStartY; y <= drawEndY; y++){ #for every pixel of the current stripe draw as circle if((stripe-spriteScreenX)*(stripe-spriteScreenX)+(y-h/2)*(y-h/2) <= spriteHeight*spriteHeight/4){ pixel = getPixel(sprite[i]["color"], sprite[i]["isBright"], colormode, sprite[i]["tex"]); buffer[stripe,y] = pixel; } } } } redraw(); system("stty -echo") #avoids depending on bash and gawk #by izabera from #bash on freenode cmd = "saved=$(stty -g); stty raw; var=$(dd bs=1 count=1 2>/dev/null); stty \"$saved\"; echo \"$var\"" cmd | getline input close(cmd) system("stty echo") if (input == MOVF_KEY || input == MOVB_KEY || input == MOVL_KEY || input == MOVR_KEY){ newPosX = posX - dirX * moveSpeed newPosY = posY - dirY * moveSpeed if(input == MOVF_KEY){ newPosX = posX + dirX * moveSpeed newPosY = posY + dirY * moveSpeed } if(input == MOVL_KEY){ newPosX = posX - dirY * moveSpeed newPosY = posY + dirX * moveSpeed } if(input == MOVR_KEY){ newPosX = posX + dirY * moveSpeed newPosY = posY - dirX * moveSpeed } ok = 1; for(i in sprite) { dist = distSP(i, newPosX, newPosY); if(dist < 0.51 && sprite[i]["type"] == "monster") ok = 0; } if(ok){ if(worldMap(newPosX,posY) == 0) posX = newPosX; if(worldMap(posX,newPosY) == 0) posY = newPosY; } } if (input == ROTL_KEY || input == ROTR_KEY || input == toupper(ROTL_KEY) || input == toupper(ROTR_KEY)){ rot = rotSpeed if(input == toupper(ROTL_KEY) || input == toupper(ROTR_KEY)) rot = rot*2 if (input == ROTR_KEY || input == toupper(ROTR_KEY)) rot = -rot #both camera direction and camera plane must be rotated oldDirX = dirX dirX = dirX * cos(rot) - dirY * sin(rot) dirY = oldDirX * sin(rot) + dirY * cos(rot) oldPlaneX = planeX planeX = planeX * cos(rot) - planeY * sin(rot) planeY = oldPlaneX * sin(rot) + planeY * cos(rot) } if(input == FIRE_KEY && reloadTimeLeft == 0){ shoot() reloadTimeLeft = reloadTime } if(input == "1") colormode = 1 if(input == "2") colormode = 2 if(input == "3") colormode = 3 if(input == "4") colormode = 4 if(input == EXIT_KEY) break if(input == UWIN_KEY && moves == 0 && inPosition()){ moves = -1 break } spawnCount = 0 for(i in sprite){ if(!(i in sprite)) continue if (sprite[i]["type"] == "monster"){ d = distSP(i, posX, posY) sprite[i]["dirX"] = (posX - sprite[i]["posX"]) / d sprite[i]["dirY"] = (posY - sprite[i]["posY"]) / d x = sprite[i]["posX"]+sprite[i]["dirX"]*0.5 y = sprite[i]["posY"]+sprite[i]["dirY"]*0.5 if(d > 0.7){ #prevent clustering of monsters ok = 1 for(j in sprite){ if(!(j in sprite)) continue if(sprite[j]["type"] == "monster" && i != j && distSP(j,x,y) < 1) ok = 0; } if(ok) moveSprite(i, 0.5) } else{ health -= 10 delete sprite[i] spawnCount++ } } } for(i in sprite){ if(!(i in sprite)) continue if (sprite[i]["type"] == "bullet"){ for(j in sprite){ if(!(j in sprite)) continue if (sprite[j]["type"] == "monster"){ if(distSS(i,j) < 1){ delete sprite[j] delete sprite[i] score += 100 reloadTimeLeft = 0 spawnCount++ break } } } if(i in sprite) if(!moveSprite(i, 1.2)) delete sprite[i] } } for (i=0; i < spawnCount*3; i++) { spawnMonster() } } }